Skip to content

Commit 4d54ad2

Browse files
docs(v0.20): pre-release hygiene - smoke spec, help, API, lessons, coverage
Pre-release sweep per release-workflow.md Step 11 + ai-workflow.md "Order for new features" step 7 (smoke spec required for every new UI feature). - e2e/smoke/ai-review.spec.ts: 4 Playwright smoke tests for the AI Review UI surface (three radio focus buttons render, non-prose warning toggles per chapter type, mocked happy-path download-report link). Uses route mocks to avoid requiring a live LLM backend. - docs/help/{de,en}/ai.md: rewrote Chapter Review section with the three focus modes (Style / Consistency / Beta Reader), cost estimate, non-prose warning, persistence + download, async progress. Mirrors the actual v0.20 UI. - docs/API.md: documents the 8 new /api/ai/ endpoints including the async flow, SSE stream, FileResponse download, cost estimate, and UI metadata endpoint. - .claude/rules/lessons-learned.md: new AI Review extension section with 7 pitfalls discovered during the release window (backup-import soft-delete dedup, manuscripta output/ cleanup between format runs, Pandoc multi-doc YAML, CSS specificity trap h2+p vs p:not(:first-child), TipTap useEditor not flushing editor.storage, prefix testid overmatch, recovery draft contentHash contract). - docs/audits/current-coverage.md: v0.20.0 addendum with the +181 test delta (1,271 -> 1,452 total automated tests).
1 parent 4207fa3 commit 4d54ad2

6 files changed

Lines changed: 334 additions & 7 deletions

File tree

.claude/rules/lessons-learned.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,49 @@ Stability filter:
395395

