# Template Layout Clarity Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Make flat-paper templates such as `A4 三列小标签` preview as real sheet layouts while keeping the editor on a single-label canvas and making that behavior explicit to users.

**Architecture:** Keep the existing single-label editor model and extend the template system with a second preview mode for sheet-based templates. Reuse one layout-calculation helper for template preview and modal rendering so flat-paper templates show trustworthy multi-cell A4 previews without changing print/export architecture.

**Tech Stack:** Static HTML/JS, Fabric.js, Node-based regression tests, Git for versioning.

---

### Task 1: Lock Down Flat-Paper Preview Semantics With Tests

**Files:**
- Modify: `tests/label-templates.test.js`
- Test: `tests/label-templates.test.js`

- [ ] **Step 1: Write the failing test**

Add assertions for the `blank-a4-63x38` template so the test expects:

```js
{
  const a4Template = getLabelTemplateById("blank-a4-63x38");
  const preview = buildTemplatePreviewItems("blank-a4-63x38");

  assert.strictEqual(a4Template.labelType, "flat");
  assert(preview.length > 3, "flat-paper preview should expose multiple sheet cells");
  assert(preview.some((item) => item.type === "sheet"), "flat-paper preview should include a sheet frame");
  assert(preview.filter((item) => item.type === "cell").length >= 3, "A4 three-column layout should expose multiple cells");
  assert(preview.some((item) => item.type === "cell" && item.emphasis === "primary"), "flat-paper preview should highlight the edited cell");
}
```

- [ ] **Step 2: Run test to verify it fails**

Run: `node tests/label-templates.test.js`
Expected: FAIL because `buildTemplatePreviewItems("blank-a4-63x38")` currently returns `[]` for blank flat-paper templates.

- [ ] **Step 3: Add one small rendering contract test for fallback behavior**

Add another assertion block to ensure business templates still use object previews:

```js
{
  const productItems = buildTemplatePreviewItems("product-price-tag");
  assert(productItems.some((item) => item.type === "text"), "business preview should still include text objects");
  assert(!productItems.some((item) => item.type === "sheet"), "business preview should not switch to sheet mode");
}
```

- [ ] **Step 4: Run test to verify the suite still fails for the right reason**

Run: `node tests/label-templates.test.js`
Expected: FAIL only on the new flat-paper preview assertions.

- [ ] **Step 5: Commit**

```bash
git add tests/label-templates.test.js
git commit -m "test: define flat-paper template preview semantics"
```

### Task 2: Build Sheet Preview Metadata In Template Helpers

**Files:**
- Modify: `static/label-templates.js`
- Test: `tests/label-templates.test.js`

- [ ] **Step 1: Add a reusable sheet-layout calculator**

In `static/label-templates.js`, add a helper that computes normalized sheet cells from template paper settings:

```js
function buildSheetPreviewItems(template) {
  const paper = template.paper || {};
  const paperWidth = Math.max(paper.width || template.widthMm || 0, 0.001);
  const paperHeight = Math.max(paper.height || template.heightMm || 0, 0.001);
  const marginTop = Math.max(paper.marginTop || 0, 0);
  const marginBottom = Math.max(paper.marginBottom || 0, 0);
  const marginLeft = Math.max(paper.marginLeft || 0, 0);
  const marginRight = Math.max(paper.marginRight || 0, 0);
  const gapX = Math.max(paper.gapX || 0, 0);
  const gapY = Math.max(paper.gapY || 0, 0);
  const labelWidth = Math.max(template.widthMm || 0, 0.001);
  const labelHeight = Math.max(template.heightMm || 0, 0.001);

  const cols = Math.max(1, Math.floor((paperWidth - marginLeft - marginRight + gapX) / (labelWidth + gapX)));
  const rows = Math.max(1, Math.floor((paperHeight - marginTop - marginBottom + gapY) / (labelHeight + gapY)));
  const items = [{ type: "sheet", left: 0, top: 0, width: 100, height: 100, cols, rows }];

  for (let row = 0; row < rows; row++) {
    for (let col = 0; col < cols; col++) {
      items.push({
        type: "cell",
        left: clampPercent(((marginLeft + col * (labelWidth + gapX)) / paperWidth) * 100),
        top: clampPercent(((marginTop + row * (labelHeight + gapY)) / paperHeight) * 100),
        width: clampPercent((labelWidth / paperWidth) * 100),
        height: clampPercent((labelHeight / paperHeight) * 100),
        emphasis: row === 0 && col === 0 ? "primary" : "secondary",
        index: row * cols + col + 1,
      });
    }
  }

  return items;
}
```

- [ ] **Step 2: Route flat-paper blank templates to sheet preview mode**

Update `buildTemplatePreviewItems()` so it branches like this:

```js
if (template.kind === "blank" && template.labelType === "flat" && template.paper && !template.paper.matchLabel) {
  return buildSheetPreviewItems(template);
}
```

Keep existing object preview logic unchanged for content templates.

- [ ] **Step 3: Expose optional preview mode metadata**

Add a small helper or inline property assignment so flat-paper templates can advertise their preview mode:

```js
function getTemplatePreviewMode(template) {
  if (template.kind === "blank" && template.labelType === "flat" && template.paper && !template.paper.matchLabel) {
    return "sheet";
  }
  return "content";
}
```

Expose it in the module API only if needed by `index.html`.

- [ ] **Step 4: Run tests to verify green**

Run:
- `node tests/label-templates.test.js`
- `node --check static/label-templates.js`

