Phase 9 of the rebrand cascade. Sweep covers everything the Phase 8
frontend pass deliberately skipped: docs/, root markdown, scripts,
Cargo.toml descriptions, code comments that survived earlier
word-boundary sed, plus a handful of identifiers caught on the final
verify pass.
transcription-app changes:
- README.md, HANDOVER.md, KNOWN-ISSUES.md, run.sh — magnotia/Magnotia
-> lumotia/Lumotia.
- docs/ — sweep across all subdirs except docs/handovers/ (preserved
as immutable audit trail). Includes architecture-map references
to magnotia_core::*, magnotia_storage::*, etc. now pointing at
lumotia_*; dev-setup.md tracing output examples (lumotia_startup
target); brief/ + superpowers/ + issues/ + whisper-ecosystem/ +
audit/.
- Cargo.toml descriptions on 9 crates (core, audio, cloud-providers,
hotkey, llm, mcp, plus referenced others).
- crates/core/src/{error,hardware,recommendation,paths}.rs +
crates/audio/src/wav.rs + crates/llm/src/model_manager.rs +
crates/cloud-providers/src/keystore.rs + crates/mcp/src/lib.rs —
doc comments and a model-manager user-agent string.
- Caught on final pass: BroadcastChannel("magnotia_task_sync") -> ...
("lumotia_task_sync"); magnotia_locale i18n localStorage key
renamed + migration shim added; CSS keyframe names
magnotiaPulse / magnotiaBar / magnotiaFade renamed in the design-
system kit; magnotia_viewer_item / magnotia_viewer_mode handoff
keys renamed in HistoryPage + viewer/+page.svelte; src/assets/
wordmark.svg text.
- src-tauri/src/lib.rs comment cleanup ("magnotia era" was sed'd
to "lumotia era" earlier — restored).
Preserved (intentional):
- crates/core/src/paths.rs — keeps "magnotia" / "Magnotia" / ".magnotia"
legacy detection strings in legacy_and_target_paths() so the
migration shim can still find user data from the magnotia era.
- src/lib/stores/{page,focusTimer}.svelte.ts + src/lib/i18n/index.ts
— migration call sites reference the legacy magnotia keys
deliberately.
- docs/handovers/ — historical audit trail.
cargo build --workspace passes. npm run check: 0 errors / 0 warnings
(3958 files). cargo test --workspace: 339 pass / 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
307 lines
21 KiB
Markdown
307 lines
21 KiB
Markdown
---
|
|
name: Phase 9 — Polish debt (design)
|
|
description: Design spec for Lumotia Phase 9. Closes six polish-debt items from the feature-complete roadmap plus the Phase 8 carryover backlog. File-system save dialog, bulk History export, on-demand LLM content tags, progressive-disclosure Settings restructure, visual polish, accessibility sweep.
|
|
type: spec
|
|
tags: [spec, phase-9, lumotia, polish, accessibility, settings, export, llm]
|
|
created: 2026/04/24
|
|
status: approved
|
|
phase: 9
|
|
roadmap: docs/roadmap/2026-04-23-lumotia-feature-complete-roadmap.md
|
|
author: Wren (CORBEL's resident agent) on behalf of Jake Sames
|
|
---
|
|
|
|
# Phase 9 — Polish debt
|
|
|
|
## Summary
|
|
|
|
Close the last polish-debt items before Phase 10 (QC + rename + release). Six items from the roadmap plus the Phase 8 carryover backlog.
|
|
|
|
1. **File-system `.md` save dialog** — replaces clipboard-only export on HistoryPage + DictationPage + FilesPage.
|
|
2. **Bulk select + bulk export in History** — multi-select rows, export as a directory of `.md` files.
|
|
3. **LLM content tags** — on-demand `topic:*` + `intent:*` extraction using `lumotia-llm`, persisted on history rows.
|
|
4. **Settings UX overhaul** — progressive disclosure. High-frequency settings always-visible; advanced behind `<details>` groups. Phase 8 sparkline toggle relocated out of Rituals.
|
|
5. **Visual polish pass** — spacing, typography, motion curves, dark-mode parity. Absorbs Phase 8 motion backlog on badge + sparkline.
|
|
6. **Accessibility pass** — keyboard navigation, focus order, screen reader labels (friendlier sparkline aria copy), WCAG AA contrast. Absorbs Phase 8 a11y backlog.
|
|
|
|
No new Rust crates. No new dependencies (`tauri-plugin-dialog` + `@tauri-apps/plugin-dialog` both already installed; capability already allowed).
|
|
|
|
This is the last phase before Phase 10. On completion: cargo + clippy + fmt + svelte-check + npm build all clean; manual dogfood walkthrough of items 1-6 passes; roadmap + HANDOVER updated.
|
|
|
|
## Design decisions locked 2026/04/24
|
|
|
|
1. **Save dialog** — no silent clipboard fallback if user cancels. Default filename derived from transcript title via the existing slug path (`<title or "transcript">-<YYYY-MM-DD>.md`).
|
|
2. **Bulk export format** — directory of per-transcript files (chosen via `open({ directory: true })`). Concatenated single-file is not offered; users can copy-paste or open selected items to achieve it.
|
|
3. **LLM tags** — on-demand per row plus a batch "Tag all untagged" affordance. Stored on `item.llmTags: string[]` alongside existing `manualTags`. Grammar-constrained JSON extraction: `{ topic: string, intent: enum }` with intent from a closed set. Re-generation replaces prior `llmTags` for that row (no history).
|
|
4. **Settings restructure** — progressive disclosure. Top group "Start here" always expanded (model, microphone, hotkey, theme). Six collapsed `<details>` groups below: Transcription, Tasks, Rituals, Notifications, Accessibility, Advanced. Phase 8 sparkline toggle moves from Rituals to Tasks.
|
|
5. **Visual polish + a11y — include in Phase 9**, not deferred to post-v0.1. Matches roadmap as written. Polish + a11y constrained to a checklist (see §Visual polish + §Accessibility below), not an open-ended pass.
|
|
|
|
## Architecture
|
|
|
|
### Item 1 — File-system `.md` save dialog
|
|
|
|
**Rust side** (`src-tauri/src/commands/fs.rs`, new file — or added to an existing commands file if the one-command-per-file convention is loose):
|
|
|
|
```rust
|
|
#[tauri::command]
|
|
pub async fn write_text_file_cmd(path: String, contents: String) -> Result<(), String> {
|
|
// Safety:
|
|
// - Path comes from a user-selected save dialog. We do not validate
|
|
// traversal or extension — the OS file picker already constrains
|
|
// the user's choice to what they can write to.
|
|
// - Parent directory is expected to exist (save dialog guarantees it).
|
|
// - Writes UTF-8 via tokio::fs. No atomic-rename dance — a transcript
|
|
// save is not mid-flight safety-critical; clobber on overwrite is fine.
|
|
tokio::fs::write(&path, contents)
|
|
.await
|
|
.map_err(|e| format!("Failed to write {path}: {e}"))
|
|
}
|
|
```
|
|
|
|
**Frontend call sites** — replace the `navigator.clipboard.writeText(md)` tail with:
|
|
|
|
```typescript
|
|
import { save } from "@tauri-apps/plugin-dialog";
|
|
|
|
const defaultName = suggestedFilename(item); // "<slug>-<YYYY-MM-DD>.md"
|
|
const path = await save({
|
|
title: "Save transcript as Markdown",
|
|
defaultPath: defaultName,
|
|
filters: [{ name: "Markdown", extensions: ["md"] }],
|
|
});
|
|
if (!path) return; // user cancelled — no toast, no fallback
|
|
await invoke("write_text_file_cmd", { path, contents: md });
|
|
toasts.success(`Saved to ${basename(path)}`);
|
|
```
|
|
|
|
A new utility `src/lib/utils/saveMarkdown.ts` centralises `suggestedFilename` + `saveTranscriptAsMarkdown(item)` so three pages (HistoryPage / DictationPage / FilesPage) each call one function.
|
|
|
|
**Call site scope.** HistoryPage `exportMarkdown` (line 343) is the primary target. DictationPage + FilesPage currently do clipboard-only copies of raw transcript text (not markdown); they are out of scope for a markdown export button in Phase 9 — their clipboard semantics are right for their UX. Only HistoryPage exposes an "Export .md" button.
|
|
|
|
### Item 2 — Bulk select + bulk export in History
|
|
|
|
**Selection model.** New frontend-only state on HistoryPage: `let selected: Set<string> = $state(new Set())`. Not persisted across refresh. Selection toolbar appears when `selected.size > 0`.
|
|
|
|
**Selection UI.**
|
|
- Checkbox on each row, left of existing content. Row click still opens the expanded view; only the checkbox toggles selection.
|
|
- A header bar surfaces when selection is non-empty: "`N selected` · Select all / Clear · Export selected · Delete selected".
|
|
- "Delete selected" uses existing `deleteFromHistory`, looped, with one confirm.
|
|
|
|
**Bulk export.**
|
|
- `save` dialog in directory mode: `await open({ directory: true, title: "Choose export folder" })`.
|
|
- For each selected item, call the same `buildMarkdown` path, then `write_text_file_cmd({ path: `${dir}/${suggestedFilename(item)}`, contents })`.
|
|
- Collision handling: if a filename already exists in the chosen dir, append ` (N)` suffix until unique. Implement in the frontend (single OS-call query per filename is simpler than a Rust batch).
|
|
- Toast on completion: "Exported N transcripts to …".
|
|
|
|
**Keyboard.** `Escape` clears selection. `Ctrl/Cmd+A` selects all visible rows when the HistoryPage has focus.
|
|
|
|
### Item 3 — LLM content tags
|
|
|
|
**Data model.** Add `llmTags: string[]` to history entries alongside `manualTags`. Persisted via a real SQLite column `transcripts.llm_tags TEXT NOT NULL DEFAULT ''` added in migration v14, exposed through an extended `update_transcript` Tauri command. (Earlier draft of this spec assumed the existing `saveHistory()` path was a real persist; closer review showed it's a no-op stub. The migration + command extension is in-scope as Task 8.5 of the plan, and additionally fixes a latent bug whereby existing `manualTags` edits weren't persisting either.)
|
|
|
|
**Tag schema.**
|
|
- `topic:<1-3 noun phrase>` — free-form, lowercase, hyphen-joined. Examples: `topic:interview-prep`, `topic:grant-application`, `topic:personal-finance`. Limit one per transcript (the dominant subject).
|
|
- `intent:<enum>` — closed set: `planning | reflection | venting | capture | decision | question`. Limit one.
|
|
|
|
**Generation.** New function in `crates/llm/src/prompts.rs`:
|
|
|
|
```rust
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ContentTags {
|
|
pub topic: String, // hyphen-joined, lowercase, 1-3 tokens
|
|
pub intent: String, // one of INTENT_CLOSED_SET
|
|
}
|
|
|
|
pub const INTENT_CLOSED_SET: &[&str] = &[
|
|
"planning", "reflection", "venting", "capture", "decision", "question",
|
|
];
|
|
|
|
pub async fn extract_content_tags(
|
|
engine: &LlamaEngine,
|
|
transcript: &str,
|
|
) -> Result<ContentTags, EngineError>;
|
|
```
|
|
|
|
Grammar-constrained GBNF in `crates/llm/src/grammars.rs` that restricts output to the schema:
|
|
|
|
```
|
|
root ::= "{" ws "\"topic\":" ws topic "," ws "\"intent\":" ws intent ws "}"
|
|
topic ::= "\"" [a-z0-9-]{3,60} "\""
|
|
intent ::= "\"planning\"" | "\"reflection\"" | "\"venting\"" | "\"capture\"" | "\"decision\"" | "\"question\""
|
|
ws ::= [ \t\n]*
|
|
```
|
|
|
|
Prompt sketch (British English, deterministic, temperature 0.0):
|
|
|
|
```
|
|
You tag a transcript with ONE topic and ONE intent.
|
|
|
|
TOPIC is a 1-3 token lowercase hyphen-joined noun phrase naming the dominant
|
|
subject. Examples: "interview-prep", "grant-application", "daily-standup".
|
|
|
|
INTENT is exactly one of: planning, reflection, venting, capture, decision,
|
|
question.
|
|
|
|
Return JSON only, with this exact shape:
|
|
{"topic":"...","intent":"..."}
|
|
|
|
Transcript:
|
|
<<<
|
|
{{transcript}}
|
|
>>>
|
|
```
|
|
|
|
Guardrails:
|
|
- Truncate transcript to the last 2000 chars if longer (LLM context budget).
|
|
- On any error (JSON-parse, grammar rejection, timeout): return `Err`. Caller surfaces "Tagging failed — try again" toast, writes nothing.
|
|
- Re-tagging a previously-tagged row replaces `llmTags`; no history.
|
|
|
|
**Tauri command.** `extract_content_tags_cmd(transcript: String) -> Result<ContentTags, String>` in `src-tauri/src/commands/llm.rs` (co-located with existing LLM commands, or a new file if absent).
|
|
|
|
**Frontend UX.**
|
|
- "Tag" button per history row, in the row's action cluster (next to "Export .md"). Icon: `Tag` from `lucide-svelte`.
|
|
- Click → spinner → on resolve: `llmTags = [`topic:${topic}`, `intent:${intent}`]`, `saveHistory()`.
|
|
- Chips render in the tag area alongside manual tags but with a distinct visual treatment: `border-dashed`, italicised, subtly lighter ink. Click-through to promote to manual (`manualTags`), with animation-free chip move.
|
|
- Batch affordance: on the History toolbar, "Tag all untagged" button (only shows if ≥ 1 untagged row). Iterates and calls the same command. Progress toast: "Tagged N / M…" updating every item.
|
|
|
|
### Item 4 — Settings restructure
|
|
|
|
**Pattern.** Single page (`SettingsPage.svelte`), flat scroll, no sidebar or tabs. Top block always expanded. Below it, six `<details>` groups each with a clear `<summary>` label.
|
|
|
|
**Architecture.**
|
|
- New file `src/lib/components/SettingsGroup.svelte` — reusable `<details>` wrapper with styled chevron, summary padding, animated open/close (`@starting-style` + `interpolate-size: allow-keywords` for modern browsers; fall back to no animation).
|
|
- Search box at the page top (new). Filters the visible controls by label text; empty filter shows everything. Non-matching groups collapse; matching groups open.
|
|
- Settings content itself is not rewritten — only re-ordered + re-grouped. Each control keeps its current markup and behaviour.
|
|
|
|
**Group membership.**
|
|
|
|
| Group | Expanded by default? | Contents |
|
|
|---|---|---|
|
|
| **Start here** | Yes | Model download + status, microphone picker, global hotkey, theme (light/dark/system) |
|
|
| **Transcription** | No | Whisper / Parakeet backend, language, punctuation, vocabulary profile, file-upload defaults |
|
|
| **Tasks** | No | Match-my-energy default, WIP limit, **Show momentum sparkline** *(Phase 8, relocated)*, MicroSteps generation preset |
|
|
| **Rituals** | No | Morning triage on/off + time, Evening shutdown on/off |
|
|
| **Notifications** | No | Notifications enabled/muted, per-trigger toggles (inactivity, pending triage, micro-step idle), TTS nudge voice + rate |
|
|
| **Accessibility** | No | Font size, line height, bionic reading, reduced motion, high contrast |
|
|
| **Advanced** | No | Database path / open-data-dir button, export app data, reset settings, model tier override, debug logging |
|
|
|
|
**Phase 8 toggle relocation.** The `showMomentumSparkline` toggle moves from its current Rituals placement into the new **Tasks** group. Discovered in Phase 8 code review (carryover backlog).
|
|
|
|
**Open/close state.** Not persisted — groups reset to default on each app launch. Persisting open-state adds storage noise for marginal value; users who live in Settings can bookmark their group with a search query instead.
|
|
|
|
### Item 5 — Visual polish (bounded checklist)
|
|
|
|
Scoped list. Not an open-ended pass.
|
|
|
|
- **Motion on Phase 8 badge + sparkline.** Badge enters via a 180 ms opacity + 2 px translate-y on `todayCount` increment. Sparkline bars ease in with a 30 ms stagger on mount only (not per refresh). `prefers-reduced-motion` disables both.
|
|
- **Typography scale audit.** One read-through of every page's `<h1>/<h2>/<h3>`; normalise to the three-step scale already used on `TasksPage`. No new fonts.
|
|
- **Dark-mode parity check.** Toggle every page in dark mode once. Fix contrast regressions against WCAG AA minimum (see §Accessibility) and fix any baked-in `text-black` / `text-white` strings to `text-text` / `text-text-inverse`.
|
|
- **Spacing pass on SettingsPage only.** Consistent `py-3` rows inside groups, `gap-4` between label + control, `pt-6` on group open. Other pages already converged in Phase 2.
|
|
- **Lucide icon audit.** Any custom SVG chip or inline path that could swap to a Lucide icon — swap for visual consistency. List before changing (expect ≤ 5 swaps).
|
|
|
|
Deliberately **out of scope**: redesigning layouts, introducing new colours, changing the typographic scale, animating page transitions.
|
|
|
|
### Item 6 — Accessibility (bounded checklist)
|
|
|
|
- **Sparkline aria-label rewrite.** Replace numeric list (`"0, 1, 3, 2, 0, 4, 3"`) with friendlier summary: `"3 completed today. 14 total over the last 7 days."` Expose to any SR on focus of the sparkline SVG (add `tabindex="0"`, keyboard-focusable).
|
|
- **Sparkline per-day tooltip.** On hover / keyboard-focus of each bar, show `<title>` with absolute date + count. Purely additive; doesn't change the existing aria-label structure.
|
|
- **Keyboard traversal of every Phase 1-8 page.** Tab order starts top-left, ends bottom-right. No hidden focus sinks. Checklist per page.
|
|
- **Focus-visible rings.** Every interactive element has a visible focus ring in both themes. The existing Tailwind `focus-visible:` ring utility is present but inconsistently applied — sweep.
|
|
- **Screen reader labels on icon-only buttons.** Every `<button>` whose content is a Lucide icon gets an `aria-label`. Run a `grep -n 'aria-label' src/lib/pages src/lib/components` audit; flag missing ones.
|
|
- **Contrast audit against WCAG AA.** Use the Chromium DevTools contrast panel on every page-level text colour in both themes. Target ratios: 4.5:1 body, 3:1 large headings / UI. Fix variables in the Tailwind theme, not per-site overrides.
|
|
- **`prefers-reduced-motion` respected on every animation** added in Phase 5-9. Timers, sparkline, badge, settings accordion all guard the motion block.
|
|
|
|
## Testing
|
|
|
|
### Rust (`cargo test` in the touched crates)
|
|
- `write_text_file_cmd` — round-trip a small UTF-8 string; assert file contents match and that nonexistent parent dir errors with a readable message.
|
|
- `extract_content_tags` — integration test in `crates/llm/tests/` against a fixture transcript. Uses the real engine with a tiny tier-0 model. Assert topic matches a regex, intent is in the closed set, JSON parse succeeds.
|
|
- Grammar unit test — feed a few malformed completions through the grammar parser and assert rejection.
|
|
|
|
### Frontend (`npm run check`)
|
|
- `suggestedFilename` tests (new `src/lib/utils/saveMarkdown.test.ts` if a vitest config appears; otherwise covered in smoke dogfood).
|
|
- `svelte-check` must be zero-zero across 3955+ files.
|
|
|
|
### Manual dogfood (folds into Phase 10a QC)
|
|
- Export one transcript via save dialog. File written to chosen path. Cancel dialog → no toast, no write.
|
|
- Select 3 history rows, export to a folder. Three files land with `suggestedFilename` names. Collision: repeat the export to the same folder; confirm ` (2)` / ` (3)` suffixes appear.
|
|
- Tag one history row. Chip appears with dashed border. Click to promote; chip moves to manual and the dashed border disappears.
|
|
- "Tag all untagged" processes remaining rows; toast updates progress; no duplicates.
|
|
- Settings page: top group expanded; other groups collapsed. Search "sparkline" — filters to the Tasks group, opens it. Clear search — returns to default state.
|
|
- Keyboard-only traversal of Dictation → Tasks → History → Settings pages. Every control reachable. Focus rings visible in both themes.
|
|
- `prefers-reduced-motion` on — badge / sparkline / details animations all stop.
|
|
|
|
### Automated a11y gate (new)
|
|
- Add a one-shot axe-core run to the dev cycle via `@axe-core/cli` invoked on `npm run build`'s preview URL. Document in HANDOVER but don't block merge on it (axe flags non-determinism that needs human triage); the CI gate stays `npm run check` + `cargo test`.
|
|
|
|
## Invariants
|
|
|
|
- `write_text_file_cmd` is only called with a path that came from a save / open dialog. Never hard-coded, never concatenated from user text input elsewhere. (Path-traversal risk is mitigated at the UI layer.)
|
|
- LLM tags are additive to `manualTags`, never replace them. Promoted LLM tags leave `llmTags` untouched (no moving-between-arrays mutation).
|
|
- Settings groups reset to their default expansion on each launch. The search box is the only persistence surface, and even it is not persisted.
|
|
- No new user-facing string anywhere violates §Conventions: British English, no em / en dashes.
|
|
- Motion in all Phase 9 additions respects `prefers-reduced-motion`.
|
|
|
|
## Acceptance (from roadmap)
|
|
|
|
All of:
|
|
|
|
1. Export one transcript from HistoryPage → `save()` dialog opens → file is written to the chosen path.
|
|
2. Select 3 rows → Export selected → directory of 3 `.md` files lands in the chosen folder.
|
|
3. Click "Tag" on one row → `topic:*` + `intent:*` chips appear within a few seconds.
|
|
4. Settings page — top "Start here" group expanded; every other group collapsed; search filters + opens matching groups.
|
|
5. Phase 8 sparkline toggle appears in Tasks group, not Rituals.
|
|
6. Every interactive element in Dictation / Tasks / History / Settings is reachable by keyboard with a visible focus ring in both themes.
|
|
7. Sparkline SR label reads: "3 completed today. 14 total over the last 7 days." (or the current day's equivalent numbers).
|
|
8. `prefers-reduced-motion` disables badge entrance + sparkline stagger + settings accordion animation.
|
|
|
|
## Out of scope
|
|
|
|
- Atomic file writes (rename-after-write) for the save dialog — a mid-write crash on a transcript export is not data-loss territory; the source stays intact in the History store.
|
|
- Server-side or cloud tag storage. Tags stay local.
|
|
- Re-training LLM on user corrections to tags. Tags are one-shot outputs.
|
|
- Search-across-transcripts UI. Settings search is the only search surface added in Phase 9.
|
|
- Moving SettingsPage to multiple files / router sub-routes. Stays as one scroll-view with groups.
|
|
- Per-group open-state persistence (covered under "Open/close state" above).
|
|
|
|
## File map
|
|
|
|
### Created
|
|
- `src-tauri/src/commands/fs.rs` *(or addition to existing commands file)* — `write_text_file_cmd`.
|
|
- `src/lib/utils/saveMarkdown.ts` — `suggestedFilename`, `saveTranscriptAsMarkdown`, `exportTranscriptsToDir`.
|
|
- `src/lib/components/SettingsGroup.svelte` — `<details>` wrapper with chevron + animation.
|
|
- `crates/llm/tests/content_tags_smoke.rs` — integration test for `extract_content_tags`.
|
|
- `docs/superpowers/plans/2026-04-24-phase9-polish-debt.md` — implementation plan (companion to this spec).
|
|
|
|
### Modified
|
|
- `src-tauri/src/lib.rs` — register `write_text_file_cmd` + `extract_content_tags_cmd`.
|
|
- `crates/llm/src/prompts.rs` — add `ContentTags`, `INTENT_CLOSED_SET`, `extract_content_tags`.
|
|
- `crates/llm/src/grammars.rs` — add content-tag GBNF grammar.
|
|
- `crates/llm/src/lib.rs` — re-export the new symbols.
|
|
- `src-tauri/src/commands/*.rs` — add `extract_content_tags_cmd` (file choice depends on the existing LLM command placement).
|
|
- `src/lib/pages/HistoryPage.svelte` — save dialog, bulk select, bulk export, LLM tag button, llmTags chip rendering.
|
|
- `src/lib/pages/SettingsPage.svelte` — full regrouping into the 7 groups (Start here + 6 collapsed). Sparkline toggle relocation.
|
|
- `src/lib/types/app.ts` — `llmTags` field on history entry type; optional `ContentTags` type.
|
|
- `src/lib/stores/page.svelte.ts` — hydrate / persist `llmTags` alongside `manualTags`.
|
|
- `src/lib/utils/frontmatter.ts` — include `llmTags` in `buildFrontmatter`'s `tags` union.
|
|
- `src/lib/components/CompletionSparkline.svelte` — friendlier aria-label, per-bar `<title>` tooltips, `tabindex="0"` for SR focus.
|
|
- `src/lib/pages/TasksPage.svelte` — badge motion on increment (behind `prefers-reduced-motion`).
|
|
- Tailwind theme if contrast audit forces colour variable changes.
|
|
|
|
### Deleted
|
|
None.
|
|
|
|
## Effort estimate
|
|
|
|
1 to 2 days of focused work. Items 1 + 2 ≈ half day. Item 3 ≈ half day (includes the grammar + smoke test). Item 4 ≈ half day (the regrouping + search). Items 5 + 6 ≈ half day combined (checklists, not discoveries).
|
|
|
|
Natural split for the plan: 9a (items 1 + 2), 9b (item 3), 9c (item 4), 9d (items 5 + 6).
|
|
|
|
## Anchors
|
|
|
|
- Roadmap: [docs/roadmap/2026-04-23-lumotia-feature-complete-roadmap.md](../../roadmap/2026-04-23-lumotia-feature-complete-roadmap.md)
|
|
- Phase 8 plan + spec + handover — for task-format reference and carryover backlog.
|
|
- Phase 8 gotchas to respect in this phase:
|
|
- `$derived` does not export at `.svelte.ts` module scope — use `export function` instead.
|
|
- `#[derive(sqlx::FromRow)]` is not available in `lumotia-storage` (no change in Phase 9, but noted if schema work drifts there).
|
|
- Progressive-disclosure pattern reference: NN/g on progressive disclosure; Material Design settings patterns; UX Collective on settings redesign.
|