396396
- `vite-plugin-pwa@1.2.0` (latest as of 2026-04-18) lists Vite `^3 || ^4 || ^5 || ^6 || ^7` in its peer deps. No Vite 8 support yet. Attempting Vite 6 -> 8 with PWA installed will either refuse to install or runtime-break.
397397
- This is why DEP-04 landed as Vite 6 -> 7, not 6 -> 8. The Vite 8 bump is deferred until vite-plugin-pwa publishes compat. Check `npm view vite-plugin-pwa peerDependencies` before attempting the next bump.
398+
399+
## AI Review extension (v0.20.0)
400+
401+
### Backup import must check soft-delete state before dedup
402+
403+
- `backup_import._restore_book_from_dir` previously treated any pre-existing `Book.id` in the DB as "already imported" and returned False. That check predates the soft-delete / trash feature: a backup made before trashing silently could not be restored once the books had been moved to trash - the importer saw them in the DB (with `deleted_at` set) and refused to rebuild.
404+
- Fix: when the pre-existing row is soft-deleted, HARD-delete it along with its chapters + assets, then fall through to the fresh-insert path. Do NOT try to revive via per-attribute setattr: the backup JSON does not carry every NOT NULL column (`ai_tokens_used`, `created_at`, `updated_at`), so SQLAlchemy emits an UPDATE that sets those to NULL and the integrity constraint trips. Hard-delete + fresh-insert sidesteps the whole partial-update dance and matches the backup's snapshot semantics.
405+
- Generalizes: any "idempotent by id" import path added before a soft-delete feature becomes silently buggy. Always branch on `deleted_at IS NULL` when deduping.
406+
407+
### manuscripta `run_export` moves `output/` to `backup/` on every call
408+
409+
- `manuscripta.export.book.run_export` copies the existing `project_dir/output/` to `project_dir/backup/` at the start of every invocation and creates a fresh `output/`. A list of per-format output paths collected across a batch-export loop contains stale paths by the time the loop finishes.
410+
- Symptom in v0.19.x: `FileNotFoundError` at `zipfile.ZipFile.write(f, f.name)` inside `/api/books/{id}/export/batch`, referencing a file that existed moments earlier.
411+
- Fix: after each `run_pandoc` call, IMMEDIATELY copy the produced file into a stable staging directory (`tmp_dir/batch/`) and zip from there. Do NOT keep references to files under `project_dir/output/` across subsequent `run_export` calls.
412+
413+
### Pandoc-wrapped metadata.yaml is a multi-doc YAML stream
414+
415+
- The project exporter wraps `metadata.yaml` in Pandoc-style `---` / `---` document markers. PyYAML's `safe_load` expects exactly one document and raises `yaml.composer.ComposerError` on any trailing `---` (even if the second document is empty).
416+
- Fix: use `yaml.safe_load_all(f)` and return the first non-empty document. Handles both the bare and the Pandoc-wrapped shapes in one code path.
417+
- Regression: `smart_import` crashing with 500 on a ZIP that `/api/backup/export` had just produced.
418+
419+
### CSS specificity trap: `h2 + p` loses to `p:not(:first-child)`
420+
421+
- Specificity for `[data-app-theme="classic"] .ProseMirror h2 + p`: (0, 1, 1, 2) - 1 attr, 1 class, 2 elements.
422+
- For `[data-app-theme="classic"] .ProseMirror p:not(:first-child)`: (0, 1, 2, 1) - 1 attr, 1 class + 1 pseudo-class = 2 "classes", 1 element. The pseudo-class pushes the base rule ahead of the adjacent-sibling override.
423+
- When both rules match (a paragraph that directly follows a heading AND is not the first child), the higher-specificity `:not(:first-child)` wins and the heading override never applies.
424+
- Fix: append `:not(:first-child)` to each `h* + p` override. Combined (0, 1, 2, 2) beats the base (0, 1, 2, 1).
425+
- Generalizes: any CSS override against a `:not(:first-child)` base rule needs at least the same pseudo-class weight.
426+
427+
### TipTap `useEditor` does NOT flush `editor.storage` reads to React
428+
429+
- A status-bar word count written as `{editor?.storage.characterCount?.words()}` inline in JSX looks fine but does not update reliably after keyboard input. TipTap's built-in re-render on transaction fires for selection changes but not for every content transaction we rely on.
430+
- Reliable fix: subscribe explicitly via `editor.on('update', () => forceUpdate())`, or mirror the count into React state via `useState` + `editor.on('update')`.
431+
- Surfaced as a smoke-test failure in `smoke/editor-formatting.spec.ts` "word count updates after typing". Currently skipped with a reference to issue #9 until the subscribe pattern is wired up.
432+
433+
### Prefix testid selectors match every nested testid that shares the prefix
434+
435+
- A selector like `[data-testid^='book-card-']` cleanly matches each card root AND every nested child testid that shares the prefix (`book-card-menu-{id}`, `book-card-menu-delete-{id}`). `toHaveCount(N)` returns `2N` or more per visible card.
436+
- Fix: `[data-testid^='book-card-']:not([data-testid*='-menu-'])`, or give the root a distinct testid like `book-card-root-{id}`.
437+
- Same shape as the `[class^=""]` overmatch antipattern. Always test a prefix selector against the full rendered surface before shipping.
438+
439+
### IndexedDB recovery draft `contentHash` is a MATCH check, not a MISMATCH
440+
441+
- `frontend/src/db/drafts.ts#checkForRecovery` returns a draft iff `draft.contentHash === hashContent(serverContent)` AND `draft.content !== serverContent`. The contract is "this draft was written against THIS server state, local content is newer". Seeding a test draft with `contentHash: '_mismatch_'` will NOT trigger the recovery banner.
442+
- A misleading test comment saying "must differ from server hash" burned multiple sessions before the `checkForRecovery` source was re-read.
443+
- When writing tests that seed IndexedDB, compute the hash of the real server content inside the seed script rather than using a sentinel value.

docs/API.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Groups under `backend/app/routers/` (router prefix in parentheses):
3232
| `licenses` | `/api/licenses` | License activation and management |
3333
| `settings` | `/api/settings` | App settings, plugin settings, theme |
3434
| `plugin_install` | `/api/plugins` | Plugin ZIP installation, discovery, health |
35+
| `ai` | `/api/ai` | Core AI: chat, generate, review (sync + async), marketing text, providers |
3536

3637
Example endpoints (not exhaustive):
3738

@@ -46,6 +47,23 @@ Example endpoints (not exhaustive):
4647
- `GET /api/export/jobs/{id}/stream` - Server-Sent Events progress feed
4748
- `POST /api/books/{id}/audiobook/dry-run` - sample preview + cost preview
4849

