Commit Graph
446 Commits
Author SHA1 Message Date
thePR0M3TH3ANandClaude Opus 4.7 e6e1ef17f5 feat(book): Manifest Links panel in Document settings
Adds an InDesign-style 'Links panel' to the book's Document settings
modal — every external event the book references, with status.

Each row shows:
- Icon (🏷 cover · 🎨 master · 📐 page · 📄 chapter · 📚 manifest)
- Label + truncated coord (kind:pubkey…:dTag)
- Status pill:
    resolved  — sidecar data is in state (layers loaded / markdown
                fetched)
    🔒 private — encrypted ref (decrypt happens at resolve time)
    unresolved — ref set but no data fetched yet (try Refresh)
    unpublished — surface has layers but no Nostr ref (publish to
                  mint an event)
    local draft / empty — chapter content without a ref
- Actions: 📋 copy naddr to clipboard · ↻ force re-fetch this
  sidecar via resolveBookSidecars(..., { force: true })

Plus a 'Refresh all' button under the list that forces a full
re-resolve and repaints the panel when done.

Counts shown in the section header summary so you can see at a
glance how many references are resolved vs. pending.

Closes the loop on the Manifest Links design the user asked for —
this is the read-only view of what the casewrap-book event points
at, alongside the chip / refresh affordances that have been on the
sidebar and tile rows since D1.7.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 22:02:45 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ff0b7348f2 feat(book): direct manifest import — from Nostr (naddr) or local JSON
Closes the loop on the manifest pattern. Two new buttons in the
book sidebar:

- 📥 Import book from Nostr… — paste an naddr / nevent at a
  casewrap-book event. The function resolves the event, tries
  NIP-44 / NIP-04 decryption (so private books work), feeds the
  payload through loadProjectFromText's existing book branch,
  injects the publish ref so future Publish replaces in place,
  then surfaces resolveBookSidecars summary (X / Y resolved, N
  failed) into #bookResolveStatus when it settles.

- 📁 Import book from JSON… — file picker that accepts the
  whole-book JSON Save Project writes (templateMode='book' +
  top-level book field). Same downstream path as Nostr import;
  also kicks off the resolver so any chapter-by-textRef refs
  populate.

Both reuse the existing book branch of loadProjectFromText (line
30441) — no new state-restore code; just clearer entry points for
discovery. Community-browser-driven loads still work; this is the
direct-paste / direct-file affordance the user couldn't find before.

Status reporting comes through #bookResolveStatus (the hint line
that already shows '↻ Refresh from Nostr' results) so users can
see progress without opening DevTools.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 21:59:06 -04:00
thePR0M3TH3ANandClaude Opus 4.7 a0e6329480 fix(book): Pages overview tile thumbnails — accurate to printed output
Three causes of inaccuracy in the thumbnail rendering, all addressed:

1. Bleed offset. Layer x/y are stored sheet-relative (include bleed
   inches). The tile canvas is sized trim-only (matches what gets
   printed). Result: every layer rendered shifted right + down by
   bleed/trim ≈ 2% for US Trade. Now offset each layer by -bleed when
   composing the tile preview — same translation
   renderBookDesignedPageToCanvas uses for PDF export.

2. Text-frame outlines. The dashed text-frame border is an authoring
   affordance, not a print element. The designed-page tile path now
   filters out shape layers with kind='text-frame' before passing to
   the preview helper (same filter the reflow-tile path already used
   on masters). Threaded chapter text still paints over the bare
   frame area via _paintThreadedFramesOnThumb.

3. Aspect ratio. Tiles defaulted to 2:3 in CSS — fine for US Trade
   6×9 but distorted for Square 8×8, A4, etc. Each tile now sets
   aspect-ratio inline from the book's actual trim dimensions.

Tiles should now match the PDF output much more closely.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 21:44:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 97597f1e3d feat(book): J4.d — line-by-line overflow + frame typography (strip markdown)
Two architectural shifts to match how design tools (InDesign, Affinity
Publisher, Scribus) handle threaded text.

1. Content vs presentation split. Chapter markdown is flattened to
   plain paragraphs on import — headings, bold, italics, list
   bullets, blockquote marks all stripped. The author writes in
   markdown because it's a good authoring format; the designer
   applies presentation on the frame, not in the source.

2. Line-by-line spill. The block-based engine moved whole paragraphs
   to the next frame when they didn't fit, leaving gaps at the
   bottom of each frame. The new engine wraps each paragraph against
   the current frame's width using its typography, then places
   lines one at a time — when the frame is full, the NEXT line
   continues in the next frame. Paragraphs break across frames
   gracefully.

What changed:

- _flattenChapterToParagraphs(md) strips markdown to plain text,
  collapsing whitespace and preserving paragraph breaks.
- _getFrameTypography(layer) returns the frame's typography (font,
  size, line height, alignment, color, paragraph spacing) with sane
  defaults.
- _paginateParagraphsIntoFrames(paragraphs, chain, typography) is
  the new line-based paginator. Wraps each paragraph using the
  active frame's inner width, fills the frame to height, advances
  to the next frame, repeats. Returns per-frame line buckets +
  hasMoreText flag for overset detection.
- _paintFrameLines / _drawFrameLinesToCanvas — DOM + Canvas2D
  rasterisers for the new format. Replace _paintFrameBlocks and
  _drawFrameBlocksToCanvas in the three render paths (editor,
  tile thumbnails, PDF export).
- addBookTextFrame seeds new frames with default typography
  (Georgia 11pt, 1.5 line-height, 0.1in paragraph gap, left align,
  near-black).
- Text-frame inspector adds a 'Typography' section: font, size,
  color, line height, paragraph spacing, alignment. Editing on
  the head propagates to the whole thread. Mid-thread frames show
  the read-only inherited values.

Existing reflow entries (chapters in book.pages used the
non-threaded way) still flow through the legacy block engine —
that path is untouched.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 21:39:37 -04:00
thePR0M3TH3ANandClaude Opus 4.7 be9a33a44c feat(book): text-frame inspector in the right panel — discoverable threading
Replaces the port-click dance as the primary path for threading +
chapter placement. Whenever a text-frame layer is the primary
selection in book mode, a new #bookTextFrameOptions section appears
in the right-side #toolOptions panel with:

- Status line: 'Frame N of M in this thread' / 'Solo frame' /
  '(Head — chapter source goes here)'.
- Chapter source dropdown — every reflow entry in book.pages. Only
  enabled when the selected frame is the head of its thread;
  mid-thread frames inherit and show a hint to that effect.
- Incoming-from dropdown — every other text frame in the book that
  could safely link TO this one (no cycle). Selecting one sets the
  source frame's nextFrame to point here; selecting (none) clears
  whatever was linking in.
- Outgoing-to dropdown — every other text frame this one could
  safely link to. Selecting one sets this frame's nextFrame;
  selecting (none) breaks the link.

Both dropdowns share _enumerateBookTextFrames, which labels each
candidate by its surface ('Page 3', 'Master · Standard interior') +
its ⌗frameId. Cycle detection reuses _bookFrameChainContains so the
dropdown only shows safe candidates.

Force-shows the outer #toolOptions panel when the inspector is
visible — the default toggling in syncToolPaletteUI hides it for
the Select tool, but with a text-frame selected we want the
inspector regardless of active tool.

