// Demo `tasks` feature + seed for scaffolded apps — wasp-like starter UX. // `createDemoTasksFeature()` feeds init-migration generation; the render* // functions emit the same shape into the user's repo. import { createBooleanField, createEntity, createNumberField, createTextField, defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler, defineFeature, type FeatureDefinition, } from "@cosmicdrift/kumiko-framework/engine"; import type { EntityEditScreenDefinition, EntityListScreenDefinition, } from "@cosmicdrift/kumiko-framework/ui-types"; const taskEntity = createEntity({ fields: { title: createTextField({ required: true, sortable: true, personal: false, reason: "is_business_data", }), status: createTextField({ sortable: true, personal: false, reason: "is_business_data" }), priority: createNumberField(), isUrgent: createBooleanField({ default: false }), }, }); const listScreen: EntityListScreenDefinition = { id: "task-list", type: "entityList", entity: "task", columns: ["title", "status", "isUrgent", "priority"], defaultSort: { field: "title", dir: "asc" }, rowActions: [{ kind: "navigate", id: "edit", label: "Edit", screen: "task-edit" }], }; const editScreen: EntityEditScreenDefinition = { id: "task-edit", type: "entityEdit", entity: "task", layout: { sections: [{ title: "Task", fields: ["title", "status", "priority", "isUrgent"] }], }, }; const open = { access: { openToAll: { reason: "scaffolded demo handler: every signed-in user of the demo tenant manages their tasks", }, }, } as const; const TASKS_I18N = { "screen:task-list.title": { de: "Aufgaben", en: "Tasks" }, "screen:task-edit.title": { de: "Aufgabe", en: "Task" }, "tasks:nav.tasks": { de: "Aufgaben", en: "Tasks" }, "tasks:nav.taskNew": { de: "Neue Aufgabe", en: "New task" }, "tasks:entity:task:field:title": { de: "Titel", en: "Title" }, "tasks:entity:task:field:status": { de: "Status", en: "Status" }, "tasks:entity:task:field:priority": { de: "Priorität", en: "Priority" }, "tasks:entity:task:field:isUrgent": { de: "Dringend", en: "Urgent" }, } as const; /** Canonical demo feature — keep in sync with `renderDemoTasksFeatureFile()`. */ export function createDemoTasksFeature(): FeatureDefinition { return defineFeature("tasks", (r) => { r.translations({ keys: TASKS_I18N }); r.entity("task", taskEntity); r.writeHandler(defineEntityCreateHandler("task", taskEntity, open)); r.writeHandler(defineEntityUpdateHandler("task", taskEntity, open)); r.writeHandler(defineEntityDeleteHandler("task", taskEntity, open)); r.queryHandler(defineEntityListHandler("task", taskEntity, open)); r.queryHandler(defineEntityDetailHandler("task", taskEntity, open)); r.screen(listScreen); r.screen(editScreen); r.nav({ id: "tasks", label: "tasks:nav.tasks", order: 10, screen: "tasks:screen:task-list" }); r.nav({ id: "task-new", label: "tasks:nav.taskNew", parent: "tasks:nav:tasks", screen: "tasks:screen:task-edit", order: 10, }); }); } export function renderDemoTasksFeatureFile(): string { return `// Demo tasks feature — scaffolded by \`kumiko new app\`. Edit or replace. // Entity + CRUD handlers + list/edit screens + sidebar nav. import { createBooleanField, createEntity, createNumberField, createTextField, defineEntityCreateHandler, defineEntityDeleteHandler, defineEntityDetailHandler, defineEntityListHandler, defineEntityUpdateHandler, defineFeature, } from "@cosmicdrift/kumiko-framework/engine"; import type { EntityEditScreenDefinition, EntityListScreenDefinition, } from "@cosmicdrift/kumiko-framework/ui-types"; import { tasksTranslationKeys } from "./i18n"; const taskEntity = createEntity({ fields: { title: createTextField({ required: true, sortable: true, personal: false, reason: "is_business_data", }), status: createTextField({ sortable: true, personal: false, reason: "is_business_data" }), priority: createNumberField(), isUrgent: createBooleanField({ default: false }), }, }); const listScreen: EntityListScreenDefinition = { id: "task-list", type: "entityList", entity: "task", columns: ["title", "status", "isUrgent", "priority"], defaultSort: { field: "title", dir: "asc" }, rowActions: [{ kind: "navigate", id: "edit", label: "Edit", screen: "task-edit" }], }; const editScreen: EntityEditScreenDefinition = { id: "task-edit", type: "entityEdit", entity: "task", layout: { sections: [{ title: "Task", fields: ["title", "status", "priority", "isUrgent"] }], }, }; const open = { access: { openToAll: { reason: "scaffolded demo handler: every signed-in user of the demo tenant manages their tasks", }, }, } as const; export const tasksFeature = defineFeature("tasks", (r) => { r.translations({ keys: tasksTranslationKeys }); r.entity("task", taskEntity); r.writeHandler(defineEntityCreateHandler("task", taskEntity, open)); r.writeHandler(defineEntityUpdateHandler("task", taskEntity, open)); r.writeHandler(defineEntityDeleteHandler("task", taskEntity, open)); r.queryHandler(defineEntityListHandler("task", taskEntity, open)); r.queryHandler(defineEntityDetailHandler("task", taskEntity, open)); r.screen(listScreen); r.screen(editScreen); r.nav({ id: "tasks", label: "tasks:nav.tasks", order: 10, screen: "tasks:screen:task-list" }); r.nav({ id: "task-new", label: "tasks:nav.taskNew", parent: "tasks:nav:tasks", screen: "tasks:screen:task-edit", order: 10, }); }); `; } export function renderDemoTasksI18n(): string { return `// Server-side translation keys for the demo tasks feature. import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer"; export const tasksTranslationKeys = { "screen:task-list.title": { de: "Aufgaben", en: "Tasks" }, "screen:task-edit.title": { de: "Aufgabe", en: "Task" }, "tasks:nav.tasks": { de: "Aufgaben", en: "Tasks" }, "tasks:nav.taskNew": { de: "Neue Aufgabe", en: "New task" }, "tasks:entity:task:field:title": { de: "Titel", en: "Title" }, "tasks:entity:task:field:status": { de: "Status", en: "Status" }, "tasks:entity:task:field:priority": { de: "Priorität", en: "Priority" }, "tasks:entity:task:field:isUrgent": { de: "Dringend", en: "Urgent" }, } as const; export const tasksTranslations: TranslationsByLocale = { de: { "screen:task-list.title": "Aufgaben", "screen:task-edit.title": "Aufgabe", "tasks:nav.tasks": "Aufgaben", "tasks:nav.taskNew": "Neue Aufgabe", "tasks:entity:task:field:title": "Titel", "tasks:entity:task:field:status": "Status", "tasks:entity:task:field:priority": "Priorität", "tasks:entity:task:field:isUrgent": "Dringend", }, en: { "screen:task-list.title": "Tasks", "screen:task-edit.title": "Task", "tasks:nav.tasks": "Tasks", "tasks:nav.taskNew": "New task", "tasks:entity:task:field:title": "Title", "tasks:entity:task:field:status": "Status", "tasks:entity:task:field:priority": "Priority", "tasks:entity:task:field:isUrgent": "Urgent", }, }; `; } export function renderDemoTasksWebIndex(): string { return `import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer-web"; import { tasksTranslations } from "../i18n"; export const tasksClient: ClientFeatureDefinition = { name: "tasks", translations: tasksTranslations, }; `; } export function renderDemoTasksIndex(): string { return `export { tasksFeature } from "./feature"; `; } export function renderDemoSeedFile(): string { return `// Demo seed — a few tasks so \`bun dev\` shows a non-empty list. import type { SeedFn } from "@cosmicdrift/kumiko-dev-server"; import { TestUsers } from "@cosmicdrift/kumiko-framework/stack"; const DEMO_TASKS = [ { title: "Welcome to Kumiko", status: "todo", priority: 1, isUrgent: false }, { title: "Try editing me", status: "in progress", priority: 2, isUrgent: true }, ] as const; export const seedDemoTasks: SeedFn = async (stack) => { const admin = TestUsers.admin; const existing = await stack.http.queryOk<{ rows: unknown[] }>( "tasks:query:task:list", {}, admin, ); // skip: tenant already has tasks — dev DB persists across restarts, don't duplicate if (existing.rows.length > 0) return; for (const task of DEMO_TASKS) { await stack.http.write("tasks:write:task:create", task, admin); } }; `; }