mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-14 08:59:01 +02:00
feat(design): carve UX doctrine + Pretext patterns into read-on-demand sections
design-html 57→49KB, design-shotgun 53→50KB. Sections wrap
{{UX_PRINCIPLES}} so scripts/resolvers/design.ts stays the source of
truth; the pretext-patterns STOP sits at the top of Step 3 so the read
provably precedes the Write.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
eae484c0f7
commit
4fce22d708
+19
-238
@@ -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
|
to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow
|
||||||
around obstacles.
|
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)
|
## DESIGN SETUP (run this check BEFORE any design mockup command)
|
||||||
|
|
||||||
```bash
|
```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
|
`docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER
|
||||||
data, not project files. They persist across branches, conversations, and workspaces.
|
data, not project files. They persist across branches, conversations, and workspaces.
|
||||||
|
|
||||||
## UX Principles: How Users Actually Behave
|
> **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.
|
||||||
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.
|
|
||||||
|
|
||||||
## SETUP (run this check BEFORE any browse command)
|
## 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
|
## 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
|
### Pretext Source Embedding
|
||||||
|
|
||||||
For **vanilla HTML output**, check for the vendored Pretext bundle:
|
For **vanilla HTML output**, check for the vendored Pretext bundle:
|
||||||
@@ -802,160 +737,6 @@ For framework output, save to:
|
|||||||
- Generic testimonial sections
|
- Generic testimonial sections
|
||||||
- Cookie-cutter hero sections with left-text right-image
|
- 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
|
## Step 3.5: Live Reload Server
|
||||||
|
|||||||
+9
-155
@@ -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
|
to content, cards size themselves, chat bubbles shrinkwrap, editorial spreads flow
|
||||||
around obstacles.
|
around obstacles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{SECTION_INDEX:design-html}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
{{DESIGN_SETUP}}
|
{{DESIGN_SETUP}}
|
||||||
|
|
||||||
{{UX_PRINCIPLES}}
|
{{SECTION:doctrine}}
|
||||||
|
|
||||||
{{BROWSE_SETUP}}
|
{{BROWSE_SETUP}}
|
||||||
|
|
||||||
@@ -220,6 +226,8 @@ If no framework detected: default to vanilla HTML, no question needed.
|
|||||||
|
|
||||||
## Step 3: Generate Pretext-Native HTML
|
## Step 3: Generate Pretext-Native HTML
|
||||||
|
|
||||||
|
{{SECTION:pretext-patterns}}
|
||||||
|
|
||||||
### Pretext Source Embedding
|
### Pretext Source Embedding
|
||||||
|
|
||||||
For **vanilla HTML output**, check for the vendored Pretext bundle:
|
For **vanilla HTML output**, check for the vendored Pretext bundle:
|
||||||
@@ -281,160 +289,6 @@ For framework output, save to:
|
|||||||
- Generic testimonial sections
|
- Generic testimonial sections
|
||||||
- Cookie-cutter hero sections with left-text right-image
|
- 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
|
## Step 3.5: Live Reload Server
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<!-- AUTO-GENERATED from doctrine.md.tmpl — do not edit directly -->
|
||||||
|
<!-- Regenerate: bun run gen:skill-docs -->
|
||||||
|
## 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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{{UX_PRINCIPLES}}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<!-- AUTO-GENERATED from pretext-patterns.md.tmpl — do not edit directly -->
|
||||||
|
<!-- Regenerate: bun run gen:skill-docs -->
|
||||||
|
### 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.
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
|
```
|
||||||
+15
-84
@@ -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
|
side-by-side in the user's browser, and iterate until they approve a direction. This is
|
||||||
visual brainstorming, not a review process.
|
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)
|
## DESIGN SETUP (run this check BEFORE any design mockup command)
|
||||||
|
|
||||||
```bash
|
```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
|
`docs/designs/`, `/tmp/`, or any project-local directory. Design artifacts are USER
|
||||||
data, not project files. They persist across branches, conversations, and workspaces.
|
data, not project files. They persist across branches, conversations, and workspaces.
|
||||||
|
|
||||||
## UX Principles: How Users Actually Behave
|
> **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.
|
||||||
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.
|
|
||||||
|
|
||||||
## Step 0: Session Detection
|
## Step 0: Session Detection
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
side-by-side in the user's browser, and iterate until they approve a direction. This is
|
||||||
visual brainstorming, not a review process.
|
visual brainstorming, not a review process.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{SECTION_INDEX:design-shotgun}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
{{DESIGN_SETUP}}
|
{{DESIGN_SETUP}}
|
||||||
|
|
||||||
{{UX_PRINCIPLES}}
|
{{SECTION:doctrine}}
|
||||||
|
|
||||||
## Step 0: Session Detection
|
## Step 0: Session Detection
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<!-- AUTO-GENERATED from doctrine.md.tmpl — do not edit directly -->
|
||||||
|
<!-- Regenerate: bun run gen:skill-docs -->
|
||||||
|
## 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.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{{UX_PRINCIPLES}}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user