Port-click + Chapters-tab 'Place' button still work as alternative
paths. The dropdown panel is the discoverable one.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 21:09:48 -04:00
thePR0M3TH3ANandClaude Opus 4.7 81dd6bf6b6 fix(book): text-frame ports were eating resize handles + drag/select events
Three reported bugs, one root cause: the in/out ports sat at the
top-left and bottom-right CORNERS of every text-frame layer —
exactly where the lh-nw and lh-se resize handles live — AND their
pointerdown bubbled to the freeLayer's startLayerDrag handler.

Symptoms:
- Couldn't resize a text frame (corner handles hidden under the
  larger-z-index ports).
- Couldn't click an in/out port to thread (the port's pointerdown
  started a layer drag, the click never fired).
- Couldn't 'Place chapter into selected frame' (the chapter button
  needs a selected frame, and selection requires a click that
  wasn't being shadowed by drag).

Fixes:
- Move ports OUTSIDE the layer's bounding box on the top edge
  (in port) and bottom edge (out port), centered. New tab shape
  with a dashed connector to the frame edge so the affordance
  reads. Both well clear of the 8 corner/edge/rotation handles.
- Bump the frameId badge to sit just above the layer (top-right,
  -16px) so it doesn't visually conflict with the in port.
- Add pointerdown + mousedown handlers on both ports AND on the
  source-chapter chip + its X clear button that stopPropagation.
  Drag handler no longer kidnaps the click.

Resize, select, link, and place-chapter all flow correctly now.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:59:28 -04:00
thePR0M3TH3ANandClaude Opus 4.7 b6b00c09f1 docs: J4.c.3 status note + implementation status table
Reader Mode reuses renderBookDesignedPageToCanvas, which the J4.c.2
commit already updated to render threaded chapter text. So J4.c.3
is implicit — no code change needed beyond what J4.c.2 shipped.

Adds an 'Implementation status' table to the spec so anyone reading
the doc later can map phase labels to actual commits + see what
still pends.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:35:56 -04:00
thePR0M3TH3ANandClaude Opus 4.7 10d9e51155 feat(book): J4.c.2 — threaded text exports to PDF on designed pages
renderBookDesignedPageToCanvas now walks page layers after the
master + page layers are drawn, finds text frames whose thread head
holds a placed chapter, paginates via _paginateChapterIntoThread,
and rasterises each frame's slice onto the export canvas at the
frame's coordinates.

New helper _drawFrameBlocksToCanvas — stripped-down counterpart to
the block-painting loop in renderBookReflowPhysicalPageToCanvas.
Handles paragraphs, headings, lists, blockquotes, code blocks, hr,
and plain text — same things the editor's _paintFrameBlocks paints
to DOM. Tables in threaded frames + real-PDF textOps emission (for
selectable text in the PDF) are deferred to a J4.b2 cleanup.

PDF output should now contain the threaded chapter text where the
user authored their frames, not just the dashed text-frame outline.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:34:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 9d8be7e0fe feat(book): J4.c.1 — threaded text shows in Pages overview tile thumbnails
After a designed-page tile is painted, walk its text-frame layers,
identify any that hold a placed chapter via their thread chain, and
paint each frame's paginated slice as an overlay at the frame's
position in the thumb canvas. Same _paintFrameBlocks helper the
editor uses, sized to the frame's bounding box.

The tile thumbnail now reflects the real text flow — author a
chapter, place it on a page-1 head frame, and the Pages overview
shows the first chunk of the chapter text in that frame; the
linked frame on page 2 shows the next chunk.

Still pending: PDF export and Reader Mode walk reflow entries the
old way (J4.c.2 / J4.c.3).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:33:26 -04:00
thePR0M3TH3ANandClaude Opus 4.7 282f5df4f3 feat(book): J4.b — variable-geometry thread pagination
Lets a thread have frames of different sizes and have text wrap
correctly in each. The chain {frame1: 6×4, frame2: 4×6, frame3:
6×6} now line-breaks at frame1's 6″ width, then at frame2's 4″
width, then at frame3's 6″ width — no clipping in the narrow
frame, no excess gutter in the wide one.

What changed:

- paginateChapter accepts opts.startTokenIndex and opts.maxPages.
  The token loop honors both: starts from the given index, stops
  after the page count is met, and returns result.endTokenIndex
  so the caller knows where to resume.
- _paginateChapterIntoThread walks the chain frame-by-frame,
  calling paginateChapter once per frame with that frame's
  geometry + the running token cursor. Each frame contributes its
  own one page to the chain.
- Overset detection switched from a page-count comparison to a
  token-cursor comparison (paginated.hasMoreText). More accurate
  with variable geometry where page count ≠ frame count.

Limitations still standing:
- PDF export, Reader Mode, and tile thumbnails still walk
  book.pages reflow entries the old way — J4.c.
- Tokens are re-lexed and re-measured per frame today. For long
  chains this is N × token-count work; cache by chain+chapter
  if it becomes a perf problem.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:31:57 -04:00
thePR0M3TH3ANandClaude Opus 4.7 2d7e757308 feat(book): J4 — thread-aware pagination, text flows through linked frames
Hooks up the threading wiring so chapter text actually pours through
the frame chain you authored in J3 + J5.

What changed:

- paginateChapter now accepts opts.frameOverride = { x, y, w, h,
  padding }. When provided, it skips the master-frame lookup and
  uses the override directly. Existing callers (master-driven
  pagination) keep working unchanged because the override is opt-in.
- New _paginateChapterIntoThread(headLayer, book) — builds a
  synthetic call to paginateChapter using the head frame's geometry,
  returns the pages + the resolved chain.
- New _paintFrameBlocks(el, page, frameW, frameH, padding) — paints
  one paginated page directly into a freeLayer wrapper. Like
  paintReflowPageOverlay but coordinates are frame-local instead of
  page-local.
- In _attachBookFramePorts: when a chip is being shown (chapter
  placed on the thread's head), paginate + paint the slice for THIS
  frame. If the chapter overruns the chain, the last frame gets a
  red ⊞ OVERSET badge. If the chapter is shorter than the chain,
  extra frames get a muted '(thread continues — chapter ended
  upstream)' placeholder.

You can now: thread frames, place a chapter on the head, and the
text actually flows across the chain. Real text, real wrapping,
real headings + lists + code blocks — same layout engine the
reflow-entry path has always used.

Limitation captured for J4.b: every frame in a thread shares the
head's geometry. Variable-geometry threads (e.g. front-matter frame
narrower than body frames) need the engine to walk tokens per-frame
instead of all at once. Today the line breaks are computed once at
head geometry and re-used.

PDF export, Reader Mode, and the per-tile thumbnail painter still
walk book.pages reflow entries the old way — they don't see threaded
frames yet. That's the J4.c cleanup once the engine settles.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:28:35 -04:00
thePR0M3TH3ANandClaude Opus 4.7 83d451dfe4 feat(book): J5 — place chapter into a thread's head frame (placeholder body)
Wires a text-frame layer to a chapter source. UX:

- Each chapter row in the Chapters tab gets a '📥 Place in selected
  frame' button. Disabled when no text-frame is selected, when the
  selected frame is mid-thread (must place on the head), or when the
  selected frame already holds this chapter.
- On click: head.sourceChapter = entry.id, history-push, re-render.
- Threaded text frames render a small blue chip up top showing
  '📄 <chapter title> · frame N / M', plus an × button on the head
  to unplace. The chip is identical across every frame in the chain
  so the chapter assignment is legible everywhere.
- Inside the frame: italic placeholder line ('(text will flow here
  — J4 pours it)'). Real text layout into per-frame geometry lands
  in J4.

