Commit graph

21 commits

Author SHA1 Message Date
Fini eb02fd2fe0 chore(editor): re-render stale preview artifacts
Brings every preview back to what the current binary produces for the
current document: 80 frame and overview PNGs and 18 card JPEGs. Most
follow the tracking re-alignment and the board re-wrap, but a share of
them had drifted purely because the renderer was rebuilt — the audit
does not distinguish, and does not need to, since the invariant is
simply that a saved image equals a fresh render byte for byte.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:17:24 +08:00
Fini ad1ed80a8d test(editor): audit preview artifacts by re-rendering them
Preview PNGs and card JPEGs go stale two ways. Editing a document and
forgetting to refresh is the obvious one. The other leaves no trace at
all: the renderer changes, the document does not, and every previously
saved image is silently wrong while git is clean, the tests are green
and the gate passes. That is what happened to three minimal-keynote
frames whose content matched HEAD exactly but whose pixels no longer
did, because the desktop binary had been rebuilt underneath them.

Nothing but re-rendering and comparing bytes can see the second case, so
that is the only criterion here. It rests on rendering being
deterministic — two runs of one document must be byte-identical, which
`--selftest` checks; if that ever stops holding, this tool's verdicts
are void and the renderer is the thing to fix.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:17:01 +08:00
Fini 4064f9c6dd style(editor): re-align template tracking to the ratio cap
Fifteen templates carried negative tracking past the cap the previous
rule failed to enforce — 51 text nodes in all, and nothing else in the
documents moves. minimal-keynote's four are hand-written literals in its
generator, so that one is fixed at the source rather than only in its
output; the rest come out of the shared `text()` clamp.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:16:46 +08:00
Fini 98f70579bf fix(ai): compare the CJK tracking cap as a ratio, not a rounded number
The negative-tracking cap was stated as `round(fontSize * -0.02)`, and
rounding it was wrong in both directions: at 76px it let -2 through when
the real cap is 1.52, and at 72px it failed a legitimate -1.4 because the
cap rounded down to 1. A rule that both over- and under-fires is not a
threshold worth tuning, so the comparison is now against the ratio itself
with fractional values allowed.

Two narrowing decisions come with it. The cap applies only to runs that
actually contain Han characters — Latin and numeric display (page
numbers, stat values) legitimately sets -0.03 to -0.05em, and 160 nodes
across the library were being flagged for it. And the 64px boundary is
gone, because below 48px the ratio is already self-limiting.

The clamp sits in `text()`, the one entry every generator goes through:
a single tracking literal is routinely shared by several sizes, so
fixing call sites one at a time is guaranteed to miss some. `trackcheck`
makes the rule enforceable outside the two generators that imported the
kit, which is how the drift got in unnoticed.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 21:16:35 +08:00
Fini c37a109e3c refactor(editor): wrap multi-board templates onto rows
Every multi-board template laid its boards out in one long horizontal
strip, so opening a seven-board carousel dropped the user onto a canvas
they had to pan sideways to survey. Three per row matches the deck kit,
and the extra row gap is deliberate: the canvas draws frame names at a
fixed screen-space offset, so at fit-to-screen zoom a plain column gap
lets the second row's labels sit on top of the boards above them.

knowledge-carousel additionally takes hard line breaks in two body
paragraphs. Greedy wrapping put a comma at the start of a line and left
a lone full stop on the last one, which are both CJK line-break faults;
authored breaks remove the engine's freedom to reintroduce them.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:55:00 +08:00
Fini 267a08f7aa feat(editor): register the six deck templates in the picker
Wires the new decks into the catalog, the preview grid and all fifteen
locales, so they are reachable rather than merely present on disk.

The preview cards ship as JPEGs like the rest, which grows the staged
scene-template payload; both figures in the asset notes are refreshed
so the wasm budget stays auditable against a real number.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:54:23 +08:00
Fini 728a32e7f6 feat(editor): add six 16:9 deck templates
The scene-template library carried one deck against a dozen carousel and
tutorial boards, so anyone starting a presentation started from a blank
board. These six cover the archetypes that actually get presented:
consulting strategy, data review, editorial keynote, workshop notes,
case dossier and financial ledger.

