All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
extract-zipeliminated from the dependency graph (Dependabot #86, GHSA-7pqw-9j4j-h8q3 / GHSA-jmr9-qjv8-65gv): the vulnerable package was reachable only via@lhci/cli's hard-pinnedlighthouse@12.6.1βpuppeteer-coreβ@puppeteer/browsers@2.xchain. Apnpm-workspace.yamloverride exact-pinslighthouseto13.4.1, whose modern@puppeteer/browsers@3.xdependency replacedextract-zipwithmodern-tarentirely β the package now has zero occurrences in the resolved graph. Lighthouse 13.4.1 requires Node>=22.19, so the repository's ownengines.nodefloor is raised to match. PR #682.- Tauri release build now fails fast on a plugin version mismatch:
tauri-build.yml's cross-platform bundle matrix (~45 min) previously started right after tag-signature verification, with no cheap check for the exact Rust/npm plugin mismatch that broke every platform'sv1.28.5release build. Added aparity-preflightjob (checkout + one dependency-free Node script, nopnpm install) that runscheck-tauri-plugin-versions.mjsbefore the bundle matrix starts, gated behindverify-release-tagso no repository code runs on an unverified release tag. On bothworkflow_dispatchand tag pushes. PR #684. - PWA: a failed precache can no longer displace a working service-worker generation (#525):
installpreviously caught acache.addAll()/admission-marker failure without rethrowing, so the browser still recorded the worker as successfully installed; it could later activate and claim clients with an incomplete cache and no working fallback.installnow rethrows on failure so the whole installation rejects β a worker that never reachesinstalledcan never activate, claim clients, or receive an update message. Theactivate-side admission check is kept as defense in depth, returning immediately (no pruning, noclients.claim()) if it is ever reached without a completed marker. Also fixed a duplicate-precache-request risk the lifecycle fix would otherwise have turned fatal: VitePWA's injected manifest independently discoversindex.html/offline.html/favicon.svg, colliding with the same files already listed explicitly inPRECACHE_URLS; these are now resolved and deduplicated at runtime (against each other too, not just the explicit list) before reachingcache.addAll(), while the manifest keeps its content-hash revision tracking for update detection. PR #699. - A governed PR can no longer merge without a CHANGELOG reference to itself: the completeness
gate in
check-doc-metrics.mjsonly enforced a PR-number reference in[Unreleased]after squash-merge, once the commit already carried(#N)β nothing stopped a governed PR from merging without ever adding the entry, even though its real PR number is knowable before merge. Recurred three times (#678β#679, #684β#685, #699β#700). A new pre-merge admission gate (.github/workflows/pr-changelog-reference.yml+check-pr-changelog-reference.mjs, run from the PR's base ref to prevent self-weakening) now fails a governed PR's CI unless[Unreleased]already references it asPR #<N>. PR #705.
- Post-release v1.28.6 truth sync: removed the now-stale release-candidate markers from
README.md and CHANGELOG.md now that the
v1.28.6tag and GitHub Release are published, and recorded real release-gate evidence in AUDIT.md for bothv1.28.6(main CI/CD run, CodeQL, the pre-tag exact-SHA Tauri qualification, tag-triggered Tauri/CI/Docker runs, published release assets) andv1.28.5(the desktop-build failure and its independently-successful Docker/GHCR publish). PR #681.
- Tauri plugin Rust/npm version parity restored:
tauri-plugin-httpandtauri-plugin-notificationhad drifted ahead of their npm counterparts (@tauri-apps/plugin-http,@tauri-apps/plugin-notification) after #661 bumped only the Rust side, failing every platform's Tauri release build. Bumped the npm packages to match; addedcheck-tauri-plugin-versions.mjs, a cheap CI guard catching this class of mismatch before the next release tag instead of at tag-triggered release time. PR #678. - CHANGELOG completeness-gate PR-number reference restored: the Tauri plugin-parity entry
above passed PR-branch CI but failed on resulting-
mainright after squash-merge. PR #678's number was already known before merge; only the final squash commit's SHA/subject did not exist yet. The PR-branch check does not enforce the current PR's own already-known number against[Unreleased], only resulting-main's commit history β so PR CI stayed green, and only after the squash commit landed onmaindiddocs:checkcorrectly flag the missing reference. PR #679.
This tagged release's desktop build never completed. The tag-triggered Tauri desktop release workflow failed on every platform (Windows/Linux/macOS) with the Rust/npm plugin version mismatch fixed in
[1.28.6]above, so no GitHub desktop Release or installer/updater assets were ever published forv1.28.5β those bundle jobs failed before producing assets. This was a desktop-build-specific failure: the separate Docker/GHCR publish workflow for this tag succeeded, so a container image forv1.28.5does exist. Thev1.28.5git tag itself is intentionally never deleted, moved, or re-tagged; it remains permanently bound to its original commit as the historical failed/incomplete desktop-release cut.v1.28.6is the corrected, complete release.
- Project schema-version classification (Slice A, #553):
PROJECT_SCHEMA_V1as a fresh production version marker, disjoint classification (LEGACY_UNVERSIONED/SUPPORTED_OLDER/UNSUPPORTED_OLDER/CURRENT/FUTURE/MALFORMED), raw/header parsing before typed parsing so aFUTUREdocument with a breaking shape still classifiesFUTURErather thanMALFORMED, and theLEGACY_TO_V1in-memory admission primitive. PR #618. - Schema-version classification observed on IDB project load (Slice B, 1/N, #553): the first ingress path wired to Slice A's classifiers, observation-only (classify and log, never alter load behavior, never throw), matching the established Core shadow-validation pattern. PR #619.
- Canonical document projection foundation: establishes the canonical parser/import/admission boundary that retains the original JSON text alongside a bounded typed projection, the foundation Slice C's admission primitive builds on.
- Explicit legacy-to-v1 admission primitive (#653): the non-destructive
LEGACY_TO_V1in-memory admission path recognizes, fully validates, losslessly overlays the V1 version marker, and revalidates the destination. PR #653. - Filesystem admission converged after #654 (#658): carries forward canonical filesystem admission and closes legacy editable-admission authority gaps. PR #658.
schemaVersionnow enforces raw integer grammar (#553): rejects rounded fractional schema versions and non-number schema-version tokens instead of silently coercing them. PR #621.- Canonical document projection now reuses its own parsed input instead of re-parsing, removing a redundant-parse divergence risk between the raw and typed projections.
- Projection failure now retains the raw header verdict instead of discarding the already-classified version header when the typed projection itself fails.
- Unsupported-project startup copy added alongside the existing migration-gap copy, so a project whose schema version this build cannot open at all gets its own distinct startup message rather than reusing the migration-gap wording.
- Migration-gap startup copy now distinguished from the unsupported-project case (#656): the two failure classes previously shared wording; each now gets copy specific to its actual cause. PR #656.
- PR-size exception governance hardened (#657): closes control gaps in how per-PR size-ceiling exceptions are recorded and validated. PR #657.
- Security docs no longer cite closed PR #356 as active desktop-encryption remediation:
docs/SECURITY-THREAT-MODEL.mdanddocs/IDB-ENCRYPTION.mdboth asserted PR #356 was the active/pending remediation for desktop plaintext storage after it was closed as superseded on 2026-08-18. Rewritten to anchor on the living Ledger-row-9/R-15 state instead of a PR number, andcheck-doc-metrics.mjsnow mechanically rejects an unqualified live/pending-remediation claim tied to a bare PR number in these two files. PR #673. - CHANGELOG completeness-check upgrade, backfill Unreleased (audit F-2, PR #674):
scanUnreleasedTruthpreviously accepted any non-empty[Unreleased]section forever, so a single unrelated bullet let arbitrarily many laterfeat/fix/perfcommits go completely undocumented β 13 real commits sincev1.28.4had gone unrecorded. It now requires every governed commit to be individually referenced by PR number or subject slug, naming any that aren't; this section was backfilled with all 13 currently-undocumented entries above. - Completeness-check review findings addressed: PR-number matching now requires a non-digit
boundary (a bare substring check let
#65satisfy#656), subject-slug matching is scoped to one changelog entry at a time instead of the whole section, each matched entry is claimed so one generic bullet can't simultaneously "document" multiple different commits, a numbered commit now requires its exact PR number rather than ever falling back to a slug match, and full per-commit completeness is enforced only outsidepull_requestCI context (an un-numbered commit there is exempted β a PR's own git-log range enumerates every commit unique to that branch, not the one commit that will exist after squash-merge, so a review-fix follow-up commit can't reference itself in advance β while an already-numbered commit from a separate, already-merged PR sitting in the same range stays fully enforced). - Stop reading the GitHub event name as the completeness check's own default: the
pull_request-context parameter defaulted to readingGITHUB_EVENT_NAMEdirectly, but that environment variable is ambiently visible to the Vitest test process too when the whole suite runs inside apull_request-triggered job β the pure function's default is now hardcodedfalse; only the real CLI invocation reads the actual environment.
- AI/session attribution now rejected in commits and PRs: a fail-closed guard
(
check-commit-attribution.mjs, a commit-msg hook, a pre-push scan of outgoing commits, and a CI check on the commit range and PR title/body) blocks Claude/Anthropic attribution trailers, session URLs, and generated-by footers before they can land in repository history. PR #672.
- Post-release v1.28.4 truth sync: removed the now-stale release-candidate markers from
README.md and CHANGELOG.md now that the
v1.28.4tag and GitHub Release are published, and recorded real release-gate evidence in AUDIT.md (main CI/CD run, CodeQL, GitHub Pages deploy, tag-triggered Tauri/CI/Docker runs, published release assets).
- PWA first-install unprompted reload:
clients.claim()on activation firedcontrollerchangeon the very first-ever page load, not only on genuine updates, causing every first-time visitor to undergo one automatic reload shortly after landing. Fixed via absolute-URL own-worker identification, shared-origin foreign-worker exclusion, persistent installation history, event queuing during classification, and recovery whengetRegistration()fails. A narrow residual β two tabs racing the very first-ever activation of this origin's service worker so closely that one tab's evidence reads as "already installed" β requires real cross-tab coordination and remains open as #614. Fixes #585, PR #613. - Service-worker cache reads are now positively ownership-scoped: every
caches.match()call in the fetch handler and offline fallback now specifies its owning cache by name, closing a shared-origin (e.g.qnbs.github.iohosting multiple projects) cache-read isolation gap. Fixes #514, PR #612. - Factory Reset could reboot back into Settings instead of Welcome Portal: a stale
view-carrying URL hash survived
wipeAllAppData()'s reload, andreadInitialView()read it with higher priority than checking whether a project still existed. PR #592. - Desktop project corruption is now preserve-first: a corrupt desktop project is quarantined
(moved to
quarantined-projectswith a collision-safe name) rather than risking deletion; the destructive IndexedDB-reset action is no longer offered for project-load failures. PR #542. - Desktop filesystem I/O errors now get a distinct, truthful recovery action: separated from corruption-quarantine and generic-storage-reset, so each failure class only exposes the action that's actually safe for it. PR #545.
- Intentionally cleared project metadata no longer reappears: an empty title/logline set by the user could be silently repopulated during returning-user bootstrap. PR #546.
- Factory Reset could report success while user data remained:
deleteDatabase()treated a genuineonerroror anonblockedevent (another connection still open) as success, sowipeAllAppData()could resolve without every database actually being deleted.onerrornow rejects, andonblockedwaits for the connection to close before giving up. PR #596.
- Welcome/Home dashboard WCAG AA contrast and a repaired
prefers-reduced-motioncascade (the override now correctly wins over equal-specificity base rules); default appearance preset is nowdefaultfor new and invalid-legacy sessions. Fixes #565, PR #609. - ManuscriptEditor deferred-mirror contrast raised from 4.45:1 to ~5.10:1. PR #560.
fflateZIP64-parsing DoS, used by this app's export path: overridden to0.8.3. PR #595.- Routine dependency maintenance:
xmldom/fast-uri/qsfloor bumps (PR #587),logcrate insrc-tauri(PR #561),codeql-actiongroup (PR #562),actions/setup-node(PR #594).
- R-15 secure desktop storage β design contract admitted, implementation not started: the full storage/migration/recovery contract (S5-A, S5-B1, S5-B2, S5-B3) is now defined and its cross-contract consistency audited. No code has shipped yet; desktop project files remain documented as plaintext until this implementation lands. PRs #564, #580, #581, #582, #584.
- Visual regression testing repaired: VRT baselines were directory listings, not the application β the gate was not protecting against real visual regressions. Now reaches actual app routes with real baselines and a proven negative control demonstrating a visible regression fails the gate. PR #610.
- Async, generation/epoch-based IDB reset-quiescence contract across 9 service modules, closing every long-lived connection before a factory reset deletes anything (PR #596; issue #532's own WelcomePortal entry-nondeterminism root cause is a related but distinct class and remains open). WelcomePortal E2E recovery navigation made locale-independent (PR #590).
- Post-release v1.28.2 truth sync: removed the now-stale release-candidate markers from
README.md and CHANGELOG.md, and corrected AUDIT.md's release-gate entry (macOS artifacts don't
each get a Minisign
.sigβ only.app.tar.gzdoes; and issue #527's release-relevance wording was overstated β the affected code shipped in v1.28.2 even though the main-push verification run (33064552219) that gated this release didn't trigger it; only the separate, later tag-triggered CI/CD run did).
- Onboarding bootstrap-effect race that could skip the welcome portal:
hooks/useApp.tsinitializedisPortalActivetofalseand only flipped ittruevia a mount effect, even thoughisNewUseris already resolved synchronously before<App>mounts. A separate project-bootstrap effect (repairs raw-i18n-key project fields, or seeds a fresh blank project) only guarded onisPortalActive/isI18nReady/project, missing theisInitialLoadguard a sibling effect in the same file already uses β occasionally letting a blank project get auto-seeded before the portal-activation state change landed, skipping the welcome portal for a new user. Fixed both: synchronousisPortalActiveinitialization, and the missingisInitialLoadguard (extracted intohooks/useProjectBootstrapEffect.tsfor direct test coverage). Fixes #527, PR #530. export.spec.tsE2E precondition assumed WelcomePortal unconditionally:waitForSpaReady()succeeds on either WelcomePortal or an already-mounted main shell, but the test'sbeforeEachclicked "Start a New Project" unconditionally, causing spurious CI failures whenever a cold boot landed in the main shell instead β a startup-state precondition gap, not a #527/#530 regression. AddedensureWelcomePortalEntry()(tests/e2e/helpers.ts), which recovers via the real Settings β Data & Backups β Factory Reset flow when needed β including re-checking both startup shapes after its own internal reload, since that reload can itself race a pending ~1s debounced autosave β and a stabledata-testid="welcome-portal"onWelcomePortal.tsx's root so portal detection no longer depends on translated button text. Regression coverage for both startup shapes, a non-English persisted-language boot, and the internal-reload race. This fixes the test-harness symptom, PR #533; issue #532's root cause (how a persisted project can appear before any test action runs) remains open and unexplained.
- Service-worker cache deletion is now positively ownership-scoped: every service-worker-managed
cache-deletion site (non-Tauri activation cleanup, the Tauri unregister path, the user-initiated
"clear cache" action, and Factory Reset) now matches against an exact, closed set of this app's own
cache-name families before deleting anything, closing a shared-origin data-deletion risk where an
unrelated cache could be swept up by a blanket prefix or unconditional
caches.keys()sweep. Local AI model caches (Settings β AI β Clear Local Models) are a separate deletion authority and are addressed by the next entry; Factory Reset does not yet clear them β tracked in #526. - Local AI model cache clearing now matches exact vendor bucket names, not a loose substring:
"Clear Local Models" previously matched any cache whose name merely contained
webllm,mlc,tvmjs, ortransformersβ broad enough to catch an unrelated cache likeother-transformers-assetson the shared origin. It now matches only the exact CacheStorage bucket names these two vendor libraries actually create. - Desktop filesystem corruption/I-O failures now fail closed, not silently absent: a corrupted or unreadable saved project file used to decompress/parse into a fake empty object and be treated as "no saved project" β risking a subsequent autosave silently overwriting the only remaining copy. Corruption and I/O failure are now distinct, typed, thrown errors the caller must handle explicitly; genuine absence is unaffected.
- PWA service-worker updates flush pending state before reloading: the visible tab now retry-flushes in-flight persistence (with a bounded final attempt and timeout) before a forced update reload, instead of reloading unconditionally on the assumption that autosave alone made it safe. This is a bounded mitigation, not a closed invariant β full cross-tab write coordination remains tracked separately (#480/#485); the residual risk after this fix is documented in #518.
- DOCX export now produces a real DOCX file everywhere it's offered: selecting DOCX in the
in-app export dialog, the Writer export view, and the desktop file-save export path all
previously silently wrote a Markdown-shaped file with a
.docxextension. All three now build a genuinedocx-package document and write real binary output. - GitHub Pages deploy no longer silently skips on main pushes: the deploy job's default admission gate inherited GitHub Actions' skip-propagation from unrelated, legitimately-skipped upstream jobs (PR-only governance checks, path-scoped Rust gates) even when the real required aggregator had genuinely succeeded β the live GitHub Pages mirror had been intermittently stale since 2026-08-20 and consistently stale since 2026-08-26. Now gated explicitly on the aggregator's own result.
- Onboarding install command corrected: README/CONTRIBUTING pointed contributors at a bare
pnpm install, which skips--frozen-lockfileand this repo's own dependency-fingerprint check. All onboarding paths now point at the frozen-lockfile reconcile command. - Desktop installer signing language narrowed to match reality: README claimed desktop installers were generically "signed"; only the auto-update manifest's per-platform Minisign signatures are produced by default β OS-level code signing (Authenticode, notarization) is not currently configured in CI.
- Structural workflow-policy YAML authority: a real-parser (not regex) validator for every
.github/workflows/*.ymland.github/actions/**/action.ymlβ top-level permissions, theneeds:graph, SHA-pinned action references, and the publishing boundary. Runs first in CI, before any job invokes the governedsetupcomposite action. - Tiered PR-size governance: advisory
target/hard/docsGovernancelimits and a blockingabsoluteceiling on changed files, meaningful lines, and commits, with a dedicated profile for all-documentation PRs. Excludes generated locale bundles, thepnpm-lock.yamlchurn, and content-guard's own mirroredcommunity-templates/index.jsonfrom the size calculation so an atomic i18n or lockfile change can't be falsely flagged; runs the base ref's own copy of the checker so a PR touching it can't raise its own limits. - Intel macOS qualification-only build lane: a
workflow_dispatch-only, non-publishing verification lane for themacos-15-intelrunner class (GitHub's current replacement for the retiredmacos-13label), with no signing secrets and no release artifacts.
- Pre-push evidence and local-admission tooling reconstructed as smaller, independently reviewed slices: canonical path-evidence completeness, change-aware local checks, bounded subprocess lifecycle for the pre-commit/pre-push runners, working-tree-vs-push divergence detection, and exact-localSha dependency-manifest compatibility proof.
- Qt/PWA native desktop architecture-governance roadmap reconciled: Wave 2.5 amendment, gates G1.5/G2.5, and reuse-ownership authority folded into the canonical roadmap and ledger docs.
google/osv-scanner-action2.5.0β2.5.1.docker/setup-buildx-action4.2.0β4.3.0.
- The frozen reconstruction-source PR was fully reconciled: every still-required piece was rebuilt
fresh against current
mainand merged through small, independently reviewed PRs; every other piece was already shipped through a separate implementation or is a deliberately deferred architecture question with durable issue tracking.
- Scenario/Screenplay canonical projection workspace: the new workspace exposes a renderer-neutral projection for scenario and screenplay work while keeping the existing TypeScript authority intact.
- Core project validation shadow caller: desktop loads now observe the bounded Rust project
validation verdict through
DesktopPlatform; this is observation-only and does not switch authority from the existing TypeScript path.
- Writing-overlay immediacy: overlay feedback is now visible without waiting for a later render cycle.
- Serialized persistence protection: overlapping persistence writes are coordinated so a later save cannot race an earlier save and corrupt the durable project state.
- Verified release source history: signing doctor, local commit/push enforcement, exact-range verification, annotated-tag enforcement, and GitHub Verified checks now form the release-source trust boundary.
- Supply-chain monitoring: the daily OSV workflow and hardened pnpm bootstrap/version pinning extend the existing dependency and CI security controls.
- Policy and mutation coverage: CI authority, selector/AI-core mutation oracles, and the packaged editor readability oracle were strengthened for this release line.
- Native lifecycle truth: the #332 lifecycle evidence and Qt early-killer gates are documented without claiming packaged closure.
- Packaged Linux lifecycle/Alt+Tab validation for #332 remains pending; reporters should
validate the new
.debusing the documented lifecycle, Alt+Tab, and persistence protocol. - Packaged dark/sepia/PWA-versus-packaged readability validation for #341 remains pending; publication or CI does not close that issue.
- PWA remains first-class, Tauri remains transitional, and Qt remains the future native target behind its evidence gates.
- No unsupported macOS Intel artifact is claimed; only artifacts actually produced by the release workflow belong in updater metadata.
- Cloud model catalog defense-in-depth: current Anthropic, OpenAI, and xAI model IDs now share one typed catalog across settings, fallbacks, and the Claude proxy allowlist.
- Native bundle and translation-quality floors: bundle budgets now distinguish entry, vendor, regular JS, and WASM assets; CI also enforces the existing translation coverage/outlier floor.
- Renderer-neutral Rust Core seed:
crates/worldscript-projectnow provides headless project schema, validation, migration, plain JSON I/O, and a test/CLI harness, with one narrow Tauri validation command wired through the cross-workspace path dependency. - Rust TaskSupervisor bounded text proof: adds bounded renderer-neutral
text.analyzeandtext.difftasks, typed qualification wrappers, deterministic LCS/whitespace contracts, and explicit fallback to the existing TypeScript path; no production caller is switched yet.
- Release-truth guard:
docs:checknow verifies the release-tag frontier, package/tag relationship, README release badges, and non-empty[Unreleased]history without treating tagless or shallow checkouts as a false failure. - Spotlight tour theme: the post-rebrand driver.js popover now binds to its WorldScript CSS selector, with a regression test protecting the binding.
- Desktop encryption scope truth: desktop project files remain explicitly documented as plaintext until the renderer-neutral R-15 storage work ships; the IndexedDB passphrase flow no longer presents itself as a project-file encryption gate on Tauri.
- CI authority closure: Core path changes now select the Tauri consumer gate, workflow-policy tests protect local path-dependency coverage and aggregate deployment gating, and Pages deployment waits for
ci-success. - Accessibility contrast: command-palette heading contrast was corrected to meet the intended WCAG threshold.
storycraft://deep links: the legacy scheme remains accepted for this release and displays a migration notice; it is scheduled for removal in the next release.
-
CSP egress parity: Web/PWA and Tauri now use one explicit provider, local-service, and Yjs signaling origin allowlist; arbitrary HTTPS egress and the contradictory loopback upgrade policy are no longer accepted. New BYOK endpoints require an explicit CSP policy update.
-
Qt Early Killer-Gate qualification: the native roadmap now requires cheap, evidence-backed lifecycle, accessibility/input, packaging/update-trust, crash/recovery, and security checks before substantial Qt UI work can create sunk cost.
-
Diagnostics sink boundary: structured log construction and recursive redaction now remain portable before serialized IDB, Tauri JSONL, and development-console adapters receive the record; legacy logger APIs remain compatible and no sink authority switch is claimed.
-
Coverage scope and threshold calibration: coverage now measures the root application/PWA shell; CI recalibrated floors to L80/F72/B66/S78 from the expanded-scope measurement, and the token audit baseline was ratcheted down from 160 to the verified current count of 159.
-
Native desktop strategy: ADR-0021 adopts Qt 6/Qt Quick as the future primary native product, keeps React/PWA first-class, makes Tauri transitional, admits GPUI later behind a gate, and retires CEF from the target architecture.
-
DesktopPlatform boundary: direct Tauri imports are mechanically constrained by the zero- tolerance guardrail while the renderer-neutral contract becomes the native transition surface.
-
Diagnostics parity and task contracts: Rust/TypeScript wire-shape and redaction fixtures now cover the diagnostics boundary, and TaskSupervisor requests/results reject unsupported contract versions before native execution (#437, #439, #440).
-
Native maturity evidence: the G1 qualification ledger now records the partial, evidence-backed status of the native migration without claiming a production authority switch (#438).
-
Mutation-test plumbing: the manual Stryker workflow now uses explicit per-module incremental files, preserves module report identity, validates aggregate metrics, fails closed on missing shards, and propagates aggregation failures through the summary pipeline.
-
Quality-gate truth: Vitest CI no longer retries the suite, the suppression ratchet was reduced from 52 to 48 through real test-mock cleanup, and docs checks now validate README test file/case metrics against the current Vitest source set.
- CI supply-chain policy: CodeQL action references and token permissions were hardened and Dependabot grouping was corrected so related action paths move together.
- Diagnostics redaction: sensitive nested diagnostic context is recursively redacted before serialized IDB, Tauri JSONL, or development-console sinks receive it.
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.
- 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.mdcorrected 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.tsxreplace three near-duplicate confirm/wipe/error/ busy implementations acrossApiKeySection,IdbUnlockModal, andEncryptionRecoveryModal; 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-existsandfs:allow-read-dironly covered$APPDATA/**, rejectingexists()/readDir()on the$APPDATAroot 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.jsonnaming 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_eventgated#[cfg(desktop)]. The call was unconditional despite the crate itself restricting that method to desktop, which would have failed to compile for the mobile targetssrc-tauri/src/lib.rsalready anticipates (#[cfg_attr(mobile, ...)]). - Re-wired the dead Rust menu-action event bridge.
registerTauriMenuHandleris 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.
- nanoid security advisory patched (PR #362) β
nanoid@3.3.17β3.3.18, added tominimumReleaseAgeExcludeinpnpm-workspace.yamlso the fix isn't held back by the 10,080-minute (7-day)minimumReleaseAgepolicy.
- CI: Rust compile/lint/test, E2E, and Visual Regression Testing are now required merge gates
(
rust-tauri,e2e,vrtjoin theci-successaggregator'sneeds). Previously native Rust code and UI regressions could merge intomainwith no automated check beyond an advisory OSV vulnerability scan.
- Added coverage for the new
clearTauriAppDatabranch 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/patchhad dropped to 52.75% before this pass.
- Durable encryption migration journal (
services/storage/encryptionMigrationJournal.ts) β persisted, versioned, no-secret coordination metadata (phase, per-store checkpoints, a compare-and-swap owner lease) for cross-database at-rest encryption migrations, which cannot be wrapped in a single atomic IndexedDB transaction. Protected access is blocked while a migration is active or requires recovery, and a durable transaction prevents competing migration owners. This is a recovery foundation β see below for what still isn't wired to a production trigger. (#337) - Resumable protected-store migration runner (
protectedStoreMigration.ts) with registered adapters for the two secondary stores (scene revisions, AI inference cache): batch conversion, interruption/resume from the last durable checkpoint, and verification that distinguishes a genuine shortfall from records legitimately deleted by cache eviction or history retention. (#337) - Cross-tab admission for protected writes vs. active migrations
(
services/storage/protectedWriteAdmission.ts) β a Web Locks API shared/exclusive lock closes the race where an ordinary protected write could commit after a migration had already claimed ownership of the same store, which could otherwise land ciphertext under a superseded key/generation. (#339) - Manual "Reduce transparency effects" accessibility toggle (Settings βΊ Accessibility,
accessibility.reducedTransparency, default off) β stripsbackdrop-blur-*GPU compositing everywhere, for desktop/Linux users whose window manager doesn't expose the OS-levelprefers-reduced-transparencypreference that the existing automatic mitigation relies on. (#332) - Downloaded/total size and speed in both model-download progress UIs (#333 item 1).
VoiceModelDownloadModalnow shows real byte counts and transfer speed, sourced directly from transformers.js's own progress payload (loaded/totalfields it was already receiving but discarding).LocalAiDownloadProgress(WebLLM text models) shows an approximate downloaded/ total MB and speed, derived from the existing 0-1 progress fraction Γ a new per-model known-size table (WEBLLM_MODEL_APPROX_MB) β the installed@mlc-ai/web-llm's own progress callback exposes no structured byte counts, so this is clearly labeled as an estimate, not measured telemetry. - Disabling at-rest encryption and rotating the storage passphrase are now live in Settings βΊ
Privacy β the production wiring the prior release's migration journal was built for. Both flows
run through the same resumable, crash-recoverable migration journal, verify identity via a
durable AES-GCM sentinel/verifier before touching any data, and surface an
EncryptionRecoveryModalon next startup if a migration was interrupted mid-flight. Closes issue #338. (#342, #343)
- IndexedDB at-rest encryption now fails closed instead of silently downgrading to plaintext.
Protected reads and writes are rejected with a typed locked-storage error whenever encryption is
configured but the session key is missing, closing several Time-Of-Check-To-Time-Of-Use gaps
where an intervening
await(opening a transaction, awaiting encryption) could let Lock Session race a write into plaintext. Every previously-unguarded destructive/listing path (deleteImage,deleteBinderAsset,listBinderAssetIds,deleteStoryCodex,listSnapshots,deleteSnapshot,deleteProject) now has the same guard. At the time of this change, disabling encryption or rotating the passphrase was intentionally unavailable pending the migration journal's resumable conversion protocol being wired to a production trigger β see the "Added" section above for the production wiring that shipped in this same release. (#335) - Desktop AI provider and Python integration hardening, addressing user-reported release-quality
issues #332/#333: removed the brittle Gemini
AIza-prefix key rejection in favor of minimal syntax checks plus real provider validation; Local AI cancel/retry now operates on the actual in-flight acquisition instead of a detached handle, and terminal failures stay visible instead of silently resetting to idle; LM Studio discovery/testing now uses the correct OpenAI-compatible/v1/modelsendpoint with sanitized diagnostics surfaced in the UI; desktop Python discovery, executable selection, and version/error reporting were centralized for consistency across commands. (#336)
- Secure-record codec no longer silently corrupts unsupported structured-clone types.
Map,Set,RegExp, and non-Uint8Arraytyped arrays previously fell through to a generic object-entries encoder that lost their type and often all content; they now fail closed instead of being encrypted as a different, wrong value. (#337) - Encryption migration journal rejects illegal phase transitions β completing a migration from
a freshly
preparedjournal (skipping conversion and verification entirely) is no longer possible; only acommittingorcleanupphase may terminate. (#337) - Migration verification no longer re-scans already-verified stores on resume, and a batch that reports progress without advancing its durable cursor is now rejected instead of being able to replay the same records indefinitely. (#337)
- Desktop (Tauri) build never read persisted state back at cold boot. The app's boot-time
hydration called the raw IndexedDB-only
dbService.loadState()unconditionally, with zero Tauri branching, while every save path already routed through the Tauri-awarestorageServiceβ every desktop launch loaded as a brand-new user regardless of what was actually saved to disk (a strict superset of the reported "appearance preference doesn't persist" symptom). Boot-time hydration (services/appBootstrap.ts) now mirrors the save path, branching onisTauriRuntime(). The first-ever-launch settings default (appearancePreset) is also reconciled betweenidbProjectStore.ts's normalizer andsettingsSlice.ts's deliberate'sepia'initial state, and quitting the desktop window now awaits any pending 1s-debounced project/settings autosave (services/desktop/desktopTray.ts) before the process actually exits, instead of allowing a quit to land mid-debounce and silently drop the last edit. (#332) SettingsViewre-rendered its entire tree on every unrelated Redux state change βuseSettingsView's return value was a fresh object every render (no memoization), so any background write anywhere in the app (autosave, AI copilot, progress tracker) forced every Settings component to re-render even while a completely different view was in the foreground. The context value is now memoized against its actual dependencies. (#332)- AI Writing Studio manuscript text was unreadable, with selection/caret position drifting from
the visible text.
ContextPanel.tsx's real (input-handling) textarea sat invisibly over a separate visible text-mirror layer; the sharedTextareaprimitive's unconditionalbackdrop-blur-mdblurred the mirror text underneath, the two layers resolved different concrete font stacks for the same font setting (different glyph metrics β position drift), and neither layer synced its scroll position with the other.components/manuscript/ManuscriptEditor.tsx(the primary writing surface) used the same fragile pattern and carried the same blur/scroll-sync defect. Fixed via a newTextareavariant="overlay"(no glass background/blur/reserved padding/mic button) and a single sharedservices/editorTypography.tsfont-stack resolver used by both the real textarea and its mirror in both components, plus one-directional scroll sync from each real textarea to its mirror. (#341) - Overlay-variant textareas dropped RTL font resolution, custom-font selection, and dictation.
resolveEditorFontFamilynow also takes the active text direction andsettings.customFont?.name(previously everyeditorFont: 'custom'silently rendered as JetBrains Mono, and RTL sessions used LTR font stacks); the newDictationButtoncomponent restores the microphone entry point thatvariant="overlay"had unconditionally removed from both Writer Studio and the manuscript editor, rendered as a sibling above the mirror instead of insideTextareaitself. Also fixes two related scroll-sync gaps: the mirror now resets to the top on a section switch instead of showing a stale offset, and re-syncs after debounced/deferred content growth instead of staying clamped to a since-invalid scroll range. (#341, #344) - Voice model download progress bar looked stuck at ~95% for most of the download.
voiceCommandService.ts's download progress handler treated transformers.js'sprogressfield as if it were already a 0-1 fraction; it's actually 0-100 (percent), so the existingMath.min(0.95, pct)safety clamp kicked in almost immediately (any progress value over 0.95%, i.e. after the very first chunk of the download) and stayed there until the download's final "complete" dispatch. Now derives the fraction from the payload's realloaded/totalbyte counts (falling back toprogress / 100only when those are absent). (#333 item 1)
- Closed out the PR #310 reconciliation ledger as superseded β
#335/#336/#337 are merged into
main, satisfying the one condition its merge decision previously left open. PR #310 itself was never merged; its own branch remained non-resumable throughout. - Opened, then closed in this same release, issue #338 tracking the Phase-4 work: wiring the migration journal to a real disable/passphrase-rotation UI flow, primary IDB-store adapters, and the two above race-condition fixes' production reachability β all landed via #342/#343 (see "Added" above).
- Opt-in native desktop notifications now report completed encrypted-library and manuscript exports, updater readiness, and ProForge stage completion through the Tauri notification plugin. Permission is requested only after the user enables the setting, web builds remain a no-op, and the settings migration defaults existing installations to disabled. (#306)
- Local-inference worker capacity now follows the detected memory tier: low-memory devices use one inference worker, the default remains two, and high-memory devices may use three. Detection failures safely retain the two-worker default. (#305)
WorkerBusis now an authoritative, event-driven priority scheduler. Tasks dispatch only after leaving the bounded queue; worker availability events replace animation-frame polling; retries re-enter the scheduler without duplicating logical handles; and an eight-task critical reserve bounds total queue growth while retaining emergency capacity. (#307)
- Worker tasks can no longer remain pending indefinitely after queue saturation or inactivity. The progress-rearmed inactivity watchdog now starts at enqueue, covers both queue wait and active execution, retries recoverable timeouts, and replaces presumed-wedged workers without returning them to the idle pool. Worker-construction, replacement, cancellation, retry-backpressure, shutdown, and pool-termination failures all settle through one cleanup path. (#305, #307)
- Completed tasks are removed from live scheduler state, so more than 32 lifetime submissions
no longer trigger false
BACKPRESSURE; priority order and FIFO-within-tier are preserved under saturation, and completed progress iterators terminate instead of hanging. (#307) - The E2E API-key setup helper uses an exact configured-status match, avoiding strict-locator ambiguity between the status badge and its descriptive label. (#307)
- Added direct regressions for queue drain and reuse, priority ordering, critical reserve bounds, queued and active timeouts, fresh retry tokens, cancellation, shutdown, pool termination, constructor/replacement failures, listener/timer cleanup, and progress-iterator completion.
- Grok (xAI) wired into the primary provider dropdown β the backend (
streamGrok(), BYOK key storage,/v1/modelsconnection test) already worked; it just wasn't reachable fromAiProviderCard.tsx's main flow. Added the key input + model selector (grok-3/grok-3-mini), aproviderToKind()case for the newer Vercel-AI-SDK layer, and fixed the whole provider array's i18n (several labels were hardcoded literals). See ADR-0016. - Claude (Anthropic) now works on desktop β
streamAnthropic()threw unconditionally on every platform; CORS is a browser-only restriction, so desktop now calls Anthropic directly via the same native-HTTP escape hatch ADR-0012 established for Ollama. Desktop Settings now shows a real key input + model selector (Opus 4.7/Sonnet 4.6/Haiku 4.5) instead of a warning-only block. - Claude (Anthropic) now works on the web/PWA (Vercel, Cloudflare Pages) via a new stateless
serverless proxy (
api/claude-proxy.ts+functions/api/claude-proxy.ts) β this app's first backend dependency ever. Schema-validated, body-size-capped, same-origin-checked, rate-limited, and timeout-bounded; never logs the API key, prompt, or response. GitHub Pages (static-only, can't host the proxy) shows an honest "not available on this deployment" state instead of a silent failure. This is the one provider whose BYOK key transits WorldScript's own infrastructure in the web build β documented plainly indocs/SECURITY-THREAT-MODEL.mdand README's privacy framing, not folded into the general "direct browserβprovider" claim every other provider gets. - Opt-in direct browserβOllama connection in the web/PWA build (
enableBrowserOllama, default off, Settings β Experimental) β for users who start their own Ollama server withOLLAMA_ORIGINScovering the page's exact origin, matching NovelCrafter's real (non-proxy) browser-Ollama model. Not a bypass and not the default: desktop remains the recommended, zero-config path. The Settings UI renders the exactOLLAMA_ORIGINS=<origin> ollama servecommand for the current deployment. See ADR-0017 (Issue #266 follow-up).
- DuckDB
codex_mentions.excerptβ the one analytics column holding literal manuscript prose β is now cell-level encrypted (SEC-6) whenenableIdbAtRestEncryptionis active.duckdbCodexWrite()(services/duckdb/duckdbAnalytics.ts) encrypts new excerpts viaservices/duckdb/duckdbEncryption.ts(AES-256-GCM, reusing the IDB at-rest encryption key) into a newexcerpt_enc BLOBcolumn and nulls the plaintextexcerptcolumn; a new backfill migration (services/duckdb/codexExcerptEncryptionMigration.ts) re-encrypts any pre-existing plaintext rows once encryption is unlocked, without blocking the schema's other migrations when encryption isn't active this session. Full OPFS file-level encryption remains infeasible β DuckDB-WASM owns the OPFS file handle directly, leaving no app-level interception point β so the other DuckDB metadata columns (title/logline/name/character_names/label) stay intentionally plaintext by design, not manuscript prose. - SEC-6 backfill migration marker was a single global key, not scoped per project β
once any project completed the excerpt-encryption backfill, every other project's
plaintext
codex_mentions.excerptrows were silently never migrated.isCodexExcerptEncryptionMigrationDone()now takes aprojectIdand scopes the_metadone-marker key per project. Also fixedduckdbCodexWrite()'sON CONFLICTclause forcodex_mentionsso an unencrypted write (a session where IDB encryption isn't unlocked yet) can no longer clobber an existing ciphertext row back to plaintext β it now preserves any priorexcerpt_encviaCOALESCE/CASEinstead of overwriting it. Added a two-project regression test proving the marker-scoping fix and a test asserting theON CONFLICTclause never downgrades ciphertext. (CodeRabbit + Copilot review, PR #303) - A failed per-row
UPDATEduring the SEC-6 excerpt-encryption backfill could still let the migration write its done-marker β the row's plaintextexcerptwould then never be retried or encrypted on any future run.runCodexExcerptEncryptionMigration()now tracks row-level failures (a failedUPDATE, orencryptDuckDbData()throwing) and skips the done-marker, returning{ aborted: true }so the still-plaintext rows are retried next run instead of being silently left unencrypted forever. (CodeRabbit outside-diff-range finding, PR #303)
event-listener(Rust, transitive via Tauri) bumped 5.4.1 β 5.4.2 β the OSV/RUSTSEC scan flagged 5.4.1 as vulnerable (RUSTSEC-2026-0221), fixed upstream in 5.4.2;Cargo.lockonly, no other dependency churn.- Doc-drift gate (
scripts/check-doc-metrics.mjs) had a blind spot for still-open checklist bullets inside dated/historical sections:stripHistoricalSections()blanked every line in a historical section indiscriminately, including a genuinely still-open- β¬bullet β exactly how a stale "Tagv1.24.2" bullet escaped the gate in a same-day doc edit (dc14bc0).OPEN_BULLETnow gets the same "always present-tense, never stripped" treatmentDONE_BULLETalready had for the opposite polarity.scanForDrift()also gains a new check: a surviving open bullet mentioning tag/release/publish (including inflected forms β "Tagging"/"Releasing"/"Publishing") of a version<=the latest git tag is now flagged as drift. (#293)
CLAUDE.mdcodifies three merge-discipline lessons learned during the PROMPT-WSS-v1.24.x recovery sprint: never commit directly tomain(always branch + PR, even for a single-file change); wait for the full CI suite β including non-required/advisory jobs like E2E, Storybook, Lighthouse, and Visual Regression β to go green before merging, not just the branch-protection-required checks; and group related small workstreams into the fewest reviewable PRs by natural/documented boundaries rather than one PR per tiny item, while keeping one commit per logical concern. Also adds a "Known merge-gate quirks" section distinguishing GitHub's real pending-check block from this repo's own stricter wait-for-pass policy and the mergeable-state cache-lag symptom, plus documents the stacked-PR auto-close-on-squash-merge side effect and its recovery steps; and strengthens the CodeAnt Correction Loop policy to explicitly cover CodeRabbit's collapsed nitpick and outside-diff-range comment sections. (#294).github/SECURITY.md/docs/SECURITY-THREAT-MODEL.mdSEC-6 status corrected to reflect the DuckDB excerpt-encryption wiring above, and the Claude serverless proxy section gains an explicit monitoring recommendation (platform-native Vercel/Cloudflare request analytics β no in-app logging, which would violate the proxy's zero-console-call stateless guarantee).docs/history/GROK-PROVIDER-INTEGRATION-PLAN.mdstatus header updated from "Plan only β do not implement yet" to reflect that all phases shipped; retained as the historical design record.TODO.mdreworded the open tag/publish bullet so it no longer embeds the literalv1.25.0version string on the- β¬bullet line itself, removing the risk thatscripts/check-doc-metrics.mjs'sscanForDrift()would flag it as stale drift once thev1.25.0tag is actually pushed (verified via ascanForDrift()simulation withlatestVersion: '1.25.0'β zero findings).docs/SECURITY-THREAT-MODEL.mdnow names the specific platform observability products (Vercel Function Logs / Vercel Observability; Cloudflare Workers Metrics/Analytics and Workers Logs) instead of a vague "platform-native analytics" reference, and notes that Cloudflare Workers Logs is opt-in and must be enabled via the Wranglerobservabilityconfig.
- Closed 3 Codecov patch-coverage gaps found on PR #303: the DuckDB analytics migration
listener in
app/listenerMiddleware.tswas never exercised by any test at all (stale mocks βloadDuckdbMigration's mock returnedrunIfNeeded, but the real listener callsrunMigrationWithRollback;loadRagVectorMigration's mock resolvedundefinedwhere the listener reads.aborted) β fixed the mocks and added 2 tests covering both branches of thecodexEncResult.abortedgate. Added 5 tests forcodexExcerptEncryptionMigration.ts(a post-loop privacy-gate abort distinct from the existing mid-loop-abort case, a failed rowUPDATEthat now aborts without writing the done-marker, a row-encryption throw that aborts the same way, and themigrated % 10 === 0yield-point). Added a test foruseDuckDb.ts's new v3 migration DDL failure branch.
Worker-generation consolidation sprint: the v1/WorkerBus-v2 duplication ADR-0014 deferred is now closed out. Executed as 5 stacked PRs (#286β288, #290β291), plus the independent #289 (Vercel auto-deploy pause); see ADR-0015 for the full decision record. This release also recovers a merge queue interrupted mid-session by a local editor crash β all five PRs required a fresh sync-merge conflict resolution against the accumulated fixes below before landing; see
docs/audit/for the run log.
- DuckDB analytics and local inference (embeddings + NLP) now route through WorkerBus v2,
replacing dedicated
new Worker(...)instances per service.services/duckdb/duckdbClient.ts,services/ai/localEmbeddingService.ts, andservices/ai/localNlpService.tskeep their exact public APIs β an adapter pattern, not a consumer rewrite, so none of their real callers needed changes.ensureDuckDbPool()/ensureInferencePool()added toservices/workerBusManager.ts, decoupled fromenableWorkerBusV2(mirrors the existingensureWebLlmPool()pattern β these are core features, not experimental infra). - The shared
inferenceWorkerBus pool'smaxWorkerscapped at 2 (was 4) β each replica independently loads its own transformers.js pipeline with no cross-replica cache sharing, so 4 concurrent workers under a burst could mean 4x the model memory footprint. - Vercel auto-deploy paused mid-term after 2 consecutive preview-deployment failures with only a generic "Deployment has failed" message and no reproducing local failure β GitHub Pages and Cloudflare Pages remain live and unaffected while the platform-side cause is investigated.
- DuckDB query/exec parameters were silently dropped, executing an unbound query β already live
and breaking
services/ai/telemetryService.ts's parameterizedINSERTwrites. Fixed via DuckDB-WASM prepared statements. WorkerBus.terminatePool()could leave tasks hanging forever β removing a pool didn't settle tasks already routed to it. Fixed by tracking taskβpool assignment and cancelling matching tasks before removal; a pool removed this way is now also transparently re-registered on next use instead of failing every subsequent call.- A respawned DuckDB worker lost its connection, failing every query after an idle timeout with "DuckDB not initialized." Fixed by transparently re-initializing once and retrying.
- DuckDB's OPFS-unavailable fallback silently swallowed the failure, giving users no warning their analytics wouldn't persist across reloads (private browsing, old Safari) β restored via the existing progress channel. A follow-up fix made the fallback's own cleanup step best-effort, so a secondary failure there can no longer block the fallback path itself.
- A missing
ensureInferencePoolexport broke production builds on Vercel (rolldown's[MISSING_EXPORT]) β not caught by Vitest (the consumer test mocks the whole module) or by localtsgotypecheck. Root-caused by reproducing Vercel's exact build command (pnpm run build:edge, which differs from the plainpnpm run buildused for an earlier, misleading local repro attempt) and fixed by adding the missing function. - A reused
AbortSignalleaked one'abort'listener per call induckdbClient.ts'ssend()β{once: true}only self-removes once the signal actually fires, so a caller reusing one signal across many non-aborted queries (a common hook/thunk pattern) accumulated listeners for the signal's whole lifetime. Fixed with an explicitfinally-blockremoveEventListener. ensureDuckDbPool()/ensureInferencePool()could reject instead of degrading gracefully β their pool re-registration helpers awaited a lazy import and calledregisterPool()without a catch, so a re-registration failure after the bus was already live broke the documented "returns null only if init failed" contract. Both now log the failure and return the live bus instead of propagating the rejection.
workers/duckdbWorker.tsandworkers/inference.worker.ts(the v1 worker generation) β both workloads now exclusively useworkers/v2/duckdb.worker.tsandworkers/v2/inference.worker.ts.
CSP functional-truth, desktop crypto, and documentation-truth hardening sprint (
PROMPT-WSS-v1.24.x). Headline fix:script-srcshipped without'wasm-unsafe-eval'for two months (2026-05-27 β 2026-07-29), silently blockingWebAssembly.instantiatein every deployed Chromium browser β the entire advertised local-inference stack (WebLLM, ONNX Runtime Web, Transformers.js, DuckDB-WASM, Whisper-STT, Kokoro-TTS) never functioned in production. No gate caught it because the only CSP tests that existed checked cross-surface consistency, never functional correctness. Seedocs/adr/0013-csp-wasm-and-blob-frames.mdanddocs/audit/WS-RUN-LOG-2026-07-29.mdfor the full record.
- F-01/F-02 β CSP functional truth.
'wasm-unsafe-eval'(never the broader'unsafe-eval') andframe-src 'self' blob:added to all 5 deployment surfaces (index.html,vercel.json,public/_headers,nginx.confΓ3,src-tauri/tauri.conf.json). The unhashed inlineaurora-disabledscript that shipped alongside the broken CSP moved intoindex.tsxas a same-origin module. New 3-layer test architecture (tests/unit/cspCorrectness.test.ts+ a hardenedscripts/smoke-prod-build.mjswith real CSP-violation and WASM-instantiate probes) so this class of defect can't silently recur. - F-05/F-06 β desktop API-key encryption.
services/fs/fsCore.ts's key derivation used a single unsalted SHA-256 digest of publicly-derivable material β fixed to PBKDF2 (600,000 iterations) + a random 32-byte salt, matching the existingstorageEncryptionService.tspattern. Pre-existing key files are discarded (not migrated) with a one-time notification prompting re-entry. Correction (2026-08-13): this hardened the KDF against rainbow-table and multi-target reuse, but the derivation input itself β${appDataPath}|${provider}|WorldScriptStudio|v1β remained fully public/reconstructible, so the root "obfuscation, not encryption" finding was never actually closed. Tracked as an open gap β seedocs/IDB-ENCRYPTION.mdΒ§ Tauri Desktop Layer andAUDIT.md's F-05/F-06 row for current status. Review-loop follow-up (2026-08-13):docs/SECURITY-THREAT-MODEL.md's Desktop Local File-Read Attack Tree and document header still called this "fixed" and said reading the ciphertext no longer reveals the key material, directly contradicting the corrected Mitigation Mapping row above it β reconciled to say "not resolved" consistently throughout. Also documented a separate, unrelated functional bug surfaced while fact-checking this row:components/ApiKeySection.tsxnever adopted thestorageService/FsSettingsStorepath this desktop API-key row describes β it still reads/writes the Gemini key throughdbService(IndexedDB) even on desktop, whileservices/geminiService.tsreads throughstorageService(filesystem on desktop), so a Gemini key saved via Settings β AI on desktop is invisible to the code that actually uses it. Tracked in #358; not fixed by this entry. Second review-loop follow-up (2026-08-13): tightened the attacker-capability wording (needs both the ciphertext and the public derivation inputs, not the ciphertext alone) in both the Mitigation Mapping row and the attack tree; added the same Gemini-exception note to the attack tree (previously only in the table row); and added a new Mitigation Mapping row for desktop project/settings/snapshot/Codex/RAG/image/binder-asset data, which this threat model previously didn't mention at all despite it having neither confidentiality nor authentication on disk β real fix in progress on PR #356. - F-08 β Tauri/web
connect-srccompleteness. Added LanguageTool's default self-hosted port (missing on all 5 surfaces, not just Tauri) and the Hugging Face hosts WebLLM/Transformers.js actually resolve models from, including the Xet CDN bridge (us.aws.cdn.hf.co) that real model weight downloads redirect to β traced empirically, not assumed. - F-09 β DuckDB-WASM self-hosted. Replaced an unversioned, unreachable (already CSP-dead)
third-party CDN with assets copied from the pinned
@duckdb/duckdb-wasmnpm dependency at build time (scripts/copy-duckdb-assets.mjs), never committed to git.
- F-07 β doc truth-up. README/CLAUDE.md no longer make a blanket "encrypted at rest" claim;
a fabricated
tauri-plugin-strongholdreference (no trace of that dependency anywhere in the repo) removed and replaced with the actual mechanism per platform. - F-10 β canonical production URL unified. A dead preview-deployment domain
(
worldscript-studio-indol.vercel.app, confirmed 404) had drifted into the in-app link and the Italian locale; both now reference a singlePRODUCTION_URLconstant. New drift gate inscripts/check-doc-metrics.mjs. - F-12 β CI Storybook deduplication + a genuine broken test-runner invocation. Storybook was
built 3Γ per CI run; deduped to 1Γ. While evaluating whether the Storybook test-runner's
|| truecould be made blocking, found the invocation was calling flags (--max-workers/--retries/--screenshot-on-failure) the installed CLI version doesn't support β every prior run failed on argument parsing before executing a single story, silently turned green by|| true. Fixed the invocation and moved the non-blocking behavior to step-levelcontinue-on-error: true, which still surfaces genuine failures in the Actions UI. - F-13 β coverage ratchet raised to CI-measured values (L79/F72/B65/S77, from L74/F67/B60/S72).
- Pre-existing a11y color-contrast failure (out of scope, fixed proactively). The CI a11y
E2E gate caught
WriterViewUI.tsx's Writer/ProForge version-control and Focus Mode toggles using--sc-accent-derived text on a tinted--sc-accentbackground β 4.47:1 on the sepia theme, below WCAG AA's 4.5:1. All 4 occurrences (2 desktop, 2 mobile, across two different broken token combos) now use the pre-vetted--nav-background-active/--nav-text-activepair already used for nav active-states elsewhere.
- F-04 β CSP gate governance.
docs/CI.mdgains a "which layer catches which failure class" table and a post-mortem on why cross-surface-consistency tests alone were never sufficient. - F-14 β worker-generation duplication documented (
docs/adr/0014-worker-generation-duplication.md). Both a v1 and a WorkerBus-v2 generation of the DuckDB and local-inference workers are live via real call chains; consolidating them is a properly-scoped migration effort of its own, tracked but out of scope for this sprint.
Local-AI reliability (desktop Ollama/LM Studio/vLLM discovery, misleading browser status badge), the AI heuristic-fallback foundation + Outline/Character/World/Plot-Board generators, real self-hosted LanguageTool integration, a 20-PR Dependabot backlog triage (16 merged directly, 4 closed as superseded after the AI SDK v4 family/biome 2.5.x/dev-tooling-Babel-8 blockers were root-caused and fixed), the Issue #60 vendor-fork audit closeout, and security/build hardening (
persist-credentialsCWE-522,ltr/rtlTailwind fix, 3 ineffective dynamic imports, 2 CWE-209 fixes).
-
Heuristic fallback for Plot-Board "suggest next beat". Completes offline coverage of the structured generators: when AI is unavailable, the Plot Board now offers localized, framework-based next-move suggestions (raise the stakes / introduce a complication / add a reversal) with rationale, instead of an error. See
docs/AI-HEURISTIC-FALLBACKS.md. -
Heuristic fallback for Character & World profiles. When AI is unavailable, the Character and World generators now produce a schema-valid, localized starting profile (structured fields with the user's concept woven in; empty
timeline/locationsfor World) instead of an error toast β extending the offline heuristic coverage to two more structured generators. Same pure-generator + hook-resolved-labels pattern as the Outline fallback. Seedocs/AI-HEURISTIC-FALLBACKS.md. -
Heuristic fallback for the Outline Generator. The first per-feature heuristic on the fallback foundation: when AI is unavailable the outline generator now produces a structurally-sound, schema-valid chapter skeleton β a three-act beat sheet scaled to the requested chapter count (setup β inciting incident β rising action / midpoint / complications β optional twist β climax β resolution) with the user's idea woven in β instead of just showing an error. Fully offline and localized (the hook resolves
outline.heuristic.*labels; the generator stays pure). Seedocs/AI-HEURISTIC-FALLBACKS.md. -
AI heuristic-fallback foundation. Groundwork so AI features can degrade gracefully (offline, quota, error, Eco/Heuristics-only mode) instead of hard-failing. A pluggable heuristic-generator registry (
services/ai/heuristicFallback/, modeled on the Copilot rule engine) + a sharedHeuristicFallbackResultenvelope (reuses ProForge'sisFallback+ a calibratedconfidence), wired into the provider choke points that previously had no degrade path βgenerateText's terminal,generateJson(Gemini-direct), andstreamText. AuseHeuristicFallback()hook + reusableAssistedModeBadgesurface the "Assisted (offline)" state and record fallbacks to telemetry. Ships inert (no per-feature generators yet β unchanged behavior); see ADR 0011. Per-feature generators (Outline/Character/World, Writing Studio tools, analysis tools) land in follow-up PRs. -
Real LanguageTool grammar & spelling integration (self-hosted, privacy-first). Replaces the hardcoded fake-typo list with a real, multilingual proofreader running on the user's own machine. Two surfaces share one offset-safe apply path (
hooks/useLanguageToolCheck.ts): an on-demand "Check this scene" panel in the Writer tools sidebar (findings list β apply / ignore / add-to-dictionary) and a live inline overlay in the manuscript editor (debounced underline + suggestion popover, reusing the existing aligned-overlay infrastructure). Newservices/languageToolService.ts(checkTextβ parsematches[], text-hash cache, abortable, silent offline degrade, never logs text); the privacy gate (assertLanguageToolAllowed) keeps cloud servers blocked under local-only mode. Locale coverage is encoded in the SSOTi18n/locales.ts(languageToolSupport+languageToolCode, verified against dev.languagetool.org) and the feature is hidden for unsupported locales (tr/he/fi/hu/is/eu/ko). Opt-in via Settings β Connections; run a server withdocker run -p 8010:8010 erikvl87/languagetool. Seedocs/LANGUAGETOOL.mdand ADR 0010. -
Native Intel-Mac (x86_64) desktop builds β added, then reverted within this release.
tauri-build.ymlbriefly addedmacos-13(Intel) to the build matrix alongsidemacos-latest(Apple Silicon). In practice themacos-13hosted runner never provisions: the job sits in GitHub's queue indefinitely instead of starting, so its owntimeout-minutes: 45never applies β and because thereleasejob'sneeds: [bundle]only resolves once every matrix leg reaches a terminal state, this blocked the entire release for the full ~24h GitHub Actions queue ceiling even though Ubuntu/Windows/macOS-ARM finished in ~12 minutes. Removedmacos-13from the matrix; Intel Mac builds are tracked as a re-opened follow-up inTODO.mdpending a working Intel runner option. Thelatest.jsongenerator's per-arch warning / hard-fail-only-if-no-arch-signs logic (added alongside the original matrix change) is unaffected and already tolerates the missing arch. Seedocs/TAURI-CI.md. -
New Help article β "AI execution modes & OpenRouter". Documents the four live-switchable execution modes (Hybrid / Cloud / Local / Eco) and OpenRouter's free tier + circuit breaker (4Γ429 β 5 min pause, RPM tracking), which shipped without a Help entry. Added to the AI Studio help category, deep-linking to Settings; translated into the five Production locales, English fallback for the other 14 (per the tag-dense-HTML help-body policy).
-
DeepSource static-analysis integration (token-free). Added
.deepsource.toml(analysis-only β no autofix transformers, so it never fights Biome and needs no repo token; activates once the free DeepSource OSS app is installed). Auto-detects JavaScript/Rust/Docker/CSS analyzers. Complements the existing CI gate as a second review layer while CodeAnt's free-tier quota is exhausted. Process:docs/DEEPSOURCE-REVIEW-LOOP.md(living runbook, complements the CodeAnt one); backlog tracking:docs/DEEPSOURCE-REMEDIATION-PLAN.md(prioritised P0-securityβP5-docs, with the triage principle: rule-ignore findings already governed by Biome/strict-TS/test convention, fix DeepSource-unique real issues).
-
AI SDK family upgraded to v4/v7 (
ai6β7,@ai-sdk/google/@ai-sdk/openai/@ai-sdk/react3β4). Bumped as one coordinated unit rather than merging the four Dependabot PRs individually β@ai-sdk/google@4emits the newLanguageModelV4spec, which theaipackage'sLanguageModeltype only recognizes from v7 onward, so upgrading@ai-sdk/googlealone (as Dependabot's #255 proposed) leftservices/ai/providerFactory.tswith a realtscerror. No application code changes were needed beyond the version bump βstreamText/toTextStreamResponse/onFinish'susageshape inservices/ai/worldScriptCompletionFetch.tsandcreateLanguageModelForWorldScript'sLanguageModelreturn type inservices/ai/providerFactory.tsare unaffected. Verified withpnpm run typecheck,pnpm run lint, and the full AI-provider/completion-fetch test suites (all passing). -
Biome upgraded to 2.5.4 (from 2.4.16), migrated cleanly. Two new rules the version enables required real fixes, not suppressions:
lint/correctness/noUnsafeOptionalChaining(4 test files β(x?.[n] as T).prop-style patterns split into a separate variable assignment so an optional-chain short-circuit can no longer throw at the point of use) andlint/suspicious/noUndeclaredEnvVars(16 environment variables used across scripts/config/tests weren't declared inturbo.json'sglobalEnv, so Turborepo's cache correctness couldn't account for them β now declared). Also ranbiome migratefor thelinter.rules.recommendedβlinter.rules.presetconfig rename ahead of Biome's next major version. -
Settings hygiene. Removed stale Experimental-category search hints (
plot board,codex,cross projectβ retired/promoted flags) in favor of current features;ProForgeDashboardnow uses the catalog-drivenMaturityBadgeinstead of a hard-coded Experimental pill, keeping the v1.24 maturity-label convention consistent. -
Desktop settings "minimize to tray" is now a proper switch.
DesktopSectionused the only raw<input type="checkbox">left in Settings, with its hint not programmatically associated. It now renders the design-systemToggleSwitch(role="switch",aria-labelledby+aria-describedbyhint, focus-visible ring, RTL-aware), matching every other Settings control β consistent styling and screen-reader behaviour. Added aDesktopSectioncomponent test (web no-op, accessible switch, state reflection, dispatch on toggle). -
Help content truthfulness pass (post-release). Two stale Help articles were corrected against code reality: Languages now states the real 19 interface languages with their Production / Near-Production / Beta status tiers + the quality dashboard (was "seven languages β German, English, French, Spanish, Italian, plus Arabic and Hebrew"); Feature flags now states 22 flags (16 default-on / 6 opt-in), explains the grouped-by-maturity Experimental UI with dependency-aware disabling, and drops the bullet for the removed
enableWebnnInferenceflag (the WebNN execution provider is still described as an adaptive-engine backend). Refreshed in the five Production locales. -
ProForge is now opt-in (default off). The experimental 8-stage agentic editing pipeline (
enableProForge) shipped on by default; it is token-heavy and carries loop risk, so it is now a user opt-in like Voice and the Global Copilot. New installs get 16 default-on / 6 default-off flags. Existing users who enabled or relied on it are unaffected (the persisted value wins); everyone can still turn it on under Settings β Experimental. -
Settings β Experimental features are grouped by category (Writing, AI, Editing Pipeline, Performance, Voice, β¦) with maturity + risk hints, dependency-aware disabling (a flag whose prerequisite is off β e.g. Voice WASM without Voice Support β is disabled with an explanation), a "Desktop app only" note for Rust Compute, and a Reset to defaults action.
-
Voice-nightly annotation cleanup. The informational
voice-nightly.ymlreal-Whisper job no longer surfaces a red "Process completed with exit code 1" annotation when the HF-CDN model download times out (a known transient). The download step is now step-levelcontinue-on-errorwith a bounded 2-attempt retry, and a summary step writes the pass/fail signal to$GITHUB_STEP_SUMMARY, so the nightly signal is preserved without the misleading error annotation.
-
pnpm run buildwarnings: invalid[dir:ltr]/[dir:rtl]Tailwind arbitrary variants and 3 ineffective dynamic imports.ToggleSwitch's thumb translate used a malformed custom variant ([dir:ltr]:translate-x-5 [dir:rtl]:-translate-x-5) that Tailwind compiled to the invalid CSS pseudo-class:is(dir:ltr)(lightningcss minify warning on every production build) β replaced with Tailwind's built-inltr:/rtl:direction variants, which correctly target thedirattributeApp.tsxalready sets on<html>. Separately, Rolldown flagged 3 dynamicimport()calls (app/transientUiStore.ts,services/ai/ecoModeService.ts,services/spotlightTour.ts) as unable to move their module into a separate chunk because each was also statically imported elsewhere in the eager bundle β converted all three call sites to plain static imports, removing the dead-weight async indirection with no bundle-size change. -
PR #274 review findings that failed to post inline (CodeRabbit/GitHub API error). A rate-limited re-review posted its 10 findings as review-body text instead of inline comments; the 2 that did post (missing
persist-credentials: false) were already fixed. Of the 7 that only existed in the review body text:AiProviderCard's status badge now hasrole="status" aria-live="polite"; a stale in-flight connection-test result (CWE-209) could overwritetestErrorafter switching to Ollama-in-browser β added a monotonic request-id guard inhandleTestand gated the action-row error span with the same!ollamaUntestablecheck the paragraph above it already used;ollamaService.ts'splugin_unavailablebranch returnedLocalServerError.messagedirectly (a public class whose message isn't guaranteed safe for every kind) β now builds its own fixed safe string, matching thetimeout/unreachablebranches; and a scan-endpoint test asserted a loose/modelssubstring instead of the exact/v1/modelspath. The remaining findings (missing// QNBS-v3:comments on several already-commented files/lines;services/CLAUDE.md's LanguageTool wording) were verified already covered by existing file-level rationale comments or already fixed in an earlier commit β not re-applied to avoid redundant/stale documentation. -
Ollama / LM Studio / vLLM discovery & connectivity on desktop, CORS noise in the PWA (#266). Root cause: all local-server traffic (
services/ollamaService.ts,scanLocalOpenAiCompatibleEndpoints()) used the WebView'sfetch, so inside the Tauri shell the cross-originlocalhostrequests died on CORS/PNA, and in the PWA the settings auto-effect probedlocalhost:11434on every visit (loud CORS console errors). All local-server calls now go through the new thinservices/localServerHttp.ts, which routes via@tauri-apps/plugin-http(native, CORS-free) on desktop and keeps browserfetchon the web, with shared URL normalization, timeout composition andunreachable/timeoutclassification (user aborts still propagate unchanged). The Settings β AI card no longer auto-probes localhost in the PWA β it shows a quiet "desktop app required" banner with a download CTA instead β and the desktop scan gained per-endpoint status badges (reachable / no response / timeout / HTTP error) plus a one-click Use this URL action. All new strings localized in 19 locales. See ADR 0012 anddocs/LOCAL-AI.md. -
Desktop Ollama/LM Studio/vLLM discovery still broken in packaged builds after #269 (#266). Root cause:
vite.config.ts'srollupOptions.externalunconditionally externalized every@tauri-apps/*package from everyvite build, including the exact build Tauri'sbeforeBuildCommandinvokes to produce the.deb/.msi. Sinceservices/localServerHttp.ts's@tauri-apps/plugin-httpimport is dynamic, this left an unresolvable bare module specifier in the shipped desktop bundle β every caller's catch classified the resulting load failure identically to a genuinely-down server (no CORS noise, no discovery, even with Ollama/LM Studio running).tauri devwas unaffected (Vite's dev server doesn't applyrollupOptions), so the regression only surfaced in packaged builds. Fixed by extracting the existing Tauri-build detection (resolveViteBase.ts'sTAURI_ENV_PLATFORM/TAURI_PLATFORMcheck) into a sharedisTauriBuild()export and making the external array conditional on it; the web/PWA build is unaffected.localServerHttp.ts'sresolveFetch()also now classifies a plugin load failure as a distinctLocalServerError('plugin_unavailable')(logged), instead of folding it into'unreachable'. See the 2026-07-28 update in ADR 0012. -
Misleading "Ready" status badge for Ollama in the browser (#266). The generic connection-status badge in
AiProviderCardand the "desktop app required" banner were driven by two independent, never-reconciled state signals βtestStatus(defaults toidleβ "Ready"/"Bereit") andisDesktop. Since the auto-test effect and the manual "Test connection" button are both disabled for Ollama in a browser,testStatuscould never leave its idle default there, so the badge always read "Ready" right next to a banner saying the opposite. It now shows a distinct "Not available in browser" label instead wheneverprovider === 'ollama' && !isDesktop, localized in all 19 locales; desktop is unaffected. Also:scanLocalOpenAiCompatibleEndpoints()now tries Ollama's native/api/tagsfirst, falling back to the OpenAI-compat/v1/modelsshim only on failure (mirrorstestOllamaConnection/listOllamaModels's existing native-first approach), so an older Ollama install without the compat shim isn't missed by the scan. -
Feature-catalog / slice default drift made structurally impossible.
features/featureCatalog.tsnow covers all 22 flags (was 16) and derives each entry'sdefaultOnfrom the slice'sdefaultFeatureFlagsStateinstead of hand-keying it β the class of bug where the catalog saidfalsewhile the slice saidtruefor ~12 flags can no longer recur (guarded by the newtests/unit/featureCatalog.test.ts). Added risk-level / desktop-requirement / dependency metadata.
- GitHub Actions workflows drop persisted checkout credentials (CWE-522, zizmor/CodeRabbit).
actions/checkoutleaves theGITHUB_TOKENin the local git config by default so later steps can push; none of the jobs acrossci.yml(8 checkout steps),codeql.yml,docker.yml,mutation.yml,storybook-debug.yml,tauri-build.yml, orvoice-nightly.ymlpush or commit after cloning, so the token had no reason to persist beyond the checkout step. All 14 checkout steps now setpersist-credentials: false. @babel/coresecurity override bounded to the 7.x line, fixing a broken Storybook build. Thepnpm-workspace.yamloverride for GHSA-4x5r-pxfx-6jf8 (>=7.29.6) had no upper bound, unlike every sibling override in the same block. When Babel 8.0.1 was published, pnpm resolved it for every consumer β includingreact-docgen@8.0.3(via the Storybook toolchain), which declares its own"@babel/core": "^7.28.0"and calls the synchronousloadPartialConfig(), removed in Babel 8 in favor ofloadPartialConfigSync(). This crashed every Storybook build (Error: Starting from Babel 8.0.0, the 'loadPartialConfig' function expects a callback). Bounded to>=7.29.6 <8, matching the pattern theundicioverride right below it already documents for the identical failure class. Verified:pnpm run build-storybookcompletes successfully again.- Tauri HTTP-plugin capability scope pinned (#266).
http:defaultalone grants no URL scope β every plugin-http call (including AI-SDK cloud calls on desktop) was silently denied by the plugin's allow-list check. The capability now explicitly allows loopback any-port (http://localhost:*/*,http://127.0.0.1:*/*β Ollama/LM Studio/vLLM + custom ports) and the cloud endpoints mirroring the Tauri CSPconnect-src(Gemini, OpenAI, x.ai, OpenRouter, Groq). - y-webrtc vendor-fork audit + CI invariant guard (#60). Full-file diff of
packages/collab-transportagainst upstreamy-webrtc@10.3.0: no deviations beyond the three documented SC patches (PBKDF2 600k iterations,extractable: false,return promise.reject) plus the DataChannel E2E encryption β recorded inpackages/collab-transport/AUDIT.md, fork bumped to10.3.0-sc2. The deprecatedpatches/y-webrtc@10.3.0.patchand the dead rooty-webrtcdependency (zero imports) are removed, and the previously-referenced-but-missingscripts/verify-vendor-fork.mjsnow exists and runs in the CI security job (verify:vendor). - Advisory batch remediated (2026-07-26, CI security gate). Cargo.lock:
quick-xml0.39.4 β 0.41.0 viaplist1.10.0 (RUSTSEC-2026-0194/0195, CVSS 7.5),anyhowβ 1.0.104,crossbeam-epochβ 0.9.20,serde_withβ 3.21.0. pnpm overrides raised:dompurifyβ 3.4.12,protobufjsβ 8.7.1,body-parserβ 1.20.6 (bounded<2for express-4 compat). The two stale no-longer-matching OSV ignores (dompurify GHSA-x4vx-rjvf-j5p4, js-yaml-3.x GHSA-h67p-54hq-rp68) were dropped fromsrc-tauri/osv-scanner.toml. Thepnpm auditCI step is now advisory (continue-on-error): some npm CDN edge nodes force-gzip the bulk-advisories response which pnpm cannot decode (ERR_PNPM_AUDIT_BAD_RESPONSE, deterministic per edge β retries useless); the OSV scan of both lockfiles remains the enforced gate.
- Dead
enableWebnnInferencefeature flag removed. The flag shipped default-on but no runtime gate ever readselectEnableWebnnInferenceβ toggling it had no observable effect (a ghost/stub, flagged byaudit-feature-parity.ts). It is removed from the slice,featureCatalog, the Settings β Experimental UI (now 22 flags / 21 user-toggleable), the i18n label (19 locales) and the parity audit. WebNN execution-provider selection remains available internally inpackages/ai-core/src/webnnBridge.ts(always attempted when the browser exposes WebNN); only the no-op user toggle is gone. - Dead Settings toggles removed (19 no-op toggles across 6 sections). A full audit found settings
that persisted a value no service or hook ever read. Removed the whole Notifications,
Performance, and Backup sections (all no-ops; the real one-click encrypted backup lives in
Settings β Data, untouched), the dead sync/import card in Integrations (Notion/Evernote/Google
Docs/Scrivener β the LanguageTool integration is real and kept), the 4 dead Collaboration
toggles (the WebRTC signaling URLs field is real and kept), and Privacy's
crashReporting/shareUsageData(analytics/encryption/data-residency stay). Also cleanedsettingsSearchHints.ts(stale hints for the removed categories; retargeted Integrations hints to grammar/spell). Each was a stacked PR with its own tests + i18n removal across 19 locales.
Critical & Immediate hardening sequence β six stacked PRs (privacy, experimental labeling, coverage, voice consent, local-AI, hygiene/docs) plus the 11β17 locale expansion, shipped as a minor release.
- Privacy β Analytics is now a real opt-out (SEC-6). The Settings β Privacy "Analytics" toggle was cosmetic β only the
enableDuckDbAnalyticsflag controlled persistence. A single enforcement point (app/analyticsGate.tsisAnalyticsPersistenceAllowed) now gates every DuckDB write path (project dual-write, codex, cross-project mirror, RAG vector mirror, seed + RAG migrations, and inference telemetry) on both the flag and the privacy toggle. The gate is re-evaluated at the last synchronous moment before each async write (no opt-out race); migrations abort without writing their done-marker (so re-opt-in retries) and recover from a transienterrorstate; a one-timeanalyticsGateMigratedmarker preserves existing-install behavior on upgrade. Analytics remain local-only metadata (never manuscript prose, never leaves the device). Full DuckDB OPFS/cell encryption stays deferred to v2.0 (docs/SECURITY-THREAT-MODEL.md). - Voice consent clarity. Corrected the misleading "all voice processing runs locally" intro (the default STT path is the cloud Web Speech API) and added a per-engine cloud-vs-on-device privacy note beside the STT engine selector (
settings.voice.engine.privacyNote, 17 locales).
- Device-aware Ollama recommendation + one-click pull.
pullOllamaModelstreamsPOST /api/pullprogress with cancel + error-retry (surfaces Ollama's in-band{error}lines, propagates AbortError for cancel, releases the reader on every path);getOllamaModelForDevicepicks a tiered model (qwen2.5:7b / llama3.2:3b / llama3.2:1b) from the device profile and steps down a size on low battery; newOllamaDevicePullsettings UI with a recommendation chip + progress/cancel/retry. - Reusable
Badgedesign-system atom (variantexperimental | beta | new | neutral, theme-token driven, accessible) with a Storybook story; applied as maturity badges (driven byFEATURE_CATALOG) in the Experimental flags list and an "Experimental" badge in the ProForge dashboard header. New "Limitations, Token Cost & Loop Risks" section indocs/PROFORGE-PIPELINE.md. - Test coverage for newer subsystems (+101 tests): collab-transport E2E crypto (the vendored y-webrtc C-1 fork), the ProForge Core Capability Layer + adapters (
proForgeCapabilityCore, schemas,agentRegistry,nodeInferenceGateway,browserProForgeCapability), and the 5 previously-untested Copilot components (CopilotPanel,CopilotLauncher,InlineAnnotationLayer,InsightSection,HeuristicsModeToggle). - Language expansion β 6 new locales (11 β 17): Finnish (
fi), Swedish (sv), Hungarian (hu), Icelandic (is), Basque (eu) and Persian/Farsi (fa, RTL, Arabic script). All ship as Beta. The high-traffic chrome (portal,sidebar,dashboard, topcommon.*verbs) plus native cold-start strings (i18nBootstrap) and glossary blocks are hand-translated; the remaining modules were then completed via the glossary-anchored bulk translator (see the bulk-translation entry below).fadirection/fonts are automatic viaRTL_LOCALES+ the existing[dir="rtl"]Noto Arabic swap β no App/CSS/font changes. New guide:docs/LANGUAGE-EXPANSION-2026.md. - Bulk-translate hardening (
scripts/bulk-translate-locales.mjs): placeholder masking ({{token}}β sentinel β restore, so MT can't mangle interpolation) and a--dry-runmode (per-file key + glossary-hit counts, no network calls, no writes). The 6 new languages are added toSUPPORTED_LANGS,check-i18n-keys.mjs, andbuild-i18n.mjs. Thei18nPlaceholdersguard now covers all 17 locale bundles. - Localized language picker:
LanguageSelectornow resolves each language's exonym label (e.g. "Finnish", "Swedish") throught('portal.language.names.<code>')at render time instead of a hardcoded string β the native endonym (Suomi,Svenska, β¦) stays hardcoded by design so users always find their own language regardless of the active UI locale. Addsportal.language.names.*(17 names) to all 17 locales, hand-translated for the 5 core + 6 new languages (other Beta locales' exonyms filled by the bulk translator).portalchrome is now fully localized for the 6 new languages. - Beta-locale bulk translation (10 languages): ran the glossary-anchored, placeholder-masked
bulk-translate-locales.mjspipeline forfi/sv/hu/is/eu/fa(full) and topped upja/zh/pt/el, lifting the 6 new locales from ~8 % to 90-93 % coverage (machine-translated, Beta quality, human native review tracked as follow-up).help.json(long-form rich HTML) stays English fallback for the new langs and is excluded from--allβ its tag-dense markup is not safely machine-translatable. Glossary expanded to v2.0 (~44 anchor terms/locale: +Co-Pilot,ProForge,Subplot,Timeline,Snapshot,Synopsis,Mind Map,Word Count,Continue Writing,Improve Writing,Consistency Checker,Plot Hole, β¦). Two bulk-script bugs fixed:glossaryTranslatepartial-match (β exact-match only) and--allmanglinghelp.jsonHTML (β excluded). - New
docs/TRANSLATION-GUIDE.md: end-to-end localization guide β architecture/build flow, placeholder/token rules, tone-by-category, RTL guidelines + per-locale verification checklist, glossary usage, native-review checklist, common pitfalls, and the new-language contribution workflow.docs/I18N-GLOSSARY.mdupdated for the v2.0 anchor set.
enableIdbAtRestEncryptiondefault drift:featureCatalogdeclareddefaultOn: false, contradicting the slice (true, the source of truth) β reconciled.- README metric drift:
scripts/sync-readme-metrics.mjshard-coded the locale count to11, so its regexes stopped matching after the 11β17 expansion and silently froze the i18n key count. Locale count is now dynamic and the regexes match any digit count β README reads the live 17 locales / 2786 keys with the drift guard green.
-
Dependency hygiene + onboarding + docs truth-up: documented the
joiaccepted-risk override (GHSA-q7cg-457f-vx79; still required viawait-on) and the SBOM deferral inAUDIT.md(pnpm audit --audit-level=highclean); corrected the stalepublic/sw.js"must hand-syncAPP_VERSION" note inCLAUDE.md; added a "Do NOT run heavy suites locally" callout + Minimal Change Checklist toCONTRIBUTING.mdand mirrored the heavy-suite warning into.github/copilot-instructions.md. -
Docs completion (language-expansion pass): README i18n badge, language list, capability table and metrics line updated to 17 locales / 2716 keys; Persian added to the RTL-Beta section;
AUDIT.mdfollow-up chain + quality-gate entry for 2026-06-17.
- Tauri desktop app stuck on "WorldScript Studio ist offline.": the PWA Service Worker was registering inside the Tauri WebView (WebView2 supports SWs) and hijacking the root navigation. When its versioned caches were empty (precache fails under the
tauri.localhostcustom protocol while a version bump had already pruned the previous caches), the SW's network-first navigation strategy fell through to the hardcoded inline offline fallback (public/sw.js), rendering a bare " ist offline." page instead of the app. A Service Worker has no place in Tauri β the desktop app is already served locally and offline-first. Two-layer fix: (1)register-sw.tsnow detects the Tauri runtime and never registers, additionally unregistering any SW + deletingworldscript-*caches so already-broken installs self-heal; (2)public/sw.jsdetects the Tauri origin (tauri:///tauri.localhost) and becomes a no-op β it precaches nothing, never interceptsfetch, and self-unregisters onactivate. The browser PWA path is unchanged.
- v1.23 P0 tracker reconciliation (docs):
ROADMAP.md,TODO.md, andAUDIT.mdbrought into agreement after a drift where ROADMAP marked all v1.23 P0 items done while TODO still listed three as open. Each item is now evidence-backed (audit output, CI run, file existence). The manual smoke-test run is split out as a tracked human-only step (the protocol document itself is complete). - AUDIT.md Known Overrides table refreshed: added the just-merged
esbuild >=0.28.1override (GHSA-67mh-4wv8-2f99, dev-server CORS), replaced placeholder advisory strings with verified GitHub Advisory IDs (re-checked 2026-06-13), and labelled preventive-only pins honestly. Dependency hygiene re-verified:pnpm audithigh and moderate β 0 vulnerabilities. - Dependency maintenance (Dependabot): bumped
@ai-sdk/openai,@ai-sdk/google,yjs,tailwindcss,turbo,lint-staged, and thelogcrate to current patch/minor releases (web CI green on each).
- Plugin sandbox adversarial test coverage:
tests/unit/workers/plugin.worker.test.tsgains WebAssembly-denial,GeneratorFunction/AsyncGeneratorFunctionconstructor-escape, and guard-restoration (success + error path) tests for the v1.22 plugin-isolation hardening. New living audit artifactdocs/AUDIT-PERFECTION-PLAN-v1.23.mdtracks the 6-phase perfection engagement and its follow-ups. - AI error taxonomy (
services/ai/aiErrorTaxonomy.ts): pureclassifyAiError(err)β{ category, retryable, messageKey }across transient / rateLimit / auth / network / offline / policy / invalidRequest / canceled / permanent (cancellations viaAbortErrorfail fast β never retried). Consumed by the retry layer. - Actionable AI error messages in the Copilot: when an AI call fails, the Copilot now shows a localized, recovery-oriented message (e.g. "Invalid or missing API key β open Settings β AI & Models to add or update it.") instead of a generic "Something went wrong". Maps each taxonomy category to a new
error.ai.*key (9 keys Γ 11 locales; translated in de/en/es/fr/it, English fallback in Beta/RTL) via a reusablegetAiErrorMessage(err, t)helper. - AI request correlation IDs: each AI generation now gets one opaque correlation id (
newCorrelationId,services/logger.ts) shared by the request-start log (useWorldScriptAI), the fetch-side failure log (worldScriptCompletionFetch, propagated via the request body), and the retry seam (withTransientRetry) β for end-to-end traceability. No prompts or keys are logged. - Rebrand to WorldScript Studio: rename StoryCraft Studio β WorldScript Studio across user-facing code, assets, PWA manifest, Tauri config, i18n (11 locales), docs, and CI/CD. New identifiers:
worldscript-studiopackage,com.worldscript.studioTauri identifier,/WorldScript-Studio/GitHub Pages base,worldscript://deep-link scheme,.worldscript/.wsstfile associations. PWA service-worker cache names bumped toworldscript-*v1.23.0 to invalidate stale caches. Feature-Flags localStorage key, IndexedDB database names, and accessibility CSS class tokens all renamed toworldscript-*. This is a pre-release rebrand with no existing installs, so no storage migration is required (thestorycraft-driver-popoveronboarding-tour token is the one intentionally unchanged token, as its class usage was not renamed). - Test coverage β Tauri filesystem backend (Phase 2):
services/fs/(the desktop FS storage chain β project/snapshot/asset/settings/codex stores +fsCore) went from 0% to ~81% line / ~66% branch coverage via an in-memory fake-TauriApisharness that drives real round-trips (LZ-String compression, AES-GCM API-key encryption, JSON, import/export). 37 new tests (tests/unit/services/fs/). - Local-first data model foundation (behind
enableLocalFirstSync, off by default): a perf baseline harness (pnpm bench, A0.1), a Y.Doc proof-of-concept (services/localFirst/projectDoc.ts, B0.1), and an incremental write-through doc binding + debounced shadow-sync (docBinding.ts/docPersistence.ts, B1.1) that keeps a Yjs shadow in lockstep with Redux without affecting users β Redux stays the source of truth during the shadow phase. ADR-0008. (#140)
- Desktop app no longer launches to a blank window: the Tauri build resolved Vite's
basefromTAURI_PLATFORMβ the Tauri 1.x env var, which Tauri 2.x never sets β so every desktop build fell through to the GitHub Pages base (/WorldScript-Studio/) and 404'd its hashed assets undertauri://localhost/, leaving only the native file/help menu over an empty webview (Windows/macOS). Base resolution now checksTAURI_ENV_PLATFORM(legacy name kept as a fallback) and is extracted into a unit-testedconfig/resolveViteBase.tsso the regression can't return. Note: the Linux.deb/AppImage requirewebkit2gtk-4.1(Ubuntu 22.04+) β a Tauri 2 platform minimum, not a bug. - AI retry no longer backs off doomed calls:
withTransientRetrynow classifies the error and fails fast on non-retryable categories (invalid API key, policy block, malformed request, offline) instead of retrying with exponential backoff. Transient / rate-limit / network errors still retry (honoringRetry-After). Each retry decision emits a structuredai.retrylog line with a per-call correlation id (no payloads or keys). AshouldRetryoption allows callers to override the default. - Plugin worker no longer leaks a sandbox guard across runs (FU-1):
workers/plugin.worker.tsrestoredFunction.prototype.constructorthrough the bareFunctionidentifier, which install had reassigned to the denied stub β so the real constructor stayed neutered after a plugin run. Restoration now routes through the module-captured nativeFunction, keeping the dedicated worker's global scope clean between tasks. Restoration is also hardened: theself.*bindings are restored first and unconditionally, and each constructor is force-redefined viaObject.defineProperty(best-effort), so a plugin that locks a property descriptor cannot abort the restore and poison subsequent runs. Isolation during execution is unchanged. - Command Palette fully localized: ~20 command labels and category headers (AI Execution Modes, editor modes, appearance presets, accessibility toggles, Navigation/Editor/Global/etc.) were showing English in de/es/fr/it because the keys existed but the values were never translated. All are now translated for the core locales (de/en/es/fr/it). A new guard test (
tests/unit/i18n/paletteLocalization.test.ts) fails if anypalette.*key reverts to English fallback in a core locale (with a small allowlist for loanwords). Beta/RTL locales remain English-fallback by policy. - Writer shows localized, classified AI errors: the Writer's generation-failure handler dispatched a hardcoded English string ("Error generating contentβ¦") and a hardcoded
[Cancelled]tag. It now usesgetAiErrorMessage(err, t)(same actionable, localized messages as the Copilot) and a localizedwriter.cancelledTag. - OpenRouter cloud-policy block is now reactive: the OpenRouter settings panel derives its blocked state from live Redux (
aiMode+privacy.localStorageOnly) instead of a one-shot async check, so toggling Local storage only or switching the AI mode updates the banner and the test-connection / catalog-fetch guards instantly. Split into distinctpolicyBlocked.mode/policyBlocked.localOnlymessages (translated across the 8 non-stub locales) and fixed an infinite render-loop in the policy-blocked catalog path. (#163) - Local AI settings localized: the 40 English-only
settings.ai.localAi.*keys (WebGPU capability, model downloads, storage usage, fallback chain, throughput) translated for de/es/fr/it/el/ja/pt/zh (ar/he stay English stubs; model names unchanged). (#164) - Real WorldScript app icons: replaced the placeholder icons β generic 16-bit RGBA PNGs that broke the macOS/Windows Tauri bundler (
unsupported ColorType: Rgba16) and blocked the desktop release β with a new "W" quill-nib monogram, regenerated as 8-bit acrossfavicon.svg, the PWA icons, and the full Tauri icon set (incl. a newly generatedicon.icns). Also fixed remaining StoryCraft URLs inrobots.txt/sitemap.xml/_redirects/CNAME.exampleand renamed the Cloudflare Pages project inwrangler.toml. (#165)
-
OpenRouter provider (Cloud 5): Unified gateway to 100+ open-source models.
services/ai/providers/openrouterProvider.tsβ circuit breaker (4 Γ 429 β 5 min pause), RPM tracking, free-tier catalog (deepseek/deepseek-r1:free,meta-llama/llama-3.3-70b-instruct:free,qwen/qwen2.5-72b-instruct:free,google/gemma-3-27b-it:free,mistralai/mistral-7b-instruct:free). Settings β OpenRouter panel with enable toggle, API key (AES-encrypted), model selector. Sign up at openrouter.ai/keys β no credit card for:freemodels. -
AI Execution Modes:
AiMode = 'hybrid' | 'cloud' | 'local' | 'eco'β four routing strategies exposed in Settings β AI & Models β AI Execution Mode:- Hybrid (default): local models when preloaded β cloud fallback
- Cloud: all requests to configured cloud provider
- Local: on-device only via Ollama / WebLLM / ONNX β nothing leaves the device
- Eco: battery-saving tiny 0.5B model + heuristics only; no cloud, no GPU
aiModeService.tspersists the active mode tosettings.aiMode;listenerMiddlewaresyncs without page reload.AiModeIndicatorchip in the Copilot header shows the active mode and turns amber when the OpenRouter circuit breaker is open.
-
Ultimate Copilot AI v2 β Phase 2+3 (PR #110, #111):
- Markdown rendering in
CopilotMessageListβ assistant messages rendered as sanitised HTML (DOMPurify + inline micro-markdown renderer; headings, bold, italic, code blocks, lists). No new runtime dependency. - Sidebar/dialog mode toggle β panel can be docked to the right edge on desktop (β₯ 768 px); preference persisted in
localStorage. Mobile always uses dialog mode. - Apply-to-chapter β "Apply to chapter" button on the last assistant code block rewrites the active manuscript chapter via
applyTextEdit(offset-safe, dispatched into redux-undo for Ctrl+Z reversal). Gated to blocks β₯ 70 % of section length to prevent partial-snippet overwrites. - InlineAnnotationLayer β absolute-positioned badge inside
ManuscriptEditorshowing the heuristic-insight count for the active chapter. Clicking opens the Copilot and auto-expands the Insights section. - ProForge "Ask Copilot" chip β each
ReviewItemCardin the ProForge Review Panel shows an β¦ Ask Copilot button (gated byenableGlobalCopilot) that pre-fills the Copilot composer with the item's context. docs/COPILOT.mdβ user-facing feature guide (architecture, modes, Apply-to-chapter, ProForge integration).docs/HEURISTIC-RULES.mdβ per-rule reference (8 rules, how-to-satisfy, i18n key pointers).- 2 new E2E tests β heuristics-only toggle and sidebar mode toggle in
copilot-flags.spec.ts.
- Markdown rendering in
-
WebLLM worker offload (P1-1, ADR-0005):
@mlc-ai/web-llm(WebGPU) inference now runs in a dedicated WorkerBus v2webllmpool (workers/v2/webllm.worker.ts, capabilityinference.webllm) instead of inline on the main thread. Worker-first with an automatic main-thread fallback onNO_WEBGPU/ worker-spawn failure / circuit-open, decoupled fromenableWorkerBusV2. GPU mutex + tab-leader election stay on the main thread; loading progress bridges toinferenceProgressEmitterso the UX is unchanged. -
Whisper WASM STT end-to-end tests (P1-2): A deterministic, deep-E2E suite (
tests/e2e/deep/voice/whisper-stt.spec.ts) exercises the full voice orchestration β simulated model download (progress / cancel / error β retry), STT β intent β command-dispatch navigation, and stop-listening stability β via a guarded test seam (services/voice/voiceTestSeam.ts). A non-blocking nightly workflow (voice-nightly.yml) runs the real Whisper download + pipeline init against the live CDN.
- Voice hardening (v1.21 follow-up): Transcript redacted from the intent-engine debug log
(C-P0 β user speech is PII and the IDB log sink persists it); single-flight guard on
VoiceCommandService.startListeningagainst re-entrant push-to-talk / wake-word starts; download modal progress is now an accessiblerole="progressbar"with a polite live region (Progressatom +VoiceModelDownloadModal). - i18n: 2 594 keys Γ 11 locales (+62 keys for AI Execution Modes, OpenRouter settings, Copilot v2 actions).
- Prompt injection & plugin isolation hardening (PR #114):
services/proForge/applyReviewEdits.tsnow rejects C0 control characters (except\t,\n,\r), null bytes, and lone surrogates in AI-proposed edits; invalid items are skipped individually instead of aborting the whole batch.services/copilot/actionApplier.tswhole-section replacement on empty chapters now works by passing an explicit full-range edit.services/pluginRegistry.tsenforces stricter storage-key validation: maximum length, allowed suffix characters, anti-traversal (..), and a 2 MiB serialized value size cap.components/copilot/CopilotMessageList.tsxDOMPurify config hardened withALLOW_DATA_ATTR: false,FORBID_ATTR: ['style'], andSANITIZE_DOM: true.
- PWA blank screen on update: SW
APP_VERSIONbumped1.20.0 β 1.21.2so the activate handler correctly prunes the stalestorycraft-static-v1.20.0cache after deployment.
- Sepia dark mode β "Candlelit Manuscript" variant: New warm low-light theme variant joining the light/dark/sepia families; body-class themed via
--sc-*tokens (nodark:prefix). (1321478) - Deep E2E coverage layer: Non-blocking
e2e-deepjob β feature-flag matrix (tests/e2e/deep/feature-flag-matrix.spec.ts) parametrized acrosstest-matrix.ts, plus error-path specs (offline AI, rapid nav, all-flags-on). Explicit per-flag specs seed state viasetFeatureFlags(). (663ca2f) - Chinese (zh) locale + pt/el Beta translation batches: zh Simplified brought under the 5% English-placeholder target; pt/el Beta translation batches landed. (
364025e,e8cddcb) - Whisper WASM STT download UI + VADβWhisper bridge:
VoiceModelDownloadModalships;VoiceActivityCoordinatorwiresWebRtcVadEnginePCM frames intoWasmSttEngine(MIN_SPEECH_CHUNKS gate + MAX_BUFFER_MS flush) behindenableVoiceWasm. (e8cddcb,364025e)
- CSP connect-src β documented BYOK tradeoff (ADR-0004): Explicit cloud-provider endpoints in
index.htmlconnect-srcremoved as redundant; the intentionalhttps:scheme-source (required by the shippedopenAiCompatibleBaseUrlBYOK feature) is retained and documented. Tauri CSP stays strict (nohttps:blanket). Regression test intests/unit/csp.test.ts. - Coverage batches AβC: Incremental unit-test coverage additions; thresholds held at lines 74 / functions 67 / branches 60 / statements 72. (
364025e) - Dependency bumps:
@huggingface/transformers3.8.1 β 4.2.0 β major bump verified (WS-3): the APIs ai-core/voice consume are unchanged in v4.2.0 (pipeline(task, model, { dtype, device }),env.backends.onnx.wasm.proxy,RawAudio/read_audioexports);pnpm typecheckclean and 63 ai-core/voice integration tests green; no source changes required. Production bundling (rolldown tree-shaking /vendor-ai-onnxchunk) is exercised by the CIbuild+smoke:prodjobs. Also@biomejs/biomeβ 2.4.16,@mlc-ai/web-llmβ 0.2.84,viteβ 8.0.16,@google/genaiβ 2.8.0,@tanstack/react-virtualβ 3.14.2.
- Command palette footer contrast (a11y):
text-mutedβtext-secondaryfor WCAG 2.2 AA contrast. (672e56d) - Voice settings tab + auto-save false-positive + help locale cleanup. (
e049f08) - E2E / CI stabilization: viewport-aware nav locators, ProForge empty-state visibility on Desktop Chrome, Voice WASM section, Early-Access hyphenated German label, and 17 unit-test fixes across SettingsView/HelpView/VoiceSettingsSection. (
43602c2,f16ba42,3cf9387,fe598ed,d73397e)
- Integrity & hardening cycle (v1.21, audit F-1β¦F-9): README badge β released v1.20.0 + refreshed metrics (433 test files / 2 357 i18n keys); 28 misfiled v1.19-era CHANGELOG entries migrated to
[1.19.0]; TODO sprint rollover. ADR-0004 (CSP/BYOK). New suppression-count ratchet gate (scripts/check-suppressions.mjs, wired into CIquality) + first abatement tranche β 22noExplicitAnysuppressions removed, baseline ratcheted 181 β 159. Bundle-budget single source of truth (bundle:budget=--max-kb 6500 --max-entry-kb 4000, script defaults aligned). Governance docs:VENDOR-FORKS.mdgains a CVE/OSV-coverage section (the vendored y-webrtc source is invisible to OSV β manual upstream-advisory process documented), newdocs/COVERAGE-POLICY.md(threshold ratchet rule).
1.20.0 β 2026-06-07
- UI Modernization Phase 1 β LanguageSelector, RadioGroup, Tabs:
LanguageSelector.tsxβ Modern combobox with search functionality, flag emojis, RTL support, and reduced-motion awareness. Replaces inline language buttons in WelcomePortal with a searchable dropdown.RadioGroup.tsxβ Accessible radio group component with proper ARIA attributes (role="radiogroup"), individual option descriptions, and glassmorphism styling.Tabs.tsxβ WAI-ARIA compliant tabs component with three variants:default,pills, andunderline. IncludesTabPanelcomponent for content association.SettingsShared.tsxβ ToggleSwitch optimized for RTL layouts with reduced-motion support.docs/UI-MODERNIZATION.mdβ Comprehensive guide for UI component usage, migration patterns, and design principles.
- Phase 3 i18n Expansion β ja/zh/pt/el Beta languages + Intl APIs:
- Added Japanese (ja), Chinese Simplified (zh), Portuguese (pt), and Greek (el) as Beta languages with English placeholder text
- Extended
Languagetype andVALID_LANGSarray inI18nContext.tsx - Added
SUPPORTED_LOCALESmetadata array with BCP47 codes, native names, direction, and font script hints - Integrated native Intl APIs with caching:
Intl.PluralRules,Intl.NumberFormat,Intl.RelativeTimeFormat,Intl.Collator,Intl.ListFormat,Intl.DisplayNames - Added
getPluralCategory(),formatNumber(),formatRelativeTime(),getCollator(),formatList(),formatDisplayName()toI18nContextType - Auto-formatting of
{{count}}placeholders int()function - Fonts: Noto Sans JP via Google Fonts CDN for Japanese/Chinese; Greek uses system fallback
- CSP updated to allow fonts.googleapis.com and fonts.gstatic.com
- Documentation:
docs/I18N-PLURALS.md,docs/I18N-NUMBERS.md,docs/I18N-LOCALE.md,docs/I18N-RELATIVETIME.md,docs/I18N-COLLATION.md,docs/I18N-LISTFORMAT.md,docs/I18N-DISPLAYNAMES.md,docs/I18N-GLOSSARY.md - 2339 keys Γ 11 locales (up from 2259 Γ 7)
- 53 unit tests covering all Intl APIs
- World Building "Add Manually" now opens the atlas editor (2026-06-03):
useWorldView.handleAddNewManuallyonly dispatchedaddWorldand left the user on the grid with a silent "New World" card and no editor β inconsistent with Characters, whose manual-add was deliberately fixed to open the dossier. Now mirrors that flow (create β select β open atlas) with fully-formed defaults (timeline/locationsas[]). Adds real-browsertests/e2e/world.spec.ts(was none) + a hook-level regression assertion. - CI unblock β OSV
pasteadvisory + Vercel rate-limit noise (2026-06-03):src-tauri/osv-scanner.tomlβ ignoreRUSTSEC-2024-0436(paste1.0.15 unmaintained; build-time proc-macro helper, no runtime exposure, no fix release). The advisory was newly published and was failing the required Security Audit check on every branch (e.g. Dependabot PR #78).vercel.jsonβ"github": { "silent": true }so Vercel still deploys but no longer posts commit statuses; the free-tier "Deployment rate limited β retry in 24 hours" preview failure can no longer show as a hard fail on PRs. (Vercel was never a required status check, so this is purely cosmetic-noise removal.)
-
Tauri Desktop Pipeline β P0-1 complete (2026-06-06):
pnpm-workspace.yamlmigration β movedoverrides,peerDependencyRules,onlyBuiltDependencies,patchedDependencies,ignoredBuiltDependencies, andallowBuildsfrom deprecatedpackage.json"pnpm"field topnpm-workspace.yaml; resolvesERR_PNPM_LOCKFILE_CONFIG_MISMATCHon CItauri-build.ymlβshell: bashfor Windows compatibility; skipsTAURI_SIGNING_PRIVATE_KEYforworkflow_dispatchtest builds;jq-disablescreateUpdaterArtifactswhen no signing key is available (prevents "public key found, but no private key" error)- macOS: removed invalid
exceptionDomainobject andsigningIdentity: "-"from bundle config; addedEntitlements.plistwith hardened-runtime permissions - Release profile hardening:
lto = true,codegen-units = 1,strip = true,panic = "abort" - Bundle metadata:
category,publisher,copyright,shortDescription,longDescription - Verified: ubuntu-22.04 (deb, rpm, AppImage), windows-latest (MSI), macos-latest (DMG) all build successfully
-
Coverage C-7 β 96 new unit tests (2026-06-06):
tests/unit/loraDatasetBuilder.test.ts(19) β scene pair extraction, quality scoring, synthetic generation, JSONL export (Alpaca/ChatML/ShareGPT), quality report estimationtests/unit/loraEvaluationService.test.ts(16) β cosine similarity, mean similarity, style consistency scoring, score labels, prompt output comparisontests/unit/intentEngine.test.ts(17) β exact template matching, fuzzy Jaccard scoring, navigation slot extraction, view context filtering, command replacementtests/unit/feedbackService.test.ts(23) β TTS queue processing, feedback level filtering (minimal/standard/verbose), mute behavior, event emission, confirm/error/info helperstests/unit/audioNavigator.test.ts(21) β ARIA landmark scanning (main/nav/aside/region/search), focus cycling,tabindexmanagement, live region announcements with priority switching
- Accidentally committed signing keys removed (2026-06-06):
~/.storycraft-tauri.keyand~/.storycraft-tauri.key.pubwere committed inda7653b; rotated in GitHub Secrets, files removed,.gitignorehardened with*.key,*.pem,*.p12,*.pfx,*.cer,*.der,*.sig.key - aiohttp CVE remediation (2026-06-06): bumped
aiohttp==3.11.16β3.14.0inscripts/ci-analyzer/requirements.txt; resolves 21 Dependabot CVEs (GHSA-...)
1.19.0 β 2026-05-28
-
B-1 β IDB At-Rest Encryption (
services/storage/storageEncryptionService.ts): Full AES-256-GCM passphrase-derived encryption for IndexedDB stores. PBKDF2 (600 000 iterations, SHA-256, 32-byte random salt stored inapp-dataasidb_kdf_salt_v1).CryptoKeyis{ extractable: false }. Feature-flagged behindenableIdbAtRestEncryption(off by default). Tauri build usestauri-plugin-strongholdfor OS-keychain-backed passphrase (zero user friction). Web build shows passphrase unlock modal on cold start (session-scoped in-memory key wiped on tab close). GDPR threat model: encrypted blobs unreadable without passphrase from browser profile or malicious extension. Storage decomposition inservices/storage/(idbCore,idbProjectStore,idbSnapshotStore,idbKeyStore,idbCodexStore,idbAssetStore). -
B-2 β Voice WASM Engine Scaffold (
services/voice/wasmSttEngine.ts,services/voice/sileroVadEngine.ts): Whisper.cpp WASM STT engine interface scaffold (model download, chunked inference, 99+ language detection). Silero VAD v4 via ONNX Runtime Web (~2 MB model, lazy-loaded). Both implement the existing abstractSttEngine/VadEngineinterfaces fromvoiceTypes.ts. Feature-flagged behindenableVoiceWasm(off by default); falls back toWebSpeechSttEngine/WebRtcVadEnginewhen off. -
B-3 β collab-transport Vendor Fork (
packages/collab-transport): Vendor fork of y-webrtc 10.3.0 with RTCDataChannel in-flight E2E encryption baked into the package source. Replaces the pnpm-patch approach (patches/y-webrtc@10.3.0.patch). All Yjs sync updates and awareness protocol messages over peer-to-peer WebRTC data channels are encrypted via AES-256-GCM usingroom.key. Workspace package consumed asworkspace:*. -
B-4 β axe-core E2E Accessibility Gate (
tests/e2e/a11y-axe.spec.ts): 8-view axe-core WCAG 2.2 AA Playwright scan run in CI on every push. Views covered: Dashboard, Writer, SceneBoard, Characters, Worlds, BookPreview, ProgressTracker, Settings. Zero violations enforced (expect(violations).toHaveLength(0)); known non-blocking notices logged but not failed. -
B-5 β RTL Layout Beta: Arabic (
ar) and Hebrew (he) locale stub files added tolocales/.enableRtlLayoutfeature flag activateshtml[dir="rtl"]and a BiDi context provider for bidirectional text layout. Full RTL translation content and Persian (fa) support are v2.0 milestones. ExistingenableRtlLayoutflag wired tohtml[dir]control inApp.tsx. -
B-6 β StructuredLogger (
services/logger.tsrewrite): Ring-buffer replaced with a multi-sink structured logger:- IDB sink β
storycraft-logs-db/logsstore, 1 000-entry LRU cap, auto-eviction via forward cursor. - Tauri JSONL sink β
$APPDATA/logs/storycraft-YYYY-MM-DD.jsonl, lazy-loaded Tauri FS modules, date-rotated,{ append: true, create: true }. - Console sink β DEV-only, prefixed
[StoryCraft:LEVEL:module]. - GDPR sanitization β
sanitizeLogContext(ctx)redacts values whose key matches/key|token|password|passphrase/i. - New API β
createLogger(module): ModuleLoggerfactory with.debug()/.info()/.warn()/.error()and.withContext(ctx)for structured context injection. Defaultloggerexport andgetRecentLogs()/formatLogsForReport()/clearLogs()retained for backward compatibility.
- IDB sink β
-
B-7 β Coverage Thresholds Raised: Vitest gate: Lines β₯ 71 / Functions β₯ 63 / Branches β₯ 57 / Statements β₯ 69. Measured: 73.06% L / 65.18% F / 58.79% B / 71.29% S β all green.
-
B-8 β Stryker Gate Raised:
thresholds.breakraised 70 β 75.mutatetargets expanded from 34 β 40 source files to cover new services introduced in B-1..B-6. -
Sequential shell execution rule codified in all 4 instruction files (
CLAUDE.mdproject root,.github/copilot-instructions.md,.cursorrules,infra/low-end-ci/DAILY-DRIVER.md) β ONE Bash call per response, no parallel shell calls on this 3.7 GB RAM hardware. -
WebGPU detector service (2026-05-18): New
services/ai/webGpuDetectorService.tsβdetectWebGpuDetails()queriesnavigator.gpu.requestAdapter(), readsadapter.limits.maxBufferSizefor VRAM tier heuristic (β₯8 GB = high, β₯4 GB = medium, else low). AiProviderCard gains live GPU status badge, WebLLM model dropdown, and ONNX model dropdown.LOCAL_INFERENCE_PROVIDERS+isLocalInferenceProvider()added toorchestrationProviders.ts. 12 new settings i18n keys across all 5 locales (1408 β 1414 total). -
ONNX Runtime Web Layer-2 in ai-core (2026-05-18):
packages/ai-core/src/index.tsadds an ONNX WASM fallback layer between WebLLM and Transformers.js.LocalAiLayertype includes'onnx'.ONNX_SUPPORTED_MODELSexported.vite.config.tsgainsvendor-ai-onnxmanual chunk to keep onnxruntime-web + @xenova/transformers under Workbox's 8 MiB SW cache limit. -
Yjs AES-256-GCM encryption foundation (2026-05-18):
collaborationService.tsgainsencryptUpdate(),decryptUpdate(),deriveEncryptionKey()(PBKDF2 600 000 iterations, SHA-256, AES-256-GCM), andgetEncryptionStatus()('encrypted' | 'psk-only' | 'plaintext').CollaborationPanel.tsxshows green/amber encryption status badge post-connect. 3 new collab i18n keys. -
Tauri v2 auto-updater pipeline (2026-05-18):
tauri-build.ymlgains aGenerate latest.jsonstep that builds the Tauri v2 update manifest from signed.sigfiles and uploads it to GitHub Release.docs/TAURI-UPDATER.mdextended with a full GitHub Secrets table.docs/TAURI-CI.mdgains a 7-step first-release checklist. -
Cross-Project-Search v2 (2026-05-18): DB_VERSION 7β8 with new
projects-index-store. NewcrossProjectIndexService.tsβindexProject(),listIndexedProjects(),removeProjectIndex()(privacy-preserving: no manuscript plaintext).searchAcrossProjectIndex()added tocrossProjectSearchService.ts.CrossProjectSearchPanel.tsxruns two-phase search (index first, then current project). 3 newcrossSearch.*i18n keys (1414 total). -
Mobile-aware E2E helpers (2026-05-17):
clickNavItem(page, name)intests/e2e/helpers.tsβ tries desktop#sidebar(hidden md:flex), then mobile bottom-tab-bar ([data-tour="nav-mobile"]), then the "More" sheet; eliminates allsidebar(page)calls that fail on Pixel 5 viewport.selectFirstEnabledWriterSectionnow switches to the context tab on mobile before locating the section selector. -
ARIA tablist on WriterView mobile segmented control: Each tab button gains
role="tab",aria-selected,aria-controls,data-testid="writer-tab-{context|tools|result}"; container gainsrole="tablist"; panel divs gainrole="tabpanel"+aria-labelledbyβ axe-compliant and stably selectable in Playwright. -
Mobile VC button in WriterViewUI:
md:hiddenversion of the version-control toggle button withdata-testid="writer-version-control-btn"andaria-expandedβ mirrors the desktop button that is hidden on Pixel 5 viewport. -
Stable test anchors:
data-testid="snapshot-label-input"on the snapshot-label<Input>inVersionControlPanel.tsx;data-testid="export-preview"on the<pre>export preview inExportView.tsx. -
OSV vulnerability scan in CI security job:
google/osv-scanner-action@v2step wired afterpnpm auditβosv-scanner.tomlexisted but was never executed; advisories now caught on every push/PR. -
JUnit E2E artifact: Playwright JUnit reporter output (
tests/e2e/results/junit.xml) uploaded ase2e-junitartifact β enables per-test check annotations on GitHub PRs.
-
services/logger.tsβ backward-compatloggerexport,getRecentLogs(),formatLogsForReport(), andclearLogs()retained; in-memory cache kept at 200 entries as fast path forformatLogsForReport. -
packages/collab-transportreplaces pnpm-patchedy-webrtcdependency;patchedDependenciesentry removed frompackage.json. -
i18n Comprehensive Sweep (2026-05-18): All remaining hardcoded user-facing strings eliminated across 5 locales β 1 440 keys total (up from 1 414). Fixes include:
help.tryTour(was rendering raw key"try.Help"in the command palette),initialProject.chapter1("Chapter 1"in projectSlice + AdvancedImportExport β extendedresetProjectpayload with optionalchapter1Title),manuscript.resizer.left/right(hardcoded German string"Linkes Panel anpassen"remained in source),writer.stopGenerating / tools.selectLabel / versionControl.tooltip,settings.ai.temperature.precise/balanced/creative,export.pasteSection.heading("Google Docs / Notion"),outline.result.body,characters.uploadImage / editorTabsAriaLabel,worlds.uploadImage / editorTabsAriaLabel,templates.tabs.myTemplates / community,error.boundary.title/description/reset/reload/report,manuscript.spellcheck.didYouMean/applyFix,manuscript.grammar.checkButton,manuscript.zenMode.enter/exit/label, andwriter.studio.controls.custom/customTonePlaceholder. -
ErrorBoundary fully localized (2026-05-18):
components/ui/ErrorBoundary.tsxrefactored with innerErrorFallbackfunctional component β accessesuseTranslation()hook to render title, description, and all buttons (Reset View, Reload Page, Report issue) in the active locale. Import path corrected tohooks/useTranslation;onResetpassed conditionally to satisfyexactOptionalPropertyTypes. -
Unit-test coverage β 1 641 tests / 150 test files (2026-05-18): Measured 62.86 % statements Β· 49.06 % branches Β· 54.10 % functions Β· 64.68 % lines. Vitest thresholds at statements 53 / branches 37 / functions 50 / lines 55 β all passing.
-
Unit-test coverage β Phase 4.5 thresholds met (2026-05-17): Measured 63.32 % lines Β· 61.5 % statements Β· 47.1 % branches Β· 53.2 % functions (1 561 tests). Vitest thresholds at lines 55 / statements 53 / branches 37 / functions 50.
-
Stryker mutation gate enforced:
thresholds.breakraisednullβ30;timeoutMSlowered 180 000 β 120 000 ms; CI mutation jobcontinue-on-errorβfalse,timeout-minutes: 20β30. -
Lighthouse performance promoted to error:
categories:performancewarn:0.5βerror:0.4;categories:seoadded aswarn:0.8; FCP tightened 6 000 β 5 000 ms; LCP tightened 8 000 β 7 000 ms. -
CI concurrency fix:
cancel-in-progressrestricted to PRs only β main-branch deploys no longer cancelled by a concurrent push. -
Artifact retention aligned:
dist7 β 3 days;lighthouse-reportandstorybook14 β 7 days. -
Unit-test coverage β Phase 1 thresholds met: 17 new test files added (733 tests total); Vitest coverage thresholds bumped to
{ lines: 35, functions: 30, branches: 22, statements: 33 }(previously 25/21/17/24). Measured coverage: 36.47 % lines Β· 35.53 % statements Β· 24.96 % branches Β· 30.22 % functions β all Phase 1 targets exceeded. New files cover:commands/(fuzzyScore, palettePreferences, commandSystem), project thunks (writing, character, binder, management), hooks (useDashboard, useManuscriptView, useGlobalKeyboardShortcuts, useCharacterView, useOutlineGenerator),aiProviderServicefallback chain,dbServicesnapshots,dbServicebinder assets, andcrossProjectSearchService. -
Stryker mutation targets expanded (Phase 4):
stryker.conf.jsonmutatearray now includesservices/commands/fuzzyScore.ts,services/commands/palettePreferences.ts, andservices/commands/commandBuilder.tsin addition to the existingcodexService.tsanddbMigration.ts.
-
TypeScript 6 strict hardening (2026-05-18):
'grok-3'and'grok-3-mini'added toAiModelunion intypes.ts(TS2322 inaiProviderService.test.ts); double-cast(x as unknown as Record<string, unknown>)['key']pattern forCollaborationServiceprivate member access (TS2352); bracket notation['gpu']/['__TAURI__']required by TypeScript 6 index-signature enforcement (TS4111); PBKDF2Uint8Array<ArrayBuffer>generic incollaborationService.tsfor Web Crypto API strict typing. -
Test mocks (2026-05-18):
tests/unit/ErrorBoundary.test.tsxgainsvi.mock('../../hooks/useTranslation', β¦)with an EN string map so rendered-text assertions survive the i18n refactor.tests/unit/AdvancedImportExport.test.tsxheading assertion updated from hardcoded'Google Docs / Notion'to'export.pasteSection.heading'(consistent with thet: (k) => kmock pattern already used in that file). -
E2E Desktop + Mobile Chrome (2026-05-17):
writer,snapshots,a11y,exportspec files migrated to 2026 Golden Hierarchy selectors (getByRole > getByTestId; no CSS, no XPath). Fixes CI exit-code 1 after WriterView component split. -
WebLLM model selector (Phase 3B):
packages/ai-corenow exportsWEBLLM_SUPPORTED_MODELS(4 curated MLC-packaged checkpoints: Llama 3.2 1B, Llama 3.2 3B, Phi-3.5 Mini, Gemma 2 2B),WebLlmModelId, andWebLlmProgressReporttypes.runLocalTextGenerationaccepts an optionalmodelIdandonProgresscallback for per-model download-progress tracking.services/localAiFacade.tsforwards both parameters.types.tsexpands theAiModelunion with the four specific MLC model IDs. Settings β AI (Advanced) now shows a dynamic model dropdown populated fromWEBLLM_SUPPORTED_MODELS, a pre-download button, a WCAG 2.2role="progressbar"progress bar, and auseRefmounted guard that preventssetState-on-unmount. All 5 localesettings.jsonfiles gain the 3 new i18n keys (settings.ai.webllm.model,settings.ai.webllm.downloadProgress,settings.ai.webllm.downloading). -
Cross-project search service (Phase 3A): New
services/crossProjectSearchService.tsβsearchAcrossProjects(query, projectData)fuzzy-searches project title, logline, manuscript sections, and character names/fields usingnormalizeSearch()fromfuzzyScore.ts; returnsCrossProjectSearchResult[]sorted by score. Results includeprojectId,projectTitle,matchType,excerpt(truncated to 120 chars withβ¦), andscore. v1 scope is single-project (multi-project search requires a DB_VERSION bump + IDB migration β deferred to v2).app/transientUiStore.tsgainsisCrossProjectSearchOpen+setCrossProjectSearchOpen. Thelabs-cross-project-searchcommand inservices/commands/commandDefinitions.tsxnow opens the search panel instead of a stub toast. All 5 localecommon.jsonfiles gain 7crossSearch.*keys. -
Collaboration security warning (Phase 3C):
CollaborationPanel.tsxdisplays a pre-connection security-warning banner (role="alert",aria-live="polite", WCAG 2.2 AA) that is only visible before connecting. The banner explains that the public y-webrtc signaling relay can observe connection metadata, includes a keyboard-accessible self-hosting link, and disappears once connected. All 5 localecommon.jsonfiles gaincollab.securityWarning,collab.securityWarningDetail, andcollab.selfHostLinkLabel. -
E2E tests for new features:
tests/e2e/commands.spec.tsβ palette open/close (Ctrl+K / Escape), "dashboard" text search surfaces nav command, Enter-to-navigate, fuzzy "wrt" match.tests/e2e/collaboration.spec.tsβ security warning[role=alert]is visible pre-connection and non-empty. Both specs are CI-only (test.skip(!isCI)).
1.18.1 β 2026-05-27
- TypeScript strict-mode compliance sweep β Zero
tsc --noEmiterrors across all 47 changed files:- ProForge pipeline agents:
AIRequestOptionsrequiresmodel+provider; addedbuildAiOpts()protected helper toBaseAgentthat derives provider/model fromPipelineConfig.aiProviderwith sensible defaults. Applied to all 7 pipeline agents +selfReflect()inBaseAgent. productionAgent.ts:EpubExportOptions.author(required field) β addedauthor: project.author ?? 'Unknown'.services/proForge/pipelineTools/toolRegistry.ts: Wrong module paths ('../../app/store') β'../../../app/store'; same forfeatures/proForge/types.features/proForge/proForgeSlice.ts:exactOptionalPropertyTypesβ optional properties assigned via conditional spread instead of explicitundefined.features/proForge/types.ts: Array index access (PIPELINE_STAGES[idx]) returnsT | undefinedwithnoUncheckedIndexedAccessβ coalesced to?? null.features/versionControl/versionControlSlice.ts: Added stubrestoreSnapshotreducer (cross-slice signal, no self-state mutation).hooks/useProForgeOrchestrator.ts:aiCreativityis on rootSettings, notAdvancedAiSettings.- Voice components (
VoicePrivacyConsentModal,VoicePrivacyStatus): WronguseTranslationimport path;Modalnamed export;setVoiceSettingsaction (notupdateSettings);selectVoiceSettingsselector. - Test fixtures (35+ test files): Corrected for
noUncheckedIndexedAccess([i]!), removed non-existentStorySection.type/orderfields,act: 1 as constfor literal union,AiModel/Theme/MindMapNodeType/StoryObjectTypevalid enum values,PrivacySettingswith all 6 required fields,DeviceHealthReportcorrect shape,FlatHelpArticle.contentKey(notbodyKey),FeatureFlagsState.enableProForgein mock objects.
- ProForge pipeline agents:
1.18.0 β 2026-05-27
- ProForge Humanization & Refinement Sprint (Phases H/A/P/X) β Full editorial-quality overhaul of the ProForge pipeline:
- Phase H β UX Polish: Author-facing stage labels and loading messages (no implementation jargon); RAG chunk count renamed to "context passages"; feature flag descriptions rewritten for non-technical readers; behavioral tests replacing implementation-detail tests.
- Phase A β Architecture:
BaseAgentabstract class eliminates ~200 LOC of duplicated scaffold across all 8 pipeline agents;services/ai/aiConstants.tsconsolidatesCREATIVITY_TO_TEMPERATURE,LOCAL_BACKEND_PRESET_DEFAULT_URL, andORCHESTRATION_READY_PROVIDERSinto a single source;addDebouncedListenerfactory inlistenerMiddleware.tsreplaces repeatedRootStatecast dance. - Phase P β Quality Supervision:
SupervisorAgentβ heuristic quality gates (no AI calls) that detect fallback sentinels and trigger retries viaexecuteStageWithSupervision; hard gate blocks pipeline if intakequalityScore < 30;BaseAgent.selfReflect()β self-evaluation loop flagsINCOHERENToutput inDiagnosticAgentandStructuralAgentfor re-run; allcreateFallback*methods now produce 0 scores +isFallback: trueinstead of fake data;reflectionNotes,supervisorDecision, andmaxRetriesfields added to relevant types. - Phase P-5 β PipelineReviewPanel Redesign: Critical Actions summary card; severity-grouped view (Critical / Warnings / Suggestions); Quick Accept High-Confidence button (confidence β₯ 0.85, non-critical, pending items only).
- Phase X-1 β Settings Nav Grouping: Semantic
NAV_GROUPSwithNavGroupHeadercomponent (Writing, AI Models, Appearance & Accessibility, Privacy & Data, Connections, System). - Phase X-2 β Flow Mode: Distraction-free writing mode via Zustand
transientUiStore(flowMode/setFlowMode);WriterViewUIshows full-screen editor on toggle;Escapekey exits. - Phase X-3 β Empty States:
<EmptyState>components for Characters, World, SceneBoard, and ProForge views β contextual guidance when collections are empty.
- i18n: 2055 keys Γ 5 locales (added
proforge.pipeline.title,proforge.pipeline.noneActive, loading messages, stage labels, empty-state strings across DE/EN/ES/FR/IT). - .gitignore: Added
.continue/(Continue IDE local config directory).
- listenerMiddleware.ts:
getOriginalState()captured synchronously before the firstawaitinsideaddDebouncedListenerfactory (RTK constraint β calling after anyawaitthrows at runtime). - WriterViewUI.test.tsx: Added
vi.mockforuseWriterViewContext(component now requires the context after X-2 Flow Mode integration). - ProForgeDashboard.test.tsx: Assertion updated to use i18n key string (mock
t()returns key; component usest('proforge.pipeline.noneActive')). - writingAndCharacterThunks / outlineAndWorldThunks / plotBoardAiThunks tests (pre-existing): Added
vi.mock('../../../services/ai/aiPolicy', ...)βsettingsReducerdefaultslocalStorageOnly: true, causingassertCloudAiAllowedSyncto throw "Cloud provider blocked" and reject all 31 AI thunk tests at the gate.
1.17.1 β 2026-05-26
- TypeScript errors in ProForge test suite β Fixed 30+ type errors across 15 ProForge test files:
EntityStatemock completeness (ids: []), fullProForgeStateshape (isActive,activeView,defaultConfig,isLoading,error),PipelineStage/ReviewItemType/ReviewItemSeverityunion casts in test helpers,activeStageResult.stage as PipelineStage,creativity as const/ragMode as constfor union literal narrowing, generict<T>(k) => k as unknown as Ttranslation mock,versionControlActions.restoreSnapshotbiome-ignore cast. - Test failures in Coverage Sprint test files β
NotificationsSection.test.tsx:getAllByRole('checkbox')βgetAllByRole('switch')(ToggleSwitchuses ARIArole="switch");Progress.test.tsx:querySelector('div > div')returned outer div (no inline style) βquerySelector('[style]')targets the inner bar div;ManuscriptEditor.test.tsx: word count badge renders"7 common.words"not"7"β fixed to regex\b7\b;AnalyticsBootstrap.test.tsx: missingbeforeEach(vi.clearAllMocks)caused stale call count;ragPromptAssembly.test.ts: token budget50too small for 500-char body (β173 tokens) β corrected to200.
- Dependencies (patch) β
@ai-sdk/google3.0.75β3.0.79,@ai-sdk/openai3.0.64β3.0.65,@ai-sdk/react3.0.187β3.0.193,ai6.0.185β6.0.191,dompurify3.4.5β3.4.6,@tanstack/react-virtual3.13.25β3.13.26,vite8.0.13β8.0.14,vitest+@vitest/coverage-v84.1.6β4.1.7,storybooksuite 10.4.0β10.4.1,@types/node25.9.0β25.9.1,@types/react19.2.14β19.2.15. - Dependencies (minor) β
@google/genai2.4.0β2.6.0,docx9.6.1β9.7.0,vite-plugin-pwa1.2.0β1.3.0,wrangler4.93.1β4.94.0.
1.17.0 β 2026-05-24
- Voice Full Support Foundation β Abstract Engine Interfaces (
SttEngine,TtsEngine,VadEngine,WakeWordEngine,IntentEngine), Web Speech API Fallbacks, Hybrid Intent Engine (exact β Jaccard β slot extraction), VoiceCommandService with State Machine (idle β listening β processing β speaking), ReduxvoiceSlice, React Hooks (useVoice,usePushToTalk,useVoiceDictation,useVoiceAccessibility), UI Components (VoiceIndicator,VoiceControlPanel,VoiceSettingsSection), Audio Navigator (ARIA landmark scanning), Feedback Service (3 verbosity levels). 83 unit tests across 9 test files. - CodeGraph semantic code intelligence β dual-graph setup alongside Graphify: symbol-level MCP server (caller/callee/impact/trace), auto-sync file watcher,
codegraph affectedfor smart test selection. Indexed: 260 files, 2754 nodes, 2443 edges. Seedocs/codegraph.mdanddocs/dual-graph-setup.md. pnpmscripts:codegraph:status,codegraph:update,codegraph:sync,codegraph:report,codegraph:affected,graphs:update.- VS Code: tasks for CodeGraph and dual-graph updates.
- Design System Audit Completion β DS-1 (legacy CSS bridge variable removal), DS-2 (elimination of all
dark:Tailwind prefix violations across components), DS-4 (radius tokens). All P0+P1 Design-System items completed. - Mobile UX Comprehensive Pass β Touch targets β₯44px, bottom tab bar, foldable layout (
useFoldableLayout), sidebar layouts, hover-only actions removed, safe-area padding, bottom navigation clearance (pb-mobile-nav). - DevContainer β Full
.devcontainer/configuration with Dockerfile, Starship prompt, VS Code extensions, and tooling documentation. - LoRA Adapter Inference Foundation β Feature flag
enableLoraSupport, IDB service (loraAdapterService), Settings-UI for personalized writing styles. - Plugin System v0.1 β Sandboxed Capability API, plugin registry (
pluginRegistryService), Settings-UI. - Visual Regression Testing (VRT) β Playwright screenshot suite, CI job
vrt.yml, baseline snapshots. - RTL Layout Foundation β Feature flag
enableRtl, BiDi context provider,html[dir]control for Arabic/Hebrew/Persian support. - Cloud Sync Stub (SYNC-1) β E2E-encrypted cloud sync foundation (AES-256-GCM, Cloudflare R2 stub).
- Performance (PERF-1) β
useDeferredValuefor large manuscripts, 500-scene notice, virtual scrolling foundation. - Community Section (COM-1) β Curated model list, GitHub links in Settings.
docs/REPO-HOUSEKEEPING.mdβ GitHub language stats and i18n layout.- Full RTCDataChannel in-flight E2E encryption β
pnpm patchfory-webrtc@10.3.0encrypts all Yjs sync updates and awareness protocol messages over peer-to-peer WebRTC data channels via AES-256-GCM usingroom.key. Previously only signaling and BroadcastChannel were encrypted; data channel traffic was plaintext.
- i18n cold start: Project title/logline no longer persist as raw keys (
initialProject.title); sync bootstrap + repair on load. - Repo languages:
.gitattributes+ solo Graphify policy (graphify-out/*gitignored exceptGRAPH_REPORT.md); removed stalepublic/locales/*module copies (runtime usesbundle.jsononly). - Voice state migration:
selectVoiceSettingsguarded against missingvoicekey in old persisted state. - Security: pnpm override for CVE
qs>=6.15.2. - Vercel deploy:
pnpm-lock.yamlupdated to includepatchedDependenciesfory-webrtc@10.3.0, resolvingERR_PNPM_LOCKFILE_CONFIG_MISMATCHon frozen install.
1.11.0 β 2026-05-22
- Cloudflare deploy (P0):
scripts/resolve-deploy-base.mjsused undefined variablebase; corrected todeployBase.sync-deploy-base.mjsnow propagates errors withprocess.exit(1)and usesconstcorrectly. - StorageBackend resilience:
saveProject()andsaveSettings()inservices/dbService.tsnow wrap IDB writes inretryDb()(2 retries, 500 ms delay on quota/state errors). Settings auto-save failures surface as an error toast. - Init recovery UI:
index.tsxmounts aStorageErrorScreenReact component on DB init failure (instead of a raw reddiv), offering Reload and Reset Database buttons. - Lint:
scripts/sync-deploy-base.mjslet textβconst text;App.tsxremoved redundantlanguagedependency fromuseEffect.
services/dbInitialization.tsβinitializeStorage()(returns{ success, migrated, error? }) andresetAllDatabases()(deletes both IDB stores + localStorage markers).- Help Center articles: All 13 previously stub articles (< 300 chars) fully written to 700β1000 chars of HTML content across all 5 locales (de/en/es/fr/it). 1931 keys Γ 5 locales verified at parity.
- Tests:
tests/unit/dbInitialization.test.ts(8 tests) +tests/unit/dbServiceRetry.test.ts(7 tests).
1.10.0 β 2026-05-21
- Help articles: Plot Board v2 canvas deep dive, Hybrid RAG guide, Tauri desktop documentation (all 5 locales).
- Tests: Indexed help search, extended
ragPromptAssemblybranches,plotLayoutUtilsgrid snap, branch coverage gate β₯55 %. - Mobile: Bottom tab bar shows Scene Board instead of Characters (desktop sidebar unchanged);
pb-mobile-navclearance for scroll content.
- Help search pre-builds translated index per locale (faster typing, less jank).
- Settings β About includes
TauriUpdaterBanner; updater auto-check only on About (not entire Settings). - Vitest branch threshold raised from 48 % to 55 %.
- Mobile main content no longer hidden behind bottom navigation (safe-area + tab bar padding).
- Help AI chat scrolls on new messages; suggestion chips submit correctly; i18n error message.
- Settings search syncs active category when filter hides current section; empty-search state.
1.9.0 β 2026-05-21
- Lazy loading & cold start: dynamic DuckDB/RAG in
listenerMiddleware, deferredaiApiprovider load, lazy Plot Board sub-components,react-force-graph-2d,CollaborationPanel;AnalyticsBootstrap+useDuckDbwhen flag on. - Help Center overhaul:
helpCatalog.ts(50+ articles), full-text search, Technical Documentation category, expanded AI help RAG chunks; complete es/fr/it article translations. - Settings guide: dedicated category with links to all 18 areas; Experimental flags section with all 12 toggles; overview quick links on General.
- Dashboard:
BackupQuickActionsCard(export/import JSON, latest snapshot, link to Backup settings). - Plot Board polish:
PlotMinimapcomponent, long-press on cards, 44px connection touch targets,prefers-reduced-motionpan path. - Tauri desktop: native File/Help menu β
menu-actionevents,tauri-plugin-window-state, updater banner in About, open data folder, runtime version display. - Resilience:
ViewErrorBoundarywith retry + live-region announce;withTransientRetryon AI provider attempts. - Docs:
docs/SPRINT-V1.9.md; updatedREADME.md,AUDIT.md,docs/TAURI-CI.md,docs/TAURI-UPDATER.md.
- Feature flags UI moved from Advanced AI to Settings β Experimental flags.
- Bundle budget script supports
--max-entry-kb; Viteplot-boardmanual chunk.
1.8.0 β 2026-05-21
- RAG prompt assembly (
services/ragPromptAssembly.ts): Writer continuation, Plot Board beat suggestions, token-budgeted context blocks. - DuckDB semantic vectors:
rag_chunks.embedding(384-dim),ragVectorMigration.ts, dual-write uses MiniLM embeddings. - Writer UI: RAG context toggle and retrieved-chunk badge.
- Plot Board: AI suggest beat + modal (
plotBoardAiThunks,usePlotBoardAi). - Docs:
docs/SPRINT-V1.8.md,docs/PWA-AUDIT.md. - Deployment: Vercel (
vercel.json+build:edge), Cloudflare Pages (wrangler.toml,_redirects,_headers, optional GH workflow), expandeddocs/DEPLOYMENT.md.
- Typecheck:
MindMapListPanelexactOptionalPropertyTypes; DuckDB worker typings (types/duckdb-wasm-worker.d.ts).
1.7.0 β 2026-05-20
DuckDB-WASM Analytics Layer (P0βP3):
workers/duckdbWorker.tsβ off-main-thread DuckDB-WASM (duckdb-eh bundle), OPFS persistence with in-memory fallback. Message protocol mirrors inference.worker.ts (messageId correlation, AbortController, OPFS_FALLBACK event).services/duckdb/duckdbClient.tsβ singleton proxy with AbortSignal support, in-flight cancellation, init retry (3Γ, exponential backoff), OPFS fallback handler.services/duckdb/duckdbSchema.tsβ schema v1:projects,sections,writing_history,writing_sessions,characters,rag_chunks(FLOAT[] vector),cross_project_index,codex_entities,codex_mentions,readability_snapshots. Analytics views:v_daily_progress,v_weekly_progress,v_section_metrics,v_scene_overlap,v_character_cooccurrence.services/duckdb/duckdbAnalytics.tsβqueryDailyProgress,queryWeeklyProgress,queryStreak,querySceneOverlaps,queryRagSimilarity(vialist_dot_product()),queryCharacterCoOccurrence,queryCrossProjectSearch.duckdbDualWrite,duckdbRagWrite,duckdbCrossProjectWrite,duckdbCodexWrite.withDuckDbRetryretry wrapper.services/duckdb/duckdbMigration.tsβ idempotent IDBβDuckDB seed migration with_metaversion marker.hooks/useDuckDb.tsβ initialization hook with 30 s timeout, auto-retry, OPFS fallback dispatch,queryAsync/execAsync.hooks/useAnalytics.tsβ feature-flagged analytics hook; parallelizes 4 queries; OPFS-unavailable toast.- Feature flag
enableDuckDbAnalyticsinfeatureFlagsSlice.
Hybrid RAG β Wired End-to-End:
ragMode: 'lexical' | 'hybrid'added toAdvancedAiSettings(default'hybrid'). Persisted to IDB viadbService.tsmigration defaults.- Settings UI: RAG mode selector (Hybrid / Lexical) in Advanced AI β Local Search Index section.
- Fix: Settings "Rebuild local search index" button now calls
rebuildHybridRagIndex(wasrebuildLocalRagIndexβ lexical only). DuckDB dual-write enabled whenenableDuckDbAnalyticsflag is on. - Consistency Checker (
useConsistencyCheckerView) now retrieves top-8 RAG chunks before the AI call and injects them into the prompt asragChunks, replacing the full 50 000-char manuscript block. Graceful fallback when RAG index is empty or embedding model unavailable. - Re-Index for AI button added to
ReferencePanelViewfooter β on-demand index refresh with success toast showing chunk count. - i18n: 3 new
settings.advancedAi.ragMode*keys + 5reference.reindex.*keys across all 5 locales.
AI Provider Extensions:
- ONNX Runtime Web (Layer 2) and Transformers.js (Layer 3) selectable as primary providers in AI Settings.
- Service-level dedup wrapper (
aiThunkUtils.ts) prevents concurrent duplicate AI requests. - Per-project AI preset: project-scoped provider/model stored in
advancedAi.localBackendPreset; harden dedup key; hash-based deep links (#/board,#/preview,#/progress,#/project/{id}/scene/{id}). WorkerBusbackpressure guard:MAX_QUEUE_SIZE= 32; critical tasks bypass; telemetry extended (peakLatencyMs,errorRate).
Collaboration:
- Y-WebRTC E2E encryption (
collaborationService.ts):encryptUpdate(),decryptUpdate(),deriveEncryptionKey()(PBKDF2 600 000 iter, SHA-256, AES-256-GCM). Deterministic salt from projectId.CollaborationPanelstatus badge (greenE2E Key Derived/ amberRoom isolation only).
Performance:
PlotCanvas.tsx: pointer-move handler throttled viarequestAnimationFrame; prevents 60 Hz Redux dispatch storm during canvas pan/zoom.
localRagService.tsauto-rebuild now correctly dual-writes to DuckDB whenenableDuckDbAnalyticsis on (5 s debounce inlistenerMiddleware).geminiService.tsconsistencyCheckcase: accepts optionalragChunksparam; uses RAG excerpts instead of full manuscript when present.- i18n: 1 590 β 1 625 keys Γ 5 locales.
dbService.tsmigration defaults:advancedAi.ragModeadded so existing IDB state without the key is upgraded to'hybrid'on first load.DuckDBresilience: init retry (3Γ), dual-write retry (3Γ), OPFS fallback to in-memory whennavigator.storage.getDirectory()unavailable, error surfaces to ReduxanalyticsActions.setDuckDbError.
1.6.2 β 2026-05-20
- Plot-Board content moved to projectSlice (undo-able): Connections, subplots, and tension overrides now live in
features/project/projectSlice.ts(and thus insideredux-undo) instead ofplotBoardSlice.plotBoardSliceis now viewport/UI-state only (zoom, pan, mode, draw-state). New actions:addPlotConnection,updatePlotConnection,removePlotConnection,removePlotConnectionsForSection,finishPlotDrawConnection,addPlotSubplot,updatePlotSubplot,deletePlotSubplot,assignSectionToPlotSubplot,removeSectionFromPlotSubplot,setPlotTensionOverride,clearPlotTensionOverride,clearAllPlotTensionOverrides. New selectors inprojectSelectors.ts:selectPlotConnections,selectPlotSubplots,selectPlotTensionOverrides. All five scene-board components (ConnectionLayer,ConnectionToolbar,PlotCanvas,SubplotPanel,TensionCurvePanel) updated to dispatch project actions and read from project selectors.handleDeleteSectionnow also dispatchesremovePlotConnectionsForSectionto keep the board consistent.
- Locale-aware readability metric:
services/readabilityFlesch.tsnow supports five language-specific formulas β EN: Flesch Reading Ease, DE: Amstad (1978), FR: Kandel-Moles, ES: FernΓ‘ndez Huerta, IT: Gulpease β instead of a single English-centric heuristic.estimateSyllables(word, locale)uses per-locale vowel patterns (including diacritics).useDashboard.tspasses the active locale tocomputeReadabilitySnapshot. Dashboard i18n labels updated in all four non-English locales to name the correct formula.
- docker.yml token permissions (CodeQL): Top-level
permissionsblock now sets onlycontents: read;packages: writescoped to thebuild-pushjob only β follows principle of least privilege as required by CodeQL Token-Permissions rule. - biome.json schema version: Updated
2.4.12β2.4.15to match installed Biome CLI.
tests/unit/plotBoardSlice.test.ts: Subplot/connection/tension test suites removed (migrated toprojectSlice); viewport tests retained.tests/unit/projectSlice.test.ts: 15 new tests covering plot connections (incl. undo, dedup, self-loop guard), subplots (CRUD, section-assign), and tension overrides.tests/unit/hooks/useSceneBoardView.test.ts:handleDeleteSectiontest extended to verifyremovePlotConnectionsForSectiondispatch;plotBoardmock state simplified (no connections/subplots/tensionOverrides).tests/unit/SceneBoardView.test.tsx,SubplotPanel.test.tsx,ConnectionLayer.test.tsx,TensionCurvePanel.test.tsx: mock state updated to new plotBoard shape.tests/unit/thunkUtils.test.ts: default model assertion updatedgemini-2.5-flash β gemini-3.5-flash(settingsSlice default changed in v1.6.1).- Total: 2 024 tests / 178 files β 0 failures. Coverage: 65.91% lines / 50.59% branches / 56.74% functions / 64.25% statements.
1.6.1 β 2026-05-19
- Gemini 3.x model catalogue: Default model bumped to
gemini-3.5-flash; Gemini 3.1 Pro Preview, 3.1 Flash, and 3.1 Flash-Lite added to the provider dropdown. Legacygemini-2.0-flashremoved;gemini-2.5-xstable group retained. All fallback model IDs updated acrossgeminiService,storyCraftCompletionFetch,dbServicemigration,AiSections, andsettingsSlice. - Docker image: Multi-stage
Dockerfile(builder β nginx:1.27-alpine runner);.dockerignore;docker.ymlGitHub Actions workflow (GHCR push onv*tags /workflow_dispatch). - Tauri v1.6:
tauri.conf.json+Cargo.tomlversion bumped1.4.0 β 1.6.0; auto-updater setactive: true;TAURI-CI.mdexample tag updated tov1.6.0.
1.6.0 β 2026-05-19
Plot-Board v2 (Days 1β3 β the Killer Feature):
- Free-form canvas mode: Scene cards positioned absolutely on a pannable/zoomable CSS-transform canvas; tap/drag updates
sceneBoardLayoutin Redux. Pan: pointer-capture on background; zoom: wheel + two-pointer pinch (0.25Γβ4Γ). Mini-map: 80Γ50 px fixed SVG overview in corner. - SVG Connection Layer: Cubic-bezier paths between scene cards rendered in
ConnectionLayer.tsx; connection types:cause-effect,parallel,subplot,temporal,character-arc. Invisible 18 px thick hit-test<path>(pointer-events: stroke) withrole="button"+tabIndexfor keyboard access. - Subplot System:
SubplotPanel.tsxβ collapsible sidebar with color-swatch list, inline name edit,<input type="color">picker, filter toggle that dims unrelated scenes. - Connection Toolbar: Floating
ConnectionToolbar.tsxappearing when a connection is selected β type select, label input, delete. - Tension Curve Panel:
TensionCurvePanel.tsxβ 800Γ200 SVG chart with auto-computed tension (status-based score 0β10) and user drag-overrides. Beat sheet overlays: Three-Act, Save the Cat!, Hero's Journey marker presets. Collapsible below the canvas. - Mode Tab Bar: Swimlane | Canvas | Timeline three-segment control in
SceneBoardView.tsxtoolbar dispatchesplotBoardActions.setActiveMode. - Feature flag:
enablePlotBoardV2: boolean(defaulttrue) infeatureFlagsSlice.ts. - Snap-to-grid option (8 px) for canvas drag in
PlotCanvas.tsx. - New Redux slice
features/plotBoard/plotBoardSlice.ts: Manages canvas viewport (zoom, pan), connections, subplots, tension overrides, draw-mode state. Persists tolocalStoragekeystorycraft-plot-board. NOT wrapped byredux-undo. - New service
services/plotBoardService.ts:computeTensionCurve(),autoLayoutScenes(),exportBoardAsSvg(). - Architecture doc
docs/PLOT-BOARD.md: Connection types, beat sheet reference, canvas gesture guide. - Mobile canvas gestures: Pinch-to-zoom, two-finger pan, long-press background β add scene at pointer position.
Real-Time Book Preview (Day 4):
components/BookPreviewView.tsx+hooks/useBookPreviewView.ts+contexts/BookPreviewContext.ts: Scrollable book-style rendering of all manuscript sections as<article>elements. IntersectionObserver (threshold 0.3) drives an active TOC entry.- Controls bar: Font size (12β24 px), font family (system-ui / serif / monospace), word-count annotation toggle, fullscreen mode (
position: fixed inset-0 z-50). - Collapsible TOC sidebar: Fixed-position; keyboard scroll via
scrollIntoView; active section highlighted. - Registered as lazy-loaded view in
App.tsx(case 'preview') andAPP_SECTIONSinconstants/sections.tsx.
Reference Panel / Split-View (Day 5):
components/manuscript/ReferencePanelView.tsx: 6-tab panel (Characters | World | Notes | Binder | Comments | Revisions) withrole="complementary"+aria-label. Tab buttons userole="tablist"/role="tab"/aria-selected/aria-controls.- Characters tab: Mini-cards for scene's
characterIds[]with avatar placeholder and backstory excerpt. - World tab: Linked location mini-description and geography excerpt.
- Notes tab: Inline editable
<textarea>synced tocurrentSection.notesviaupdateManuscriptSection. - Binder tab: BinderNode links for current section.
- Comments & Revisions tabs: Integrate
CommentsPanelandSceneRevisionPanel(see Day 6).
Per-Scene Revision History + Threaded Comments (Day 6):
services/sceneRevisionService.ts: IndexedDBscene-revisionsstore;saveRevision(),listRevisions()(newest-first, max 50 per scene),deleteRevision()._resetDbForTest()exported for test isolation.components/manuscript/SceneRevisionPanel.tsx: Word-level diff view usingservices/wordDiff.ts; two-step restore (confirm button); labeled snapshot save.features/sceneComments/sceneCommentsSlice.ts: EntityAdapter forSceneCommentwith selectorsselectCommentsBySection,selectUnresolvedCount,selectUnresolvedCountBySection. Actions:addComment,resolveComment,unresolveComment,addReply,deleteComment,deleteCommentsForSection.components/manuscript/CommentsPanel.tsx: Thread expand/collapse, inline reply input (Enter to send), resolve/unresolve/delete buttons withrole="list"/role="listitem"ARIA semantics.- New types in
types.ts:SceneRevision,SceneComment,CommentReply.
Progress Tracker Dashboard (Day 7):
features/progressTracker/progressTrackerSlice.ts:startSession,endSession(calculateswordsWritten = current - start, prevents negative delta),setDailyGoal(clamps β₯ 1),setWeeklyGoal,syncStreak. Exported pure functioncomputeStreak(history).components/ProgressTrackerView.tsx+hooks/useProgressTrackerView.ts+contexts/ProgressTrackerContext.ts: 2-column dashboard (single-column mobile): circular SVG progress ring, live session timer (role="timer"), daily/weekly goal bars, 30-day SVG area velocity chart with gradient fill, 12-week GitHub-style heatmap (84<rect>cells, 5 intensity shades).- Registered as lazy-loaded view (
case 'progress') inApp.tsxandAPP_SECTIONS. - Session shortcut
Ctrl+Shift+Sto start/stop writing sessions.
Mobile Polish (Days 8β9):
hooks/useFoldableLayout.ts: Readsenv(fold-top)/env(fold-left)CSS environment variables (W3C Device Posture API). Returns{ isFolded, foldAxis: 'horizontal'|'vertical'|null, foldPosition }. Applied inApp.tsxasdata-fold-axison<body>.services/deepLinkService.ts: URL hash routing (#/project/{id},#/project/{id}/scene/{sectionId},#/board,#/preview,#/progress).parseHash(),pushHash(),readCurrentView().hooks/useHaptics.tsupgraded: NamedHAPTIC_PATTERNSlibrary βscene-drop,connection-made,streak-milestone,session-start,goal-achieved,error.HapticPatterntype exported.
hooks/useSceneBoardView.ts: Extended withhandleAddConnection,handleDeleteConnection,handleStartDrawConnection,handleFinishDrawConnection,handleCancelDrawConnection,handleAddSubplot,handleDeleteSubplot,handleAssignToSubplot.contexts/SceneBoardViewContext.ts: Extended with new connection/subplot handlers.components/SceneBoardView.tsx: Refactored to orchestratePlotCanvas,ConnectionLayer,SubplotPanel,TensionCurvePanel,ConnectionToolbar; mode tab bar wired toplotBoardActions.setActiveMode.components/scene-board/subcomponents extracted:SceneCard.tsx,ActSwimlane.tsx(previously inline inSceneBoardView.tsx).- i18n: 131 new keys (preview 21 + progress 25 + reference 11 + comments 13 + revisions 13 + plotboard 20 + mobile 6 + haptics 2 = 131) β 1590 keys Γ 5 locales.
app/store.ts: RegisteredplotBoard,progressTracker,sceneCommentsreducers.types.ts: AddedSubplot,PlotConnection,SceneRevision,SceneComment,CommentReplyinterfaces.workers/inference.worker.ts: Added@ts-expect-errorfor@xenova/transformersdynamic import (lives inpackages/ai-core; Vite resolves at build time β pre-existing resolution gap intsc).
- 174 test files / 1966 tests (up from 166/1851) β 0 failures.
- New:
plotBoardSlice.test.ts,plotBoardService.test.ts,ConnectionLayer.test.tsx,SubplotPanel.test.tsx,TensionCurvePanel.test.tsx,sceneRevisionService.test.ts,sceneCommentsSlice.test.ts,progressTrackerSlice.test.ts. - Fixed:
useSceneBoardView.test.tsmock state extended withplotBoardshape;ConnectionLayer.test.tsxupdated to usedata-testid="connection-group"(biome correctly removed redundantrole="img"from<g>insiderole="img"SVG).
1.5.0 β 2026-05-18
- WorkerBus v2: Backpressure cap (32-task queue), priority preemption (max 3Γ requeue), AbortController map,
cancel(taskId), extended telemetry (peakLatencyMs,errorRate,lastSuccessAt). - GpuResourceManager: Mutex for WebLLM/ONNX-WebGPU consumers; priority queue; 30s auto-release deadlock prevention.
- DeviceHealthService: Full device report (CPU cores, memory heap, storage quota, battery level, GPU VRAM tier, device class).
getModelRecommendation()maps tier Γ task to concrete model IDs. - EcoModeService: Battery API integration; explicit override API;
applyAdaptiveMode(). - InferenceProgressEmitter: Pub/sub progress snapshots for WebLLM loading;
subscribeWebLlmLoading(),reportWebLlmProgress(),reportWebLlmReady(),reportWebLlmError(),reset(). - Active ONNX + Transformers.js inference:
inference.worker.tsβ WorkerBus protocol, pipeline cache (8 entries), trusted-message origin guard, AbortController integration. Layers 2 & 3 now perform real inference instead of returning echo strings. - AiInferenceCacheService: Two-layer LRU β in-memory 64 entries (DJB2+FNV hash) + IndexedDB 256 entries (7-day TTL). Skips cache for prompts > 512 chars.
- LocalEmbeddingService:
Xenova/all-MiniLM-L6-v2384-dim embeddings; L2-normalised; worker-offloaded; micro-batch (8);embedText(),embedBatch(),cosineSimilarity(). - LocalNlpService: Sentiment analysis (distilbert), summarisation (distilbart), keyword topic classification β all worker-offloaded via WorkerBus.
- LocalAiDownloadProgress: WCAG 2.2 AA modal (
role="progressbar",aria-valuenow,aria-valuetextwith ETA,aria-live="polite"/"assertive",role="dialog", focus on open, cancel button). - GpuMetricsPanel: GPU queue state, WorkerBus telemetry, device-class badge, eco-mode toggle (
role="switch"). Feature-gated byenableAppHealthPanel. - Model recommendations engine:
getModelRecommendationForTask(task, report, ecoMode)β VRAM tier Γ task β concrete model IDs.getProviderSpeedEstimate()for Ollama ping. - Hybrid RAG service (
localRagService.ts): Token-based chunking (300 tokens, 50-token overlap), MAX_CHUNKS 500,indexedAtrecency field.retrieveContext()with'lexical'|'semantic'|'hybrid'modes; hybrid = 60% semantic + 30% token overlap + 10% recency; sliding window 3 most-recent chunks always included. - Cross-Project AI enrichment:
enrichProjectIndex()generatesaiSummary(100 chars) +embeddingVectorfrom local model.semanticSearchProjects()uses cosine similarity with keyword fallback. - Mobile: PointerEvent resize handles:
ManuscriptView.tsxupgraded fromMouseEventtoPointerEventwithsetPointerCapture()/releasePointerCapture().touch-action: noneon drag handles. - useSwipeGesture: PointerEvent swipe detection; threshold + velocity window; dominant-axis direction. Wired to
WriterViewUImobile panel switching. - useLongPress: PointerEvent long-press; 10px movement cancel threshold; configurable ms duration.
- useHaptics:
navigator.vibrate()wrapper with graceful degradation. - BottomSheet: WCAG 2.2 compliant drawer;
role="dialog",aria-modal, focus trap (querySelectorAll-based), Escape to close, drag-to-dismiss (> 30% height),touch-action: none. - PromptLibrary: 17 original prompts + 3 new (styleTransfer, plotHoleFix, chapterAutoGeneration) in a versioned, category-organised registry.
getPrompt(id, vars),listByCategory(),exportPromptLibrary(),importPromptLibrary()with JSON validation. A/B variant selection. - StyleTransfer prompt:
geminiServicecase'styleTransfer'β author voice mimicry withauthorStyleexemplar. Returns JSON{ transformed, voiceNotes }. - PlotHoleFix prompt:
geminiServicecase'plotHoleFix'β extends detection with chainable fix generation. 2048-token thinking budget. - ChapterAutoGeneration prompt:
geminiServicecase'chapterAutoGeneration'β outline section β full chapter. 8192-token extended thinking budget. - PluginRegistry:
PluginDescriptorinterface;register(),unregister(),getByType(),list(),size,clear(). SingletonpluginRegistryexport. - UsageAnalyticsService: Opt-in only (default: off). Ring buffer 500 events.
track(),getAnonymizedSummary(),flush(). No PII β event type + timestamp + device class only. - Updated AI model catalogue (2025 releases): WebLLM list now includes Qwen 2.5 0.5B, Phi-4 Mini 3.8B, Gemma 3 1B/4B, Llama 3.3 70B. ONNX default model updated to SmolLM2-135M-Instruct (replaces DistilGPT-2). OpenAI model list adds GPT-4.1, o3, o4-mini. aiProviderService validates
o\dprefixes alongsidegpt-. - DeepWiki badge added to README header.
- i18n: 20 new keys for download progress + GPU panel (1459 total across 5 locales).
services/ai/index.tsnow re-exports all Day 4 service functions.crossProjectIndexService.ts:ProjectSearchIndexgains optionalaiSummaryandembeddingVectorfields.
1.4.0 β 2026-05-12
-
Command Center: Central
services/commands/registry consumed bycomponents/CommandPalette.tsxβ fuzzy search with highlights, sections, recent/pinned commands (persisted), optional on-device AI-suggested rows, voice query unchanged;CommandExecutorProvider(contexts/CommandExecutorContext.tsx) +runCommandByIdfor Help βTry itβ and toastcommandIdactions. -
Global shortcuts:
hooks/useGlobalKeyboardShortcuts.ts,services/keyboard/(matching + conflict hints), expanded defaults infeatures/settings/keyboardShortcutsDefaults.ts, Settings β Shortcuts (components/settings/ShortcutsSection.tsx); palette visibility viaapp/transientUiStore.ts. -
Settings hub: Top-of-view search over registered control hints (
services/settingsSearchHints.ts); settings JSON import/export (Zod, non-sensitive subset) in Data (services/settingsExchange.ts). -
Help: RAG-lite static retrieval (
services/help/helpDocRetrieval.ts) injected intostreamAiHelpResponse; locale articles supporttryActionId;spotlightTouracceptstourId(e.g. navigation preset). -
UI / polish:
components/ui/Tooltip.tsx,EmptyState.tsx; manuscript empty state; ErrorBoundary βReport issueβ GitHub link; dashboard Project Health card behindenableProjectHealthScore;enableCrossProjectSearchstub for future cross-project search. -
CI hardening: Composite setup action (
.github/actions/setup/action.yml) centralises Node + pnpm bootstrap across all 8 jobs β eliminates 4-step duplication and guarantees--frozen-lockfileon every runner.gitleakssecrets scan added to thesecurityjob. SLSA build provenance attestation (actions/attest-build-provenance@v2) attached to everymainbuild. OpenSSF Scorecard (scorecard.yml) runs weekly and onmainpush β SARIF uploaded to GitHub Code Scanning. Dependabot configured for npm (weekly, dev-tooling PRs grouped) and GitHub Actions (weekly, max 5 open PRs). -
Lighthouse accessibility gate:
categories:accessibilityassertion promoted fromwarntoerroratminScore: 0.88in.lighthouserc.cjsβ WCAG 2.2 enforcement now blocks CI rather than just warning. -
pnpm strict config:
.npmrcgainsstrict-peer-dependencies,engine-strict(Node β₯ 22),prefer-frozen-lockfile,verify-store-integrity.pnpm-workspace.yamlcorrected fromallowBuilds(map, silently ignored by pnpm v10) toonlyBuiltDependencies(list β the actual v10 field);@google/genaiandsharpadded. -
GitHub Actions SHA pinning: All actions across
ci.yml,tauri-build.yml,scorecard.yml, and the composite setup action now reference immutable commit SHAs (with# vNversion comments) β eliminates tag-mutable supply-chain attack surface. Action versions also bumped:actions/checkoutv5βv6,actions/configure-pagesv5βv6,actions/download-artifactv6βv8,actions/dependency-review-actionv4βv5,codecov/codecov-actionv5βv6. -
CodeQL SAST:
.github/workflows/codeql.ymladded β JavaScript/TypeScript static analysis runs on every push tomain, every PR, and weekly. Results uploaded to GitHub Code Scanning. -
Branch protection:
mainbranch protected β 1 required approving review, stale reviews dismissed, required conversation resolution, required status checks (security,qualityΓ2,build), force-push and deletion blocked. -
Hybrid-AI settings: Local backend presets (Ollama/LM Studio/vLLM/custom URLs), optional OpenAI-compatible base URL + OpenRouter-style attribution headers, configurable fallback chain for legacy AI thunks; desktop local port scan for
/v1/models; model recommendation hints for Ollama. -
Gold-Standard author pipeline (offline-first): Binder blob storage + import/GC; manuscript research split; compile profile / norm-page TXT / EPUB matter; optional Tauri Pandoc EPUB (
pandoc_markdown_to_epub) with JS fallback; VC snapshot word-level diff (bounded rows); scene timeline UI + rule engine (capped hints); dashboard readability sampling + timeline summaries (bounded text samples); optional LanguageTool (user URL + privacy gate); local RAG index rebuild βsaveRagVectors; WebGPU tab leader election for WebLLM; settings local RAG rebuild control.
- Performance: Manuscript metrics sampling (
services/manuscriptMetricsSampling.ts), diff/word-diff caps, scene timeline DOM caps, RAG rebuild yields between sections β tuned for low-end hardware.
- Characters: "Add Manually" opens the dossier immediately again (dispatch + local selection state).
- Playwright (CI): Gemini route mock returns
candidates[].content.parts[].textfor@google/genai; import E2E follows Import Project β modal β Import; VC snapshot assertions avoid[aria-label*="snapshot"]matching the "Create new snapshot" button. - Playwright (CI):
seedGeminiApiKeybefore outline generation (otherwiseNO_API_KEYblocks HTTP mocks); Writer textareadata-testid="writer-studio-editor"; export flow returns to Outline after saving key; character rename assertion uses{ exact: true }so "Braxton Hale Jr." does not satisfy "Braxton Hale". - Playwright (CI):
flushWriterDebounceafter Writer fills (750ms DebouncedTextarea β Redux); snapshot restore re-selects manuscript section; import success uses exact toast copy (strict-mode vs markdown preview); delete assertion targets character card button counts. - Playwright (CI): Removed flaky visual baseline
export-preview.pngfrom export E2E (text assertion retained); import persistence waits for debounced IndexedDB save, pre-checks Dashboard title, then reload +#projectTitle; Settings API key step skips fill when key already configured afterseedGeminiApiKey; export flow opens Appearance before Dark|Dunkel (theme controls not mounted on AI tab). - Playwright (CI): E2E helpers use
#writer-section-select(avoids wrong combobox); native<option>assertions replaced with count/selectOption; snapshot panel usesgetByRole('heading')so/Snapshots/idoes not match empty-state copy; export flow navigates via AI Writing Studio label. aiProviderServicetest: Pre-existing test "throws for anthropic provider" asserted a stale'placeholder response'string. Replaced with "falls back to local AI" β mockslocalAiFacade.generateLocalTextviavi.spyOnand asserts the correct fallback text, testing the real behavior rather than an obsolete error message.
- Corpus sync (2026-05-10):
AUDIT.mdcurated markdown inventory updated to 19 entries (docs/BEST-PRACTICES.md,docs/Design-System.md);README.mdDocumentation Hub adds Design-System row;docs/CI.mddocumentstests/e2e/a11y.spec.ts+ Lighthouse accessibility assertion;CONTRIBUTING.mdAccessibility section aligned with WCAG 2.2-oriented architecture;.cursor/index.mdclinks Barrierefreiheit paths;docs/BEST-PRACTICES.mdcross-linksdocs/ACCESSIBILITY.md;.github/copilot-instructions.mdlocale module count + A11y doc pointer. - README / CLAUDE / CONTRIBUTING /
.cursor/index.mdc/.github/copilot-instructions.md/docs/Design-System.md/ AUDIT: Documented Command Center stack (registry, palette, executor context, transient store, keyboard layer), Settings search + JSON exchange, Help RAG-lite +tryActionId+ tours, Tooltip/EmptyState/toast command actions, and feature flagsenableProjectHealthScore/enableCrossProjectSearch. docs/DEPLOYMENT.md+ rootvercel.json: GitHub Pages and Vercel documented as equal static-SPA paths; privacy note for API keys (client-side only).- README / AUDIT / CLAUDE / copilot-instructions: Hybrid-AI architecture; i18n runtime bundles (
public/locales/*/bundle.json) must stay in sync viapnpm run i18n:bundle/i18n:check/predevβ fixes missing-translation key placeholders in the UI after editinglocales/**/*.json. - README / AUDIT: CI vs local validation (typecheck, lint, i18n; defer heavy E2E to cloud CI); Gold-Standard audit section dated 2026-05-10.
- Complete curated markdown pass (16
.mdsources incl.docs/DEPLOYMENT.md): explicit inventory and cross-links inAUDIT.md; README Documentation Hub includes deployment guide and.github/ACTIONS-OPTIMIZATIONS.md;docs/CI.mdrelated-files table links the historical Actions doc;.github/copilot-instructions.mdi18n bundle wording updated. References throughout: Playwrighttests/e2e/helpers.ts(nonetworkidleunder Vite), Version Control overlay / Escape, memoizedselectCurrentBranchSnapshots. Generated paths (tests/e2e/html-report/,.stryker-tmp/) remain non-doc.
1.3.0 β 2026-05-08
- Legacy IndexedDB migration: Idempotent copy from monolithic
storycraft-dbinto dualstorycraft-state-db/storycraft-data-db(services/dbMigration.ts, Vitest +fake-indexeddbintests/unit/dbMigration.test.ts). - Codex & Story Bible: Feature flags
enableCodexAutoTracking/enableStoryBibleAdvanced; advanced Codex extracts co-occurrence edges + consistency hints; Consistency Checker shows Story Bible panel when Codex data exists. - Scene visualization: Manuscript inspector button generates a scene image via Gemini (
sceneVisualizationprompt) and storesscene-{sectionId}in image storage. - Local AI core: Expanded
sanitizeForPrompt(truncation + jailbreak-like filters); optional@mlc-ai/web-llm/@xenova/transformersdynamic imports in@domain/ai-core. - Quality: Stryker config (
stryker.conf.json), Playwright axe smoke test (tests/e2e/a11y.spec.ts), visual regression enabled (tests/e2e/visual-regression.spec.ts), Modal unit test (tests/unit/Modal.test.tsx). - Lint:
pnpm run lintuses Biome--error-on-warnings.
- Redux listener middleware:
getOriginalState()is read before debounce delays in project/settings auto-save listeners (RTK requirement), eliminatinggetOriginalState can only be called synchronouslyerrors during async effects. - IndexedDB Story Codex:
CODEX_STOREuses inlinekeyPath: 'projectId'βsaveStoryCodexno longer passes an explicit key toput(); large payloads wrap{ projectId, compressedUtf16 }for LZ-compressed strings;getStoryCodexunwraps accordingly. - Vitest IDB mock: fake
objectStore.putderives the map key fromvalue.projectIdwhen the explicit key argument is omitted (matches real IndexedDB inline-key behavior). - Playwright: CI runs Chromium-only projects;
snapshotPathTemplateshares one baseline across OSes; visual regression uses stable load + screenshot timeout. - Stryker:
thresholds.breakset tonulluntil mutation kill-rate on targeted files improves (report still generated; CI mutation job remainscontinue-on-error).
- Dependencies: Added
@axe-core/playwright,@stryker-mutator/*; refreshed@google/genaiwhere applicable. - Documentation: README install/PWA/desktop CTA; AUDIT migration + accessibility notes.
1.2.0 β 2026-05-02
- Spotlight onboarding tour:
driver.js+services/spotlightTour.tsβ guided steps (nav, header / optional command palette, Settings); completion stored locally; entry points on Dashboard and Help. - Five UI locales: French, Spanish, and Italian enabled alongside German and English (Settings, Welcome Portal, Command Palette); FR/ES/IT copy brought to parity with EN keys (native sidebar/portal/tour/settings strings where applicable).
- i18n CI gate:
pnpm run i18n:check(scripts/check-i18n-keys.mjs) enforces identical translation keys acrossen/de/fr/es/it; runs in the quality job. Optional--fixfills missing keys from English. - Dashboard onboarding: Dismissible βQuick tipsβ banner (sidebar, AI settings, auto-save / snapshots) stored per device via
localStorage. - Tauri workflow:
.github/workflows/tauri-build.ymlbuilds desktop bundles onworkflow_dispatchandv*tags (Ubuntu/Windows/macOS artifacts); onv*tags, installers are attached to the matching GitHub Release. Documented indocs/TAURI-CI.md. - Welcome portal: Localized demo project (outline + first chapter) loadable as in-app import; first-visit hint and CTA.
hasSavedDatanow usesstorageServiceso the welcome flow matches the active backend (browser IndexedDB or Tauri FS). - Storage contract module:
StorageBackend+SaveProjectInput(flatStoryProjector Redux{ data }/{ present }envelope) inservices/storageBackend.ts; Tauri FS unwraps to flat JSON on disk.
- Codex extraction:
escapeRegExpLiteral()wraps nativeRegExp.escapewhen present and falls back for runtimes without it (restores Vitest/jsdom compatibility forextractStoryCodex). - AI providers:
generateTextandstreamTextnow merge a standaloneAbortSignalintoAIRequestOptionsfor OpenAI and Ollama, matching cancellation behavior already relied upon for Gemini (services/aiProviderService.ts; tests intests/unit/aiProviderService.test.ts).
- Lint / DX:
pnpm run lintis warning-free β driver.js spotlight popover uses higher CSS specificity instead of!important; template literals inscripts/check-i18n-keys.mjsandtests/unit/ollamaService.test.ts;biome.jsonoverrides turn offnoConsoleforscripts/**/*.mjs,services/logger.ts, andtests/**. Release version 1.2.0 aligned inpackage.json,src-tauri/tauri.conf.json, andsrc-tauri/Cargo.toml. CONTRIBUTING adds Windows Corepack/pnpm and Graphify setup;docs/graphify.mdtroubleshooting notes Windows PATH. - Documentation:
README.md/CONTRIBUTING.md/CLAUDE.mdβ five UI locales, spotlight tour, Tauri β GitHub Releases on tags;docs/CI.md+docs/TAURI-CI.mdaligned. Earlier: CI job ids,.lighthouserc.cjs, Node 22;.github/ACTIONS-OPTIMIZATIONS.mddisclaimer;AUDIT.mdfollow-up 2026-05-02.
- StorageManager:
saveProjectacceptsStoryProject(notunknown). - projectSlice Decomposition: Split monolithic
projectSlice.ts(777 β 248 lines) by extracting all 14 AI thunks into per-domain files underfeatures/project/thunks/:characterThunks.ts,worldThunks.ts,outlineThunks.ts,writingThunks.ts,projectManagementThunks.ts. Shared lazy service loaders +buildAiOptionsextracted tothunks/thunkUtils.ts; entity adapters toadapters.tsto break circular deps.projectSlicere-exports everything for backward compatibility.
- Tauri fileSystemService Parity (5 of 6 gaps closed): Added retry logic (
retryFs()with 2 retries + 500 ms backoff), LZ-String compression matching dbService algorithm (10 KB threshold,\x00lz1\x00prefix), numeric snapshot IDs with metadata envelope,deleteImage(),hasSavedData(), and auto-snapshot every 5 min (max 20, FIFO pruning) tofileSystemService.ts. - Tauri Story Codex + RAG Parity (Gap 3): Implemented file-per-project storage for Story Codex (
projects/{id}/codex/codex.snap) and RAG vectors (projects/{id}/codex/vectors.snap) in the Tauri FS backend. ExtendedStorageBackendinterface andStorageManagerproxy with 6 new methods.codexServiceanduseConsistencyCheckerViewnow route throughstorageServiceinstead of callingdbServicedirectly.
- Expanded unit test suite from ~80 to ~160+ tests across 12 new test files:
aiUtils(20 tests),projectSelectors(15 tests),logger(6 tests),communityTemplateService(6 tests),thunkUtils(2 tests),aiThunkUtils(4 tests),ollamaService(12 tests),aiProviderService(17 tests),storageService(11 tests),useApp(9 tests),usePWA(9 tests),useSpeechRecognition(6 tests). ExtendedwriterSlice(+8),featureFlagsSlice(+2),projectSlice(+5),dbService(+3). - Node 24 localStorage Polyfill: Added in-memory
localStoragemock intests/setup.tsfor full CI compatibility across Node LTS and current (Node 24) versions. Node 24 exposes a nativelocalStoragewithout.clear(); the polyfill activates only when.clearis absent. - Vitest Config Hardening: Added
testTimeout: 30000,maxWorkers: 1(RAM-constrained environments), lowered coverage thresholds to 15%/10% for honest baselines. JUnit reporter output toreports/junit.xml.
- TypeScript 6.0 Adoption: Enabled
stableTypeOrderingcompiler flag intsconfig.jsonto ensure consistent type union ordering between TS 6.0 and the upcoming TS 7.0 Go-native compiler. - Native RegExp.escape(): Replaced custom
escapeRegExp()helper inservices/codexService.tswith nativeRegExp.escape()from ES2025 (available in TS 6.0 without polyfill).
- SettingsView Decomposition: Split 2112-LOC monolith
components/SettingsView.tsxinto 8 focused section files undercomponents/settings/(SettingsShared, AiProviderCard, SettingsModals, GeneralSections, EditorSections, AiSections, SystemSections, DataSection). Main component reduced to ~234 LOC. - Constants Split: Split 506-LOC
constants.tsxintoconstants/icons.tsx(SVG paths),constants/defaults.ts(STORY_TEMPLATES), andconstants/index.ts(barrel). All 18 existing imports resolve via barrel. - Listener Separation: Split combined auto-save listener in
listenerMiddleware.tsinto separate project and settings listeners to prevent full project serialization on theme toggle. - StorageBackend Interface: Unified
StorageManagerbackend typing β removedtypeof dbServiceunion, typed asStorageBackendwithas unknown as StorageBackendcasts. FixedlistSnapshots()return type fromstring[]toProjectSnapshot[].
- HelpView Array Keys: Replaced bare array index keys with prefixed keys (
code-${index},t-${index},b-${index}-${subIndex}) and added biome-ignore comments for deterministic regex-split patterns. - Collaboration Awareness Validation: Added runtime validation for remote peer awareness state (type checks for id/name/color, length limits) to prevent malicious data injection.
- Lighthouse CI: Changed
continue-on-errorfromtruetofalsefor Lighthouse job in CI. - codexService Infinite Loop: Replaced
while+exec()loop withfor...of matchAll()to prevent browser freeze on English manuscripts. - Modal Focus-Trap: Consolidated cleanup into single function with early return for
!isOpen. - FOUC Theme Init: Added inline theme script in
<head>reading from localStorage. - dbService Decrypt: Added missing
awaitbeforedecryptWithMigration()in try/catch blocks.
- CryptoKey: Replaced reconstructible key derivation with
crypto.subtle.generateKey()non-extractable CryptoKey. - CSP img-src: Tightened from
https:wildcard to'self' data: blob:only. Addedframe-ancestors 'none'andupgrade-insecure-requests. - Import Validation: Added Valibot schema validation for imported project JSON.
- AI Provider:
testAIConnection('gemini')now makes real API validation call. OpenAI non-gpt models throw descriptive error instead of silent downgrade. OpenAI stream loop checkssignal.aborted. - Coverage Config: Replaced curated file list with glob patterns for honest all-up coverage.
- Community Templates: Updated error messages to reflect local static asset source instead of GitHub API references.
- CI / Codecov: Replaced deprecated
pnpm dlx codecovupload flow withcodecov/codecov-action@v5in.github/workflows/ci.yml. - CI / Failure Visibility: Removed
continue-on-errorfrom the Storybook job so broken Storybook builds fail CI as expected. - CI / Lighthouse Behavior: Kept Lighthouse job soft-fail semantics for budget misses while using
lhci autorun --assert.exitCode=0to avoid false-red budget exits and still surface runtime crashes. - Security Process: Added
.github/SECURITY.mdwith supported versions table, private disclosure channels, and a default 90-day coordinated disclosure policy. - PWA Update UX: Switched Service Worker update activation to explicit user consent.
SKIP_WAITINGis now sent only from the update toast action instead of auto-activation paths. - Service Worker Lifecycle: Removed install-time
self.skipWaiting()frompublic/sw.jsto prevent forced activation during active writing sessions. - Collaboration Resilience: Added
wss://signaling.yjs.devas a signaling fallback inservices/collaborationService.tsto reduce single-point-of-failure risk. - CSP Alignment: Extended
connect-srcinindex.htmlfor additional collaboration signaling endpoints (wss://signaling.yjs.dev,wss://*.workers.dev). - Owner Documentation: Added collaboration failover and self-hosted signaling guidance (Cloudflare Worker path) to
README.md. - Test Hardening: Replaced the stub
settingsSliceunit test with a comprehensive suite (29 tests, 331 LOC) covering all reducer actions and edge cases. - Theme Roundtrip Testability: Exported
applyInitialThemefromfeatures/settings/settingsSlice.tsand added persisted-state roundtrip tests forlocalStorage+ system-theme resolution.
- Render-Blocking Fonts: Replaced 3 render-blocking
@import url("https://fonts.googleapis.com/...")inindex.csswith self-hosted@fontsource/inter,@fontsource/jetbrains-mono,@fontsource/merriweather(woff2). Fonts are now bundled by Vite, eliminating external network requests and improving First Contentful Paint.
- CSP Tightening (Fonts): Removed
https://fonts.googleapis.comfromstyle-srcandconnect-src, removedhttps://fonts.gstatic.comfromfont-srcandconnect-srcin bothindex.htmlandsrc-tauri/tauri.conf.json. Fonts are now served from'self'only.
- Service Worker: Removed Google Fonts Cache-First fetch handler and
CACHE_FONTScache bucket frompublic/sw.js(no longer needed with self-hosted fonts). - Documentation Consolidation: Merged
audit15april2026.mdintoAUDIT.mdas a collapsible baseline section. Moved completed tasks fromTODO.mdtodocs/history/completed-v1.1.md. Cleaned upTODO.md(current sprint only) andROADMAP.md(quarterly+) with cross-references.
audit15april2026.md(consolidated intoAUDIT.md).- Preconnect links to
fonts.googleapis.comandfonts.gstatic.comfromindex.html.
@fontsource/inter,@fontsource/jetbrains-mono,@fontsource/merriweatheras dependencies for self-hosted font loading.docs/history/completed-v1.1.mdarchive for completed v1.1 sprint tasks.
- Logger No-ops: Fixed empty
debug()andinfo()method bodies inlogger.tsthat silently discarded all debug/info log messages. - Community Templates CSP: Replaced GitHub raw URL fetch in
communityTemplateService.tswith local static asset (public/community-templates/index.json), eliminating CSPconnect-srcviolations and enabling offline support. - Ollama Browser Guard: Added
window.__TAURI__check inaiProviderService.tsto prevent Ollama connection attempts in the browser (CSP blockslocalhostin the deployed PWA). Added amber warning banner in SettingsView for non-desktop environments. - Tauri Ollama CSP: Changed Tauri CSP
connect-srcfrom broadhttp://localhostto explicithttp://localhost:11434 http://127.0.0.1:11434for Ollama API access. - Service Worker Double-Track: Switched VitePWA from
generateSWtoinjectManifeststrategy, preventing conflicts with the custompublic/sw.jsservice worker. Addedself.__WB_MANIFESTinjection point for precache manifest. - i18n Eager Loading: Replaced 70 parallel fetch calls (14 modules Γ 5 languages) at boot with lazy single-bundle loading (2 fetches max: active language + EN fallback). Added
scripts/build-i18n.mjsprebuild step to merge per-module JSON files intopublic/locales/<lang>/bundle.json. - modulePreload Optimization: Converted all 14 AI thunks in
projectSlice.tsfrom static imports to dynamicimport()calls foraiProviderServiceandgeminiService, keeping@google/genaiout of the eager chunk graph. Added VitemodulePreload.resolveDependenciesfilter to skip preloading vendor chunks (ai-vendor,export-vendor,data-vendor,collaboration-vendor,canvas-vendor).
- Tauri FS Scope: Replaced unscoped
fs:allow-*permissions insrc-tauri/capabilities/default.jsonwith$APPDATA/**-scoped entries, preventing filesystem access outside the application data directory.
settings.ai.ollamaBrowserNotetranslation key in all 5 locale files (de, en, es, fr, it).public/community-templates/index.jsonstatic asset for offline community template loading.scripts/build-i18n.mjsbuild script for i18n bundle generation.prebuildnpm script hook to auto-generate i18n bundles before production builds.
- Critical: Configured Tailwind CDN dark mode to use
selectorstrategy with.dark-themeclass. Previously, alldark:prefixed Tailwind classes responded to OS system preference instead of the in-app theme toggle, causing broken styling when OS and app theme diverged. - Light Mode Overlays: Replaced all hardcoded
bg-black/40,bg-black/60,bg-gray-900/50modal/drawer/panel backdrops with theme-aware--overlay-backdropCSS custom property across Modal, Drawer, CommandPalette, Sidebar, CollaborationPanel, and VersionControlPanel. - Light Mode Card Overlays: Fixed CharacterView and WorldView card gradient overlays (
via-black/40) and hardcodedtext-white/text-gray-300text to use theme-aware CSS custom properties. - Light Mode Glass Effects: Replaced all
bg-white/5,bg-white/10,border-white/5,via-white/15dark-mode-only glass morphism classes with theme-aware CSS custom properties (--glass-bg,--glass-bg-hover,--glass-border,--glass-highlight) across Input, Textarea, Select, Checkbox, Card, AddNewCard, Skeleton, Button, Header, Dashboard, WriterView, ExportView, SettingsView, HelpView, TemplateView, WorldView, ManuscriptView, and CommandPalette. - Light Mode Aurora: Reduced aurora blob opacity from 0.25 to 0.08 in light mode to prevent visual noise on white backgrounds.
- Light Mode Prose Links: Fixed HelpView prose link color (
prose-a:text-indigo-400) to useprose-a:text-indigo-600 dark:prose-a:text-indigo-400for proper contrast in both themes. - Light Mode Ring/Focus Indicators: Replaced
ring-white/10,ring-black/5 dark:ring-white/5with theme-aware--glass-borderfor consistent visibility in both themes. - Tauri Version Mismatch: Aligned
src-tauri/tauri.conf.jsonversion from1.0.0to1.1.1(matchingpackage.json). - Tauri Build Path: Fixed
frontendDistfrom../buildto../distto match Vite's default output directory (was breakingtauri build). - Hardcoded German String: Replaced hardcoded
'EPUB-Export fehlgeschlagen: 'in ExportView with i18n keyexport.error.epubFailed. - Hardcoded EPUB Language: Replaced hardcoded
lang: 'de'in EPUB export with dynamiclanguagefrom user settings.
- CSP Tightening: Removed overly broad
https://*.googleapis.comwildcard from Tauri CSPconnect-src, retaining only the specifichttps://generativelanguage.googleapis.comdomain needed for Gemini API.
- Tauri Window Defaults: Improved window configuration from 800Γ600 to 1280Γ800 with
minWidth: 800,minHeight: 600, andcenter: truefor better desktop UX. - Tauri Product Name: Changed from
storycraft-studiotoStoryCraft Studiofor proper branding in window title and system tray.
- New CSS custom properties for theme-aware glass/overlay effects:
--overlay-backdrop,--glass-bg,--glass-bg-hover,--glass-border,--glass-highlight,--card-gradient-overlaywith appropriate values for both dark and light themes. - Added
export.error.epubFailedtranslation key to all 5 locale files (de, en, es, fr, it).
1.1.1 β 2026-04-17
- Resolved all npm audit vulnerabilities: 0 high, 0 critical (was 4 high + 1 critical).
- Fixed
protobufjscritical arbitrary code execution vulnerability (upgraded to β₯7.5.5). - Resolved
serialize-javascriptRCE + DoS vulnerabilities via npm overrides for thevite-plugin-pwaβworkbox-buildβ@rollup/plugin-terserchain. - Guarded all unprotected
localStorageaccesses inuseApp.tswith try/catch. - Guarded all unprotected
sessionStorageaccesses inusePWA.tsandCollaborationPanel.tsx. - Added missing Tauri capabilities:
fs:allow-read-dir,fs:allow-remove(fixes runtime failures forlistProjects,deleteProject,deleteSnapshot,clearApiKey). - Removed type-unsafe references to non-existent
StoryProject.author/.descriptioninfileSystemService.ts.
- CI security audit job:
npm audit --audit-level=high+dependency-review-actionon PRs. - CI Lighthouse job: performance budget assertions from
.lighthouserc.cjswith artifact upload. - CI Storybook job: automated build + artifact upload.
- Bundle analyzer:
rollup-plugin-visualizeras opt-in devDep (npm run analyze). - Shared AI utility module
services/aiUtils.ts:stripControlChars,sanitizePromptValue,sanitizePromptBlock,cleanPrompt,attachCause,stripJsonFences.
- CI pipeline order: security β quality β build β lighthouse/storybook β deploy.
- Reduced Vite
chunkSizeWarningLimitfrom 900 KB to 600 KB for more informative dev warnings.
- Deduplicated 4 utility functions between
geminiService.tsandaiProviderService.ts. - Documented Tauri feature parity gaps as tracked tech debt in AUDIT.md.
1.1.0 β 2026-04-16
- Set restrictive Content Security Policy for Tauri desktop app (
src-tauri/tauri.conf.json) - Narrowed Tauri capabilities to granular permissions (fs read/write, dialog open/save, shell open)
- Fixed Tauri identifier from
com.tauri.devtocom.storycraft.studio - Synced Tauri version to
1.0.0(was0.1.0) - Added AbortController support to all 14 AI-calling async thunks in projectSlice
- Added signal parameter to
checkConsistency,analyzeAsCritic,detectPlotHolesservice functions - Activated retry logic in geminiService (was defined but never called)
- Added PSK-based room isolation for P2P collaboration (SHA-256 room ID derivation)
- API key decrypt failures now return explicit
DECRYPT_FAILEDstatus with UI recovery flow
- Hardcoded
'en'language inuseConsistencyCheckerViewanduseCriticViewhooks now dynamically reads from user settings - Missing
src-tauri/target/entry in.gitignore - Removed duplicate empty
.prettierrcfile (.prettierrc.jsonis authoritative) - Fixed 50+ Markdown lint errors in
README.md(MD022, MD031, MD032, MD040, MD060) - Removed
as anytype casts inapp/hooks.ts(shallowEqual) andapp/store.ts(preloadedState) - Auto-save now validates state before writing to IndexedDB (null-check, 5MB size warning)
- Per-view error boundaries with
key={currentView}auto-reset and "Reset View" button - AbortController + cleanup in useConsistencyCheckerView and useCriticView hooks
- Generation history capped at 50 entries (FIFO) in writerSlice
- Room password input field in CollaborationPanel for PSK-based collaboration
- Decrypt failure warning banner in ApiKeySection with re-entry prompt
ROADMAP.mdwith Ollama/Local-AI strategy, model comparison table, and feature roadmapTODO.mdwith prioritized task tracker- Unit tests: geminiService, projectSlice, writerSlice, settingsSlice, dbService, listenerMiddleware, collaborationService (80 tests total)
- Coverage thresholds (50%) in vitest.config.ts
- Manual chunks for leaflet, konva, recharts in Vite build config
- Redux logger middleware now opt-in via
localStorage.getItem('debugRedux') - CI pipeline: ESLint and typecheck switched from soft-fail to hard-fail mode
- ErrorBoundary component now accepts
onResetcallback prop AUDIT.mdupdated with resolution status for addressed findings- Lazy-loaded
docx/jszipexport libraries and improved Vite manual chunk splitting with a higherchunkSizeWarningLimitfor optimized production builds
1.0.0 β 2025-01-01
- React 19 + TypeScript 5 (strict mode) single-page application
- Vite 6 build tooling with ES2022 target and manual chunk splitting
- Redux Toolkit 2.x state management with Redux-Undo (100-step history)
- Feature-sliced architecture (
project,settings,status,writer,versionControl) - Listener middleware for debounced auto-save to IndexedDB (1000ms)
- Three-panel manuscript editor with chapter navigator and project inspector
- Real-time
@characterand#worldmention overlay with linking - Scene board β kanban-style drag-and-drop visual story planning (DnD Kit)
- Voice dictation via Web Speech API with multi-language support
- Command palette (Ctrl+K / βK) for keyboard-first navigation
- 10 specialized AI writing tools: Continue, Improve, Change Tone, Dialogue, Brainstorm, Synopsis, Grammar & Style, Critic, Plot-Hole Detector, Consistency Checker
- AI outline generator with genre, pacing, and plot twist controls
- AI character profile generator with backstory, motivations, and personality traits
- AI character portrait generation in multiple styles (realistic, anime, cartoon, comic)
- AI world-building content generation with atmospheric ambiance images
- AI logline suggestions for project dashboard
- RAG-based consistency checker cross-referencing manuscript against character/world data
- Streaming AI responses with chunk-by-chunk rendering
- Multi-provider architecture (Gemini primary, OpenAI and Ollama support)
- Intelligent story template library (Three-Act, Hero's Journey, Save the Cat!, Fichtean Curve)
- Genre templates (Fantasy, Thriller, Horror, Romance, Space Opera, Dystopian)
- Community template system with GitHub-hosted template repository
- Interactive character relationship graph (force-directed visualization)
- IndexedDB storage with LZ-String compression for payloads > 10KB
- AES-256-GCM encryption for API keys via Web Crypto API
- Version control with branch management and snapshot system
- Project import/export as JSON with image handling
- Auto-save with configurable debounce interval
- Markdown (
.md) export - Plain text (
.txt) export - PDF export with title page, configurable font and spacing (jsPDF)
- DOCX export (docx + jszip)
- EPUB 3.0 client-side generation (epubApiService)
- AI-generated synopsis for export
- P2P real-time editing via Yjs + WebRTC (no backend required)
- Awareness system for presence tracking
- Shared Y.Text documents for concurrent editing
- Service Worker with versioned caches and smart caching strategies
- Cache-First for static assets, Stale-While-Revalidate for dynamic content
- NetworkOnly for AI API calls (never cached)
- Offline fallback page with branded UI
- Installable on desktop and mobile (iOS & Android)
- App shortcuts for quick access from home screen
- Background sync and periodic update support
- Web App Manifest v3 with share target and protocol handlers
- 5 languages: German (complete), English (complete), French, Spanish, Italian (in progress)
- 14 modular translation files per language
- Custom React Context-based i18n system
- Language persistence via localStorage
- Document
langattribute synchronization
- WCAG 2.1 AA compliance
- Semantic HTML with comprehensive ARIA attributes
- Focus trapping in modals and drawers
- Keyboard navigation throughout
- Screen reader support with sr-only labels
- High contrast, reduced motion, and color-blind mode settings
- Tauri 2 wrapper for native desktop distribution
- File system access via Tauri plugins
- Dialog and shell integration
- ESLint 9 flat config with TypeScript, React, React Hooks, and jsx-a11y plugins
- Prettier formatting with pre-commit hooks (Husky + lint-staged)
- Vitest unit testing with jsdom environment
- Playwright E2E testing (Chromium + Firefox)
- Storybook component development environment
- GitHub Actions CI/CD pipeline (lint β typecheck β test β build β deploy)
- Automatic GitHub Pages deployment on push to main
- No hardcoded API keys β all keys encrypted at rest in IndexedDB
- Content Security Policy in index.html
- Local-first architecture β no data leaves the browser
- HTTPS-only external API communication
- Device-scoped encryption key derivation