The thread-walk uses _bookCollectThreadChain, so a placed chapter
is visible across all linked frames immediately. Unplacing from the
head clears it everywhere in the chain.

Mid-thread frames don't get a clear button — clearing on a non-head
would be ambiguous. The Chapters tab button refuses to place on a
mid-thread frame for the same reason.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 20:01:14 -04:00
thePR0M3TH3ANandClaude Opus 4.7 a419160b3a feat(book): J3.c — show-thread overlay on port hover
Hovering any in / out port of a threaded frame highlights every
frame in the same chain. The head of the thread (first frame, no
incoming link) gets a blue outline; the rest get green. Hover off
clears the highlight.

_bookCollectThreadChain walks backwards along incoming nextFrame
refs to the head, then forward along outgoing refs to the tail.
Cycle-safe and depth-capped. Makes long threads (e.g., a chapter
flowing across many designed pages) immediately legible — useful
in J5+ when threads start spanning more than two frames.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 16:09:44 -04:00
thePR0M3TH3ANandClaude Opus 4.7 4afe726704 feat(book): J3.b — click-to-link UX for text-frame threading
Wires the in/out ports added in J3.a. Workflow:

- Click an empty out port → enters 'loading' state (cursor turns to
  copy, body gets .book-port-loading, source port pulses, every
  empty in port highlights as a drop target).
- Click another frame's in port → links them. sourceLayer.nextFrame =
  { target, frameId }. Toast confirms.
- Click an already-linked out port → breaks the thread at that point.
- Click the same source out port again, or press Esc → cancels.
- A cycle guard refuses links that would close a loop (A → B → C,
  then trying C → A).

Pure data updates so far — no rendering change to the chapter
content. The pagination engine still uses the master-frame-clone
model from C4. J4 swaps in a thread-walking pagination engine that
honours these nextFrame refs.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 16:08:35 -04:00
thePR0M3TH3ANandClaude Opus 4.7 3cab9482e7 feat(book): J3.a — render in/out ports + frameId badge on text frames
First step of the threading UI. Every text-frame layer in book mode
now shows:

- An 'in' port (small square, top-left corner) — turns green when
  another frame's nextFrame ref points at this one.
- An 'out' port (small square, bottom-right corner) — turns green
  when this frame's nextFrame points somewhere.
- A tiny '⌗ <frameId>' badge in the bottom-left so the user can see
  the frame's stable id while authoring.

Reads thread-link state from layer.nextFrame on each frame (no
book.threads[] consumer yet — that lands in J4 with the pagination
refactor). Legacy text-frame layers (created pre-J2) get a frameId
backfilled the first time they render, so old books gain the new
ports automatically.

Click handling is not yet wired — J3.b adds the load-cursor + link
flow. The ports are visible affordances and tooltips today.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 16:01:07 -04:00
thePR0M3TH3ANandClaude Opus 4.7 2110e724b9 feat(book): J2 — first-class text-frame on designed pages + stable frameId
Generalises the text-frame layer from master-only (Phase C4) to any
book surface. Designed pages can now host text frames the same way
masters do, with the same '+ Text frame' button in the page-edit
bar.

What changed:

- addBookTextFrameToActiveMaster() → addBookTextFrame(targetOverride)
  that picks up book.editingMasterId OR book.editingEntryId depending
  on which surface is open. Legacy alias kept so old callers don't
  break.
- New layer carries a stable frameId (independent of layer.id) and
  a nextFrame field (null for now; J3 will use it for threading).
- bookMasterAddTextFrame button now visible in both master-edit
  AND page-edit modes. Title updated to read 'a text frame to this
  surface (master or designed page)'.
- Click handler now calls the generalized addBookTextFrame().

The frame still renders as the existing dashed-blue text-frame
shape with no content of its own — pagination still flows chapter
text through master frames as before. J3 wires the in/out ports
and the click-to-link UX; J4 makes the pagination engine walk
thread.frames[] instead of cloning masters.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:58:58 -04:00
thePR0M3TH3ANandClaude Opus 4.7 961b24d0d8 feat(book): J1.b — distinct Pages-overview empty state when only chapters exist
The starter-template panel fired on '!pages.length' which is now
ambiguous: a user can have chapters (counted in pages[]) but no
designed pages, and the Pages tab needs to say something. Splits
into three cases:

  (1) no designed pages AND no chapters → starter templates (as
      before, with the hint updated to point at the new Chapters tab
      for + Add chapter)
  (2) chapters but no designed pages → 'No designed pages yet' panel
      with + Add page / + Add 10 pages / Go to Chapters buttons
  (3) at least one designed page → render the grid normally

Phase J1 polish — pure UI; data model unchanged.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:57:23 -04:00
thePR0M3TH3ANandClaude Opus 4.7 1e6f2e9a57 feat(book): Phase J1.a — Chapters tab + Pages/Cover/Chapters split
First step on the phase-j-linked-content branch. Splits the book
view's pages-and-chapters mash-up into three tabs (Pages / Chapters
/ Cover) without yet touching the data model or the pagination
engine. Foundation for J2..J6.

What changed:

- New 'Chapters' tab between Pages and Cover. Lists every reflow
  entry in book.pages as a row with title, source (naddr or local
  draft), size, pin / live / private pills, and per-row Edit / Pin /
  Delete actions.
- '+ Add chapter' button moved off the Pages overview's toolbar and
  onto the new Chapters tab. Pages toolbar now reads cleanly:
  Add page / Add 10 pages / Import page / Read / Export PDF.
- Pages overview hides reflow-entry tiles via a CSS rule
  (display:none on .bookPageTile.reflowSegment). Data still lives
  in book.pages — drag-reorder indices unchanged, pagination engine
  unchanged, PDF export unchanged. J2+ migrates the data out.
- Count readout updated from '0 pages · 0 reflow' to
  'N pages · M chapters' (uses 'design' count + 'reflow' count).
- defaultBookState + ensureBookState now scaffold empty links[] and
  threads[] arrays so the rest of the codebase can assume they
  exist. No code reads them yet.
- setBookSubView extended to handle three views, sets active class
  + display + repaints the active list. renderBookChaptersList
  hooked into the main render() loop only when its tab is visible.

Everything that worked before still works — chapter editor, publish
flow, PDF export, Reader Mode, all untouched. The visible change is
the new tab + the cleaner Pages overview.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:46:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 79c89101c4 docs: Phase J spec — Linked Content + threaded text frames
Drafts the next architectural arc for the Book Designer around two
ideas:

- Events are linked files. Generalizes the pin-vs-live toggle
  shipped for chapters to every external reference. A new
  book.links[] collection becomes the project's 'Links panel'
  (chapters, masters, pages, illustration sets, future asset kinds).
- Threaded text frames (InDesign-style). First-class text-frame
  layers on designed pages, threaded port-to-port; a thread
  declares a content source (a linked chapter event) and the
  pagination engine pours text through the frame chain.

The current 'reflow entries in book.pages' model collapses into
links[] + threads[] + auto-generated designed pages — a one-time
migration on load. Pagination engine, PDF export, Reader Mode,
Linked Designs resolver, and NIP-23 chapter events all survive.

Phasing J1..J6 captured in the spec + TODO. No code yet — this is
just the design doc and arc placement.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:36:45 -04:00
thePR0M3TH3ANandClaude Opus 4.7 3f5540712b fix(book): stop double-offsetting cover layers in the preview composite
The earlier cover-preview commit assumed layers were stored in
panel-local coordinates (0..trimW relative to their panel's trim
corner). They're not. Cover layers are appended directly to
#layerOverlay (renderLayers, ~line 22344: 'el.style.left = inches(layer.x)'),
which lives inside #sheet, so layer.x is sheet-absolute: a back
layer's x already starts at >= bleed, a front layer's x already
starts at >= bleed + trimW + spineW (targetBounds() seeds them
there at creation time). Adding backX / spineX / frontX in the
preview shifted every layer twice — front cover content slid off
the right edge of the canvas, spine content drifted onto front,
back content drifted onto spine.

