From 091780086beaec06a22fd3bfd912498ed040a26b Mon Sep 17 00:00:00 2001 From: Jake Date: Fri, 15 May 2026 13:22:13 +0100 Subject: [PATCH] v0.3 Phase 4a: Settings tab shell MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discovers that v0.2 SettingsPage.svelte was already structured as eight numbered sections aligning almost 1:1 with the v0.3 plan. The cheapest path to "one section visible at a time" is therefore a tab-shell overlay rather than a file split. Behaviour stays identical to v0.2 when quietware is off; with quietware on, only the active tab's section renders. Changes. - NEW: src/lib/ui/LumotiaSettingsTabs.svelte. Tab list following the WAI-ARIA "tabs (automatic activation)" pattern. Arrow / Home / End keyboard navigation. Active tab uses the brand left-bar accent (border-l-caution) — same grammar as the LumotiaNotice refactor from Phase 3. - MODIFIED: src/lib/pages/SettingsPage.svelte. Added an isQuietware reactive flag, a MutationObserver watching , an activeTab state, and eight {#if !isQuietware || activeTab === 'X'} wrappers around the existing top-level SettingsGroup sections. The settings tree is unchanged; only its top-level rendering gate changed. - v0.2 fallback: when data-design="quietware" is absent the tablist does not render and all eight sections stack as before. The search filter spans every section in v0.2 mode. Smallest- possible behavioural change for "one section at a time". Tab IDs in this commit map 1:1 to existing v0.2 section names (start-here / transcription / models / tasks / accessibility / privacy / advanced / help). Phase 4b will restructure to the plan's canonical names (carving Output from Advanced and Vocabulary from Transcription). Phases 4b and 4c are logged as pending in the plan doc: 4b. Restructure to canonical tab names (Output, Vocabulary). 4c. Texture-opacity slider + high-contrast toggle in Accessibility. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/release/v0.3-tactile-quietware.md | 24 +++++++ src/lib/pages/SettingsPage.svelte | 65 ++++++++++++++++++ src/lib/ui/LumotiaSettingsTabs.svelte | 93 ++++++++++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 src/lib/ui/LumotiaSettingsTabs.svelte diff --git a/docs/release/v0.3-tactile-quietware.md b/docs/release/v0.3-tactile-quietware.md index 19c77df..a7d79b5 100644 --- a/docs/release/v0.3-tactile-quietware.md +++ b/docs/release/v0.3-tactile-quietware.md @@ -220,6 +220,30 @@ Architectural finding: both `LumotiaStatusPill` and `LumotiaNotice` already ship - All four tones (info / caution / danger / success) verified by the pattern itself: the visible signal is the left bar, which is `border-l-{tone}` at 100% opacity, working at any colour intensity. Caution remains a fill-only convention on cream by design. +### Phase 4a — Settings tab shell. Landed 2026-05-15. + +Architectural finding: the v0.2 `SettingsPage.svelte` (2 891 LOC after Phase 4a) was already structured as eight numbered sections that align almost 1:1 with the v0.3 plan. The cheapest path to "one section visible at a time" is therefore a tab-shell overlay rather than a file split. Behaviour stays identical to v0.2 when quietware is off; with quietware on, only the active tab's section renders. + +- New `LumotiaSettingsTabs.svelte` ([src/lib/ui/LumotiaSettingsTabs.svelte](../../src/lib/ui/LumotiaSettingsTabs.svelte)) implements the WAI-ARIA tabs (automatic activation) pattern. Arrow / Home / End keyboard navigation. Active tab uses the brand left-bar accent (`border-l-caution`) so it carries the same grammar as the LumotiaNotice refactor. + +- `SettingsPage.svelte` gained a small reactive `isQuietware` flag, a `MutationObserver` watching ``, and 8 `{#if !isQuietware || activeTab === 'X'}` wrappers around the existing eight top-level `` sections. The settings tree is unchanged; only its top-level rendering gate changed. + +- v0.2 fallback behaviour: when `data-design="quietware"` is absent the tablist does not render and all eight sections stack as before. The search filter still spans every section in v0.2 mode. This is the smallest-possible behavioural change for "one section at a time". + +- Tab IDs in this commit map 1:1 to existing v0.2 section names (start-here / transcription / models / tasks / accessibility / privacy / advanced / help). Phase 4b will restructure to match the plan's canonical names (carving Output from Advanced and Vocabulary from Transcription). + +### Phase 4b — Settings restructure to canonical tab names. Pending. + +- Carve **Output** out of `7. Advanced > Output & Capture` (currently at line 2398 in the pre-Phase-4a file) into its own top-level tab. +- Carve **Vocabulary** out of `2. Transcription > Custom vocabulary` (line 1472 in the pre-Phase-4a file) into its own top-level tab. +- Rename the remainder of `2. Transcription` to be subsumed by **Output** + **Vocabulary**, or kept as legacy section pending content audit. +- Final tab order: Start Here / Models / Output / Vocabulary / Tasks / Accessibility / Privacy / Advanced (eight tabs, matching the plan). + +### Phase 4c — Texture-opacity slider + high-contrast toggle. Pending. + +- Texture-opacity slider in the Accessibility tab, range 0.05–0.08, default 0.06. Persists to existing preferences store. +- High-contrast toggle in the Accessibility tab. Toggle overrides system `prefers-contrast: more` query when set. + --- ## Regression diary diff --git a/src/lib/pages/SettingsPage.svelte b/src/lib/pages/SettingsPage.svelte index 96a4b5f..e794b5d 100644 --- a/src/lib/pages/SettingsPage.svelte +++ b/src/lib/pages/SettingsPage.svelte @@ -10,6 +10,7 @@ import HotkeyRecorder from "$lib/components/HotkeyRecorder.svelte"; import ImplementationRulesEditor from "$lib/components/ImplementationRulesEditor.svelte"; import SettingsGroup from "$lib/ui/LumotiaSettingsGroup.svelte"; + import SettingsTabs from "$lib/ui/LumotiaSettingsTabs.svelte"; import StatusPill from "$lib/ui/LumotiaStatusPill.svelte"; import ZonePicker from "$lib/components/ZonePicker.svelte"; import AccessibilityControls from "$lib/components/AccessibilityControls.svelte"; @@ -1212,6 +1213,42 @@ saveTemplates(); editingTemplate = -1; } + + // v0.3 Phase 4a — sectional tab nav. The full 2 791 LOC settings tree + // stays in this file; quietware mode shows ONE top-level section at a + // time via {#if !isQuietware || activeTab === 'X'} wrappers below. + // Without quietware (data-design unset on ) the page renders + // exactly as v0.2 did (all eight sections stacked, search filter + // active across all of them). + let isQuietware = $state(false); + let activeTab = $state("start-here"); + const settingsTabs = [ + { id: "start-here", label: "Start Here" }, + { id: "transcription", label: "Transcription" }, + { id: "models", label: "Models" }, + { id: "tasks", label: "Tasks" }, + { id: "accessibility", label: "Accessibility" }, + { id: "privacy", label: "Privacy" }, + { id: "advanced", label: "Advanced" }, + { id: "help", label: "Help" }, + ]; + let quietwareObserver: MutationObserver | null = null; + onMount(() => { + if (typeof document === "undefined") return; + const sync = () => { + isQuietware = document.documentElement.dataset.design === "quietware"; + }; + sync(); + quietwareObserver = new MutationObserver(sync); + quietwareObserver.observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-design"], + }); + }); + onDestroy(() => { + quietwareObserver?.disconnect(); + quietwareObserver = null; + });
@@ -1253,6 +1290,18 @@ {/if}
+ {#if isQuietware} + +
+ (activeTab = id)} + /> +
+ {/if}
@@ -1298,6 +1347,7 @@
+ {#if !isQuietware || activeTab === 'start-here'} @@ -1429,7 +1479,9 @@
+ {/if} + {#if !isQuietware || activeTab === 'transcription'} @@ -1837,7 +1889,9 @@ + {/if} + {#if !isQuietware || activeTab === 'models'} @@ -2055,7 +2109,9 @@ + {/if} + {#if !isQuietware || activeTab === 'tasks'} @@ -2099,7 +2155,9 @@ + {/if} + {#if !isQuietware || activeTab === 'accessibility'} @@ -2112,7 +2170,9 @@ + {/if} + {#if !isQuietware || activeTab === 'privacy'} @@ -2197,7 +2257,9 @@ + {/if} + {#if !isQuietware || activeTab === 'advanced'} @@ -2743,8 +2805,10 @@ + {/if} + {#if !isQuietware || activeTab === 'help'} + {/if} diff --git a/src/lib/ui/LumotiaSettingsTabs.svelte b/src/lib/ui/LumotiaSettingsTabs.svelte new file mode 100644 index 0000000..78e8696 --- /dev/null +++ b/src/lib/ui/LumotiaSettingsTabs.svelte @@ -0,0 +1,93 @@ + + +
+ {#each tabs as tab, i (tab.id)} + {@const isActive = tab.id === activeId} + + {/each} +