Each is seven or eight boards built from a shared generator kit, so the
grid, type scale and board wrap stay one decision rather than six.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:53:37 +08:00
Fini 3a8c154739 fix(renderer): keep transparent decoration layers out of the way of clicks
Two carousel templates stack a full-bleed transparent shell (washi tape,
punch holes, film sprockets) in front of their content. Every click over
the board landed on that shell instead of the text behind it.

The engine half is the jian bump: a container that paints nothing no
longer claims its own body in the hit-test. The template half marks
those shells `locked`, so the shell is unselectable as a layer while the
marks inside it stay individually selectable — dragging a piece of tape
no longer drags the whole layer, and a marquee no longer nets the board.

Both templates are regenerated output, so they also carry the row wrap
their generators gained in the same pass.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 19:53:09 +08:00
Fini 7970c0c108 chore(editor): keep the template QA gates beside the generators
cjkcheck (CJK line-break simulation), cjkreal (resolved-height cross
check that filters simulator false positives) and gate.sh (audit +
both, per-file verdicts) move from session staging into the generator
tree so the next template batch starts with the gates that vetted
this one. stubcheck is deliberately not kept — the audit's diagnostics
already cover empty decorated stubs via the same predicate.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:24:44 +08:00
Fini 6fd0b343a1 fix(editor): tighten template copy that wrapped into orphan lines
A CJK line-break gate over all fifty-eight templates caught eleven
sets whose body copy wrapped into three-character orphan tails or
punctuation-led lines. Each line is shortened to fit its measured
column (paired sides stay symmetric where the layout demands it),
affected root heights re-measured, previews re-baked. The one
remaining gate report is a simulator false positive disproven by the
rendered frame.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:24:44 +08:00
Fini c998d551eb feat(editor): grow the template library to fifty-eight across six scenes
Every non-deck, non-web scene now holds ten templates: cards (spec
themes as living samples), infographics, tutorials, comparisons and
carousels (nine spec themes with per-set master-board rules), plus
two web landings whose 'generate from this' pins their measured
style guides — a basis the document swap used to drop before the
turn ever saw it. All fifty-eight ship generator-reproduced (sha256-
matched), audit-clean, with baked preview cards, and the scene tab
row pins Slides second-from-last and Web last. Two prompt-center
entries carry the web pair with re-rendered previews and honest
render provenance.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-09 02:24:43 +08:00
Fini bb01fd54bc feat(editor): refresh deck template visuals 2026-08-07 02:00:22 +08:00
Fini a73f19a852 feat(editor): grow the asset center into a full-window gallery with dual-action templates
The scene template center was a fixed 720x554 dialog centred on the canvas
region, which a 240px rail visibly pushed off the window's centre. It now
insets from the whole viewport under a full-window scrim, flows 2/3/4
columns by width with card heights derived from a 16:10 preview (the old
0.62 cap that letterboxed every preview is gone), caps the content column
at 1680, and ships previews rebaked at 1024x640.

Template cards grow two actions: "add to canvas" appends the template's
boards into the current document in one transaction — template variables
are namespaced per template so two decks can no longer recolour each other,
and batch insertion no longer swallows a pre-existing empty root — while a
pristine starter still opens the template directly. "Generate from this"
pins the template's style guide, narrows the scene filter, and focuses the
topic field behind a clearable "based on" chip, so the existing pipeline
generates in that template's look. The web host also gains the missing
open/adopt drain; clicking a template there previously did nothing.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-07 02:00:22 +08:00
Fini cc9b7889e2 feat(editor): grow the template library to five deck styles and a card tier
Adds a knowledge-card scene (vertical and square social cards) and
four presentation styles beside the original warm-white deck: a dark
pitch deck, a paper-white lecture deck, a minimal keynote and a
gradient-tech deck with glassmorphism built from opaque composited
stops — the linter drops alpha channels, and a truly translucent card
would exempt its text from contrast checking forever. Every palette
pair ships with measured WCAG ratios baked into the generators, and
low-contrast probe documents prove the audit actually fires on each
template family.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-05 00:47:50 +08:00
Fini e7016a3302 fix(canvas): attach frame-name labels to the frames they name
The label band was built from raw magic numbers — baseline 18 px above the
frame, hit box from 32 px above it — which left the text's visual bottom
15 px clear of the frame. Because the offset is screen-space and does not
scale with zoom, that gap is the same 15 px however far out the canvas is,
so on a zoomed-out board the name reads as floating in empty canvas rather
than belonging to the frame beneath it.

