Frontend refactoring — remaining work
Status of the frontend SPA (Vue 3 / Quasar / TS) clean-up that followed the bad-practices / dead-code / duplication audit. This doc captures what is left, the recommended approach, and — importantly — the findings that were verified as non-issues so they are not re-investigated later.
Approach (what made the done work safe)
God-component decomposition is done as behaviour-preserving extraction into composables / child components, each verified by the existing gate:
pnpm lint # eslint (type-aware)
pnpm exec vue-tsc --noEmit -p tsconfig.json # incl. template bindings
pnpm exec vitest run
vue-tsc type-checks template bindings, so a missing composable return surfaces as a compile
error — that is the safety net for the extractions below. State-/timing-coupled cores cannot
be fully verified this way and need a manual smoke-test with the app running.
Done so far
- Quick wins: removed 3 dead components;
apiErrorDetailutil (25 call sites);structuredClone(JSON-hack removed, 17 sites); dashboard card config typingRecord<string, unknown>→ 0/44;MicButtonunmount cleanup;DeviceTerminali18n;useMermaidobserver dispose;datetimeutil. SettingsPage.vue2496 → ~1719 LOC: extractedSettingsAppDownloads,SettingsCommands,SettingsCompanion,SettingsUsage(undersrc/components/settings/).DashboardPage.vue1010 → ~972:useUndoableState(+ unit tests),useDashboardStrategy.MainLayout.vue1229 → ~1055:useChatRunSettings(header model/reasoning/mode/classification/ security/collab/memory controls).ChatPage.vue1480 → ~1439:useWorkingPhrase(rotating spinner phrase + its 5s timer).
Remaining work — prioritized
1. God-components still over budget
| File | LOC | Suggested cut | Verification |
|---|---|---|---|
stores/chat.ts |
~2539 | Split cohesive subsystems into composables: goal-loop autonomy, workspace-setup state machine, voice I/O, older-message pagination. | App smoke-test — highest stakes (streaming/runs, SSE/WS). |
pages/SettingsPage.vue |
~1719 | Remaining 4 tabs: profile, agent, appearance, behavior. |
App smoke-test — they share one onMounted that hydrates all four forms from one auth.loadMe(), plus a behaviorReady flag that arms behaviour auto-save watchers only after hydration. A mis-armed watcher would silently POST during hydration. |
pages/ChatPage.vue |
~1439 | useChatMobileNav (~100 LOC: mobileView, nav views, unread badges, navPulse, seen counters + their watchers) → next safe step, no app needed. Then useMessageEditing (~60 LOC: inline message + queue edit) and useChatScroll (scroll-to-bottom / load-older). |
tsc/lint/tests for the nav + editing cuts; the watchers in the nav block have side effects (reset seen counters) so move them with care. |
pages/DashboardPage.vue |
~972 | The remaining body is the editor core (dialog state + card/view/section/badge CRUD) all hanging off the shared config ref + mutate(). Extracting it wholesale yields a 25+ member composable — not a clean separation; only worth it with an app smoke-test, otherwise leave as-is. |
App smoke-test if attempted. |
layouts/MainLayout.vue |
~1055 | Lower priority. The rest is the layout shell (nav drawer, folders, recent chats, export). The ~20 go* route helpers could be data-driven, but it is cosmetic. |
tsc/lint. |
2. Deferred real items
GenericPicker—IntegrationsPicker,MemoryAccessPicker,WorkspaceDevicePickerand the other menu pickers share ~70% structure (q-btn → q-menu → filter → q-list → toggle/select). A shared base component would remove the duplication, but each picker has subtle filtering/selection differences → needs an app smoke-test, treat as its own ticket.
3. Verified NON-issues — do not re-chase
These were flagged by the audit pass but verified to be already-correct; changing them would regress behaviour:
- Store error handling (
integrations,marketplace) — the stores intentionally do not toast; every caller already wraps mutations intry/catch+Notify(e.g.IntegrationsManager.toggle,MarketplacePage). Adding store-levelNotifywould double-toast. Architecture is deliberate: store = loading/optimistic state, component = user-facing error. reasoningOptions“duplication” — the two remaining copies (MainLayout/useChatRunSettingsvsSettingsPage) are semantically different types (ThinkingValuewithnull/falsevs the settings-default string'off'). The real duplicate (ModelPicker.vue) was deleted.- Date formatting —
formatTime(utils/datetime.ts) is adopted; the remainingtoLocale*call sites use genuinely different formats, not duplicates. v-html/ markdown — all bindings go throughrenderMarkdown(DOMPurify,html:false, KaTeXtrust:false, DoS guards) or hljs-escaped output. No XSS surface.- i18n parity -
de-DEanden-USverified equal (2129 leaf keys each, 0 missing either way). useAsyncResource— not worth adding: withNotifyit would double-toast (see above); as a loading-only wrapper it saves ~3 lines per store across ~6 stores that can’t be smoke-tested.
Notes
- All hard-coded backend/UI-framework strings stay English; user-facing copy goes through i18n.
- Keep extractions behaviour-preserving and run the gate above before each commit.