feat(cards): auto-paginate long class/subclass cards (#73) #79

Merged
Reliodas merged 16 commits from feat/73-classes-pagination into develop 2026-07-06 14:46:35 +00:00
Owner

Génère automatiquement plusieurs cartes recto/verso pour les classes et sous-classes longues à partir d'une entrée de données unique, en remplacement du pré-découpage manuel des données (#73).

Mécanisme (partagé dans card-editor.js)

  • distributePages (pure) : répartition des groupes de niveaux par carte (1 colonne en portrait, 2 en paysage), coupe à la frontière de groupe, jamais en son milieu.
  • measureGroupHeights + renderLevelColumns : mesure DOM et markup de colonnes.
  • Hook expand sur initPrintQueue : un snapshot par carte générée (compteur d'impression correct, agencement duplex inchangé). Défaut = identité → autres éditeurs intacts.

classes.html / subclasses.html

  • Aperçu et impression rendent chaque carte via la même fonction paire (aperçu == impression).
  • Snapshots du panier deep-clonés → stabilité (une édition post-ajout ne fuit plus dans l'impression).
  • Gabarit de mesure aligné sur le vrai en-tête ; sous-classes : hauteur de 1re carte réduite pour le flavor.
  • Recto de chaque carte : plage de niveaux couverte (« NIV. 6–10 »). Sous-classes : flavor + source sur la 1re carte seulement, label par tranche (repli sur f-levels).
  • Police à taille lisible fixe → on pagine plutôt que de réduire jusqu'à l'illisible ; groupe isolé trop grand → filet du fit #72.

Données

  • data/classes.js : 26 entrées pré-découpées consolidées en 5 classes (Barde, Clerc, Roublard, Moine, Artificier). Aucun niveau/feature perdu (vérifié). NB : les 3 entrées « Barde » avaient des theme divergents → « ombre » conservé (à valider côté contenu).

Tests

  • distributePages : assertions unitaires dans scripts/smoke-test.js.
  • tests/visual-validation.js : cas classe longue (Moine) → >1 carte + zéro clipping en portrait et paysage (les 2 colonnes mesurées).
  • Docs : AGENTS.md + README.md.

Vérification

  • node scripts/smoke-test.js · node tests/visual-validation.js (8 pages)
  • Processus subagent-driven : 8 tâches revues + revue finale de branche (opus) « Ready to merge: Yes ». Régression zoom d'aperçu trouvée en revue finale et corrigée (.pair-zoom-wrapper, aperçu seulement ; impression inchangée).

Réf. #73

🤖 Generated with Claude Code

Génère automatiquement plusieurs cartes recto/verso pour les classes et sous-classes longues à partir d'une **entrée de données unique**, en remplacement du pré-découpage manuel des données (#73). ## Mécanisme (partagé dans `card-editor.js`) - **`distributePages`** (pure) : répartition des groupes de niveaux par carte (1 colonne en portrait, 2 en paysage), coupe à la frontière de groupe, jamais en son milieu. - **`measureGroupHeights`** + **`renderLevelColumns`** : mesure DOM et markup de colonnes. - Hook **`expand`** sur `initPrintQueue` : un snapshot par carte générée (compteur d'impression correct, agencement duplex inchangé). Défaut = identité → autres éditeurs intacts. ## classes.html / subclasses.html - Aperçu **et** impression rendent chaque carte via la **même** fonction paire (aperçu == impression). - Snapshots du panier **deep-clonés** → stabilité (une édition post-ajout ne fuit plus dans l'impression). - Gabarit de mesure aligné sur le vrai en-tête ; sous-classes : hauteur de 1re carte réduite pour le flavor. - Recto de chaque carte : plage de niveaux couverte (« NIV. 6–10 »). Sous-classes : flavor + source sur la 1re carte seulement, label par tranche (repli sur `f-levels`). - Police à taille lisible fixe → on pagine plutôt que de réduire jusqu'à l'illisible ; groupe isolé trop grand → filet du fit #72. ## Données - `data/classes.js` : 26 entrées pré-découpées consolidées en **5 classes** (Barde, Clerc, Roublard, Moine, Artificier). Aucun niveau/feature perdu (vérifié). NB : les 3 entrées « Barde » avaient des `theme` divergents → « ombre » conservé (à valider côté contenu). ## Tests - `distributePages` : assertions unitaires dans `scripts/smoke-test.js`. - `tests/visual-validation.js` : cas classe longue (Moine) → >1 carte + zéro clipping en portrait **et** paysage (les 2 colonnes mesurées). - Docs : AGENTS.md + README.md. ## Vérification - `node scripts/smoke-test.js` ✅ · `node tests/visual-validation.js` (8 pages) ✅ - Processus subagent-driven : 8 tâches revues + revue finale de branche (opus) « Ready to merge: Yes ». Régression zoom d'aperçu trouvée en revue finale et corrigée (`.pair-zoom-wrapper`, aperçu seulement ; impression inchangée). Réf. #73 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Design for auto-paginating long class and subclass cards into multiple
recto/verso cards from a single data entry: shared measure + pure
distribute helpers in card-editor.js, stacked preview rendered through the
print pair function, print-queue expand hook yielding one snapshot per card,
level-range recto label as continuation marker, and consolidation of the
manually pre-split data entries. Fixed legible font size, paginate rather
than shrink to illegibility.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the task-by-task implementation plan (pure distributePages + smoke
assertions, DOM measurement, shared column markup, print-queue expand hook,
classes and subclasses pagination, data consolidation, visual test, docs).
Record the locked subclasses decisions in the spec (per-card level label,
flavor/source on first card only, reduced first-card height).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the shared pagination helpers (distributePages,
measureGroupHeights, renderLevelColumns, expand hook) into classes.html:
updatePreview() now measures level-group heights against two persistent
off-screen measuring cards (portrait full-width, landscape half-width)
and regenerates #cards-container as N stacked recto/verso pairs, one
per generated page, each with its own "NIV. x-y" recto label. The print
queue captures a base snapshot and expands it into one entry per page.

Since #cards-container is now fully regenerated on every render, the
old fixed ids (#card-front, #card-back, #back-levels...) no longer
persist across calls: guard the header-writing lines in updatePreview,
guard the shared setFormat()'s classList.toggle in card-editor.js, and
re-attach the #card-art id (with its drag-to-pan wiring) to the live
first-page art zone after each regeneration so image positioning keeps
working. Also generalize visual-validation.js's per-format check to
walk each generated .print-pair when present, instead of assuming a
single fixed #card-front/#card-back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review found three defects in the classes.html pagination rewrite:

- The hidden measuring scaffold's header only rendered the name line,
  under-measuring available height by the ability/saves lines that the
  real back header always includes, clipping the first generated page.
  Sync the two persistent measuring cards' ability/saves lines with the
  live form values before measuring.
- capturePages() queued direct references to live levelGroups objects,
  so editing a feature after adding a page to the print queue silently
  rewrote the already-queued snapshot. Deep-clone each group and its
  features array when building queue snapshots (capturePages), matching
  the depth of the pre-rewrite captureSnapshot.
- The rewrite dropped the "Ajoutez des niveaux dans l'onglet Niveaux."
  empty-state message for a class with zero level groups, rendering a
  blank panel instead. Reintroduce it in renderClassCardPairHtml when a
  page has no groups.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(cards): restore preview zoom on paginated class/subclass cards (#73)
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
3ee4a3154c
Pagination replaced #cards-container with .print-pair blocks that carry
no .card-wrapper, so --zoom (only consumed by .card-wrapper) no longer
scaled the preview. Add a preview-only .pair-zoom-wrapper (shared.css)
sized for a full front+back pair, and wrap each generated pair in it
from renderClassPages()/renderSubclassPages() only. The shared pair
render functions and the print queue / #print-area path are untouched,
so print output stays byte-identical.
Root cause: pagination measured group heights and available height in an
on-screen scaffold with no card border, but the printed card
(#print-area.print-aligned .card and @media print .card) has a ~0.5px→1px
border. That border makes the printed levels column ~2px narrower, which
re-wraps the feature text onto extra lines and adds real height (e.g. a dense
level-1 Roublard card grew from 273px measured to 291px printed), so a card
packed to the measured limit clipped its last line when printed
recto/verso — visible in the aligned duplex output.

Fix: give the two measuring scaffolds the same 0.5px border as the printed
card, so their inner width (text wrapping) and height match print. Also
reserve a small CARD_PAGINATION_SAFETY_PX margin for sub-pixel rounding and
distributePages' +1 tolerance, so cards never pack to the exact pixel.

Test: the existing pagination check measured only the preview (borderless), so
it could not see this. Add checkAlignedPrintOverflow, which rebuilds the
aligned-print back sheet exactly like printAligned() and asserts no back column
clips in the print geometry, for Roublard (classes) and a long subclass. It
fails on the pre-fix code (Roublard overflows 2px) and passes after.

Verified: node scripts/smoke-test.js and node tests/visual-validation.js pass;
a standalone print-geometry sweep of all consolidated classes and every
subclass shows 0px overflow in both formats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The earlier preview-zoom restore wrapped a whole recto+verso pair in a single
.pair-zoom-wrapper scaled from its center, so zooming grew the pair outward
from the point between the two cards and pushed it out of the frame (negative
left offset), instead of scaling one card like every other editor.

Replace it with renderPagesToWrappers(): each generated card goes in its own
.card-wrapper (the same structure the non-paginated editors use), so preview
zoom scales each card individually, cards flow/wrap inside #cards-container and
stay framed, and labels read RECTO/VERSO n/N. The shared pair function and the
print/#print-area flow are untouched — only the preview wrapping changed.

Remove the now-unused .pair-zoom-wrapper CSS.

Tests: the preview is no longer built from .print-pair / #card-front, so update
checkFormat to pick cards by #card-front id (single-card editors) or by
.card-front/.card-back class (paginated classes/subclasses), and count generated
cards by .card-front. Aligned-print overflow guard and all editors stay green.

Verified: smoke-test, visual-validation, and a print-geometry sweep pass; a
zoom repro shows classes/subclasses now match creatures (952px card, framed,
0 horizontal overflow at 4x) with no card off-frame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(cards): landscape wrapper margins + regression tests (#73)
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
25fc6d2185
Code review of the two fix commits found one confirmed bug and one convention
gap:

- Landscape preview overlap: renderPagesToWrappers wraps each generated card in
  .card-wrapper, whose zoom margin reservation used the portrait constants
  --card-w/--card-h. For landscape cards (wider/taller) the reservation is too
  small, so stacked wrappers overlap — the VERSO label bled onto the RECTO below
  (41px at 2x on a multi-card class). Add a .card-wrapper:has(.card.landscape)
  rule using --card-w-land/--card-h-land; this also fixes the same latent case
  for the single-card editors, rather than special-casing pagination.
- CHANGELOG had no #73 entry though pagination changes print behaviour; add one.

Regression tests (all verified to fail on the corresponding broken code, pass
after):
- checkClassLongPagination now also asserts one .card in each .card-wrapper (no
  pair-wrapper regression) and no vertical overlap between stacked preview cards.
- checkAlignedPrintOverflow now sweeps EVERY class and subclass preset (not just
  Roublard / Domaine de la Vie) in the aligned-print geometry.
- New checkQueueSnapshotStability: editing a group after capture must not leak
  into a queued snapshot (guards the Task 4 deep-clone).

Verified: smoke-test and visual-validation pass; the overlap and snapshot guards
each fail on the reverted fix and pass restored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reliodas merged commit 748c7b7b12 into develop 2026-07-06 14:46:35 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: dnd/cards#79
No description provided.