@@ -115,7 +46,7 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
Application Builder
- {nerdMode
+ {nerdMode
? "Design your application declaratively. Define schemas, create workflows, and write Lua scripts."
: "Build your application visually. Configure pages, users, and data models with simple forms."
}
@@ -124,25 +55,13 @@ export function Level4({ user, onLogout, onNavigate, onPreview }: Level4Props) {
{
- const newConfig = { ...appConfig, schemas }
- setAppConfig(newConfig)
- await Database.setAppConfig(newConfig)
- }}
- onWorkflowsChange={async (workflows) => {
- const newConfig = { ...appConfig, workflows }
- setAppConfig(newConfig)
- await Database.setAppConfig(newConfig)
- }}
- onLuaScriptsChange={async (scripts) => {
- const newConfig = { ...appConfig, luaScripts: scripts }
- setAppConfig(newConfig)
- await Database.setAppConfig(newConfig)
- }}
+ nerdMode={nerdMode}
+ onSchemasChange={handleSchemasChange}
+ onWorkflowsChange={handleWorkflowsChange}
+ onLuaScriptsChange={handleLuaScriptsChange}
/>
-
+
{nerdMode && (
diff --git a/frontends/nextjs/src/components/level/levels/hooks/useLevel4AppState.ts b/frontends/nextjs/src/components/level/levels/hooks/useLevel4AppState.ts
new file mode 100644
index 000000000..d2c146ac8
--- /dev/null
+++ b/frontends/nextjs/src/components/level/levels/hooks/useLevel4AppState.ts
@@ -0,0 +1,127 @@
+import { useCallback, useEffect, useState } from 'react'
+import { toast } from 'sonner'
+import { useKV } from '@github/spark/hooks'
+
+import { Database } from '@/lib/database'
+import type { AppConfiguration } from '@/lib/level-types'
+import { seedDatabase } from '@/lib/seed-data'
+
+type ConfigUpdater = (config: AppConfiguration) => AppConfiguration
+
+const createDefaultConfig = (): AppConfiguration => ({
+ id: 'app_001',
+ name: 'MetaBuilder App',
+ schemas: [],
+ workflows: [],
+ luaScripts: [],
+ pages: [],
+ theme: {
+ colors: {},
+ fonts: {},
+ },
+})
+
+const persistConfig = async (config: AppConfiguration, setConfig: (value: AppConfiguration) => void) => {
+ setConfig(config)
+ await Database.setAppConfig(config)
+}
+
+export const useLevel4AppState = () => {
+ const [appConfig, setAppConfig] = useState
(null)
+ const [isLoading, setIsLoading] = useState(true)
+ const [nerdMode, setNerdMode] = useKV('level4-nerd-mode', false)
+
+ useEffect(() => {
+ const loadConfig = async () => {
+ await seedDatabase()
+
+ const config = await Database.getAppConfig()
+ if (config) {
+ setAppConfig(config)
+ } else {
+ const defaultConfig = createDefaultConfig()
+ await persistConfig(defaultConfig, setAppConfig)
+ }
+ setIsLoading(false)
+ }
+
+ void loadConfig()
+ }, [])
+
+ const updateConfig = useCallback(
+ async (updater: ConfigUpdater) => {
+ if (!appConfig) return
+
+ const updatedConfig = updater(appConfig)
+ await persistConfig(updatedConfig, setAppConfig)
+ },
+ [appConfig]
+ )
+
+ const handleExportConfig = useCallback(async () => {
+ const dataStr = await Database.exportDatabase()
+ const dataBlob = new Blob([dataStr], { type: 'application/json' })
+ const url = URL.createObjectURL(dataBlob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = 'database-export.json'
+ link.click()
+ toast.success('Database exported')
+ }, [])
+
+ const handleImportConfig = useCallback(() => {
+ const input = document.createElement('input')
+ input.type = 'file'
+ input.accept = 'application/json'
+ input.onchange = async (e) => {
+ const file = (e.target as HTMLInputElement).files?.[0]
+ if (!file) return
+
+ const text = await file.text()
+ try {
+ await Database.importDatabase(text)
+ const newConfig = await Database.getAppConfig()
+ if (newConfig) {
+ await persistConfig(newConfig, setAppConfig)
+ }
+ toast.success('Database imported successfully')
+ } catch (error) {
+ toast.error('Invalid database file')
+ }
+ }
+ input.click()
+ }, [])
+
+ const toggleNerdMode = useCallback(() => {
+ const nextValue = !nerdMode
+ setNerdMode(nextValue)
+ toast.info(nextValue ? 'Nerd Mode enabled' : 'Nerd Mode disabled')
+ }, [nerdMode, setNerdMode])
+
+ const handleSchemasChange = useCallback(
+ async (schemas: AppConfiguration['schemas']) => updateConfig((config) => ({ ...config, schemas })),
+ [updateConfig]
+ )
+
+ const handleWorkflowsChange = useCallback(
+ async (workflows: AppConfiguration['workflows']) => updateConfig((config) => ({ ...config, workflows })),
+ [updateConfig]
+ )
+
+ const handleLuaScriptsChange = useCallback(
+ async (luaScripts: AppConfiguration['luaScripts']) => updateConfig((config) => ({ ...config, luaScripts })),
+ [updateConfig]
+ )
+
+ return {
+ appConfig,
+ isLoading,
+ nerdMode: nerdMode || false,
+ handleExportConfig,
+ handleImportConfig,
+ toggleNerdMode,
+ handleSchemasChange,
+ handleWorkflowsChange,
+ handleLuaScriptsChange,
+ }
+}
diff --git a/frontends/nextjs/src/types/dbal.d.ts b/frontends/nextjs/src/types/dbal.d.ts
index 178b52c5c..0484a463b 100644
--- a/frontends/nextjs/src/types/dbal.d.ts
+++ b/frontends/nextjs/src/types/dbal.d.ts
@@ -2,153 +2,14 @@
* DBAL type stubs
* These types are used when the full DBAL module is not available
* The actual implementation lives in ../../dbal/development/src
+ *
+ * The declarations are split into focused modules to keep each file
+ * under the large-file threshold and easier to maintain.
*/
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-declare module '@/dbal/development/src' {
- export interface DBALConfig {
- mode?: 'development' | 'production'
- adapter?: string
- auth?: any
- database?: {
- url?: string
- }
- security?: {
- sandbox?: 'strict' | 'permissive' | 'disabled'
- enableAuditLog?: boolean
- }
- [key: string]: any
- }
-
- export interface DBALUser {
- id: string
- email: string
- name?: string
- username?: string
- level?: number
- role?: string
- tenantId?: string
- createdAt?: number | string | Date
- [key: string]: any
- }
-
- export interface ListResult {
- data: T[]
- total?: number
- }
-
- export interface UsersAPI {
- list(): Promise>
- create(data: Partial): Promise
- update(id: string, data: Partial): Promise
- delete(id: string): Promise
- }
-
- export class DBALClient {
- users: UsersAPI
- constructor(config: DBALConfig)
- query(sql: string, params?: unknown[]): Promise
- execute(sql: string, params?: unknown[]): Promise
- capabilities(): Promise>
- }
-
- export class DBALError extends Error {
- code: DBALErrorCode
- message: string
- constructor(message: string, code: DBALErrorCode)
- }
-
- export enum DBALErrorCode {
- UNKNOWN = 'UNKNOWN',
- CONNECTION_ERROR = 'CONNECTION_ERROR',
- QUERY_ERROR = 'QUERY_ERROR',
- VALIDATION_ERROR = 'VALIDATION_ERROR',
- }
-}
-
-declare module '@/dbal/development/src/core/types' {
- export interface User {
- id: string
- email: string
- name?: string
- level: number
- tenantId: string
- }
-}
-
-declare module '@/dbal/development/src/core/tenant-context' {
- export interface TenantContext {
- tenantId: string
- userId?: string
- }
-
- export class InMemoryTenantManager {
- setCurrentTenant(tenantId: string): void
- getCurrentTenant(): string | null
- createTenant(id: string, metadata: Record, ...args: any[]): Promise
- getTenantContext(tenantId: string, userId?: string): Promise
- }
-}
-
-declare module '@/dbal/development/src/core/kv-store' {
- import type { TenantContext } from '@/dbal/development/src/core/tenant-context'
-
- export class InMemoryKVStore {
- get(key: string, context?: TenantContext): Promise
- set(key: string, value: T, context?: TenantContext, ttl?: number): Promise
- delete(key: string, context?: TenantContext): Promise
- listAdd(key: string, items: any[], context?: TenantContext): Promise
- listGet(key: string, context?: TenantContext, start?: number, end?: number): Promise
- }
-}
-
-declare module '@/dbal/development/src/blob' {
- export interface BlobStorageConfig {
- type: 'filesystem' | 'memory' | 's3'
- basePath?: string
- }
-
- export interface BlobMetadata {
- contentType?: string
- size?: number
- lastModified?: Date
- [key: string]: any
- }
-
- export interface BlobListItem {
- key: string
- [key: string]: any
- }
-
- export interface BlobListResult {
- items: BlobListItem[]
- [key: string]: any
- }
-
- export interface BlobStorage {
- upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise
- download(key: string): Promise
- delete(key: string): Promise
- exists(key: string): Promise
- list(options?: { prefix?: string }): Promise
- getMetadata(key: string): Promise
- }
-
- export function createBlobStorage(config: BlobStorageConfig): BlobStorage
-}
-
-declare module '@/dbal/development/src/blob/tenant-aware-storage' {
- import type { BlobStorage, BlobMetadata, BlobListResult } from '@/dbal/development/src/blob'
- import type { InMemoryTenantManager } from '@/dbal/development/src/core/tenant-context'
-
- export class TenantAwareBlobStorage implements BlobStorage {
- constructor(storage: BlobStorage, tenantManager: InMemoryTenantManager, ...args: any[])
- upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise
- download(key: string): Promise
- delete(key: string): Promise
- exists(key: string): Promise
- list(options?: { prefix?: string }): Promise
- getMetadata(key: string): Promise
- }
-}
+///
+///
+///
+///
+///
+///
diff --git a/frontends/nextjs/src/types/dbal/blob.d.ts b/frontends/nextjs/src/types/dbal/blob.d.ts
new file mode 100644
index 000000000..09f14389a
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/blob.d.ts
@@ -0,0 +1,36 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+declare module '@/dbal/development/src/blob' {
+ export interface BlobStorageConfig {
+ type: 'filesystem' | 'memory' | 's3'
+ basePath?: string
+ }
+
+ export interface BlobMetadata {
+ contentType?: string
+ size?: number
+ lastModified?: Date
+ [key: string]: any
+ }
+
+ export interface BlobListItem {
+ key: string
+ [key: string]: any
+ }
+
+ export interface BlobListResult {
+ items: BlobListItem[]
+ [key: string]: any
+ }
+
+ export interface BlobStorage {
+ upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise
+ download(key: string): Promise
+ delete(key: string): Promise
+ exists(key: string): Promise
+ list(options?: { prefix?: string }): Promise
+ getMetadata(key: string): Promise
+ }
+
+ export function createBlobStorage(config: BlobStorageConfig): BlobStorage
+}
diff --git a/frontends/nextjs/src/types/dbal/core-config.d.ts b/frontends/nextjs/src/types/dbal/core-config.d.ts
new file mode 100644
index 000000000..976251d1c
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/core-config.d.ts
@@ -0,0 +1,66 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+/**
+ * Root DBAL module declarations.
+ * Split from the monolithic dbal.d.ts to keep each set of exports contained.
+ */
+declare module '@/dbal/development/src' {
+ export interface DBALConfig {
+ mode?: 'development' | 'production'
+ adapter?: string
+ auth?: any
+ database?: {
+ url?: string
+ }
+ security?: {
+ sandbox?: 'strict' | 'permissive' | 'disabled'
+ enableAuditLog?: boolean
+ }
+ [key: string]: any
+ }
+
+ export interface DBALUser {
+ id: string
+ email: string
+ name?: string
+ username?: string
+ level?: number
+ role?: string
+ tenantId?: string
+ createdAt?: number | string | Date
+ [key: string]: any
+ }
+
+ export interface ListResult {
+ data: T[]
+ total?: number
+ }
+
+ export interface UsersAPI {
+ list(): Promise>
+ create(data: Partial): Promise
+ update(id: string, data: Partial): Promise
+ delete(id: string): Promise
+ }
+
+ export class DBALClient {
+ users: UsersAPI
+ constructor(config: DBALConfig)
+ query(sql: string, params?: unknown[]): Promise
+ execute(sql: string, params?: unknown[]): Promise
+ capabilities(): Promise>
+ }
+
+ export class DBALError extends Error {
+ code: DBALErrorCode
+ message: string
+ constructor(message: string, code: DBALErrorCode)
+ }
+
+ export enum DBALErrorCode {
+ UNKNOWN = 'UNKNOWN',
+ CONNECTION_ERROR = 'CONNECTION_ERROR',
+ QUERY_ERROR = 'QUERY_ERROR',
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
+ }
+}
diff --git a/frontends/nextjs/src/types/dbal/core-types.d.ts b/frontends/nextjs/src/types/dbal/core-types.d.ts
new file mode 100644
index 000000000..3c564c162
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/core-types.d.ts
@@ -0,0 +1,11 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+declare module '@/dbal/development/src/core/types' {
+ export interface User {
+ id: string
+ email: string
+ name?: string
+ level: number
+ tenantId: string
+ }
+}
diff --git a/frontends/nextjs/src/types/dbal/kv-store.d.ts b/frontends/nextjs/src/types/dbal/kv-store.d.ts
new file mode 100644
index 000000000..d9d2a152a
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/kv-store.d.ts
@@ -0,0 +1,13 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+declare module '@/dbal/development/src/core/kv-store' {
+ import type { TenantContext } from '@/dbal/development/src/core/tenant-context'
+
+ export class InMemoryKVStore {
+ get(key: string, context?: TenantContext): Promise
+ set(key: string, value: T, context?: TenantContext, ttl?: number): Promise
+ delete(key: string, context?: TenantContext): Promise
+ listAdd(key: string, items: any[], context?: TenantContext): Promise
+ listGet(key: string, context?: TenantContext, start?: number, end?: number): Promise
+ }
+}
diff --git a/frontends/nextjs/src/types/dbal/tenant-aware-blob.d.ts b/frontends/nextjs/src/types/dbal/tenant-aware-blob.d.ts
new file mode 100644
index 000000000..812ea8bcd
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/tenant-aware-blob.d.ts
@@ -0,0 +1,16 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+declare module '@/dbal/development/src/blob/tenant-aware-storage' {
+ import type { BlobStorage, BlobMetadata, BlobListResult } from '@/dbal/development/src/blob'
+ import type { InMemoryTenantManager } from '@/dbal/development/src/core/tenant-context'
+
+ export class TenantAwareBlobStorage implements BlobStorage {
+ constructor(storage: BlobStorage, tenantManager: InMemoryTenantManager, ...args: any[])
+ upload(key: string, data: Buffer | string, metadata?: BlobMetadata): Promise
+ download(key: string): Promise
+ delete(key: string): Promise
+ exists(key: string): Promise
+ list(options?: { prefix?: string }): Promise
+ getMetadata(key: string): Promise
+ }
+}
diff --git a/frontends/nextjs/src/types/dbal/tenant-context.d.ts b/frontends/nextjs/src/types/dbal/tenant-context.d.ts
new file mode 100644
index 000000000..178152829
--- /dev/null
+++ b/frontends/nextjs/src/types/dbal/tenant-context.d.ts
@@ -0,0 +1,15 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
+declare module '@/dbal/development/src/core/tenant-context' {
+ export interface TenantContext {
+ tenantId: string
+ userId?: string
+ }
+
+ export class InMemoryTenantManager {
+ setCurrentTenant(tenantId: string): void
+ getCurrentTenant(): string | null
+ createTenant(id: string, metadata: Record, ...args: any[]): Promise
+ getTenantContext(tenantId: string, userId?: string): Promise
+ }
+}
diff --git a/tools/analysis/code/reports/large-ts-files.json b/tools/analysis/code/reports/large-ts-files.json
index 45a8bd46c..2e27fb85b 100644
--- a/tools/analysis/code/reports/large-ts-files.json
+++ b/tools/analysis/code/reports/large-ts-files.json
@@ -12,9 +12,9 @@
"out",
"tmp"
],
- "scanned": 1381,
- "overLimit": 106,
- "timestamp": "2025-12-27T15:33:16.258Z",
+ "scanned": 1407,
+ "overLimit": 100,
+ "timestamp": "2025-12-27T15:45:04.923Z",
"files": [
{
"path": "frontends/nextjs/src/lib/packages/core/package-catalog.ts",
@@ -511,40 +511,11 @@
"lines": 165,
"recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
},
- {
- "path": "frontends/nextjs/src/app/levels/LevelsClient.tsx",
- "lines": 165,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
{
"path": "frontends/nextjs/src/components/rendering/Builder.tsx",
"lines": 164,
"recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
- {
- "path": "frontends/nextjs/src/components/molecules/form/Select.tsx",
- "lines": 161,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
- {
- "path": "tools/generation/generate-quality-summary.ts",
- "lines": 160,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
- {
- "path": "tools/analysis/code/list-large-typescript-files.ts",
- "lines": 159,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
- {
- "path": "frontends/nextjs/src/components/level/levels/Level4.tsx",
- "lines": 156,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
- },
- {
- "path": "frontends/nextjs/src/types/dbal.d.ts",
- "lines": 155,
- "recommendation": "Split into focused modules; keep one primary lambda per file and extract helpers."
}
- ]
+ ],
+ "outFile": "tools/analysis/code/reports/large-ts-files.json"
}