Move unbounded Stories and Audiobook project data to revisioned IndexedDB storage with bounded local fallback, durable clear tombstones, migration/recovery safeguards, and deadline-safe persistence before exits and relaunches.
Closes#1636.
Adds oxfmt (Rust formatter, Prettier-conformant) — the repo had no formatter, so
this is a one-time normalization of the JS/TS/JSX code (257 files; purely
cosmetic — full suite stays 638/638).
Scope is deliberately narrowed in .oxfmtrc.json to JS/TS/JSX only:
- singleQuote:true + jsxSingleQuote:false — preserve the project's existing
style (single-quoted JS, double-quoted JSX attrs), not oxfmt's double-quote
default. (Flipping quotes globally also broke a source-string-parsing test;
preserving them keeps featureCoverage green.)
- Excludes **/*.css (the CSS→Tailwind migration will rewrite those — formatting
them now is wasted churn), **/*.json (avoids reformatting 20 i18n locale
files + config), **/*.toml, and src-tauri/** (Rust/Tauri config — out of scope
for a frontend JS formatter; oxfmt was reformatting Cargo.toml/tauri.conf.json).
Tooling:
- `bun run format` (write) / `bun run format:check` (verify).
- ci.yml: new "Frontend format check (oxfmt)" gate after the oxlint gate.
Verified: format:check clean; oxlint 0 errors; vite build; full suite 638/638;
bun install --frozen-lockfile in sync.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(spec): updates-in-status-bar design (move pill to LogsFooter + Updates panel)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(plan): updates-in-status-bar implementation plan (11 tasks, TDD)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(plan): pin i18n task to scripts/translate_all.py backfill
* feat(update): pure chip + release presentation helpers
* feat(update): listReleases + fetchAppVersion wrappers
* feat(update): transient releasesSlice composed into store
* feat(update): app version + channel in updaterSlice
* feat(update): list_releases Tauri command (GitHub releases)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(update): UpdateStatusChip bar indicator
* feat(update): UpdatesPanel (live row + channel + releases list)
* feat(update): mount chip+panel in LogsFooter, retire floating UpdateBadge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(update): Settings channel switcher shares store value (auto-sync)
* i18n(update): add updates.* keys across 21 locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(deps): lock reqwest for list_releases command
* polish(update): a11y radiogroup on channel switch, safer release key, drop dead test seam
Addresses final-review nits (non-blocking): role=radiogroup/radio + aria-checked
on the channel Segmented; key={r.name||r.version} to avoid collisions; remove the
unused vi import + __loader seam in releasesSlice.test.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(update): i18n the channel-set error + correct flagged updates.* translations (PR #240 review)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(update): add 10s timeout to list_releases HTTP client (PR #240 review)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(update): guard chip Restart against in-flight dub job (greptile P1, PR #240)
The always-visible status chip's one-click Restart (ready state) called
installUpdate→relaunch without the dub-busy guard the panel uses, so a user
with a dub/transcription job running could lose in-flight work. Mirror the
panel's gate: toast update.busy and bail when dubStep === 'generating'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(update): surface channel-switch failures in the Updates panel (greptile, PR #240)
Mirror Settings' error handling: the panel's stable/preview switch now catches a
failed set_update_channel and toasts settings.channel_set_failed instead of an
unhandled rejection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(update): keep error/retry pill across periodic re-check (#214 follow-up)
PR #214's 6h periodic re-check guard skipped only downloading/ready, not
error. setUpdateChecking() clears updateError and the badge renders null
for 'checking', so a tick while 'Update failed · Retry' was showing
silently erased the prompt the user still needed to act on — defeating
the PR's own error-surfacing goal (greptile P1, unresolved).
- updater.js: also short-circuit the re-check on 'error'. Retry is
user-initiated (installUpdate → downloading), so auto re-check is
unnecessary in that state.
- updater.test.js: new regression test — guard no-ops on
error/downloading/ready, proceeds from idle.
- UpdateBadge.jsx: add aria-controls + panel id to the 'What's new'
disclosure (greptile P2 a11y).
- UpdateBadge.css: word-break:break-word → overflow-wrap:break-word
(CodeRabbit; the deprecated value).
Verified: vitest 166/166, typecheck:ci clean, bun run build OK, CJK guard pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(update): keep notes panel mounted so aria-controls always resolves
greptile P2: aria-controls pointed at a conditionally-rendered panel, so
the IDREF dangled while collapsed. Render the panel whenever notes exist
and toggle with the hidden attribute (canonical disclosure pattern) — the
reference now always resolves.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>