50+
### AI review extension (v0.20.0)
51+
52+
The `/api/ai/` router hosts the multi-provider AI features. The review path supports both synchronous (legacy) and async flows:
53+
54+
- `POST /api/ai/review` - synchronous chapter review; accepts `chapter_type` so the system prompt can tailor feedback per section (e.g. dedication vs chapter).
55+
- `POST /api/ai/review/async` - submit a review as a background job. Returns `{job_id, review_id}`. The worker persists a Markdown report to `uploads/{book_id}/reviews/{review_id}-{chapter-slug}-{YYYY-MM-DD}.md`.
56+
- `GET /api/ai/jobs/{job_id}` - poll current status, progress and (when terminal) the inline review.
57+
- `GET /api/ai/jobs/{job_id}/stream` - Server-Sent Events feed of progress events (`review_start`, `review_llm_call`, `review_done`, `stream_end`).
58+
- `DELETE /api/ai/jobs/{job_id}` - cancel a running review.
59+
- `GET /api/ai/review/{review_id}/report.md?book_id=...` - download the persisted Markdown report.
60+
- `POST /api/ai/review/estimate` - rough input-token + USD cost estimate (uses a chars/4 heuristic and a small per-model pricing dict).
61+
- `GET /api/ai/review/meta` - UI metadata: all focus values, the three primary UI focus values, non-prose chapter types, supported languages, chapter types. The frontend reads this to drive the radio buttons + non-prose warning without hardcoding.
62+
63+
Review focus values: `style` (existing) plus `consistency` (new: within-chapter contradictions, distinct from `coherence` which checks logical flow) and `beta_reader` (new: simulated first-read feedback). Legacy values (`coherence`, `pacing`, `dialogue`, `tension`) stay on the API for power users but are no longer exposed in the UI.
64+
65+
Cascade on chapter delete: when a chapter is removed, all review Markdown files whose filename contains the chapter's slug are deleted alongside the chapter row.
66+
4967
---
5068

5169
## Plugin routers

docs/audits/current-coverage.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,50 @@
1-
# Test Coverage Audit - v0.17.0
1+
# Test Coverage Audit - v0.17.0 (with v0.20.0 addendum)
22

3-
Audit date: 2026-04-18
3+
Audit date: 2026-04-18 (primary), 2026-04-20 (v0.20.0 addendum below)
44
Previous audit: 2026-04-13 (archived as `history/2026-04-13-coverage.md`)
5-
Scope: everything on `main` as of commit `decb531`.
5+
Scope: everything on `main` as of commit `decb531` (primary) plus
6+
the v0.19.1 -> v0.20.0 delta.
7+
8+
## v0.20.0 addendum
9+
10+
Delta since the 2026-04-18 primary audit:
11+
12+
| Suite | 2026-04-18 | 2026-04-20 (v0.20.0) | Delta |
13+
|-------|-----------|----------------------|-------|
14+
| Backend (`backend/tests/`) | 511 | 638 | +127 |
15+
| Plugins in `make test` matrix | 409 | 409 | 0 |
16+
| Frontend (Vitest) | 351 | 405 | +54 |
17+
| E2E (Playwright smoke) | 135 passing / 31 failing | 162 passing / 0 failing / 4 skipped | +27 pass, -31 fail |
18+
| **Total automated tests** | 1,271 | 1,452 | +181 |
19+
20+
### Gaps closed in the v0.20.0 window
21+
22+
- **AI Review Extension**: 31 new backend tests across `test_ai_review.py` (47 total; includes prompt-builder for 8 languages, chapter-type injection, cost estimate, async review flow via JobStore + SSE, FileResponse download, cascade on chapter delete) and `test_ai_review_store.py` (15 direct unit tests: slugify, filename shape, report roundtrip, cascade boundary safety, DELETE chapter cascade integration).
23+
- **Backup regressions**: `test_backup_import_revive.py` pins 9 data-critical paths (soft-deleted book revival, idempotent live re-import, merge with non-empty DB, multi-doc YAML parse, smart-import project ZIP roundtrip, batch-export pandoc-skip gate).
24+
- **Playwright smoke for AI Review**: new `smoke/ai-review.spec.ts` (4 tests) covering the three radio focus buttons, non-prose warning visibility per chapter type, and a mocked happy-path download-report flow. Closes the step-7 protocol gap for the new UI feature.
25+
- **Frontend Vitest for AI Review**: `src/data/ai-review-strings.test.ts` (8 tests) pins 8-language coverage of the book-language status + warning strings and the non-prose chapter-type set's parity with the backend `NON_PROSE_TYPES` constant.
26+
- **Smoke test-infra cleanup**: `dashboard-filters` selector narrowed, content-safety recovery seed fixed, Ctrl+Z timing, theme reload seed via page.evaluate, export content-type tolerance, trash restore view switch, Classic first-line-indent CSS specificity, CreateBookModal Select testid, dashboard sort direction expectation, export dialog migrated to download event, word-count + viewport-zoom tracked as skipped with issue #9 references.
27+
28+
### Still open as of v0.20.0
29+
30+
- **Audiobook generation E2E** - still no dedicated smoke spec; same as 2026-04-18.
31+
- **Image/asset upload in editor E2E** - still no spec.
32+
- **TipTap useEditor -> React re-render for status bar** - currently ships with a stale word count on keyboard input. Lessons-learned has the subscribe pattern documented; dedicated fix + test un-skip is deferred to post-v0.20.0.
33+
- **Chapter-sidebar dropdown layout at 125% / 150% CSS zoom** - 3 skipped smoke tests; needs Radix Popper collision rework or a viewport-downscale test proxy.
34+
35+
### v0.20.0 running totals
36+
37+
| Suite | Count |
38+
|-------|-------|
39+
| Backend only | 638 |
40+
| Plugins via `make test` matrix | 409 |
41+
| Total Python tests in repo | 1,047 |
42+
| Frontend (Vitest) | 405 |
43+
| E2E (Playwright smoke) | 162 passing / 4 skipped |
44+
45+
---
46+
47+
## Primary audit (2026-04-18 baseline)
648

