Settings screen #9
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Depends on
#10 Provider configuration from the UI
lyssieth/sermones
#3 Add configuration loader for sermones-runtime
lyssieth/sermones
#5 Design token core + light/dark themes
lyssieth/sermones
Reference
lyssieth/sermones#9
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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 settings surface, per
docs/designs/draft/frontend.md. Both blockers landed long ago: the token core (#5) and the config loader's IPC surface (#3).Status: every checklist item is implemented.
frontend/src/lib/Settings.svelteis an inline panel mounted fromApp.sveltecovering Appearance / Providers / Models / Default model / Backups, with full CRUD over providers and models; the provider sections carry #10's work. The first pass was reviewed from a screenshot; the editors added since are verified by build, typecheck, lint, and tests only.Checklist
<aside>panel beside the chat column; sections are plain<section>blocks, no nested modalsset_config(#3); takes effect without restartHow the theme item was resolved
Config.ui.themealready existed in the schema (crates/sermones-core/src/config.rs,Theme::System | Light | Dark) and was generated into TS, but nothing on the frontend read or wrote it — the preference lived inlocalStorageand the only control was a two-way light/dark toggle. Now:Config.ui.themeis the source of truth.theme.tsgainedpreferenceFromConfig/configThemefor the mapping (an absent field meansSystem);App.svelteadopts the config's value on boot and after a backup restore, overriding the cache.localStorageis demoted to a first-paint cache. The config arrives asynchronously, andmain.tsstill callsinitTheme()before mount so the first paint is not a flash of the wrong scheme. Where the two disagree, the config wins and the cache is rewritten.set_config. Both affordances apply locally first, so the change lands without a round-trip and without a restart. Write failures surface onerror_raisedlike every other fire-and-forget slot (docs/architecture/errors.md).system. Styling needed no listener (tokens.scssfollows the media query), but the shell subscribes toprefers-color-schemeanyway so the sidebar's toggle icon does not go stale when the desktop flips mid-session.One interaction worth recording: theme is the only control in the panel that does not wait for Save, so a draft saved afterwards could have rolled it back. Two guards —
onSettingsSavemerges the shell's current preference into the config it sends, and the panel'sdraftno longer tracks theconfigprop, so a theme write mid-edit cannot wipe unsaved provider/model edits either. The draft is seeded once at mount (the panel is unmounted while closed, so that is "when the user opened it"); a backup restore bumps aconfigRevisionkey that remounts the panel, which is the one case where the draft must be re-seeded.How the keyboard / focus item was resolved
aria-modaldropped. The panel claimedrole="dialog" aria-modal="true"with no focus trap, no focus-on-open, and no Escape handler — promising a screen reader three things it did not do. It is now an<aside aria-labelledby>, which is what it visually is, and matches this issue's "modals are a last resort"..linkbuttons (Close, Remove, Restore, the theme toggle) declaredbackground: none; border: 0and no:focus-visible. They are nowprimitives.interactive-based icon and secondary buttons, so the ring comes from the shared state matrix. The bare Default-model<select>was unstyled entirely — it now usesprimitives.inputlike the rest.<label>-wrapped fields in<form>elements now, which also makes Enter submit the row.visually-hiddenmixin landed inprimitives.scssfor the group legend and the default-model label (MessageView's hand-rolled.sr-onlynow uses it too).Editing existing providers and models
Added after the first pass: the panel could add and remove, but not change anything, so fixing a typo'd base URL or renaming a model meant remove-and-re-add — which for a model in use also drops the conversations' selection.
aria-expanded/aria-controls), one at a time, so a section stays a list you can scan. Providers expose display name, kind, base URL, and API key; models expose wire name, display name, context window, max output tokens, and temperature. Edits write straight into the draft — the panel's Save is the commit, Close is the discard, same as add and remove.ModelKeyare map keys, and the slug is also embedded in everyModelKey("<slug>|<model_id>"), in each conversation'sselected_model, and in the frozenmodelattribution on every assistant message already in the tree. Renaming one is a cascade across live conversation history, not a settings edit. Each editor states the fixed key with "remove and re-add to rename" rather than leaving the user to wonder.active_modelpresent in the list. This closes the hole noted in #10 —set_configstores the config wholesale andConfigStore::saveonly serialises, so the panel is the sole enforcement point forProviderRecord::new's invariant. It also fixes a live bug: the add-form only demanded an API key forOpenAI, butProviderKind::requires_api_keyis true forOpenRoutertoo."", which the renderers would otherwise show as a blank label.Two things from the screenshot came along with it:
--text-disabled. At--text-secondarythey read as filled-in values —localandhttps://api.openai.com/v1in an empty add-form looked like a configured provider. Scoped to this panel; the composer's placeholder is unchanged.Follow-ups this did not cover
Tooltipprimitive;title=is still the only hint on icon buttons (pre-existing, tracked indocs/components/frontend-ui.md).Refs
frontend/src/lib/Settings.svelte— the panelfrontend/src/lib/theme.ts+theme.test.ts— preference ⇄ config mapping, OS resolutionfrontend/src/App.svelte— theme adoption, save merge, focus restore, restore-remountfrontend/src/styles/primitives.scss—visually-hidden, the shared state matrixdocs/components/frontend-ui.md— "Theme" and "Settings panel" sectionsdocs/designs/palette.md— decision 5, the override seamLanded in
608f09e.All seven checklist items are done: the scaffold, the theme section (now backed by
Config.ui.themerather thanlocalStorage), the provider section, full keyboard navigation and visible focus, room to grow, and editing for existing providers and models.Reviewed on the desktop across two screenshot rounds — the add-forms moved behind disclosure buttons, placeholders were dimmed so they stop reading as filled-in values, backups became collapsible with their own bounded scroll area, and
list_config_backupsnow projects newest-first.Two things deliberately left out, both tracked elsewhere:
ModelKey, each conversation'sselected_model, and the frozenmodelattribution on assistant messages already in the tree. Worth its own issue if it ever comes up; remove-and-re-add covers it for now.