🖥️ YAGNI frontend decision
✅ Executive decision
StoryMatrix uses one bounded NiceGUI console now. This remains the YAGNI frontend decision: the active UI covers the immediate submit/monitor/review journey while the backend boundary is durable enough for a future client. A richer editor is deferred until a named product need or measured trigger justifies it.
The committed sequence is:
- keep NiceGUI as the only active UI;
- expose durable, owner-scoped jobs and authorized artifacts through HTTP;
- keep the console accessible and responsive;
- evaluate a richer client only when a trigger is recorded; and
- cut over cleanly, retiring NiceGUI rather than carrying two permanent UIs.
A future richer client is likely Svelte + TypeScript + Vite + daisyUI, or a better maintained/accessibility-proven option available at the decision point. That is a hypothesis, not a commitment: no SPA toolchain, Node runtime, or rich-editor framework is installed or designed now. React is not the committed destination.
🧭 What YAGNI means here
YAGNI means solving the next user journey, not building an imagined collaborative studio. The console can configure a bounded generation request, submit it, show durable state, and present resulting text, image, and audio. It does not grow a speculative editor or client state framework merely because a future client might need one.
Until a trigger is met, we explicitly do not build:
- a rich block/timeline editor or arbitrary drag-and-drop composition;
- a SPA toolchain, Node build/runtime, or client-side routing architecture;
- a separately deployed BFF;
- WebSocket infrastructure, offline drafts, or collaborative editing;
- a client-owned domain/state framework, optimistic cache, or synchronization protocol;
- a second active UI alongside NiceGUI; or
- claims that production deployment, multi-tenant product identity, or live SSE streaming already exists.
The boundary is deliberate: backend contracts serve a future client, but future-client needs do not dictate present UI complexity.
🔎 Implementation and verification status
The bounded implementation is complete and verified for its supported journey as of 2026-08-03. Durable owner-scoped jobs and artifacts back the console; NiceGUI uses signed app.storage.user state and polls the same durable application service exposed through HTTP. Browser evidence covers submit, refresh recovery, queued cancellation, simulated offline-worker success, authorized download, a 390px single-column layout without overflow, and a desktop two-column layout.
This is bounded local/service behavior, not a claim of production deployment, multi-tenant identity, or a richer client. Polling is the only progress transport; SSE remains deferred.
🧩 Current NiceGUI surface
src/storymatrix/interfaces/web/main.py defines / and /web and starts NiceGUI with configured host, port, title, dark-mode, reload, and storage-secret settings. NiceGUI runtime is 2.23.3 and the project constrains it to >=2.23.3,<3.
The console provides a catalog-driven request form covering prompt, title, genre, narrative style, language, story structure and scene count; output controls for final-mix assembly, background music, ambient sounds, sound effects, scene-image skipping and audio format; and an Advanced settings disclosure holding voice selection, provider, gender, age and quality plus Piper high-quality-only and keep-temp-files. Every one of those options is wired end-to-end into the generation pipeline; options the worker ignores are deliberately not offered. Results render as a metric report (runtime, scenes, segments, characters, voice segments, music tracks, sound effects, images, file size, generation time) above artifacts grouped into final mix, cover art, scene images, scripts, voice segments, music, effects, stems and opt-in diagnostics, with inline audio and image preview served through an owner-authorized media route. A command palette (Ctrl/Cmd+K), keyboard layer, theme cycle and density toggle sit in the masthead; job filters, pagination and per-browser “reuse settings” sit in the jobs rail. Signed app.storage.user retains a stable opaque owner, form state, preferences and active job across refresh. Polling recovers the active job and renders queued, running, succeeded, failed, and cancelled states.
The UI does not directly run GenerateStoryUseCase; it submits through the owner-scoped durable application service. FastAPI exposes the same job service as the future-client HTTP boundary. The console does not expose Celery task IDs, server paths, raw Markdown/HTML, fake progress, or direct production orchestration.
🔌 Existing HTTP boundary
The current API is a durable resource/command boundary:
POST /storiesaccepts a bounded request and returns202with public story/job identifiers.GET /jobslists the authenticated owner’s durable jobs.GET /jobs/{job_id}returns one authenticated, owned durable job.POST /jobs/{job_id}/cancelrequests cancellation of an authenticated, owned active job.GET /jobs/{job_id}/artifacts/{artifact_id}andHEAD /jobs/{job_id}/artifacts/{artifact_id}serve an authorized owned artifact resource.GET /healthreturns a generic liveness body and remains subject to the API’s application-wide authorization/rate-limit dependency.GET /metricsis protected.
Celery task IDs remain internal implementation details. The obsolete GET /stories/{job_id}/status AsyncResult route is gone. API-key mode maps a valid configured key to a deterministic opaque owner; auth-disabled local mode uses local-api. Active/hourly durable job quotas are admitted atomically per owner, and a process rate limit is enforced.
⚙️ Production and media facts
Generation jobs and generated artifacts are durable owner-scoped resources persisted in SQLite. Celery is an internal worker mechanism, not a public contract. Artifact identifiers are opaque and deterministic; only approved extensions/types are accepted. Files are collected under a job-specific subdirectory, checked with true path containment, and rejected when symlinks are involved. Responses hide relative filesystem paths.
The API and web hosts default to loopback (127.0.0.1), with API port 8000 and web port 8080; deployment exposure and production identity remain outside this bounded implementation. Security behavior includes owner authorization, safe error responses, secret redaction, quotas, and protected metrics. Generated summaries are rendered as safe plain text.
🧰 Included console scope
The NiceGUI console is intentionally small but complete for a bounded journey:
- Configure — enter a prompt and choose supported narrative options and toggles.
- Submit — issue one clearly labeled command and receive an accepted/in-progress state.
- Track — poll durable queued, running, succeeded, failed, and cancelled states.
- Review — show safe summary text and authorized artifact metadata/downloads.
- Recover — restore form and active-job state after refresh and explain failure/offline conditions.
It may add small navigation and presentation improvements serving these steps, but it may not become an editor platform by accretion.
🚫 Excluded and deferred scope
The console does not provide arbitrary scene graph editing, pixel/timeline manipulation, collaborative cursors, presence, comments, version branches, offline-first synchronization, browser workers, or client-side ownership of production orchestration. It does not require Node, a separately deployed BFF, WebSockets, SPA state management, or raw generated HTML/Markdown.
SSE is explicitly deferred; polling remains the sole progress transport until a later contract and deployment decision. Production deployment and multi-tenant product identity are also deferred and must not be inferred from API-key ownership.
🏗️ Current NiceGUI architecture
The architecture is server-driven: page functions build controls, signed app.storage.user retains per-user form/owner/active-job state, and handlers call the durable application service shared with FastAPI. The /web layout is responsive: one column at 390px and two columns on desktop. Durable polling, refresh recovery, recent jobs, cancellation, safe summaries, and authorized downloads are implemented.
NiceGUI remains a bounded adapter, not a home for domain policy or long-running orchestration. Domain and application contracts remain behind the interface, consistent with System Design.
🧱 Durable backend foundation before richer UI
The durable foundation now exists:
📦 Resources and commands
Public responses expose stable opaque identifiers, state, timestamps, safe summaries, validation/error information, and authorized resource links. Celery task IDs, worker details, and relative/server paths are hidden.
🧵 Owner-scoped jobs
A submission creates a durable owner-scoped job in SQLite. API-key ownership is deterministic and opaque; local auth-disabled operation uses local-api. Status, listing, cancellation, and artifact access enforce ownership. Active/hourly durable quotas and a process rate limit constrain work.
📡 Progress transport
Polling is implemented and is the only progress transport. SSE is deferred; WebSockets are not part of the bounded design.
🗂️ Authorized media
Artifacts use opaque deterministic IDs and approved content types/extensions. Job-specific collection, true containment checks, and symlink rejection prevent path escape. API responses never serialize relative filesystem paths.
🛡️ Security and quota controls
Owner authorization, API-key/local ownership modes, safe errors, secret redaction, protected metrics, quotas, process rate limiting, and plain-text summary rendering are implemented. Production CSRF/origin policy, multi-tenant identity, and deployment hardening remain environment-specific deferred work.
♿ Accessible responsive UX contract
The verified console has labeled controls, textual status, understandable errors, safe summary output, authorized downloads, and responsive layouts. Browser evidence specifically covers no-overflow single-column behavior at 390px and a desktop two-column layout. Continued WCAG review remains part of any future richer-client evaluation.
🪜 Phased delivery
1. Durable contract and bounded console — complete
Durable owner-scoped jobs/artifacts, HTTP commands/resources, cancellation, quotas, artifact containment, safe rendering, signed session state, polling, refresh recovery, and the bounded NiceGUI journey are implemented and browser-verified.
2. Production hardening — deferred
Deployment exposure, production identity and multi-tenant authorization, operational scaling, and environment-specific CSRF/origin policy require an explicit production decision. They are not claimed by this page.
3. Rich-client trigger review — deferred
Measure product friction and operational cost. Re-evaluate maintained, accessible options only when a named requirement or measured trigger exists. SSE may be evaluated separately if a future contract and deployment need it; it is not a prerequisite for the current console.
4. Clean cutover — future
Build any selected richer client against the stable contracts, verify parity/accessibility/security/artifact authorization/recovery/performance, switch the active route, and retire NiceGUI. Do not leave two UIs as an indefinite support obligation.
🚦 Go/no-go acceptance criteria
Go for the bounded console is satisfied for the verified scope: valid submission, durable state, refresh recovery, cancellation, and authorized artifacts work without exposing secrets or server paths; narrow and desktop layouts were exercised.
No-go for a richer client remains in force unless backend resources/commands, owner-scoped state, authorized media, security controls, quotas, and polling fallback are documented and exercised alongside a named product need or measured trigger.
🎯 Explicit rich-editor triggers
Re-open the decision only when one or more measured triggers is approved and recorded:
- a named product requirement needs non-linear scene/timeline editing that the console cannot express;
- repeated observed workflows show the bounded form causes material re-entry, abandonment, or error cost;
- accessibility testing demonstrates a necessary interaction cannot be made conformant in NiceGUI without disproportionate custom work;
- measured multi-user collaboration/versioning requirements become committed scope; or
- backend contract and operational maturity accompany an observed product requirement or measured user friction.
A trigger starts an options evaluation; it does not preselect a framework.
🧼 Clean cutover rule
There is one active UI at a time. NiceGUI remains the supported console until a replacement passes functional, accessibility, security, artifact-authorization, failure-recovery, and performance acceptance against the same backend contracts. Once the replacement is active, retire NiceGUI routes and UI-specific orchestration rather than maintaining a parallel legacy surface.
⚖️ Compact alternatives rationale
| Option | Decision | Why |
|---|---|---|
| NiceGUI now | Choose | Python-first, bounded, already present, and sufficient for the verified submit/status/review journey. |
| Rich SPA now | Reject for now | Adds Node/toolchain, state, deployment, and accessibility surface before a measured product need exists. |
| React as destination | Do not commit | No evidence requires React; choosing it now violates the YAGNI boundary. |
| Future Svelte + TypeScript + Vite + daisyUI | Likely candidate only | Potentially suitable for a richer accessible client, but maintenance and accessibility evidence must be rechecked when a trigger occurs. |
| Better maintained alternative then | Always allowed | The decision point—not today’s preference—determines the supported, accessibility-proven option. |
This page supersedes the prior frontend research packet as the canonical decision. It distinguishes verified bounded behavior from deferred production, multi-tenant identity, SSE, and richer-client work.