Fix: just remap target to 'canvas' and pass x / y through. The
panel-offset variables are kept (renamed in scope) only for the
overlay's fold-line and label positioning, which IS in absolute
spread coordinates.

The 'spec backfill' from the prior commit still stands — defensive
against undefined coverHasBack / coverHasSpine on legacy state —
but the visible misalignment in the preview vs. the editor was this
coordinate-system mismatch.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:23:46 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ef7e85a418 fix(book): backfill cover spec flags so older books don't drop back/spine layers
ensureBookState() now fills in any defaultBookSpec() fields missing
from state.book.spec. Older saved books that pre-date the
coverHasBack / coverHasSpine flags would otherwise restore with those
fields undefined, and the eleven '!!s.coverHasBack' / '!!s.coverHasSpine'
truthiness checks scattered across the cover-preview filter, sheet
sizing, renderDimensions, the cover edit slot layout, and the PDF
export pipeline would all read those as 'false' — silently filtering
back-cover and spine layers out of the spread.

User symptom: a cover with text + linked designs + a background shape
that spans the spread would render only the front panel's content,
because the back / spine layers were being dropped by the filter.
The spread width also collapsed to front + bleed, throwing off the
bleed visualization.

renderBookCoverPreview() now calls ensureBookState() before reading
the spec so the backfill runs even if no prior code path touched the
book this render cycle.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:10:19 -04:00
thePR0M3TH3ANandClaude Opus 4.7 842e672fc0 feat(book): load cover from Nostr or JSON (+ fix bookCoverPayload name collision)
Two new import paths for book covers:

- Load cover from Nostr: paste an naddr / nevent pointing at a
  casewrap-page cover-spread event. Pulls layers + spec hints (trim,
  bleed, hasBack, hasSpine) + the publish ref so the existing
  Re-publish cover flow keeps replacing in place.
- Load cover from JSON: file picker that accepts the format
  bookCoverFileExportPayload() emits. Applies layers + spec hints;
  clears book.cover.ref because the file isn't yet published.

Both ask before overwriting an existing cover and update the live
preview immediately.

The 'Replace cover from Nostr…' button was already in the HTML but
hard-disabled with a 'Phase D1 stub' note — now wired and re-labelled
'📥 Load cover from Nostr…'.

Also fixes a name collision from the prior JSON-export commit:
bookCoverPayload() was defined twice (Nostr publisher at line 11938,
file exporter that I added at line 26056). JS function hoisting let
the later one win, silently breaking publishBookCoverToNostr's
'page.layers' lookup. Renamed the file exporter to
bookCoverFileExportPayload() so both coexist.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:05:25 -04:00
thePR0M3TH3ANandClaude Opus 4.7 c66b1c03af fix(book): cover preview paints actual layers + book-aware project-title readouts
Two fixes wrapped together because they're both about the user
not seeing a meaningful preview in book mode.

1. renderBookCoverPreview() now paints the actual back / spine /
   front layers into a single composite canvas the size of the full
   wrap-around spread, using the same renderCustomArtPreviewDOM
   helper the Pages-overview tiles use. Each layer's target is
   remapped to 'canvas' and offset by its panel's position within the
   spread (back: x=bleed, spine: x=bleed+trimW, front: x=bleed+
   trimW+spineW). On top: a dashed-red trim guide, dashed fold-line
   dividers, subtle 'Back / Spine / Front' panel labels, and an
   empty-state hint when no cover artwork exists yet. The geometric
   panel-mock fallback is gone — the .bookCoverPanel CSS still ships
   but is now dead.

2. renderDimensions() no longer falls through to the DVD-cover
   else-branch in book mode. The 'Trim: 10.8 × 7.25 in / PDF page:
   8.50 × 11.00 in (portrait)' strings that showed for books were
   the DVD totalTrimW + state.trimH leaking through. A dedicated
   book branch now reads from state.book.spec and shows
   'Trim: 6 × 9 in / Spread: ... (with bleed)' when editing the
   cover, 'Page: ... (with bleed)' otherwise.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 15:00:26 -04:00
thePR0M3TH3ANandClaude Opus 4.7 faa4849523 fix(book): show page-edit bar in cover-edit mode + add Book/Meme/Other categories
- The bookPageEditBar CSS only matched .editing-book-page, so the bar
  (Back / Publish / Proof / Cover JSON) was completely hidden while
  editing the cover. Added .editing-book-cover to the selector so the
  bar shows in both modes.
- Back button label is now '✓ Done — back to Cover view' with a tooltip
  that says changes are autosaved. The old 'Back to Cover view' label
  hid the fact that work persists automatically.
- mediaCategories: added 'Book', 'Meme', 'Other'.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:54:59 -04:00
thePR0M3TH3ANandClaude Opus 4.7 6c33571329 feat(book): JSON export — whole book + cover-only
Two new payload builders:

- bookPayload() — captures spec, pages, masters, cover metadata,
  every layer that targets a book surface. Hooked into downloadProject
  so Save Project (JSON) in book mode now writes a real, round-
  trippable book file instead of falling through to coverTemplatePayload
  (which silently wrote a malformed DVD-cover JSON for books).

- bookCoverPayload() — cover-only payload with the three cover panels
  + the spec fields the cover needs to lay out correctly (trim, bleed,
  safe, spine width snapshotted at export so the file stays portable
  between books with different page counts). Surfaced via a new
  '💾 Cover JSON' button in the page-edit bar that only shows in
  cover-edit mode.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:50:29 -04:00
thePR0M3TH3ANandClaude Opus 4.7 9b5d6fa459 ui: disable trackpad two-finger swipe-to-navigate
Adds 'overscroll-behavior-x: none' on html / body so the browser
treats horizontal trackpad swipes as no-ops instead of back / forward
navigation. Vertical scroll is unaffected. Prevents the classic
'I lost my work because I scrolled the canvas' design-tool gotcha.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:43:02 -04:00
thePR0M3TH3ANandClaude Opus 4.7 3f5d244a8e fix(book): Edit cover now actually shows the cover scaffolding
Two bugs prevented the book cover editor from rendering anything
visible:

1. body.template-book had an unconditional 'display:none !important'
   on .coverOnly (and on .bleedFill, .trimArea, .foldLine, .spineText),
   so when editing-book-cover was set the slot boxes, fold lines, crop
   marks, and trim guides stayed hidden. The hide rule now uses
   :not(.editing-book-cover) for the cover scaffolding while still
   unconditionally hiding the disc/jewel scaffolding (which never
   belongs on a book cover).

2. The .slot positions are driven by --panel-w, --spine-w, --trim-h,
   --bleed, --safe CSS custom properties. renderDimensions() was
   writing DVD-cover state.* values into those for every render,
   including in book cover mode — so even if the slots were visible
   they would have been sized for the last DVD preset, not the book
   spec. renderDimensions now writes book-spec values into the CSS
   vars whenever state.book.editingCover is true, so the back / spine
   / front panels lay out at the book's trim with the spine derived
   from page count × paper thickness.

