feat(layouts): Design cover and standalone pages in the layout editor - #100
Merged
Conversation
Summary: - give every layout a role: response, front cover, back cover, or standalone page - pin the front-cover layout first and the back-cover layout last in the layout tab bar, in the generated book, and across reordering - hide response-bound palette actions on layouts that have no submission behind them - back standalone book pages with a layout instead of their own title, body, and background, and render them through the shared element renderer in preview and PDF export - convert books saved with text-only standalone pages on load, creating one layout per page Rationale: - a standalone page could only hold one heading and one paragraph, so covers could not carry artwork, shapes, or freely placed text - reusing the layout editor keeps one authoring surface and one renderer instead of a second, weaker text-only path - covers are pinned by role rather than position so regeneration and page reordering cannot move them out of place - the conversion runs on read because a stored book has to render correctly before the organizer regenerates it; it is idempotent and probes cheaply so the read path pays for it only once Tests: - bun run format:check, lint, typecheck, build - 263 unit tests across src/domain and src/components passed - integration and end-to-end suites not run locally: this machine has no Docker access, so Postgres and the seeded dev server are unavailable; CI runs them Closes #92 AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
Summary: - ignore .vscode/** in .oxfmtrc.json Rationale: - .vscode/PythonImportHelper-v2-Completion.json is a VS Code extension cache committed directly to main in f14a8f7; it fails format:check on a clean checkout and blocks every pull request - editor state is not source, so excluding it is the narrow fix; the file itself is better removed from the repository separately Tests: - bun run format:check AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
Summary: - run the standalone-page conversion at the start of generateProjectBook - extract the static-text content check in inspectStandalonePage into a named local Rationale: - regeneration reads the stored book directly rather than through getProject, so a book that had never been loaded still held legacy pages; generateBook could not resolve their layout and dropped them instead of carrying them over Tests: - bun run format:check, lint, typecheck - 191 unit tests in src/domain passed AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
The exporter no longer draws standalone pages itself; they render through the shared layout element path like every other page. Tests: - bun run typecheck, lint, format:check - 5 PDF renderer tests passed AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
patriksimms
marked this pull request as ready for review
August 29, 2026 17:02
Addresses the Macroscope review on #100. Summary: - copy layouts.role in duplicateProject, which silently reset every cover and standalone layout to a response layout in the copy - offer only response layouts in the Book review page-layout selector, so a manual assignment cannot be made to a layout generation would discard - reject page sets that PATCH /book must never persist: a response page on a non-response layout, a standalone page on a response layout, a duplicated cover, or a new page on a layout the project does not own - reproduce the old exporter's standalone geometry in the conversion: 15 mm side margin, title baseline at 68 % of trim height, body baseline 14 mm below on a 16 pt grid, body stopping 12 mm above the trim edge - shrink migrated titles and truncate migrated bodies, as the old exporter did Rationale: - roles are only meaningful if every write path preserves them; project duplication and the book PATCH both bypassed them - a stored page keeps its layout even when that layout is gone, so deleting a layout still leaves a stale book editable - the migrated body previously inherited overflow "flag", which renders every line past the box and raises a blocking overflow problem; an already-exported book would have become un-exportable on load Tests: - bun run format:check, lint, typecheck, build - 273 unit tests passed, including new suites for page validation and the reproduced legacy geometry across A5, A6, and A4 - 3 new integration tests for duplication, page rejection, and stale book reordering; not run locally (no Docker), CI runs them AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
The duplication test asserted presentation order, but getProject returns layouts in stored position order and duplicateProject copies positions verbatim; only the UI derives cover-first ordering from the role. Comparing name and role against the source project tests the property that matters, which is that duplication preserves roles. Tests: - bun run format:check, lint, typecheck AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
Summary: - pass items to the role Select so it renders "Response layout" rather than the raw "submission" value - add before/after screenshots for issue #92 Rationale: - base-ui's Select.Value resolves its label from the items prop, which every other Select in the editor passes; without it the trigger shows the stored enum value - caught while capturing the visual evidence for the pull request Tests: - bun run format:check, lint, typecheck - 72 component tests passed AI-Assisted: true AI-Agent: claude-code AI-Model: anthropic/claude-opus-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #92
Outcome
Cover and standalone pages are designed in the same visual editor as response layouts, instead of being a title and a body paragraph.
Every layout now has a role:
The role is chosen in the existing "New layout" dialog, alongside the background. Covers are pinned by role, not by position, so neither regeneration, layout reordering, layout deletion, nor page drag-and-drop can move them out of place — the server re-pins whatever page order the client sends. Layouts without a response behind them offer only response-independent palette actions (static text, shapes, decorative images), because a bound element there would always render empty.
Standalone book pages now reference a layout, and preview and PDF export both render them through the shared element renderer that already draws response pages. That let the separate text-only drawing path in
pdf-renderer.tsgo away.Migration
Books saved before this change carry
pageType/title/body/backgroundon their standalone pages. They are converted on load and before regeneration: one layout per page, reproducing the old serif-title-over-sans-body rendering, then the page is rewritten to reference it. A legacy page claims a cover role only if it already sat first (cover) or last (closing), so the conversion never reorders an existing book. It probes cheaply and is idempotent, so a project pays for it once.bookStatusis deliberately left untouched by the conversion: the rendering is equivalent, and forcing every existing project tostaleon first load after deploy would be a worse outcome than leaving it current.Validation
verifygreen (format, lint, typecheck, unit, integration, e2e, build, compose checks)Visual evidence
Captured on the seeded demo project, 1440×900, light theme; "before" is
origin/main, "after" is this branch, against the same database so the same book is shown.Layout tab bar. The front cover is pinned first and the back cover last; a response layout is selected in both, so the palette is unchanged.
New layout dialog. Choosing what the layout is for now sits above the background choices.
Standalone page in Book review. The same page before and after the on-load conversion: it keeps its title and body, but now renders through the layout renderer and is edited in the layout editor. Its up/down arrows are disabled because the front cover is pinned.
Cover layout in the editor (after only). The converted front cover open for editing. There is no equivalent "before" because cover layouts did not exist. The palette offers no response-bound action.
Notes for the reviewer
submissionvalue instead of "Response layout", because it did not passitemsto the Select the way every other picker in the editor does.build: keep editor caches out of the formatteradds.vscode/**to.oxfmtrc.json..vscode/PythonImportHelper-v2-Completion.jsonis a VS Code extension cache pushed directly tomaininf14a8f7; it failsformat:checkon a clean checkout and blocks every pull request. Ignoring editor state was the narrow unblock — the file itself is probably better removed from the repository separately.Deployment
Needs
bun run db:migrate:drizzle/20260829162036_gifted_menace.sqladdslayouts.role(defaulting tosubmission, so existing rows keep working) plus two partial unique indexes enforcing one front and one back cover per project. No other out-of-repository configuration is required.