Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

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

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

Expand Down
5 changes: 3 additions & 2 deletions components/manuscript/ManuscriptEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const ManuscriptEditor: FC<{ isFocusMode: boolean }> = React.memo(({ isFo
// QNBS-v3: Defer highlight computation so keystroke → textarea updates stay synchronous even for long scenes.
const deferredContent = useDeferredValue(activeSection?.content ?? '');
const isHighlightPending = deferredContent !== (activeSection?.content ?? '');
// QNBS-v3 (#341): retain a visible pending cue without dropping light-sepia mirror contrast below 4.5:1.
Comment thread
qnbs marked this conversation as resolved.
Resolved

// QNBS-v3 (#341): shared with components/ui/Textarea.tsx and ContextPanel.tsx — the raw enum
// value (e.g. 'custom') is not a valid font-family, and the highlight overlay must render the
Expand Down Expand Up @@ -398,7 +399,7 @@ export const ManuscriptEditor: FC<{ isFocusMode: boolean }> = React.memo(({ isFo
ref={highlightRef}
data-testid="manuscript-editor-mirror"
dir={dir}
className={`absolute inset-0 p-4 sm:p-6 md:p-12 pt-2 leading-relaxed pointer-events-none overflow-auto max-w-3xl mx-auto transition-all duration-500 ${isFocusMode ? 'max-w-4xl pt-12' : ''} ${isHighlightPending ? 'opacity-70' : 'opacity-100'}`}
className={`absolute inset-0 p-4 sm:p-6 md:p-12 pt-2 leading-relaxed pointer-events-none overflow-auto max-w-3xl mx-auto transition-all duration-500 ${isFocusMode ? 'max-w-4xl pt-12' : ''} ${isHighlightPending ? 'opacity-75' : 'opacity-100'}`}
style={editorStyles}
aria-hidden="true"
>
Expand Down Expand Up @@ -526,4 +527,4 @@ export const ManuscriptEditor: FC<{ isFocusMode: boolean }> = React.memo(({ isFo
</div>
);
});
ManuscriptEditor.displayName = 'ManuscriptEditor';
ManuscriptEditor.displayName = 'ManuscriptEditor';
20 changes: 19 additions & 1 deletion tests/unit/manuscript/ManuscriptEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const reactDeferredMock = vi.hoisted(() => ({ forceStaleValue: false }));

vi.mock('react', async () => {
const actual = await vi.importActual<typeof import('react')>('react');
const useDeferredValue = <T,>(value: T): T =>
reactDeferredMock.forceStaleValue ? ('' as T) : value;
return { ...actual, useDeferredValue };
});

// ---------------------------------------------------------------------------
// Mocks
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -180,6 +189,7 @@ describe('ManuscriptEditor', () => {
ltMock.available = false;
ltMock.matches = [];
ltMock.applySuggestion.mockReset();
reactDeferredMock.forceStaleValue = false;
});

it('shows empty state when no section is selected', () => {
Expand Down Expand Up @@ -321,5 +331,13 @@ describe('ManuscriptEditor', () => {
} as unknown as React.UIEvent<HTMLTextAreaElement>);
expect(mirror.scrollTop).toBe(360);
});

// QNBS-v3 (#341): deferred mirror content must retain WCAG contrast while a long edit settles.
it('does not dim the visible mirror below the contrast contract while deferred', () => {
reactDeferredMock.forceStaleValue = true;
render(<ManuscriptEditor isFocusMode={false} />);
const mirror = screen.getByTestId('manuscript-editor-mirror');
expect(mirror).not.toHaveClass('opacity-70');
});
Comment thread
qnbs marked this conversation as resolved.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
});
});
});
Loading