feat: promotion
This commit is contained in:
@@ -0,0 +1,708 @@
|
||||
---
|
||||
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.
|
||||
Reference in New Issue
Block a user