For the asymmetric cases (coverHasBack=false or coverHasSpine=false),
a new applyBookCoverSlotLayout() helper writes inline left / width
overrides for the slot, trim, fold line, and crop-mark elements so
they sit in the right places. clearBookCoverSlotLayout() drops those
overrides whenever we leave cover edit mode so the CSS defaults take
back over. New body classes no-cover-back / no-cover-spine hide the
slot box for the missing panel.

Slot placeholders ('Enter back image URL' etc.) are hidden in book
cover edit mode — those belong to the DVD URL flow, not the book
cover layer system.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:40:01 -04:00
thePR0M3TH3ANandClaude Opus 4.7 549d6a61d9 ui: 'Beta' badge on the Book layout tab + experimental banner in the Book sidebar
Surfaces that Book Designer is still under construction in two places:
- An amber 'Beta' pill on the Book layout-picker tab so users see it
  before they switch modes
- A full-width amber notice at the top of the Book sidebar fieldset
  that's always visible whenever the user is working in Book mode,
  with a short explanation of what works and what to expect

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:23:25 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5706c4b21a docs: 'Copy as markdown' button in the docs modal header
Each docs page now has a header button that copies the raw markdown
source to the clipboard. Useful for pasting the docs into the Book
Designer chapter editor as test content.

- _docsCache now stores { html, md } so the raw source survives the
  marked.parse() pass without a re-fetch on copy
- _docsActiveMd tracks the currently displayed page's source
- Button shows a brief "✓ Copied" confirmation and routes through
  toastSuccess / toastError for status

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 14:14:08 -04:00
thePR0M3TH3AN 0f18580178 Merge book-designer: Book Designer feature + in-app docs + contextual help icons
67 commits delivering the Book Designer arc (phases A-I) plus the
in-app 'Read the docs' markdown viewer (30 pages across 5 groups)
and ~48 contextual ⓘ help icons knitted into tabs, layouts, panels,
tool sections, the pixel-art UI, welcome / community / tip modals.
2026-06-04 13:45:30 -04:00
thePR0M3TH3ANandClaude Opus 4.7 388ca5fce2 fix(docs-icons): use capture-phase delegation so clicking ⓘ inside a button doesn't fire the button's handler
Layout-tab buttons (mode-cover / mode-disc / etc) and app-mode tabs
(modeTabTemplate / modeTabDesigner) have direct click listeners that
fire during the bubble phase. The previous bubble-phase docs
delegation ran AFTER those handlers — so clicking the ⓘ inside a
layout tab would swap modes *and* open the docs.

Switching the docs handler to capture phase intercepts the click
before it reaches the wrapping button's bubble listener, and
stopPropagation prevents bubble delivery entirely. Now the ⓘ
opens docs without changing modes.

Verified with a slug-integrity sweep: every data-help attribute in
the UI resolves to a real _DOCS_TOPICS entry (no 404s).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 10:17:19 -04:00
thePR0M3TH3ANandClaude Opus 4.7 12b6beaffa docs: help icons in welcome / community / tip modals
Three more surfaces where docs help is high-value at the moment a
user is making a decision:

- Welcome modal h2 — opens the Overview docs page so first-run
  users can orient themselves before picking a starting layout.
- Browse Community modal h2 — opens the Publishing overview
  (public/private/premium comparison + tipping vs unlocking).
- Browse Community Premium tab — opens the dedicated
  Premium-publishing page so users browsing locked designs can
  read about how unlocking works before clicking ⚡.
- Tip modal h2 — opens the Zaps page; restructured the title
  area as a flex row so the icon survives the dynamic
  textContent updates that swap "Send a tip" vs other labels.

Coverage is now 48 ⓘ icons across the UI.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 10:13:18 -04:00
thePR0M3TH3ANandClaude Opus 4.7 4ff705709e docs: help icon in the pixel-art editor toolbar
Adds an ⓘ at the start of the pixel editor's tool bar (left of the
pencil/eraser/fill tool group) that opens the Pixel art docs page.
Pixel mode runs through its own bespoke toolbar (not the standard
left-sidebar fieldset flow), so the editor bar is the right home
for the icon.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 10:02:56 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ca2a722656 docs: help icons on layout-picker buttons + book sub-surfaces
Each individual layout button now carries its own ⓘ so authors get
straight-to-the-page help even before picking a mode. Same delegated
click handler from the previous slice — the layout-tab's mode-switch
click and the icon's docs-open click coexist because the docs handler
stopPropagation's the event before the tab handler sees it.

Layout-picker buttons (8):
- Template tab: Case cover, Disc labels, Slide deck, Book.
- Designer tab: Custom art, Slide, Pixel art, Disc design.

Book sub-surfaces (4):
- Pages / Cover toggle bar — opens the Book page.
- Page editor top bar (book-page + book-master editing) — opens the
  Book page.
- Chapter editor toolbar — opens the Book page.
- Reader Mode header — opens the Book page.

CSS:
- .layoutTab .docsHelpIcon — smaller (.76rem) + reduced opacity by
  default; active state inverts colour to white-on-accent so the
  icon stays readable on a selected tab.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:59:09 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ad39a72554 docs: contextual ⓘ help icons across the UI
Every major surface now carries a small ⓘ glyph that opens the in-
app docs modal directly to that surface's page. Single delegated
click handler on document.body (data-help attribute → openDocsModal
slug); zero per-icon wiring.

Placement (30+ icons):

- App-mode tabs — Template → template; Designer → designer.
- Sidebar legends (18) — Layout/Template/Canvas/Slides/Book/Deck
  theme/Artwork/Disc Layout/Jewel Inserts/Layers/Project metadata/
  Credits/Display options/Print & Save/Save & Export plus the
  Designer-tab equivalents — each linking to its matching page.
- Tool-options pane headings (6) — Pen tool, Direct Selection,
  Path actions, Pencil tool, Shape tool, Text tool — each opens
  its dedicated tool page.
- Modal titles (4) — Document settings, Export PDF, Master pages,
  Ready-to-publish — the publish-confirm h2 was restructured to a
  flex row so the icon doesn't get wiped by the JS that swaps the
  textContent.
- Settings → ⚡ Lightning wallet → opens the NWC setup page.

CSS:
- .docsHelpIcon — 16×16, muted color, inline-flex so it nests
  cleanly next to headings/labels. Hover brightens + scales 1.1.
- App-tab variant tones the colour down further (rgba on white +
  active states) so the icon doesn't compete with the tab label.

Behaviour:
- Cmd/Ctrl/Shift clicks pass through (let the browser handle if
  the icon is ever wrapped in a link).
- Each icon has role=button + aria-label + title for accessibility.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:54:53 -04:00
thePR0M3TH3ANandClaude Opus 4.7 7d63c353d5 TODO: surface industry-convention parity gaps in vector tools
The docs audit (commit ef03f73) caught several universal design-tool
conventions that aren't wired in the vector toolset — most are
single-branch additions to existing drag handlers. Capture them in
TODO.md under "Design-tool follow-ups" as a discrete cluster so we
remember they exist.

The 14 items:
- Select tool: Shift-constrain-axis on drag, Alt-drag duplicate,
  Shift-aspect-lock on corner scale, Shift-rotation 15° snap.
- Global: Cmd+A select-all, Spacebar temp-pan, Cmd+0 fit-zoom,
  Cmd+=/- zoom step.
- Shape tool: Shift-constrain to perfect primitive, Alt-from-center
  draw.
- Text tool: drag-to-size.
- Pencil tool: smoothing slider, Alt-drag append, auto-close near
  start.

