diff --git a/AGENTS.md b/AGENTS.md
index 76fc1a6b..bd657369 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,7 +11,7 @@
- **Primary deploy target:** Static SPA on GitHub Pages (`/WorldScript-Studio/` base path)
- **Secondary targets:** Vercel (root base) and Cloudflare Pages via edge builds (`pnpm run build:edge`)
- **Desktop:** Tauri 2 bundles for Linux (AppImage), macOS (DMG), and Windows (MSI); auto-updater enabled via `latest.json`
-- **Version:** `1.27.0`
+- **Version:** `1.27.1`
- **License:** MIT
The app supports a multi-provider AI stack (Gemini, OpenAI, Claude, Grok, OpenRouter, Ollama, WebLLM, ONNX Runtime Web, Transformers.js), four AI execution modes (Hybrid / Cloud / Local / Eco), real-time collaboration with E2E encryption, a Plot Board v2 with swimlane/canvas/timeline modes, character/world management, manuscript export, voice dictation, and a 19-locale i18n layer.
diff --git a/AUDIT.md b/AUDIT.md
index 612d1a57..ed534b75 100644
--- a/AUDIT.md
+++ b/AUDIT.md
@@ -1,8 +1,10 @@
# WorldScript Studio — Codebase Audit Report
-**Date:** 2026-04-17 (baseline); **follow-up chain:** … → 2026-05-28 (v1.19.0 — Security/Voice/RTL/Logger B-1..B-8) → **2026-05-30 (B-1 passphrase UX + CI unblock)** → **2026-05-31 (i18n audit + settings features + CI stabilization)** → **2026-05-31 (Edge-AI Perfection Cycle — Phases 0-7 complete)** → **2026-06-01 (Post-crash session: CI stabilisation + 14 CodeAnt AI fixes + E2E hardening)** → **2026-06-02 (Perf Phase 2.3 — pipeline-LRU unification + PR #69 CodeAnt fixes)** → **2026-06-03 (WorkerBus v2 Phase 3 — Rust TaskSupervisor + Tauri-build unblock)** → **2026-06-06 (Phase 3 i18n Expansion — ja/zh/pt/el + Intl APIs)** → **2026-06-09 (v1.21 Deep Audit Correction — Whisper WASM download UI + 3 CodeAnt fixes + CloudSync LWW)** → **2026-06-09 (feat/deep-audit-v1.21 — CSP hardening, zh locale ≤5% EN, coverage Batches A/B/C, VoiceActivityCoordinator B-2 bridge)** → **2026-06-11 (Ultimate Copilot v2 Phase 2+3 — markdown, sidebar, Apply-to-chapter, InlineAnnotation, ProForge chip; PR #110+#111)** → **2026-06-11 (v1.22.0 release — OpenRouter Cloud 5 provider, AI Execution Modes hybrid/cloud/local/eco, AiModeIndicator, SW cache-invalidation fix)** → **2026-06-13/14 (v1.23 perfection batch — OpenRouter + AI-Execution-Mode settings sections localized/modernized, i18n single-brace interpolation bug-class fix + `i18nPlaceholders` regression guard, bundle split + budget tightening PR #130)** → **2026-06-16 (v1.23.0 release — rebrand StoryCraft → WorldScript Studio, local-first data foundation ADR-0008, Tauri blank-screen + asset-URL fixes, AI error taxonomy + retry hardening, command-palette & local-AI settings localization, WorldScript W monogram icons)** → **2026-06-16 (post-release documentation perfection pass — corpus sync, metric reconciliation, history archival, dependabot hardening)** → **2026-06-17 (Language expansion — +6 locales fi/sv/hu/is/eu/fa (RTL); PR #174 merged; `LanguageSelector` exonym localization via `portal.language.names.*`; portal chrome 100 % for the 6 new langs; README/AUDIT/CHANGELOG docs sync)** → **2026-07-28 (v1.24.1 — Local-AI reliability fixes #266, Dependabot backlog triage, Issue #60 audit closeout, security/build hardening)** → **2026-07-29 (v1.24.2, PR #284 — CSP functional-truth, desktop crypto, and doc-truth hardening; the local-inference stack had been silently non-functional in production for two months; merged and tagged)** → **2026-07-30 (v1.24.3, WorkerBus v2 worker-generation consolidation — 5 stacked PRs #286–288/#290–291 + independent #289 recovered after an interrupted merge queue; ADR-0015; merged and tagged)** → **2026-08-12 (encryption lifecycle + desktop reliability + recovery journal — stacked PRs #335/#336/#337 merged into `main`; fail-closed IDB encryption lifecycle, desktop AI/Python provider hardening for #332/#333, durable resumable migration journal with secondary-store adapters; PR #339 closes the write-vs-migration TOCTOU race CodeRabbit and Qodo independently found on #337; PR #310 closed as superseded; Phase-4 production wiring for disable/rotate tracked as issue #338)** → **2026-08-13 (v1.27.0 — Phase 4 encryption disable/rotate production wiring + i18n (#342/#343 close issue #338), Tauri desktop boot-hydration + flush-on-quit + Settings re-render perf fixes (#332, PR #345), AI Writing Studio unreadable-text fix (#341, PR #344), download-progress bytes/speed for voice + WebLLM models (#333 item 1, PR #346); all 5 PRs' correction loops ran to full quiescence — codebase-wide QNBS-v3/i18n fixes, a real data-integrity bug found and fixed in the rekey-recovery try/catch scoping, and sequential main-reconciliation merges across the whole 5-PR stack)**
+**Date:** 2026-04-17 (baseline); **follow-up chain:** … → 2026-05-28 (v1.19.0 — Security/Voice/RTL/Logger B-1..B-8) → **2026-05-30 (B-1 passphrase UX + CI unblock)** → **2026-05-31 (i18n audit + settings features + CI stabilization)** → **2026-05-31 (Edge-AI Perfection Cycle — Phases 0-7 complete)** → **2026-06-01 (Post-crash session: CI stabilisation + 14 CodeAnt AI fixes + E2E hardening)** → **2026-06-02 (Perf Phase 2.3 — pipeline-LRU unification + PR #69 CodeAnt fixes)** → **2026-06-03 (WorkerBus v2 Phase 3 — Rust TaskSupervisor + Tauri-build unblock)** → **2026-06-06 (Phase 3 i18n Expansion — ja/zh/pt/el + Intl APIs)** → **2026-06-09 (v1.21 Deep Audit Correction — Whisper WASM download UI + 3 CodeAnt fixes + CloudSync LWW)** → **2026-06-09 (feat/deep-audit-v1.21 — CSP hardening, zh locale ≤5% EN, coverage Batches A/B/C, VoiceActivityCoordinator B-2 bridge)** → **2026-06-11 (Ultimate Copilot v2 Phase 2+3 — markdown, sidebar, Apply-to-chapter, InlineAnnotation, ProForge chip; PR #110+#111)** → **2026-06-11 (v1.22.0 release — OpenRouter Cloud 5 provider, AI Execution Modes hybrid/cloud/local/eco, AiModeIndicator, SW cache-invalidation fix)** → **2026-06-13/14 (v1.23 perfection batch — OpenRouter + AI-Execution-Mode settings sections localized/modernized, i18n single-brace interpolation bug-class fix + `i18nPlaceholders` regression guard, bundle split + budget tightening PR #130)** → **2026-06-16 (v1.23.0 release — rebrand StoryCraft → WorldScript Studio, local-first data foundation ADR-0008, Tauri blank-screen + asset-URL fixes, AI error taxonomy + retry hardening, command-palette & local-AI settings localization, WorldScript W monogram icons)** → **2026-06-16 (post-release documentation perfection pass — corpus sync, metric reconciliation, history archival, dependabot hardening)** → **2026-06-17 (Language expansion — +6 locales fi/sv/hu/is/eu/fa (RTL); PR #174 merged; `LanguageSelector` exonym localization via `portal.language.names.*`; portal chrome 100 % for the 6 new langs; README/AUDIT/CHANGELOG docs sync)** → **2026-07-28 (v1.24.1 — Local-AI reliability fixes #266, Dependabot backlog triage, Issue #60 audit closeout, security/build hardening)** → **2026-07-29 (v1.24.2, PR #284 — CSP functional-truth, desktop crypto, and doc-truth hardening; the local-inference stack had been silently non-functional in production for two months; merged and tagged)** → **2026-07-30 (v1.24.3, WorkerBus v2 worker-generation consolidation — 5 stacked PRs #286–288/#290–291 + independent #289 recovered after an interrupted merge queue; ADR-0015; merged and tagged)** → **2026-08-12 (encryption lifecycle + desktop reliability + recovery journal — stacked PRs #335/#336/#337 merged into `main`; fail-closed IDB encryption lifecycle, desktop AI/Python provider hardening for #332/#333, durable resumable migration journal with secondary-store adapters; PR #339 closes the write-vs-migration TOCTOU race CodeRabbit and Qodo independently found on #337; PR #310 closed as superseded; Phase-4 production wiring for disable/rotate tracked as issue #338)** → **2026-08-13 (v1.27.0 — Phase 4 encryption disable/rotate production wiring + i18n (#342/#343 close issue #338), Tauri desktop boot-hydration + flush-on-quit + Settings re-render perf fixes (#332, PR #345), AI Writing Studio unreadable-text fix (#341, PR #344), download-progress bytes/speed for voice + WebLLM models (#333 item 1, PR #346); all 5 PRs' correction loops ran to full quiescence — codebase-wide QNBS-v3/i18n fixes, a real data-integrity bug found and fixed in the rekey-recovery try/catch scoping, and sequential main-reconciliation merges across the whole 5-PR stack)** → **2026-08-14 (v1.27.1 — desktop persistence/security stabilization; #363: atomic writes across every Tauri fs-backed store, unified fail-closed desktop API-key routing, factory-reset UI/logic consolidated into `useFactoryReset` + `FactoryResetDangerZone`, packaged-build factory-reset capability-scope fix, `#[cfg(desktop)]`-gated menu-event handler + re-wired native menu-action bridge, `rust-tauri`/`e2e`/`vrt` promoted to required CI gates; plus the nanoid security-advisory patch, PR #362)**
**Scope:** Full application, repository configuration, CI/CD, documentation, release validation
-**Current version:** **v1.27.0** — Phase 4 closes out the encryption lifecycle feature (issue #338: production disable/rotate wiring, now user-facing in Settings › Privacy), alongside three independently-shipped reliability/UX fixes for #332, #333, and #341. **2919 keys × 19 locales**. Historical release sections below remain historical.
+**Current version:** **v1.27.1** — Desktop persistence/security stabilization (#363: atomic writes, fail-closed key routing, factory-reset consolidation, packaged-build capability fix, Rust/E2E/VRT promoted to required CI gates) plus the nanoid security-advisory patch (#362). **2919 keys × 19 locales**. Historical release sections below remain historical.
+
+**Quality gate (2026-08-14 — v1.27.1):** lint ✅ · typecheck ✅ (tsgo) · i18n:check ✅ (**2919 keys × 19 locales**) — verified locally per the CI-cloud-first workflow; full CI suite (Quality Gate Node 22+24, rust-tauri, Build, E2E, E2E Deep Coverage, Storybook, Lighthouse, Visual Regression) tracked on the `release/v1.27.1` → `main` PR before merge/tag.
**Quality gate (2026-08-13 — v1.27.0, 5-PR merge):** lint ✅ · typecheck ✅ (tsgo) · i18n:check ✅ (**2919 keys × 19 locales**) · targeted unit tests ✅ across all 5 PRs' correction loops (storageEncryptionService 75 tests, useSettingsView 38, VoiceModelDownloadModal/voiceCommandService/downloadProgressFormat, desktopTray/desktopMenu, ContextPanel/Textarea, and the full post-merge reconciliation suite) · CI Quality Gate (Node 22 + 24), E2E, E2E Deep Coverage, Build, Storybook, Lighthouse, and Visual Regression all green on every one of #342/#343/#344/#345/#346's final commits. #342 (disable/rotate production wiring), #343 (its stacked i18n), #344 (#341 fix), #345 (#332 fix), #346 (#333 item 1) all merged into `main`. Phase-4 (issue #338) is now fully closed — encryption disable and passphrase rotation are live in the Settings UI.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e943880c..dbb6e745 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [1.27.1] — 2026-08-14
+
+> Desktop persistence/security stabilization (#363) — atomic writes and fail-closed key routing
+> across every Tauri filesystem-backed store, a factory-reset UI/logic consolidation, and three
+> new required CI gates (Rust compile/lint/test, E2E, Visual Regression) that previously let
+> native-code and UI-regression risk merge unchecked — plus a nanoid security-advisory patch.
+
+### Fixed
+
+- **Atomic writes across all Tauri filesystem-backed stores** (`services/fs/{assetFsStore,
+ codexFsStore,fsCore,projectFsStore,settingsFsStore,snapshotFsStore}.ts`) — writes are now
+ serialized, and an orphaned temp file left behind by a failed write is retried on cleanup (with a
+ warning logged, not silently dropped) instead of risking corruption of concurrently-written
+ project data.
+- **Unified, fail-closed desktop API-key routing.** Desktop key storage now routes unconditionally
+ through the same encrypted IndexedDB path used on the web, with recovery-reset hardened to fail
+ closed rather than leaving a partially-wiped state; `docs/SECURITY-THREAT-MODEL.md` corrected to
+ describe the unconditional routing and the best-effort (not guaranteed) nature of legacy-key-file
+ cleanup.
+- **Factory reset / danger-zone UI consolidated.** New `hooks/useFactoryReset.ts` +
+ `components/settings/FactoryResetDangerZone.tsx` replace three near-duplicate confirm/wipe/error/
+ busy implementations across `ApiKeySection`, `IdbUnlockModal`, and `EncryptionRecoveryModal`; also
+ fixes a bug where the stuck "recovery-required" branch had no error paragraph to render a failed
+ reset's error state.
+- **Packaged-build factory reset was broken.** The Tauri fs capability glob for `fs:allow-exists`
+ and `fs:allow-read-dir` only covered `$APPDATA/**`, rejecting `exists()`/`readDir()` on the
+ `$APPDATA` root itself; the capability now grants the exact root path too
+ (`src-tauri/capabilities/default.json`).
+- **Corrupt binder-asset detection fixed.** A binary/metadata byte-size mismatch is now treated as
+ corruption instead of silently pairing two independent atomic writes from different save
+ generations as if they were one transaction.
+- **Legacy API-key file cleanup no longer provider-list-bound.** Scans the config directory by the
+ `*_key.enc.json` naming convention instead of a hardcoded 5-provider list, so a future or
+ unlisted provider's legacy key file is no longer left behind after migration.
+- **Rust: `Builder::on_menu_event` gated `#[cfg(desktop)]`.** The call was unconditional despite
+ the crate itself restricting that method to desktop, which would have failed to compile for the
+ mobile targets `src-tauri/src/lib.rs` already anticipates (`#[cfg_attr(mobile, ...)]`).
+- **Re-wired the dead Rust menu-action event bridge.** `registerTauriMenuHandler` is back as a
+ pre-paint-window fallback, self-unregistering once the JS-owned menu (`installDesktopMenu`) takes
+ over — the native fallback menu's items were previously silently inert, and the two menus can no
+ longer double-dispatch a single click.
+
+### Security
+
+- **nanoid security advisory patched** (PR #362) — `nanoid@3.3.17` → `3.3.18`, added to
+ `minimumReleaseAgeExclude` in `pnpm-workspace.yaml` so the fix isn't held back by the
+ 10,080-minute (7-day) `minimumReleaseAge` policy.
+
+### Changed
+
+- **CI: Rust compile/lint/test, E2E, and Visual Regression Testing are now required merge gates**
+ (`rust-tauri`, `e2e`, `vrt` join the `ci-success` aggregator's `needs`). Previously native Rust
+ code and UI regressions could merge into `main` with no automated check beyond an advisory OSV
+ vulnerability scan.
+
+### Tests
+
+- Added coverage for the new `clearTauriAppData` branch and the factory-reset danger-zone UI
+ (`tests/unit/factoryResetService.test.ts`, `tests/unit/settings/{EncryptionRecoveryModal,
+ IdbUnlockModal}.test.tsx`, `tests/unit/services/fs/{fsCore,fsStores}.test.ts`,
+ `tests/unit/tauriServices.test.ts`, `tests/unit/ApiKeySection.test.tsx`) — `codecov/patch` had
+ dropped to 52.75% before this pass.
+
## [1.27.0] — 2026-08-13
### Added
diff --git a/README.md b/README.md
index 93786834..54233af7 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
diff --git a/package.json b/package.json
index a3ae75e4..ccd63fc5 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "worldscript-studio",
"private": true,
- "version": "1.27.0",
+ "version": "1.27.1",
"description": "WorldScript Studio — offline-first, AI-powered creative writing application.",
"author": "QNBS",
"keywords": [
diff --git a/public/sw.js b/public/sw.js
index f1be5c9d..f6d47ff8 100644
--- a/public/sw.js
+++ b/public/sw.js
@@ -6,7 +6,7 @@
// ============================================================
// QNBS-v3 (CodeRabbit): version bump auto-synced from package.json by scripts/sync-sw-version.mjs — every CACHE_STATIC/CACHE_DYNAMIC name changes too, so this alone invalidates all prior caches on next activate.
-const APP_VERSION = '1.27.0';
+const APP_VERSION = '1.27.1';
const CACHE_STATIC = `worldscript-static-v${APP_VERSION}`;
const CACHE_DYNAMIC = `worldscript-dynamic-v${APP_VERSION}`;
const CACHE_IMAGES = `worldscript-images-v${APP_VERSION}`;
diff --git a/scripts/sync-tauri-version.mjs b/scripts/sync-tauri-version.mjs
index 406701db..f034f5ec 100755
--- a/scripts/sync-tauri-version.mjs
+++ b/scripts/sync-tauri-version.mjs
@@ -1,7 +1,10 @@
#!/usr/bin/env node
/**
- * Sync Tauri version from package.json → src-tauri/Cargo.toml + src-tauri/tauri.conf.json
- * QNBS-v3: Prevents version drift between web (package.json) and desktop builds.
+ * Sync version from package.json → src-tauri/Cargo.toml, src-tauri/tauri.conf.json,
+ * src-tauri/Cargo.lock (the workspace package's own locked entry), and AGENTS.md.
+ * QNBS-v3: Prevents version drift between web (package.json) and desktop builds/docs — a stale
+ * Cargo.lock entry fails `cargo check --locked` in CI's rust-check gate, and a stale AGENTS.md
+ * `Version:` field is agent-facing documentation drift (found by review-loop bots on PR #364).
* Run via predev / prebuild hooks or manually:
* node scripts/sync-tauri-version.mjs
*/
@@ -15,6 +18,8 @@ const root = path.join(__dirname, '..');
const pkgPath = path.join(root, 'package.json');
const cargoPath = path.join(root, 'src-tauri', 'Cargo.toml');
const tauriConfPath = path.join(root, 'src-tauri', 'tauri.conf.json');
+const cargoLockPath = path.join(root, 'src-tauri', 'Cargo.lock');
+const agentsPath = path.join(root, 'AGENTS.md');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
const version = pkg.version;
@@ -25,25 +30,27 @@ if (!version || typeof version !== 'string') {
let changed = false;
-// --- Cargo.toml ---
-const cargo = fs.readFileSync(cargoPath, 'utf8');
-const cargoRe = /^version = "[^"]+"/m;
-const cargoNew = cargo.replace(cargoRe, `version = "${version}"`);
-if (cargoNew !== cargo) {
- fs.writeFileSync(cargoPath, cargoNew);
- console.log(`[sync-tauri-version] ${cargoPath} → ${version}`);
- changed = true;
+function syncFile(filePath, pattern, replacement) {
+ const content = fs.readFileSync(filePath, 'utf8');
+ const next = content.replace(pattern, replacement);
+ if (next !== content) {
+ fs.writeFileSync(filePath, next);
+ console.log(`[sync-tauri-version] ${filePath} → ${version}`);
+ changed = true;
+ }
}
+// --- Cargo.toml ---
+syncFile(cargoPath, /^version = "[^"]+"/m, `version = "${version}"`);
+
// --- tauri.conf.json ---
-const tauriConf = fs.readFileSync(tauriConfPath, 'utf8');
-const tauriRe = /"version":\s*"[^"]+"/;
-const tauriNew = tauriConf.replace(tauriRe, `"version": "${version}"`);
-if (tauriNew !== tauriConf) {
- fs.writeFileSync(tauriConfPath, tauriNew);
- console.log(`[sync-tauri-version] ${tauriConfPath} → ${version}`);
- changed = true;
-}
+syncFile(tauriConfPath, /"version":\s*"[^"]+"/, `"version": "${version}"`);
+
+// --- Cargo.lock (the workspace package's own locked entry, not registry deps) ---
+syncFile(cargoLockPath, /(name = "worldscript-studio"\nversion = )"[^"]+"/, `$1"${version}"`);
+
+// --- AGENTS.md ---
+syncFile(agentsPath, /(\*\*Version:\*\* `)[^`]+(`)/, `$1${version}$2`);
if (!changed) {
console.log('[sync-tauri-version] Already in sync.');
diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock
index 83083f08..9017018a 100644
--- a/src-tauri/Cargo.lock
+++ b/src-tauri/Cargo.lock
@@ -6677,7 +6677,7 @@ dependencies = [
[[package]]
name = "worldscript-studio"
-version = "1.27.0"
+version = "1.27.1"
dependencies = [
"base64 0.23.1",
"candle-core",
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 1435ba94..5fed8d12 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "worldscript-studio"
-version = "1.27.0"
+version = "1.27.1"
description = "AI-powered creative writing and world-building application"
authors = ["QNBS"]
license = "MIT"
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index ee736c88..b1a8ba5c 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "WorldScript Studio",
- "version": "1.27.0",
+ "version": "1.27.1",
"identifier": "com.worldscript.studio",
"build": {
"frontendDist": "../dist",