StoryCraft Studio is at v1.4.0 with an extremely solid foundation: 4-layer local-AI fallback (detection-only for layers 2-3), Yjs E2E encryption, Cross-Project Search v2, CI gold-standard, 1,652 tests / 151 files, 62.86 % coverage. The goal is v1.5: integrate the full CannaGuide-2025 production-grade local-AI architecture, perfect the mobile 3-panel editor, and deliver Phase 2/3 feature completeness β zero compromises.
CannaGuide has 66+ local-AI files adapted for cannabis. We CANNOT import them directly (domain-specific). Instead, we extract every architectural pattern and create StoryCraft-native equivalents. The CannaGuide code is at /home/pc/CannaGuide-2025/apps/web/services/local-ai/.
| Dimension | StoryCraft Current | CannaGuide Pattern | Action |
|---|---|---|---|
| WorkerBus | Priority queue, no backpressure, no preemption | Priority preemption (max 3Γ), backpressure (concurrency cap), transferables, rate limiting, W-02/W-03 telemetry | Upgrade packages/ai-core/src/index.ts WorkerBus class |
| GPU Mutex | Tab leader election (BroadcastChannel) | Full GPU mutex: consumer queue, priority sort, 30s auto-release, eviction hooks, multi-consumer (webllm / onnx-webgpu) | New services/ai/gpuResourceManager.ts |
| Device Health | VRAM tier from WebGPU adapter limits | Full health report: CPU cores, memory heap, storage quota, battery level, device class (high/mid/low/unknown) | New services/ai/deviceHealthService.ts |
| Eco Mode | None | Battery API + low-power detection β force 0.5B model + heuristics | New services/ai/ecoModeService.ts |
| Download Progress | Callback type defined, not wired to UI | Full progress emitter: subscribe/snapshot pattern, ARIA live region, cancel button | New services/ai/inferenceProgressEmitter.ts + WCAG modal |
| Model Recommendation | Static curated lists | Device-class β model auto-select (VRAM tier Γ task type) | Upgrade services/ai/modelRecommendations.ts |
| ONNX Inference | Detection only (returns message string) | Active inference via Transformers.js ONNX backend (quantized, WASM/WebGPU) | Upgrade Layer-2 in packages/ai-core/src/index.ts + worker |
| Transformers.js Inference | Detection only (returns echo string) | Worker-offloaded pipeline; singleton per model; pipeline cache (8 entries); device hint | New src/workers/inference.worker.ts + activate Layer-3 |
| Inference Cache | None at inference level | IndexedDB LRU (persistent) + in-memory LRU 64 entries (DJB2+FNV hash) | New services/ai/aiInferenceCacheService.ts |
| Embedding Service | BoW hash 64-dim (not semantic) | Xenova/all-MiniLM-L6-v2 384-dim, L2-normalized, worker-offloaded, batch micro-batch (8) |
New services/ai/localEmbeddingService.ts |
| NLP Service | None | Sentiment analysis, text classification, summarization | New services/ai/localNlpService.ts |
| RAG Service | Single-project BoW cosine similarity | Hybrid: 60% semantic + 30% keyword token overlap + 10% recency; sliding window; max 500 chunks | Upgrade services/localRagIndex.ts to localRagService.ts |
| Cross-Project AI | None | AI-enriched index (summaries, embeddings) | Extend crossProjectIndexService.ts |
| Streaming Service | Via Vercel AI SDK only | Token-by-token streaming with backpressure via WorkerBus | Extend services/ai/storyCraftCompletionFetch.ts |
| Telemetry | WorkerBus counters only | Full inference telemetry: latencyMs, tokensPerSecond, errorRate, peakLatency, cached/uncached | New services/ai/aiTelemetryService.ts |
| Mobile Touch | Mouse events only (ManuscriptView resize) | Not in CannaGuide scope | Custom: PointerEvent resize, useSwipeGesture, BottomSheet, useLongPress, useHaptics |
| Prompt Library | Inline hardcoded in geminiService.ts | Not in CannaGuide scope | New services/promptLibrary.ts + versioned JSON |
| StyleTransfer | Missing | Not in CannaGuide scope | New AI tool |
| Plot Hole Auto-Fix | Detection only, no generation | Not in CannaGuide scope | Extend AI tool |
| Chapter Auto-Gen | Missing | Not in CannaGuide scope | New AI tool |
| Character Arc Visualizer | Missing | Not in CannaGuide scope | New view + hook + context |
Day 1 β WorkerBus v2 + GPU Mutex
- [M] Upgrade
WorkerBusinpackages/ai-core/src/index.ts:- Add
MAX_CONCURRENTconcurrency cap (default 3 for heavy inference, 8 for text) - Add
backpressure()method that rejects tasks whentotalQueueDepth > MAX_QUEUE_SIZE (32) - Add priority preemption: when
criticaltask arrives, requeue interruptedlowtask (max 3Γ requeue before drop) - Add Transferable Object support to
WorkerTaskinterface (already hastransferables?) - Upgrade
WorkerBusTelemetry: addpeakLatencyMs,errorRate,lastSuccessAt: number | null - Add
AbortControllermap:taskAbortControllers: Map<string, AbortController>;cancel(taskId)method
- Add
- [M] New
services/ai/gpuResourceManager.ts(adapted from CannaGuide):- Consumers:
'webllm' | 'onnx-webgpu' - Priority:
'high' | 'normal' | 'low' - Auto-release timeout: 30s deadlock prevention
acquireGpu(consumer, priority): Promise<void>,releaseGpu(consumer): voidgetQueueState(): { current: Consumer | null, queue: Consumer[] }
- Consumers:
- [M] Tests: upgrade
tests/unit/aiCoreWorkerBus.test.ts(add backpressure, preemption, cancel tests); newtests/unit/gpuResourceManager.test.ts
Day 2 β Device Health + Eco Mode + Progress Emitter
- [M] New
services/ai/deviceHealthService.ts:getDeviceClass(): 'high-end' | 'mid-range' | 'low-end' | 'unknown'getHealthReport(): DeviceHealthReport(cores, heapUsed/total, storageQuota, batteryLevel, gpuVramTier)getModelRecommendation(task: 'text-gen' | 'embedding' | 'rag'): string(maps device class + VRAM β specific model ID)- Memory pressure threshold: 80% mobile, 90% desktop
- Storage quota check: 200 MB minimum for model downloads
- [S] New
services/ai/ecoModeService.ts:isEcoMode(): boolean(low battery < 20% + device class low-end)isCriticalBattery(): boolean(< 10%)setEcoModeExplicit(active: boolean): voidapplyAdaptiveMode(): Promise<void>(reads Battery API, sets mode)
- [M] New
services/ai/inferenceProgressEmitter.ts:WebLlmLoadingStatetype:'idle' | 'loading' | 'ready' | 'error'WebLlmLoadProgresstype:{ state, progress, text, estimatedSecondsRemaining }reportWebLlmProgress(progress: number, text: string): voidreportWebLlmReady(): void,reportWebLlmError(msg: string): voidsubscribeWebLlmLoading(cb): () => void(pub/sub, returns unsubscribe)getWebLlmLoadingSnapshot(): WebLlmLoadProgress
- [M] Tests:
tests/unit/deviceHealthService.test.ts(device class detection, model recommendation, memory thresholds);tests/unit/inferenceProgressEmitter.test.ts
Day 3 β Active ONNX + inference.worker.ts
- [M] New
src/workers/inference.worker.ts:- WorkerBus protocol (request/response with
messageIdcorrelation) isTrustedWorkerMessage(event): boolean(origin check for security)- Dynamic import of
@xenova/transformerswith pipeline cache (max 8 pipelines) - Supported tasks:
text-generation,feature-extraction(embeddings),sentiment-analysis,summarization - Inference options:
max_new_tokens,do_sample,temperature,return_full_text - AbortController integration (listen for
WORKER_CANCELmessages) - TypeScript:
/// <reference lib="webworker" />at top - Vite:
{ type: 'module' }worker registration
- WorkerBus protocol (request/response with
- [M] Upgrade Layer-2/3 in
packages/ai-core/src/index.tsrunLocalTextGeneration():- Layer-2 ONNX: Actually load a model via Transformers.js ONNX backend (default
Xenova/distilgpt2when no modelId supplied); useinference.worker.tschannellocal.text.generate; timeout 30s desktop / 15s mobile - Layer-3 Transformers.js: Use CPU device hint WASM fallback; same worker, different quantization
- Replace static message returns with real inference calls
- Fix Layer-4 heuristic: fix bug where final return incorrectly says
layer: 'transformers'β should belayer: 'heuristic'
- Layer-2 ONNX: Actually load a model via Transformers.js ONNX backend (default
- [M] Update
services/ai/index.tsto re-export new services - [M] Tests:
tests/unit/inferenceWorker.test.ts(mock worker env, pipeline cache, trusted-message guard, abort); upgradetests/unit/aiCoreFallbackPaths.test.tsfor real ONNX/Transformers inference paths
Day 4 β Cache Service + Embedding Service
- [S] New
services/ai/aiInferenceCacheService.ts:- In-memory LRU 64 entries (DJB2 + FNV hash of prompt + modelId)
- Persistent IndexedDB cache (store:
storycraft-inference-cache, max 256 entries, 7d TTL) getCachedInference(prompt, modelId): Promise<string | null>setCachedInference(prompt, modelId, result): Promise<void>clearPersistentCache(): Promise<void>- Smart invalidation: skip cache for prompts > 512 chars (streaming / long context)
- [C] New
services/ai/localEmbeddingService.ts:- Model:
Xenova/all-MiniLM-L6-v2(384-dim viainference.worker.tschannellocal.embeddings.create) embedText(text: string): Promise<Float32Array>(L2-normalized)embedBatch(texts: string[]): Promise<Float32Array[]>(micro-batch size 8)cosineSimilarity(a: Float32Array, b: Float32Array): number- MAX_INPUT_LENGTH: 512 chars (truncate with warning)
- Depends on
aiInferenceCacheServicefor embedding cache
- Model:
- [C] New
services/ai/localNlpService.ts:analyzeSentiment(text): Promise<{ label: 'POSITIVE'|'NEGATIVE'|'NEUTRAL', score: number }>summarizeText(text, maxLength?): Promise<string>classifyWritingTopic(text): Promise<string>(genre/mood classification)- Routes via WorkerBus channel
local.text.generate
- [M] Tests:
tests/unit/aiInferenceCacheService.test.ts(LRU eviction, TTL, IDB persistence mock);tests/unit/localEmbeddingService.test.ts(cosine sim, batch, truncation)
Day 5 β Download Progress WCAG UI + Model Recommendation Engine
- [M] Upgrade
services/ai/modelRecommendations.ts:- Replace static list with
getModelRecommendationForTask(task, deviceReport): { webllm?, onnx?, transformers? } - VRAM tier mapping:
highβ Phi-3.5 Mini / Llama-3.2-3B;mediumβ Llama-3.2-1B;lowβ Gemma-2-2B q4 / DistilGPT-2 - Task-specific:
text-genprefers larger models;embeddingalways routes to all-MiniLM;ragroutes to DistilGPT-2 - Eco mode override: always return 0.5B model (Xenova/Qwen2.5-0.5B-Instruct)
- Export
getProviderSpeedEstimate(provider): Promise<number>(lightweight ping test for Ollama)
- Replace static list with
- [M] New
components/settings/LocalAiDownloadProgress.tsx:- Triggered by
subscribeWebLlmLoading() - WCAG 2.2 AA:
role="progressbar",aria-valuenow,aria-valuetext="42% downloaded, approximately 30 seconds remaining" aria-live="polite"on status text;aria-live="assertive"on error- Cancel button: calls
gpuResourceManager.releaseGpu('webllm')+ signals AbortController - Progress bar, percentage text, estimated time (seconds remaining computed from rate)
- Error recovery: "Retry" button
- i18n: 8 new keys per locale β
locales/*/settings.json
- Triggered by
- [M] New
components/settings/GpuMetricsPanel.tsx:- GPU queue state display (current consumer, waiting consumers)
- WorkerBus telemetry:
processedTasks,avgExecutionMs,peakLatencyMs,errorRate - Device class badge: High-end / Mid-range / Low-end
- Eco mode toggle (calls
ecoModeService.setEcoModeExplicit()) - Feature flag:
enableAppHealthPanel(already exists in featureFlagsSlice) - i18n: 10 new keys per locale β
locales/*/settings.json
- [M] Wire both components into
components/settings/AiSections.tsx - [M] Tests:
tests/unit/settings/LocalAiDownloadProgress.test.tsx(progress bar render, ARIA attrs, cancel callback, error state);tests/unit/settings/GpuMetricsPanel.test.tsx(badge display, eco toggle)
Day 6 β Upgrade RAG + Cross-Project AI Enrichment
- [M] Upgrade
services/localRagIndex.tsβ rename toservices/localRagService.ts:- Keep existing BoW as fallback (lexical branch)
- Add semantic branch: uses
localEmbeddingService.embedText()when embedding model loaded - Hybrid scoring: 60% cosine similarity + 30% token overlap + 10% recency boost
- Sliding window: most recent 3 entries always included
- MAX_CHUNKS: 500 (OOM prevention)
retrieveContext(projectId, query, topK, mode: 'lexical'|'semantic'|'hybrid'): Promise<RagChunk[]>- Add token-based chunking (replace section-based): 300-token chunks, 50-token overlap
- Backward compat: old
searchLocalRag()delegates to newretrieveContext()
- [S] Extend
crossProjectIndexService.ts:- Add
aiSummary?: stringfield toProjectSearchIndex(100-char AI-generated essence) enrichProjectIndex(projectId): Promise<void>β calls local AI to generate summary if model available; feature-flagged underenableCrossProjectSearchsemanticSearchProjects(query, topK): Promise<ProjectSearchIndex[]>β uses embeddings
- Add
- [M] Tests: upgrade
tests/unit/localRagService.test.ts(hybrid scoring, sliding window, token chunking); extendtests/unit/crossProjectIndexService.test.ts(AI enrichment with mocked embedding service)
Day 7 β Mobile Writer Experience: Touch + PointerEvent
- [M] Upgrade
components/ManuscriptView.tsxresize handles:- Replace
MouseEventwithPointerEventeverywhere (onPointerDown,onPointerMove,onPointerUp) - Add
setPointerCapture()/releasePointerCapture()for reliable mobile drag - Keep keyboard resize (arrow keys) intact
- Add
touch-action: noneCSS to resize handle elements (prevents scroll conflict) - Test: resize handles work on iOS Safari + Chrome Mobile
- Replace
- [M] New
hooks/useSwipeGesture.ts:useSwipeGesture(ref, { onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, threshold?: number })- Uses PointerEvent (pointerdown β pointermove β pointerup)
- Threshold: 50px swipe distance, 300ms velocity window
- Directional logic: horizontal vs vertical dominant axis
- Applied in
components/writing/WriterViewUI.tsxto switch 3-panel focus (outline β manuscript β tools)
- [S] New
hooks/useLongPress.ts:useLongPress(callback, ms?: 600)- PointerEvent-based; calls callback if held > ms without > 10px move
- Applied to chapter items in outline panel β context menu
- [S] New
hooks/useHaptics.ts:useHaptics()β{ vibrate(pattern?: number | number[]): void, canVibrate: boolean }- Wraps
navigator.vibrate(), degrades gracefully when unavailable - Called on swipe confirm, long-press trigger, panel resize snap
- [M] i18n: 6 new keys per locale (panel names, swipe hints)
- [M] Tests:
tests/unit/useSwipeGesture.test.ts(mock PointerEvent sequence, threshold, direction);tests/unit/useLongPress.test.ts; upgradetests/unit/ManuscriptView.test.tsx(PointerEvent resize)
Day 8 β Mobile: BottomSheet + ARIA Live Regions
- [M] New
components/ui/BottomSheet.tsx:- Drawer-style bottom sheet with handle, backdrop, drag-to-dismiss
props: { open, onClose, title, children, height?: 'half'|'full' }- Drag handle: PointerEvent drag, snap to closed when dragged > 30% height
- ARIA:
role="dialog",aria-modal="true",aria-labelledby - Focus trap on open, restore on close
- Backdrop:
onClick β onClose;Escapekey closes - Tailwind:
fixed inset-x-0 bottom-0 z-50 rounded-t-2xl bg-white dark:bg-slate-900 - Used on mobile for: AI Tools panel (mobile β€ md), Character quick-view, World quick-view
- [M] Integrate
BottomSheetintocomponents/writing/WriterViewUI.tsx:- On mobile (< md), "AI Tools" tab opens a BottomSheet instead of inline column 3
- Trigger: existing mobile segmented control AI tab
- [M] Add ARIA live regions for AI responses:
components/writing/AiWritingPanel.tsx(or equivalent): wrap streaming text in<div role="status" aria-live="polite" aria-atomic="false">- Per WCAG 2.2: each token chunk appended β screen reader announces when idle
- Existing error messages: upgrade to
role="alert"+aria-live="assertive"
- [M] New
tests/e2e/mobile-touch.spec.ts:- Bottom sheet open/close on mobile viewport
- Panel switch via swipe gesture simulation
- AI response live region announced
- [M] Tests:
tests/unit/BottomSheet.test.tsx(open/close, focus trap, ARIA attrs, drag-to-dismiss)
Day 9 β Prompt Library
- [M] New
services/promptLibrary.ts:PromptTemplateinterface:{ id, version, name, category, localeKey, template(vars), chainable, abTestVariants? }- Categories:
'outline' | 'character' | 'world' | 'manuscript' | 'consistency' | 'style-transfer' | 'plot-hole' | 'chapter-gen' - Versioned:
v1.0.0semver string per template - Locale-aware:
namekey points to i18n key;template()usest()at call-time - Prompt chaining:
chainable: true+inputFromPreviousId?: stringβ output of step N becomes input of step N+1 - A/B hooks:
abTestVariants?: PromptTemplate[](uniform random selection, logged to telemetry) - Export/import:
exportPromptLibrary(): string(JSON),importPromptLibrary(json): void(with JSON-schema validation) getPrompt(id: string, vars: Record<string, string>): stringlistByCategory(category): PromptTemplate[]- Migrate all 17 existing hardcoded prompts from
geminiService.tsinto this registry (keep geminiService calls pointing here)
- [S] New
components/settings/PromptLibraryPanel.tsx:- List all prompts by category (accordion)
- Export / Import buttons
- Preview pane with variable substitution
- [M] i18n: prompt names in all 5 locales
- [M] Tests:
tests/unit/promptLibrary.test.ts(getPrompt vars, chainable, export/import, a/b variant selection)
Day 10 β StyleTransfer + Auto-Plot-Hole-Fixer
- [M] New AI tool:
services/geminiService.tsadditions:promptType: 'styleTransfer'β author voice mimicry: system prompt embeds${authorStyle}exemplar text; user message is the passage to transform; returns JSON{ transformed: string, voiceNotes: string[] }promptType: 'plotHoleFix'β extends existingplotHoleDetection: takes existing analysis + manuscript context; generates specific fix suggestions per hole; returns JSON{ fixes: Array<{ hole: string, suggestion: string, chapter?: string }> }
- [M] View pattern for StyleTransfer:
components/StyleTransferView.tsx+hooks/useStyleTransferView.ts+contexts/StyleTransferContext.tsx- Pure render: textarea for exemplar style + source passage, output panel
- Business logic: debounced AI call, loading state, progress indicator
- Author style presets: 5 built-in (Hemingway sparse, Gothic atmospheric, Literary flair, Thriller crisp, Fantasy epic)
- [S] Upgrade
components/ConsistencyCheckerView.tsx(or equivalent): wireplotHoleFixas "Auto-Fix" button alongside existing detection results - [M] i18n: 15 new keys (StyleTransfer view labels, tool names, presets)
- [M] Tests:
tests/unit/styleTransfer.test.ts(geminiService mock, prompt construction, JSON response parsing);tests/unit/plotHoleFix.test.ts;tests/unit/StyleTransferView.test.tsx
Day 11 β Chapter Auto-Generation + Character Arc Visualizer
- [C] New AI tool
promptType: 'chapterAutoGeneration':- Input:
outlineSection(structured outline item) +existingChapters(context) +wordTarget - Extended thinking: Gemini 2.0, budget 8192 tokens (complex narrative generation)
- Returns JSON:
{ title, content: string, endingHook: string, wordCount: number } - Placed behind feature flag
enableChapterAutoGen(new flag in featureFlagsSlice)
- Input:
- [C] View pattern:
components/ChapterAutoGenView.tsx+hooks/useChapterAutoGenView.ts+contexts/ChapterAutoGenContext.tsx- Select outline sections to expand
- Word target slider (500β5000)
- Preview generated chapter in inline editor
- [S] New
components/CharacterArcVisualizerView.tsx+ hook + context:- Extracts character mentions per chapter from manuscript (existing Codex data)
- Timeline visualization: SVG-based arc chart (X = chapters, Y = emotional state from consistency data)
- Integration with Relationship-Graph (existing) for dual view
- No AI call required β derives from existing Codex + Redux state
- Feature flag:
enableCharacterArcVisualizer
- [M] i18n: 18 new keys for both views
- [M] Tests:
tests/unit/chapterAutoGen.test.ts(prompt construction, extended thinking params, JSON parsing);tests/unit/CharacterArcVisualizerView.test.tsx(timeline data derivation, SVG structure)
Day 12 β Community Template Marketplace
- [S] New
services/communityTemplateService.tsadditions:- Already exists; extend with:
CommunityTemplate.schema.jsonβ JSON Schema v7 for template validationvalidateCommunityTemplate(raw: unknown): ValidationResultβ uses ajv (or manual zod schema)queueForModeration(template): voidβ client-side moderation queue (IDB storemoderation-queue)rateCommunityTemplate(id: string, rating: 1-5): voidβ local ratings storeModerationStatus:'pending' | 'approved' | 'rejected'- Contribution guide:
docs/COMMUNITY-TEMPLATES.md
- [S] Upgrade
components/TemplateView.tsx/TemplateGallery:- "Submit Template" flow: form β zod validate β moderation queue
- Rating UI (star rating per template)
- Filter: show community vs built-in vs pending moderation
- "Export My Templates" button
- [M] i18n: 12 new keys
- [M] Tests: upgrade
tests/unit/communityTemplateService.test.ts(JSON schema validation, moderation queue, rating store)
Day 13 β Plugin Seam + Usage Analytics
- [S] New
services/pluginRegistry.ts:PluginDescriptorinterface:{ id, version, name, type: 'command'|'ai-tool'|'local-ai-service', entrypoint: string, permissions: string[] }PluginRegistryclass:register(descriptor),unregister(id),list(),getByType(type)- JSON-Registry pattern: reads from
~/.storycraft/plugins/*.json(Tauri) or IndexedDB (web) - Plugin API surface: exported types in
types/plugin.ts - Plugin Dev Guide:
docs/PLUGIN-DEV-GUIDE.md(with examples for Command + AI-Tool + Local-AI-Service extension)
- [S] New
services/usageAnalyticsService.ts:- Opt-in only (Redux
settings.analytics.enabled, default false) - Events: AI provider selected, local model loaded, feature flag toggled, prompt category used
- No PII: strip all user content, only metadata (event type + timestamp + device class)
- Storage: IndexedDB ring buffer (last 500 events)
- Export:
getAnonymizedSummary(): AnalyticsSummary(aggregated counts only) - UI toggle in Settings β Privacy
- Opt-in only (Redux
- [M] i18n: 10 new keys (analytics toggle, plugin registry labels)
- [M] Tests:
tests/unit/pluginRegistry.test.ts;tests/unit/usageAnalyticsService.test.ts(opt-in enforcement, PII strip, ring buffer eviction)
Day 14 β Documentation + Coverage Push + Final QA
- [M] Update
README.md: add "Local-AI Architecture" section with ASCII diagram of 4-layer fallback; update feature list with StyleTransfer, Plot-Hole-Fixer, Chapter Auto-Gen, Character Arc - [M] Update
ROADMAP.md: mark all v1.5 items complete; add v2.0 RTL + multi-language items - [M] Update
TODO.md: close all Sprint items; add Plugin API v1 + RTL as v2.0 - [M] Update
CHANGELOG.md: full v1.5 entry - [S] Update
AUDIT.md: add security audit for new AI services (input sanitization, cache invalidation, IDB encryption scope), performance audit (inference timeout budgets, cache hit rates) - [M] Update
CLAUDE.md: expand Best-Practices section for Local-AI (WorkerBus patterns, GPU mutex usage, progress emitter subscribe pattern, inference cache invalidation rules) - [M] New
docs/CONTRIBUTOR-QUICKSTART.md: dev env setup, branching, test requirements, local AI mock patterns - [S] New
docs/PLUGIN-DEV-GUIDE.md: extension points, PluginDescriptor interface, example plugin (command + AI tool) - [S] RTL infrastructure: add
dir="rtl"toggle toI18nContext.tsx, add Arabic/Hebrew placeholder locale structure (empty JSON files for future translation), CSS: add[dir="rtl"] .manuscript-editor { text-align: right; } - [M] Coverage push: target branches β 55%
- Add tests for the 9 Stryker-monitored files (currently NoCoverage):
codexService.ts,dbMigration.ts,fuzzyScore.ts,palettePreferences.ts,commandBuilder.ts,hybridFallback.ts,providerFactory.ts,helpDocRetrieval.ts,listenerMiddleware.ts - Each: minimum 5 unit tests covering main branches
- Add tests for the 9 Stryker-monitored files (currently NoCoverage):
- [M] Run full quality gate:
pnpm run lint && pnpm run i18n:check && pnpm run typecheck && pnpm run test:run && pnpm run test:coverage && pnpm run build
Files: packages/ai-core/src/index.ts
SMART Goal: WorkerBus handles 100 concurrent inference tasks without memory leak; priority preemption measurable in <1ms overhead.
Sub-tasks (est. hours):
- Add
MAX_CONCURRENT = 3,MAX_QUEUE_SIZE = 32constants (0.5h) - Add
private inFlight = 0counter +backpressure()check (0.5h) - Add preemption: in
enqueue(), if priority is critical andlow.length > 0, splice interrupted task back (1h) - Upgrade
WorkerBusTelemetryinterface: addpeakLatencyMs,errorRate,lastSuccessAt(0.5h) - Add
taskAbortControllers: Map<string, AbortController>+cancel(taskId)(1h) - Tests: 6 new test cases in
aiCoreWorkerBus.test.ts(2h) Total: ~5.5h Failure Modes:
- Preemption causes task starvation for low-priority β Mitigation: max 3 preemptions per task then auto-promote to normal
- AbortController map leaks β Mitigation: clean map entry in
recordResult()+cancel() - Backpressure rejects critical tasks β Mitigation: skip backpressure check for
criticalpriority
Files: services/ai/gpuResourceManager.ts (new), services/ai/index.ts (re-export)
SMART Goal: Zero WebGPU VRAM collisions across webllm + onnx-webgpu consumers; max 30s acquire timeout.
Sub-tasks:
- Define
GpuConsumer = 'webllm' | 'onnx-webgpu'type (0.25h) - Implement mutex:
currentConsumer,queue: {consumer, priority, resolve}[](1.5h) acquireGpu(): if free β set current + resolve; else β push to queue sorted by priority (1h)releaseGpu(): clear current β sort queue by priority β grant next (0.5h)- Auto-release timeout:
setTimeout 30000set on acquire, cleared on release (0.5h) - Tests:
gpuResourceManager.test.tsβ 8 tests (sequential acquire, priority sort, timeout, multi-consumer race) (2h) Total: ~5.75h Failure Modes:
- Deadlock if acquirer throws before release β Mitigation: try-finally in all callers; auto-timeout
- Priority inversion when queue re-sorts β Mitigation: sort DESC on priority at dequeue, not enqueue
Files: components/settings/LocalAiDownloadProgress.tsx (new), services/ai/inferenceProgressEmitter.ts (new)
SMART Goal: 0 WCAG 2.2 violations (Lighthouse accessibility β₯ 0.95); progress updates announced β€ 2s intervals.
WCAG Checklist:
role="progressbar"witharia-valuenow={Math.round(progress*100)}βaria-valuemin="0" aria-valuemax="100"βaria-valuetext="42% heruntergeladen, ca. 30 Sekunden verbleibend"(locale-aware) βaria-live="polite"on status text (throttled to 2s to avoid spam) βaria-live="assertive"on error message β- Cancel button:
aria-label= i18n key β - Focus on modal open:
autoFocuson cancel button β role="dialog" aria-modal="true" aria-labelledby="modal-title"β Performance Budget: Progress emitter update < 1ms (pub/sub, no Redux dispatch needed)
Files: components/ui/BottomSheet.tsx (new), components/writing/WriterViewUI.tsx (modify)
SMART Goal: BottomSheet renders in < 16ms (1 frame); focus trap passes WCAG 2.2; drag dismiss works on Pixel 5 (393Γ851px).
Touch Handling:
onPointerDown: record start Y +setPointerCaptureonPointerMove: translate sheet Y by delta, clamp to 0..heightonPointerUp: if delta > 30% height β close; else snap back (CSS transition 300ms ease-out)touch-action: noneon drag handle to prevent scroll Focus Trap: UseFocusTrapfrom@radix-ui/react-focus-trap(already in deps via Radix primitives) or implement withTreeWalker
Files: services/promptLibrary.ts (new), services/geminiService.ts (migrate 17 prompts), components/settings/PromptLibraryPanel.tsx (new)
Key architectural decision: All 17 existing buildPrompt_X() functions in geminiService.ts are migrated to PromptTemplate objects. geminiService.ts calls promptLibrary.getPrompt(id, vars). No external API change β all callers unchanged.
Versioning: Template version: '1.0.0' bumped when prompt changes. Old prompts cached in IDB with version key for cache invalidation.
A/B testing: abTestVariants?: PromptTemplate[] β getPrompt() randomly selects from variants (50/50); selection logged to usageAnalyticsService if analytics enabled.
packages/ai-core/src/index.ts MODIFY (WorkerBus v2)
src/workers/inference.worker.ts CREATE
services/ai/gpuResourceManager.ts CREATE
services/ai/deviceHealthService.ts CREATE
services/ai/ecoModeService.ts CREATE
services/ai/inferenceProgressEmitter.ts CREATE
services/ai/aiInferenceCacheService.ts CREATE
services/ai/localEmbeddingService.ts CREATE
services/ai/localNlpService.ts CREATE
services/ai/modelRecommendations.ts MODIFY (dynamic recommendation)
services/promptLibrary.ts CREATE
services/localRagService.ts RENAME+UPGRADE from localRagIndex.ts
services/pluginRegistry.ts CREATE
services/usageAnalyticsService.ts CREATE
services/crossProjectIndexService.ts MODIFY (AI enrichment)
services/geminiService.ts MODIFY (new prompts + promptLibrary delegation)
components/ui/BottomSheet.tsx CREATE
components/settings/LocalAiDownloadProgress.tsx CREATE
components/settings/GpuMetricsPanel.tsx CREATE
components/settings/PromptLibraryPanel.tsx CREATE
components/settings/AiSections.tsx MODIFY (wire progress + metrics)
components/StyleTransferView.tsx CREATE
components/ChapterAutoGenView.tsx CREATE
components/CharacterArcVisualizerView.tsx CREATE
hooks/useSwipeGesture.ts CREATE
hooks/useLongPress.ts CREATE
hooks/useHaptics.ts CREATE
hooks/useStyleTransferView.ts CREATE
hooks/useChapterAutoGenView.ts CREATE
hooks/useCharacterArcVisualizerView.ts CREATE
contexts/StyleTransferContext.tsx CREATE
contexts/ChapterAutoGenContext.tsx CREATE
contexts/CharacterArcVisualizerContext.tsx CREATE
components/writing/WriterViewUI.tsx MODIFY (swipe gesture, BottomSheet)
components/ManuscriptView.tsx MODIFY (PointerEvent resize)
features/featureFlags/featureFlagsSlice.ts MODIFY (add enableChapterAutoGen, enableCharacterArcVisualizer, enablePromptLibrary, enablePluginRegistry, enableUsageAnalytics)
App.tsx MODIFY (lazy-load 3 new views)
types/plugin.ts CREATE
types.ts MODIFY (new shared interfaces)
locales/*/settings.json MODIFY (all 5 locales, ~35 new keys)
locales/*/common.json MODIFY (all 5 locales, ~20 new keys)
locales/*/writer.json MODIFY (all 5 locales, ~15 new keys)New test files (all in tests/unit/ or tests/e2e/):
tests/unit/aiCoreWorkerBus.test.ts MODIFY (backpressure, preemption, cancel)
tests/unit/gpuResourceManager.test.ts CREATE
tests/unit/deviceHealthService.test.ts CREATE
tests/unit/inferenceProgressEmitter.test.ts CREATE
tests/unit/aiInferenceCacheService.test.ts CREATE
tests/unit/localEmbeddingService.test.ts CREATE
tests/unit/localNlpService.test.ts CREATE
tests/unit/inferenceWorker.test.ts CREATE
tests/unit/aiCoreFallbackPaths.test.ts MODIFY (real ONNX/Transformers paths)
tests/unit/localRagService.test.ts RENAME+UPGRADE
tests/unit/promptLibrary.test.ts CREATE
tests/unit/communityTemplateService.test.ts MODIFY (validation, rating, moderation)
tests/unit/pluginRegistry.test.ts CREATE
tests/unit/usageAnalyticsService.test.ts CREATE
tests/unit/useSwipeGesture.test.ts CREATE
tests/unit/useLongPress.test.ts CREATE
tests/unit/settings/LocalAiDownloadProgress.test.tsx CREATE
tests/unit/settings/GpuMetricsPanel.test.tsx CREATE
tests/unit/settings/PromptLibraryPanel.test.tsx CREATE
tests/unit/BottomSheet.test.tsx CREATE
tests/unit/StyleTransferView.test.tsx CREATE
tests/unit/ChapterAutoGenView.test.tsx CREATE
tests/unit/CharacterArcVisualizerView.test.tsx CREATE
tests/e2e/mobile-touch.spec.ts CREATEsanitizeForPrompt()β ai-core/index.ts:120. Use for ALL new AI prompt inputsdetectWebGpuDetails()β services/ai/webGpuDetectorService.ts. Feed intodeviceHealthServiceWorkerBusclass β ai-core/index.ts:45. Use inlocalEmbeddingService,localNlpService,localRagServiceelectSingleHeavyInferenceTab()β ai-core/tabLeaderElection.ts. Keep; complement withgpuResourceManagerdbService.tsβ Use existing IDB dual-DB foraiInferenceCacheService(store instorycraft-data-db)assertCloudAiAllowed()β services/ai/aiPolicy.ts. Call before any cloud AI tool dispatchresolveProviderFallbackChain()β services/ai/hybridFallback.ts. Still used by orchestration layeruseTranslation()β hooks/useTranslation.ts. ALL new UI stringsuseAppDispatch/Selectorβ app/hooks.ts. ALL new hooksAPP_SECTIONSβ constants/sections.tsx. Add new views (StyleTransfer, ChapterAutoGen, CharacterArc)SectionIconβ components/ui/SectionIcon.tsx. Apply to all 3 new view headersFocusTrap/ modal pattern β existing Modal.tsx. Reuse for BottomSheet focus management
pnpm run lint && pnpm run i18n:check && pnpm run typecheck
pnpm exec vitest run <specific test file>pnpm run lint && pnpm run i18n:check && pnpm run typecheck && pnpm run test:run && pnpm run test:coverage && pnpm run build && pnpm run bundle:budgetTarget: branches coverage β₯ 48%, statements β₯ 64%
pnpm run lint && pnpm run i18n:check && pnpm run typecheck && pnpm run test:run && pnpm run test:coverage && pnpm run build && pnpm run bundle:budget
# Then (in separate terminal or CI):
pnpm run test:e2e # Mobile Chrome Pixel 5
pnpm run storybookTarget: branches β₯ 55%, statements β₯ 67%, Lighthouse β₯ 0.95
- Run
pnpm run test:e2ewitha11y.spec.tsβ currently covers all views via axe-core - New views (StyleTransfer, ChapterAutoGen, CharacterArc) must be added to
a11y.spec.ts LocalAiDownloadProgressWCAG checklist: verify all ARIA attributes inLocalAiDownloadProgress.test.tsxBottomSheetfocus trap: verify withBottomSheet.test.tsxuserEvent keyboard navigation
inference.worker.ts: verifyisTrustedWorkerMessage()checks origin before processingaiInferenceCacheService.ts: confirm inference results stored encrypted (or in-memory only for sensitive content)promptLibrary.ts: verify import validation uses zod/JSON schema before executingpluginRegistry.ts: confirm plugins cannot access Redux store directly (only via exported API)usageAnalyticsService.ts: verify PII strip: all user text excluded, only event metadata
- Inference timeout: WebLLM 45s desktop / 20s mobile; Transformers.js ONNX 30s desktop / 15s mobile
- Embedding: all-MiniLM-L6-v2 inference < 2s desktop; < 5s mobile
- Cache lookup: < 10ms (in-memory LRU)
- WorkerBus enqueue + priority sort: < 1ms for 100 concurrent tasks
- BottomSheet animation: CSS 300ms ease-out (no JS animation loop)
- New Vite chunks:
vendor-ai-worker(inference.worker.ts deps β€ 15 MB), existingvendor-ai-onnxunchanged
- Branch:
feat/v1.5-master-perfection - Commit prefix:
feat(local-ai):,feat(mobile):,feat(prompt-lib):,feat(templates):,feat(plugins):,docs:,test: - All commits: conventional commits + QNBS-v3 comments on non-trivial code changes
- PR: single
[v1.5] Master Perfection RunPR with full description referencing this plan
Architectural completeness:
- β All 4 layers active (Ollama Layer-0 already in fetchAdapter; Layer-1 WebLLM ready; Layer-2 ONNX activated; Layer-3 Transformers.js activated; Layer-4 Heuristic fixed)
- β GPU mutex prevents VRAM collision between webllm + onnx-webgpu
- β Device health drives model recommendation β no more one-size-fits-all
- β Inference cache reduces redundant model calls by estimated 40-60%
- β All new UI: WCAG 2.2 AA (ARIA, focus trap, live regions)
- β All new strings: i18n (5 locales)
- β All new code: unit tests first (TDD order)
- β
No breaking changes (backward compat on
searchLocalRag(),runLocalTextGeneration()) - β View pattern enforced: components/X.tsx + hooks/useXView.ts + contexts/XContext.tsx for all 3 new views
- β Feature flags for all experimental features (enableChapterAutoGen, enableCharacterArcVisualizer, enablePromptLibrary, enablePluginRegistry, enableUsageAnalytics)
- β Coverage target: 55% branches achievable with 24 new test files