runcastle 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>[Feature title] preview</title>
7
+ <style>
8
+ /* ---- runcastle theme: dark is the default, light is a full peer.
9
+ The app sets data-theme on <html>; opened as a file, the OS setting decides.
10
+ Token names and values are the app's own (apps/web/src/theme.css): keep them in step. ---- */
11
+ :root{
12
+ color-scheme:dark;
13
+ --color-canvas:#0a0c11;--color-surface:#10131a;--color-surface-raised:#161a24;--color-surface-hover:#1a1e29;--color-surface-selected:#212636;--color-surface-inset:#0c0e14;
14
+ --color-border:#222837;--color-border-subtle:rgb(200 215 255 / 0.06);--color-border-strong:#333a4d;
15
+ --color-text:#eceef4;--color-text-secondary:#a2a8b9;--color-text-tertiary:#8a91a5;--color-text-disabled:#4b5163;--color-icon:#8990a4;
16
+ --color-primary:#eceef4;--color-on-primary:#0a0c11;
17
+ --color-accent:#4f8ef7;--color-accent-text:#7aaafa;--color-accent-subtle:#16213a;
18
+ --color-success:#46b67a;--color-warning:#e2a53b;--color-danger:#f06363;--color-danger-subtle:#2a1515;
19
+ --color-phase-ideation:#ec7cb5;--color-phase-spec:#35c2b8;--color-phase-implementation:#f08a4b;
20
+ --color-phase-planning:var(--color-phase-spec);--color-phase-building:var(--color-phase-implementation);
21
+ --color-phase-review:var(--color-accent);--color-phase-shipped:var(--color-success);
22
+ --color-scrim:rgb(0 0 0 / 0.6);
23
+ --font-sans:"Geist Variable","Segoe UI Variable",system-ui,-apple-system,sans-serif;
24
+ --font-mono:"Geist Mono Variable","Cascadia Code",ui-monospace,Consolas,monospace;
25
+ --radius-sm:4px;--radius-md:6px;--radius-lg:10px;--ease-app:cubic-bezier(.22,.61,.24,1);
26
+ }
27
+ :root[data-theme="light"]{
28
+ color-scheme:light;
29
+ --color-canvas:#f4f6f9;--color-surface:#ffffff;--color-surface-raised:#ffffff;--color-surface-hover:#eef1f6;--color-surface-selected:#e4e8f1;--color-surface-inset:#f4f6f9;
30
+ --color-border:#dfe3ec;--color-border-subtle:rgb(22 30 50 / 0.07);--color-border-strong:#cdd2de;
31
+ --color-text:#161a24;--color-text-secondary:#4c5366;--color-text-tertiary:#5c6377;--color-text-disabled:#a4aab9;--color-icon:#6a7185;
32
+ --color-primary:#161a24;--color-on-primary:#ffffff;
33
+ --color-accent:#2f6fe4;--color-accent-text:#2463d6;--color-accent-subtle:#ecf2fe;
34
+ --color-success:#1e8e53;--color-warning:#a86a06;--color-danger:#c42828;--color-danger-subtle:#fdeeee;
35
+ --color-phase-ideation:#c13d7e;--color-phase-spec:#0b7f78;--color-phase-implementation:#b44f0c;
36
+ --color-scrim:rgb(22 26 36 / 0.28);
37
+ }
38
+ @media (prefers-color-scheme:light){
39
+ :root:not([data-theme]){
40
+ color-scheme:light;
41
+ --color-canvas:#f4f6f9;--color-surface:#ffffff;--color-surface-raised:#ffffff;--color-surface-hover:#eef1f6;--color-surface-selected:#e4e8f1;--color-surface-inset:#f4f6f9;
42
+ --color-border:#dfe3ec;--color-border-subtle:rgb(22 30 50 / 0.07);--color-border-strong:#cdd2de;
43
+ --color-text:#161a24;--color-text-secondary:#4c5366;--color-text-tertiary:#5c6377;--color-text-disabled:#a4aab9;--color-icon:#6a7185;
44
+ --color-primary:#161a24;--color-on-primary:#ffffff;
45
+ --color-accent:#2f6fe4;--color-accent-text:#2463d6;--color-accent-subtle:#ecf2fe;
46
+ --color-success:#1e8e53;--color-warning:#a86a06;--color-danger:#c42828;--color-danger-subtle:#fdeeee;
47
+ --color-phase-ideation:#c13d7e;--color-phase-spec:#0b7f78;--color-phase-implementation:#b44f0c;
48
+ --color-scrim:rgb(22 26 36 / 0.28);
49
+ }
50
+ }
51
+
52
+ /* ---- the fixed parts of every preview ---- */
53
+ *{box-sizing:border-box}
54
+ html{scroll-behavior:smooth}
55
+ body{margin:0;background:var(--color-surface);color:var(--color-text);font:13px/20px var(--font-sans);-webkit-font-smoothing:antialiased}
56
+ .page{max-width:1040px;margin:0 auto;padding:40px 32px 72px}
57
+ h1{margin:0;font-size:22px;line-height:28px;font-weight:600;letter-spacing:-.02em}
58
+ h2{margin:0 0 4px;font-size:16px;line-height:24px;font-weight:600;letter-spacing:-.01em}
59
+ h3{margin:0;font-size:13px;line-height:20px;font-weight:500}
60
+ p{margin:0}
61
+ .lede{margin-top:8px;max-width:620px;font-size:14px;line-height:22px;color:var(--color-text-secondary);text-wrap:pretty}
62
+ .meta{display:flex;flex-wrap:wrap;gap:4px 14px;margin-top:10px;font-size:12px;line-height:16px;color:var(--color-text-tertiary)}
63
+ .hint{color:var(--color-text-tertiary);font-size:12px;line-height:16px}
64
+ .mono{font-family:var(--font-mono);font-size:12px}
65
+ section{margin-top:44px}
66
+ .sub{margin-top:28px}
67
+ .sub > .hint{margin:2px 0 12px}
68
+ svg.i{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
69
+ button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
70
+ :focus-visible{outline:2px solid var(--color-accent);outline-offset:2px;border-radius:var(--radius-sm)}
71
+ .jump{display:flex;flex-wrap:wrap;gap:2px;margin-top:20px;padding-bottom:12px;border-bottom:1px solid var(--color-border-subtle)}
72
+ .jump a{padding:4px 10px;border-radius:var(--radius-md);color:var(--color-text-secondary);text-decoration:none;transition:background .12s var(--ease-app)}
73
+ .jump a:hover{background:var(--color-surface-hover);color:var(--color-text)}
74
+ .jump a b{font-weight:400;color:var(--color-text-tertiary);font-variant-numeric:tabular-nums}
75
+
76
+ /* a decision's number, wherever that decision is visible: <a class="pin" data-d="3">3</a> */
77
+ .pin{display:inline-grid;place-items:center;min-width:18px;height:18px;padding:0 4px;border-radius:var(--radius-sm);
78
+ background:var(--color-accent-subtle);color:var(--color-accent-text);font:500 11px/1 var(--font-mono);text-decoration:none;vertical-align:1px;cursor:pointer}
79
+ .pin:hover{background:var(--color-accent);color:#fff}
80
+
81
+ /* a segmented toggle, for switching a figure between states */
82
+ .seg{display:inline-flex;gap:2px;padding:2px;border-radius:var(--radius-md);background:var(--color-surface-inset)}
83
+ .seg button{height:24px;padding:0 10px;border-radius:var(--radius-sm);color:var(--color-text-secondary);transition:background .12s var(--ease-app)}
84
+ .seg button:hover{color:var(--color-text)}
85
+ .seg button[aria-pressed="true"]{background:var(--color-surface-selected);color:var(--color-text)}
86
+ .dot{display:inline-block;width:6px;height:6px;border-radius:999px;background:var(--color-text-disabled);flex:none}
87
+ .dot.ok{background:var(--color-success)}.dot.no{background:var(--color-danger)}.dot.live{background:var(--color-accent)}
88
+
89
+ /* the framed panel a mocked screen sits in; inside it, use the project's own styling */
90
+ .screen{border:1px solid var(--color-border);border-radius:var(--radius-lg);overflow:hidden;background:var(--color-surface);position:relative}
91
+
92
+ /* decided / left out / under the hood */
93
+ .dlist{border-top:1px solid var(--color-border-subtle);margin-top:12px}
94
+ .dlist details{border-bottom:1px solid var(--color-border-subtle)}
95
+ .dlist summary{display:flex;align-items:center;gap:12px;min-height:40px;padding:6px 8px;cursor:pointer;list-style:none;border-radius:var(--radius-md)}
96
+ .dlist summary::-webkit-details-marker{display:none}
97
+ .dlist summary:hover{background:var(--color-surface-hover)}
98
+ .dlist summary svg{margin-left:auto;color:var(--color-icon);transition:transform .18s var(--ease-app)}
99
+ .dlist details[open] summary svg{transform:rotate(90deg)}
100
+ .dlist details p{padding:0 8px 12px 42px;color:var(--color-text-secondary);max-width:700px}
101
+ .dlist details.flash summary{background:var(--color-accent-subtle)}
102
+ .tag{display:inline-flex;align-items:center;height:18px;padding:0 6px;border-radius:var(--radius-sm);background:var(--color-surface-inset);color:var(--color-text-tertiary);font-size:11px;flex:none}
103
+ .dlist summary .tag{margin-left:auto}.dlist summary .tag + svg{margin-left:0}
104
+ /* check first: built from the C list, absent when it is empty */
105
+ .cf{margin-top:12px}
106
+ .cf-item{border:1px solid var(--color-border);border-left:2px solid var(--color-warning);border-radius:var(--radius-md);padding:8px 10px;margin-top:8px;background:var(--color-surface-raised)}
107
+ .cf-item .t{display:flex;align-items:center;gap:8px;font-weight:500}
108
+ .cf-item dl{display:grid;grid-template-columns:86px 1fr;gap:2px 10px;margin:6px 0 0;color:var(--color-text-secondary)}
109
+ .cf-item dt{color:var(--color-text-tertiary);font-size:12px}
110
+ .cf-item dd{margin:0}
111
+ .out2{display:grid;grid-template-columns:1fr 1fr;gap:8px 32px;margin-top:12px}
112
+ .out2 h3{margin-bottom:6px;color:var(--color-text-tertiary);font-weight:400;font-size:12px}
113
+ .out2 li{display:flex;align-items:center;gap:10px;min-height:28px;color:var(--color-text-secondary)}
114
+ .out2 ul{margin:0;padding:0;list-style:none}
115
+ .hood > summary{display:flex;align-items:center;gap:8px;cursor:pointer;list-style:none}
116
+ .hood > summary::-webkit-details-marker{display:none}
117
+ .hood > summary svg{color:var(--color-icon);transition:transform .18s var(--ease-app)}
118
+ .hood[open] > summary svg{transform:rotate(90deg)}
119
+ .hood > summary h2{margin:0}
120
+ @media (max-width:760px){
121
+ .page{padding:28px 16px 56px}.out2{grid-template-columns:1fr}
122
+ }
123
+
124
+ /* ---- the show's own styles go below this line ---- */
125
+ </style>
126
+ </head>
127
+ <body>
128
+ <svg width="0" height="0" style="position:absolute" aria-hidden="true">
129
+ <symbol id="i-chev" viewBox="0 0 16 16"><path d="m6 3.500 4.500 4.500L6 12.500"/></symbol>
130
+ </svg>
131
+
132
+ <div class="page">
133
+
134
+ <!-- 1 · what this is -->
135
+ <header id="what">
136
+ <h1>[Feature title]</h1>
137
+ <p class="lede">[One or two sentences: what this feature is, in the human's words.]</p>
138
+ <div class="meta"><span>Lap 1</span><span><span data-dcount></span> decisions</span><span class="mono">docs/features/[slug]/preview.html</span></div>
139
+ <nav class="jump" aria-label="Parts of this preview">
140
+ <a href="#show">The show</a><a href="#decided">Decided <b data-dcount></b></a><a href="#left-out">Left out</a><a href="#hood">Under the hood</a>
141
+ </nav>
142
+ </header>
143
+
144
+ <!-- 2 · the show -->
145
+ <section id="show">
146
+ <h2>The show</h2>
147
+ <!-- THE SHOW IS FREE-FORM. Replace this comment with whatever explains how the
148
+ feature will work: screens, clickable prototypes, skeletons, flows, before
149
+ and after, scenarios. There is no template. Pin a decision's number where
150
+ it is visible: <a class="pin" data-d="3">3</a>. Diagrams and flows use the
151
+ runcastle tokens above; a mocked screen uses the project's own styling
152
+ inside a .screen panel, wrapped in a screen block.
153
+
154
+ Example screen block (copy it, rename it in kebab case, uncomment the
155
+ panel; delete it if the show has no screen):
156
+ -->
157
+ <!-- screen:example-name -->
158
+ <!--
159
+ <div class="screen">
160
+ ... the mocked screen, in the project's own styling ...
161
+ </div>
162
+ -->
163
+ <!-- /screen:example-name -->
164
+ </section>
165
+
166
+ <!-- 3 · what was decided (built from the list in the script below) -->
167
+ <section id="decided">
168
+ <h2>What was decided</h2>
169
+ <p class="hint">Open one for the reason.</p>
170
+ <div class="dlist" id="dlist"></div>
171
+ </section>
172
+
173
+ <!-- 4 · what is left out -->
174
+ <section id="left-out">
175
+ <h2>What is left out</h2>
176
+ <div class="out2">
177
+ <div><h3>Later laps</h3><ul>
178
+ <li><i class="dot"></i>[Deferred to a later lap]</li>
179
+ </ul></div>
180
+ <div><h3>Not this feature</h3><ul>
181
+ <li><i class="dot"></i>[Out of scope]</li>
182
+ </ul></div>
183
+ </div>
184
+ </section>
185
+
186
+ <!-- 5 · under the hood (collapsed by default) -->
187
+ <section id="hood-s">
188
+ <details class="hood" id="hood">
189
+ <summary><svg class="i"><use href="#i-chev"/></svg><h2>Under the hood</h2></summary>
190
+ <div class="sub">
191
+ <!-- [The technical shape, for a reader who wants it. Draw it where you can.] -->
192
+ </div>
193
+ </details>
194
+ </section>
195
+
196
+ </div>
197
+
198
+ <script>
199
+ (function () {
200
+ var $ = function (s, r) { return (r || document).querySelector(s) }
201
+ var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)) }
202
+
203
+ // every decision in decisions.md, by number and title, in order: [title, reason, mark?]
204
+ // mark is 'assumed' for a decision made without the human; a grilled feature has none
205
+ var D = [
206
+ ['[Decision 1 title]', '[Why, in a sentence]']
207
+ ]
208
+ // check first, a self-planned feature only (at most five): [decisionNumber, alternative, whyUnsure, tag?]
209
+ // leave it empty for a grilled feature, and no group is drawn
210
+ var C = []
211
+ var list = $('#dlist')
212
+ D.forEach(function (d, i) {
213
+ var el = document.createElement('details')
214
+ el.id = 'd' + (i + 1)
215
+ el.innerHTML = '<summary><span class="pin">' + (i + 1) + '</span><span></span><svg class="i"><use href="#i-chev"/></svg></summary><p></p>'
216
+ $('summary span:nth-child(2)', el).textContent = d[0]
217
+ $('p', el).textContent = d[1]
218
+ if (d[2]) {
219
+ var mark = document.createElement('span')
220
+ mark.className = 'tag'
221
+ mark.textContent = d[2]
222
+ $('summary svg', el).before(mark)
223
+ }
224
+ list.appendChild(el)
225
+ })
226
+ if (C.length) {
227
+ var cf = document.createElement('div')
228
+ cf.className = 'cf'
229
+ cf.innerHTML = '<h3>Check first</h3><p class="hint">At most five. The ones most likely to be wrong for you.</p>'
230
+ C.forEach(function (c) {
231
+ var item = document.createElement('div')
232
+ item.className = 'cf-item'
233
+ item.innerHTML = '<div class="t"><a class="pin" data-d="' + c[0] + '">' + c[0] + '</a><span></span></div>' +
234
+ '<dl><dt>Alternative</dt><dd></dd><dt>Why unsure</dt><dd></dd></dl>'
235
+ $('.t span', item).textContent = D[c[0] - 1][0]
236
+ if (c[3]) {
237
+ var tag = document.createElement('span')
238
+ tag.className = 'tag'
239
+ tag.textContent = c[3]
240
+ $('.t', item).appendChild(tag)
241
+ }
242
+ var dd = $$('dd', item)
243
+ dd[0].textContent = c[1]
244
+ dd[1].textContent = c[2]
245
+ cf.appendChild(item)
246
+ })
247
+ list.before(cf)
248
+ }
249
+ $$('[data-dcount]').forEach(function (el) { el.textContent = String(D.length) })
250
+
251
+ // a pin opens its decision; in-page links scroll by script, because hash
252
+ // navigation is unreliable inside the app's sandboxed frame
253
+ function go(el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }) }
254
+ document.addEventListener('click', function (e) {
255
+ var pin = e.target.closest('a.pin[data-d]')
256
+ if (pin) {
257
+ e.preventDefault()
258
+ var d = $('#d' + pin.dataset.d)
259
+ d.open = true; go(d); d.classList.add('flash')
260
+ setTimeout(function () { d.classList.remove('flash') }, 1400)
261
+ return
262
+ }
263
+ var a = e.target.closest('.jump a')
264
+ if (a) {
265
+ e.preventDefault()
266
+ var t = $(a.getAttribute('href'))
267
+ if (t.tagName === 'DETAILS') t.open = true
268
+ t.scrollIntoView({ behavior: 'smooth', block: 'start' })
269
+ }
270
+ })
271
+ })()
272
+ </script>
273
+ </body>
274
+ </html>
@@ -0,0 +1,98 @@
1
+ # The plan preview — the page the human looks at
2
+
3
+ Load this when the decisions are locked and the preview step (§3 of the ideate
4
+ skill) arrives, or when a decision edit at planning means the preview has to be
5
+ redrawn. Otherwise it costs this conversation nothing.
6
+
7
+ The preview is the one page the human reads the feature by. They confirm the
8
+ plan by looking at it, and when tired it is the only thing they read before
9
+ Burn. Draw it for that reader.
10
+
11
+ ## The file
12
+
13
+ - **Exactly one file, `preview.html`, at the root of the feature's docs dir**:
14
+ `docs/features/<slug>/preview.html`. Several screens or flows are sections of
15
+ that one page, never more files.
16
+ - **Start from `./preview-starter.html`.** Copy it to that path and fill it in.
17
+ It carries runcastle's theme tokens for both themes and the styles and markup
18
+ for the fixed parts; do not restyle them.
19
+ - **Throwaway explorations** may go under `docs/features/<slug>/prototypes/`.
20
+ They are never shown; the app shows only `preview.html`.
21
+
22
+ ## Five parts, in this order
23
+
24
+ 1. **What this is.** One or two sentences.
25
+ 2. **The show.** Whatever explains how the feature will work. **It is free, with
26
+ no template**: screens, clickable prototypes, skeletons, flows, before and
27
+ after, scenarios. Choose the form that explains *this* feature — work with no
28
+ screen gets flows, use cases, before and after, or a clickable skeleton.
29
+ 3. **What was decided.** Every decision in `decisions.md` by its number and
30
+ title, with the reason available on demand (the starter's `D` list builds
31
+ this). Where a decision is visible in the show, pin its number on that spot:
32
+ `<a class="pin" data-d="3">3</a>`.
33
+ **A self-planned feature** (§1a of the ideate skill) opens this part with a
34
+ **Check first** group of at most five decisions, the ones the planner picked.
35
+ Each entry shows the decision's number and title, its alternative, and why it
36
+ is uncertain (the starter's `C` list builds this). The remaining decisions
37
+ follow in the list, each showing that it was assumed (a third element on its
38
+ `D` entry). **A grilled feature has neither**: no group and no assumed marks.
39
+ 4. **What is left out.** Out of scope, and anything deferred to a later lap.
40
+ 5. **Under the hood.** The technical shape for a reader who wants it,
41
+ **collapsed by default**, drawn where it can be.
42
+
43
+ ## Visual first
44
+
45
+ **A short sentence beside a picture, never paragraphs, detail behind a click.**
46
+ If a part reads as prose, draw it instead: a flow, a before and after, a
47
+ toggled state, a list of dots.
48
+
49
+ ## The theme rule
50
+
51
+ - **The page and every diagram and flow are runcastle-themed**, through the
52
+ starter's tokens, in light or dark to match the app.
53
+ - **A mocked screen uses the styling of the project it belongs to**, inside the
54
+ framed `.screen` panel. A prototype of another product's UI can only be judged
55
+ in that product's look.
56
+
57
+ ## Screen blocks
58
+
59
+ Each screen in the show is a **screen block**: the lines between two markers,
60
+
61
+ ```html
62
+ <!-- screen:<name> -->
63
+ <div class="screen"> … </div>
64
+ <!-- /screen:<name> -->
65
+ ```
66
+
67
+ with `<name>` in kebab case and unique in the file. A ticket that builds that
68
+ screen names the block, and its burn agent reads only those lines — so put
69
+ everything the screen needs to be understood between the markers, and nothing
70
+ else. Delete the starter's `example-name` block if the show has no screen.
71
+
72
+ ## Inline only
73
+
74
+ **Styles, scripts and images are inline. The file makes no network request** —
75
+ no `<link>`, no `@import`, no external `src` or `url(`; images are `data:` URIs
76
+ or inline SVG. This is enforced, not a preference: the app renders the preview
77
+ in a sandboxed frame that runs scripts and refuses every fetch, external image
78
+ and font, form post and popup. Anything hosted elsewhere simply does not load.
79
+ In-page links scroll by script (the starter does this), because hash navigation
80
+ is unreliable inside that frame.
81
+
82
+ ## Sizing
83
+
84
+ **There is no minimum.** A small feature's show can be a single picture and the
85
+ other parts a line each. Reading target, as guidance and not a limit: **about a
86
+ minute for a small feature, a few minutes for a large one.**
87
+
88
+ ## Self-check, before you tell the human to look
89
+
90
+ - Every decision in `decisions.md` appears in **What was decided**, by its number
91
+ and its title, in order — none missing, none renumbered.
92
+ - For a self-planned feature, the **Check first** group holds at most five
93
+ decisions, each with its alternative and why it is uncertain, and every
94
+ decision carrying an `**Assumed.**` line in `decisions.md` shows as assumed.
95
+ For a grilled feature, `C` is empty and no decision is marked.
96
+ - Every pin points at the decision the spot actually shows.
97
+ - The five parts are in order; **Under the hood** is closed.
98
+ - The file is still fully inline.
@@ -16,7 +16,9 @@ That is the ordinary revisit, and it is what the moves below describe. The one e
16
16
  2. **Context.** Call `mcp__runcastle__get_feature_context` — the feature, its `phase` and `lap`, the canonical docs inlined in `docs[]` (brief, decisions, spec), an index of everything else in `moreDocs[]` (test notes and any other doc the feature's sessions left behind; fetch one with `mcp__runcastle__read_feature_doc({ relPath })`), and every ticket as a summary row with its id, `seq`, status and `goal` — its context, acceptance criteria and digest come from `mcp__runcastle__get_ticket({ seq })`. A canonical doc too large to inline is listed in `notInlined` instead; read it before acting. The injected system prompt carries the slug and paths; trust `get_feature_context` for the live state. When all you need is ticket ids to operate on, `mcp__runcastle__list_tickets({ status? })` is the cheap call — it returns the queue without the docs.
17
17
  3. **Docs.** Capture the change as decision prose:
18
18
  - Append to `docs/features/<slug>/decisions.md` under a dated `## Revisited <date>` heading — never rewrite old decisions, supersede them ("Supersedes: <old decision>").
19
+ - **Clear the assumed mark.** A decision the session made without the human carries an `**Assumed.**` line, and perhaps a `**Challenged:**` line. When the human confirms or changes such a decision, remove both lines from it in the same edit. Decisions the human did not touch keep theirs.
19
20
  - If `spec.md` exists and the change touches it, amend the affected sections in place.
21
+ - **Redraw the preview.** When the feature is at `planning` and `docs/features/<slug>/preview.html` exists, update `preview.html` in the same move as the decisions edit, before anything else, following `../ideate/references/preview.md`, check-first group included: a decision that lost its assumed mark leaves that group. The human reads the plan off the preview, so a preview that disagrees with `decisions.md` misleads them at Burn.
20
22
  4. **Ticket surgery.** Walk the ticket list against the new reality:
21
23
  - Stale but still needed → `mcp__runcastle__update_ticket({ id, ...changed fields })` (pending/failed only).
22
24
  - No longer needed → `mcp__runcastle__cancel_ticket({ id, reason })` (pending/failed only).
@@ -35,7 +37,7 @@ Your kickoff line reads `PLAN LAP <n> FROM REVIEW`. The human burned the last la
35
37
  - **What the docs say is not demonstrable yet.** Grep `spec.md` and this feature's tickets for `not demonstrable`, `do not demo` and `later laps`, and tell the human what you found **before** you offer a test drive. A human was once handed a lap-1 build whose own plan said "no UI, nobody should demo this before ticket X"; the drive taught nothing and cost them the time to find that out. Say which parts are drivable and which are not, then offer the drive.
36
38
  3. **Never re-emit a promoted note.** Notes the human already promoted from the review checklist are tickets in *this* lap and arrive as ids in your context. However well such a note reads as a ticket, if its id is in front of you the work is already carded — emitting it again gives the burner the same job twice.
37
39
  4. **Grill, briefly.** A small lap is a short grilling: a few questions, one at a time, each with your recommended answer attached. Prune and promote `## Later laps` entries with the human as part of that conversation — what the drive taught is usually what decides which deferred scope this lap picks up and which stays parked.
38
- 5. **Amend the docs.** Append this lap's learning to `docs/features/<slug>/decisions.md` under a `## Lap <n>` heading (the convention) — never rewrite old decisions, supersede them. Then amend `spec.md` in place: the sections this lap changes, plus the pruned `## Later laps`.
40
+ 5. **Amend the docs.** Append this lap's learning to `docs/features/<slug>/decisions.md` under a `## Lap <n>` heading (the convention) — never rewrite old decisions, supersede them. Then amend `spec.md` in place: the sections this lap changes, plus the pruned `## Later laps`. A lap planned from review does not redraw the preview: leave `preview.html` as it is.
39
41
  6. **Emit this lap's tickets, dispositioning the defects as you do.** `mcp__runcastle__emit_tickets({ tickets })` — only the work this lap will burn; the rest stays in `## Later laps`. They land `pending`, and the Burn click carries them onto lap `<n>`. Every ticket that answers a bug the human reported obeys **Stand on the failure** below. Reconcile stale pending tickets with `update_ticket` / `cancel_ticket`; `done` work that is now wrong gets a NEW ticket, as always.
40
42
 
41
43
  The defects an **earlier** lap's review left open arrive in your context as `openDefects` (`carriedDefects` are the ones a lap already parked — agenda, not obligation). Each open one needs exactly one of three answers. Nothing refuses over the ones you leave: the human reads them back as warnings in the Burn dialog, so finishing this triage is yours, not a gate's:
@@ -71,6 +71,12 @@ And width only counts if it lands: parallel tickets touching the same files coll
71
71
  A **wide refactor** — one mechanical change (rename a column, retype a shared symbol) whose blast radius breaks thousands of call sites at once — cannot land green as a vertical slice. Sequence it **expand → migrate → contract** instead. Expand: add the new form beside the old so nothing breaks (one ticket). Migrate: move call sites over in batches sized by blast radius (per package/dir), each batch a ticket blocked by the expand, CI green throughout because the old form still exists. Contract: delete the old form once no caller remains, blocked by every migrate batch. If even the batches cannot stay green alone, let them share an integration branch that all block a final integrate-and-verify ticket — green is promised only there.
72
72
  </wide-refactor-exception>
73
73
 
74
+ <preview-screen-block>
75
+ When the feature has a `preview.html` and a ticket **builds a screen that appears in it as a screen block**, point that ticket at **one named screen block**, never the file. Its **context** names the path (`docs/features/<slug>/preview.html`) and the block name, and tells the burn agent to find the two markers, `<!-- screen:<name> -->` and `<!-- /screen:<name> -->`, and **read only the lines between them**. Say plainly that the block shows the approved look and behaviour and **is not the implementation**: build it with the project's real components and never copy the mock's markup. The mock's styles live in the page head and are deliberately not read. **Never tell an agent to read the whole file** — the rest repeats docs it already has.
76
+
77
+ A ticket with no screen does not mention the preview.
78
+ </preview-screen-block>
79
+
74
80
  ## 2. Build each ticket to the schema
75
81
 
76
82
  Emit an array of `TicketInput`. Number them from 1 in the array in **dependency order (blockers first)**; `blockedBy` holds those numbers (they are the seq numbers the store assigns).