Refactor UI: pages as templates and organisms; tasks, projects and workflows over RPC #9
Loading…
Reference in a new issue
No description provided.
Delete branch "refactor/u2-pages"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The UI track to the end of U2, and the Board's, Projects' and Workflows' reads over RPC.
Pages become thin. Every page is route params, composables and a template filled with data; the sections are organisms, their pieces molecules with stories.
app/layouts/default.vueis the app shell. Lines: board 575 → 173, task 1,639 → 148, projects 428 → 213, workflows 455 → 213, time 502 → 127, reports 457 → 112, settings 1,017 → 71. The large editor organisms (WorkflowCanvas, WorkflowInspector) are split too. No component loads its own data: the atomic check has no exceptions left.Reads over RPC, with Schemas.
TaskQueries.list / detail / fields: plain-Struct views mirroringshared/types, served over the Board context'sTaskReadsport;useTaskList,useTaskDetail,useTaskFieldsthroughuseRpcQuery(SSR, shared keys, live refresh).packages/core/src/projects): commands Create/Update/Delete/Reorder throughCommands.executewith events projected to the live messages the screens already handle;Projects.list/get/create/update/delete/reorder. Five REST routes gone; what concerns a project's folder itself stays REST until the Workspace port (Phase 7).packages/core/src/workflow/domain/definition, one per file, withEditabletypes so the canvas can still edit a decoded value), and the workflow library isWorkflows.list/presets/create/change/remove/duplicate/reset, with events and refusals by catalogue key. All eight REST routes under/api/workflowsand the zod schema are gone.Checked: 534 tests (from 390: the new stories mount in real Nuxt, plus RPC, command and Schema tests); lint, typecheck, Storybook and the Nuxt build; 45 of 45 screenshots pixel-identical to the Phase 4 build (nine pages, five themes).
LEGACY_API_ROUTESis at 12 (from 15); the lint baseline at 24 files (from 34), all legacy code.Behaviour to know about: live refresh of the lists is now
useRpcQuery's, undebounced (same end state, possibly one extra fetch after a command). The "Same column", "Any priority" and "Blank preset" select options carried an empty-string value, which reka-ui refuses to render; they use sentinels now and should appear in those dropdowns where they were silently missing. Definitions over RPC are validated by the Schema (a generic error instead of zod's message; the UI never sends partial nodes).Part of the DDD/CQRS refactor; the PRs are stacked, each based on the one before, so merge them in order: Phase 0 → U0 → Phase 1 → Phase 2 → Phase 3 → Phase 4 → structure → plugin classes → UI pages.
CI: the Forgejo job has not run on these branches (every run was cancelled before a runner picked it up; no runner is registered for the repo). The same steps (
npm ci, lint, typecheck, tests, Storybook build) pass innode:24-bookwormlocally.🤖 Generated with Claude Code
The board's list, a task's page and the custom fields in use are read through `TaskQueries.{list,detail,fields}` (packages/contracts/src/board), answered by the Board's new read-side port `TaskReads` (over today's repository in TaskReadsLegacy) and the `getTaskDetail` query, which names a missing task as TaskNotFound. The views in contracts/src/board/views mirror shared/types field for field (TaskView, TaskListItemView, TaskDetailView and the parts of a detail: events, questions, attachments, reviews, time entries, memory proposals, the plan report and the workflow state as stored, with the fields older rows lack optional). The app keeps consuming the shared types: the api module hands the decoded views out under those names. A task's project and workflow cross as declared JSON (EmbeddedProject, EmbeddedWorkflow) until their own contexts' Schemas land. useTaskList, useTaskFields and useTaskDetail are useRpcQuery queries, one per file, under the keys and refresh conditions the pages had ('tasks' on task.*, project.* and runner.changed; `task-<id>` on the task's messages, runner.changed and launchers.changed); the board refreshes only the projects' counts itself now. useTaskActions gets planAnswer; useReviewActions and useTimeEntryCommands hold what the task page still posted by hand. /api/tasks/fields is gone. /api/tasks/[id] stays for the Electron shell but answers through the query, so it leaves LEGACY_API_ROUTES (14 entries). test/rpc/taskQueries.test.ts checks each view equals the repository's output. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>packages/core/src/execution holds what a session is told and allowed: the AgentSession and Question Schemas, the Session*/Question*/Plan/Review/SentBack events, the pure rules (readOnlyViolation, planFirstViolation, mutatingGitCommand, classifyCommand, decideToolUse), Kanbot's tools as AgentTools over a ToolHost port, and the prompts as pure functions with {{tool:name}} tokens. Its ports — AgentRuntimes, SessionStore, QuestionStore, Workspace, TaskBoard, LiveSessions, WorkflowGate — are served by the adapters in server/kanbot/execution and by the new agent_sessions table (migration 0010, dual-written with the task's session id and metrics). server/kanbot/execution runs the sessions: ExecutionService forks each task, review and plan session into a FiberMap keyed by task, builds the SessionSpec (tools bound with JSON Schema, the gate over the rules and approvals, ask over the question row), turns the runtime's events into the same timeline lines the runner wrote, and tells the workflow as before. Claude Code runs behind the AgentRuntimes port as a legacy adapter over the SDK until the claude-code plugin serves it. server/agent/runner.ts, plan.ts and prompt.ts are shims over the service, so every caller and mock keeps working; the characterisation suite's expectations are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Both tracks defined the AgentRuntimes port the same way; the Execution context now sits above the plugin host in the layer stack and takes the runtimes from RuntimeKind, so the interim adapter and its private Claude runtime go. Tests put a scripted runtime in front through startKanbot({ runtimes }), a layer that wraps the plugins' AgentRuntimes (test/support/scriptedRuntime.ts); without an override the sessions run on the claude-code plugin over the fake SDK, which the deliverAnswer characterisation test relies on. The plugin list also names a plugin's agent runtimes, and labels the two new permissions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>A setting had to be added in four places (the defaults and the per-field merge in getSettings, the zod schema and setSetting in PUT /api/settings, the app's defaultSettings) or it silently dropped. Now each section is a Schema in the Settings context (packages/core/src/settings), its defaults in SETTINGS_DEFAULTS beside it (plain data, so the app shows them without loading Effect), and reading is decoding the stored rows with it (readSettings): a field or section never stored reads back as its default, a row that does not decode as its section's default. The legacy fixes are decode steps: groomingModel becomes planningModel, this machine's launchers and the KANBOT_* numbers apply only where nothing was stored, and the columns workflows provide are added by the server's shim. getSettings, getSettingsRow, publicSettings and setSetting stay as that synchronous shim, so the legacy readers and the characterisation tests are unchanged; the old merge is kept in test/settings as the reference every stored shape must still read back as. Changing settings is the ChangeSettings command over the new Settings RPC group (get, update a patch of whole sections): checked by the Schemas, text trimmed and the board's own columns kept as PUT did, and one SettingsChanged (LocaleChanged for the language) per section that really changed. They are projected to the messages screens already handle (board.changed, launchers.changed, runner.changed, sync.changed, locale.changed) plus settings.changed, which the app's single settings query listens for; a consumer restarts the standup timer and lets the scheduler start what a higher limit allows. PUT /api/settings goes; GET stays for Electron. The app reads one shared copy (useAppSettings on useRpcQuery) and writes through useSettingsCommands, whose answer replaces that copy, so the refreshNuxtData('app-settings') workaround and defaultSettings() go. The tour finishes through the same command. Discord is a plugin now: its section, types and form leave the settings (the Integrations section goes; the tour and the menu point at Plugins). At boot, before the plugins start, a stored `discord` row becomes the Discord plugin's settings and secret (and an off bot an off plugin), then the row is dropped, so it happens once. The legacy bot reads the plugin's config until its plugin replaces it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>