709 lines
25 KiB
Markdown
709 lines
25 KiB
Markdown
---
|
||
name: worksheet-layout
|
||
description: >
|
||
Unified layout system for printable A4 math worksheets. Use this skill when
|
||
creating or editing template.html files, building editors (tune or main),
|
||
writing generate.mjs scripts, or working with data.json for any task type.
|
||
Covers: page structure, coordinate system, units, element hierarchy, grouping,
|
||
z-index layering, data.json delta format, merge operations, editor helpers API,
|
||
and mandatory e2e testing protocols. Does NOT apply to existing task types
|
||
(space-exploration, collecting-asteroids, asteroid-splitting, space-route) —
|
||
only to newly created task types.
|
||
---
|
||
|
||
# Worksheet Layout System
|
||
|
||
Unified rules for structuring, positioning, and editing elements in printable A4 math worksheets. This skill is the single source of truth for layout decisions in new task types.
|
||
|
||
## When to Use
|
||
|
||
- Creating a new task type (`tasks/{type}/`)
|
||
- Writing or editing `*.template.html` files for new task types
|
||
- Building tune-editor or main-editor for a new task type
|
||
- Writing `generate.mjs` for a new task type
|
||
- Performing a merge operation on any task type using this system
|
||
- Reviewing or debugging layout/editor issues in new task types
|
||
|
||
## Does NOT Apply To
|
||
|
||
Legacy task types created before this system: `space-exploration`, `collecting-asteroids`, `asteroid-splitting`, `space-route`. Those keep their existing patterns.
|
||
|
||
---
|
||
|
||
## 1. Units
|
||
|
||
| What | Unit | Notes |
|
||
|------|------|-------|
|
||
| All layout dimensions | `mm` | widths, heights, positions, margins, gaps, paddings |
|
||
| Font sizes | `rem` | Only exception. Never mm for fonts |
|
||
| Borders, shadows | `px` | Thin decorative lines only (1-2px) |
|
||
|
||
**Banned in layout context:** `%`, `em`, `vw`, `vh`, `calc()` with mixed units.
|
||
|
||
**Why mm:** These are print documents (A4 = 210×297mm). Millimeters map 1:1 to physical output. No ambiguity, no container-relative surprises, no calc() fragility.
|
||
|
||
**Conversion reference:** At 96 DPI, 1mm ≈ 3.78px. Editor helpers handle mm↔px conversion via `mmToPx` ratio from EditorCore.
|
||
|
||
---
|
||
|
||
## 2. Page Structure
|
||
|
||
Every page follows a fixed three-layer structure:
|
||
|
||
```
|
||
┌─────────────────────────────┐
|
||
│ Page (210×297mm) │
|
||
│ ┌─────────────────────────┐ │
|
||
│ │ Header │ │ ← hero image, title, subtitle
|
||
│ ├─────────────────────────┤ │
|
||
│ │ │ │
|
||
│ │ Content Area │ │ ← fills remaining space
|
||
│ │ ┌───────┐ ┌───────┐ │ │
|
||
│ │ │Section│ │Section│ │ │ ← sections arranged by layout
|
||
│ │ └───────┘ └───────┘ │ │
|
||
│ │ ┌───────┐ ┌───────┐ │ │
|
||
│ │ │Section│ │Section│ │ │
|
||
│ │ └───────┘ └───────┘ │ │
|
||
│ │ │ │
|
||
│ ├─────────────────────────┤ │
|
||
│ │ Footer │ │ ← decorative (planet, wave, etc.)
|
||
│ └─────────────────────────┘ │
|
||
└─────────────────────────────┘
|
||
```
|
||
|
||
### HTML pattern
|
||
|
||
```html
|
||
<div class="page w-[210mm] h-[297mm] relative overflow-hidden mx-auto bg-white"
|
||
style="break-after: page;">
|
||
|
||
<!-- Header -->
|
||
<div class="page-header" style="height: 45mm;">
|
||
<!-- hero image, title, subtitle -->
|
||
</div>
|
||
|
||
<!-- Content Area — sections live here -->
|
||
<div class="page-content"
|
||
style="position: absolute; top: 45mm; bottom: 30mm; left: 8mm; right: 8mm;">
|
||
<!-- Section layout (grid, flex, stack) defined per task type -->
|
||
</div>
|
||
|
||
<!-- Footer -->
|
||
<div class="page-footer absolute bottom-0 left-0 right-0" style="height: 30mm;">
|
||
<!-- decorative footer image -->
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**Header and footer heights** are set per task type in the template. Content area fills the remaining space using absolute positioning with `top` and `bottom`.
|
||
|
||
**Section layout** is decided per task type and coded directly in the template. Common patterns:
|
||
- **Vertical stack:** `display: flex; flex-direction: column; gap: 4mm;`
|
||
- **2-column grid:** `display: grid; grid-template-columns: 1fr 1fr; gap: 3mm;`
|
||
- **2×3 grid:** `display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr 1fr; gap: 2mm;`
|
||
- **Dense table:** `display: grid; grid-template-columns: repeat(4, 1fr); gap: 1mm;`
|
||
|
||
No abstraction layer for layouts — each task type sets this directly in CSS. The formalization is in shared vocabulary and understanding, not in code.
|
||
|
||
---
|
||
|
||
## 3. Section (Base Unit)
|
||
|
||
A **section** is the atomic container for one task/problem. All coordinates inside a section are relative to the section's top-left corner.
|
||
|
||
```html
|
||
<div class="section" style="position: relative; overflow: hidden;">
|
||
<!-- All child elements positioned relative to this container -->
|
||
</div>
|
||
```
|
||
|
||
**Rules:**
|
||
- Section has `position: relative` — it is the coordinate origin for children
|
||
- Section has `overflow: hidden` — nothing bleeds out
|
||
- Section dimensions come from the layout (grid/flex) — not set explicitly
|
||
- All child positions are in mm, relative to section's top-left (0,0)
|
||
|
||
---
|
||
|
||
## 4. Element Hierarchy Inside a Section
|
||
|
||
```
|
||
Section (position: relative, overflow: hidden)
|
||
├── Group (position: absolute, left/top in mm)
|
||
│ ├── Element (position: absolute, left/top in mm from group)
|
||
│ └── Element
|
||
├── Element (position: absolute, left/top in mm from section)
|
||
└── Group
|
||
└── Element
|
||
```
|
||
|
||
### Groups
|
||
|
||
A **group** bundles related elements that move together. Group position is relative to section. Element positions inside a group are relative to the group.
|
||
|
||
```html
|
||
<div class="section" style="position: relative; overflow: hidden;">
|
||
|
||
<!-- Group: ship assembly -->
|
||
<div data-edit="ship-group" data-edit-props="dx,dy"
|
||
style="position: absolute; left: 50mm; top: 10mm;">
|
||
|
||
<img src="ship.png" style="width: 40mm; height: 20mm;">
|
||
|
||
<div data-edit="badge" data-edit-props="dx,dy,scale"
|
||
style="position: absolute; left: 15mm; top: 8mm;">
|
||
<span>10</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Standalone element -->
|
||
<img data-edit="asteroid" data-edit-props="dx,dy,scale,rotate"
|
||
style="position: absolute; left: 5mm; top: 12mm; width: 14mm; height: 14mm;"
|
||
src="asteroid.png">
|
||
</div>
|
||
```
|
||
|
||
### When to use a Group vs standalone Element
|
||
|
||
- **Group** when elements are conceptually one object (ship + badge + cargo = ship-group)
|
||
- **Standalone** when the element is independent (a floating asteroid, an input field)
|
||
- Moving a group moves all children. Moving a child moves only within the group
|
||
- Never nest groups deeper than 2 levels (section → group → element). If you need deeper nesting, flatten
|
||
|
||
### Z-Index Inside Section
|
||
|
||
Use z-index values 1-10 within a section. Assign in semantic order:
|
||
|
||
```
|
||
z-1: background decorations
|
||
z-2: main objects (ships, large images)
|
||
z-3: secondary objects (smaller elements on top of main)
|
||
z-4: connectors (arrows, lines, SVG paths)
|
||
z-5: interactive elements (input fields, answer boxes)
|
||
z-6: overlays (badges, labels, indicators)
|
||
```
|
||
|
||
Set z-index on the group/element level, not on inner wrappers. Keep it flat — if you need z-index within a group, the group's own stacking context handles it.
|
||
|
||
---
|
||
|
||
## 5. data-edit Attributes
|
||
|
||
Mark editable elements in the template HTML:
|
||
|
||
```html
|
||
<div data-edit="element-id" data-edit-props="dx,dy,scale,rotate">
|
||
```
|
||
|
||
| Attribute | Purpose |
|
||
|-----------|---------|
|
||
| `data-edit` | Unique ID within the section. Used as key in data.json |
|
||
| `data-edit-props` | Comma-separated list of allowed delta properties |
|
||
|
||
**Naming convention:** kebab-case. Examples: `ship-group`, `space-asteroid`, `badge-label`, `inner-cargo`.
|
||
|
||
**Allowed props:**
|
||
|
||
| Prop | Type | Unit | Meaning |
|
||
|------|------|------|---------|
|
||
| `dx` | number | mm | Horizontal offset from base position |
|
||
| `dy` | number | mm | Vertical offset from base position |
|
||
| `scale` | number | multiplier | Size multiplier (1.0 = original) |
|
||
| `rotate` | number | degrees | Rotation angle |
|
||
|
||
Additional props can be added ad-hoc per task type (e.g., `opacity`, `color`). Base props are always dx, dy, scale, rotate.
|
||
|
||
---
|
||
|
||
## 6. data.json Format
|
||
|
||
Stores **deltas only** for elements marked with `data-edit`. Created by the main editor.
|
||
|
||
```json
|
||
{
|
||
"pages": [
|
||
{
|
||
"page": 1,
|
||
"sections": [
|
||
{
|
||
"index": 0,
|
||
"elements": {
|
||
"ship-group": { "dx": 3.5, "dy": -1.0 },
|
||
"badge": { "scale": 1.2 },
|
||
"space-asteroid": { "dx": -2, "dy": 1, "rotate": 15 }
|
||
}
|
||
},
|
||
{
|
||
"index": 1,
|
||
"elements": {
|
||
"space-asteroid": { "scale": 1.3 }
|
||
}
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
**Rules:**
|
||
- Only elements with actual changes appear (sparse, not exhaustive)
|
||
- Zero-deltas are omitted (`dx: 0` → don't include)
|
||
- Scale default is 1.0 — only include if ≠ 1.0
|
||
- Rotate default is 0 — only include if ≠ 0
|
||
- Element IDs match `data-edit` values in template HTML
|
||
- Section index is 0-based within the page
|
||
|
||
---
|
||
|
||
## 7. generate.mjs — Applying Deltas
|
||
|
||
Each task type has its own `scripts/generate.mjs`. The delta application logic is standardized:
|
||
|
||
```javascript
|
||
// Pseudocode for applying deltas to a section element
|
||
function applyDelta(element, delta) {
|
||
const style = parseInlineStyle(element);
|
||
|
||
if (delta.dx != null || delta.dy != null) {
|
||
const baseLeft = parseFloat(style.left); // mm
|
||
const baseTop = parseFloat(style.top); // mm
|
||
style.left = `${baseLeft + (delta.dx || 0)}mm`;
|
||
style.top = `${baseTop + (delta.dy || 0)}mm`;
|
||
}
|
||
|
||
if (delta.scale != null) {
|
||
addTransform(style, `scale(${delta.scale})`);
|
||
}
|
||
|
||
if (delta.rotate != null) {
|
||
addTransform(style, `rotate(${delta.rotate}deg)`);
|
||
}
|
||
|
||
element.setAttribute('style', serializeStyle(style));
|
||
}
|
||
```
|
||
|
||
**Key principles:**
|
||
- dx/dy are ADDITIVE to base position (base 50mm + dx 3.5 = 53.5mm)
|
||
- scale is ABSOLUTE (scale 1.3 means 1.3×, not base × 1.3)
|
||
- rotate is ABSOLUTE (rotate 15 means 15°, not base + 15°)
|
||
- Find elements by `[data-edit="id"]` selector, scoped to section index
|
||
- Never chain scripts. generate.mjs reads template + data.json, writes output.html
|
||
|
||
### Shared helper for generate scripts
|
||
|
||
Task-type generate.mjs scripts should use a shared helper for delta application. This helper lives at `src/scripts/apply-deltas.mjs`:
|
||
|
||
```javascript
|
||
// src/scripts/apply-deltas.mjs
|
||
// Shared logic for applying data.json deltas to template HTML
|
||
//
|
||
// Usage in generate.mjs:
|
||
// import { applyDeltas } from '../../src/scripts/apply-deltas.mjs';
|
||
// const outputHtml = applyDeltas(templateHtml, dataJson);
|
||
|
||
export function applyDeltas(html, data) {
|
||
// Parse HTML with JSDOM or cheerio
|
||
// For each page in data.pages:
|
||
// Find page container (nth .page element)
|
||
// For each section in page.sections:
|
||
// Find section container (nth .section within page)
|
||
// For each [elementId, delta] in section.elements:
|
||
// Find element by [data-edit="elementId"]
|
||
// Apply delta props to inline style
|
||
// Return modified HTML string
|
||
}
|
||
```
|
||
|
||
This helper is created once and reused by all new task types. Task-specific generate.mjs handles any custom logic (problem generation, SVG creation, etc.) and calls `applyDeltas()` for the standard positioning step.
|
||
|
||
---
|
||
|
||
## 8. Two Editors
|
||
|
||
### 8.1 Tune Editor (Stage 1 — Layout Calibration)
|
||
|
||
**Purpose:** Adjust the base layout of ONE section. Changes propagate to all sections.
|
||
|
||
**When to use:** Early stage, before per-section fine-tuning. Setting up the initial element positions, sizes, and grouping within a section.
|
||
|
||
**Flow:**
|
||
```
|
||
Tune Editor UI
|
||
→ User adjusts element positions in a single section
|
||
→ Save → POST /api/save-tune → writes tune-data.json
|
||
→ Claude reads tune-data.json
|
||
→ Claude reviews changes, decides what to apply
|
||
→ Claude edits template.html directly (applies to all sections)
|
||
→ Claude deletes tune-data.json
|
||
```
|
||
|
||
**tune-data.json** is NOT automatically applied. It is a proposal for Claude. Claude is the arbiter — it reads the data, validates it makes sense, and manually updates the template. No script chaining.
|
||
|
||
**tune-data.json format:**
|
||
```json
|
||
{
|
||
"section": {
|
||
"elements": {
|
||
"ship-group": { "left": 52.5, "top": 8.0 },
|
||
"badge": { "left": 16.0, "top": 9.5, "scale": 1.1 },
|
||
"space-asteroid": { "left": 4.0, "top": 14.0, "rotate": 10 }
|
||
}
|
||
},
|
||
"hierarchy": {
|
||
"ship-group": {
|
||
"editable": true,
|
||
"children": {
|
||
"badge": { "editable": true },
|
||
"inner-asteroid": { "editable": true }
|
||
}
|
||
},
|
||
"space-asteroid": { "editable": true },
|
||
"arrows-svg": { "editable": false },
|
||
"formula": { "editable": false }
|
||
}
|
||
}
|
||
```
|
||
|
||
**Note:** `hierarchy.*.editable` marks which elements will be available in the main editor. Tune editor sets this; Claude writes the corresponding `data-edit` attributes into the template.
|
||
|
||
**Sidebar panel:** The tune editor has a sidebar showing the element hierarchy tree, auto-built from DOM by scanning elements with `data-edit` attributes within the section. Each element shows:
|
||
- Name (from `data-edit` value)
|
||
- Nesting level (indentation)
|
||
- Checkbox: editable in main editor (yes/no)
|
||
- Current position summary (left, top in mm)
|
||
|
||
### 8.2 Main Editor (Stage 2 — Per-Section Fine-Tuning)
|
||
|
||
**Purpose:** Make per-section, per-page adjustments. Only elements marked as editable (via `data-edit` + `data-edit-props`) can be modified.
|
||
|
||
**Flow:**
|
||
```
|
||
Main Editor UI
|
||
→ User selects a section, clicks an editable element
|
||
→ Drag / keyboard adjustments
|
||
→ Save → POST /api/save-edits → writes data.json
|
||
→ Server runs generate.mjs → output.html + screenshots
|
||
```
|
||
|
||
**This is the standard editor described in the project CLAUDE.md.** It saves to data.json, and generate.mjs applies deltas to produce output.html.
|
||
|
||
### Editor Helpers
|
||
|
||
Common functionality lives in shared modules alongside `editor-core.js`:
|
||
|
||
**`src/editor/editor-elements.js`** — Element interaction for BOTH editors:
|
||
```javascript
|
||
// Register an editable element
|
||
EditorElements.register(el, {
|
||
props: ['dx', 'dy', 'scale', 'rotate'], // allowed operations
|
||
mmToPx: ratio, // from EditorCore
|
||
onSelect: (el, info) => {}, // callback
|
||
onChange: (el, prop, value) => {} // callback
|
||
});
|
||
|
||
// Built-in behaviors (automatic after register):
|
||
// • Click → thin rounded outline (selection highlight)
|
||
// • Drag → updates left/top, converts px movement to mm
|
||
// • Arrow keys → ±0.5mm nudge (dx/dy)
|
||
// • +/- keys → ±0.05 scale
|
||
// • [/] keys → ±5° rotate
|
||
// • Orange circle indicator on changed elements
|
||
// • Tracks original state for change detection + reset
|
||
|
||
// Serialize all changes (for save)
|
||
EditorElements.serialize({ changesOnly: true })
|
||
// → { elements: { "ship-group": { dx: 3.5, dy: -1 }, ... } }
|
||
|
||
// Reset element to original state
|
||
EditorElements.reset(el)
|
||
|
||
// Reset all elements on current page
|
||
EditorElements.resetAll()
|
||
```
|
||
|
||
**`src/editor/editor-tune.js`** — Tune-editor specific (sidebar, hierarchy):
|
||
```javascript
|
||
// Build hierarchy tree from DOM
|
||
EditorTune.buildTree(sectionEl)
|
||
// Scans [data-edit] elements, returns nested structure
|
||
|
||
// Render sidebar panel
|
||
EditorTune.renderSidebar(treeData, containerEl)
|
||
// Shows hierarchy with checkboxes, position info
|
||
|
||
// Serialize for tune-data.json
|
||
EditorTune.serialize()
|
||
// → { section: { elements: {...} }, hierarchy: {...} }
|
||
```
|
||
|
||
**These helpers are implemented once and imported by all new editors.** Task-specific editors only need to define which elements exist and any custom behavior.
|
||
|
||
---
|
||
|
||
## 9. Merge Operation
|
||
|
||
**What:** Bake current data.json deltas into the template, reset data.json, start fresh.
|
||
|
||
**Who:** Claude only. Never automated.
|
||
|
||
**When:** User wants to lock in editor adjustments and start a new round of editing (or make further AI-driven changes to the template).
|
||
|
||
**Steps:**
|
||
|
||
```
|
||
1. Verify output.html exists and is up-to-date
|
||
→ Run generate.mjs if needed
|
||
|
||
2. Copy output.html → template.html (overwrite)
|
||
→ This bakes all deltas into the base template
|
||
|
||
3. Delete data.json (or write empty {})
|
||
→ Fresh start for the editor
|
||
|
||
4. Verify: run generate.mjs again
|
||
→ output.html should be identical to template.html (no deltas to apply)
|
||
→ Compare file sizes or diff to confirm
|
||
|
||
5. Update .md if new patterns were established
|
||
|
||
6. Test: open main editor, verify elements show at their new base positions
|
||
→ No orange indicators (no deltas)
|
||
→ Drag/scale/rotate creates new deltas from the merged baseline
|
||
```
|
||
|
||
**After merge, deltas are absolute, not multiplicative:**
|
||
- Pre-merge: template has `scale(1.0)`, data.json has `scale: 1.5` → output: `scale(1.5)`
|
||
- Post-merge: template has `scale(1.5)`, data.json empty
|
||
- New edit: data.json `scale: 1.2` → output: `scale(1.2)` (replaces, not multiplies)
|
||
|
||
---
|
||
|
||
## 10. Template HTML Conventions
|
||
|
||
### Page container
|
||
```html
|
||
<div class="page w-[210mm] h-[297mm] relative overflow-hidden mx-auto bg-white"
|
||
style="break-after: page;">
|
||
```
|
||
|
||
### Section container
|
||
```html
|
||
<div class="section" style="position: relative; overflow: hidden;">
|
||
```
|
||
|
||
### Editable element
|
||
```html
|
||
<img data-edit="asteroid" data-edit-props="dx,dy,scale,rotate"
|
||
style="position: absolute; left: 25mm; top: 40mm; width: 14mm; height: 14mm;"
|
||
src="...">
|
||
```
|
||
|
||
### Editable group
|
||
```html
|
||
<div data-edit="ship-group" data-edit-props="dx,dy"
|
||
style="position: absolute; left: 50mm; top: 10mm;">
|
||
<img style="width: 40mm; height: 20mm;" src="ship.png">
|
||
<div data-edit="badge" data-edit-props="dx,dy,scale"
|
||
style="position: absolute; left: 15mm; top: 8mm;">
|
||
<span style="font-size: 0.6rem;">10</span>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### What NOT to do
|
||
|
||
```html
|
||
<!-- WRONG: percentage positioning -->
|
||
<div style="left: 40%; top: 50%;">
|
||
|
||
<!-- WRONG: calc with mixed units -->
|
||
<div style="left: calc(52% - 2mm);">
|
||
|
||
<!-- WRONG: negative margins for layout -->
|
||
<div style="margin-top: -5mm;">
|
||
|
||
<!-- WRONG: pixel dimensions for layout -->
|
||
<div style="width: 500px;">
|
||
|
||
<!-- WRONG: element bleeding out of section -->
|
||
<div style="left: -10mm;"> <!-- goes outside section boundary -->
|
||
|
||
<!-- WRONG: deep nesting (>2 levels) -->
|
||
<div data-edit="a">
|
||
<div data-edit="b">
|
||
<div data-edit="c"> <!-- too deep, flatten -->
|
||
```
|
||
|
||
### Inline styles vs Tailwind
|
||
|
||
For positions and dimensions of editable elements: **always inline `style=""`**. This is what editors read/write and generate.mjs modifies.
|
||
|
||
Tailwind classes are fine for:
|
||
- Page container (`w-[210mm] h-[297mm]`)
|
||
- Non-editable decorative elements
|
||
- Typography, colors, borders
|
||
- Flex/grid layout of the section grid
|
||
|
||
**Rule of thumb:** If generate.mjs or an editor might touch it → inline style. If it's static structure → Tailwind is fine.
|
||
|
||
---
|
||
|
||
## 11. Mandatory Editor Testing Protocol
|
||
|
||
**BLOCKING REQUIREMENT.** Before presenting any editor (tune or main) to the user, Claude MUST complete ALL of the following steps using Chrome DevTools MCP. If any step fails, fix and re-test. Never skip.
|
||
|
||
### 11.1 Tune Editor Testing
|
||
|
||
```
|
||
Step 1: LOAD
|
||
→ navigate_page to tune editor URL
|
||
→ take_screenshot → verify section renders, sidebar shows hierarchy tree
|
||
→ verify element count in sidebar matches data-edit elements in DOM
|
||
|
||
Step 2: SIDEBAR VALIDATION
|
||
→ verify hierarchy nesting matches DOM structure
|
||
→ verify editable checkboxes reflect data-edit-props presence
|
||
→ verify position values shown match inline styles in HTML
|
||
|
||
Step 3: SELECT
|
||
→ click on an editable element
|
||
→ take_screenshot → verify thin rounded selection outline appears
|
||
→ verify sidebar highlights the selected element
|
||
→ verify status bar shows element ID and current position
|
||
|
||
Step 4: MOVE
|
||
→ press Arrow key 4 times (should move 2mm total)
|
||
→ take_screenshot → verify element visually moved
|
||
→ verify sidebar position values updated
|
||
|
||
Step 5: SCALE
|
||
→ press + key 3 times (should scale by 0.15 total)
|
||
→ take_screenshot → verify element visually scaled
|
||
→ verify status shows new scale value
|
||
|
||
Step 6: ROTATE
|
||
→ press ] key 2 times (should rotate 10° total)
|
||
→ take_screenshot → verify element visually rotated
|
||
|
||
Step 7: SAVE
|
||
→ click Save button
|
||
→ wait for save completion (toast "Saved!")
|
||
→ take_screenshot → verify no page reload occurred
|
||
→ read tune-data.json → verify it contains the changes made in steps 4-6
|
||
→ verify element positions in tune-data.json match what was shown in sidebar
|
||
|
||
Step 8: VERIFY TUNE-DATA CONTENT
|
||
→ read tune-data.json with Read tool
|
||
→ verify hierarchy section is present with correct nesting
|
||
→ verify editable flags match checkbox states
|
||
→ verify position values are in mm (not px, not %)
|
||
```
|
||
|
||
### 11.2 Main Editor Testing
|
||
|
||
```
|
||
Step 1: LOAD
|
||
→ navigate_page to main editor URL (?file=docId)
|
||
→ take_screenshot → verify all pages load with sections visible
|
||
→ verify only data-edit elements have hover/click affordance
|
||
|
||
Step 2: SELECT
|
||
→ click on an editable element in section 0
|
||
→ take_screenshot → verify selection highlight (thin rounded outline)
|
||
→ verify status bar shows element ID, section index, page number
|
||
|
||
Step 3: DRAG
|
||
→ mouse drag the element ~5mm to the right
|
||
→ take_screenshot → verify element moved
|
||
→ verify orange changed indicator appears on the element
|
||
|
||
Step 4: KEYBOARD
|
||
→ select another element
|
||
→ press Arrow-Right 4× (2mm), press + 2× (scale +0.1), press ] 1× (rotate 5°)
|
||
→ take_screenshot → verify all three modifications visible
|
||
→ verify orange indicators on both modified elements
|
||
|
||
Step 5: SAVE
|
||
→ click Save button
|
||
→ wait for toast "Saved!" and no page reload
|
||
→ take_screenshot after save
|
||
|
||
Step 6: VERIFY DATA.JSON
|
||
→ read data.json with Read tool
|
||
→ verify it contains entries for ONLY the two modified elements
|
||
→ verify section indices are correct
|
||
→ verify dx/dy values are in mm (reasonable magnitude, not px)
|
||
→ verify scale and rotate values match what was applied
|
||
|
||
Step 7: VERIFY GENERATE
|
||
→ run generate.mjs via Bash
|
||
→ read output.html — verify data-edit elements have modified inline styles
|
||
→ compare: base position + delta = output position (arithmetic check)
|
||
|
||
Step 8: RELOAD PERSISTENCE
|
||
→ navigate_page to same editor URL (fresh load)
|
||
→ take_screenshot → verify saved state is restored
|
||
→ verify orange indicators still show on previously modified elements
|
||
→ verify element positions match the saved state, not the original template
|
||
|
||
Step 9: VISUAL COMPARISON
|
||
→ take_screenshot of editor view (specific section)
|
||
→ read output page screenshot from temp/ (generated by postGenerate)
|
||
→ compare: element positions in editor must match output screenshots
|
||
→ if mismatch → editor or generate.mjs has a bug, fix before proceeding
|
||
```
|
||
|
||
### 11.3 Post-Merge Testing
|
||
|
||
```
|
||
Step 1: Verify output.html ≈ template.html (after merge, no deltas)
|
||
Step 2: Open main editor → no orange indicators visible
|
||
Step 3: Make a small edit → save → verify new data.json has only the new change
|
||
Step 4: Run generate.mjs → verify only the new change appears in output
|
||
```
|
||
|
||
---
|
||
|
||
## 12. Checklist: Creating a New Task Type
|
||
|
||
Use this checklist when setting up a new task type that follows the worksheet-layout system.
|
||
|
||
```
|
||
□ Create tasks/{type}/ folder structure
|
||
□ Create tasks/{type}/CLAUDE.md with type-specific rules
|
||
□ Define section layout in template (grid/flex/stack)
|
||
□ All layout dimensions in mm, fonts in rem
|
||
□ All editable elements have data-edit + data-edit-props
|
||
□ No % positioning, no calc() with mixed units, no negative margins
|
||
□ Sections have position: relative + overflow: hidden
|
||
□ Groups max 2 levels deep (section → group → element)
|
||
□ Z-index 1-6 within sections, semantically ordered
|
||
□ Create generate.mjs using shared applyDeltas() helper
|
||
□ Create tune-editor.html with sidebar hierarchy panel
|
||
□ Create editor.html (main editor) for per-section adjustments
|
||
□ Run tune editor e2e test (Section 11.1) — all steps pass
|
||
□ Run main editor e2e test (Section 11.2) — all steps pass
|
||
□ Add card to tasks/{type}/index.html
|
||
□ Add card to tasks/index.html
|
||
□ Verify all navigation links in browser (Chrome DevTools)
|
||
```
|
||
|
||
---
|
||
|
||
## 13. File Reference
|
||
|
||
```
|
||
src/editor/
|
||
editor-core.js — Shared editor framework (existing)
|
||
editor-elements.js — Element interaction helpers (NEW — this system)
|
||
editor-tune.js — Tune editor sidebar + hierarchy (NEW — this system)
|
||
|
||
src/scripts/
|
||
apply-deltas.mjs — Shared delta application logic (NEW — this system)
|
||
generate-pdf.mjs — PDF generation (existing)
|
||
post-generate.mjs — Post-generate screenshots (existing)
|
||
```
|
||
|
||
Helper files (`editor-elements.js`, `editor-tune.js`, `apply-deltas.mjs`) are created when implementing the first task type that uses this system. Their API is defined in this skill; implementation follows from the first real use case.
|