Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,9 @@ jobs:
- name: Native-readiness roadmap policy gate
run: pnpm run native-readiness:check

- name: Tauri plugin Rust/npm version-parity gate
run: pnpm run tauri-plugins:check
Comment thread
qnbs marked this conversation as resolved.

- name: Feature parity audit
run: pnpm run parity:check

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- **Tauri plugin Rust/npm version parity restored:** `tauri-plugin-http` and
`tauri-plugin-notification` had 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; added
`check-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.

<!-- release-candidate: v1.28.5 -->
## [1.28.5] — 2026-09-09

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<img src="https://img.shields.io/badge/Storage-IndexedDB_v8-F59E0B" alt="IndexedDB v8">
<img src="https://img.shields.io/badge/PWA-v3.0-5BB974?logo=pwa" alt="PWA v3.0">
<img src="https://img.shields.io/badge/i18n-19_locales-2942_keys-0EA5E9" alt="i18n 19 locales — 2942 keys">
<img src="https://img.shields.io/badge/Tests-7635%2B_%2F_603_files-22C55E" alt="7635+ tests / 603 files">
<img src="https://img.shields.io/badge/Tests-7643%2B_%2F_604_files-22C55E" alt="7643+ tests / 604 files">
<img src="https://img.shields.io/codecov/c/github/qnbs/WorldScript-Studio?logo=codecov&label=Coverage" alt="Codecov Coverage">
<img src="https://img.shields.io/badge/License-MIT-22C55E" alt="License MIT">
<img src="https://img.shields.io/github/actions/workflow/status/qnbs/WorldScript-Studio/.github/workflows/ci.yml?branch=main&logo=github" alt="CI Status">
Expand Down Expand Up @@ -512,7 +512,7 @@ The Settings → AI panel shows a live GPU status badge with adapter details and
| **Document Export** | docx + jszip | Word-compatible `.docx` generation (lazy-loaded) |
| **PWA** | Service Worker + Web App Manifest v3 | Offline support, installability, Workbox chunking |
| **i18n** | Custom React Context (`I18nContext.tsx`) | 2942 keys × 19 locales (de/en/es/fr/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta); EN fallback; `localStorage` persistence |
| **Testing** | Vitest 4.x (7635+ tests / 603 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) |
| **Testing** | Vitest 4.x (7643+ tests / 604 files) + Playwright E2E | Unit/integration + cross-browser E2E; Stryker mutation (manual workflow) |
| **Code Quality** | Biome (lint + format) + TypeScript 7 (tsgo) strict | `--error-on-warnings` in CI; zero `any` policy |
| **Visualization** | Force-directed graph | Interactive character relationship network |
| **Desktop** | Tauri v2 | Cross-platform installer; auto-updater via `latest.json` |
Expand Down Expand Up @@ -550,7 +550,7 @@ WorldScript-Studio/
│ ├── sw.js # PWA Service Worker
│ └── manifest.json # PWA Web App Manifest v3
├── tests/
│ ├── unit/ # Vitest unit tests (7635+ tests, 603 files) — count spans tests/, components/, packages/*/tests/, not just this folder
│ ├── unit/ # Vitest unit tests (7643+ tests, 604 files) — count spans tests/, components/, packages/*/tests/, not just this folder
│ │ ├── ai/ # aiSmallModules, aiCoreFallbackPaths
│ │ └── settings/ # WebLlmPanel, AiSections
│ └── e2e/ # Playwright specs + helpers.ts
Expand Down Expand Up @@ -715,7 +715,7 @@ The main pipeline is [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Opt
Raw bundle-budget ceilings (KB per uncompressed asset): entry **2500 KB**, vendor **6200 KB**, other JavaScript **2500 KB**, and WASM **30000 KB**.

**Current test metrics (2026-09-07, source-synchronized; CI remains authoritative for pass/fail):**
- **7635+ unit tests** across **603 test files** — CI is authoritative for pass/fail
- **7643+ unit tests** across **604 test files** — CI is authoritative for pass/fail
- Coverage thresholds: lines ≥ 80 · branches ≥ 66 · functions ≥ 72 · statements ≥ 78 — enforced in CI (see Codecov badge for live metrics)
- i18n: **2942 keys × 19 locales** (en/de/fr/es/it + ar/he/fa RTL Beta + ja/zh/pt/el/fi/sv/hu/is/eu/ru/ko Beta)

Expand Down
2 changes: 1 addition & 1 deletion docs/CI.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ the local block above; GitHub Actions owns those heavy checks on this hardware.

`pnpm run ci:prepush` always resolves a change classification from the outgoing evidence first
(`scripts/ci-prepush-classifier.mjs`), then runs docs/release-truth, CSP, desktop-import boundary,
native-readiness, and dependency-state checks unconditionally on every invocation. It does **not**
native-readiness, Tauri plugin version-parity, and dependency-state checks unconditionally on every invocation. It does **not**
run Biome lint — full-repository lint stays CI-owned (`quality` job); only staged files are linted
locally, by the separate pre-commit hook (`lint-staged`). Two check groups are conditional on the
change classification instead of always running:
Expand Down
4 changes: 2 additions & 2 deletions docs/PR-CI-MERGE-WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

Full pre-push gate mechanics, worktree bootstrap, CI pipeline order, the PR review-comment correction loop (bot roster, three-channel comment check, quiescence loop), and known GitHub merge-gate quirks with recovery steps. The short list of non-negotiable rules distilled from this doc lives in the root `AGENTS.md`.

**Mandatory pre-push gate:** Run `pnpm run ci:prepush` before every push and again after every local correction before re-pushing. It always resolves a change-aware classification (`scripts/ci-prepush-classifier.mjs`) from the outgoing evidence first, then runs docs/release-truth, CSP, desktop-import-boundary, native-readiness, and dependency-state checks unconditionally — it does **not** run Biome lint; that stays the pre-commit hook's job on staged files only (`lint-staged`), and full-repository lint is CI-owned. The single-checker (`--checkers 1`) local typecheck and the i18n/content-guard checks run only when the classification requires them — `DOCS_ONLY`, `WORKFLOW_ONLY`, `NON_CODE_ONLY`, `RUST_TAURI`, `TOOLING`, and non-TypeScript `TEST_ONLY` changes report typecheck as `DEFERRED_TO_REQUIRED_CI` instead of running it locally, and i18n/content-guard checks run only for changes matching their own governed paths or implementation files (see `scripts/ci-prepush-check-registry.mjs`). It is the same `tsgo --noEmit` check as CI, not literally identical to it — CI uses `--checkers 4`. Whenever outgoing path evidence is incomplete, unresolved, or the manual committed-range diff fails, the gate fails closed into full local admission (every conditional check runs) rather than deferring anything. A targeted test or changed-file lint run alone is insufficient. If pnpm reports a dependency verification failure after a branch or lockfile change, run `node scripts/dependency-state.mjs reconcile` (or `pnpm run deps:reconcile`) first, then rerun the gate. The pre-commit hook does not replace this gate. Required GitHub CI remains the unconditional authority for the complete lint, TypeScript, and i18n validation regardless of what the local gate deferred.
**Mandatory pre-push gate:** Run `pnpm run ci:prepush` before every push and again after every local correction before re-pushing. It always resolves a change-aware classification (`scripts/ci-prepush-classifier.mjs`) from the outgoing evidence first, then runs docs/release-truth, CSP, desktop-import-boundary, native-readiness, Tauri plugin version-parity, and dependency-state checks unconditionally — it does **not** run Biome lint; that stays the pre-commit hook's job on staged files only (`lint-staged`), and full-repository lint is CI-owned. The single-checker (`--checkers 1`) local typecheck and the i18n/content-guard checks run only when the classification requires them — `DOCS_ONLY`, `WORKFLOW_ONLY`, `NON_CODE_ONLY`, `RUST_TAURI`, `TOOLING`, and non-TypeScript `TEST_ONLY` changes report typecheck as `DEFERRED_TO_REQUIRED_CI` instead of running it locally, and i18n/content-guard checks run only for changes matching their own governed paths or implementation files (see `scripts/ci-prepush-check-registry.mjs`). It is the same `tsgo --noEmit` check as CI, not literally identical to it — CI uses `--checkers 4`. Whenever outgoing path evidence is incomplete, unresolved, or the manual committed-range diff fails, the gate fails closed into full local admission (every conditional check runs) rather than deferring anything. A targeted test or changed-file lint run alone is insufficient. If pnpm reports a dependency verification failure after a branch or lockfile change, run `node scripts/dependency-state.mjs reconcile` (or `pnpm run deps:reconcile`) first, then rerun the gate. The pre-commit hook does not replace this gate. Required GitHub CI remains the unconditional authority for the complete lint, TypeScript, and i18n validation regardless of what the local gate deferred.

**New-worktree / first-time dependency bootstrap — never a bare `pnpm install`:** When materializing `node_modules` for the first time on a new git worktree (or any branch checkout that hasn't been installed yet), always run `node scripts/dependency-state.mjs reconcile` (or `pnpm run deps:reconcile`) — never a bare `pnpm install`. The bare form omits `--frozen-lockfile`, so on any manifest/lockfile drift it silently *rewrites* `pnpm-lock.yaml` instead of failing loudly, and it never writes the repo's own dependency fingerprint (`node_modules/.worldscript-deps-fingerprint`) — that fingerprint is checked only by `pnpm run deps:verify`, `ci:prepush`, and the pre-commit hook, not by arbitrary `pnpm run <script>` calls, so a stale one from a bare install can go undetected until one of those three actually runs. Separately, pnpm's own built-in `verifyDepsBeforeRun` check (unrelated to this repo's fingerprint file; it compares `node_modules` against the lockfile) can independently surface a broader `node_modules`/lockfile mismatch on other `pnpm run` calls as `ERR_PNPM_VERIFY_DEPS_BEFORE_RUN`. This matters most exactly where it looks safest: on a branch that deliberately touches *nothing* dependency-related — a Dependabot GitHub-Actions-only bump, a docs-only PR, a workflow-policy fix — a stray unverified bare install is the one action that could introduce an out-of-scope `pnpm-lock.yaml`/`package.json` change nobody asked for, or run install/postinstall scripts against an unverified graph. `deps:reconcile`'s `--frozen-lockfile` failing loudly is the primary safeguard; after it succeeds, `git status --porcelain` / `git diff --stat -- pnpm-lock.yaml package.json` showing no diff is corroborating evidence, not a substitute for using the right command in the first place — a clean lockfile diff after a bare install proves the graph happened to already be in sync, not that the bare install was the correct or safe choice to make. **Name the worktree directory itself dot-free** (e.g. `release-v1282`, not `release-v1.28.2`) — see the `tsgo` gotcha under Build & bundler gotchas.

**Quality gate (local pre-push subset):** `pnpm run ci:prepush` runs dependency-state/docs/CSP/native-readiness checks unconditionally (never full-repository lint — see the pre-push gate note above for what runs lint locally), and the single-checker local typecheck and i18n/content-guard checks only for changes the classifier marks as potentially impacting them (fail-closed to "run everything conditional" when evidence is incomplete); CI additionally runs full-repository lint, the 4-checker typecheck, full-suite coverage, and heavy jobs regardless of what the local gate ran. Locally use only the targeted form `pnpm exec vitest run <path> --coverage` when debugging coverage. Full pipeline graph: [`CI.md`](CI.md). Coverage thresholds: lines 80, branches 66, functions 72, statements 78 (see `vitest.config.ts`).
**Quality gate (local pre-push subset):** `pnpm run ci:prepush` runs dependency-state/docs/CSP/native-readiness/Tauri-plugin-parity checks unconditionally (never full-repository lint — see the pre-push gate note above for what runs lint locally), and the single-checker local typecheck and i18n/content-guard checks only for changes the classifier marks as potentially impacting them (fail-closed to "run everything conditional" when evidence is incomplete); CI additionally runs full-repository lint, the 4-checker typecheck, full-suite coverage, and heavy jobs regardless of what the local gate ran. Locally use only the targeted form `pnpm exec vitest run <path> --coverage` when debugging coverage. Full pipeline graph: [`CI.md`](CI.md). Coverage thresholds: lines 80, branches 66, functions 72, statements 78 (see `vitest.config.ts`).

**CI pipeline order:** `security` → `quality` (Biome + tsgo + Vitest matrix) → `build` / `e2e` / `storybook` (parallel) → `lighthouse` (after build) → `deploy` on `main`. `ci-success` is a required-status aggregator (`if: always()`) so branch protection can require one context instead of many individual ones — it fails if any governed required job doesn't resolve to `success` (Rust jobs and `pr-size` legitimately skip when their trigger conditions don't apply, and that counts as pass for that job only). The exact, current dependency list lives in [`CI.md`](CI.md#job-graph) and the live `ci-success` job in `.github/workflows/ci.yml` — don't hardcode it here, it changes as jobs are added or removed. Two additional jobs run in parallel with `quality`, both path-scoped via the `changes` job (legitimately `skipping` on PRs that don't touch their directory, which `ci-success` treats as a pass for that job only): `rust-tauri` (`src-tauri/**` — fmt/check/clippy/test, needs the GTK/WebKit apt-get steps) and `core-rust` (`crates/**` — same fmt/check/clippy/test for the renderer-neutral Rust Core, no GUI deps so no apt-get steps needed).

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"deps:verify": "node scripts/dependency-state.mjs verify",
"deps:reconcile": "node scripts/dependency-state.mjs reconcile",
"ci:prepush": "node scripts/ci-prepush-lowend.mjs",
"ci:local:full": "pnpm run lint && pnpm run typecheck && pnpm run i18n:check && pnpm run guardrail:desktop-imports && pnpm run native-readiness:check",
"ci:local:full": "pnpm run lint && pnpm run typecheck && pnpm run i18n:check && pnpm run guardrail:desktop-imports && pnpm run native-readiness:check && pnpm run tauri-plugins:check",
"predev": "node scripts/sync-csp.mjs && node scripts/sync-sw-version.mjs && node scripts/sync-tauri-version.mjs && node scripts/sync-readme-metrics.mjs && node scripts/build-i18n.mjs && node scripts/copy-duckdb-assets.mjs",
"dev": "vite",
"dev:turbo": "turbo run dev --parallel",
Expand All @@ -46,6 +46,7 @@
"csp:check": "node scripts/check-csp-policy.mjs",
"csp:verify": "node scripts/sync-csp.mjs && git diff --exit-code -- index.html nginx.conf public/_headers vercel.json src-tauri/tauri.conf.json && node scripts/check-csp-policy.mjs",
"native-readiness:check": "node scripts/check-native-readiness.mjs",
"tauri-plugins:check": "node scripts/check-tauri-plugin-versions.mjs",
"workflow-policy:check": "node scripts/workflow-policy-check.mjs",
"pr-size:check": "node scripts/check-pr-size.mjs",
"suppressions:check": "node scripts/check-suppressions.mjs",
Expand Down Expand Up @@ -142,8 +143,8 @@
"@tauri-apps/api": "^2.11.1",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-http": "^2.5.9",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-http": "^2.6.0",
"@tauri-apps/plugin-notification": "^2.4.0",
Comment thread
qnbs marked this conversation as resolved.
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-updater": "^2.11.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"@tauri-apps/api": "^2.11.1",
"@tauri-apps/plugin-dialog": "^2.7.1",
"@tauri-apps/plugin-fs": "^2.5.1",
"@tauri-apps/plugin-notification": "^2.3.3",
"@tauri-apps/plugin-notification": "^2.4.0",
"@tauri-apps/plugin-process": "^2.3.1",
"@tauri-apps/plugin-shell": "^2.3.5",
"@tauri-apps/plugin-updater": "^2.11.0"
Expand Down
24 changes: 12 additions & 12 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions scripts/check-tauri-plugin-versions.d.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function resolvedCargoPluginVersions(cargoLock: string): Map<string, string | null>;
export function resolvedPnpmImporterVersions(pnpmLock: string): Map<string, Map<string, string>>;
export function findTauriPluginVersionMismatches(
cargoLock: string,
pnpmLock: string,
importerPackages: Array<{ importer: string; pkg: { dependencies?: Record<string, string> } }>,
): string[];
Loading
Loading