749
## Deltas since 2026-04-13
850

docs/help/de/ai.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,44 @@ Die KI passt ihre Vorschläge an das Genre und die Sprache deines Buches an.
4141

4242
## Kapitel-Review
4343

44-
Klicke den **Review**-Tab im KI-Panel. Die KI analysiert das gesamte Kapitel und gibt strukturiertes Feedback:
44+
Klicke den **Review**-Tab im KI-Panel. Die KI analysiert das gesamte Kapitel und liefert einen strukturierten Markdown-Bericht, den du speichern und wieder öffnen kannst.
45+
46+
### Fokusmodi
47+
48+
Wähle genau einen Fokus, bevor du **Kapitel reviewen** anklickst:
49+
50+
- **Stil** - Schreibstil: Wortwahl, Satzvariation, Lesbarkeit, Stimmkonsistenz. Nutze diesen Modus, wenn die Story funktioniert, die Prosa aber geschliffen werden soll.
51+
- **Konsistenz** - Widersprüche innerhalb des Kapitels: Fakten, Zeitlinie, Figurenmerkmale, Orte, Objektbeschreibungen. Fängt die kleinen "ihr Mantel war vor zwei Seiten blau, jetzt grün"-Fehler ab, bevor sie ein Leser entdeckt.
52+
- **Testleser** - offenes Erstleser-Feedback: Was zieht rein, was zieht sich, was verwirrt, welche Fragen bleiben offen. Nutze diesen Modus, wenn das Kapitel fertig ist und du einen "frische Augen"-Durchgang willst.
53+
54+
Die vier Legacy-Fokuswerte (Kohärenz, Pacing, Dialog, Spannung) sind auf API-Ebene weiterhin verfügbar, tauchen aber nicht mehr im UI auf.
55+
56+
### Kostenschätzung
57+
58+
Der Start-Button zeigt eine grobe Schätzung der Input-Tokens und der USD-Kosten basierend auf Kapitellänge und konfiguriertem Modell (z.B. `~5k Tokens, ~$0.075`). Die Schätzung ist konservativ; die tatsächliche Nutzung liegt meist darunter. Ohne bekanntes Modell erscheint keine Kostenangabe.
59+
60+
### Nicht-Prosa-Kapitel
61+
62+
Für Kapiteltypen, die keine erzählende Prosa sind (Titelseite, Copyright, Inhaltsverzeichnis, Impressum, Index, Schmutztitel, Auch-vom-Autor, Nächster-Band, Call-to-Action, Endnoten, Literatur, Glossar), zeigt Bibliogon eine kleine Warnung über dem Start-Button. Du kannst das Review trotzdem starten; das Feedback ist dann meist eingeschränkter als bei Prosa.
63+
64+
### Strukturiertes Ergebnis
65+
66+
Jedes Review nutzt die gleiche Struktur:
4567

4668
- **Zusammenfassung** - ein Satz zum Kapitelinhalt
4769
- **Stärken** - was gut funktioniert, mit konkreten Verweisen
4870
- **Vorschläge** - konkrete Verbesserungen mit Erklärungen
4971
- **Gesamtbewertung** - eine kurze Einschätzung
5072