Each entry notes rough scope so we can pick them off in any order.
None are blockers; the docs audit just reminded us they're missing.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:39:55 -04:00
thePR0M3TH3ANandClaude Opus 4.7 10a5022e9c docs: publishing tiers + zaps + NWC + JPEG/PDF fallback
Adds a "Publishing & payments" group covering the three tiers, the
Lightning plumbing, and how to wire a wallet up:

- publishing.md            — Overview of Public / Private / Premium
                             with a comparison table; how tipping vs
                             unlocking differs; replaceability +
                             republish-vs-fork.
- publishing-public.md     — Default tier. Event shape, tag layout,
                             discoverability, edit-vs-fork.
- publishing-private.md    — End-to-end encrypted to self via NIP-44
                             / NIP-04. AES-256-GCM pipeline, what stays
                             visible (pubkey, kind, d-tag), multi-
                             device decrypt via "Mine" tab, caveats
                             (no multi-recipient envelopes in v1).
- publishing-premium.md    — Zap-gated soft-gate publishing. Soft-gate
                             key derivation, watermarked preview,
                             zap-split tags (creator + platform),
                             buyer unlock flow, My Purchases vault,
                             honest framing about soft-gate limits.
- zaps.md                  — NIP-57 lightning payments. Tipping vs
                             unlocking. How to receive zaps (set
                             lud16 on profile). Fallback flow without
                             NWC (BOLT11 + QR + external wallet).
- nwc.md                   — Nostr Wallet Connect (NIP-47) setup.
                             CoinOS quickstart, supported wallets,
                             multi-device sync via NIP-44 encrypted
                             event, test-connection button, security
                             model (treat URI like a password + set a
                             budget).

Plus a "Why JPEG sometimes routes through PDF" section added to
panel-print-save.md, explaining the three fallback cases (CORS-
blocked images, tainted canvas, unsupported mode) and the auto-
switch to the print → PDF path that the browser's print engine
can handle.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:37:04 -04:00
thePR0M3TH3ANandClaude Opus 4.7 ef03f731d1 docs: audit pass — fix wrong shortcut + behaviour claims
Swept the help pages against the actual code paths and removed /
corrected claims that didn't ship.

vector-tools.md
- Removed: spacebar temp-pan, Alt duplicate-as-you-drag, generic
  Shift-constrain.
- Added: verified shortcuts (Cmd+Z/Y/D/C/X/V/G, Cmd+]/[ with
  Shift modifier, Cmd+J / Cmd+Alt+J in Pen edit mode,
  Delete/Backspace for layers + anchors).

tool-select.md
- Removed Cmd+A (not wired), Alt-drag-duplicate (not wired),
  Shift-constrain-axis drag (not wired), Cmd-click stack-walk
  (not wired). Kept the verified marquee + multi-select.

tool-direct-select.md
- Re-wrote anchor / handle interactions to match the actual hit-
  test branches (Alt-click anchor toggles kind in one click; Alt-
  drag a handle breaks symmetry; Alt-click segment inserts an
  anchor; plain drag of a segment bows it). Removed the "click an
  endpoint to continue drawing" claim — that's a Pen-only flow.

tool-hand.md
- Removed every fictional zoom shortcut (Cmd+wheel, Cmd+0,
  Cmd+= / Cmd+-). Documented the real zoom controls (−/+/Fit
  buttons + dropdown in the canvas toolbar). Spacebar reposition
  scoped correctly to the Pen tool only.

tool-pen.md
- Clarified that the Pen options pane has its own Fill / Stroke
  inputs (separate from any "Stroke panel" that doesn't actually
  exist).

tool-pencil.md
- Removed every claim that wasn't true: smoothing slider, Alt-drag
  append, auto-close. Pencil writes a plain polyline (M…L L L…),
  one layer per stroke. Tool options pane has only colour + width.

tool-shape.md
- Filled in the missing primitives (rounded-rect / circle /
  triangle — total 8, not 5). Removed Shift / Alt drag modifiers
  that aren't checked in startShapeDrag / commitShapeFromDrag.
  Added the small-drag → default-size fallback that the code does
  do.

tool-text.md
- Replaced the "drag a rectangle to size" claim with the actual
  click-to-place behaviour (placeTextWithTool drops a fixed-size
  box at the cursor + auto-flips to Select with the box focused).
  Softened the Cmd+B/I/U claims to "browser native rich-text
  shortcuts" since they're not wired in the app code.

tool-eyedropper.md
- Replaced the fictional "inline ⊙ buttons next to colour fields"
  with the real OS-colour-picker eyedropper that Chrome's native
  <input type="color"> shows.