Expected: both PASS.

- [ ] **Step 5: Commit**

```bash
git add static/label-templates.js tests/label-templates.test.js
git commit -m "feat: add sheet preview data for flat-paper templates"
```

### Task 3: Render Sheet-Based Template Previews In The New-Label Modal

**Files:**
- Modify: `index.html`
- Test: `tests/label-templates.test.js`

- [ ] **Step 1: Update template card copy for flat-paper templates**

In `editor.templates.buildTemplateCardHtml()`, append flat-paper layout details when `template.labelType === "flat"`:

```js
const detailLines = [];
detailLines.push(`${template.widthMm} × ${template.heightMm} mm`);
if (template.labelType === "flat" && template.paper && !template.paper.matchLabel) {
  detailLines.push(`${(template.paper.preset || "").toUpperCase()} / 预设版式`);
}
```

Render the details as a muted extra line below the description.

- [ ] **Step 2: Teach preview rendering about `sheet` and `cell` items**

Extend `buildPreviewItemHtml()` with branches like:

```js
if (item.type === "sheet") {
  return `<div class="absolute inset-0 rounded-xl border border-slate-300/80 bg-white/80"></div>`;
}
if (item.type === "cell") {
  const classes = item.emphasis === "primary"
    ? "border-blue-400 bg-blue-100/70 shadow-sm"
    : "border-slate-300 bg-slate-100/60";
  return `<div class="absolute rounded-[6px] border ${classes}" style="${base}">
    <span class="absolute inset-0 flex items-center justify-center text-[9px] font-semibold text-slate-500/70">${item.index}</span>
  </div>`;
}
```
Keep existing `text`, `barcode`, `qrcode`, `rect`, `line` branches unchanged.

- [ ] **Step 3: Make the selected preview wrapper fit sheet previews cleanly**

Update `buildSelectedTemplatePreviewMarkup()` or the surrounding preview container so sheet previews use the full preview box with a visible paper frame and content previews still overlay per-cell objects normally.

The resulting markup should still look like:

```js
return `<div class="absolute inset-0 pointer-events-none">${items.map((item) => this.buildPreviewItemHtml(item)).join("")}</div>`;
```

but the new branches must no longer depend on a single-label-only assumption.

- [ ] **Step 4: Run targeted verification**

Run:
- `node tests/label-templates.test.js`
- `node -e "new Function(require('fs').readFileSync('index.html','utf8').match(/<script>([\\s\\S]*)<\\/script>/)[1]); console.log('inline scripts parsed: 1')"`

Expected:
- tests PASS
- inline script parser prints `inline scripts parsed: 1`

- [ ] **Step 5: Commit**

```bash
git add index.html tests/label-templates.test.js static/label-templates.js
git commit -m "feat: render sheet previews for flat-paper templates"
```

### Task 4: Explain Single-Label Editing After Creating A Flat-Paper Template

**Files:**
- Modify: `index.html`
- Test: `tests/label-templates.test.js`

- [ ] **Step 1: Mark tabs created from flat-paper templates**

In `createNewFromModal()`, persist minimal metadata when the selected template is a flat-paper layout:

```js
templateHint:
  editor.templates.getSelectedNewLabelTemplate()?.labelType === "flat" &&
  !editor.templates.getSelectedNewLabelTemplate()?.paper?.matchLabel
    ? {
        type: "flat-sheet-layout",
        message: "当前编辑的是单个标签；打印/导出时将按 A4 三列版式排版。",
      }
    : null,
```

- [ ] **Step 2: Show a one-time hint after the tab loads**

In the empty-tab load branch of `switchTab()`, after `applyPendingTemplateToCanvas()` resolves or after the tab becomes active, show:

```js
if (targetTab.templateHint && !targetTab.templateHintShown) {
  toast.message(targetTab.templateHint.message);
  targetTab.templateHintShown = true;
}
```

Use the same message path for tabs with or without template content.

- [ ] **Step 3: Add a focused regression test for flat-paper template metadata**

Extend `tests/label-templates.test.js` with a lightweight expectation that the flat template stays `labelType === "flat"` and `paper.matchLabel === false`, so the UI branch has stable source data.

- [ ] **Step 4: Run full local checks**

Run:
- `node tests/label-templates.test.js`
- `node tests/object-placement.test.js`
- `node tests/desktop-runtime.test.js`
- `node --check static/label-templates.js`
- `node --check static/object-placement.js`
- `node --check static/desktop-runtime.js`
- `node --check static/desktop-bridge.js`

Expected: all PASS.

- [ ] **Step 5: Commit**

```bash
git add index.html tests/label-templates.test.js static/label-templates.js
git commit -m "feat: clarify flat-paper template editing behavior"
```

## Self-Review

- Spec coverage: template semantics, sheet preview rendering, explanatory hint, and regression tests are all covered by Tasks 1-4.
- Placeholder scan: no `TODO`, `TBD`, or deferred “handle later” steps remain.
- Type consistency: `sheet` and `cell` are the only new preview item types; the plan keeps existing object preview items unchanged.

## Execution Handoff

Plan complete and saved to `docs/superpowers/plans/2026-04-20-template-layout-clarity.md`.

Two execution options:

1. Subagent-Driven (recommended) - dispatch a fresh subagent per task, review between tasks
2. Inline Execution - execute tasks in this session using executing-plans

The user explicitly asked to start immediately, so continue with **Inline Execution** in this session.
