diff --git a/design-html/SKILL.md b/design-html/SKILL.md
index dba83d9f8..0ad7d67b6 100644
--- a/design-html/SKILL.md
+++ b/design-html/SKILL.md
@@ -402,6 +402,20 @@ approximations. Computed layout via Pretext. Text reflows on resize, heights adj
to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow
around obstacles.
+---
+
+## Section index — Read each section when its situation applies
+
+This skill is a decision-tree skeleton. The steps below point to on-demand
+sections. Read a section in full before doing its step; do not work from memory.
+
+| When | Read this section |
+|------|-------------------|
+| analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice | `sections/doctrine.md` |
+| writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code | `sections/pretext-patterns.md` |
+
+---
+
## DESIGN SETUP (run this check BEFORE any design mockup command)
```bash
@@ -445,90 +459,8 @@ MUST be saved to `~/.gstack/projects/$SLUG/designs/`, NEVER to `.context/`,
`docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER
data, not project files. They persist across branches, conversations, and workspaces.
-## UX Principles: How Users Actually Behave
-
-These principles govern how real humans interact with interfaces. They are observed
-behavior, not preferences. Apply them before, during, and after every design decision.
-
-### The Three Laws of Usability
-
-1. **Don't make me think.** Every page should be self-evident. If a user stops
- to think "What do I click?" or "What does this mean?", the design has failed.
- Self-evident > self-explanatory > requires explanation.
-
-2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks
- beat one click that requires thought. Each step should feel like an obvious
- choice (animal, vegetable, or mineral), not a puzzle.
-
-3. **Omit, then omit again.** Get rid of half the words on each page, then get
- rid of half of what's left. Happy talk (self-congratulatory text) must die.
- Instructions must die. If they need reading, the design has failed.
-
-### How Users Actually Behave
-
-- **Users scan, they don't read.** Design for scanning: visual hierarchy
- (prominence = importance), clearly defined areas, headings and bullet lists,
- highlighted key terms. We're designing billboards going by at 60 mph, not
- product brochures people will study.
-- **Users satisfice.** They pick the first reasonable option, not the best.
- Make the right choice the most visible choice.
-- **Users muddle through.** They don't figure out how things work. They wing
- it. If they accomplish their goal by accident, they won't seek the "right" way.
- Once they find something that works, no matter how badly, they stick to it.
-- **Users don't read instructions.** They dive in. Guidance must be brief,
- timely, and unavoidable, or it won't be seen.
-
-### Billboard Design for Interfaces
-
-- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass.
- Don't innovate on navigation to be clever. Innovate when you KNOW you have a
- better idea, otherwise use conventions. Even across languages and cultures,
- web conventions let people identify the logo, nav, search, and main content.
-- **Visual hierarchy is everything.** Related things are visually grouped. Nested
- things are visually contained. More important = more prominent. If everything
- shouts, nothing is heard. Start with the assumption everything is visual noise,
- guilty until proven innocent.
-- **Make clickable things obviously clickable.** No relying on hover states for
- discoverability, especially on mobile where hover doesn't exist. Shape, location,
- and formatting (color, underlining) must signal clickability without interaction.
-- **Eliminate noise.** Three sources: too many things shouting for attention
- (shouting), things not organized logically (disorganization), and too much stuff
- (clutter). Fix noise by removal, not addition.
-- **Clarity trumps consistency.** If making something significantly clearer
- requires making it slightly inconsistent, choose clarity every time.
-
-### Navigation as Wayfinding
-
-Users on the web have no sense of scale, direction, or location. Navigation
-must always answer: What site is this? What page am I on? What are the major
-sections? What are my options at this level? Where am I? How can I search?
-
-Persistent navigation on every page. Breadcrumbs for deep hierarchies.
-Current section visually indicated. The "trunk test": cover everything except
-the navigation. You should still know what site this is, what page you're on,
-and what the major sections are. If not, the navigation has failed.
-
-### The Goodwill Reservoir
-
-Users start with a reservoir of goodwill. Every friction point depletes it.
-
-**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing
-users for not doing things your way (formatting requirements on phone numbers).
-Asking for unnecessary information. Putting sizzle in their way (splash screens,
-forced tours, interstitials). Unprofessional or sloppy appearance.
-
-**Replenish:** Know what users want to do and make it obvious. Tell them what they
-want to know upfront. Save them steps wherever possible. Make it easy to recover
-from errors. When in doubt, apologize.
-
-### Mobile: Same Rules, Higher Stakes
-
-All the above applies on mobile, just more so. Real estate is scarce, but never
-sacrifice usability for space savings. Affordances must be VISIBLE: no cursor
-means no hover-to-discover. Touch targets must be big enough (44px minimum).
-Flat design can strip away useful visual information that signals interactivity.
-Prioritize ruthlessly: things needed in a hurry go close at hand, everything
-else a few taps away with an obvious path to get there.
+> **STOP.** Before analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice, Read `~/.claude/skills/gstack/design-html/sections/doctrine.md` and execute it
+> in full. Do not work from memory — that section is the source of truth for this step.
## SETUP (run this check BEFORE any browse command)
@@ -741,6 +673,9 @@ If no framework detected: default to vanilla HTML, no question needed.
## Step 3: Generate Pretext-Native HTML
+> **STOP.** Before writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code, Read `~/.claude/skills/gstack/design-html/sections/pretext-patterns.md` and execute it
+> in full. Do not work from memory — that section is the source of truth for this step.
+
### Pretext Source Embedding
For **vanilla HTML output**, check for the vendored Pretext bundle:
@@ -802,160 +737,6 @@ For framework output, save to:
- Generic testimonial sections
- Cookie-cutter hero sections with left-text right-image
-### Pretext Wiring Patterns
-
-Use these patterns based on the tier selected in Step 2. These are the correct
-Pretext API usage patterns. Follow them exactly.
-
-**Pattern 1: Basic height computation (Simple layout, Card/grid)**
-```js
-import { prepare, layout } from './pretext-inline.js'
-// Or if inlined: const { prepare, layout } = window.Pretext
-
-// 1. PREPARE — one-time, after fonts load
-await document.fonts.ready
-const elements = document.querySelectorAll('[data-pretext]')
-const prepared = new Map()
-
-for (const el of elements) {
- const text = el.textContent
- const font = getComputedStyle(el).font
- prepared.set(el, prepare(text, font))
-}
-
-// 2. LAYOUT — cheap, call on every resize
-function relayout() {
- for (const [el, handle] of prepared) {
- const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight))
- el.style.height = `${height}px`
- }
-}
-
-// 3. RESIZE-AWARE
-new ResizeObserver(() => relayout()).observe(document.body)
-relayout()
-
-// 4. CONTENT-EDITABLE — re-prepare when text changes
-for (const el of elements) {
- if (el.contentEditable === 'true') {
- new MutationObserver(() => {
- const font = getComputedStyle(el).font
- prepared.set(el, prepare(el.textContent, font))
- relayout()
- }).observe(el, { characterData: true, subtree: true, childList: true })
- }
-}
-```
-
-**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)**
-```js
-import { prepareWithSegments, walkLineRanges } from './pretext-inline.js'
-
-// Find the tightest width that produces the same line count
-function shrinkwrap(text, font, maxWidth, lineHeight) {
- const segs = prepareWithSegments(text, font)
- let bestWidth = maxWidth
- walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => {
- // walkLineRanges calls back with progressively narrower widths
- // The first call gives us the line count at maxWidth
- // We want the narrowest width that still produces this line count
- })
- // Binary search for tightest width with same line count
- const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight)
- let lo = 0, hi = maxWidth
- while (hi - lo > 1) {
- const mid = (lo + hi) / 2
- const { lineCount } = layout(prepare(text, font), mid, lineHeight)
- if (lineCount === targetLines) hi = mid
- else lo = mid
- }
- return hi
-}
-```
-
-**Pattern 3: Text around obstacles (Editorial layout)**
-```js
-import { prepareWithSegments, layoutNextLine } from './pretext-inline.js'
-
-function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) {
- const segs = prepareWithSegments(text, font)
- let state = null
- let y = 0
- const lines = []
-
- while (true) {
- // Calculate available width at current y position, accounting for obstacles
- let availWidth = containerWidth
- for (const obs of obstacles) {
- if (y >= obs.top && y < obs.top + obs.height) {
- availWidth -= obs.width
- }
- }
-
- const result = layoutNextLine(segs, state, availWidth, lineHeight)
- if (!result) break
-
- lines.push({ text: result.text, width: result.width, x: 0, y })
- state = result.state
- y += lineHeight
- }
-
- return { lines, totalHeight: y }
-}
-```
-
-**Pattern 4: Full line-by-line rendering (Complex editorial)**
-```js
-import { prepareWithSegments, layoutWithLines } from './pretext-inline.js'
-
-const segs = prepareWithSegments(text, font)
-const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight)
-
-// lines = [{ text, width, x, y }, ...]
-// Use for Canvas/SVG rendering or custom DOM positioning
-for (const line of lines) {
- const span = document.createElement('span')
- span.textContent = line.text
- span.style.position = 'absolute'
- span.style.left = `${line.x}px`
- span.style.top = `${line.y}px`
- container.appendChild(span)
-}
-```
-
-### Pretext API Reference
-
-```
-PRETEXT API CHEATSHEET:
-
-prepare(text, font) → handle
- One-time text measurement. Call after document.fonts.ready.
- Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'.
-
-layout(prepared, maxWidth, lineHeight) → { height, lineCount }
- Fast layout computation. Call on every resize. Sub-millisecond.
-
-prepareWithSegments(text, font) → handle
- Like prepare() but enables line-level APIs below.
-
-layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height }
- Full line-by-line breakdown. For Canvas/SVG rendering.
-
-walkLineRanges(segs, maxWidth, onLine) → void
- Calls onLine(lineCount, startIdx, endIdx) for each possible layout.
- Find minimum width for N lines. For tight-fit containers.
-
-layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null
- Iterator. Different maxWidth per line = text around obstacles.
- Pass null as initial state. Returns null when text is exhausted.
-
-clearCache() → void
- Clears internal measurement caches. Use when cycling many fonts.
-
-setLocale(locale?) → void
- Retargets word segmenter for future prepare() calls.
-```
-
---
## Step 3.5: Live Reload Server
diff --git a/design-html/SKILL.md.tmpl b/design-html/SKILL.md.tmpl
index 3cdec9a14..816381737 100644
--- a/design-html/SKILL.md.tmpl
+++ b/design-html/SKILL.md.tmpl
@@ -39,9 +39,15 @@ approximations. Computed layout via Pretext. Text reflows on resize, heights adj
to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow
around obstacles.
+---
+
+{{SECTION_INDEX:design-html}}
+
+---
+
{{DESIGN_SETUP}}
-{{UX_PRINCIPLES}}
+{{SECTION:doctrine}}
{{BROWSE_SETUP}}
@@ -220,6 +226,8 @@ If no framework detected: default to vanilla HTML, no question needed.
## Step 3: Generate Pretext-Native HTML
+{{SECTION:pretext-patterns}}
+
### Pretext Source Embedding
For **vanilla HTML output**, check for the vendored Pretext bundle:
@@ -281,160 +289,6 @@ For framework output, save to:
- Generic testimonial sections
- Cookie-cutter hero sections with left-text right-image
-### Pretext Wiring Patterns
-
-Use these patterns based on the tier selected in Step 2. These are the correct
-Pretext API usage patterns. Follow them exactly.
-
-**Pattern 1: Basic height computation (Simple layout, Card/grid)**
-```js
-import { prepare, layout } from './pretext-inline.js'
-// Or if inlined: const { prepare, layout } = window.Pretext
-
-// 1. PREPARE — one-time, after fonts load
-await document.fonts.ready
-const elements = document.querySelectorAll('[data-pretext]')
-const prepared = new Map()
-
-for (const el of elements) {
- const text = el.textContent
- const font = getComputedStyle(el).font
- prepared.set(el, prepare(text, font))
-}
-
-// 2. LAYOUT — cheap, call on every resize
-function relayout() {
- for (const [el, handle] of prepared) {
- const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight))
- el.style.height = `${height}px`
- }
-}
-
-// 3. RESIZE-AWARE
-new ResizeObserver(() => relayout()).observe(document.body)
-relayout()
-
-// 4. CONTENT-EDITABLE — re-prepare when text changes
-for (const el of elements) {
- if (el.contentEditable === 'true') {
- new MutationObserver(() => {
- const font = getComputedStyle(el).font
- prepared.set(el, prepare(el.textContent, font))
- relayout()
- }).observe(el, { characterData: true, subtree: true, childList: true })
- }
-}
-```
-
-**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)**
-```js
-import { prepareWithSegments, walkLineRanges } from './pretext-inline.js'
-
-// Find the tightest width that produces the same line count
-function shrinkwrap(text, font, maxWidth, lineHeight) {
- const segs = prepareWithSegments(text, font)
- let bestWidth = maxWidth
- walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => {
- // walkLineRanges calls back with progressively narrower widths
- // The first call gives us the line count at maxWidth
- // We want the narrowest width that still produces this line count
- })
- // Binary search for tightest width with same line count
- const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight)
- let lo = 0, hi = maxWidth
- while (hi - lo > 1) {
- const mid = (lo + hi) / 2
- const { lineCount } = layout(prepare(text, font), mid, lineHeight)
- if (lineCount === targetLines) hi = mid
- else lo = mid
- }
- return hi
-}
-```
-
-**Pattern 3: Text around obstacles (Editorial layout)**
-```js
-import { prepareWithSegments, layoutNextLine } from './pretext-inline.js'
-
-function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) {
- const segs = prepareWithSegments(text, font)
- let state = null
- let y = 0
- const lines = []
-
- while (true) {
- // Calculate available width at current y position, accounting for obstacles
- let availWidth = containerWidth
- for (const obs of obstacles) {
- if (y >= obs.top && y < obs.top + obs.height) {
- availWidth -= obs.width
- }
- }
-
- const result = layoutNextLine(segs, state, availWidth, lineHeight)
- if (!result) break
-
- lines.push({ text: result.text, width: result.width, x: 0, y })
- state = result.state
- y += lineHeight
- }
-
- return { lines, totalHeight: y }
-}
-```
-
-**Pattern 4: Full line-by-line rendering (Complex editorial)**
-```js
-import { prepareWithSegments, layoutWithLines } from './pretext-inline.js'
-
-const segs = prepareWithSegments(text, font)
-const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight)
-
-// lines = [{ text, width, x, y }, ...]
-// Use for Canvas/SVG rendering or custom DOM positioning
-for (const line of lines) {
- const span = document.createElement('span')
- span.textContent = line.text
- span.style.position = 'absolute'
- span.style.left = `${line.x}px`
- span.style.top = `${line.y}px`
- container.appendChild(span)
-}
-```
-
-### Pretext API Reference
-
-```
-PRETEXT API CHEATSHEET:
-
-prepare(text, font) → handle
- One-time text measurement. Call after document.fonts.ready.
- Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'.
-
-layout(prepared, maxWidth, lineHeight) → { height, lineCount }
- Fast layout computation. Call on every resize. Sub-millisecond.
-
-prepareWithSegments(text, font) → handle
- Like prepare() but enables line-level APIs below.
-
-layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height }
- Full line-by-line breakdown. For Canvas/SVG rendering.
-
-walkLineRanges(segs, maxWidth, onLine) → void
- Calls onLine(lineCount, startIdx, endIdx) for each possible layout.
- Find minimum width for N lines. For tight-fit containers.
-
-layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null
- Iterator. Different maxWidth per line = text around obstacles.
- Pass null as initial state. Returns null when text is exhausted.
-
-clearCache() → void
- Clears internal measurement caches. Use when cycling many fonts.
-
-setLocale(locale?) → void
- Retargets word segmenter for future prepare() calls.
-```
-
---
## Step 3.5: Live Reload Server
diff --git a/design-html/sections/doctrine.md b/design-html/sections/doctrine.md
new file mode 100644
index 000000000..7ec4bdca9
--- /dev/null
+++ b/design-html/sections/doctrine.md
@@ -0,0 +1,86 @@
+
+
+## UX Principles: How Users Actually Behave
+
+These principles govern how real humans interact with interfaces. They are observed
+behavior, not preferences. Apply them before, during, and after every design decision.
+
+### The Three Laws of Usability
+
+1. **Don't make me think.** Every page should be self-evident. If a user stops
+ to think "What do I click?" or "What does this mean?", the design has failed.
+ Self-evident > self-explanatory > requires explanation.
+
+2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks
+ beat one click that requires thought. Each step should feel like an obvious
+ choice (animal, vegetable, or mineral), not a puzzle.
+
+3. **Omit, then omit again.** Get rid of half the words on each page, then get
+ rid of half of what's left. Happy talk (self-congratulatory text) must die.
+ Instructions must die. If they need reading, the design has failed.
+
+### How Users Actually Behave
+
+- **Users scan, they don't read.** Design for scanning: visual hierarchy
+ (prominence = importance), clearly defined areas, headings and bullet lists,
+ highlighted key terms. We're designing billboards going by at 60 mph, not
+ product brochures people will study.
+- **Users satisfice.** They pick the first reasonable option, not the best.
+ Make the right choice the most visible choice.
+- **Users muddle through.** They don't figure out how things work. They wing
+ it. If they accomplish their goal by accident, they won't seek the "right" way.
+ Once they find something that works, no matter how badly, they stick to it.
+- **Users don't read instructions.** They dive in. Guidance must be brief,
+ timely, and unavoidable, or it won't be seen.
+
+### Billboard Design for Interfaces
+
+- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass.
+ Don't innovate on navigation to be clever. Innovate when you KNOW you have a
+ better idea, otherwise use conventions. Even across languages and cultures,
+ web conventions let people identify the logo, nav, search, and main content.
+- **Visual hierarchy is everything.** Related things are visually grouped. Nested
+ things are visually contained. More important = more prominent. If everything
+ shouts, nothing is heard. Start with the assumption everything is visual noise,
+ guilty until proven innocent.
+- **Make clickable things obviously clickable.** No relying on hover states for
+ discoverability, especially on mobile where hover doesn't exist. Shape, location,
+ and formatting (color, underlining) must signal clickability without interaction.
+- **Eliminate noise.** Three sources: too many things shouting for attention
+ (shouting), things not organized logically (disorganization), and too much stuff
+ (clutter). Fix noise by removal, not addition.
+- **Clarity trumps consistency.** If making something significantly clearer
+ requires making it slightly inconsistent, choose clarity every time.
+
+### Navigation as Wayfinding
+
+Users on the web have no sense of scale, direction, or location. Navigation
+must always answer: What site is this? What page am I on? What are the major
+sections? What are my options at this level? Where am I? How can I search?
+
+Persistent navigation on every page. Breadcrumbs for deep hierarchies.
+Current section visually indicated. The "trunk test": cover everything except
+the navigation. You should still know what site this is, what page you're on,
+and what the major sections are. If not, the navigation has failed.
+
+### The Goodwill Reservoir
+
+Users start with a reservoir of goodwill. Every friction point depletes it.
+
+**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing
+users for not doing things your way (formatting requirements on phone numbers).
+Asking for unnecessary information. Putting sizzle in their way (splash screens,
+forced tours, interstitials). Unprofessional or sloppy appearance.
+
+**Replenish:** Know what users want to do and make it obvious. Tell them what they
+want to know upfront. Save them steps wherever possible. Make it easy to recover
+from errors. When in doubt, apologize.
+
+### Mobile: Same Rules, Higher Stakes
+
+All the above applies on mobile, just more so. Real estate is scarce, but never
+sacrifice usability for space savings. Affordances must be VISIBLE: no cursor
+means no hover-to-discover. Touch targets must be big enough (44px minimum).
+Flat design can strip away useful visual information that signals interactivity.
+Prioritize ruthlessly: things needed in a hurry go close at hand, everything
+else a few taps away with an obvious path to get there.
diff --git a/design-html/sections/doctrine.md.tmpl b/design-html/sections/doctrine.md.tmpl
new file mode 100644
index 000000000..7de81c4db
--- /dev/null
+++ b/design-html/sections/doctrine.md.tmpl
@@ -0,0 +1 @@
+{{UX_PRINCIPLES}}
diff --git a/design-html/sections/manifest.json b/design-html/sections/manifest.json
new file mode 100644
index 000000000..caa6940de
--- /dev/null
+++ b/design-html/sections/manifest.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://gstack.dev/schemas/section-manifest.json",
+ "skill": "design-html",
+ "version": 1,
+ "note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's decision-tree prose is the ONLY place that decides WHEN to read a section; required-reads live in the E2E fixtures. No machine predicate here — see docs/designs/v2_PLAN.md:663.",
+ "sections": [
+ {
+ "id": "doctrine",
+ "file": "doctrine.md",
+ "title": "UX principles doctrine (how users actually behave)",
+ "trigger": "analyzing the design or making any layout/visual decision (Step 1 onward) — the UX-principles doctrine governs every design choice"
+ },
+ {
+ "id": "pretext-patterns",
+ "file": "pretext-patterns.md",
+ "title": "Pretext wiring patterns + API reference",
+ "trigger": "writing the finalized HTML in Step 3 — the Pretext wiring patterns and API cheatsheet are the required reference for all text-layout code"
+ }
+ ]
+}
diff --git a/design-html/sections/pretext-patterns.md b/design-html/sections/pretext-patterns.md
new file mode 100644
index 000000000..8823c853a
--- /dev/null
+++ b/design-html/sections/pretext-patterns.md
@@ -0,0 +1,155 @@
+
+
+### Pretext Wiring Patterns
+
+Use these patterns based on the tier selected in Step 2. These are the correct
+Pretext API usage patterns. Follow them exactly.
+
+**Pattern 1: Basic height computation (Simple layout, Card/grid)**
+```js
+import { prepare, layout } from './pretext-inline.js'
+// Or if inlined: const { prepare, layout } = window.Pretext
+
+// 1. PREPARE — one-time, after fonts load
+await document.fonts.ready
+const elements = document.querySelectorAll('[data-pretext]')
+const prepared = new Map()
+
+for (const el of elements) {
+ const text = el.textContent
+ const font = getComputedStyle(el).font
+ prepared.set(el, prepare(text, font))
+}
+
+// 2. LAYOUT — cheap, call on every resize
+function relayout() {
+ for (const [el, handle] of prepared) {
+ const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight))
+ el.style.height = `${height}px`
+ }
+}
+
+// 3. RESIZE-AWARE
+new ResizeObserver(() => relayout()).observe(document.body)
+relayout()
+
+// 4. CONTENT-EDITABLE — re-prepare when text changes
+for (const el of elements) {
+ if (el.contentEditable === 'true') {
+ new MutationObserver(() => {
+ const font = getComputedStyle(el).font
+ prepared.set(el, prepare(el.textContent, font))
+ relayout()
+ }).observe(el, { characterData: true, subtree: true, childList: true })
+ }
+}
+```
+
+**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)**
+```js
+import { prepareWithSegments, walkLineRanges } from './pretext-inline.js'
+
+// Find the tightest width that produces the same line count
+function shrinkwrap(text, font, maxWidth, lineHeight) {
+ const segs = prepareWithSegments(text, font)
+ let bestWidth = maxWidth
+ walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => {
+ // walkLineRanges calls back with progressively narrower widths
+ // The first call gives us the line count at maxWidth
+ // We want the narrowest width that still produces this line count
+ })
+ // Binary search for tightest width with same line count
+ const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight)
+ let lo = 0, hi = maxWidth
+ while (hi - lo > 1) {
+ const mid = (lo + hi) / 2
+ const { lineCount } = layout(prepare(text, font), mid, lineHeight)
+ if (lineCount === targetLines) hi = mid
+ else lo = mid
+ }
+ return hi
+}
+```
+
+**Pattern 3: Text around obstacles (Editorial layout)**
+```js
+import { prepareWithSegments, layoutNextLine } from './pretext-inline.js'
+
+function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) {
+ const segs = prepareWithSegments(text, font)
+ let state = null
+ let y = 0
+ const lines = []
+
+ while (true) {
+ // Calculate available width at current y position, accounting for obstacles
+ let availWidth = containerWidth
+ for (const obs of obstacles) {
+ if (y >= obs.top && y < obs.top + obs.height) {
+ availWidth -= obs.width
+ }
+ }
+
+ const result = layoutNextLine(segs, state, availWidth, lineHeight)
+ if (!result) break
+
+ lines.push({ text: result.text, width: result.width, x: 0, y })
+ state = result.state
+ y += lineHeight
+ }
+
+ return { lines, totalHeight: y }
+}
+```
+
+**Pattern 4: Full line-by-line rendering (Complex editorial)**
+```js
+import { prepareWithSegments, layoutWithLines } from './pretext-inline.js'
+
+const segs = prepareWithSegments(text, font)
+const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight)
+
+// lines = [{ text, width, x, y }, ...]
+// Use for Canvas/SVG rendering or custom DOM positioning
+for (const line of lines) {
+ const span = document.createElement('span')
+ span.textContent = line.text
+ span.style.position = 'absolute'
+ span.style.left = `${line.x}px`
+ span.style.top = `${line.y}px`
+ container.appendChild(span)
+}
+```
+
+### Pretext API Reference
+
+```
+PRETEXT API CHEATSHEET:
+
+prepare(text, font) → handle
+ One-time text measurement. Call after document.fonts.ready.
+ Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'.
+
+layout(prepared, maxWidth, lineHeight) → { height, lineCount }
+ Fast layout computation. Call on every resize. Sub-millisecond.
+
+prepareWithSegments(text, font) → handle
+ Like prepare() but enables line-level APIs below.
+
+layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height }
+ Full line-by-line breakdown. For Canvas/SVG rendering.
+
+walkLineRanges(segs, maxWidth, onLine) → void
+ Calls onLine(lineCount, startIdx, endIdx) for each possible layout.
+ Find minimum width for N lines. For tight-fit containers.
+
+layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null
+ Iterator. Different maxWidth per line = text around obstacles.
+ Pass null as initial state. Returns null when text is exhausted.
+
+clearCache() → void
+ Clears internal measurement caches. Use when cycling many fonts.
+
+setLocale(locale?) → void
+ Retargets word segmenter for future prepare() calls.
+```
diff --git a/design-html/sections/pretext-patterns.md.tmpl b/design-html/sections/pretext-patterns.md.tmpl
new file mode 100644
index 000000000..15d33171c
--- /dev/null
+++ b/design-html/sections/pretext-patterns.md.tmpl
@@ -0,0 +1,153 @@
+### Pretext Wiring Patterns
+
+Use these patterns based on the tier selected in Step 2. These are the correct
+Pretext API usage patterns. Follow them exactly.
+
+**Pattern 1: Basic height computation (Simple layout, Card/grid)**
+```js
+import { prepare, layout } from './pretext-inline.js'
+// Or if inlined: const { prepare, layout } = window.Pretext
+
+// 1. PREPARE — one-time, after fonts load
+await document.fonts.ready
+const elements = document.querySelectorAll('[data-pretext]')
+const prepared = new Map()
+
+for (const el of elements) {
+ const text = el.textContent
+ const font = getComputedStyle(el).font
+ prepared.set(el, prepare(text, font))
+}
+
+// 2. LAYOUT — cheap, call on every resize
+function relayout() {
+ for (const [el, handle] of prepared) {
+ const { height } = layout(handle, el.clientWidth, parseFloat(getComputedStyle(el).lineHeight))
+ el.style.height = `${height}px`
+ }
+}
+
+// 3. RESIZE-AWARE
+new ResizeObserver(() => relayout()).observe(document.body)
+relayout()
+
+// 4. CONTENT-EDITABLE — re-prepare when text changes
+for (const el of elements) {
+ if (el.contentEditable === 'true') {
+ new MutationObserver(() => {
+ const font = getComputedStyle(el).font
+ prepared.set(el, prepare(el.textContent, font))
+ relayout()
+ }).observe(el, { characterData: true, subtree: true, childList: true })
+ }
+}
+```
+
+**Pattern 2: Shrinkwrap / tight-fit containers (Chat bubbles)**
+```js
+import { prepareWithSegments, walkLineRanges } from './pretext-inline.js'
+
+// Find the tightest width that produces the same line count
+function shrinkwrap(text, font, maxWidth, lineHeight) {
+ const segs = prepareWithSegments(text, font)
+ let bestWidth = maxWidth
+ walkLineRanges(segs, maxWidth, (lineCount, startIdx, endIdx) => {
+ // walkLineRanges calls back with progressively narrower widths
+ // The first call gives us the line count at maxWidth
+ // We want the narrowest width that still produces this line count
+ })
+ // Binary search for tightest width with same line count
+ const { lineCount: targetLines } = layout(prepare(text, font), maxWidth, lineHeight)
+ let lo = 0, hi = maxWidth
+ while (hi - lo > 1) {
+ const mid = (lo + hi) / 2
+ const { lineCount } = layout(prepare(text, font), mid, lineHeight)
+ if (lineCount === targetLines) hi = mid
+ else lo = mid
+ }
+ return hi
+}
+```
+
+**Pattern 3: Text around obstacles (Editorial layout)**
+```js
+import { prepareWithSegments, layoutNextLine } from './pretext-inline.js'
+
+function layoutAroundObstacles(text, font, containerWidth, lineHeight, obstacles) {
+ const segs = prepareWithSegments(text, font)
+ let state = null
+ let y = 0
+ const lines = []
+
+ while (true) {
+ // Calculate available width at current y position, accounting for obstacles
+ let availWidth = containerWidth
+ for (const obs of obstacles) {
+ if (y >= obs.top && y < obs.top + obs.height) {
+ availWidth -= obs.width
+ }
+ }
+
+ const result = layoutNextLine(segs, state, availWidth, lineHeight)
+ if (!result) break
+
+ lines.push({ text: result.text, width: result.width, x: 0, y })
+ state = result.state
+ y += lineHeight
+ }
+
+ return { lines, totalHeight: y }
+}
+```
+
+**Pattern 4: Full line-by-line rendering (Complex editorial)**
+```js
+import { prepareWithSegments, layoutWithLines } from './pretext-inline.js'
+
+const segs = prepareWithSegments(text, font)
+const { lines, height } = layoutWithLines(segs, containerWidth, lineHeight)
+
+// lines = [{ text, width, x, y }, ...]
+// Use for Canvas/SVG rendering or custom DOM positioning
+for (const line of lines) {
+ const span = document.createElement('span')
+ span.textContent = line.text
+ span.style.position = 'absolute'
+ span.style.left = `${line.x}px`
+ span.style.top = `${line.y}px`
+ container.appendChild(span)
+}
+```
+
+### Pretext API Reference
+
+```
+PRETEXT API CHEATSHEET:
+
+prepare(text, font) → handle
+ One-time text measurement. Call after document.fonts.ready.
+ Font: CSS shorthand like '16px Inter' or 'bold 24px Georgia'.
+
+layout(prepared, maxWidth, lineHeight) → { height, lineCount }
+ Fast layout computation. Call on every resize. Sub-millisecond.
+
+prepareWithSegments(text, font) → handle
+ Like prepare() but enables line-level APIs below.
+
+layoutWithLines(segs, maxWidth, lineHeight) → { lines: [{text, width, x, y}...], height }
+ Full line-by-line breakdown. For Canvas/SVG rendering.
+
+walkLineRanges(segs, maxWidth, onLine) → void
+ Calls onLine(lineCount, startIdx, endIdx) for each possible layout.
+ Find minimum width for N lines. For tight-fit containers.
+
+layoutNextLine(segs, state, maxWidth, lineHeight) → { text, width, state } | null
+ Iterator. Different maxWidth per line = text around obstacles.
+ Pass null as initial state. Returns null when text is exhausted.
+
+clearCache() → void
+ Clears internal measurement caches. Use when cycling many fonts.
+
+setLocale(locale?) → void
+ Retargets word segmenter for future prepare() calls.
+```
diff --git a/design-shotgun/SKILL.md b/design-shotgun/SKILL.md
index 32804ed9c..63f13694f 100644
--- a/design-shotgun/SKILL.md
+++ b/design-shotgun/SKILL.md
@@ -415,6 +415,19 @@ You are a design brainstorming partner. Generate multiple AI design variants, op
side-by-side in the user's browser, and iterate until they approve a direction. This is
visual brainstorming, not a review process.
+---
+
+## Section index — Read each section when its situation applies
+
+This skill is a decision-tree skeleton. The steps below point to on-demand
+sections. Read a section in full before doing its step; do not work from memory.
+
+| When | Read this section |
+|------|-------------------|
+| writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction | `sections/doctrine.md` |
+
+---
+
## DESIGN SETUP (run this check BEFORE any design mockup command)
```bash
@@ -458,90 +471,8 @@ MUST be saved to `~/.gstack/projects/$SLUG/designs/`, NEVER to `.context/`,
`docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER
data, not project files. They persist across branches, conversations, and workspaces.
-## UX Principles: How Users Actually Behave
-
-These principles govern how real humans interact with interfaces. They are observed
-behavior, not preferences. Apply them before, during, and after every design decision.
-
-### The Three Laws of Usability
-
-1. **Don't make me think.** Every page should be self-evident. If a user stops
- to think "What do I click?" or "What does this mean?", the design has failed.
- Self-evident > self-explanatory > requires explanation.
-
-2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks
- beat one click that requires thought. Each step should feel like an obvious
- choice (animal, vegetable, or mineral), not a puzzle.
-
-3. **Omit, then omit again.** Get rid of half the words on each page, then get
- rid of half of what's left. Happy talk (self-congratulatory text) must die.
- Instructions must die. If they need reading, the design has failed.
-
-### How Users Actually Behave
-
-- **Users scan, they don't read.** Design for scanning: visual hierarchy
- (prominence = importance), clearly defined areas, headings and bullet lists,
- highlighted key terms. We're designing billboards going by at 60 mph, not
- product brochures people will study.
-- **Users satisfice.** They pick the first reasonable option, not the best.
- Make the right choice the most visible choice.
-- **Users muddle through.** They don't figure out how things work. They wing
- it. If they accomplish their goal by accident, they won't seek the "right" way.
- Once they find something that works, no matter how badly, they stick to it.
-- **Users don't read instructions.** They dive in. Guidance must be brief,
- timely, and unavoidable, or it won't be seen.
-
-### Billboard Design for Interfaces
-
-- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass.
- Don't innovate on navigation to be clever. Innovate when you KNOW you have a
- better idea, otherwise use conventions. Even across languages and cultures,
- web conventions let people identify the logo, nav, search, and main content.
-- **Visual hierarchy is everything.** Related things are visually grouped. Nested
- things are visually contained. More important = more prominent. If everything
- shouts, nothing is heard. Start with the assumption everything is visual noise,
- guilty until proven innocent.
-- **Make clickable things obviously clickable.** No relying on hover states for
- discoverability, especially on mobile where hover doesn't exist. Shape, location,
- and formatting (color, underlining) must signal clickability without interaction.
-- **Eliminate noise.** Three sources: too many things shouting for attention
- (shouting), things not organized logically (disorganization), and too much stuff
- (clutter). Fix noise by removal, not addition.
-- **Clarity trumps consistency.** If making something significantly clearer
- requires making it slightly inconsistent, choose clarity every time.
-
-### Navigation as Wayfinding
-
-Users on the web have no sense of scale, direction, or location. Navigation
-must always answer: What site is this? What page am I on? What are the major
-sections? What are my options at this level? Where am I? How can I search?
-
-Persistent navigation on every page. Breadcrumbs for deep hierarchies.
-Current section visually indicated. The "trunk test": cover everything except
-the navigation. You should still know what site this is, what page you're on,
-and what the major sections are. If not, the navigation has failed.
-
-### The Goodwill Reservoir
-
-Users start with a reservoir of goodwill. Every friction point depletes it.
-
-**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing
-users for not doing things your way (formatting requirements on phone numbers).
-Asking for unnecessary information. Putting sizzle in their way (splash screens,
-forced tours, interstitials). Unprofessional or sloppy appearance.
-
-**Replenish:** Know what users want to do and make it obvious. Tell them what they
-want to know upfront. Save them steps wherever possible. Make it easy to recover
-from errors. When in doubt, apologize.
-
-### Mobile: Same Rules, Higher Stakes
-
-All the above applies on mobile, just more so. Real estate is scarce, but never
-sacrifice usability for space savings. Affordances must be VISIBLE: no cursor
-means no hover-to-discover. Touch targets must be big enough (44px minimum).
-Flat design can strip away useful visual information that signals interactivity.
-Prioritize ruthlessly: things needed in a hurry go close at hand, everything
-else a few taps away with an obvious path to get there.
+> **STOP.** Before writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction, Read `~/.claude/skills/gstack/design-shotgun/sections/doctrine.md` and execute it
+> in full. Do not work from memory — that section is the source of truth for this step.
## Step 0: Session Detection
diff --git a/design-shotgun/SKILL.md.tmpl b/design-shotgun/SKILL.md.tmpl
index 230dbc292..7b9c038d8 100644
--- a/design-shotgun/SKILL.md.tmpl
+++ b/design-shotgun/SKILL.md.tmpl
@@ -50,9 +50,15 @@ You are a design brainstorming partner. Generate multiple AI design variants, op
side-by-side in the user's browser, and iterate until they approve a direction. This is
visual brainstorming, not a review process.
+---
+
+{{SECTION_INDEX:design-shotgun}}
+
+---
+
{{DESIGN_SETUP}}
-{{UX_PRINCIPLES}}
+{{SECTION:doctrine}}
## Step 0: Session Detection
diff --git a/design-shotgun/sections/doctrine.md b/design-shotgun/sections/doctrine.md
new file mode 100644
index 000000000..7ec4bdca9
--- /dev/null
+++ b/design-shotgun/sections/doctrine.md
@@ -0,0 +1,86 @@
+
+
+## UX Principles: How Users Actually Behave
+
+These principles govern how real humans interact with interfaces. They are observed
+behavior, not preferences. Apply them before, during, and after every design decision.
+
+### The Three Laws of Usability
+
+1. **Don't make me think.** Every page should be self-evident. If a user stops
+ to think "What do I click?" or "What does this mean?", the design has failed.
+ Self-evident > self-explanatory > requires explanation.
+
+2. **Clicks don't matter, thinking does.** Three mindless, unambiguous clicks
+ beat one click that requires thought. Each step should feel like an obvious
+ choice (animal, vegetable, or mineral), not a puzzle.
+
+3. **Omit, then omit again.** Get rid of half the words on each page, then get
+ rid of half of what's left. Happy talk (self-congratulatory text) must die.
+ Instructions must die. If they need reading, the design has failed.
+
+### How Users Actually Behave
+
+- **Users scan, they don't read.** Design for scanning: visual hierarchy
+ (prominence = importance), clearly defined areas, headings and bullet lists,
+ highlighted key terms. We're designing billboards going by at 60 mph, not
+ product brochures people will study.
+- **Users satisfice.** They pick the first reasonable option, not the best.
+ Make the right choice the most visible choice.
+- **Users muddle through.** They don't figure out how things work. They wing
+ it. If they accomplish their goal by accident, they won't seek the "right" way.
+ Once they find something that works, no matter how badly, they stick to it.
+- **Users don't read instructions.** They dive in. Guidance must be brief,
+ timely, and unavoidable, or it won't be seen.
+
+### Billboard Design for Interfaces
+
+- **Use conventions.** Logo top-left, nav top/left, search = magnifying glass.
+ Don't innovate on navigation to be clever. Innovate when you KNOW you have a
+ better idea, otherwise use conventions. Even across languages and cultures,
+ web conventions let people identify the logo, nav, search, and main content.
+- **Visual hierarchy is everything.** Related things are visually grouped. Nested
+ things are visually contained. More important = more prominent. If everything
+ shouts, nothing is heard. Start with the assumption everything is visual noise,
+ guilty until proven innocent.
+- **Make clickable things obviously clickable.** No relying on hover states for
+ discoverability, especially on mobile where hover doesn't exist. Shape, location,
+ and formatting (color, underlining) must signal clickability without interaction.
+- **Eliminate noise.** Three sources: too many things shouting for attention
+ (shouting), things not organized logically (disorganization), and too much stuff
+ (clutter). Fix noise by removal, not addition.
+- **Clarity trumps consistency.** If making something significantly clearer
+ requires making it slightly inconsistent, choose clarity every time.
+
+### Navigation as Wayfinding
+
+Users on the web have no sense of scale, direction, or location. Navigation
+must always answer: What site is this? What page am I on? What are the major
+sections? What are my options at this level? Where am I? How can I search?
+
+Persistent navigation on every page. Breadcrumbs for deep hierarchies.
+Current section visually indicated. The "trunk test": cover everything except
+the navigation. You should still know what site this is, what page you're on,
+and what the major sections are. If not, the navigation has failed.
+
+### The Goodwill Reservoir
+
+Users start with a reservoir of goodwill. Every friction point depletes it.
+
+**Deplete faster:** Hiding info users want (pricing, contact, shipping). Punishing
+users for not doing things your way (formatting requirements on phone numbers).
+Asking for unnecessary information. Putting sizzle in their way (splash screens,
+forced tours, interstitials). Unprofessional or sloppy appearance.
+
+**Replenish:** Know what users want to do and make it obvious. Tell them what they
+want to know upfront. Save them steps wherever possible. Make it easy to recover
+from errors. When in doubt, apologize.
+
+### Mobile: Same Rules, Higher Stakes
+
+All the above applies on mobile, just more so. Real estate is scarce, but never
+sacrifice usability for space savings. Affordances must be VISIBLE: no cursor
+means no hover-to-discover. Touch targets must be big enough (44px minimum).
+Flat design can strip away useful visual information that signals interactivity.
+Prioritize ruthlessly: things needed in a hurry go close at hand, everything
+else a few taps away with an obvious path to get there.
diff --git a/design-shotgun/sections/doctrine.md.tmpl b/design-shotgun/sections/doctrine.md.tmpl
new file mode 100644
index 000000000..7de81c4db
--- /dev/null
+++ b/design-shotgun/sections/doctrine.md.tmpl
@@ -0,0 +1 @@
+{{UX_PRINCIPLES}}
diff --git a/design-shotgun/sections/manifest.json b/design-shotgun/sections/manifest.json
new file mode 100644
index 000000000..198220262
--- /dev/null
+++ b/design-shotgun/sections/manifest.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://gstack.dev/schemas/section-manifest.json",
+ "skill": "design-shotgun",
+ "version": 1,
+ "note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's decision-tree prose is the ONLY place that decides WHEN to read a section; required-reads live in the E2E fixtures. No machine predicate here — see docs/designs/v2_PLAN.md:663.",
+ "sections": [
+ {
+ "id": "doctrine",
+ "file": "doctrine.md",
+ "title": "UX principles doctrine (how users actually behave)",
+ "trigger": "writing variant concepts or design briefs (Step 3 onward) — the UX-principles doctrine governs every design direction"
+ }
+ ]
+}