51-
Das Review berücksichtigt das Genre deines Buches und gibt genregerechtes Feedback (z.B. Pacing-Feedback für Thriller, Klarheits-Feedback für Sachbücher).
73+
Das Review berücksichtigt Genre, Sprache (alle 8 unterstützten UI-Sprachen) und den ausgewählten Kapiteltyp, so dass das Feedback zum jeweiligen Abschnitt passt (z.B. Pacing-Feedback für Thriller, minimale Ton-Hinweise bei einer Widmung, Compliance-Hinweise auf Copyright-Seiten).
74+
75+
### Persistenz + Download
76+
77+
Jedes Review wird als Markdown-Datei unter `uploads/{book_id}/reviews/` gespeichert, Dateiname nach dem Schema `{review-id}-{kapitel-slug}-{YYYY-MM-DD}.md`. Ein **Bericht herunterladen**-Button erscheint neben dem Ergebnis, so kannst du die Datei lokal sichern, in ein Schreib-Notizbuch legen oder an einen Commit hängen. Beim Löschen eines Kapitels werden die zugehörigen Review-Dateien automatisch mit aufgeräumt.
78+
79+
### Asynchroner Fortschritt
80+
81+
Grosse Kapitel brauchen 5-60 Sekunden. Das Review läuft als Hintergrund-Job; der Editor bleibt bedienbar, und eine rotierende Statusmeldung (in der Sprache deines Buches) zeigt den Fortschritt. Du kannst das KI-Panel mitten im Review schliessen; sobald das Review fertig ist, taucht das Ergebnis beim erneuten Öffnen wieder auf.
5282

5383
## Marketing-Texte
5484

docs/help/en/ai.md

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,44 @@ The AI adapts its suggestions to your book's genre and language.
4141

4242
## Chapter review
4343

44-
Click the **Review** tab in the AI panel. The AI analyzes your entire chapter and provides structured feedback:
44+
Click the **Review** tab in the AI panel. The AI analyzes your entire chapter and returns a structured Markdown report you can save and re-open.
45+
46+
### Focus modes
47+
48+
Pick exactly one focus before clicking **Review chapter**:
49+
50+
- **Style** - writing style: word choice, sentence variety, readability, voice consistency. Use this when the story works but the prose needs polish.
51+
- **Consistency** - internal contradictions within the chapter: facts, timing, character traits, locations, object descriptions. Use this to catch the small "her coat was blue two pages ago, now green" kind of mistakes before a reader spots them.
52+
- **Beta Reader** - open-ended first-read feedback: what engages, what drags, what confuses, questions left in the reader's mind. Use this when the chapter is done and you want a "fresh eyes" pass.
53+
54+
The three legacy focus values (Coherence, Pacing, Dialogue, Tension) are still supported on the API level for power users but are no longer exposed in the UI.
55+
56+
### Cost estimate
57+
58+
The Start button shows a rough input-token count and USD cost estimate based on your chapter length and the configured model (e.g. `~5k tokens, ~$0.075`). The estimate is conservative; actual usage is usually lower. No estimate is shown when the model is unknown to Bibliogon's price table.
59+
60+
### Non-prose chapters
61+
62+
For chapter types that are not narrative prose (title page, copyright, table of contents, imprint, index, half title, also-by-author, next-in-series, call-to-action, endnotes, bibliography, glossary), Bibliogon shows a small warning above the Start button. You can still run a review; the feedback may be more limited than for prose.
63+
64+
### Structured output
65+
66+
Every review uses the same structure:
4567

4668
- **Summary** - one sentence about the chapter's content
4769
- **Strengths** - what works well, with specific references
4870
- **Suggestions** - concrete improvements with explanations
4971
- **Overall** - a brief assessment
5072

51-
The review considers your book's genre and provides genre-appropriate feedback (e.g. pacing feedback for thrillers, clarity feedback for non-fiction).
73+
The review considers your book's genre, language (all 8 supported UI languages), and the selected chapter type, so feedback is appropriate to the section (e.g. pacing feedback for thrillers, minimal tone notes on a dedication, compliance hints on copyright pages).
74+
75+
### Persistence + download
76+
77+
Every review is saved as a Markdown file under `uploads/{book_id}/reviews/` with a filename like `{review-id}-{chapter-slug}-{YYYY-MM-DD}.md`. A **Download report** button appears next to the result so you can save the file locally for a writing notebook, attach it to a commit, or keep a history per chapter. When a chapter is deleted, its review files are automatically cleaned up alongside the chapter content.
78+
79+
### Async progress
80+
81+
Large chapters can take 5-60 seconds. The review runs as a background job; the editor stays usable, and a rotating status message (in your book's language) is shown while the analysis runs. You can close the AI panel mid-review; when the review finishes, the result re-appears on reopen.
5282

5383
## Marketing text
5484

0 commit comments

Comments
 (0)