docs(architecture): record Phase 4.5 builder polish
CI and Deploy / test (push) Failing after 2m4s
CI and Deploy / deploy (push) Has been skipped

cv-builder.md (outline-driven Content tab, DnD, rich text, preview page nav,
public-CV routing fix + deployment note), cv-theme-engine.md (rich text/ATS/
print), roadmap Phase 4.5 shipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
cesnimda
2026-07-18 14:48:33 +02:00
parent 582c4e07e9
commit 074c78a7ef
3 changed files with 51 additions and 15 deletions
+27 -12
View File
@@ -60,9 +60,19 @@ tenant filter — there is no current user). Unknown/private slug → 404
`/career/builder` lists variants (`CvBuilderPage`); the editor (`CvBuilderEditor`) is three tabs —
**Content**, **Customize**, **AI Tools** (plus **History**) — beside an always-on live preview that
re-renders through `POST /api/cv/preview` on a 350 ms debounce. Edits autosave on an 800 ms debounce
(`source: autosave`), appending a version each save. `/cv/:slug` (`PublicCvPage`) renders a public CV
in a sandboxed iframe.
re-renders through `POST /api/cv/preview` on a 300 ms debounce. Edits autosave on an 800 ms debounce
(`source: autosave`), appending a version each save; the header shows Unsaved / Saving / Saved.
`/cv/:slug` (`PublicCvPage`) renders a public CV in a sandboxed iframe.
**Content tab** reads `GET /api/cv/outline` (the master profile as sections+entries with ItemKeys).
Sections and entries reorder by native drag-and-drop (`useDragReorder`, no dependency) or arrow
buttons (the keyboard-accessible path); entry order is stored per section as `ItemOrder` on the
variant, never on the profile. Each entry exposes hide, title/subtitle override, and rich-text bullet
editing (`RichTextField` — a markdown toolbar over a textarea; storage stays plain text, the server
renderer converts the `**bold** *italic* __underline__ [text](url)` whitelist to safe HTML).
**Preview** has zoom presets (±, slider, Fit), a measured page count with prev/next page navigation
and dashed page-break indicators, and an "updating…" chip. **Customize** badges ATS-friendly themes.
## AI
@@ -75,14 +85,19 @@ carries "preserve every factual claim — never invent" (MASTER_IMPLEMENTATION_G
- **New theme**: append a `CvTheme` to the catalog (`cv-theme-engine.md`). No renderer change.
- **New section kind**: add a builder in `CvVariantResolver` + a `Kind` branch in `ThemedCvRenderer`.
- **DOCX export**: add an `IDocxExporter` consuming the same `CvRenderModel`; the model is format-neutral.
- **Rich text / page numbers**: see `cv-theme-engine.md` "Deliberately not here (yet)".
- **PDF page numbers**: `ShowPageNumbers` is carried but the footer is a Playwright `footerTemplate`
concern, not CSS — see `cv-theme-engine.md`.
## Known limitation
## Deployment — public CV routing (fixed 2026-07-18)
Direct external loads of any deep link (including `/cv/{slug}`) currently bounce to the app root — the
CSR lift-and-shift Next.js static-export setup only prerenders `/`, so a hard navigation to a deep path
doesn't reach the react-router route (in-app client navigation works). This affects the whole SPA, not
just public CVs, but public CVs are the first feature that depends on external direct links working.
Fix path: serve `index.html` for all non-`/api` paths so react-router owns routing (nginx already has
`try_files $uri /index.html`; the redirect is client-side in the Next shell). Tracked as a follow-up —
see `docs/architecture/frontend.md` / the Next.js migration note.
`/cv/{slug}` (and every deep link) must work on direct navigation, refresh, and shared links. The app
is a React Router SPA behind Next.js `output: export`, which previously prerendered only `/`, so a hard
load of any deep path hit Next's client not-found before React Router could route it.
**Fix:** the single `app/page.tsx` is now an optional catch-all `app/[[...slug]]/` (a server
`page.tsx` exporting `generateStaticParams` + a `"use client"` `ClientShell`, because
`generateStaticParams` can't live in a client file). The catch-all matches every client path, so React
Router owns routing on any hard load. Build still emits just `index.html`; nginx serves it for all
unknown paths via `try_files $uri /index.html` (unchanged). **No infra/nginx change was required**
the fix is entirely in the frontend build. Verified live: `/login` and `/cv/{slug}` both resolve on
direct navigation.
+12 -3
View File
@@ -43,11 +43,20 @@ theme is smoke-tested automatically.
dense), `ats-classic` (single, no graphics), `nordic` (sidebar-right), `elegant` (single, editorial),
`creative` (sidebar-left, bold).
## Rich text, ATS, and print (2026-07-18)
- **Rich-text bullets** are shipped: bullets/summary support `**bold**`, `*italic*`, `__underline__`,
`[text](url)` via `ThemedCvRenderer.Inline` — everything is HTML-escaped first, then a whitelist is
re-applied, so no user tag survives. Storage stays plain text (no HTML, no sanitiser dependency).
- **ATS-friendliness** is a data flag: `CvTheme.AtsFriendly` (set on the single-column themes),
surfaced in `GET /api/cv/themes` and badged in the Customize tab. Two-column (sidebar) themes are not
flagged, as sidebar layouts can trip naive resume parsers.
- **Print quality**: the renderer emits `break-inside: avoid` on entries, `break-after: avoid` on
section headings, and widow/orphan control, so entries don't split across a page in the Playwright
PDF pass.
## Deliberately not here (yet)
- **Rich-text inside bullets.** The render model treats bullets as plain strings, so the builder's text
areas are plain text. Adding bold/italic/links means the render model carries inline runs and the
renderer emits them — a self-contained extension, no theme changes.
- **PDF page numbers.** `ShowPageNumbers` is carried through settings but the actual footer is a
Playwright `footerTemplate` concern (the PDF exporter), not CSS paged-media (Chromium's margin boxes
are unreliable). Wire it in `PlaywrightCvPdfExporter` when needed.
+12
View File
@@ -114,6 +114,18 @@ Goal: `Content Tab → Customise Tab → Preview → Export` (guide `:312`).
> per-item override UI in the Content tab (the API + resolver support it; the editor exposes section-
> level controls). **Known limitation:** external direct loads of `/cv/{slug}` bounce to root (SPA
> deep-link issue, app-wide) — fix in the frontend static-export/routing config.
>
> **Phase 4.5 — Builder Polish SHIPPED 2026-07-18** (commits `585047d`, `e3b255f`, `582c4e0`).
> Public-CV deep links fixed (optional catch-all `app/[[...slug]]`; direct nav/refresh/shared links
> work, no infra change). Native drag-and-drop reorder for sections + entries (keyboard arrows kept).
> Per-item editing via `GET /api/cv/outline`: hide, title/subtitle override, rich-text bullets
> (`**bold** *italic* __underline__ [link]`, escaped-then-whitelisted server-side). `ItemOrder`
> reorders entries without touching the master profile. Preview: zoom presets, measured page count +
> page navigation + page-break indicators, "updating" state. Unsaved/Saving/Saved indicator, loading
> skeletons, better empty states, ATS-friendly theme badge, a11y (ARIA labels, keyboard theme cards,
> focus rings), print-quality page-break CSS, AA contrast fix. Docs: `cv-builder.md`,
> `cv-theme-engine.md`. **Still open:** PDF page-number footer (Playwright `footerTemplate`);
> client-side preview (kept server-rendered for fidelity); `ProfileCvController` split.
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|---|---|---|---|---|---|