Shrink the band instead of scaling it with zoom. A constant screen-space
offset is the right model for chrome — it is why the label stays legible
at any zoom — and the defect was only that the constant was too big;
scaling by zoom would have kept the same too-tall band at 100% and made
the doc-space clearance a moving target that the generator cannot reserve
space for ahead of time. So the geometry is now derived from the type it
draws rather than picked: a 6 px gap from the text's descender to the
frame edge, plus the 12 px font's 9 px cap height and 3 px descent, plus
3 px of box padding. Baseline lands 9 px above the frame and the hit box
spans 21 px to 3 px above it — down from a 32 px band, with the visible
gap cut from 15 px to 6.

Paint, hit-test and cull all read the same derived constants, so they
cannot drift apart. Three invariants are now asserted rather than assumed:
the hit box never reaches the frame's top edge (otherwise a click just
inside the frame would select the root via its label instead of the node
under the cursor), the glyphs and the taller generating icon stay inside
the box that hit-tests them, and the text stays within ~8 px of its frame.

ROW_LABEL_HEADROOM stays at 240 doc px, re-derived against the narrower
band: the extra margin costs nothing on an infinite canvas and the
zoomed-out end of the range is worth over-serving. Comments there and in
the slide template are updated to the new number.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 23:43:33 +08:00
Fini e5e84ea932 fix(agent): give wrapped canvas rows headroom for frame labels
The canvas paints each top-level frame's name ABOVE the frame at a fixed
screen-space offset that does not scale with zoom (canvas_frame_labels.rs
puts the baseline at sy-18 and the label box top at sy-32). A row gap
equal to the column gap therefore cannot work: at the zoom where a
wrapped canvas is actually viewed, the gap is narrower than the label
itself, so every second-row label sat pressed against the bottom edge of
the row above.

For the 3x2 deck the numbers are exact: 6000 doc px of content framed by
zoom_to_fit into a 944-1424 px canvas region lands at zoom 0.14-0.22,
where a 120 doc px row gap is only 16-26 screen px against a label that
always occupies 32. It overlapped at every realistic window size.

Add 240 doc px of headroom to the VERTICAL step only — 33-52 screen px at
that zoom, clearing the label with room to spare. Columns are untouched:
labels are left-aligned to their own frame and never reach sideways. The
slide-deck template takes the same allowance on top of its own gap, so
both the generated canvas and the shipped template space rows the same
way.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 23:31:36 +08:00
Fini 2b01566aac feat(agent): wrap multi-screen roots into rows on the canvas
A fan-out laid every screen root in one unbroken horizontal strip. Six
1920px deck boards are ~12,000px across, so the result could only be read
by panning, and twenty boards would be unusable.

Wrap by BOARD WIDTH, not by design type or screen name: width is the fact
that makes a strip unreadable, whereas "is this a deck" is a guess that
misfires on every design whose naming we did not anticipate. A row budget
of 8200px lands where each device class wants it — 1920 decks wrap at 4
per row, 1200 desktop screens at 6, and 390 phone screens at 17, i.e. a
mobile fan-out is unchanged for any realistic count.

The row step reads the same resolved height the scaffold builds with, so
a plan asking for `height: 0` ("size me from content") steps down by the
device-class preset instead of overlapping row one.

The slide-deck template follows the same convention, wrapping at 3 per
row so the six-slide deck reads as the 3x2 grid its preview shows.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 21:27:19 +08:00
Fini ad2f0e4a6a fix(panels): scroll the template grid on trackpad, and lay the deck out
Two defects from the same blind spot: verifying a thing one way and
assuming the other way works.