panel-layers.md
- Removed Cmd+A select-all (not wired). Added Shift modifier to
  Cmd+]/[ for bring-to-front / send-to-back. Clarified that
  position units are inches in print modes, pixels in Custom Art
  / Slide / Deck (matches the unit toggle behaviour).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:32:11 -04:00
thePR0M3TH3ANandClaude Opus 4.7 e9f3682bff docs: sidebar-panel pages — left fieldsets + right Tool Options pane
Adds a "Sidebar panels" group to the in-app docs:

- panels.md             — overview + a table of every sidebar panel
                          with where it lives + a one-liner
- panel-metadata.md     — Project Metadata fields (title, category,
                          IMDb/UPC/TVDB/MBID/custom-tag/language)
                          + how they feed publish + designer-mode
                          equivalent
- panel-layers.md       — Layers panel — per-mode surface scoping
                          (Cover/Disc/Jewel/Custom Art/Slide/Deck/
                          Pixel/Book), per-row controls, reorder +
                          group + multi-select, layer-options
                          field list
- panel-export.md       — Export options panel — show/print toggles
                          for guides/bleed/safe/crops/fold, mode-
                          specific hides
- panel-print-save.md   — Save / Open project JSON + Print/Save-as-
                          PDF print-dialog tips + Export JPEG/PNG +
                          mode-specific gotchas
- panel-credits.md      — Credits panel — Linked Designs contributor
                          surfacing, tip button, publish-time tag
                          emission, private-publish behaviour
- panel-tool-options.md — Right-side floating Tool Options pane —
                          how it switches between tool options and
                          layer options, cross-links to per-tool docs

Registered as a new "Sidebar panels" group ordered after Vector
tools in the docs sidebar.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 09:08:27 -04:00
thePR0M3TH3ANandClaude Opus 4.7 2d201ee522 docs: per-tool pages for the vector-canvas toolbar
Adds a new "Vector tools" group to the in-app docs sidebar with one
page per toolbar tool + one page per supporting panel:

Toolbar tools (8):
- tool-select.md           — Select (V): move/scale/rotate whole layers,
                             multi-select, marquee, dive-into-path
- tool-direct-select.md    — Direct Selection (A): per-anchor edits,
                             handle break, segment-direct-edit
- tool-hand.md             — Hand (H): pan + temp-pan via Spacebar,
                             plus zoom controls (which work from any
                             tool)
- tool-pen.md              — Pen (P): full Illustrator parity —
                             Bézier draw, mid-draw spacebar reposition,
                             Alt handle-break, auto-add/delete anchors,
                             Cmd+J join, Cmd+Alt+J average
- tool-pencil.md           — Pencil (B): freehand → smoothed Bézier
- tool-shape.md            — Shape (S): rect/ellipse/polygon/star/line
                             primitives + Shift/Alt modifiers
- tool-text.md             — Text (T): rich-text layer + Type panel
                             + token notes
- tool-eyedropper.md       — Eyedropper (I): window.EyeDropper sample +
                             inline ⊙ buttons + browser-support notes

Supporting panels (3):
- pathfinder.md            — Unite/Subtract/Intersect/Exclude via the
                             vendored polygon-clipping
- align-distribute.md      — six align ops + four distribute ops, with
                             behaviour around locked layers
- free-transform.md        — multi-layer scale + rotate handles on
                             the selection bbox

Plus an overview page (vector-tools.md) with a keyed table of every
shortcut, a description of how the toolbar interacts with the Layers
panel, and the canvas-level keyboard shortcuts (Shift/Alt/arrow
nudges/Cmd+D/etc).

All linked into _DOCS_TOPICS as a new "Vector tools" group, ordered
after the existing Template-mode layouts section.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 08:59:47 -04:00
thePR0M3TH3ANandClaude Opus 4.7 23a16c8e89 docs: in-app "Read the docs" modal + per-mode help pages
Adds a 📖 Docs link to the sidebar footer (next to GitHub + artstr.org)
that opens a Read-the-Docs-style two-pane modal: topic sidebar on the
left, rendered markdown on the right.

Content (src/help/*.md, 11 files):
- index.md            — overview + nav
- template.md         — Template tab explainer
- designer.md         — Designer tab explainer
- cover.md            — Case cover wrap
- disc.md             — Disc-label sheet
- jewel.md            — Jewel-case insert
- customart.md        — Custom art
- slide.md            — Slide
- deck.md             — Slide deck
- pixelart.md         — Pixel art
- book.md             — Book Designer (links out to the deeper
                        BOOK_DESIGNER_TUTORIAL.md + spec on GitHub)

Implementation:
- _DOCS_TOPICS registry maps slug → { title, group, file }. Sidebar
  groups headings under "Start here", "App tabs", "Template-mode
  layouts".
- loadDocsTopic(slug) fetches `help/<slug>.md` (force-cache), renders
  via the already-vendored marked, caches the rendered HTML for
  session-instant re-visits, and updates the URL hash via
  history.replaceState so deep links work.
- #/help/<slug> hash route auto-opens the modal at that topic on
  page load + on hashchange.
- Esc closes; backdrop click closes; internal `#/help/foo` links in
  page content load via the dispatcher (no page bounce); external
  http(s) links get target="_blank" + noopener.

Styling:
- Inherits the rest of the modal aesthetic (var(--line),
  var(--accent), system font for chrome, Georgia serif for body
  text). Dark-mode-aware backdrops on code / blockquote / table
  blocks.
- Two-pane layout collapses to a narrower sidebar on <720px viewports.

Files live in src/help/ (not docs/) because Vercel's outputDirectory
is "src" — the production site only serves files under src/. The
canonical deeper docs (BOOK_DESIGNER_TUTORIAL, FEATURE specs) stay
in docs/ and are linked-out from the in-app pages.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 08:52:34 -04:00
thePR0M3TH3ANandClaude Opus 4.7 f50d92043c docs: add BOOK_DESIGNER_TUTORIAL.md
Hands-on end-to-end walkthrough for the Book Designer feature —
setup, starting a book (templates / deck-import / scratch), Document
settings, masters + text-frame, Pages overview, designed-page editor,
chapter editor (full feature exercise: tables, task lists, footnotes,
xrefs, {toc}, {index}, ^[term]), cover view, visibility chip,
publishing order, resolver behaviour, Reader Mode, embed integration,
PDF export modal, tokens reference, edge cases to probe, and a
codebase map.

Companion to docs/BOOK_DESIGNER_FEATURE.md (the spec).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-04 08:41:25 -04:00
thePR0M3TH3ANandClaude Opus 4.7 0651a3e4fc book(I4): footnote refs + chapter-end definitions (v1)
Inline \`[^N]\` markers render as Unicode-superscript numerals
(⁰¹²³…) and \`[^N]: definition\` lines collect into a "Footnotes"
section appended to the chapter (after the body, separated by a
horizontal rule).

How it works:

- _processBookFootnotes(md):
  1. Sweeps definitions via /^[ \t]*\[\^id\]:[ \t]+(.+)$/gm,
     stores them in a Map keyed by raw id, strips the line from
     the body.
  2. Renumbers: each defined id gets a 1..N number based on first
     encounter of its \`[^id]\` ref in the body. Refs without a
     matching def pass through unchanged.
  3. Replaces every resolved ref with _bookSuperscript(num) so
     ¹² renders as actual superscript glyphs.
  4. Appends a "### Footnotes" section with one bullet per
     definition in canonical (first-ref) order.

Substitution sites:
- paginateChapter: runs after the {index} expansion + marker
  stripping, before xref resolution. Only fires when \`[\^\` is
  present in the source so most chapters pay zero cost.
- markdownToSafeHtml: same flow for the live preview pane.

Known v1 limitation (deferred to I4.2):
- Definitions render as a single end-of-chapter section rather than
  per-page in a footnote-frame layer on the master. Per-page
  footnote placement requires:
    * A new footnote-frame layer kind on masters (parallel to
      text-frame).
    * Page-aware definition collection in the pagination engine.
    * Page-break logic that keeps a ref + its def on the same page.
  Out of scope for this slice; the chapter-end section is the
  pragmatic v1.

Phase I scope: I1 ✅ I2.1 ✅ I2.2 ✅ I3 ✅ I4 ✅ I5 ✅ I6 ✅.
All spec'd Phase I features ship in v1 form.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:46:45 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5e3e7d1d7b book(I5): \{index}\ token + \^[term]\ inline markers
Authors mark indexable terms with \`^[term]\` inline anywhere in
chapter markdown. A standalone \`{index}\` line expands at render
time into a sorted alphabetical index with the page numbers the
term appears on.

How it works:

- _computeBookIndexEntries(book) walks every reflow chapter and
  scans its markdown for the \`^[term]\` pattern. Each unique term
  collects the page numbers (chapter-start granularity in v1) of
  the chapters it appears in. Results sort alphabetically by
  term.localeCompare. Chapter physical page counts are
  re-derived via paginateChapter({ _skipToc:true, _skipXref:true })
  so the index numbering tracks the same physical-page sequence
  enumerateBookPhysicalPages produces.
- _formatBookIndexMarkdown(entries) emits a "## Index" heading
  + one bullet per term: "- term — Page A, Page B".
- _stripBookIndexMarkers(md) replaces every \`^[term]\` with just
  the term text so the marker stays invisible in the rendered
  body. Done before xref substitution so a marker inside a link
  text doesn't confuse the matcher.

Substitution sites:
- paginateChapter: when opts._skipIndex is unset, expands
  \`{index}\` lines + strips markers. When _skipIndex is true (the
  inner pass triggered by the index builder), strips both the
  \`{index}\` placeholder line AND the markers.
- markdownToSafeHtml: same expansion for the chapter editor
  preview pane, so authors see the index as they type around it.

Phase I scope now: I1 ✅ I2.1 ✅ I2.2 ✅ I3 ✅ I5 ✅ I6 ✅. Only
I4 (footnotes with per-page footnote-frame layer on masters)
remains — it's the deepest of the bunch, requires a new layer kind
+ page-aware definition collection, and is the natural last slice
before the branch lands.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:45:33 -04:00
thePR0M3TH3ANandClaude Opus 4.7 f3d8135a9a book(I6): cross-references — [text](#chapter-N) → page numbers
Markdown links pointing at in-book anchors get their link text
augmented with the destination's page number at pagination time.

Slug matching:
- _bookSlugify(s) = lowercase + non-alphanumeric → hyphen, trim
  edge dashes (matches GitHub-style heading-anchor convention).
- _computeBookSlugToPageMap(book) walks every reflow chapter with
  _skipXref:true to avoid recursion, building a slug → page-number
  map that registers:
    * Each chapter's slugified chapterTitle.
    * "chapter-N" aliases for entries with chapterNumber.
    * Each H1-H6 heading's slugified text (first occurrence wins).

Substitution sites:
- paginateChapter: when the chapter markdown contains `](#anchor)`
  and !opts._skipXref, rewrites every matching link to include
  " (p. N)" in its visible text. Renders through DOM tile thumbs,
  Reader Mode, and PDF real-text emission the same way.
- markdownToSafeHtml: live preview pane gets the same rewrite so
  the author sees page numbers as they type.
- Unknown slugs pass through unchanged so a typo'd anchor stays
  visible as a regular markdown link instead of silently breaking.

Phase I now has every spec'd feature except I4 (footnotes) and I5
(auto-index from inline ^[term] markers) shipped end-to-end.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:43:48 -04:00
thePR0M3TH3ANandClaude Opus 4.7 4b2f124fde book(I2.2): full table layout in pagination + DOM + canvas + PDF
GFM tables are now first-class content blocks across every reflow
rendering surface: tile thumbnails, Reader Mode, PDF export with
real text emission. Replaces the I2.1 "[Table — N×M]" placeholder.

Pagination engine:

- For \`token.type === 'table'\`, computes equal-width columns
  (availW / colCount), wraps each cell's text into lines at the
  column's content width, and tracks row heights = max line count
  per row × line height + cell padding.
- Returns the block with \`tokenType: 'table'\` and a \`tableLayout\`
  payload carrying { colCount, colWidthIn, cellPadIn, align,
  headerCellLines, rows, header/row line-heights, totalHeightIn }.
- blockH baked from the layout so the page-break logic treats
  tables identically to other blocks.

DOM (paintReflowPageOverlay):

- Table blocks render as a real <table> with table-layout:fixed
  and per-column text-align, header in bold with a solid bottom
  border, body rows separated by hairline borders. white-space:
  pre-wrap preserves the engine's wrap output.

Canvas (renderBookReflowPhysicalPageToCanvas):

- Each cell draws via fillText at the column's offset; alignment
  honours the cell's column align directive. Hairline strokes
  separate the header row + each body row so the table reads as
  a grid even at lowish DPIs.

PDF real-text emission:

- When the block's font resolves to a StandardFonts key, each
  cell's lines emit as textOps with leftIn / widthIn / align
  scoped to the column's content rectangle. Header cells force
  the bold variant so the printer sees real bold glyphs.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:41:49 -04:00
thePR0M3TH3ANandClaude Opus 4.7 d16c475dca book(I3): \{toc}\ token — auto-generated table of contents
A standalone \`{toc}\` line in any chapter's markdown expands at
pagination time into a list of every reflow chapter's H1 / H2 / H3
headings paired with their physical page number.

Two-pass internally:

- _computeBookTocEntries(book) walks the manifest and paginates
  each reflow chapter with { _skipToc: true } so the inner pass
  treats {toc} as an empty placeholder, then collects every
  heading block plus the page number it lands on. Driven off the
  same numbering rules enumerateBookPhysicalPages uses
  (startAt / firstNumberedPage / skipNumber / skipNumberOnFirstPage).
- paginateChapter, when called without _skipToc and the markdown
  contains a standalone \`{toc}\` line, replaces that line with the
  generated TOC markdown before lexing. The TOC chapter itself
  paginates normally with the expanded content.
- markdownToSafeHtml does the same substitution so the chapter
  editor preview pane shows the rendered TOC as the user types
  around it.

Known v1 limitation:
- TOC page numbers come from the pre-expansion enumeration; if
  the TOC itself takes N pages, downstream chapter starts shift
  by ~N from what the TOC reports. For typical books this is a
  1–2 page offset. A full converging iteration is a follow-up
  (acceptable v1 trade-off so we don't spin paginating the same
  book repeatedly on every render).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:36:49 -04:00
thePR0M3TH3ANandClaude Opus 4.7 e5721b67a7 book(fix): tile action buttons + page-number chip always on top
The X / duplicate / proof / master-picker chrome on Pages overview
tiles was rendering behind the composited master+page thumbnail
because text layers carry their own zIndex inline (layer.z) and
the actions container had no stacking context.

- .bookPageTileActions gains z-index: 20 and the per-button
  children + master <select> get z-index: 21 with position:
  relative so they create their own stacking contexts (necessary
  for the select to dropdown above neighbouring tiles too).
- .bookPageTileNum gains z-index: 10 + a translucent white
  background so the page-number chip stays legible over dark
  text layers.

Reported: "the X button on the pages overview slide is behind
the master content. The pages buttons like the X should always
be rendered on top so the user can see them."

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:35:42 -04:00
thePR0M3TH3ANandClaude Opus 4.7 5caf1d48cd book(I2.1): GFM task lists + nested lists in pagination
marked switches to gfm:true everywhere (lexer + parser), unlocking:

- Task lists — `- [ ] todo` / `- [x] done` parse as list items with
  task:true + checked:bool. The pagination engine emits ☐ / ☑ as
  the marker prefix (replacing the bullet) so paginated and preview
  output both render the checkbox state.
- Nested lists — list items whose tokens contain an inner list
  recurse with a 3-space-per-level indent. Capped at depth 6 so
  pathological input can't blow the stack.
- Autolinks + strikethrough — parsed by marked into the HTML
  preview pane (the pagination engine flattens to plain text so
  strike doesn't visually render in paginated tiles, but the
  inline preview shows it).

Tables get a placeholder row in v1 — "[Table — N cols × M rows: …]"
with a warning surfaced through pag.warnings. Full column layout
ships in I2.2 once the renderer side (DOM + canvas + PDF emission)
gains a per-cell branch.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:32:58 -04:00
thePR0M3TH3ANandClaude Opus 4.7 a46d13f189 book(I1): CodeMirror markdown editor in chapter pane
Replaces the plain textarea with a CodeMirror 5 instance configured
for markdown — syntax highlighting, line wrapping, smart Enter for
list continuation.

Library:
- Vendored CodeMirror 5.65.16 as three files in src/vendor (core,
  markdown mode, CSS). ~188 KB total. Lazy-loaded through
  loadCodeMirrorOnce() so the cost is only paid when a chapter
  opens.

Wiring:
- openBookChapter kicks off _upgradeChapterEditorToCodeMirror after
  the existing sync runs. The upgrade swaps the textarea for a
  CodeMirror instance bound to its DOM node and stores the editor
  handle on ta._cmEditor.
- syncBookChapterEditor pushes value changes through the CM editor
  when present (only if value differs AND the editor doesn't have
  focus — preserves the user's caret + IME composition).
- Editor change → updateBookChapterMarkdown, which already debounces
  history + repaints the markdown preview + page-count readout.
- Enter on a list line auto-continues the bullet (- / * / +) or
  numbered marker (1. → 2.); pressing Enter on an empty list item
  breaks out of the list.
- Falls back gracefully to the plain textarea if the CodeMirror
  download fails (offline / blocked CDN re-host).

CSS:
- Forces .bookChapterEditorBody .CodeMirror to fill its grid cell
  (CodeMirror's default is 300 px); matches the surrounding
  monospace font + line-height so the pre/post-upgrade UX is
  visually consistent.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-06-03 18:08:42 -04:00