diff --git a/frontends/nextjs/.storybook/main.ts b/frontends/nextjs/.storybook/main.ts
new file mode 100644
index 000000000..debb4b3e2
--- /dev/null
+++ b/frontends/nextjs/.storybook/main.ts
@@ -0,0 +1,40 @@
+import type { StorybookConfig } from '@storybook/react-vite'
+import { mergeConfig } from 'vite'
+import path from 'path'
+
+const config: StorybookConfig = {
+ stories: [
+ '../src/**/*.mdx',
+ '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
+ // Include Lua package stories
+ '../src/storybook/**/*.stories.@(js|jsx|mjs|ts|tsx)',
+ ],
+ addons: [
+ '@storybook/addon-essentials',
+ '@storybook/addon-interactions',
+ ],
+ framework: {
+ name: '@storybook/react-vite',
+ options: {},
+ },
+ docs: {},
+ staticDirs: [
+ // Serve package static content
+ { from: '../../packages', to: '/packages' },
+ ],
+ async viteFinal(config) {
+ return mergeConfig(config, {
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, '../src'),
+ },
+ },
+ // Mock Node.js modules that aren't available in browser
+ define: {
+ 'process.env': {},
+ },
+ })
+ },
+}
+
+export default config
diff --git a/frontends/nextjs/.storybook/preview.tsx b/frontends/nextjs/.storybook/preview.tsx
new file mode 100644
index 000000000..faf043d97
--- /dev/null
+++ b/frontends/nextjs/.storybook/preview.tsx
@@ -0,0 +1,43 @@
+import type { Preview } from '@storybook/react'
+import React from 'react'
+
+// Import global styles
+import '../src/app/globals.scss'
+
+// Create a mock context provider for Storybook
+const MockProviders: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ return <>{children}>
+}
+
+const preview: Preview = {
+ parameters: {
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/i,
+ },
+ },
+ // Default viewport
+ viewport: {
+ defaultViewport: 'desktop',
+ },
+ // Background options
+ backgrounds: {
+ default: 'light',
+ values: [
+ { name: 'light', value: '#ffffff' },
+ { name: 'dark', value: '#1a1a2e' },
+ { name: 'canvas', value: '#f5f5f5' },
+ ],
+ },
+ },
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+}
+
+export default preview
diff --git a/frontends/nextjs/next.config.ts b/frontends/nextjs/next.config.ts
index 825344c8d..b2496bf86 100644
--- a/frontends/nextjs/next.config.ts
+++ b/frontends/nextjs/next.config.ts
@@ -99,7 +99,10 @@ const nextConfig: NextConfig = {
path: false,
crypto: false,
stream: false,
+ 'stream/promises': false,
os: false,
+ buffer: false,
+ util: false,
}
}
diff --git a/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts b/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts
new file mode 100644
index 000000000..8d46312f9
--- /dev/null
+++ b/frontends/nextjs/src/storybook/mocks/lua-engine-mock.ts
@@ -0,0 +1,148 @@
+/**
+ * Mock Lua Engine for Storybook
+ *
+ * Since fengari requires a real Lua VM, we mock the execution
+ * by interpreting the JSON-like component trees that Lua packages produce.
+ * This allows Storybook to render Lua packages without running actual Lua code.
+ */
+
+import type { LuaUIComponent } from '@/lib/lua/ui/types/lua-ui-package'
+import type { JsonValue } from '@/types/utility-types'
+
+export interface MockLuaContext {
+ user?: {
+ id: string
+ username: string
+ level: number
+ email?: string
+ }
+ nerdMode?: boolean
+ theme?: 'light' | 'dark'
+ [key: string]: JsonValue | undefined
+}
+
+export interface MockLuaScript {
+ id: string
+ name: string
+ code: string
+ /**
+ * Pre-computed output for this script given specific contexts.
+ * Keys are JSON-stringified context objects.
+ */
+ mockOutputs?: Record
+ /**
+ * Default output to use when no matching mock is found
+ */
+ defaultOutput?: LuaUIComponent
+}
+
+export interface MockPackageManifest {
+ id: string
+ name: string
+ version: string
+ description: string
+ category: string
+ minLevel: number
+ scripts: MockLuaScript[]
+ /**
+ * Pre-rendered component trees for different contexts
+ */
+ renderedPages?: Record
+}
+
+/**
+ * Create a mock user context for Storybook stories
+ */
+export function createMockUser(overrides?: Partial): MockLuaContext['user'] {
+ return {
+ id: 'mock-user-1',
+ username: 'storybook_user',
+ level: 4,
+ email: 'user@example.com',
+ ...overrides,
+ }
+}
+
+/**
+ * Create a full mock context for Lua package rendering
+ */
+export function createMockContext(overrides?: Partial): MockLuaContext {
+ return {
+ user: createMockUser(overrides?.user),
+ nerdMode: false,
+ theme: 'light',
+ ...overrides,
+ }
+}
+
+/**
+ * Mock Lua execution result
+ */
+export interface MockLuaResult {
+ success: boolean
+ result?: LuaUIComponent
+ error?: string
+ logs: string[]
+}
+
+/**
+ * Execute a mock Lua script with the given context
+ * Returns pre-computed output based on the context
+ */
+export function executeMockLuaScript(
+ script: MockLuaScript,
+ context: MockLuaContext
+): MockLuaResult {
+ const contextKey = JSON.stringify(context)
+
+ // Check for exact context match
+ if (script.mockOutputs && script.mockOutputs[contextKey]) {
+ return {
+ success: true,
+ result: script.mockOutputs[contextKey],
+ logs: [],
+ }
+ }
+
+ // Fall back to default output
+ if (script.defaultOutput) {
+ return {
+ success: true,
+ result: script.defaultOutput,
+ logs: [],
+ }
+ }
+
+ return {
+ success: false,
+ error: `No mock output defined for script: ${script.id}`,
+ logs: [`Mock script ${script.id} has no output for context`],
+ }
+}
+
+/**
+ * Registry of mock package outputs
+ * This is populated by individual package mock files
+ */
+export const mockPackageRegistry: Map = new Map()
+
+/**
+ * Register a mock package for Storybook rendering
+ */
+export function registerMockPackage(manifest: MockPackageManifest): void {
+ mockPackageRegistry.set(manifest.id, manifest)
+}
+
+/**
+ * Get a registered mock package
+ */
+export function getMockPackage(packageId: string): MockPackageManifest | undefined {
+ return mockPackageRegistry.get(packageId)
+}
+
+/**
+ * List all registered mock packages
+ */
+export function listMockPackages(): MockPackageManifest[] {
+ return Array.from(mockPackageRegistry.values())
+}