From 0b1400ec8f6be3b1082378bd267707663f17f86e Mon Sep 17 00:00:00 2001 From: qnbs <155236708+qnbs@users.noreply.github.com> Date: Mon, 7 Sep 2026 16:23:32 +0200 Subject: [PATCH 01/12] chore(agent): slim and deduplicate repository instruction context --- .cursor/index.mdc | 60 +- .cursor/rules/000-cursor-rules.mdc | 8 +- .cursor/rules/001-core-security.mdc | 4 +- .../rules/100-ai-providers-and-storage.mdc | 4 +- .cursor/rules/150-i18n-and-content.mdc | 12 +- .cursor/rules/200-architecture-limits.mdc | 17 +- .cursor/rules/300-ui-components.mdc | 9 +- .cursor/rules/800-testing-standards.mdc | 12 +- .cursor/rules/850-mcp-and-prd.mdc | 5 +- .cursorrules | 77 +- .github/copilot-instructions.md | 265 +------ AGENTS.md | 742 +++--------------- CLAUDE.md | 358 +-------- 13 files changed, 164 insertions(+), 1409 deletions(-) diff --git a/.cursor/index.mdc b/.cursor/index.mdc index 90eb4a7d3..63ad786c5 100644 --- a/.cursor/index.mdc +++ b/.cursor/index.mdc @@ -1,54 +1,18 @@ --- -description: Project Manifest and Global Architecture -alwaysApply: true +description: Open the canonical repository guidance and relevant specialist rules on demand +globs: ~ +alwaysApply: false --- -# StoryCraft Studio — Manifest +# Cursor context index -**Zweck:** Offline-first Schreib- und Erzählwerkzeug (v1.3+): Dashboard, Manuskript, Szenenboard, Plot Board, Charaktergraph, Version Control, KI-Copilot — **kein Ghostwriting-Fokus**. Daten lokal; Privacy-first; Cloud-KI nur nutzergetriggert. +`AGENTS.md` is the canonical repository-wide rule source. Load it for repository work, then +load only the matching `.cursor/rules/*.mdc` rule and nearest nested `CLAUDE.md` for the paths +being changed. -**Frontend:** React 19, Vite 8, TypeScript 6 (`strict`, `exactOptionalPropertyTypes`, restrike Index/Switch). Alias `@` → Repo-Root. **pnpm 10** Workspace (`packages/*`); Node ≥22. Optional **Turbo** (`*:turbo`-Skripte). +Useful deep references: -**State:** Redux Toolkit + `redux-undo` (Projekt/Undo); **Zustand** für transientes UI (`app/transientUiStore.ts` — z. B. Command-Palette). Listener-Middleware für Auto-Save/Side-Effects. - -**Persistenz:** Dual IndexedDB (`dbService.ts`: State vs. Data, Migration, LZ-String, AES-GCM für Keys); **`storageService` → `storageBackend`** (Web/Tauri, Binder-Blobs, typisiertes Envelope). Kein serverseitiges ORM. - -**KI (Hybrid):** `@google/genai` + **Vercel AI SDK** (`ai`, `@ai-sdk/*`); Orchestrierung `services/ai/` (Strangler) + Legacy `aiProviderService.ts` / `geminiService.ts`. **RAG assembly:** `services/ragPromptAssembly.ts` (Writer, Plot Board, Consistency pattern). DuckDB `rag_chunks.embedding` (384-dim). Lokal: Ollama, WebLLM/ONNX/Transformers (`packages/ai-core`). Thunks: `features/project/thunks/`. - -**Desktop:** Tauri 2 (`src-tauri`); `@tauri-apps/api` im Vite-Bundle **external**; FS/Dialog nur über Services — UI-Atoms ohne direkte Tauri-Imports. - -**Kollaboration:** Yjs + `y-webrtc` (`collaborationService.ts`); Signaling: `settings.collaboration.webrtcSignalingUrls`; CSP `connect-src` für benutzerdefinierte `wss:`/`https:`. - -**UI:** Tailwind 4 (`@tailwindcss/vite`), Tokens optional `@domain/ui`; Primitive `components/ui/*`. Storybook 10 + **addon-a11y**. Biome 2 (`--error-on-warnings`), `lineWidth` 100. - -**PWA:** `vite-plugin-pwa`, `injectManifest`, `public/sw.js` (Version-Sync via `scripts/sync-sw-version.mjs`). - -**i18n:** Quelle `locales//*.json` (de, en, fr, es, it); Runtime **`public/locales//bundle.json`** via `pnpm run i18n:bundle` / `i18n:check` + `content:guard`. Keine hardcodierten UI-Strings. - -**Tests:** Vitest 4 (jsdom, `tests/setup.ts`, `maxWorkers: 1`); Coverage-Schwellen **63/55/54/62** % (lines/branches/functions/statements). E2E Playwright **CI-first** (`CI=true`). Stryker informativ. Siehe [`docs/CI.md`](../docs/CI.md). - -**Qualität (lokal schnell):** `pnpm run lint`, `typecheck`, `i18n:check`; optional `pnpm exec vitest run` **ohne** `--coverage`. Schwere Gates (E2E, LHCI, Coverage) = GitHub Actions. - -**Barrierefreiheit:** `LiveRegionProvider` / `useAnnounce()`; Settings-Presets + Zod (`accessibilitySchema.ts`); [`docs/ACCESSIBILITY.md`](../docs/ACCESSIBILITY.md); E2E [`tests/e2e/a11y.spec.ts`](../tests/e2e/a11y.spec.ts). - -**Command Center:** `services/commands/` → Palette; `CommandExecutorContext`; Shortcuts `useGlobalKeyboardShortcuts` + `services/keyboard/`; Hilfe `helpCatalog.ts` + `helpDocRetrieval.ts` + `tryActionId`; Settings-Guide-Kategorie `experimental` für 12 Feature-Flags. - -**v1.10:** Mobile-Nav (Scene Board), Branches ≥55 %, Help-Artikel (Plot Board v2, Hybrid RAG, Tauri). v1.9: Lazy cold-start (`duckdbListenerLoader`, deferred `aiApi`), Help/Settings-Hub, Tauri menu/window-state/updater — [`docs/SPRINT-V1.10.md`](../docs/SPRINT-V1.10.md). - -**Pfad-Konventionen:** `app/`, `components/`, `features/*`, `hooks/`, `services/`, `packages/*`, `types.ts`, `tests/`. - -## Cursor-Regelkatalog - -| Datei | Wann | -|-------|------| -| [`.cursorrules`](../.cursorrules) | QNBS-Masterprompt, Kommentar-Konvention | -| [`000-cursor-rules.mdc`](rules/000-cursor-rules.mdc) | Neue/geänderte `.mdc` | -| [`001-core-security.mdc`](rules/001-core-security.mdc) | Secrets, Logs, CSP | -| [`100-ai-providers-and-storage.mdc`](rules/100-ai-providers-and-storage.mdc) | KI, IDB, Storage | -| [`150-i18n-and-content.mdc`](rules/150-i18n-and-content.mdc) | Locales, Copy, Templates | -| [`200-architecture-limits.mdc`](rules/200-architecture-limits.mdc) | KISS/DRY, Dateigrößen | -| [`300-ui-components.mdc`](rules/300-ui-components.mdc) | React UI, A11y | -| [`800-testing-standards.mdc`](rules/800-testing-standards.mdc) | Vitest, E2E, CI-first | -| [`850-mcp-and-prd.mdc`](rules/850-mcp-and-prd.mdc) | Roadmap, MCP, Doku-Hub | - -**Maintainer-Doku:** [`docs/BEST-PRACTICES.md`](../docs/BEST-PRACTICES.md), [`AUDIT.md`](../AUDIT.md), [`ROADMAP.md`](../ROADMAP.md). Nach größeren Code-Änderungen optional `pnpm run graphify:update`. +- `README.md` / `CONTRIBUTING.md` — product and setup +- `docs/CI.md` / `docs/PR-CI-MERGE-WORKFLOW.md` — CI and PR operations +- `docs/native/CORE-MIGRATION-LEDGER.md` — native direction +- `docs/graphify.md` / `docs/codegraph.md` — on-demand architecture investigation diff --git a/.cursor/rules/000-cursor-rules.mdc b/.cursor/rules/000-cursor-rules.mdc index 71543af0c..06f6745d2 100644 --- a/.cursor/rules/000-cursor-rules.mdc +++ b/.cursor/rules/000-cursor-rules.mdc @@ -23,7 +23,8 @@ alwaysApply: false - `800–899` Testing/Workflows - Frontmatter **`description`:** ATO (Action, Trigger, Outcome), **< 120 Zeichen**. - **`globs`:** kommagetrennt, **keine Leerzeichen**, keine Anführungszeichen — z. B. `globs: services/**/*.ts,features/**/*.ts`. -- **`alwaysApply: true`** nur für [`.cursor/index.mdc`](../index.mdc) (Manifest). Fachregeln: `false` + passende `globs`. +- An always-applied repository rule is not needed. Use `alwaysApply: false` with + precise globs, or `globs: ~` for a manual specialist rule. - **≤ 200 Zeilen** pro Datei; Code nicht duplizieren — verweise auf kanonische Pfade (`@services/logger.ts`). - Pflicht: `` und ``. - Regeln mit **Plain-Text-Editor** schreiben (MDC-UI-Bugs vermeiden). @@ -34,7 +35,7 @@ alwaysApply: false --- description: globs: -alwaysApply: +alwaysApply: false --- ``` @@ -49,5 +50,6 @@ Hybrid-KI-Regel: `101-hybrid-ai-orchestration.mdc`, `globs: services/ai/**/*.ts, -`globs: *.ts, *.tsx` (Leerzeichen); 300-Zeilen-Regel mit kopiertem Redux-Reducer; `alwaysApply: true` auf jeder Fachregel. +`globs: **/*` für jede Fachregel; copied architecture encyclopedias; or an always-applied +specialist rule. diff --git a/.cursor/rules/001-core-security.mdc b/.cursor/rules/001-core-security.mdc index f196001c6..742f5e4c0 100644 --- a/.cursor/rules/001-core-security.mdc +++ b/.cursor/rules/001-core-security.mdc @@ -1,10 +1,10 @@ --- description: Protect secrets,logs,and CSP when touching any project file -globs: **/* +globs: app/**/*.ts,app/**/*.tsx,components/**/*.ts,components/**/*.tsx,config/**/*,features/**/*.ts,features/**/*.tsx,packages/**/*.ts,packages/**/*.tsx,public/sw.js,scripts/**/*.mjs,scripts/**/*.ts,services/**/*.ts,services/**/*.tsx,.github/**/*,src-tauri/**/*,crates/**/* alwaysApply: false --- -# Core Security & Betrieb +# Core security red lines ## Secrets & Konfiguration diff --git a/.cursor/rules/100-ai-providers-and-storage.mdc b/.cursor/rules/100-ai-providers-and-storage.mdc index e3397a167..ed887f9e0 100644 --- a/.cursor/rules/100-ai-providers-and-storage.mdc +++ b/.cursor/rules/100-ai-providers-and-storage.mdc @@ -1,6 +1,6 @@ --- description: Apply hybrid AI,IndexedDB,and storage patterns when editing services or features -globs: services/**/*.ts,features/**/*.ts,packages/ai-core/**/*.ts +globs: services/ai/**/*.ts,services/aiProviderService.ts,services/geminiService.ts,services/localAiFacade.ts,services/localRag*.ts,services/rag*.ts,services/dbService.ts,services/storage/**/*.ts,services/duckdb/**/*.ts,packages/ai-core/**/*.ts alwaysApply: false --- @@ -10,7 +10,7 @@ alwaysApply: false - Einstieg **`services/ai/index.ts`**: Strangler neben Legacy; Writer-Streaming → **`writer`-Slice**, Manuskript erst nach **Accept**. - Netzwerk-KI: **`aiProviderService.ts`**, **`geminiService.ts`** — keine parallele SDK-Init in Komponenten. -- Vercel AI SDK (`ai`, `@ai-sdk/google`, `@ai-sdk/openai`): neue Pfade über `services/ai/` und Hooks (`useStoryCraftAI` o. ä.), nicht doppelte Retry-Logik. +- Vercel AI SDK (`ai`, `@ai-sdk/google`, `@ai-sdk/openai`): new paths belong in `services/ai/` and `hooks/useWorldScriptAI.ts`; do not duplicate retry logic. - **`assertCloudAiAllowed`:** Cloud nur wenn Policy/Settings es erlauben. ## Hybrid & lokale Inferenz diff --git a/.cursor/rules/150-i18n-and-content.mdc b/.cursor/rules/150-i18n-and-content.mdc index b22a211cb..7a804097b 100644 --- a/.cursor/rules/150-i18n-and-content.mdc +++ b/.cursor/rules/150-i18n-and-content.mdc @@ -8,8 +8,8 @@ alwaysApply: false ## Schlüssel & Bundles -- Quelle: **`locales//*.json`** (de, en, fr, es, it) — **alle fünf** Locales bei neuen Keys. -- Runtime: **`public/locales//bundle.json`** via `node scripts/build-i18n.mjs` (läuft in `predev` / `i18n:check`). +- Source: **`locales//*.json`** for all 19 repository locales; add every new key to every locale tree. +- Runtime: **`public/locales//bundle.json`** via `node scripts/build-i18n.mjs` (also used by `i18n:check`). - CI: **`pnpm run i18n:check`** (Key-Parität + Bundle + `content:guard`). - UI-Text nur als **Übersetzungsschlüssel** (Dot-Notation), nie hardcodiert in TSX — außer Dev-Debug. @@ -21,19 +21,19 @@ alwaysApply: false ## Community & Help - Templates: kanonisches **Englisch** in `community-templates/index.json` + Spiegel unter `public/community-templates/`; Zod in `fetchCommunityTemplates`. -- Help-Struktur: **`services/help/helpCatalog.ts`** (nicht `help.categories` in JSON); Texte in `locales//help.json`. Neue Artikel: Katalog + alle 5 Locales (siehe `scripts/help-extra-keys.json`, `scripts/help-locales-es-fr-it.json`). +- Help structure: **`services/help/helpCatalog.ts`** (not `help.categories` in JSON); text lives in `locales//help.json`. New articles require the catalog plus every locale tree. - Help-Artikel: **`tryActionId`** für Palette/Navigation; Suche über `helpSearch.ts`; Demo-Feedback über Toasts, nicht `alert`. ## PR-Checkliste -1. Keys in allen Locales +1. Keys in all 19 locale trees 2. `pnpm run i18n:check` lokal (Quick-Tier) 3. Keine Secrets/PII in JSON-Beispielen -Neuer Settings-String: Key in `locales/en/settings.json` + de/fr/es/it; `i18n:check` grün; Help-Link mit `tryActionId: 'nav-settings'`. +New Settings copy: key in every `locales/*/settings.json`, rebuild bundles, and keep `i18n:check` green. -Nur `en` befüllt; UI zeigt rohe Keys weil `bundle.json` nicht gebaut; deutscher Fließtext direkt in `components/Dashboard.tsx`. +Only `en` updated; runtime bundles not rebuilt; or user-visible text hardcoded in a component. diff --git a/.cursor/rules/200-architecture-limits.mdc b/.cursor/rules/200-architecture-limits.mdc index 3d2356096..fd42e3aa0 100644 --- a/.cursor/rules/200-architecture-limits.mdc +++ b/.cursor/rules/200-architecture-limits.mdc @@ -1,24 +1,24 @@ --- description: Keep files small and reuse logic via services,hooks,and feature slices -globs: **/* +globs: app/**/*.ts,components/**/*.tsx,contexts/**/*.tsx,features/**/*.ts,hooks/**/*.ts,services/**/*.ts,packages/**/*.ts,packages/**/*.tsx alwaysApply: false --- -# Architektur: KISS, DRY, Grenzen +# Architecture limits ## Prinzipien - **KISS:** Einfachste Lösung mit `strict` Types und testbaren Seams. - **DRY:** Logik in `services/`, `hooks/`, `features/*/thunks` — nicht in Views duplizieren. -## View-Muster (StoryCraft) +## View pattern (WorldScript Studio) -- **Component + Hook + Context:** Rendering in `components/*View.tsx`, Logik in `hooks/use*View.ts`, Kontext für Kindbaum. +- **Component + Hook + Context:** rendering in `components/*View.tsx`, logic in `hooks/use*View.ts`, context for child trees. - **Redux** für persistierten App-State; **Zustand** nur transient (`app/transientUiStore.ts`). - **Undo:** `redux-undo` am Projekt-Slice; Plot-Board-**Viewport** lokal — Verbindungen/Subplots im `projectSlice` wenn undo-relevant. - Schwere Views: `React.lazy` in `App.tsx`; Plot-Board-Subchunks, ForceGraph, Collaboration lazy; `listenerMiddleware` + `aiApi` dynamic import für DuckDB/RAG/Provider. -- **ProForge Pipeline** (`services/proForge/`, `features/proForge/`, `components/proForge/`): 8-stage Agentic-Pipeline hinter `enableProForge`-Flag. Orchestrator nie direkt in Komponenten instantiieren — über `hooks/useProForgeOrchestrator.ts`. -- **Voice** (`services/voice/`): Abstract-Engine-Pattern; `VoiceCommandService` als Singleton hinter `enableVoiceSupport`-Flag. +- ProForge orchestration stays behind `hooks/useProForgeOrchestrator.ts`; do not instantiate it in components. +- Voice uses the existing engine abstractions and `VoiceCommandService`; honor its feature/settings gates. ## Dateigrößen @@ -32,9 +32,8 @@ alwaysApply: false ## Feature Flags -- Experimentelles über `features/featureFlags/featureFlagsSlice.ts` (19 Flags) — nicht verstreute `if (true)`-Hacks. -- Standard **ein**: `enableCodexAutoTracking`, `enableCrossProjectSearch`, `enablePlotBoardV2`. Alle anderen: aus. -- Major-Features (Voice, ProForge, DuckDB, LoRA, CloudSync, PluginSystem) müssen per Flag ein-/ausgeschaltet werden. +- Experimental behavior belongs in `features/featureFlags/featureFlagsSlice.ts`; do not use scattered `if (true)` hacks. +- Read current flag defaults from that slice; do not duplicate a stale flag count or default table in rules. Manuskript >700 Zeilen: `ManuscriptInspector.tsx` + `hooks/useManuscript.ts`; Plot-Board-Canvas eigene Datei unter `components/plotBoard/`. diff --git a/.cursor/rules/300-ui-components.mdc b/.cursor/rules/300-ui-components.mdc index a8b6bc8e2..07cf55356 100644 --- a/.cursor/rules/300-ui-components.mdc +++ b/.cursor/rules/300-ui-components.mdc @@ -1,6 +1,6 @@ --- description: Enforce accessible,i18n-ready React UI in shared components -globs: components/**/*.tsx +globs: components/**/*.tsx,hooks/**/*.ts,packages/ui/**/*.ts,packages/ui/**/*.tsx,index.css,stories/**/* alwaysApply: false --- @@ -32,14 +32,15 @@ alwaysApply: false - Palette: keine zweite Open-State-Quelle neben `transientUiStore`. - Tooltips/EmptyState aus `components/ui/`; Toasts mit `commandId` wo passend. -## Styling & Hosts +## Styling and platform boundary - Tailwind; Inline nur für gemessene Werte. -- **Keine** `@tauri-apps/api` in UI-Atoms — Services/Hooks. +- No `dark:` Tailwind classes; use body themes and semantic CSS tokens. +- No `@tauri-apps/api` in UI atoms; use services/hooks and the platform abstraction. ## Storybook -- Neue Primitive: Story + **addon-a11y** prüfen (`pnpm run storybook`). +- Neue Primitive need a story and **addon-a11y** coverage; the full Storybook runner is CI work. Neues Panel: `Modal` + i18n-Titel; Fokus zurück auf Trigger; Tastatur-Reihenfolge in Scene Board wie `moveManuscriptSectionWithinAct`. diff --git a/.cursor/rules/800-testing-standards.mdc b/.cursor/rules/800-testing-standards.mdc index 7cf55421a..b0a4df45e 100644 --- a/.cursor/rules/800-testing-standards.mdc +++ b/.cursor/rules/800-testing-standards.mdc @@ -1,6 +1,6 @@ --- description: Require deterministic Vitest mocks and CI-first heavy suites per docs/CI.md -globs: tests/**/*.test.ts,tests/**/*.test.tsx,tests/**/*.spec.ts,tests/**/*.spec.tsx,components/**/*.test.ts,components/**/*.test.tsx +globs: tests/**/*,components/**/*.test.ts,components/**/*.test.tsx,vitest.config.ts,playwright.config.ts alwaysApply: false --- @@ -30,19 +30,19 @@ alwaysApply: false ## Spezialfälle -- `sceneRevisionService` / reine IDB: `@vitest-environment node` + `IDBFactory` + `_resetDbForTest()` (siehe `CLAUDE.md`). +- `sceneRevisionService` / reine IDB: `@vitest-environment node` + `IDBFactory` + `_resetDbForTest()` (see `tests/CLAUDE.md`). - Risiko-Hotspots bei Änderungen testen: `dbService`, `aiProviderService`, Import/Export, `storageBackend`. ## CI-first (kanonisch: [`docs/CI.md`](../../docs/CI.md)) | Tier | Befehle | |------|---------| -| **Lokal schnell** | `lint`, `typecheck`, `i18n:check`; optional `pnpm exec vitest run` **ohne** `--coverage` | -| **CI schwer** | Vitest **mit** Coverage (Schwellen 63/55/54/62 — lines/branches/functions/statements), `CI=true pnpm run test:e2e`, LHCI, `bundle:budget` | +| **Lokal schnell** | `pnpm run ci:prepush`; optional focused `pnpm exec vitest run ` **ohne** `--coverage` | +| **CI schwer** | Vitest **mit** Coverage (thresholds 74/60/67/72 — lines/branches/functions/statements), `CI=true pnpm run test:e2e`, LHCI, `bundle:budget` | - Merge-Bar = **grüner GitHub-Workflow**, nicht voller lokaler E2E auf schwacher Hardware. -- Mobile E2E lokal nur mit `RUN_MOBILE_E2E=1` (siehe `playwright.config.ts`). -- **Stryker:** informativ (`continue-on-error`); Score-Gate nicht umgehen durch Test-Löschung. +- Playwright E2E is CI-only on this constrained workstation; use `playwright.config.ts` for CI matrix details. +- **Stryker:** CI/manual only; never lower a score gate or delete tests to make it green. `aiProviderService`: `vi.mock` für SDK; feste Antwort; bei IDB-Test `node`-Environment + Factory-Reset. diff --git a/.cursor/rules/850-mcp-and-prd.mdc b/.cursor/rules/850-mcp-and-prd.mdc index 07700c834..cd9c945eb 100644 --- a/.cursor/rules/850-mcp-and-prd.mdc +++ b/.cursor/rules/850-mcp-and-prd.mdc @@ -4,7 +4,7 @@ globs: ~ alwaysApply: false --- -# Produktkontext, Doku-Hub & MCP +# Product context, documentation, and MCP ## Vor großen Änderungen lesen @@ -40,7 +40,8 @@ Server-Namen und Tokens **nur** in lokaler Cursor-Konfiguration — nie ins Repo ## Agent-Parität -- [`CLAUDE.md`](../../CLAUDE.md), [`.github/copilot-instructions.md`](../../.github/copilot-instructions.md) und **diese `.mdc`-Regeln** sollten bei Stack-Updates zusammen gehalten werden. +- `AGENTS.md` is the canonical root source; keep this scoped rule and tool adapters as pointers, + not duplicated architecture or workflow encyclopedias. Neues Plot-Board-Feature: ROADMAP-Eintrag prüfen; BEST-PRACTICES Glossar; nach Merge CI-Metriken in README/AUDIT (nur wenn Maintainer es verlangt). diff --git a/.cursorrules b/.cursorrules index 2be17c7c6..dc88afef4 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,74 +1,5 @@ -# QNBS Master System Prompt v3.0 — Cursor (qnbs Creative AI Architect) +# Legacy Cursor compatibility adapter -Du bist der **qnbs Creative AI Architect** — der intelligente, flexible Begleiter für alle qnbs-Repositories (u. a. StoryCraft-Studio, Nexus-HEMS-Dash, AI-Research-Orchestrator). - ---- - -## Kernaufgabe - -Analysiere **jede Datei, jedes Feature und jedes Repo individuell und ganzheitlich** im Kontext seiner eigenen Domain, Ziele, bestehenden Architektur und Nutzerbedürfnisse. Keine „One-Size-Fits-all“-Refactors über Repo-Grenzen hinweg. - ---- - -## Verbindliche Prinzipien - -- **Bestehendes respektieren:** Nutze den Tech-Stack, die Ordnerlogik und etablierte Patterns **dieses** Repos. Ändere nichts nur der vermeintlichen Einheitlichkeit wegen. -- **Kreativ & eigenständig:** Schlage dort, wo es passt, innovative Ideen vor, die den Charakter der App schärfen und echte „Wow-Momente“ ermöglichen — ohne die Produktvision zu verwässern. -- **Kontext-sensitiv, nicht schematisch:** Jede App hat eine eigene „Seele“. Beispiele: kreatives Storytelling & Manuskript-Flow (StoryCraft-Studio) vs. präzise Energie-Orchestrierung (Nexus-HEMS-Dash) vs. strukturierte Forschungs-Pipelines (AI-Research-Orchestrator). -- **Perfektion relativ zur App:** Audit, Korrektur, Optimierung und Erweiterung sind **maßgeschneidert** — nicht maximale Komplexität um ihrer selbst willen. -- **Analyse vor Änderung:** Erst **`README`**, Ordnerstruktur, zentrale `services/`, `features/`, `components/`, Datenmodelle (`types.ts` o. Ä.), KI-Anbindungen und Tests skizzieren oder verifizieren; dann gezielt eingreifen. -- **Technisch präzise, bei Bugs konsequent, kreativ inspirierend:** Klare Diagnosen, ehrliche Bewertung echter Probleme, konstruktive kreative Impulse wo sie Mehrwert haben. - ---- - -## WorldScript-Studio (dieses Repo, ehem. StoryCraft-Studio) — Kurzkontext - -Offline-first, **Privacy**, Redux Toolkit + IndexedDB + Zustand (transient UI), Multi-Provider-KI (Gemini, Vercel AI SDK, OpenAI/Claude/Grok/OpenRouter, Ollama/WebLLM/ONNX/Transformers.js), pnpm-Workspace (`packages/*`), PWA + Tauri 2, 19-Locale-i18n. Fokus: **Erzählung**, Schreibflow, KI als Co-Pilot — nicht als Ghostwriter. Vollständiger Stack und Regelkatalog: **`.cursor/index.mdc`**. - ---- - -## Kommentar-Konvention bei Code-Änderungen (QNBS-v3) - -Bei **jeder inhaltlich relevanten** Code-Änderung (nicht bei reiner Formatierung, Lockfiles oder rein generierten Artefakten) einen kurzen Vermerk setzen — **sprachtypisch**: - -| Kontext | Syntax | -|--------|--------| -| TS / JS | `// QNBS-v3: [Grund / Impact / Kreativer Mehrwert]` | -| TSX / JSX | Präferenz: eine Zeile `// QNBS-v3: …` unmittelbar **über** der Änderung; in JSX nur wenn nötig `{/* QNBS-v3: … */}` | -| CSS | `/* QNBS-v3: … */` | -| Reine Config (JSON, YAML, `.env.example`) | Kein erzwungener Inline-Kommentar; stattdessen in Commit-Beschreibung oder Antwort ausformulieren | - -Inhalt des Kommentars: **warum** die Änderung, **was** sie verbessert, optional **kreativer** oder UX-Mehrwert — maximal eine Zeile, kein Romankommentar. - ---- - -## PR-Review: CodeRabbit Correction Loop (proaktiv, jede PR) - -Bei jeder offenen PR **alle drei Kommentar-Kanäle unabhängig** prüfen (CodeRabbit + jeder andere Bot/Mensch) **proaktiv, ungefragt**: (1) Inline-Review-Threads (GraphQL `reviewThreads`, einzig auflösbarer Kanal via `resolveReviewThread`), (2) Top-Level-Issue-Kommentare (`gh api .../issues//comments`, kein Resolve-Mechanismus — nur beantworten/dispositionieren), (3) volle Review-Body-Texte (`gh api .../pulls//reviews`, `.body`, ebenfalls kein Resolve-Mechanismus). Pro Finding: gegen den **aktuellen** Code validieren (Anker oft veraltet), dann **Root-Cause-Fix** vollständig umsetzen (Code **+ Tests + i18n + Doku**) oder mit Begründung als False-Positive ablehnen. Threads mit Commit-Referenz beantworten und **resolven** (`UNRESOLVED_REVIEW_THREADS = 0`); Issue-Kommentare/Review-Body-Findings beantworten und bestätigen, dass nichts Aktionables offenbleibt (kein `resolveReviewThread` darauf anwenden — dafür gibt es dort keinen Thread). Committen + pushen. - -**Iron Rule — Loop bis Ruhe:** Ein Push löst normalerweise automatisch eine inkrementelle CodeRabbit-Review aus — komplette Review-Historie prüfen, nicht nur den letzten Status (ein „rate limited"-Status kann eine frühere echte Review verdecken). **Manuell erneut triggern** (`gh pr comment --body "@coderabbitai review"`) nur, wenn die automatische Review pausiert/nicht anwendbar ist — CodeRabbit reviewt bereits konsumierte Commits nicht erneut, daher niemals als routinemäßiges „Frische"-Ritual nach jedem Push posten. Zyklus wiederholen, bis alle drei Kanal-Zustände oben gelten und entweder eine Review des aktuellen Deltas **0 neue** Findings ergibt, oder das Delta laut `docs/PR-CI-MERGE-WORKFLOW.md`'s Low-Risk-Kriterien qualifiziert (`NO_NEW_INCREMENTAL_DIFF` bei bereits konsumiertem Delta ist ein legitimer Endzustand, kein Grund endlos zu warten). **Niemals ein neues `biome-ignore`/Suppression** einbauen, um ein Finding stummzuschalten — der Suppression-Ratchet-Gate lässt die CI scheitern; stattdessen sauber refaktorieren. **Reviewer-Fähigkeiten/Kanäle werden pro PR live beobachtet, nie aus einer statischen Rolle angenommen:** CodeAnt AI kann 5 CI-Status-Checks (Quality Gates/SAST/SCA/SCR/Test Coverage) zeigen **und** kann auch einen echten Inline-Review-Thread posten (bestätigt 2026-08-28) — tatsächliche aktuelle Ausgabe prüfen statt aus der Rolle anzunehmen; postet er einen Thread, genauso behandeln wie jeden anderen. Lokale Qualität (Low-End-Hardware): `pnpm run ci:prepush` ist das erforderliche Gate vor jedem Push; volles `lint`/`typecheck`/`i18n:check` ist CI-eigen, nicht pro Wave verpflichtend. Übergeordnete Lifecycle-Autorität (Merge-Mechanik, Risikoklassifizierung, Auto-Merge-Reihenfolge, Drei-Kanal-Definitionen): **`docs/PR-CI-MERGE-WORKFLOW.md`**. Reconciliation-Sub-Prozedur (inkl. des komplementären, token-freien DeepSource-Loops): **`docs/CODEANT-REVIEW-LOOP.md`** + **`docs/DEEPSOURCE-REVIEW-LOOP.md`**. - ---- - -## Antwortformat nach substanziellen Vorschlägen oder Edits - -Kurz und konkret: - -1. **Vorher → Nachher** (1–2 Sätze: Qualität, Risiko, Wartbarkeit oder UX). -2. **Passung zur App** (1 Satz: warum das hier passt — Domain StoryCraft vs. anderes qnbs-Repo). - ---- - -## Cursor & Zusammenarbeit mit Nutzerregeln - -- Wenn der Nutzer **Deutsch** wünscht: Antworten auf **Deutsch**; Code- und API-Bezeichner unverändert nach Repo-Konvention. -- Zusätzliche Repo-Leitplanken: **`.cursor/index.mdc`** (Manifest, `alwaysApply`) und **`.cursor/rules/*.mdc`** (001 Security, 100 KI/Storage, 150 i18n/Content, 200 Architektur, 300 UI, 800 Testing, 850 Doku/MCP). Nach Code-Änderungen optional **`pnpm run graphify:update`** bzw. `graphify:bootstrap`. -- Qualität lokal (schnell, Low-End-Hardware): `pnpm run ci:prepush` ist das erforderliche lokale Gate vor jedem Push; volles `lint`/`typecheck`/`i18n:check` ist CI-eigen, nicht pro Wave verpflichtend (nur bei leistungsfähigerer Hardware zusätzlich lokal sinnvoll). Schwere Suites CI-first — siehe **`docs/CI.md`** und Regel **`800-testing-standards.mdc`**. -- Bestehende Projekt-Tools (z. B. **Biome**, **Vitest**, **Playwright**, **Vite**) vor neuen Hilfsmitteln bevorzugen. -- Keine unnötigen neuen Markdown-Dateien; Ausnahme: ausdrücklich gewünschte Doku (z. B. diese Rules). - ---- - -## Erinnerung - -Du bist **präzise** bei technischen Verbesserungen, **gnadenlos ehrlich** bei echten Problemen und **maximal inspirierend** bei kreativen Möglichkeiten — immer im Rahmen dessen, was für **diese** Codebasis verantwortlich ist. +Use `AGENTS.md` as the canonical repository guidance. Use the relevant files in +`.cursor/rules/` for path-scoped details; do not apply a cross-repository persona, historical +product manifest, broad exploration ritual, or duplicate review procedure. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index ba0e7a0ae..df3119d4c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,259 +1,12 @@ -# Copilot Instructions — WorldScript Studio +# GitHub Copilot repository adapter -## Project Overview +Use `AGENTS.md` as the canonical WorldScript Studio guidance. Load the nearest nested +`CLAUDE.md` or matching `.cursor/rules/*.mdc` only for the paths being changed. -WorldScript Studio is an AI-powered creative writing application built as an offline-first PWA. It combines a React 19 SPA with Google Gemini AI integration, IndexedDB persistence, and optional Tauri desktop packaging. +For PR, CI, review, merge, signing, or Vercel work, follow the procedures linked by +`AGENTS.md`: `docs/PR-CI-MERGE-WORKFLOW.md` and +`docs/VERCEL-PREVIEW-RETENTION-POLICY.md`. -**Live:** `https://qnbs.github.io/WorldScript-Studio/` - -**Documentation map:** [`README.md`](../README.md#-documentation-hub) § Documentation Hub lists every maintainer `.md` guide (see also [`AUDIT.md`](../AUDIT.md)). - -## Architecture - -### Tech Stack - -- **Frontend:** React 19 + TypeScript (strict mode), Vite 8 -- **State:** Redux Toolkit 2.x + Redux-Undo, feature-sliced design -- **Styling:** Tailwind CSS 4.x with CSS custom properties for theming -- **AI:** Google Gemini API via `@google/genai`, multi-provider abstraction (`aiProviderService.ts`) -- **Storage:** Dual IndexedDB via `services/storage/` (decomposed from `dbService.ts` in Phase 1); LZ-String compression + AES-256-GCM key encryption; `storageEncryptionService.ts` for at-rest IDB encryption (B-1, v1.19.0); `storageService.ts` switches browser vs Tauri filesystem -- **Collaboration:** Yjs + `packages/collab-transport` (vendor fork of y-webrtc 10.3.0, RTCDataChannel E2E AES-256-GCM) for P2P real-time editing -- **Desktop:** Tauri 2 (optional) -- **Package manager:** pnpm@11.x -- **Testing:** Vitest + @testing-library/react (unit), Playwright (E2E) - -### Directory Structure - -```text -app/ → Redux store, hooks (useAppDispatch/useAppSelector), listener middleware, utils -components/ → React view components (one per view) - ui/ → Reusable design system primitives (Button, Modal, Card, Toast, etc.) -contexts/ → React context providers (one per major view + I18nContext + CommandExecutorContext) -features/ → Redux Toolkit slices: project, settings, status, writer, versionControl, featureFlags -hooks/ → Custom hooks with view business logic (one hook per view) -services/ → External adapters: geminiService, aiProviderService, dbService (dual IndexedDB + migration), storageService, collaborationService; **ai/** (aiModeService — execution modes, aiPolicy, aiRetry; **providers/** — openrouterProvider with circuit breaker); **copilot/** (heuristicEngine 8 rules, insightGenerator, copilotContextService, actionApplier); **commands/** (palette registry); **keyboard/** (shortcut matching); **help/** (doc retrieval for AI); **settingsExchange** (settings JSON) -locales/ → i18n source files — de/en/es/fr/it (core) + ar/he/fa (RTL Beta) + el/ja/pt/zh/fi/sv/hu/is/eu/ru/ko (Beta) × 21 JSON modules (19 locales; see the README badge for the live key count) -public/locales/ → i18n runtime files served at BASE_URL -tests/ → Unit + E2E tests (Vitest + Playwright) -types/ → Additional TypeScript type definitions -types.ts → Core shared interfaces and types -``` - -### Key Patterns - -1. **View = Component + Hook + Context:** Each major view (e.g., Dashboard) has: - - `components/Dashboard.tsx` — Pure rendering - - `hooks/useDashboard.ts` — Business logic, Redux selectors, thunk dispatches - - `contexts/DashboardContext.ts` — React context to pass hook return to child components - -2. **Redux:** All state mutations go through Redux slices. Async operations use `createAsyncThunk`. Side effects (auto-save) run in the listener middleware. The `project` slice is wrapped with `redux-undo` for undo/redo. - - `features/project/aiThunkUtils.ts` provides a reusable deduplicated async-thunk wrapper for AI requests. - -3. **AI Service:** `services/ai/index.ts` is the canonical entry (Vercel AI SDK layer). `geminiService.ts` is the primary legacy adapter. `aiProviderService.ts` provides the multi-provider abstraction (Gemini, OpenAI, OpenRouter, Claude, Grok, Ollama, WebLLM, ONNX, Transformers.js). **AI Execution Modes** (`aiModeService.ts`): `hybrid` | `cloud` | `local` | `eco` — control routing strategy, persisted to `settings.aiMode`. **OpenRouter** (`services/ai/providers/openrouterProvider.ts`): Cloud 5 in the routing chain, circuit breaker (4×429 → 5 min pause), free-tier catalog (`:free` suffix models). All cloud AI calls gated by `assertCloudAiAllowed` from `aiPolicy.ts`; retries via `withTransientRetry` in `aiRetry.ts`. - -4. **Storage:** `dbService.ts` wraps **dual** IndexedDB (state vs data stores, legacy migration) with compression (LZ-String for payloads > 10KB) and encryption (AES-256-GCM for API keys). `storageService.ts` provides a unified interface that auto-detects IndexedDB vs Tauri filesystem. - -5. **i18n:** Custom React Context system in `I18nContext.tsx`. Translation keys use dot notation (`common.save`, `dashboard.wordCount`). All user-facing strings MUST be translation keys, never hardcoded text. - -6. **Code Splitting:** All views are lazy-loaded in `App.tsx` via `React.lazy()`. Heavy dependencies (Konva, Leaflet, react-force-graph) are in separate Vite manual chunks. The export stack also uses dynamic imports for `docx` and `jszip` so large document libraries are only loaded when export actions are executed. - -7. **Command Center:** Palette commands live in **`services/commands/`** (i18n keys, fuzzy search, recent/pinned). **`CommandExecutorProvider`** exposes execution for Help „Try it” (`tryActionId`) and toasts with **`commandId`**. **`useGlobalKeyboardShortcuts`** reads Redux shortcut bindings; **`app/transientUiStore`** toggles palette visibility. - -8. **ProForge Pipeline:** 8-stage agentic manuscript editing pipeline gated behind `featureFlags.enableProForge` (on by default). Stage sequence: `intake` → `structural` → `lineProse` → `copyEdit` → `proof` → `production` → `publishing` → `analytics`. Manuscripts are **never auto-modified** — each stage pauses at `awaitingReview`. Orchestrator: `services/proForge/proForgeOrchestrator.ts`; Redux slice: `features/proForge/proForgeSlice.ts`; UI: `components/proForge/` (ProForgeDashboard, PipelineProgressPanel, PipelineReviewPanel); docs: `docs/PROFORGE-PIPELINE.md`. - -9. **Voice Full Support:** Gated behind `featureFlags.enableVoiceSupport` + `settings.voice.enabled`. Abstract engine pattern in `services/voice/voiceTypes.ts` (SttEngine, TtsEngine, VadEngine, WakeWordEngine, IntentEngine). `VoiceCommandService` singleton manages state machine (idle → listening → processing → speaking). Web Speech API fallbacks require zero downloads. Hooks: `useVoice`, `usePushToTalk` (Ctrl+Shift+V), `useVoiceDictation`. - -10. **Feature Flags:** **23 flags** in `features/featureFlags/featureFlagsSlice.ts`. New installs get the **full feature set** — all default **on** except seven opt-in flags that default **off**: `enableRtlLayout`, `enableVoiceSupport`, `enableProForge`, `enableVoiceWasm`, `enableGlobalCopilot`, `enableLocalFirstSync`, `enableBrowserOllama`. (`enableCodexAutoTracking` + `enableCrossProjectSearch` were promoted to permanent core; `enablePlotBoardV2` + `enableCloudSync` were retired — none remain in the slice.) See `docs/FEATURE-PARITY.md`. Do not use scattered `if (true)` hacks — all experimental features must go through a flag. - -11. **Global AI Copilot (v2):** `enableGlobalCopilot` flag. `CopilotPanel` (dialog/sidebar mode), `CopilotMessageList` (markdown rendering via DOMPurify), `InlineAnnotationLayer` (badge in ManuscriptEditor). Heuristic rules: `services/copilot/heuristicEngine.ts` (8 rules). Apply-to-chapter: `services/copilot/actionApplier.ts` (offset-safe edit, redux-undo, ≥70% length gate). ProForge integration: Ask-Copilot chip on each `ReviewItemCard`. Docs: `docs/COPILOT.md`, `docs/HEURISTIC-RULES.md`. - -## Coding Standards - -### TypeScript - -- `strict: true` is enforced globally — do NOT add `any` types -- `exactOptionalPropertyTypes: true` — use `undefined` explicitly for optional props -- Use typed Redux hooks: `useAppDispatch()`, `useAppSelector()`, `useAppSelectorShallow()` -- Prefer `interface` for component props, `type` for unions and utility types - -### React - -- Functional components only, use `React.memo()` for expensive renders -- Props forwarding with `React.forwardRef()` for UI primitives -- Hooks must follow the `use*View` naming convention for view logic hooks -- Always clean up event listeners, timeouts, and subscriptions in `useEffect` return - -### Accessibility (WCAG 2.2 AA-oriented) - -- See [`docs/ACCESSIBILITY.md`](../docs/ACCESSIBILITY.md) for architecture (`LiveRegionProvider`, focus traps, CI gates). -- All interactive elements need proper `role`, `aria-label`, `aria-expanded`, etc. -- Modals must trap focus and restore focus on close -- Icons must have `aria-hidden="true"` when decorative -- Use `focus-visible:ring-2` for keyboard focus styles -- Dynamic content updates need `aria-live` regions - -### Security - -- NEVER log, console.log, or expose API keys -- API keys are encrypted with AES-256-GCM before IndexedDB storage -- Never store sensitive data in localStorage (use IndexedDB with encryption) -- Sanitize any user input before rendering (XSS prevention) -- AI API responses are text-only — never execute or `eval()` them -- Gemini API calls must use `NetworkOnly` caching strategy (never cache AI responses) - -#### CI/CD Security Hardening (QNBS-v3) - -- **Token-Permissions**: All workflow files MUST have `permissions: contents: read` at top-level. Write permissions belong at job-level only. -- **Pinned-Dependencies**: All GitHub Actions MUST be pinned to SHA hashes (e.g., `actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10`). Branch/tags are only acceptable when upstream does not provide SHA tags. -- **Proactive Security Remediation**: On every PR and commit, treat ALL security alerts (OpenSSF Scorecard, CodeQL, Dependabot, Renovate, CodeAnt AI) as actionable work to be addressed immediately — never defer. Validate against current code, implement root-cause fixes, and verify via CI. - -### Pull Request Workflow (QNBS-v3) - -- **Branch-based development**: All changes MUST be made on feature branches (e.g., `fix/security-vulnerabilities-2026-06-06`). Never commit directly to `main`. -- **CI verification**: Push to branch and wait for ALL CI jobs (security, quality, build, e2e, lighthouse) to pass before merging. -- **PR merge**: Only merge to `main` when CI is fully green. Use "Squash and merge" for clean history. -- **Inline comment handling — the correction loop (proactive, automatic, every PR):** - Address ALL inline review comments (CodeRabbit + any other bot/human) **without being asked**. - Overall lifecycle authority: [`docs/PR-CI-MERGE-WORKFLOW.md`](../docs/PR-CI-MERGE-WORKFLOW.md); - reconciliation-mechanics sub-procedure: [`docs/CODEANT-REVIEW-LOOP.md`](../docs/CODEANT-REVIEW-LOOP.md). - Each pass checks all three review channels independently (see `PR-CI-MERGE-WORKFLOW.md` for the - full three-channel definitions — only inline threads have a resolve mutation): - 1. Fetch all three channels **exhaustively** (a single unpaginated page can hide a later finding - on any of them) using `PR-CI-MERGE-WORKFLOW.md`'s canonical paginated recipes: GraphQL - `reviewThreads` via `gh api graphql --paginate` (cursor pagination, `isResolved:false`) plus the - flat paginated `.../pulls//comments` REST endpoint for full thread content/replies; top-level - issue comments via `gh api --paginate -X GET .../issues//comments`; full review bodies via - `gh api --paginate -X GET .../pulls//reviews` — a bot can use any of the three. - 2. Validate each finding against the **current** code (anchors may be stale). - 3. Implement the real **root-cause** fix (code **+ tests + i18n + docs**), or reply with evidence - if false-positive / by-design. **Never** add a new `biome-ignore` (suppression ratchet fails - CI — refactor instead; run `node scripts/check-suppressions.mjs`). - 4. Local gate (sequential): `pnpm run ci:prepush` (required constrained-hardware gate); run - targeted vitest only for files with relevant changed tests — full `lint`/`typecheck`/ - `i18n:check` are CI-owned, not mandatory per wave. - 5. Commit + push. For inline threads: reply citing the resolving commit, then `resolveReviewThread` - → `UNRESOLVED_REVIEW_THREADS = 0`. For top-level comments and review-body findings (no resolve - mutation exists for either): reply where useful and confirm none remain actionable → - `NO_ACTIONABLE_TOP_LEVEL_ISSUE_COMMENTS = YES` / `NO_ACTIONABLE_REVIEW_BODY_FINDINGS = YES`. - 6. A push normally **auto-triggers** CodeRabbit's incremental review. Check the **full** review - history, not just the latest status. Post `gh pr comment --body "@coderabbitai review"` - only when that history shows automatic review is paused/inapplicable — it does not re-review - commits it already consumed, so don't post it as a routine "freshness" ritual. - - **Iron rule — loop until quiescent:** repeat until all three channel states above hold and - either a review of the current delta yields zero new actionable findings, or the delta qualifies - as LOW-RISK under `PR-CI-MERGE-WORKFLOW.md`'s precise condition list. `NO_NEW_INCREMENTAL_DIFF` - on an already-consumed delta is a legitimate terminal state, not something to wait out forever. - - Reviewer capability/channels are **observed live per PR, never assumed from a static roster**. - CodeAnt AI may expose 5 CI **status checks** (`CodeAnt - Quality Gates/SAST/SCA/SCR/Test Coverage`) - and may also post a genuine inline review thread (confirmed 2026-08-28) — inspect its actual - current output; if it creates a resolvable thread, reconcile it exactly like any other reviewer's. - -### Test Stability Guidelines (QNBS-v3) - -- **ICU-dependent APIs**: Tests using `Intl.Segmenter`, `Intl.PluralRules`, or other ICU-dependent APIs MUST use relaxed assertions (non-zero counts, monotonic behavior, locale invariants) instead of exact counts to ensure cross-environment stability. -- **Environment variance**: Node.js ICU versions and browser implementations can differ; tests should verify behavior, not exact output. - -### Code Comment Convention & Recurring Review-Loop Findings (QNBS-v3) - -On any non-trivial code change add a single-line comment explaining **why**, not what: `// QNBS-v3: ` (TS/JS), `{/* QNBS-v3: … */}` (JSX, only when needed), `/* QNBS-v3: … */` (CSS). No inline comment in pure JSON/YAML config — explain in the commit message instead. - -**Hard rule — never wrap:** the comment MUST fit on a single physical line, however long. Never split it across two `//` lines. This exact mistake has recurred 3× in one PR and is a guaranteed CodeRabbit nitpick — shorten the wording instead of wrapping it. - -**Other recurring findings, codified so they stop recurring:** -- Name DOM elements created for download/print descriptively (`anchor`, not `a`). -- Never mutate `ref.current` during render — sync via `useEffect(() => { ref.current = value }, [value])`, never as a bare statement in the component body. -- Prefer `@testing-library/user-event` over `fireEvent` for click/type/change interactions in tests. -- Prefer a lookup table (`Partial>`) over long `if/else if` dispatch chains to keep cyclomatic complexity low, especially inside `useCallback`. - -### Testing - -- Unit tests: Vitest + @testing-library/react in `tests/unit/` (see `tests/setup.ts`) -- E2E tests: Playwright in **`tests/e2e/*.spec.ts`** — **`CI=true`** is required (`pnpm run test:e2e`). Shared waits/bootstrap live in **`tests/e2e/helpers.ts`**; do **not** use `networkidle` against the Vite dev server (HMR keeps sockets open). Scope sidebar navigation via **`#sidebar`** when both mobile and desktop nav exist. -- Test file naming: `ComponentName.test.tsx` or `serviceName.test.ts` -- Mock external services (Gemini API, IndexedDB) in unit tests -- Verify accessibility: assert `role`, `aria-*` attributes in component tests - -### i18n - -- All user-facing strings must use `t('key.path')` from `useTranslation()` -- Source files: `locales/{lang}/{module}.json` (15 modules). Runtime: **one** merged **`public/locales/{lang}/bundle.json`** per language — regenerate with **`pnpm run i18n:bundle`** or **`pnpm run i18n:check`** (parity check **and** bundle build); **`predev`** / **`prebuild`** also rebuild bundles so the UI never shows raw keys after editing locale JSON. -- **19 locales ship** (de/en/es/fr/it core + ar/he/fa RTL + el/ja/pt/zh/fi/sv/hu/is/eu/ru/ko Beta); all must keep key parity with English (`pnpm run i18n:check` in CI). The `/i18n-key` skill auto-fills the **5 core** (de/en/es/fr/it); update Beta/RTL locales manually afterward. -- English is the fallback language -- New keys: add to **`locales/en/`** first, then **de**, **fr**, **es**, **it** (or run `node scripts/check-i18n-keys.mjs --fix` and translate), then commit updated **`bundle.json`** files - -### Git & CI - -- Conventional Commits format: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:` -- Pre-commit: after explicit `pnpm run hooks:install`, `simple-git-hooks` runs Biome on staged files; CI is mandatory regardless -- **⚠️ Constrained local hardware — do NOT run heavy suites locally.** This machine has ~3–4 GB RAM. **Never** run the full Vitest **coverage** suite, **Playwright E2E**, **Stryker mutation**, **Lighthouse CI**, or the **Storybook test-runner** locally — they are **CI-only by design**. Run **one heavy command at a time** (no parallel `vitest`/`biome`/`tsc`/`vite`). -- Local preflight (sequential, minimal): `pnpm run ci:prepush` is the required constrained-hardware gate before every push; run **targeted** `pnpm exec vitest run ` (no `--coverage`) only when the changed files have a relevant test surface — docs/workflow/tooling/Rust/config-only changes typically have none, so there's no placeholder `` to invoke and none is required. Full `pnpm run lint` / `pnpm run typecheck` / `pnpm run i18n:check` are **CI-owned**, not a mandatory local step per push — only run them locally too if your hardware is capable enough to spare the extra minutes. Run `pnpm run build && pnpm run smoke:prod` only when you touched `vite.config.ts`, `packages/ai-core`, or `workers/`. Coverage, E2E, Lighthouse, Stryker, and Storybook are **CI gate jobs** — let GitHub Actions run them. -- **Vitest watch-mode hard rule:** Never run `pnpm test`, `npm run test`, a bare Vitest command, or an untargeted wrapper. Always use `pnpm exec vitest run `; CI is the only place that runs the full coverage suite. -- CI pipeline (see [`docs/CI.md`](../docs/CI.md)): **`security` → `quality`** (Biome + `tsc` + Vitest matrix) **→ `build` / `e2e` / `storybook` in parallel** → **`lighthouse`** after build → **`deploy`** on `main` after build+e2e -- Branch protection should require the **`quality`** job (and other checks your team enables); job ids match `.github/workflows/ci.yml` -- CI runs **`pnpm audit`** every workflow; **dependency-review** on pull requests -- CI installs dependencies with `pnpm install --frozen-lockfile` -- Local CI can be simulated with `act` (requires Docker), e.g. `act pull_request --job security --job quality` -- Local developers should use `node scripts/dependency-state.mjs reconcile` (never a bare `pnpm install`) to install dependencies — the bare form skips `--frozen-lockfile` and the repo's dependency-fingerprint check -- Most repo-facing markdown is English for accessibility; user-facing app strings remain fully i18n-driven - -## Known Technical Debt - -See `AUDIT.md` and `TODO.md`. Key items: - -- **`StorageBackend` parity** — tighten typings across `dbService` / `fileSystemService` / `storageService` -- `components/AdvancedImportExport.tsx` — some export paths remain Tauri-centric; keep browser fallbacks explicit -- `app/listenerMiddleware.ts` — occasional TypeScript friction with redux-undo `StateWithHistory` -- `workers/inference.worker.ts:50` — `@ts-expect-error` on `@xenova/transformers` dynamic import (Vite resolves at build, `tsc` cannot) -- **DS-5:** Delete legacy bridge block from `index.css` — deferred until DS-1 token migration verified in production -- **v2.0 stubs behind feature flags:** RTL layout (`enableRtlLayout`), LoRA adapter inference (`enableLoraAdapters`), Plugin system loader (`enablePluginSystem`). (The Cloud-Sync R2 adapter's `enableCloudSync` flag was **retired** in v1.20 — activation is now `CloudSyncBackend.create(..., explicitConsent)`.) -- RTCDataChannel in-flight E2E encryption is **shipped** (y-webrtc patch v1.17.0) — no longer open - -## Commands - -```bash -pnpm run dev # Start dev server on port 3000 -pnpm run build # Production build to dist/ -pnpm run preview # Preview production build locally -pnpm run lint # Biome lint check -pnpm run lint:fix # Biome auto-fix (lint + format) -pnpm run format # Biome format -pnpm run typecheck # TypeScript type checking (tsc --noEmit) -pnpm exec vitest run # Targeted Vitest single run -pnpm exec vitest run --coverage \ - --coverage.thresholds.lines=0 --coverage.thresholds.functions=0 \ - --coverage.thresholds.branches=0 --coverage.thresholds.statements=0 # Targeted coverage debugging -pnpm run test:e2e # Playwright E2E (requires CI=true per package.json scripts) -pnpm run storybook # Storybook on port 6006 -``` - -## Storage Health - -`services/dbInitialization.ts` exports `checkStorageHealth()` — proactive low-storage warning that runs on app init and surfaces a toast. Returns `StorageHealth`; does not block writes. - -## Collaboration - -Real-time P2P via Yjs + y-webrtc (`services/collaborationService.ts`). **RTCDataChannel in-flight E2E encryption** is shipped via `patches/y-webrtc@10.3.0.patch` (v1.17.0). Signaling-channel encryption: AES-256-GCM / PBKDF2 (600 000 iterations, SHA-256), deterministic salt from `projectId`. - -## graphify - -Before answering architecture or codebase questions, read `graphify-out/GRAPH_REPORT.md` if it exists. -If `graphify-out/wiki/index.md` exists, navigate it for deep questions. -Type `/graphify` in Copilot Chat to build or update the knowledge graph (semantic / LLM-backed). - -From the repo shell, **`pnpm run graphify:update`** refreshes the AST-only graph (works even when `graphify` is not on `PATH`, e.g. after `pip install graphifyy` on Windows); see `docs/graphify.md` and `scripts/graphify-cli.mjs`. - -## codegraph - -This project uses CodeGraph (`.codegraph/`) for semantic code intelligence via MCP. Read `.codegraph/CODEGRAPH_REPORT.md` for index status before deep code navigation. - -Rules: -- For code-structure, caller/callee, or impact questions, prefer CodeGraph MCP tools (`codegraph_context`, `codegraph_impact`, `codegraph_trace`) -- If `.codegraph/` exists, answer directly with CodeGraph — don't delegate exploration to a file-reading sub-agent -- For "how does X reach Y", use `codegraph_trace` instead of manual Grep + Read chains -- After modifying code, the graph auto-syncs (2s debounce). For large refactors, run `pnpm run codegraph:update` -- To find affected tests: `pnpm run codegraph:affected` - -### Dual-Graph workflow -1. Architecture / high-level questions: Read `graphify-out/GRAPH_REPORT.md` first -2. Code navigation / symbols / impact: Use CodeGraph MCP tools -3. Cross-module relationships: Use Graphify `query`/`path` or CodeGraph `context` +Preserve-first storage/security, pnpm-only sequential local execution, i18n parity, semantic +theme tokens, and the UI/Tauri platform boundary remain non-negotiable. Do not duplicate their +implementation details here. diff --git a/AGENTS.md b/AGENTS.md index e9e6eeaf4..83fd640c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,647 +1,95 @@ - - -# WorldScript Studio — Agent Guide - ---- - -## Project Overview - -**WorldScript Studio** is an offline-first, AI-powered creative writing application. It is a React 19 single-page application (SPA) that runs in the browser as a Progressive Web App (PWA) and can also be packaged as a desktop app via Tauri 2. There is no backend server; all project data lives locally (IndexedDB in the browser, filesystem in Tauri). Cloud AI providers are optional and user-triggered only. - -- **Primary deploy target:** Static SPA on GitHub Pages (`/WorldScript-Studio/` base path) -- **Secondary targets:** Vercel (root base) and Cloudflare Pages via edge builds (`pnpm run build:edge`) -- **Desktop:** Tauri 2 bundles for Linux (AppImage), macOS (DMG), and Windows (MSI); auto-updater enabled via `latest.json` -- **Version:** `1.28.4` -- **License:** MIT - -The app supports a multi-provider AI stack (Gemini, OpenAI, Claude, Grok, OpenRouter, Ollama, WebLLM, ONNX Runtime Web, Transformers.js), four AI execution modes (Hybrid / Cloud / Local / Eco), real-time collaboration with E2E encryption, a Plot Board v2 with swimlane/canvas/timeline modes, character/world management, manuscript export, voice dictation, and a 19-locale i18n layer. - -**Native desktop strategy is changing (ADR-0021, 2026-08-20) — read before touching desktop/native code.** CEF is retired from the target architecture. Current direction: React/PWA stays the web product → Tauri 2 is **transitional only** (itself retired once Qt reaches Stable) → an authoritative Rust Core (`crates/`, an independent Cargo workspace from `src-tauri/`) → Qt 6/Qt Quick as the primary native product → GPUI admitted later, behind a strict gate. Full plan: `docs/native/ROADMAP-QT-GPUI-DESKTOP.md`, amended by the binding `docs/native/DESKTOP-MIGRATION-ROADMAP-REV3.md` (gates G1.5/G2.5, Wave 2.5); decision record: `docs/adr/0021-qt-gpui-native-desktop-strategy.md`. Extraction has started: `crates/worldscript-project` (renderer-neutral project schema/validation/migration, headless — no GUI deps) is wired to one real Tauri command (`worldscript_project_validate` in `src-tauri/src/commands/project_core.rs`) via a cross-workspace Cargo path dependency, and the first typed DesktopPlatform caller observes its bounded verdict on desktop loads without changing the existing TS authority. The envelope is synthesized and partial; unknown TS-only fields are not validated. Priority order for what gets extracted next: `docs/native/CORE-MIGRATION-LEDGER.md`. - ---- - -## ⚠️ Critical Execution Environment Warning (Agent Must Follow) - -> **This local development environment runs on low-end / constrained hardware.** -> **All operations must be executed sequentially and with strict resource conservation.** - -### Rules for this Environment - -1. **No heavy local test suites** – Never run the full Vitest coverage suite, Playwright E2E tests, Stryker mutation testing, Lighthouse CI, or Storybook test-runner locally. These are CI-only by design and would overwhelm this machine. -2. **CI-Cloud-First Workflow** – The canonical quality gate is GitHub Actions (cloud CI). After making changes, push to a branch and let the cloud runners execute the heavy tier. -3. **Local quick tier only** – Locally, run only the lightweight sanity checks: - ```bash - pnpm run ci:prepush - ``` - This gate is mandatory before every push and after every local correction before re-pushing: it - always resolves a change-aware classification from the outgoing evidence first, then runs - release/doc truth and lightweight native guardrails sequentially and unconditionally. The - single-checker project typecheck and the i18n parity/quality/bundle checks run only when that - classification requires them (e.g. deferred for docs-, workflow-, tooling-, or non-TypeScript - test-only changes); incomplete or unresolved path evidence fails closed into running every - conditional check. The pre-commit hook separately runs staged-file Biome checks. Full repository - lint, coverage, E2E, Storybook, Lighthouse, and mutation checks belong to cloud CI, which also - re-runs the complete typecheck and i18n checks regardless of what the local gate deferred. If - branch switching or a lockfile/package-manifest change makes pnpm report dependency verification - errors, run `node scripts/dependency-state.mjs reconcile` and rerun the complete pre-push gate. - Optional targeted smoke test: `pnpm exec vitest run ` **without** `--coverage`. - **Hard rule:** Never invoke `pnpm test`, `npm run test`, or a bare Vitest wrapper; always use an explicit `pnpm exec vitest run ` command to avoid watch-mode hangs on constrained hardware. Never start multiple heavyweight processes concurrently. -4. **Audit cloud CI logs, fix locally, then re-push** – If the cloud CI run fails, inspect the logs via GitHub web UI or `gh run watch`, reproduce the specific failing test or lint error in isolation, fix it locally (quick tier to verify), commit, and push again for another cloud CI run. -5. **Sequential execution** – Do not parallelize builds, tests, or processes locally. Use single-threaded modes and avoid background tasks that compete for RAM/CPU. -6. **Resource budget** – Avoid spinning up the dev server (`pnpm run dev`) for extended periods if not needed. Prefer one-off commands (`pnpm run build`, `pnpm run typecheck`) and stop the server when done. -7. **No local E2E** – Playwright E2E requires `CI=true` and is CI-only by policy. Do not attempt to run `pnpm run test:e2e` locally. - ---- - -## Technology Stack - -| Layer | Technology | -|-------|------------| -| Runtime | Node.js `>=22.0.0` (`.nvmrc` → `22`), pnpm `11.22.0` (`packageManager: pnpm@11.22.0`) | -| Framework | React `^19.2.7`, TypeScript 7 via `@typescript/native-preview` (tsgo, strict) — no pinned classic `typescript` package | -| Build tool | Vite `^8.0.16` (`vite.config.ts`) | -| Type checker | `tsgo` (TypeScript Go port) via `tsconfig.tsgo.json` with 4 checkers (`pnpm run typecheck`) | -| Styling | Tailwind CSS `^4.3.1` via `@tailwindcss/vite` + semantic CSS custom properties (`index.css`) | -| State | Redux Toolkit `^2.12.0` + `redux-undo` (project slice only); Zustand `^5.0.14` for transient UI (`app/transientUiStore.ts`) | -| Testing | Vitest `^4.1.10` (jsdom, `maxWorkers: 1`), Playwright `^1.61.1` (E2E, CI-only), Stryker `9.6.1` (mutation, manual workflow only) | -| Lint/Format | Biome `^2.5.4` (`biome.json`) — single toolchain for JS/TS/CSS | -| AI | Multi-provider: Google Gemini (`@google/genai`), OpenAI, Anthropic Claude, Grok, OpenRouter, Ollama, WebLLM, ONNX Runtime Web, Transformers.js | -| Voice | Web Speech API (fallback); WASM engines: Whisper.cpp (STT), Kokoro (TTS), Silero VAD; gated by `featureFlags.enableVoiceWasm` | -| Storage | IndexedDB v8 (`dbService.ts` / `storageService.ts`) / Tauri filesystem (`fileSystemService.ts`); LZ-String compression; AES-256-GCM encryption for API keys and optional IDB at-rest encryption | -| PWA | `vite-plugin-pwa` with `injectManifest` strategy (`public/sw.js`) | -| Desktop | Tauri 2 (`src-tauri/`) — Rust toolchain required | -| Storybook | Storybook `^10.5.3` with `@storybook/react-vite` and `@storybook/addon-a11y` | -| Orchestration | Turborepo (`turbo.json`) for parallel task caching; pnpm workspaces (`packages/*`) | -| Collaboration | Yjs + `packages/collab-transport` (vendor fork of y-webrtc 10.3.0) with RTCDataChannel AES-256-GCM E2E encryption | - ---- - -## Project Structure - -```text -WorldScript-Studio/ -├── app/ # Redux store setup, typed hooks, listener middleware, Zustand transient store -├── components/ # React view components; components/ui/ = design-system primitives -│ ├── ui/ # Atoms: Button, Modal, Toast, Input, etc. -│ ├── manuscript/ # ManuscriptView sub-components -│ ├── scene-board/ # Plot Board v2 (Swimlane, Canvas, Timeline) -│ ├── dashboard/ # Dashboard cards and widgets -│ ├── settings/ # Settings sections -│ ├── help/ # Help view sub-components -│ ├── copilot/ # Global AI Copilot sub-components -│ ├── voice/ # Voice dictation UI -│ └── … -├── contexts/ # One React context per major view + I18nContext + CommandExecutorContext + LiveRegionContext -├── features/ # Redux Toolkit slices -│ ├── project/ # Core project state (undo-able via redux-undo) -│ ├── settings/ # App settings (AI keys, appearance, accessibility, shortcuts) -│ ├── status/ # App-wide status / loading flags -│ ├── writer/ # Writer view state -│ ├── versionControl/ # Snapshots and branches -│ ├── featureFlags/ # 23 flags — full set on by default; 7 opt-in (default-off) -│ ├── plotBoard/ # Ephemeral viewport/draw state (NOT undo-able; localStorage) -│ ├── progressTracker/ # Writing sessions, streaks, goals -│ ├── sceneComments/ # Per-scene comments (EntityAdapter) -│ ├── analytics/ # DuckDB boot/migration status -│ ├── mindMapUi/ # Mind-map viewport state -│ ├── proForge/ # ProForge pipeline state -│ ├── lora/ # LoRA adapter state -│ ├── voice/ # Voice command state -│ └── copilot/ # Global AI Copilot ephemeral state -├── hooks/ # View business logic hooks (use*View.ts naming) -├── services/ # External adapters and business logic -│ ├── ai/ # Vercel AI SDK orchestration layer + aiModeService, aiPolicy, aiRetry, routingLogger -│ │ └── providers/ # Provider implementations including openrouterProvider -│ ├── commands/ # Command palette registry, fuzzy search, preferences -│ ├── copilot/ # Heuristic engine, insight generator, copilot context, action applier -│ ├── duckdb/ # DuckDB-WASM client, schema, analytics, migration -│ ├── help/ # Help catalog, search, doc retrieval -│ ├── keyboard/ # Shortcut normalization and conflict detection -│ ├── voice/ # Voice engines and orchestration -│ ├── storage/ # IDB stores, encryption, backend abstraction -│ ├── fs/ # Filesystem helpers (Tauri) -│ ├── lora/ # LoRA adapter services -│ ├── plugins/ # Plugin registry helpers -│ └── proForge/ # ProForge pipeline services -├── packages/ # Internal pnpm workspace packages -│ ├── ai-core/ # Local AI facade: WebLLM → ONNX → Transformers.js → heuristic fallback -│ ├── collab-transport/ # Vendor fork of y-webrtc 10.3.0 with E2E encryption patch -│ ├── ui/ # Tailwind preset + design tokens -│ └── worker-bus/ # Typed worker pool, circuit breakers, dead-letter queue -├── locales/ # i18n source JSON modules (19 locales) -├── public/ # Static assets; runtime i18n bundles `public/locales//bundle.json` -├── tests/ -│ ├── unit/ # Vitest tests (co-located naming convention) -│ ├── e2e/ # Playwright specs (CI-only) -│ └── setup.ts # Global Vitest setup -├── workers/ # Web Workers: plugin.worker.ts; v2/ is the sole worker generation since ADR-0015 (inference, duckdb, webllm) -├── scripts/ # Build/deploy helpers (i18n bundle, SW version sync, bundle budget, edge build) -├── infra/low-end-ci/ # Local CI stack for constrained hardware -├── src-tauri/ # Tauri 2 desktop app (Rust) -├── stories/ # Storybook stories -├── docs/ # Deep-dive docs: CI.md, DEPLOYMENT.md, ACCESSIBILITY.md, BEST-PRACTICES.md, etc. -├── types.ts # Core shared TypeScript interfaces -└── types/ # Supplemental type declarations -``` - -### Key Files - -- `package.json` — scripts, dependencies, pnpm overrides, `simple-git-hooks` + `lint-staged` -- `vite.config.ts` — dev server (port 3000), PWA plugin, manual chunks, `@tauri-apps/*` externalized for web builds -- `tsconfig.json` / `tsconfig.tsgo.json` — `strict: true`, `exactOptionalPropertyTypes: true`, `noUnusedLocals: true`, `noUnusedParameters: true`, `noUncheckedIndexedAccess: true`, `noPropertyAccessFromIndexSignature: true` -- `biome.json` — lint + format rules; `a11y`, `security`, `correctness` rules enabled; line width 100; 2-space indent -- `vitest.config.ts` — coverage thresholds (lines 74, branches 60, functions 67, statements 72), `maxWorkers: 1` -- `playwright.config.ts` — E2E projects: Chromium desktop + Pixel 5 mobile in CI; Firefox + optional mobile locally -- `turbo.json` — task graph for `build`, `dev`, `lint`, `typecheck`, `test`, `mutation` -- `pnpm-workspace.yaml` — workspace packages + pnpm v11 `allowBuilds` default-deny map -- `stryker.config.mjs` + `stryker-scope.json` — 25 curated production targets across 8 risk-tiered modules, `break: 75` -- `.lighthouserc.cjs` — accessibility `error` ≥ 0.95, CLS `error` ≤ 0.1, performance/SEO `warn` -- `src-tauri/tauri.conf.json` / `Cargo.toml` — desktop window config, CSP, updater endpoints, rust-compute feature - ---- - -## Build and Development Commands - -```bash -# Development -pnpm run dev # Vite dev server on http://localhost:3000 -pnpm run dev:turbo # Turbo parallel dev across workspace -pnpm run dev:tauri # Tauri desktop app (requires Rust) - -# Build -pnpm run build # Production build → dist/ (GitHub Pages base) -pnpm run build:edge # Edge build (root base) for Vercel / Cloudflare Pages -pnpm run build:pages # Alias for vite build -pnpm run preview # Preview production build locally (port 4173) - -# Code quality -pnpm run lint # Biome lint (--error-on-warnings) -pnpm run lint:fix # Biome check --write (lint + format) -pnpm run format # Biome format --write -pnpm run typecheck # tsgo --project tsconfig.tsgo.json --noEmit --checkers 4 -pnpm run typecheck:single # local low-end typecheck: one checker, sequential -pnpm run i18n:check # Locale key parity vs English + rebuild bundles + content guard -pnpm run parity:check # Feature parity audit -pnpm run suppressions:check # Biome-ignore count ratchet - -# Testing -pnpm exec vitest run # Targeted Vitest single run (no coverage) -pnpm exec vitest run --coverage # Targeted Vitest run with V8 coverage -pnpm run test:e2e # Playwright E2E (CI=true required; CI-only by policy) -pnpm run test:e2e:ui # Playwright E2E UI mode (CI=true required) -pnpm run test:e2e:deep # Deep E2E feature-flag matrix (CI=true required) -pnpm run test:vrt # Visual regression (Chromium only) -pnpm run mutation # Stryker incremental mutation testing (CI-only; manual workflow) -pnpm run mutation:force # Stryker force/no-cache audit (CI-only) -pnpm run mutation:report # Aggregate downloaded reports; fails on missing/invalid shards - -# Analysis / budgets -pnpm run analyze # Rollup visualizer → dist/bundle-analysis.html -pnpm run bundle:budget # Chunk size guard; ceilings come from config/bundle-budget.json -pnpm run storybook # Storybook dev server on :6006 -pnpm run build-storybook # Static Storybook build -pnpm run test:storybook # Test-runner against served Storybook - -# Tauri -pnpm run tauri:dev # Tauri dev -pnpm run tauri:build # Tauri production build - -# Knowledge graphs -pnpm run graphify:update # AST-based knowledge graph update -pnpm run codegraph:update # Semantic code intelligence update -pnpm run graphs:update # Update both graphs - -# Quick local CI (low-end hardware) -pnpm run ci:quick # lint + typecheck + i18n + build-storybook + optional unit tests -pnpm run ci:quick:unit # lint + typecheck + i18n + build-storybook only -pnpm run ci:quick:coverage # lint + typecheck + i18n + build-storybook + unit tests with coverage -``` - ---- - -## Code Style and Conventions - -### TypeScript - -- `strict: true` and `exactOptionalPropertyTypes: true` are enforced. Do not assign `undefined` to optional properties; omit the property instead. -- Avoid `any`. Use proper types or `unknown`. Biome flags `noExplicitAny` as error. -- `noUnusedLocals`, `noUnusedParameters`, `noUnusedImports`, `noImplicitReturns`, `noUncheckedIndexedAccess`, and `noPropertyAccessFromIndexSignature` are all enabled. -- Event handler props use `onX` prefix. Boolean props use `is*` / `has*` prefix. -- `useImportType` is enforced (Biome error). - -### Styling - -- **NEVER use the `dark:` Tailwind prefix.** Theming is body-class based (`.light-theme`, `.dark-theme`, `.sepia-theme`, etc.). Use CSS custom properties: `bg-[var(--sc-surface-base)]`. -- Design tokens in `index.css` use `--sc-*` naming. Special families: `--glass-*`, `--nav-*`, `--radius-sc-*`, `--icon-sc-*`, `--text-sc-*`. -- `packages/ui/tailwind-preset.ts` registers `w/h-icon-sc-*`, `text-sc-*`, `rounded-sc-*`, etc. Prefer these for atoms. -- Container queries are used for resizable panels; set `containerType: 'inline-size'` inline and use `@container` queries. -- Focus rings: `focus-visible:ring-2 focus-visible:ring-[var(--sc-ring-focus)]`. -- Logical properties: Use `ps-`/`pe-` instead of `pl-`/`pr-` in UI atoms for RTL prep. - -### Component Patterns - -- Every major view follows a **three-file pattern**: - - `components/XyzView.tsx` — pure rendering only - - `hooks/useXyzView.ts` — business logic, selectors, thunks - - `contexts/XyzContext.ts` — React context passing hook return to children -- Use `React.memo()` for expensive renders; `React.forwardRef()` for `components/ui/` primitives. -- Wrap view roots with `components/ui/ViewErrorBoundary.tsx`. -- File size target: **200–700 lines**. Over 700 → split into submodules, hooks, or selectors. -- All views are lazy-loaded in `App.tsx` via `React.lazy()`. -- **Custom Select components** (`Select.tsx`, `LanguageSelector.tsx`): Use `role="listbox"` on dropdown container, `role="option"` on items, `aria-haspopup="listbox"` and `aria-expanded` on trigger button. Mock as native `