docs(architecture): record Phase 4.5 builder polish
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:
@@ -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 —
|
`/career/builder` lists variants (`CvBuilderPage`); the editor (`CvBuilderEditor`) is three tabs —
|
||||||
**Content**, **Customize**, **AI Tools** (plus **History**) — beside an always-on live preview that
|
**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
|
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. `/cv/:slug` (`PublicCvPage`) renders a public CV
|
(`source: autosave`), appending a version each save; the header shows Unsaved / Saving / Saved.
|
||||||
in a sandboxed iframe.
|
`/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
|
## 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 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`.
|
- **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.
|
- **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
|
`/cv/{slug}` (and every deep link) must work on direct navigation, refresh, and shared links. The app
|
||||||
CSR lift-and-shift Next.js static-export setup only prerenders `/`, so a hard navigation to a deep path
|
is a React Router SPA behind Next.js `output: export`, which previously prerendered only `/`, so a hard
|
||||||
doesn't reach the react-router route (in-app client navigation works). This affects the whole SPA, not
|
load of any deep path hit Next's client not-found before React Router could route it.
|
||||||
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
|
**Fix:** the single `app/page.tsx` is now an optional catch-all `app/[[...slug]]/` (a server
|
||||||
`try_files $uri /index.html`; the redirect is client-side in the Next shell). Tracked as a follow-up —
|
`page.tsx` exporting `generateStaticParams` + a `"use client"` `ClientShell`, because
|
||||||
see `docs/architecture/frontend.md` / the Next.js migration note.
|
`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.
|
||||||
|
|||||||
@@ -43,11 +43,20 @@ theme is smoke-tested automatically.
|
|||||||
dense), `ats-classic` (single, no graphics), `nordic` (sidebar-right), `elegant` (single, editorial),
|
dense), `ats-classic` (single, no graphics), `nordic` (sidebar-right), `elegant` (single, editorial),
|
||||||
`creative` (sidebar-left, bold).
|
`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)
|
## 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
|
- **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
|
Playwright `footerTemplate` concern (the PDF exporter), not CSS paged-media (Chromium's margin boxes
|
||||||
are unreliable). Wire it in `PlaywrightCvPdfExporter` when needed.
|
are unreliable). Wire it in `PlaywrightCvPdfExporter` when needed.
|
||||||
|
|||||||
@@ -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-
|
> 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
|
> 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.
|
> 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 |
|
| # | Task | Priority | Difficulty | Dependencies | Expected value |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
|
|||||||
Reference in New Issue
Block a user