Trackpad scrolling over the Scene Template Center moved the canvas while
the card grid sat still. Wheel deltas route through `apply_wheel_inner`,
trackpad pans through `apply_pan_gesture` — two ladders — and the panel
was wired into only the first. Every mouse-wheel check passed. The
Prompt Center's own host test already covered both paths; this adds its
twin, which fails without the fix.

The shipped deck template opened as one visible slide with five stacked
underneath it. Its generator never set `x`/`y`, so every board defaulted
to the origin. It went unnoticed because the deck was only ever
inspected through per-frame rendering, which draws each board alone —
the stacking exists solely in the assembled document. `knowledge-carousel`
has laid its frames out since it shipped, so this was breaking an
existing convention rather than lacking one.

The catalogue now asserts that every multi-frame template positions each
frame and that no two share a spot, so the next template cannot repeat
it. Fixed in the generator, not the artefact, so regenerating keeps it.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 21:21:13 +08:00
Fini 048d3cf36f feat(editor): add the six-page presentation template
The `slides` scene had a filter chip and no content. This fills it with a
deck that covers the shapes a report actually needs — cover, agenda,
three-point argument, metrics, chart with its takeaway spelled out, and a
closing — so a user replaces text rather than building structure.

Typography and layout follow the `slides` domain contract rather than
being eyeballed: 1920x1080 frames at a fixed size (never fit_content —
projector aspect is a hard constraint), 120px safe margins, body at
26-34, titles at 64, and the metric numbers at 140 so each data slide has
exactly one visual anchor. Frames are generated through `oplib` against
the canonical schema, like the other templates, so they stay reproducible.

The card baker learns to tile multiple renders into a grid. A 16:9 deck
composited as a 1x6 strip is a 10:1 image that shrinks each slide to
~100px inside the 640x400 card — visible as noise, not as slides. Column
count is picked to land nearest the card's own aspect, which puts this
deck at 3x2 and leaves each slide legible.

Also ignores `__pycache__` repo-wide; the generators are run in place and
were leaving bytecode next to their sources.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 11:00:34 +08:00
Fini 20cfbddd4b feat(editor): add the scene template catalogue
First layer of the scene template center: the data model behind "pick a
scene, get a finished document". Templates differ from Prompt Center
entries in what they produce — a document to edit, not text for the
model — so each entry embeds its `.op` and the catalogue refuses to load
when one is missing. A prompt without a preview still works; a template
without a document is a card that does nothing when clicked, which is
worse than no card at all.

The three step-0 documents move from `templates/` into the crate's
assets: they are shipped content now, and one authoritative copy beats a
source and an embedded duplicate that drift. `templates/step0` keeps the
generators and their full-resolution renders — the workshop, not the
shelf — and `previews.sh` follows the documents to their new home.

Card previews are baked to the prompt-center convention (640x400 JPEG,
13-15 KB) by a new generator. The panel paints every card through one
fixed rect with `ImageDrawMode::Fill`, so a 2160x2880 page render or a
5880x1440 overview would be cropped to an unreadable strip; the baker
fits the whole design inside the card and pads with the document's own
backdrop, sampled from the render's corners.

The TOML value parsers move to a shared `catalog_toml` rather than being
copied for the second catalogue. Each catalogue keeps its own error type
so the messages still name the right asset file; only the parsing is
shared. Prompt Center behaviour is unchanged (its 14 tests still pass).

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 11:00:34 +08:00
Fini d64a4ffe9b feat(editor): add step-0 scene templates for the mass-market track
Three finished scene documents — screenshot tutorial, before/after
comparison, and knowledge carousel — plus their preview renders and the
generator scripts that produced them.

These have lived outside version control since 2026-07-27 while the
mass-market direction was still provisional. They are about to become
the seed content for the scene-template entry point, so the documents
and the scripts that regenerate them need to travel together: a template
whose generator is lost can only be edited by hand from then on.

The `_generators` scripts build the documents through the canonical
schema rather than exporting from a live editor, which keeps them
reproducible and free of editor-session state.

Claude-Session: https://claude.ai/code/session_01FqKQqNj8exYwopGDpYUU7x
2026-08-02 11:00:34 +08:00