Expand concept system: modes, ratings, re-roll, breadth strategy

Catalog: mode-aligned staging surfaces (persuade/operate/read/experience),
star ratings on approvals feeding challenger draw weights, family
retirements, authoring strategy and territory guide, rework and breadth
authoring rounds, composition mining from rejected worlds.

Seed: six challengers (two per tier), --reroll chains, --mode staging
filter, rating-weighted draws. New-work: Present/visualize/re-roll flow,
image-gen requirement, register-neutral vocabulary.

Pipeline: per-mode staging prompts with split frames, hero-from-board
reference generation, render-safety guards. Labs: ratings UI, unrated
filter, mode chips, composition approve-guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-20 22:10:10 -07:00
co-authored by Claude Fable 5
parent 144cee5c36
commit 7557935fdb
55 changed files with 24574 additions and 24025 deletions
+5
View File
@@ -106,6 +106,11 @@ site/public/tutorials/
site/public/visual-mode/
site/public/slop/
# World design-system cards live in R2 (functions/worlds/cards); only the
# manifest is tracked. Generate: bun run world-cards; upload: world-cards:publish.
site/public/worlds/cards/*.webp
site/public/worlds/cards/.published.json
# Build artifacts written to site/public/ so Astro copies them to build/
site/public/_data/
site/public/_headers
+16 -15
View File
@@ -9,14 +9,15 @@
"site/pages/shader-lab/**",
"site/scripts/demos/commands/**",
"site/styles/skill-demos.css",
"site/styles/slop-kinpaku.css"
"site/styles/slop-kinpaku.css",
"site/styles/worlds-lab.css"
],
"ignoreValues": [
{
"rule": "bounce-easing",
"value": "bounce-ball",
"createdAt": "2026-06-15T04:15:03.164Z",
"reason": "User confirmed ball bounce animation is intentional"
"reason": "User confirmed ball bounce animation is intentional",
"createdAt": "2026-06-15T04:15:03.164Z"
},
{
"rule": "design-system-color",
@@ -24,8 +25,8 @@
"files": [
"site/styles/home-rebuild.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "AURELIA hotel picker is intentionally a foreign boutique-hotel palette inside the live picker demo"
"reason": "AURELIA hotel picker is intentionally a foreign boutique-hotel palette inside the live picker demo",
"createdAt": "2026-06-15T23:37:38.170Z"
},
{
"rule": "design-system-color",
@@ -33,8 +34,8 @@
"files": [
"site/styles/main.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "Generic AI slop card intentionally uses off-system colors for the before-state comparison"
"reason": "Generic AI slop card intentionally uses off-system colors for the before-state comparison",
"createdAt": "2026-06-15T23:37:38.170Z"
},
{
"rule": "design-system-color",
@@ -42,8 +43,8 @@
"files": [
"site/styles/home-kinpaku.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "Homepage before-state slop demo intentionally uses off-system purple/magenta colors"
"reason": "Homepage before-state slop demo intentionally uses off-system purple/magenta colors",
"createdAt": "2026-06-15T23:37:38.170Z"
},
{
"rule": "design-system-color",
@@ -51,8 +52,8 @@
"files": [
"site/styles/design-system.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "Design-system comparison intentionally shows an off-system before-state"
"reason": "Design-system comparison intentionally shows an off-system before-state",
"createdAt": "2026-06-15T23:37:38.170Z"
},
{
"rule": "design-system-color",
@@ -60,8 +61,8 @@
"files": [
"site/styles/workflow.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "Generic slop card intentionally uses off-system purple colors for the before-state comparison"
"reason": "Generic slop card intentionally uses off-system purple colors for the before-state comparison",
"createdAt": "2026-06-15T23:37:38.170Z"
},
{
"rule": "design-system-font",
@@ -69,8 +70,8 @@
"files": [
"site/styles/workflow.css"
],
"createdAt": "2026-06-15T23:37:38.170Z",
"reason": "Generic slop card intentionally uses Inter for the before-state comparison"
"reason": "Generic slop card intentionally uses Inter for the before-state comparison",
"createdAt": "2026-06-15T23:37:38.170Z"
}
]
},
@@ -0,0 +1,33 @@
---
version: 1
slug: "site-pages-labs-worlds-astro"
primary_target: "site/pages/labs/worlds.astro"
related_targets: []
---
# Surface brief: World Catalog
## Scope
`/worlds` is an Operate-mode catalog review surface for the systems that challenge grounded Impeccable design directions. It is public and read-only in production; under `bun run dev` it becomes a source-writing review tool for the repository owner.
## Product strategy
The primary user is Paul reviewing a curated, growing concept backlog. The desired outcome is a trustworthy approved challenger pool whose entries can provoke world-leading visual systems—not merely recognizable motifs, literal industry software, operational archetypes, or near-duplicate specimens. The schema-v6 catalog groups 23 families into seven deliberately different inspiration wells spanning graphic systems, canon movements, vernacular ephemera, instruments and signals, medium-native work, material worlds, and performed time. Every concept must paint an immediate creative scene, inherit concrete material or spatial laws, define reusable topology/navigation, controls/state, and responsive/motion behavior, and name a browser-native advantage distinct from its nearest catalog neighbors. Richness and semantic distinctness are universal admission bars. Approve is the primary action; reject, restore to pending, edit, search, filter, and well/family navigation are secondary. Editing any reviewed concept returns it to pending so human approval always applies to the current content. Catalog content and review decisions remain separate so automation can add candidates without self-approving them.
## Selected direction
Use the confirmed Neo Kinpaku world and the `/detector` developer-tool lineage. The surface is a backlog-first catalog workbench: fixed territory rail, compact queue, and a large central review stage that gives one concept enough space for careful judgment. The creative spark leads the review, with system grammar and web leverage proving that the idea can become more than art direction. Progress, status, provenance, structural tags, and keyboard actions remain visible without turning the page into a dashboard of cards.
## Direction contract
- **THESIS:** Turn an abstract challenger corpus into a human-governed editorial queue that rejects the tiled catalog and admin-dashboard defaults; one evocative world earns focused attention and proves it can become a generative web system.
- **OWN-WORLD:** Lacquer ground, kinpaku commitment, patina state, neutral hairlines, Albert Sans UI, compact geometry.
- **STORY:** Find pending work, picture its creative world immediately, judge its material laws together with system grammar and use of the medium, decide, and advance the trusted pool.
- **FIRST VIEWPORT:** Well/progress rail, searchable queue, and one full-height review stage with immediate keyboard-visible decisions.
- **BAR-RAISER:** Every concept must expose an unforgettable creative spark, reusable topology/navigation, controls/state, responsive/motion behavior, and specific browser-native leverage before approval. Literal dashboards, control rooms, workflows, and tacked-on spectacle fail before review.
- **FORM:** A catalog editor crossed with a contact-sheet loupe; selection collapses hundreds of entries into one precise judgment at a time. Seed key: not run—structure is inherited from the established `/detector` tool surface.
## Open decisions
None.
@@ -1,33 +0,0 @@
---
version: 1
slug: "site-pages-worlds-astro"
primary_target: "site/pages/worlds/index.astro"
related_targets: []
---
# Surface brief: World Catalog
## Scope
`/worlds` is an Operate-mode catalog review surface for the concepts that challenge grounded Impeccable design directions. It is public and read-only in production; under `bun run dev` it becomes a source-writing review tool for the repository owner.
## Product strategy
The primary user is Paul reviewing a large, growing concept backlog. The desired outcome is a trustworthy approved challenger pool with clear progress through pending work. Approve is the primary action; reject, restore to pending, edit, search, filter, and category navigation are secondary. The page must make the 2,304-entry catalog comprehensible without rendering an overwhelming wall. Concepts remain ineligible for seeding until approved. Existing 113 entries begin approved; new and automated entries begin pending. Review decisions persist separately from catalog content so automation cannot self-approve or overwrite human judgment.
## Selected direction
Use the confirmed Neo Kinpaku world and the `/detector` developer-tool lineage. The surface is a backlog-first catalog workbench: fixed family rail, compact queue, and a large central review stage that gives one concept enough space for careful judgment. Progress, status, provenance, structural tags, and keyboard actions remain visible without turning the page into a dashboard of cards.
## Direction contract
- **UNIQUE:** Turn an abstract challenger corpus into a human-governed editorial queue.
- **NOT-TEMPLATE:** Refuse the generic analytics dashboard and equal-card catalog grid.
- **OWN-WORLD:** Lacquer ground, kinpaku commitment, patina state, neutral hairlines, Albert Sans UI, compact geometry.
- **STORY:** Find pending work, understand one form, approve or reject it, and see the trusted pool advance.
- **FIRST VIEWPORT:** Family/progress rail, searchable queue, and one full-height review stage with immediate keyboard-visible decisions.
- **FORM:** A catalog editor crossed with a contact-sheet loupe; selection collapses thousands of entries into one precise judgment at a time. Seed key: not run—structure is inherited from the established `/detector` tool surface.
## Open decisions
None.
+4
View File
@@ -98,6 +98,10 @@ Hosted on Cloudflare Pages. Static assets served from `build/`, API routes handl
bun run deploy # Build + deploy to Cloudflare Pages
```
### World cards (R2, not git)
The `/worlds` design-system card images are generated per concept (`bun run world-cards`, gpt-image-2) into `site/public/worlds/cards/`, which is gitignored except `manifest.json` (content hashes + generation stamps). Production serves them from the `impeccable-world-cards` R2 bucket via `functions/worlds/cards/[[file]].js`; the build strips local card files from `build/` (`scripts/strip-local-world-cards.mjs`) so deploys stay light. After generating or regenerating cards, run `bun run world-cards:publish` to upload changed files. The worlds page prefers local files in dev and falls back to the published URLs on clones without local generation output.
## Social sharing image (OG card)
The OG / Twitter card is generated, not hand-drawn. To regenerate after a brand or copy change:
+19
View File
@@ -803,6 +803,9 @@ function checkBorders(tag, widths, colors, radius, opts = {}) {
// text-level borders, not chips. They skip the left/right arms below.
const spanBadge = tag === 'span' && !!opts.badgeLike;
if (BORDER_SAFE_TAGS.has(tag) && !spanBadge) return [];
// A live status/alert region wears a colored single-edge border as a
// severity accent (toast, snackbar, callout), not as the side-tab tell.
if (opts.statusContext) return [];
const findings = [];
const sides = ['Top', 'Right', 'Bottom', 'Left'];
@@ -2516,6 +2519,20 @@ function isTabContextElement(el) {
return false;
}
// Status-surface context for accent borders. On a live status/alert region
// (role=status|alert|alertdialog|log, or aria-live=polite|assertive) a colored
// single-edge border is the established severity-accent convention — a toast,
// snackbar, or callout bar — not the decorative side-tab tell. The element
// itself or a wrapping live region qualifies. This never fires from the
// CSS-only / regex scanners, which have no role information.
function isStatusContextElement(el) {
if (!el) return false;
try {
if (el.closest?.('[role="status"], [role="alert"], [role="alertdialog"], [role="log"], [aria-live="polite"], [aria-live="assertive"]')) return true;
} catch { /* selector engine differences — fall through */ }
return false;
}
function checkElementBordersDOM(el) {
const tag = el.tagName.toLowerCase();
if (BORDER_SAFE_TAGS.has(tag)) return [];
@@ -2531,6 +2548,7 @@ function checkElementBordersDOM(el) {
const ownBg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
return checkBorders(tag, widths, colors, parseFloat(style.borderRadius) || 0, {
tabContext: isTabContextElement(el),
statusContext: isStatusContextElement(el),
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
});
}
@@ -3982,6 +4000,7 @@ function checkElementBorders(tag, style, overrides, resolvedRadius, el = null) {
const ownBg = parseAnyColor(style.backgroundColor);
return checkBorders(tag, widths, colors, radius, {
tabContext: isTabContextElement(el),
statusContext: isStatusContextElement(el),
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
});
}
+19
View File
@@ -30,6 +30,9 @@ function checkBorders(tag, widths, colors, radius, opts = {}) {
// text-level borders, not chips. They skip the left/right arms below.
const spanBadge = tag === 'span' && !!opts.badgeLike;
if (BORDER_SAFE_TAGS.has(tag) && !spanBadge) return [];
// A live status/alert region wears a colored single-edge border as a
// severity accent (toast, snackbar, callout), not as the side-tab tell.
if (opts.statusContext) return [];
const findings = [];
const sides = ['Top', 'Right', 'Bottom', 'Left'];
@@ -1743,6 +1746,20 @@ function isTabContextElement(el) {
return false;
}
// Status-surface context for accent borders. On a live status/alert region
// (role=status|alert|alertdialog|log, or aria-live=polite|assertive) a colored
// single-edge border is the established severity-accent convention — a toast,
// snackbar, or callout bar — not the decorative side-tab tell. The element
// itself or a wrapping live region qualifies. This never fires from the
// CSS-only / regex scanners, which have no role information.
function isStatusContextElement(el) {
if (!el) return false;
try {
if (el.closest?.('[role="status"], [role="alert"], [role="alertdialog"], [role="log"], [aria-live="polite"], [aria-live="assertive"]')) return true;
} catch { /* selector engine differences — fall through */ }
return false;
}
function checkElementBordersDOM(el) {
const tag = el.tagName.toLowerCase();
if (BORDER_SAFE_TAGS.has(tag)) return [];
@@ -1758,6 +1775,7 @@ function checkElementBordersDOM(el) {
const ownBg = parseRgb(style.backgroundColor) || parseAnyColor(style.backgroundColor);
return checkBorders(tag, widths, colors, parseFloat(style.borderRadius) || 0, {
tabContext: isTabContextElement(el),
statusContext: isStatusContextElement(el),
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
});
}
@@ -3209,6 +3227,7 @@ function checkElementBorders(tag, style, overrides, resolvedRadius, el = null) {
const ownBg = parseAnyColor(style.backgroundColor);
return checkBorders(tag, widths, colors, radius, {
tabContext: isTabContextElement(el),
statusContext: isStatusContextElement(el),
badgeLike: !!(ownBg && (ownBg.a ?? 1) > 0.1),
});
}
+4
View File
@@ -44,6 +44,10 @@ Your skill instructions here...
## Building
### Developer Lab URLs
No-index visual harnesses and internal inspectors live under `/labs/<subject>`. Use a short subject noun (`/labs/live-ui`, later `/labs/detector`), not a second `-lab` suffix. Stable public references such as `/docs` and `/design-system` stay top-level. Keep legacy top-level lab routes working through redirects when a lab moves; migrate existing exceptions when that surface is next changed rather than duplicating the page.
### Prerequisites
- Bun (fast JavaScript runtime and package manager)
- No external dependencies required
+51
View File
@@ -0,0 +1,51 @@
# World catalog authoring guide
How new concept-world rounds are authored, gated, and reviewed. Distilled from the full human sweep of 2026-07-20/21 (325 entries reviewed, 169 approved, all approved entries star-rated). The machine-readable core lives in `skill/scripts/concept-ingredients.json` under `qualityBar` (`rejectIf`, `authoringStrategy`); this guide carries the reasoning and the territory map.
## The pipeline
1. An authoring agent reads `qualityBar` (including `authoringStrategy`) and the review file. Three-star approvals are positive exemplars; rejection and rating notes are negative space.
2. New entries merge as `pending`. Nothing ships without human review.
3. The render gate runs before review: specimen board first, then the desktop hero generated with the board attached as binding reference (`scripts/generate-world-cards.mjs`; the images/edits path keeps both images one system).
4. The reviewer decides in `/labs/worlds`: approve or reject, star ratings on approvals (3 exceptional, 2 solid, 1 marginal), notes on anything instructive. Ratings feed challenger draws in `concept-seed.mjs` (3-star doubles odds, 1-star sits out).
## What wins
The winner-property test. Every candidate must be:
- **Born-designed**: the source is a produced 2D or display artifact with an existing graphic system, not a material, mood, or place. Even atmosphere-tier winners are secretly graphic (wax-print cloth, brick-build instructions, raku surface).
- **Dense**: the tradition immediately yields palette, materials, a type voice, several component roles, and a signature state change.
- **Era-and-school specific**: "1950s Blue Note session sleeve", never "record covers".
- **New territory**: the peak artifact of a culture the catalog has not touched. Proven seams saturate fast: the 2026-07-21 depth round scored 3/12 with 0 flagships because second-tier artifacts from mined veins read as near-duplicates ("too similar to others we already have"). Breadth-first beats depth-first.
- **System-distinct**: check the candidate against approved entries at the system level (palette plus type voice), not just by name. A different artifact with the same green-phosphor system is a duplicate.
Flagship share by tier after the full sweep: graphic 52%, interaction 55%, atmosphere 29%. Interaction display languages stay overweighted; atmosphere qualifies only through the intrinsic-pattern rule in `rejectIf`.
## What loses
- Translation failures: the dominant rejection ("doesn't translate to interface"). Material worlds without an intrinsic 2D pattern system never recover, and re-authoring them fails again (rework hit rate ~33%, and only for taste fixes, never translation fixes).
- Too narrow (single prop, single color), too abstract, operations archetypes, generic categories.
- Render traps: brass or metal interface chrome reads cheap (skeuomorphism itself is fine; execution is the issue), non-Latin copy drift on non-Western worlds (interface copy stays English), AI-cliche motifs (Matrix glyph rain, recording dots), dated game chrome (game-born worlds are welcome at contemporary award standard), em dashes in rendered copy.
## Cultural care
Skip living sacred, ceremonial, or community-owned traditions without an established commercial graphic lineage; skip highly religious material outright. Traditions enter cleanly when they already have a commercial or civic design history the way adinkra printing, thangka-informed diagram craft, hanafuda (a published card game), or azulejo (civic architecture) do. When in doubt, leave it out.
## Territory map (unmined as of 2026-07-21)
One candidate per territory, always the territory's canonical peak:
- **Print and publishing**: Penguin/Pelican Marber grid, ligne claire comics, fotonovela, children's book schools (Scarry, Golden Books), Victorian trade cards, stamp design, marbled endpapers.
- **Technical and scientific illustration**: Haeckel plates, patent drawings, anatomical atlases, exploded-view manuals, Sanborn insurance maps, airline safety cards.
- **Fashion as artifact** (not draping): tartan clan registries, kimono/obi pattern grammar, sewing-pattern envelopes with tissue markings, Take Ivy catalog photography, sneaker-box and colorway-naming culture, techwear spec labels.
- **Games and decks**: tarot (commercial deck lineage), hanafuda, mahjong tiles, Tamiya model-kit box art with sprue diagrams, Game & Watch LCD language, casino chip and felt graphics.
- **Global commercial traditions**: Portuguese azulejo, heraldry, sonidero/cumbia posters, Jamaican soundsystem graphics, Tropicália, Ethiopian commercial iconography, Mayan codex facsimile publishing.
- **Fine-art movements with systems**: Vorticism, Precisionism, Le Corbusier polychromy, Barragan color walls.
Mark territories off as rounds mine them; a mined territory moves to the saturation gate.
## Round mechanics
- Rounds stay small (~12) and are treated as experiments; expected hit rate is 25-40% now that the initial canonical harvest is done. Read the verdicts before the next round; every rejection note is calibration.
- Standing reviewer requests: more non-Western systems, more cassette/VHS-era systems.
- Retired families never receive new entries: machines-contraptions, food-potioncraft, memory-ruins, festivals-public-life.
+31
View File
@@ -0,0 +1,31 @@
// Serves world design-system cards from R2 so the 100MB+ of generated WebP
// stays out of git and out of every Pages deploy. Cards are uploaded by
// `bun run world-cards:publish` (scripts/publish-world-cards.mjs); the build
// strips local card files from the static output so this route owns the path.
const FILE_PATTERN = /^[a-z0-9-]+\.webp$|^manifest\.json$/;
export async function onRequestGet(context) {
const parts = context.params.file;
const file = Array.isArray(parts) ? parts.join('/') : parts;
if (!file || !FILE_PATTERN.test(file)) {
return new Response('Not found', { status: 404 });
}
const object = await context.env.WORLD_CARDS.get(file);
if (!object) {
return new Response('Not found', { status: 404 });
}
const headers = new Headers();
headers.set('Content-Type', file.endsWith('.json') ? 'application/json; charset=utf-8' : 'image/webp');
headers.set('ETag', object.httpEtag);
// Card URLs carry a ?v= generation stamp, so long immutable caching is safe:
// a regenerated card gets a new URL and bypasses every cache.
headers.set('Cache-Control', file.endsWith('.json')
? 'public, max-age=0, s-maxage=3600, stale-while-revalidate=600'
: 'public, max-age=31536000, immutable');
return new Response(object.body, { headers });
}
+4 -2
View File
@@ -43,7 +43,7 @@
"build:skills": "bun run scripts/build.js --skip-root-sync",
"build:skills:release": "bun run scripts/build.js",
"build:site": "npx astro build",
"build": "bun run build:skills && bun run build:site && cp -R dist build/_data/dist",
"build": "bun run build:skills && bun run build:site && cp -R dist build/_data/dist && node scripts/strip-local-world-cards.mjs",
"build:release": "bun run build:skills:release && bun run build:site && cp -R dist build/_data/dist",
"build:browser": "node scripts/build-browser-detector.js",
"build:extension": "node scripts/build-extension.js",
@@ -75,7 +75,9 @@
"release:cli": "node scripts/release.mjs cli",
"release:ext": "node scripts/release.mjs extension",
"screenshot": "bun run scripts/screenshot-antipatterns.js",
"og-image": "bun run scripts/generate-og-image.js"
"og-image": "bun run scripts/generate-og-image.js",
"world-cards": "node scripts/generate-world-cards.mjs",
"world-cards:publish": "node scripts/publish-world-cards.mjs"
},
"dependencies": {
"css-select": "^7.0.0",
+3 -1
View File
@@ -633,6 +633,8 @@ function generateCFConfig(buildDir) {
/docs/teach /docs/init 301
/anti-patterns /slop#catalog 301
/visual-mode /slop#see-it 301
/worlds /labs/worlds 301
/worlds/stagings /labs/stagings 301
/neon-mirai /neo-mirai/ 301
/neon-mirai/ /neo-mirai/ 301
/cases/neon-mirai /cases/neo-mirai 301
@@ -644,7 +646,7 @@ function generateCFConfig(buildDir) {
// Without this, the SPA fallback serves index.html for function routes
const routes = {
version: 1,
include: ['/api/download/*'],
include: ['/api/download/*', '/worlds/cards/*'],
exclude: [],
};
fs.writeFileSync(path.join(buildDir, '_routes.json'), JSON.stringify(routes, null, 2));
+353
View File
@@ -0,0 +1,353 @@
#!/usr/bin/env node
/**
* Generate design-system translation cards for the concept world catalog.
*
* Each card renders one world as the web design system it implies: palette
* swatches and material chips, a type specimen, core UI components, and one
* small screen composition. The prompt is built from the concept's authored
* Palette/material and Type/composition rules, so the card tests exactly the
* translation the catalog claims.
*
* Output: site/public/worlds/cards/<concept-id>.webp (16:9, 1K, WebP) plus
* manifest.json mapping id -> { hash, generatedAt }. hash is the concept's
* content hash (same fingerprint reviews pin), so an edited concept marks its
* card stale and a rerun regenerates only what changed.
*
* Two image kinds per concept:
* board (<id>.webp): the design-system specimen sheet (palette, type,
* components, phone composition).
* hero (<id>-hero.webp): a full-frame desktop landing page composed in the
* world's system; the 16:9 frame IS the viewport, proving the world holds
* at desktop scale.
*
* Usage:
* node scripts/generate-world-cards.mjs # all stale/missing
* node scripts/generate-world-cards.mjs --kind hero # board | hero | both
* node scripts/generate-world-cards.mjs --only <id[,id]> # specific concepts
* node scripts/generate-world-cards.mjs --limit 5 # first N stale jobs
* node scripts/generate-world-cards.mjs --force # ignore manifest
* node scripts/generate-world-cards.mjs --model pro # lite | pro | gpt
* node scripts/generate-world-cards.mjs --out <dir> # alternate output dir
*/
import { GoogleGenAI } from '@google/genai';
import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import sharp from 'sharp';
import { conceptContentHash, readConceptCatalog } from '../skill/scripts/lib/concept-catalog.mjs';
import { compositionContentHash, readCompositionCatalog } from '../skill/scripts/lib/composition-catalog.mjs';
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const CARD_DIR = join(ROOT, 'site', 'public', 'worlds', 'cards');
// gpt (the default) won the human calibration bake-off decisively; lite and
// pro remain for future comparisons.
const MODELS = {
lite: { kind: 'gemini', id: 'models/gemini-3.1-flash-lite-image', imageSize: '1K' },
pro: { kind: 'gemini', id: 'models/gemini-3-pro-image', imageSize: '2K' },
gpt: { kind: 'openai', id: 'gpt-image-2', size: '2048x1152', quality: 'high' },
};
const DEFAULT_MODEL = 'gpt';
const CONCURRENCY = 6;
const MAX_ATTEMPTS = 4;
function loadEnv() {
if (process.env.GEMINI_API_KEY || process.env.GOOGLE_CLOUD_API_KEY) return;
const envPath = join(ROOT, '.env');
if (!existsSync(envPath)) return;
for (const line of readFileSync(envPath, 'utf8').split('\n')) {
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
if (match && !process.env[match[1]]) process.env[match[1]] = match[2].replace(/^"|"$/g, '');
}
}
function boardTitle(concept) {
return concept.form
.split(',')[0]
.replace(/^(a|an|the)\s+/i, '')
.replace(/\b\w/g, letter => letter.toUpperCase());
}
function buildHeroPrompt(concept) {
const palette = concept.system[0].replace(/^Palette\/material:\s*/, '');
const type = concept.system[1].replace(/^Type\/composition:\s*/, '');
return `A complete desktop landing page filling the entire 16:9 frame edge to edge, as if screenshotted at 1440 pixels wide: a slim top navigation with a wordmark and a few links, a hero section with a large headline, one supporting line, and a primary call-to-action button, and the top of the next content section just visible at the bottom edge. No browser chrome, no device mockup, no specimen-board framing, no caption: the page itself is the entire image.
The page is designed wholly inside this visual world: ${concept.form}.
Palette and materials: ${palette}
Typography and composition: ${type}
Atmosphere, never written on the page but carried into it: ${concept.spark}
Light the page like the world: its hour, light quality, and mood shape the page ground, surfaces, and imagery, not just the accent color. A nocturnal or interior world produces a genuinely dark, atmospherically lit page.
Invent a plausible fictional product or brand this world would naturally serve and write short realistic copy for it (plain punctuation, never an em dash): an invented name that reuses no brand, label, designer, or place name from the world description, a headline of at most eight words, one supporting sentence, and button labels. The result must read as a real, current, award-caliber website built from this world's laws: disciplined grid, aligned edges, believable interface details, generous intentional spacing. Not a poster, not a pastiche, a landing page.
The world must live in the interface itself, not only in imagery: the navigation, buttons, cards, dividers, and type voices are built from the world's materials, colors, textures, and lettering traditions. A photograph or illustration may appear as content, but a generic clean website wearing a themed hero image is a failure; a stranger shown only the page footer or a single button should still recognize the world.
Craft rules, all mandatory: every piece of interface copy is English, even when the world is Japanese, Arabic, or otherwise non-Latin in origin; at most one small non-Latin glyph may appear as a decorative motif, never in the wordmark, navigation, headlines, or body text, and when in doubt use none. Rendered materials must read premium and physically plausible: real metal, wood, paper, and glass with honest light, never plasticky gradient fakes of them. This is a product or editorial website, not a video-game HUD: no health bars, minimaps, or game-menu chrome unless the world itself is a game interface; when the world is screen- or game-born, art-direct it to contemporary award standard, never dated chrome. Vary the composition beyond the world's single most famous motif and refuse AI-cliche renderings of it (Matrix-style glyph rain, red recording dots); use the world's wider grammar.`;
}
function buildPrompt(concept) {
const palette = concept.system[0].replace(/^Palette\/material:\s*/, '');
const type = concept.system[1].replace(/^Type\/composition:\s*/, '');
return `A single flat design-system specimen board, the kind a design studio produces to prove a visual world translates into web and app design. The board is titled "${boardTitle(concept)}" and fills the whole image edge to edge: no desk, wall, binder clips, pins, or drop shadows around it.
The visual world being translated: ${concept.form}.
Mood reference for art direction only: ${concept.spark}
Board sections, left to right:
1. COLOR & MATERIAL column: five to seven labeled palette swatches plus two or three material texture chips, derived strictly from: ${palette}.
2. TYPOGRAPHY panel: a large display headline specimen and a short body-text block obeying: ${type}.
3. COMPONENTS panel: a primary button, a secondary button, a text input, one content card, and a small navigation bar, all styled by this exact system, with normal and active states.
4. One small phone-screen composition in the corner showing the system as a real app or landing screen.
The board ground and dividers take their tone from the world's palette. Precise, crisp, flat graphic rendering like a printed specimen sheet; not a photograph or illustration of the world itself, only the interface system it yields. The only text on the board is the title, short section labels, and brief invented specimen words; never transcribe any sentence from these instructions onto the board.`;
}
async function extractImage(interaction) {
if (interaction?.output_image?.data) return Buffer.from(interaction.output_image.data, 'base64');
for (const step of interaction?.steps || []) {
if (step.type !== 'model_output' || !step.content) continue;
for (const part of step.content) {
if (part.type === 'image' && part.data) return Buffer.from(part.data, 'base64');
}
}
return null;
}
// Sequence-heavy families render two consecutive beats as a split frame; a
// still image cannot show motion any other honest way.
const SEQUENCE_FAMILIES = new Set(['narrative-scroll', 'reveal-transform', 'interaction-physics']);
const MODE_FRAMES = {
persuade: 'an extraordinary marketing page at desktop scale',
operate: 'an extraordinary application workspace at desktop scale, real work visibly in progress',
read: 'an extraordinary documentation or long-form reading page at desktop scale, structure visibly serving comprehension',
experience: 'an extraordinary portfolio or showcase surface at desktop scale, the work itself owning the frame',
};
function buildStagingPrompt(composition) {
const modeFrame = MODE_FRAMES[composition.surface] || MODE_FRAMES.persuade;
const hierarchy = composition.grammar[0].replace(/^Staging\/hierarchy:\s*/, '');
const sequence = composition.grammar[1].replace(/^Sequence\/attention:\s*/, '');
const controls = composition.grammar[2].replace(/^Controls\/state:\s*/, '');
const capture = SEQUENCE_FAMILIES.has(composition.familyId)
? 'Render two consecutive beats of the same page as one hard-cut split frame, a thin seam dividing left and right halves like film frames seconds apart. The staging visibly advances between the beats: something mid-reveal on the left has landed on the right, attention has moved, state has changed.'
: composition.familyId === 'first-viewport'
? 'Render one cinematic frame of the opening screen at the most dramatic moment of its signature staging.'
: 'Render one cinematic frame of the page caught mid-sequence, never its resting opening state: elements between states, partial reveals, momentum readable in the composition itself.';
const copy = composition.surface === 'operate'
? 'Invent a plausible product with short realistic English copy (plain punctuation, never an em dash): an invented name and real-looking task content, labels, and data, never placeholder text.'
: 'Invent a plausible product with short realistic English copy (plain punctuation, never an em dash): an invented name, a headline of at most eight words, one supporting line, one committed action.';
return `${capture} The subject: ${modeFrame}, filling the entire 16:9 frame edge to edge. No browser chrome, no device mockup, no board framing, no caption.
The staging: ${composition.form}
What owns the frame: ${hierarchy}
The motion to make visible: ${sequence}
One interaction in play: ${controls}. Show exactly one control or element mid-gesture with its state visibly changing: a cursor in contact, a drag mid-flight, a value mid-update. Everything else holds still.
This must read as interactive, dynamic, fully realized web design that raises the bar: monumental scale contrast, fearless negative space, editorial typographic confidence, real art direction discipline, and web technology pushed as far as the staging implies (${composition.webLeverage}). Choose a product domain the staging serves best; when it is technical or game-adjacent, art-direct it to contemporary award standard, never dated game chrome or generic sci-fi mission styling, and never lean on cliche AI-generated motifs like recording indicators. Identity stays quiet so the staging is the star: a monochrome ground, one accent color, one strong typeface family, wielded with award-winning conviction, never as a wireframe. Only the elements this staging requires exist on the page: no toolbars, settings chrome, feature grids, or decorative widgets beyond the one interaction in play.
${copy} Never transcribe these instructions onto the image. The viewer should gasp at the design first, then understand the mechanism from what the frame implies.`;
}
function promptFor(concept, kind) {
if (kind === 'staging') return buildStagingPrompt(concept);
return kind === 'hero' ? buildHeroPrompt(concept) : buildPrompt(concept);
}
async function generateGemini(ai, model, concept, kind) {
const interaction = await ai.interactions.create({
model: model.id,
input: promptFor(concept, kind),
generation_config: {
temperature: 1,
top_p: 0.95,
thinking_level: 'low',
image_config: { image_size: model.imageSize, aspect_ratio: '16:9' },
},
response_modalities: ['image', 'text'],
});
return extractImage(interaction);
}
// Set by main() so hero generation can pick up the concept's board as a
// binding style reference (the board/hero pair must read as one system).
let BOARD_REFERENCE_DIR = null;
async function generateOpenAI(model, concept, kind) {
// Heroes are generated from the specimen board when it exists: the edits
// endpoint takes the board as reference so both images share one system.
if (kind === 'hero' && BOARD_REFERENCE_DIR) {
const boardPath = join(BOARD_REFERENCE_DIR, `${concept.id}.webp`);
if (existsSync(boardPath)) {
try {
const form = new FormData();
form.append('model', model.id);
form.append('image[]', new Blob([readFileSync(boardPath)], { type: 'image/webp' }), `${concept.id}.webp`);
form.append('prompt', `The attached image is this world's design-system specimen board. Treat its palette, materials, type voices, and component grammar as binding reference; the page you render must read as the same system. ${promptFor(concept, kind)}`);
form.append('size', model.size);
form.append('quality', model.quality);
const response = await fetch('https://api.openai.com/v1/images/edits', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: form,
});
const json = await response.json();
if (!response.ok) throw new Error(json.error?.message || `HTTP ${response.status}`);
const b64 = json.data?.[0]?.b64_json;
if (b64) return Buffer.from(b64, 'base64');
} catch (error) {
process.stderr.write(` board-reference hero failed for ${concept.id} (${error.message}); falling back to plain generation\n`);
}
}
}
const response = await fetch('https://api.openai.com/v1/images/generations', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: model.id,
prompt: promptFor(concept, kind),
size: model.size,
quality: model.quality,
}),
});
const json = await response.json();
if (!response.ok) throw new Error(json.error?.message || `HTTP ${response.status}`);
const b64 = json.data?.[0]?.b64_json;
return b64 ? Buffer.from(b64, 'base64') : null;
}
async function generateCard(ai, model, concept, kind) {
let lastError;
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt += 1) {
try {
const raw = model.kind === 'openai'
? await generateOpenAI(model, concept, kind)
: await generateGemini(ai, model, concept, kind);
if (!raw) throw new Error('no image part in response');
return await sharp(raw).webp({ quality: 90 }).toBuffer();
} catch (error) {
lastError = error;
const wait = attempt * 8000;
process.stderr.write(` retry ${concept.id} (${attempt}/${MAX_ATTEMPTS}): ${error.message}\n`);
await new Promise(resolve => setTimeout(resolve, wait));
}
}
throw lastError;
}
async function main() {
loadEnv();
const args = process.argv.slice(2);
const modelKey = args.includes('--model') ? args[args.indexOf('--model') + 1] : DEFAULT_MODEL;
const model = MODELS[modelKey];
if (!model) throw new Error(`unknown --model ${modelKey}; use ${Object.keys(MODELS).join(', ')}`);
const outDir = args.includes('--out') ? args[args.indexOf('--out') + 1] : CARD_DIR;
const manifestPath = join(outDir, 'manifest.json');
const only = args.includes('--only') ? args[args.indexOf('--only') + 1].split(',').filter(Boolean) : null;
const limit = args.includes('--limit') ? Number(args[args.indexOf('--limit') + 1]) : Infinity;
const force = args.includes('--force');
const kindArg = args.includes('--kind') ? args[args.indexOf('--kind') + 1] : 'both';
if (!['board', 'hero', 'both', 'staging'].includes(kindArg)) throw new Error(`unknown --kind ${kindArg}; use board, hero, both, staging`);
const kinds = kindArg === 'both' ? ['board', 'hero'] : [kindArg];
const isStaging = kindArg === 'staging';
const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_CLOUD_API_KEY;
if (model.kind === 'gemini' && !geminiKey) throw new Error('set GEMINI_API_KEY or GOOGLE_CLOUD_API_KEY (repo .env)');
if (model.kind === 'openai' && !process.env.OPENAI_API_KEY) throw new Error('set OPENAI_API_KEY (repo .env)');
const ai = model.kind === 'gemini' ? new GoogleGenAI({ apiKey: geminiKey }) : null;
// Staging images render composition-catalog entries; boards and heroes
// render world-catalog concepts. Both share the manifest and output dir.
const concepts = isStaging
? readCompositionCatalog(
join(ROOT, 'skill', 'scripts', 'composition-ingredients.json'),
join(ROOT, 'skill', 'scripts', 'composition-reviews.json')
).compositions
: readConceptCatalog(
join(ROOT, 'skill', 'scripts', 'concept-ingredients.json'),
join(ROOT, 'skill', 'scripts', 'concept-reviews.json')
).concepts;
const hashOf = isStaging ? compositionContentHash : conceptContentHash;
mkdirSync(outDir, { recursive: true });
BOARD_REFERENCE_DIR = outDir;
const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, 'utf8')) : {};
const writeManifest = () => writeFileSync(
manifestPath,
`${JSON.stringify(Object.fromEntries(Object.entries(manifest).sort(([a], [b]) => a.localeCompare(b))), null, 2)}\n`
);
const fileFor = (concept, kind) => join(outDir, `${concept.id}${kind === 'hero' ? '-hero' : ''}.webp`);
const stampKey = kind => (kind === 'hero' ? 'heroGeneratedAt' : 'generatedAt');
const queue = [];
for (const concept of concepts) {
if (only && !only.includes(concept.id)) continue;
const hash = hashOf(concept);
const row = manifest[concept.id];
for (const kind of kinds) {
const needed = force
|| !existsSync(fileFor(concept, kind))
|| row?.hash !== hash
|| !row?.[stampKey(kind)];
if (needed || only) queue.push({ concept, kind });
}
}
queue.splice(limit);
if (only) {
const found = new Set(queue.map(job => job.concept.id));
const missing = only.filter(id => !found.has(id));
if (missing.length) throw new Error(`concept not found: ${missing.join(', ')}`);
}
process.stdout.write(`generating ${queue.length} images (${kinds.join('+')}) across ${concepts.length} concepts with ${modelKey} -> ${outDir}\n`);
let done = 0;
let failed = 0;
const worker = async () => {
while (queue.length > 0) {
const { concept, kind } = queue.shift();
try {
const webp = await generateCard(ai, model, concept, kind);
writeFileSync(fileFor(concept, kind), webp);
const row = manifest[concept.id] || {};
row.hash = hashOf(concept);
row.model = modelKey;
row[stampKey(kind)] = new Date().toISOString();
manifest[concept.id] = row;
writeManifest();
done += 1;
process.stdout.write(` [${done}] ${concept.id} ${kind} (${Math.round(webp.length / 1024)}KB)\n`);
} catch (error) {
failed += 1;
process.stderr.write(` FAILED ${concept.id} ${kind}: ${error.message}\n`);
}
}
};
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
// Prune manifest entries for ids that left both catalogs (they share the
// manifest, so pruning must check the union).
const worldIds = readConceptCatalog(
join(ROOT, 'skill', 'scripts', 'concept-ingredients.json'),
join(ROOT, 'skill', 'scripts', 'concept-reviews.json')
).concepts.map(concept => concept.id);
const stagingIds = readCompositionCatalog(
join(ROOT, 'skill', 'scripts', 'composition-ingredients.json'),
join(ROOT, 'skill', 'scripts', 'composition-reviews.json')
).compositions.map(composition => composition.id);
const liveIds = new Set([...worldIds, ...stagingIds]);
for (const id of Object.keys(manifest)) {
if (!liveIds.has(id)) delete manifest[id];
}
writeManifest();
process.stdout.write(`done: ${done} generated, ${failed} failed\n`);
if (failed > 0) process.exitCode = 1;
}
main().catch(error => {
process.stderr.write(`${error.message}\n`);
process.exit(1);
});
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env node
/**
* Upload world design-system cards to the R2 bucket that backs
* functions/worlds/cards/[[file]].js. Skips files already uploaded at their
* current generation (tracked in a local .published.json sidecar), so routine
* runs only push what changed. Requires an authenticated wrangler (same auth
* as `bun run deploy`) and the bucket:
* wrangler r2 bucket create impeccable-world-cards
*
* Usage:
* bun run world-cards:publish # changed files only
* bun run world-cards:publish -- --force # everything
*/
import { execFile } from 'node:child_process';
import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
const run = promisify(execFile);
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
const CARD_DIR = join(ROOT, 'site', 'public', 'worlds', 'cards');
const STATE_PATH = join(CARD_DIR, '.published.json');
const BUCKET = 'impeccable-world-cards';
const CONCURRENCY = 6;
const force = process.argv.includes('--force');
const manifest = JSON.parse(readFileSync(join(CARD_DIR, 'manifest.json'), 'utf8'));
const state = existsSync(STATE_PATH) ? JSON.parse(readFileSync(STATE_PATH, 'utf8')) : {};
const files = readdirSync(CARD_DIR).filter(file => file.endsWith('.webp'));
const queue = files.filter(file => {
if (force) return true;
const id = file.replace(/(-hero)?\.webp$/, '');
const stamp = file.endsWith('-hero.webp') ? manifest[id]?.heroGeneratedAt : manifest[id]?.generatedAt;
return !stamp || state[file] !== stamp;
});
console.log(`publishing ${queue.length} of ${files.length} cards to r2://${BUCKET}`);
let done = 0;
let failed = 0;
async function upload(file) {
const contentType = 'image/webp';
await run('wrangler', [
'r2', 'object', 'put', `${BUCKET}/${file}`,
'--file', join(CARD_DIR, file),
'--content-type', contentType,
'--remote',
], { cwd: ROOT });
}
const worker = async () => {
while (queue.length > 0) {
const file = queue.shift();
try {
await upload(file);
const id = file.replace(/(-hero)?\.webp$/, '');
state[file] = (file.endsWith('-hero.webp') ? manifest[id]?.heroGeneratedAt : manifest[id]?.generatedAt) || new Date().toISOString();
writeFileSync(STATE_PATH, `${JSON.stringify(state, null, 2)}\n`);
done += 1;
if (done % 25 === 0 || queue.length === 0) console.log(` ${done} uploaded, ${queue.length} remaining`);
} catch (error) {
failed += 1;
console.error(` FAILED ${file}: ${error.message}`);
}
}
};
await Promise.all(Array.from({ length: CONCURRENCY }, worker));
try {
await run('wrangler', [
'r2', 'object', 'put', `${BUCKET}/manifest.json`,
'--file', join(CARD_DIR, 'manifest.json'),
'--content-type', 'application/json',
'--remote',
], { cwd: ROOT });
console.log(' manifest.json uploaded');
} catch (error) {
failed += 1;
console.error(` FAILED manifest.json: ${error.message}`);
}
console.log(`done: ${done} uploaded, ${failed} failed`);
if (failed > 0) process.exitCode = 1;
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env node
// World cards are served from R2 by functions/worlds/cards/[[file]].js.
// Astro copies site/public/worlds/cards (local generation output) into the
// static build; this strips it so deploys stay light and the Function owns
// the route. Runs as part of `bun run build`.
import { rmSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const buildCards = join(dirname(fileURLToPath(import.meta.url)), '..', 'build', 'worlds', 'cards');
if (existsSync(buildCards)) {
rmSync(buildCards, { recursive: true });
console.log('✓ Stripped local world cards from build output (served from R2)');
}
+3 -2
View File
@@ -25,11 +25,11 @@ export const SUITES = {
triggers: [
...COMMON_INFRA_PATTERNS,
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|concept-ingredients|concept-reviews|concept-seed|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/(concept-catalog|provider|surface-briefs|target-slug)|pin|surface-brief|validate-concept-catalog))/,
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|composition-ingredients|composition-reviews|concept-ingredients|concept-reviews|concept-seed|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/(composition-catalog|concept-catalog|provider|surface-briefs|target-slug)|pin|surface-brief|validate-concept-catalog))/,
/^site\/(pages|content|components|layouts)\//,
/^README(\.npm)?\.md$/,
/^cli\/bin\//,
/^tests\/(build|cleanup-deprecated|cli-ignores|concept-seed|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|openai-plugin|pin|shiki-theme|skills-cli|slop-catalog|surface-brief|target-args|test-suites|theme|windows-path-fix|worlds-review-vite-plugin|zip)\.test\.(js|mjs)$/,
/^tests\/(build|cleanup-deprecated|cli-ignores|concept-seed|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|live-ui-lab|openai-plugin|pin|shiki-theme|skills-cli|slop-catalog|surface-brief|target-args|test-suites|theme|windows-path-fix|worlds-review-vite-plugin|zip)\.test\.(js|mjs)$/,
/^tests\/lib\//,
],
commands: [
@@ -63,6 +63,7 @@ export const SUITES = {
'tests/hook-build.test.mjs',
'tests/hook.test.mjs',
'tests/impeccable-paths.test.mjs',
'tests/live-ui-lab.test.mjs',
'tests/openai-plugin.test.mjs',
'tests/pin.test.mjs',
'tests/target-args.test.mjs',
+158 -4
View File
@@ -1,5 +1,18 @@
import { readFile, rename, writeFile } from 'node:fs/promises';
import path from 'node:path';
import {
conceptContentHash,
CONCEPT_STRENGTHS,
normalizeConceptForm,
SYSTEM_PREFIXES,
validateConceptCatalog,
validateConceptEntry,
WELL_TIERS,
} from '../skill/scripts/lib/concept-catalog.mjs';
import {
compositionContentHash,
validateCompositionCatalog,
} from '../skill/scripts/lib/composition-catalog.mjs';
const API_PATH = '/__impeccable/worlds';
const MAX_BODY_BYTES = 64 * 1024;
@@ -51,18 +64,84 @@ function findConcept(catalog, id) {
return null;
}
function assertApprovedFloor(catalog, reviewData) {
const approvedIds = new Set(
Object.entries(reviewData.reviews)
.filter(([, review]) => review.status === 'approved')
.map(([id]) => id)
);
const tierByWell = new Map((catalog.wells || []).map(well => [well.id, well.tier]));
const approvedTiers = new Set(
catalog.families
.filter(family => family.concepts.some(concept => approvedIds.has(concept.id)))
.map(family => tierByWell.get(family.well))
.filter(tier => WELL_TIERS.includes(tier))
);
if (approvedIds.size < 3 || approvedTiers.size < WELL_TIERS.length) {
throw new Error('At least three approved concepts covering every challenger tier must remain available to the challenger');
}
}
function assertValidCatalog(catalog, reviewData) {
const { errors } = validateConceptCatalog(catalog, reviewData, { requireApprovedMinimum: false });
if (errors.length > 0) throw new Error(errors[0]);
}
function validateTags(tags) {
return Array.isArray(tags)
&& tags.length === 3
&& tags.every(tag => typeof tag === 'string' && tag.trim().length >= 2 && tag.trim().length <= 40);
}
function validateSystem(system) {
return Array.isArray(system)
&& system.length === SYSTEM_PREFIXES.length
&& system.every((rule, index) =>
typeof rule === 'string'
&& rule.trim().length >= 12
&& rule.trim().length <= 180
&& rule.trim().startsWith(SYSTEM_PREFIXES[index])
);
}
export function worldsReviewPlugin({ root = process.cwd() } = {}) {
const catalogPath = path.join(root, 'skill', 'scripts', 'concept-ingredients.json');
const reviewsPath = path.join(root, 'skill', 'scripts', 'concept-reviews.json');
const compositionCatalogPath = path.join(root, 'skill', 'scripts', 'composition-ingredients.json');
const compositionReviewsPath = path.join(root, 'skill', 'scripts', 'composition-reviews.json');
let mutationQueue = Promise.resolve();
// Composition-catalog reviews share the review mechanics but none of the
// world catalog's floors or editing; v1 supports the review action only.
async function mutateComposition(body) {
if (body.action !== 'review') throw new Error('Compositions support the review action only');
if (!REVIEW_STATUSES.has(body.status)) throw new Error('Review status is invalid');
const note = typeof body.note === 'string' ? body.note.trim() : '';
if (note.length > 500) throw new Error('Review note must be 500 characters or fewer');
const catalog = await readJson(compositionCatalogPath);
const reviewData = await readJson(compositionReviewsPath);
const entry = (catalog.compositions || []).find(composition => composition.id === body.id);
if (!entry) throw new Error('Composition was not found');
if (body.status === 'pending') {
delete reviewData.reviews[body.id];
} else {
reviewData.reviews[body.id] = {
status: body.status,
reviewedBy: 'pbakaus',
reviewedAt: new Date().toISOString(),
formHash: compositionContentHash(entry),
...(note ? { note } : {}),
};
}
reviewData.reviews = Object.fromEntries(Object.entries(reviewData.reviews).sort(([a], [b]) => a.localeCompare(b)));
const { errors } = validateCompositionCatalog(catalog, reviewData);
if (errors.length > 0) throw new Error(errors[0]);
await writeJsonAtomic(compositionReviewsPath, reviewData);
return { id: body.id, status: body.status, review: reviewData.reviews[body.id] || null };
}
async function mutate(body) {
if (body.catalog === 'compositions') return mutateComposition(body);
const catalog = await readJson(catalogPath);
const reviewData = await readJson(reviewsPath);
const match = findConcept(catalog, body.id);
@@ -70,6 +149,16 @@ export function worldsReviewPlugin({ root = process.cwd() } = {}) {
if (body.action === 'review') {
if (!REVIEW_STATUSES.has(body.status)) throw new Error('Review status is invalid');
// Composition strength is a routing verdict, not an approvable type:
// every composition-typed world entry the reviewer processed was
// rejected, and approved stagings live in the composition catalog.
if (body.status === 'approved' && match.concept.strength === 'composition') {
throw new Error('Stagings live in the composition catalog; reject it here and it joins the mining queue');
}
const note = typeof body.note === 'string' ? body.note.trim() : '';
if (note.length > 500) throw new Error('Review note must be 500 characters or fewer');
const previousStatus = reviewData.reviews[body.id]?.status || 'pending';
const previousRating = reviewData.reviews[body.id]?.rating;
if (body.status === 'pending') {
delete reviewData.reviews[body.id];
} else {
@@ -77,22 +166,61 @@ export function worldsReviewPlugin({ root = process.cwd() } = {}) {
status: body.status,
reviewedBy: 'pbakaus',
reviewedAt: new Date().toISOString(),
formHash: conceptContentHash(match.concept),
...(note ? { note } : {}),
...(body.status === 'approved' && [1, 2, 3].includes(previousRating) ? { rating: previousRating } : {}),
};
}
if (previousStatus === 'approved' && body.status !== 'approved') assertApprovedFloor(catalog, reviewData);
reviewData.reviews = Object.fromEntries(Object.entries(reviewData.reviews).sort(([a], [b]) => a.localeCompare(b)));
assertValidCatalog(catalog, reviewData);
await writeJsonAtomic(reviewsPath, reviewData);
return { id: body.id, status: body.status, review: reviewData.reviews[body.id] || null };
}
if (body.action === 'strength') {
// Strength is curation metadata outside the content hash, so retyping a
// concept never invalidates its human review.
if (!CONCEPT_STRENGTHS.has(body.strength)) throw new Error('Strength must be world, composition, or dual');
match.concept.strength = body.strength;
catalog.catalogVersion = new Date().toISOString();
assertValidCatalog(catalog, reviewData);
await writeJsonAtomic(catalogPath, catalog);
return { id: body.id, strength: body.strength, catalogVersion: catalog.catalogVersion };
}
if (body.action === 'rate') {
// Rating grades an approved concept's strength (3 exceptional, 2 solid,
// 1 marginal keep) as a calibration signal. It lives on the review but
// stays outside the content hash and never changes status.
const review = reviewData.reviews[body.id];
if (review?.status !== 'approved') throw new Error('Rating only applies to approved concepts');
if (body.rating === null) {
delete review.rating;
} else if ([1, 2, 3].includes(body.rating)) {
review.rating = body.rating;
} else {
throw new Error('Rating must be 1, 2, or 3');
}
assertValidCatalog(catalog, reviewData);
await writeJsonAtomic(reviewsPath, reviewData);
return { id: body.id, rating: review.rating ?? null, review };
}
if (body.action === 'update') {
const form = typeof body.form === 'string' ? body.form.trim() : '';
const lineage = typeof body.lineage === 'string' ? body.lineage.trim() : '';
const targetFamily = catalog.families.find(family => family.id === body.familyId);
if (form.length < 12 || form.length > 600 || !form.includes(',')) {
throw new Error('Form must be 12600 characters and include inherited structure after a comma');
if (form.length < 40 || form.length > 360 || !form.includes(',')) {
throw new Error('Form must be 40360 characters and include inherited structure after a comma');
}
if (lineage.length < 2 || lineage.length > 160) throw new Error('Lineage must be 2160 characters');
if (lineage.length < 12 || lineage.length > 200) throw new Error('Lineage must be 12200 characters');
if (!validateTags(body.tags)) throw new Error('Exactly three structural tags are required');
if (!validateSystem(body.system)) throw new Error('Exactly five system grammar rules of 12180 characters are required');
const spark = typeof body.spark === 'string' ? body.spark.trim() : '';
if (spark.length < 80 || spark.length > 320) throw new Error('Creative spark must be 80320 characters');
const webLeverage = typeof body.webLeverage === 'string' ? body.webLeverage.trim() : '';
if (webLeverage.length < 20 || webLeverage.length > 240) throw new Error('Web leverage must be 20240 characters');
if (!targetFamily) throw new Error('Family was not found');
const updated = {
@@ -100,7 +228,19 @@ export function worldsReviewPlugin({ root = process.cwd() } = {}) {
form,
lineage,
tags: body.tags.map(tag => tag.trim()),
system: body.system.map(rule => rule.trim()),
spark,
webLeverage,
};
const existingForms = new Map();
for (const family of catalog.families) {
for (const concept of family.concepts) {
if (concept.id === body.id) continue;
existingForms.set(normalizeConceptForm(concept.form), concept.id);
}
}
const entryErrors = validateConceptEntry(updated, { existingForms });
if (entryErrors.length > 0) throw new Error(entryErrors[0]);
if (targetFamily.id === match.family.id) {
match.family.concepts[match.index] = updated;
} else {
@@ -109,8 +249,22 @@ export function worldsReviewPlugin({ root = process.cwd() } = {}) {
targetFamily.concepts.sort((a, b) => a.id.localeCompare(b.id));
}
catalog.catalogVersion = new Date().toISOString();
const previousStatus = reviewData.reviews[body.id]?.status || 'pending';
if (reviewData.reviews[body.id]) {
delete reviewData.reviews[body.id];
reviewData.reviews = Object.fromEntries(Object.entries(reviewData.reviews).sort(([a], [b]) => a.localeCompare(b)));
}
if (previousStatus === 'approved') assertApprovedFloor(catalog, reviewData);
assertValidCatalog(catalog, reviewData);
await writeJsonAtomic(catalogPath, catalog);
return { id: body.id, concept: updated, familyId: targetFamily.id };
if (previousStatus !== 'pending') await writeJsonAtomic(reviewsPath, reviewData);
return {
id: body.id,
concept: updated,
familyId: targetFamily.id,
status: 'pending',
catalogVersion: catalog.catalogVersion,
};
}
throw new Error('Action is invalid');
+155
View File
@@ -0,0 +1,155 @@
---
import '../styles/live-ui-gallery.css';
import { LIVE_COMMANDS } from '../../skill/scripts/live/vocabulary.mjs';
import { LIVE_UI_SURFACES } from '../../skill/scripts/live/ui-core.mjs';
const STATE_GROUPS = [
{
key: 'global-chrome',
label: 'Global chrome',
summary: 'Session status, page-wide tools, and steering.',
states: [
['global-ready', 'Ready', 'global-bottom-bar'],
['global-disconnected', 'Agent disconnected', 'global-bottom-bar'],
['global-foreground', 'Codex CLI fallback', 'global-bottom-bar'],
['global-tools', 'Detect + DESIGN.md', 'global-bottom-bar'],
['steer-expanded', 'Steer composing', 'global-bottom-bar'],
['steer-processing', 'Steer processing', 'global-bottom-bar'],
],
},
{
key: 'selection',
label: 'Selection',
summary: 'Target, annotate, and insert before generation.',
states: [
['configure-replace', 'Configure selection', 'element-selection-chrome'],
['action-picker', 'Action picker', 'action-picker'],
['configure-listening', 'Voice input', 'element-selection-chrome'],
['configure-locked', 'Apply locked', 'element-selection-chrome'],
['annotation', 'Annotations', 'annotation-chrome'],
['insert-placeholder', 'Insert placeholder', 'insert-mode-chrome'],
],
},
{
key: 'generation-review',
label: 'Generation + review',
summary: 'Progressive arrival, tuning, application, and confirmation.',
states: [
['generating', 'Generating', 'generating-row'],
['generation-recovery', 'Recovery', 'generating-row'],
['cycling-progressive', 'Variant 1 arrived', 'variant-cycling-row'],
['cycling-second', 'Variant 2 arrived', 'variant-cycling-row'],
['tune-open', 'Tune panel', 'variant-params-panel'],
['applying', 'Applying variant', 'saving-confirmed-rows'],
['confirmed', 'Variant applied', 'saving-confirmed-rows'],
],
},
{
key: 'copy-support',
label: 'Copy + support',
summary: 'Inline copy edits, design context, and recovery feedback.',
states: [
['edit-copy', 'Edit copy', 'edit-chrome'],
['copy-pending', 'Copy edits pending', 'pending-copy-edit-dock'],
['copy-applying', 'Copy edits applying', 'pending-copy-edit-dock'],
['copy-attention', 'Apply needs attention', 'pending-copy-edit-dock'],
['design-panel', 'DESIGN.md panel', 'design-system-panel'],
['toast-error', 'Error toast', 'toasts-and-errors'],
],
},
];
// Keep the catalog honest as Live grows. CSS isolation is a structural mount
// contract rather than a visible state; every other surface must own at least
// one snapshot here or the site build fails with the missing surface name.
const STRUCTURAL_SURFACES = new Set(['css-isolation-boundary']);
const coveredSurfaces = new Set(
STATE_GROUPS.flatMap((group) => group.states.map(([, , surface]) => surface)),
);
const visualSurfaces = LIVE_UI_SURFACES.filter((surface) => !STRUCTURAL_SURFACES.has(surface.key));
const uncoveredSurfaces = visualSurfaces.filter((surface) => !coveredSurfaces.has(surface.key));
if (uncoveredSurfaces.length > 0) {
throw new Error(`Live UI lab is missing snapshots for: ${uncoveredSurfaces.map((surface) => surface.key).join(', ')}`);
}
const statePayload = STATE_GROUPS.flatMap((group) =>
group.states.map(([key, label, surface]) => ({
key,
label,
surface,
groupKey: group.key,
group: group.label,
groupSummary: group.summary,
})),
);
const groupPayload = STATE_GROUPS.map((group) => ({
key: group.key,
label: group.label,
summary: group.summary,
states: statePayload.filter((state) => state.groupKey === group.key),
}));
const commandPayload = JSON.stringify(LIVE_COMMANDS).replace(/</g, '\\u003c');
const groupsJson = JSON.stringify(groupPayload).replace(/</g, '\\u003c');
---
<!--
Dev-only state harness for Impeccable Live. The chrome mirrors:
- skill/scripts/live-browser.js (standalone browser UI and exact wording)
- skill/scripts/live/ui-core.mjs (surface/state inventory)
- skill/scripts/live/vocabulary.mjs (canonical command labels and SVGs)
Production Live deliberately uses the same dark Neo Kinpaku chrome on light
and dark host pages (barPaletteForTheme, live-browser.js). The host control
therefore changes the canvas behind every state, not the product chrome.
-->
<section class="live-ui-gallery" data-live-ui-gallery data-dev-only="true" aria-label="Live UI state inspector">
<aside class="live-ui-gallery__sidebar">
<header class="live-ui-gallery__sidebar-head">
<span>State catalog</span>
<strong>{statePayload.length} snapshots</strong>
<small>{visualSurfaces.length} visible surfaces</small>
</header>
<nav class="live-ui-gallery__group-nav" aria-label="Live UI workflow groups" aria-keyshortcuts="ArrowUp ArrowDown Home End">
{STATE_GROUPS.map((group, index) => (
<button type="button" data-gallery-group={group.key} aria-current={index === 0 ? 'true' : 'false'}>
<span>{String(index + 1).padStart(2, '0')}</span>
<span>
<strong>{group.label}</strong>
<small>{group.states.length} states</small>
</span>
</button>
))}
</nav>
<footer>
<span>Product rule</span>
<p>Live chrome stays dark. Host context changes around it.</p>
</footer>
</aside>
<div class="live-ui-gallery__workspace">
<header class="live-ui-gallery__toolbar">
<div class="live-ui-gallery__group-readout" aria-live="polite" aria-atomic="true">
<span data-gallery-group-index>Group 01</span>
<h2 data-gallery-group-label>Global chrome</h2>
<p data-gallery-group-summary>Session status, page-wide tools, and steering.</p>
</div>
<div class="live-ui-gallery__host-switch" role="group" aria-label="Preview host theme">
<span>Host</span>
<button type="button" data-gallery-host="light" aria-pressed="true">Light</button>
<button type="button" data-gallery-host="dark" aria-pressed="false">Dark</button>
</div>
</header>
<div class="live-ui-gallery__cluster" data-gallery-cluster role="list" aria-label="Global chrome states"></div>
</div>
<script is:inline type="application/json" data-live-gallery-groups set:html={groupsJson}></script>
<script is:inline type="application/json" data-live-gallery-vocabulary set:html={commandPayload}></script>
</section>
<script>
import '../scripts/live-ui-gallery.js';
</script>
+44
View File
@@ -0,0 +1,44 @@
---
import Base from '../../../layouts/Base.astro';
import LiveUiGallery from '../../../components/LiveUiGallery.astro';
import '../../../styles/live-ui-lab.css';
// Developer-only visual harnesses use /labs/<subject>. The old /live-lab
// route remains as a redirect so bookmarks survive the namespace cleanup.
---
<Base
title="Live UI States | Impeccable Labs"
description="Inspect every visible Impeccable Live surface across its product states on light and dark host pages."
canonicalPath="/labs/live-ui"
bodyClass="live-ui-lab-page"
mainId="live-ui-lab-main"
mainClass="live-ui-lab"
hideHeader
hideFooter
noIndex
>
<header class="live-ui-lab__bar">
<a class="live-ui-lab__brand ks-brand" href="/" aria-label="Impeccable home">
<span class="ks-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"></path>
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"></path>
</svg>
</span>
<span class="ks-wordmark">IMPECCABLE</span>
</a>
<div class="live-ui-lab__heading">
<span>Developer lab</span>
<h1>Live UI states</h1>
</div>
<p>Every visible Live surface, clustered by workflow and host context.</p>
<a class="live-ui-lab__back" href="/live-mode">Live overview <span aria-hidden="true">→</span></a>
</header>
<section class="live-ui-lab__canvas" aria-label="Live UI state catalog">
<LiveUiGallery />
</section>
</Base>
+455
View File
@@ -0,0 +1,455 @@
---
import fs from 'node:fs';
import path from 'node:path';
import Base from '../../layouts/Base.astro';
import '../../styles/sub-pages.css';
import '../../styles/worlds-lab.css';
import { compositionContentHash } from '../../../skill/scripts/lib/composition-catalog.mjs';
const ROOT = process.cwd();
const catalog = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill', 'scripts', 'composition-ingredients.json'), 'utf8'));
const reviewData = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill', 'scripts', 'composition-reviews.json'), 'utf8'));
const reviews = reviewData.reviews || {};
const CARD_DIR = path.join(ROOT, 'site', 'public', 'worlds', 'cards');
const cardManifest = (() => {
try { return JSON.parse(fs.readFileSync(path.join(CARD_DIR, 'manifest.json'), 'utf8')); } catch { return {}; }
})();
const PROD_CARDS_BASE = 'https://impeccable.style/worlds/cards';
const stagingCardFor = composition => {
const row = cardManifest[composition.id];
const localExists = fs.existsSync(path.join(CARD_DIR, `${composition.id}.webp`));
if (!localExists && !row) return null;
const generated = Date.parse(row?.generatedAt || 0) || 0;
const base = localExists ? '/worlds/cards' : PROD_CARDS_BASE;
return {
src: `${base}/${composition.id}.webp?v=${generated.toString(36)}`,
stale: row?.hash !== compositionContentHash(composition),
};
};
const familiesById = new Map(catalog.families.map(family => [family.id, family]));
const compositions = (catalog.compositions || []).map(composition => ({
...composition,
familyLabel: familiesById.get(composition.familyId)?.label || composition.familyId,
status: reviews[composition.id]?.status || 'pending',
review: reviews[composition.id] || null,
card: stagingCardFor(composition),
}));
const counts = {
total: compositions.length,
approved: compositions.filter(item => item.status === 'approved').length,
pending: compositions.filter(item => item.status === 'pending').length,
rejected: compositions.filter(item => item.status === 'rejected').length,
};
const initial = compositions.find(item => item.status === 'pending') || compositions[0] || null;
const pageData = JSON.stringify({ canWrite: import.meta.env.DEV, compositions }).replace(/</g, '\\u003c');
---
<!-- DIRECTION CONTRACT: THESIS: A staging catalog reviewed with the same one-judgment loupe as the world catalog; structure is the only star, so the stage shows a neutral demonstration image beside identity-free grammar. OWN-WORLD: Inherits the Worlds lab wholesale: lacquer, kinpaku, patina state, compact Albert Sans UI. STORY: Pick a pending staging, read its mechanism, see it demonstrated in neutral dress, decide, annotate. FIRST VIEWPORT: Compact queue and one review stage with keyboard-visible decisions. BAR-RAISER: Every staging must survive the wireframe test and any-identity dressing before approval. FORM: The Worlds lab's sibling page, narrower by design: no strength toggle, no editor, review only. UNIQUE: Same instrument, second catalog: the queue lists stagings by family and the stage pairs grammar with its neutral-skin demonstration. NOT-TEMPLATE: No card grid, no dashboard; one staging at a time. Seed key: not run; structure inherited from /worlds. -->
<Base
title="Staging Catalog | Impeccable"
description="Review the composition catalog: identity-free stagings for heroes, pages, and app screens."
bodyClass="sub-page worlds-lab-page"
mainClass="worlds-main-shell"
noIndex
hideHeader
hideFooter
>
<section class="worlds-workbench worlds-stagings" aria-label="Staging catalog">
<aside class="worlds-family-rail" aria-label="Staging families and review status">
<div class="worlds-rail-head">
<a class="worlds-home-link ks-brand" href="/" aria-label="Back to Impeccable">
<span class="ks-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"></path>
<path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"></path>
</svg>
</span>
<span class="ks-wordmark">Impeccable</span>
</a>
<div class="worlds-title-lockup">
<p>Composition catalog</p>
<h1>Stagings</h1>
</div>
<nav class="worlds-catalog-switch" aria-label="Catalog">
<a href="/labs/worlds">Worlds</a>
<a href="/labs/stagings" aria-current="page">Stagings</a>
</nav>
<div class="worlds-progress-block">
<div class="worlds-status-grid" role="group" aria-label="Filter stagings by review status">
<button type="button" data-status-filter="pending" aria-pressed="true">
<span>Backlog</span><strong data-count-pending>{counts.pending}</strong>
</button>
<button type="button" data-status-filter="approved" aria-pressed="false">
<span>Approved</span><strong data-count-approved>{counts.approved}</strong>
</button>
<button type="button" data-status-filter="rejected" aria-pressed="false">
<span>Rejected</span><strong data-count-rejected>{counts.rejected}</strong>
</button>
<button type="button" data-status-filter="all" aria-pressed="false">
<span>All</span><strong data-count-total>{counts.total}</strong>
</button>
</div>
</div>
</div>
<nav class="worlds-family-scroll" aria-label="Staging families">
<button type="button" class="worlds-family-button" data-family-filter="all" aria-pressed="true">
<span>All families</span><span data-family-count="all">{counts.pending}</span>
</button>
{catalog.families.map(family => (
<button type="button" class="worlds-family-button" data-family-filter={family.id} aria-pressed="false">
<span>{family.label}</span>
<span data-family-count={family.id}>{compositions.filter(item => item.familyId === family.id && item.status === 'pending').length}</span>
</button>
))}
</nav>
</aside>
<div class="worlds-workspace">
<header class="worlds-toolbar">
<label class="worlds-search">
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<circle cx="10.5" cy="10.5" r="6.5"></circle>
<path d="m15.5 15.5 5 5"></path>
</svg>
<input id="worlds-search" type="search" aria-label="Search stagings" placeholder="Search forms, grammar, or IDs" autocomplete="off" data-search>
<kbd>/</kbd>
</label>
<div class="worlds-runtime-state">
<span class:list={{ 'is-live': import.meta.env.DEV }} aria-hidden="true"></span>
<div>
<strong>{import.meta.env.DEV ? 'Source writing active' : 'Read-only catalog'}</strong>
<small>{import.meta.env.DEV ? 'Changes persist to skill/scripts' : 'Run bun run dev to review'}</small>
</div>
</div>
</header>
<div class="worlds-workspace-grid">
<aside class="worlds-queue" aria-label="Filtered staging queue">
<header class="worlds-queue-head">
<div>
<p>Review queue</p>
<strong data-queue-title>Backlog</strong>
</div>
<span data-result-count>{counts.pending.toLocaleString()}</span>
</header>
<div class="worlds-queue-list" data-queue-list></div>
<footer class="worlds-queue-foot">
<span data-window-label>Loading catalog…</span>
<div>
<button type="button" aria-label="Previous staging" data-previous aria-keyshortcuts="ArrowUp K"><kbd>↑</kbd><kbd>K</kbd></button>
<button type="button" aria-label="Next staging" data-next aria-keyshortcuts="ArrowDown J"><kbd>↓</kbd><kbd>J</kbd></button>
</div>
</footer>
</aside>
<article id="worlds-review" class="worlds-review-stage" aria-labelledby="current-concept-form" tabindex="-1">
<header class="worlds-review-head">
<div class="worlds-review-path">
<span data-concept-family>{initial?.familyLabel}</span>
<span aria-hidden="true">/</span>
<code data-concept-id>{initial?.id}</code>
</div>
<div class="worlds-review-meta">
<span class="worlds-review-folio">Staging <strong data-concept-position>001</strong> / {counts.total.toLocaleString()}</span>
<span class={`worlds-status-badge is-${initial?.status || 'pending'}`} data-concept-status>{initial?.status || 'pending'}</span>
</div>
</header>
<div class="worlds-concept-body">
<div class="worlds-concept-copy">
<p class="worlds-concept-kicker" data-concept-mode>{initial?.surface} staging</p>
<h2 id="current-concept-form" data-concept-form>{initial?.form}</h2>
<section class="worlds-creative-spark">
<p>Spark</p>
<strong data-concept-spark>{initial?.spark}</strong>
</section>
<div class="worlds-tag-row" data-concept-tags>
{(initial?.tags || []).map(tag => <span>{tag}</span>)}
</div>
<div class="worlds-concept-meta">
<div>
<p>Lineage</p>
<strong data-concept-lineage>{initial?.lineage}</strong>
</div>
<div data-concept-provenance-wrap hidden={!initial?.sourceConceptId}>
<p>Extracted from</p>
<strong data-concept-provenance>{initial?.sourceConceptId}{initial?.provenanceNote ? ` · "${initial?.provenanceNote}"` : ''}</strong>
</div>
<div data-concept-review-note-wrap hidden={!initial?.review?.note}>
<p>Review note</p>
<strong data-concept-review-note>{initial?.review?.note}</strong>
</div>
</div>
</div>
<div class="worlds-concept-evidence">
<figure class="worlds-concept-card" data-concept-card-wrap hidden={!initial?.card}>
<a data-concept-card-link href={initial?.card?.src} target="_blank" rel="noopener">
<img data-concept-card src={initial?.card?.src} alt={initial?.card ? `Neutral demonstration of ${initial?.form}` : ''} width="2048" height="1152" loading="lazy" decoding="async">
</a>
<figcaption data-concept-card-stale hidden={!initial?.card?.stale}>Demonstration predates the latest edit</figcaption>
</figure>
<div class="worlds-system-contract">
<section>
<p>Staging grammar</p>
<ol data-concept-system>
{(initial?.grammar || []).map(rule => <li>{rule}</li>)}
</ol>
</section>
<section>
<p>Web leverage</p>
<strong data-concept-web-leverage>{initial?.webLeverage}</strong>
</section>
</div>
</div>
</div>
<footer class="worlds-review-actions">
<div class="worlds-action-status">
<input class="worlds-decision-note" data-decision-note type="text" maxlength="500" placeholder="Optional note, attached to the next decision" aria-label="Optional note attached to the next review decision" autocomplete="off" disabled={!import.meta.env.DEV}>
<p class="worlds-action-note" data-action-note role="status" aria-live="polite"></p>
</div>
<div class="worlds-action-buttons">
<button type="button" class="worlds-action-reject" data-review-status="rejected" disabled={!import.meta.env.DEV} aria-keyshortcuts="Alt+R"><span>Reject</span><kbd>⌥R</kbd></button>
<button type="button" class="worlds-action-pending" data-review-status="pending" disabled={!import.meta.env.DEV} aria-keyshortcuts="Alt+U"><span>Return to backlog</span><kbd>⌥U</kbd></button>
<button type="button" class="worlds-action-approve" data-review-status="approved" disabled={!import.meta.env.DEV} aria-keyshortcuts="Alt+A"><span>Approve</span><kbd>⌥A</kbd></button>
</div>
</footer>
</article>
</div>
</div>
</section>
<div class="worlds-toast" role="status" aria-live="polite" data-toast hidden></div>
<script id="stagings-data" type="application/json" set:html={pageData}></script>
<script>
const payload = JSON.parse(document.getElementById('stagings-data').textContent);
const canWrite = payload.canWrite;
const compositions = payload.compositions;
const byId = new Map(compositions.map(item => [item.id, item]));
const initialId = location.hash.startsWith('#staging=') ? decodeURIComponent(location.hash.slice(9)) : '';
const state = {
status: byId.get(initialId)?.status || 'pending',
family: 'all',
query: '',
currentId: initialId,
};
const queueList = document.querySelector('[data-queue-list]');
const searchInput = document.querySelector('[data-search]');
const decisionNoteInput = document.querySelector('[data-decision-note]');
const actionNote = document.querySelector('[data-action-note]');
const toast = document.querySelector('[data-toast]');
let toastTimer = 0;
let notedId = '';
const el = selector => document.querySelector(selector);
function rows() {
const query = state.query.trim().toLowerCase();
return compositions.filter(item =>
(state.status === 'all' || item.status === state.status)
&& (state.family === 'all' || item.familyId === state.family)
&& (!query || [item.id, item.form, item.spark, item.lineage, item.familyLabel, item.webLeverage, ...item.tags, ...item.grammar].join(' ').toLowerCase().includes(query))
);
}
function current() {
const visible = rows();
return byId.get(state.currentId) && visible.some(item => item.id === state.currentId)
? byId.get(state.currentId)
: visible[0] || null;
}
function showToast(message, kind = 'ok') {
toast.textContent = message;
toast.dataset.kind = kind;
toast.hidden = false;
window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(() => { toast.hidden = true; }, 2600);
}
function renderCounts() {
const counts = compositions.reduce((sum, item) => { sum[item.status] += 1; return sum; }, { approved: 0, pending: 0, rejected: 0 });
el('[data-count-approved]').textContent = counts.approved.toLocaleString();
el('[data-count-pending]').textContent = counts.pending.toLocaleString();
el('[data-count-rejected]').textContent = counts.rejected.toLocaleString();
el('[data-count-total]').textContent = compositions.length.toLocaleString();
el('[data-family-count="all"]').textContent = compositions.filter(item => state.status === 'all' || item.status === state.status).length.toLocaleString();
for (const button of document.querySelectorAll('[data-family-filter]:not([data-family-filter="all"])')) {
const familyId = button.dataset.familyFilter;
button.querySelector('[data-family-count]').textContent = compositions
.filter(item => item.familyId === familyId && (state.status === 'all' || item.status === state.status)).length.toLocaleString();
}
}
function renderQueue(visible) {
queueList.replaceChildren(...visible.slice(0, 80).map((item, index) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'worlds-queue-item';
button.dataset.queueConceptId = item.id;
button.setAttribute('aria-pressed', String(item.id === state.currentId));
button.setAttribute('aria-label', `${index + 1} in current queue: ${item.form}`);
const indexEl = document.createElement('span');
indexEl.textContent = String(index + 1).padStart(3, '0');
const copy = document.createElement('span');
const family = document.createElement('small');
family.textContent = item.familyLabel;
const form = document.createElement('strong');
form.textContent = item.form.split(',')[0];
copy.append(family, form);
const dot = document.createElement('i');
dot.className = `is-${item.status}`;
dot.setAttribute('aria-label', item.status);
button.append(indexEl, copy, dot);
return button;
}));
el('[data-result-count]').textContent = visible.length.toLocaleString();
el('[data-window-label]').textContent = `Showing 1${Math.min(80, visible.length)} of ${visible.length}`;
el('[data-queue-title]').textContent = state.status === 'all' ? 'All' : state.status === 'pending' ? 'Backlog' : state.status[0].toUpperCase() + state.status.slice(1);
}
function renderStage(visible) {
const item = current();
if (!item) {
el('[data-concept-form]').textContent = 'No staging selected';
el('[data-concept-family]').textContent = 'Empty queue';
el('[data-concept-id]').textContent = 'none';
return;
}
state.currentId = item.id;
el('[data-concept-form]').textContent = item.form;
el('[data-concept-family]').textContent = item.familyLabel;
el('[data-concept-id]').textContent = item.id;
el('[data-concept-status]').textContent = item.status;
el('[data-concept-status]').className = `worlds-status-badge is-${item.status}`;
el('[data-concept-mode]').textContent = `${item.surface} staging`;
el('[data-concept-spark]').textContent = item.spark;
el('[data-concept-lineage]').textContent = item.lineage;
el('[data-concept-web-leverage]').textContent = item.webLeverage;
el('[data-concept-position]').textContent = String(compositions.findIndex(entry => entry.id === item.id) + 1).padStart(3, '0');
el('[data-concept-tags]').replaceChildren(...item.tags.map(tag => { const span = document.createElement('span'); span.textContent = tag; return span; }));
el('[data-concept-system]').replaceChildren(...item.grammar.map(rule => { const li = document.createElement('li'); li.textContent = rule; return li; }));
const provenanceWrap = el('[data-concept-provenance-wrap]');
provenanceWrap.hidden = !item.sourceConceptId;
el('[data-concept-provenance]').textContent = item.sourceConceptId
? `${item.sourceConceptId}${item.provenanceNote ? ` · "${item.provenanceNote}"` : ''}`
: '';
const note = item.review?.note || '';
el('[data-concept-review-note-wrap]').hidden = !note;
el('[data-concept-review-note]').textContent = note;
if (item.id !== notedId) { decisionNoteInput.value = note; notedId = item.id; }
const cardWrap = el('[data-concept-card-wrap]');
const img = el('[data-concept-card]');
const link = el('[data-concept-card-link]');
if (item.card) {
cardWrap.hidden = false;
img.src = item.card.src;
img.alt = `Neutral demonstration of ${item.form}`;
link.href = item.card.src;
el('[data-concept-card-stale]').hidden = !item.card.stale;
} else {
cardWrap.hidden = true;
img.removeAttribute('src');
link.removeAttribute('href');
}
history.replaceState(null, '', `#staging=${encodeURIComponent(item.id)}`);
for (const button of document.querySelectorAll('[data-review-status]')) {
button.classList.toggle('is-current', button.dataset.reviewStatus === item.status);
}
const position = visible.findIndex(entry => entry.id === item.id);
el('[data-previous]').disabled = position <= 0;
el('[data-next]').disabled = position === -1 || position >= visible.length - 1;
}
function render() {
const visible = rows();
renderCounts();
renderQueue(visible);
renderStage(visible);
}
function move(delta) {
const visible = rows();
if (visible.length === 0) return;
const index = Math.max(0, visible.findIndex(item => item.id === state.currentId));
state.currentId = visible[Math.max(0, Math.min(visible.length - 1, index + delta))].id;
render();
}
async function review(status) {
const item = current();
if (!canWrite || !item) return;
const note = decisionNoteInput.value.trim();
const savedNote = item.review?.note || '';
if (note && status === 'pending') {
showToast('Notes attach to approve or reject; clear the note first to return to backlog', 'error');
return;
}
if (item.status === status && note === savedNote) return;
actionNote.textContent = 'Saving…';
try {
const response = await fetch('/__impeccable/worlds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'review', catalog: 'compositions', id: item.id, status, ...(note ? { note } : {}) }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Review could not be saved');
item.status = status;
item.review = result.result.review;
render();
showToast(`${status === 'pending' ? 'Returned' : status === 'approved' ? 'Approved' : 'Rejected'}: ${item.form.split(',')[0]}`);
} catch (error) {
showToast(error instanceof Error ? error.message : String(error), 'error');
} finally {
actionNote.textContent = '';
}
}
document.querySelectorAll('[data-status-filter]').forEach(button => {
button.addEventListener('click', () => {
state.status = button.dataset.statusFilter;
document.querySelectorAll('[data-status-filter]').forEach(item => item.setAttribute('aria-pressed', String(item === button)));
render();
});
});
document.querySelectorAll('[data-family-filter]').forEach(button => {
button.addEventListener('click', () => {
state.family = button.dataset.familyFilter;
document.querySelectorAll('[data-family-filter]').forEach(item => item.setAttribute('aria-pressed', String(item === button)));
render();
});
});
queueList.addEventListener('click', event => {
const button = event.target.closest('[data-queue-concept-id]');
if (!button) return;
state.currentId = button.dataset.queueConceptId;
render();
});
searchInput.addEventListener('input', () => { state.query = searchInput.value; render(); });
el('[data-previous]').addEventListener('click', () => move(-1));
el('[data-next]').addEventListener('click', () => move(1));
document.querySelectorAll('[data-review-status]').forEach(button => {
button.addEventListener('click', () => review(button.dataset.reviewStatus));
});
document.addEventListener('keydown', event => {
const target = event.target;
if (target === decisionNoteInput && event.altKey) {
if (event.code === 'KeyA') { event.preventDefault(); review('approved'); return; }
if (event.code === 'KeyR') { event.preventDefault(); review('rejected'); return; }
if (event.code === 'KeyU') { event.preventDefault(); review('pending'); return; }
}
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) return;
if (event.key === '/') { event.preventDefault(); searchInput.focus(); }
else if (event.key.toLowerCase() === 'j' || (event.key === 'ArrowDown' && !event.altKey && !event.metaKey && !event.ctrlKey)) { event.preventDefault(); move(1); }
else if (event.key.toLowerCase() === 'k' || (event.key === 'ArrowUp' && !event.altKey && !event.metaKey && !event.ctrlKey)) { event.preventDefault(); move(-1); }
else if (event.altKey && event.code === 'KeyA') { event.preventDefault(); review('approved'); }
else if (event.altKey && event.code === 'KeyR') { event.preventDefault(); review('rejected'); }
else if (event.altKey && event.code === 'KeyU') { event.preventDefault(); review('pending'); }
});
render();
</script>
</Base>
@@ -4,23 +4,59 @@ import path from 'node:path';
import Base from '../../layouts/Base.astro';
import '../../styles/sub-pages.css';
import '../../styles/worlds-lab.css';
import { conceptContentHash } from '../../../skill/scripts/lib/concept-catalog.mjs';
const ROOT = process.cwd();
const catalog = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill', 'scripts', 'concept-ingredients.json'), 'utf8'));
const reviewData = JSON.parse(fs.readFileSync(path.join(ROOT, 'skill', 'scripts', 'concept-reviews.json'), 'utf8'));
const reviews = reviewData.reviews || {};
const CARD_DIR = path.join(ROOT, 'site', 'public', 'worlds', 'cards');
const cardManifest = (() => {
try { return JSON.parse(fs.readFileSync(path.join(CARD_DIR, 'manifest.json'), 'utf8')); } catch { return {}; }
})();
// Cards live in R2 in production (functions/worlds/cards). A local generation
// output is preferred when present; a clone without local cards falls back to
// the published set so the lab still shows them.
const PROD_CARDS_BASE = 'https://impeccable.style/worlds/cards';
const cardFor = concept => {
const row = cardManifest[concept.id];
const localExists = fs.existsSync(path.join(CARD_DIR, `${concept.id}.webp`));
if (!localExists && !row) return null;
// Cards are regenerated under the same filename, so the URL carries the
// generation timestamp: browsers and CDNs refetch exactly when a card changes.
const generated = Date.parse(row?.generatedAt || 0) || 0;
const base = localExists ? '/worlds/cards' : PROD_CARDS_BASE;
const heroLocal = fs.existsSync(path.join(CARD_DIR, `${concept.id}-hero.webp`));
const heroGenerated = Date.parse(row?.heroGeneratedAt || 0) || 0;
return {
src: `${base}/${concept.id}.webp?v=${generated.toString(36)}`,
heroSrc: (heroLocal || row?.heroGeneratedAt)
? `${heroLocal ? '/worlds/cards' : PROD_CARDS_BASE}/${concept.id}-hero.webp?v=${heroGenerated.toString(36)}`
: null,
stale: row?.hash !== conceptContentHash(concept),
};
};
const wellsById = new Map((catalog.wells || []).map(well => [well.id, well]));
const families = catalog.families.map(family => ({
id: family.id,
label: family.label,
description: family.description,
wellId: family.well,
wellLabel: wellsById.get(family.well)?.label || '',
concepts: family.concepts.map(concept => ({
...concept,
card: cardFor(concept),
familyId: family.id,
familyLabel: family.label,
wellId: family.well,
wellLabel: wellsById.get(family.well)?.label || '',
status: reviews[concept.id]?.status || 'pending',
review: reviews[concept.id] || null,
})),
}));
const wellGroups = (catalog.wells || [])
.map(well => ({ well, families: families.filter(family => family.wellId === well.id) }))
.filter(group => group.families.length > 0);
const concepts = families.flatMap(family => family.concepts);
const initialConcept = concepts.find(concept => concept.status === 'pending') || concepts[0];
const counts = {
@@ -37,7 +73,7 @@ const pageData = JSON.stringify({
}).replace(/</g, '\\u003c');
---
<!-- DIRECTION CONTRACT: UNIQUE: Turn an abstract challenger corpus into a human-governed editorial queue. NOT-TEMPLATE: Refuse analytics dashboards and equal-card grids. OWN-WORLD: Lacquer, kinpaku commitment, patina state, neutral hairlines, compact Albert Sans UI. STORY: Find pending work, understand one form, decide, and advance the trusted pool. FIRST VIEWPORT: Family/progress rail, searchable queue, and one full-height review stage with keyboard-visible actions. FORM: A catalog editor crossed with a contact-sheet loupe; thousands of entries collapse into one precise judgment. Seed key: not run; structure inherits the established Detector Lab. -->
<!-- DIRECTION CONTRACT: THESIS: Turn an abstract challenger corpus into a human-governed editorial queue that rejects the tiled catalog and admin-dashboard defaults; one vivid world earns focused attention and proves its durable web structure. OWN-WORLD: Lacquer, kinpaku commitment, patina state, neutral hairlines, compact Albert Sans UI. STORY: Find pending work, picture the world immediately, judge whether its material and spatial laws become a real web system, decide, and advance the trusted pool. FIRST VIEWPORT: Well/progress rail, searchable queue, and one full-height review stage with keyboard-visible actions. BAR-RAISER: Every concept exposes an unforgettable scene, reusable system grammar, and browser-native leverage before approval. FORM: A catalog editor crossed with a contact-sheet loupe; hundreds of entries collapse into one precise judgment. UNIQUE: The review stage is a single editorial loupe with a folio counter (Catalog 001 of N), a lineage margin, and Option-chord decisions that write straight to source JSON; the queue lives in a side rail and never becomes the page. NOT-TEMPLATE: No tiled card grid, no stat-hero band, no modal-per-decision; aggregate numbers stay confined to the rail's progress block and the stage never shows more than one world. Seed key: not run; structure inherits the established Detector Lab. -->
<Base
title="World Catalog | Impeccable"
description="Browse and review the concept forms that challenge grounded Impeccable design directions."
@@ -68,6 +104,11 @@ const pageData = JSON.stringify({
<h1>World Catalog</h1>
</div>
<nav class="worlds-catalog-switch" aria-label="Catalog">
<a href="/labs/worlds" aria-current="page">Worlds</a>
<a href="/labs/stagings">Stagings</a>
</nav>
<div class="worlds-progress-block">
<div class="worlds-progress-copy">
<span>Human reviewed</span>
@@ -76,36 +117,45 @@ const pageData = JSON.stringify({
<div class="worlds-progress-track" role="progressbar" aria-label="Catalog review progress" aria-valuemin="0" aria-valuemax={counts.total} aria-valuenow={counts.reviewed} data-progress-track>
<span style={`--worlds-progress: ${(counts.reviewed / counts.total) * 100}%`} data-progress-fill></span>
</div>
<dl class="worlds-count-grid">
<div><dt>Approved</dt><dd data-count-approved>{counts.approved}</dd></div>
<div><dt>Backlog</dt><dd data-count-pending>{counts.pending}</dd></div>
<div><dt>Rejected</dt><dd data-count-rejected>{counts.rejected}</dd></div>
<div><dt>Total</dt><dd data-count-total>{counts.total}</dd></div>
</dl>
</div>
<div class="worlds-status-tabs" role="group" aria-label="Filter concepts by review status">
<button type="button" data-status-filter="pending" aria-pressed="true">Backlog</button>
<button type="button" data-status-filter="approved" aria-pressed="false">Approved</button>
<button type="button" data-status-filter="rejected" aria-pressed="false">Rejected</button>
<button type="button" data-status-filter="all" aria-pressed="false">All</button>
<div class="worlds-status-grid" role="group" aria-label="Filter concepts by review status">
<button type="button" data-status-filter="pending" aria-pressed="true">
<span>Backlog</span><strong data-count-pending>{counts.pending}</strong>
</button>
<button type="button" data-status-filter="approved" aria-pressed="false">
<span>Approved</span><strong data-count-approved>{counts.approved}</strong>
</button>
<button type="button" data-status-filter="rejected" aria-pressed="false">
<span>Rejected</span><strong data-count-rejected>{counts.rejected}</strong>
</button>
<button type="button" data-status-filter="unrated" aria-pressed="false" title="Approved worlds without a star rating yet">
<span>Unrated</span><strong data-count-unrated>{concepts.filter(concept => concept.status === 'approved' && !concept.review?.rating).length}</strong>
</button>
<button type="button" data-status-filter="all" aria-pressed="false">
<span>All</span><strong data-count-total>{counts.total}</strong>
</button>
</div>
</div>
</div>
<label class="worlds-family-search">
<span>Find a family</span>
<input type="search" placeholder="Filter 36 families" autocomplete="off" data-family-search>
<input type="search" placeholder={`Filter ${catalog.families.length} families`} autocomplete="off" data-family-search>
</label>
<nav class="worlds-family-scroll" aria-label="Concept families">
<button type="button" class="worlds-family-button" data-family-filter="all" aria-pressed="true">
<span>All families</span>
<span data-family-count="all">{counts.pending}</span>
</button>
{families.map(family => (
<button type="button" class="worlds-family-button" data-family-filter={family.id} data-family-label={family.label.toLowerCase()} aria-pressed="false" tabindex="-1" hidden>
<span>{family.label}</span>
<span data-family-count={family.id}>{family.concepts.filter(concept => concept.status === 'pending').length}</span>
</button>
{wellGroups.map(group => (
<>
<p class="worlds-well-heading" data-well-heading={group.well.id} hidden>{group.well.label}</p>
{group.families.map(family => (
<button type="button" class="worlds-family-button" data-family-filter={family.id} data-family-well={group.well.id} data-family-label={`${family.label} ${group.well.label}`.toLowerCase()} aria-pressed="false" tabindex="-1" hidden>
<span>{family.label}</span>
<span data-family-count={family.id}>{family.concepts.filter(concept => concept.status === 'pending').length}</span>
</button>
))}
</>
))}
<p class="worlds-family-empty" data-family-empty hidden>No families match.</p>
<button type="button" class="worlds-family-expand" data-family-expand aria-expanded="false">
@@ -121,7 +171,7 @@ const pageData = JSON.stringify({
<circle cx="10.5" cy="10.5" r="6.5"></circle>
<path d="m15.5 15.5 5 5"></path>
</svg>
<input id="worlds-search" type="search" aria-label="Search concepts" placeholder="Search forms, lineage, tags, or IDs" autocomplete="off" data-search>
<input id="worlds-search" type="search" aria-label="Search concepts" placeholder="Search forms, systems, lineage, or IDs" autocomplete="off" data-search>
<kbd>/</kbd>
</label>
<div class="worlds-runtime-state">
@@ -146,8 +196,8 @@ const pageData = JSON.stringify({
<footer class="worlds-queue-foot">
<span data-window-label>Loading catalog…</span>
<div>
<button type="button" aria-label="Previous concept" data-previous><kbd>K</kbd></button>
<button type="button" aria-label="Next concept" data-next><kbd>J</kbd></button>
<button type="button" aria-label="Previous concept" data-previous aria-keyshortcuts="ArrowUp K"><kbd>↑</kbd><kbd>K</kbd></button>
<button type="button" aria-label="Next concept" data-next aria-keyshortcuts="ArrowDown J"><kbd>↓</kbd><kbd>J</kbd></button>
</div>
</footer>
</aside>
@@ -155,43 +205,111 @@ const pageData = JSON.stringify({
<article id="worlds-review" class="worlds-review-stage" aria-labelledby="current-concept-form" tabindex="-1">
<header class="worlds-review-head">
<div class="worlds-review-path">
<span data-concept-well>{initialConcept.wellLabel}</span>
<span aria-hidden="true">/</span>
<span data-concept-family>{initialConcept.familyLabel}</span>
<span aria-hidden="true">/</span>
<code data-concept-id>{initialConcept.id}</code>
</div>
<span class={`worlds-status-badge is-${initialConcept.status}`} data-concept-status>{initialConcept.status}</span>
<div class="worlds-review-meta">
<div class="worlds-rating" role="group" aria-label="Approval rating: how strong this approved world is" data-rating-group hidden={initialConcept.status !== 'approved'}>
{[1, 2, 3].map(value => (
<button
type="button"
data-rating-option={value}
aria-pressed={String((initialConcept.review?.rating || 0) >= value)}
disabled={!import.meta.env.DEV}
title={value === 3 ? 'Exceptional: flagship-grade world (press 3)' : value === 2 ? 'Solid approved world (press 2)' : 'Marginal keep (press 1)'}
>★</button>
))}
</div>
<div class="worlds-strength-toggle" role="group" aria-label="Concept strength: what this entry is strong at" data-strength-toggle>
{['world', 'composition', 'dual'].map(value => (
<button
type="button"
data-strength-option={value}
aria-pressed={String(initialConcept.strength === value)}
disabled={!import.meta.env.DEV}
title={value === 'world' ? 'Durable visual identity' : value === 'composition' ? 'Staging idea, dressable in any identity' : 'Identity and staging fused'}
>{value}</button>
))}
</div>
<span class="worlds-review-folio">Catalog <strong data-concept-position>{String(concepts.findIndex(concept => concept.id === initialConcept.id) + 1).padStart(3, '0')}</strong> / {counts.total.toLocaleString()}</span>
<span class={`worlds-status-badge is-${initialConcept.status}`} data-concept-status>{initialConcept.status}</span>
</div>
</header>
<div class="worlds-concept-body">
<div class="worlds-concept-index" aria-hidden="true">
<span>Catalog</span>
<strong data-concept-position>001</strong>
<i></i>
<span>of {counts.total.toLocaleString()}</span>
</div>
<div class="worlds-concept-copy">
<p class="worlds-concept-kicker">Structural challenger</p>
<h2 id="current-concept-form" data-concept-form>{initialConcept.form}</h2>
<section class="worlds-creative-spark">
<p>Creative spark</p>
<strong data-concept-spark>{initialConcept.spark}</strong>
</section>
<div class="worlds-tag-row" data-concept-tags>
{initialConcept.tags.map(tag => <span>{tag}</span>)}
</div>
<div class="worlds-concept-meta">
<div>
<p>Design lineage</p>
<strong data-concept-lineage>{initialConcept.lineage}</strong>
</div>
<div data-concept-review-note-wrap hidden={!initialConcept.review?.note}>
<p>Review note</p>
<strong data-concept-review-note>{initialConcept.review?.note}</strong>
</div>
<div class="worlds-concept-meta-row">
<span>Catalog version <em data-catalog-version>{catalog.catalogVersion}</em></span>
<button type="button" data-edit-toggle disabled={!import.meta.env.DEV}>
Edit concept <kbd>E</kbd>
</button>
</div>
</div>
</div>
<div class="worlds-concept-evidence">
<figure class="worlds-concept-card" data-concept-hero-wrap hidden={!initialConcept.card?.heroSrc}>
<a data-concept-hero-link href={initialConcept.card?.heroSrc} target="_blank" rel="noopener">
<img
data-concept-hero
src={initialConcept.card?.heroSrc}
alt={initialConcept.card?.heroSrc ? `Landing page composed in the world of ${initialConcept.form}` : ''}
width="2048"
height="1152"
loading="lazy"
decoding="async"
>
</a>
</figure>
<figure class="worlds-concept-card" data-concept-card-wrap hidden={!initialConcept.card}>
<a data-concept-card-link href={initialConcept.card?.src} target="_blank" rel="noopener">
<img
data-concept-card
src={initialConcept.card?.src}
alt={initialConcept.card ? `Design-system translation of ${initialConcept.form}` : ''}
width="2048"
height="1152"
loading="lazy"
decoding="async"
>
</a>
<figcaption data-concept-card-stale hidden={!initialConcept.card?.stale}>Card predates the latest edit of this concept</figcaption>
</figure>
<div class="worlds-system-contract">
<section>
<p>System grammar</p>
<ol data-concept-system>
{initialConcept.system.map(rule => <li>{rule}</li>)}
</ol>
</section>
<section>
<p>Web leverage</p>
<strong data-concept-web-leverage>{initialConcept.webLeverage}</strong>
</section>
</div>
</div>
</div>
<aside class="worlds-context-panel" aria-label="Concept context">
<div>
<p>Design lineage</p>
<strong data-concept-lineage>{initialConcept.lineage}</strong>
</div>
<div>
<p>Catalog version</p>
<strong>{catalog.catalogVersion}</strong>
</div>
<button type="button" data-edit-toggle disabled={!import.meta.env.DEV}>
Edit concept <kbd>E</kbd>
</button>
</aside>
<form class="worlds-edit-form" data-edit-form role="dialog" aria-modal="true" aria-labelledby="worlds-edit-title" hidden>
<header>
<div>
@@ -201,19 +319,27 @@ const pageData = JSON.stringify({
<button type="button" data-edit-cancel aria-label="Close editor">×</button>
</header>
<label>
<span>Form and inherited structure</span>
<textarea name="form" rows="5" maxlength="600" required></textarea>
<span>Grounded form</span>
<textarea name="form" rows="5" minlength="40" maxlength="360" required></textarea>
</label>
<label>
<span>Creative spark</span>
<textarea name="spark" rows="4" minlength="80" maxlength="320" required></textarea>
</label>
<div class="worlds-edit-row">
<label>
<span>Family</span>
<span>Territory</span>
<select name="familyId">
{families.map(family => <option value={family.id}>{family.label}</option>)}
{wellGroups.map(group => (
<optgroup label={group.well.label}>
{group.families.map(family => <option value={family.id}>{family.label}</option>)}
</optgroup>
))}
</select>
</label>
<label>
<span>Lineage</span>
<input name="lineage" maxlength="160" required>
<input name="lineage" minlength="12" maxlength="200" required>
</label>
</div>
<fieldset>
@@ -224,6 +350,20 @@ const pageData = JSON.stringify({
<input name="tag3" maxlength="40" aria-label="Structural tag three" required>
</div>
</fieldset>
<fieldset>
<legend>System grammar</legend>
<div class="worlds-edit-system">
<textarea name="system1" rows="2" maxlength="180" aria-label="Palette and material rule" required></textarea>
<textarea name="system2" rows="2" maxlength="180" aria-label="Type and composition rule" required></textarea>
<textarea name="system3" rows="2" maxlength="180" aria-label="Topology and navigation rule" required></textarea>
<textarea name="system4" rows="2" maxlength="180" aria-label="Controls and state rule" required></textarea>
<textarea name="system5" rows="2" maxlength="180" aria-label="Responsive and motion rule" required></textarea>
</div>
</fieldset>
<label>
<span>Web leverage</span>
<textarea name="webLeverage" rows="3" minlength="20" maxlength="240" required></textarea>
</label>
<footer>
<button type="button" class="worlds-button-secondary" data-edit-cancel>Cancel</button>
<button type="submit" class="worlds-button-primary">Save to catalog</button>
@@ -232,10 +372,17 @@ const pageData = JSON.stringify({
<footer class="worlds-review-actions">
<div class="worlds-action-status">
<div class="worlds-action-note">
<span class:list={{ 'is-live': import.meta.env.DEV }} aria-hidden="true"></span>
<p data-action-note>{import.meta.env.DEV ? 'Decisions write to concept-reviews.json. Review shortcuts require Option.' : 'Review actions are available on the local development server.'}</p>
</div>
<input
class="worlds-decision-note"
data-decision-note
type="text"
maxlength="500"
placeholder="Optional note, attached to the next decision"
aria-label="Optional note attached to the next review decision"
autocomplete="off"
disabled={!import.meta.env.DEV}
>
<p class="worlds-action-note" data-action-note role="status" aria-live="polite"></p>
<button type="button" class="worlds-undo" data-undo hidden disabled={!import.meta.env.DEV} aria-keyshortcuts="Alt+Z">
<span data-undo-label>Undo last decision</span><kbd>⌥Z</kbd>
</button>
@@ -282,27 +429,45 @@ const pageData = JSON.stringify({
const queueTitle = document.querySelector('[data-queue-title]');
const searchInput = document.querySelector('[data-search]');
const formEl = document.querySelector('[data-concept-form]');
const wellEl = document.querySelector('[data-concept-well]');
const familyEl = document.querySelector('[data-concept-family]');
const idEl = document.querySelector('[data-concept-id]');
const statusEl = document.querySelector('[data-concept-status]');
const lineageEl = document.querySelector('[data-concept-lineage]');
const tagsEl = document.querySelector('[data-concept-tags]');
const systemEl = document.querySelector('[data-concept-system]');
const sparkEl = document.querySelector('[data-concept-spark]');
const cardWrapEl = document.querySelector('[data-concept-card-wrap]');
const cardImgEl = document.querySelector('[data-concept-card]');
const cardLinkEl = document.querySelector('[data-concept-card-link]');
const cardStaleEl = document.querySelector('[data-concept-card-stale]');
const heroWrapEl = document.querySelector('[data-concept-hero-wrap]');
const heroImgEl = document.querySelector('[data-concept-hero]');
const heroLinkEl = document.querySelector('[data-concept-hero-link]');
const webLeverageEl = document.querySelector('[data-concept-web-leverage]');
const positionEl = document.querySelector('[data-concept-position]');
const editForm = document.querySelector('[data-edit-form]');
const toast = document.querySelector('[data-toast]');
const reviewStage = document.querySelector('[data-concept-form]').closest('.worlds-review-stage');
const actionNote = document.querySelector('[data-action-note]');
const decisionNoteInput = document.querySelector('[data-decision-note]');
const strengthButtons = [...document.querySelectorAll('[data-strength-option]')];
const ratingGroup = document.querySelector('[data-rating-group]');
const ratingButtons = [...document.querySelectorAll('[data-rating-option]')];
let notedConceptId = '';
const reviewNoteWrapEl = document.querySelector('[data-concept-review-note-wrap]');
const reviewNoteEl = document.querySelector('[data-concept-review-note]');
const undoButton = document.querySelector('[data-undo]');
const undoLabel = document.querySelector('[data-undo-label]');
const familySearchInput = document.querySelector('[data-family-search]');
const familyExpandButton = document.querySelector('[data-family-expand]');
const familyEmpty = document.querySelector('[data-family-empty]');
const familyButtons = [...document.querySelectorAll('[data-family-filter]')];
const defaultActionNote = canWrite
? 'Decisions write to concept-reviews.json. Review shortcuts require Option.'
: 'Review actions are available on the local development server.';
// The header runtime indicator already states write mode; this line only
// carries transient operation messages (saving, restoring, errors).
const defaultActionNote = '';
let toastTimer = 0;
let familyExpanded = false;
let familyExpanded = true;
let lastReview = null;
let editorBaseline = '';
@@ -311,13 +476,17 @@ const pageData = JSON.stringify({
}
function searchableText(concept) {
return [concept.id, concept.form, concept.lineage, concept.familyLabel, ...concept.tags].join(' ').toLowerCase();
return [concept.id, concept.form, concept.spark, concept.lineage, concept.familyLabel, concept.wellLabel, concept.webLeverage, ...concept.tags, ...concept.system].join(' ').toLowerCase();
}
function filteredConcepts() {
const query = state.query.trim().toLowerCase();
return concepts.filter(concept => {
if (state.status !== 'all' && concept.status !== state.status) return false;
if (state.status === 'unrated') {
if (concept.status !== 'approved' || concept.review?.rating) return false;
} else if (state.status !== 'all' && concept.status !== state.status) {
return false;
}
if (state.family !== 'all' && concept.familyId !== state.family) return false;
return !query || searchableText(concept).includes(query);
});
@@ -354,6 +523,10 @@ const pageData = JSON.stringify({
if (visible && !isAll && matchesQuery) matches += 1;
button.tabIndex = isSelected ? 0 : -1;
}
for (const heading of document.querySelectorAll('[data-well-heading]')) {
const wellId = heading.dataset.wellHeading;
heading.hidden = !familyButtons.some(button => button.dataset.familyWell === wellId && !button.hidden);
}
familyEmpty.hidden = !query || matches > 0;
familyExpandButton.hidden = Boolean(query);
familyExpandButton.setAttribute('aria-expanded', String(familyExpanded));
@@ -374,6 +547,8 @@ const pageData = JSON.stringify({
document.querySelector('[data-count-approved]').textContent = counts.approved.toLocaleString();
document.querySelector('[data-count-pending]').textContent = counts.pending.toLocaleString();
document.querySelector('[data-count-rejected]').textContent = counts.rejected.toLocaleString();
document.querySelector('[data-count-unrated]').textContent = concepts
.filter(concept => concept.status === 'approved' && !concept.review?.rating).length.toLocaleString();
document.querySelector('[data-count-total]').textContent = concepts.length.toLocaleString();
const reviewed = counts.approved + counts.rejected;
const percent = Math.round((reviewed / concepts.length) * 100);
@@ -448,22 +623,81 @@ const pageData = JSON.stringify({
const concept = currentConcept();
if (!concept) {
formEl.textContent = 'No concept selected';
wellEl.textContent = '';
familyEl.textContent = 'Empty queue';
idEl.textContent = 'none';
statusEl.textContent = 'empty';
statusEl.className = 'worlds-status-badge';
lineageEl.textContent = 'Change the current filters to continue.';
tagsEl.replaceChildren();
systemEl.replaceChildren();
sparkEl.textContent = '';
webLeverageEl.textContent = '';
cardWrapEl.hidden = true;
cardImgEl.removeAttribute('src');
cardLinkEl.removeAttribute('href');
heroWrapEl.hidden = true;
heroImgEl.removeAttribute('src');
heroLinkEl.removeAttribute('href');
reviewNoteWrapEl.hidden = true;
reviewNoteEl.textContent = '';
decisionNoteInput.value = '';
notedConceptId = '';
return;
}
const globalIndex = concepts.findIndex(item => item.id === concept.id);
formEl.textContent = concept.form;
wellEl.textContent = concept.wellLabel;
familyEl.textContent = concept.familyLabel;
idEl.textContent = concept.id;
statusEl.textContent = concept.status;
statusEl.className = `worlds-status-badge is-${concept.status}`;
lineageEl.textContent = concept.lineage;
systemEl.replaceChildren(...concept.system.map(rule => {
const item = document.createElement('li');
item.textContent = rule;
return item;
}));
sparkEl.textContent = concept.spark;
webLeverageEl.textContent = concept.webLeverage;
const reviewNote = concept.review?.note || '';
reviewNoteWrapEl.hidden = !reviewNote;
reviewNoteEl.textContent = reviewNote;
strengthButtons.forEach(button => {
button.setAttribute('aria-pressed', String(button.dataset.strengthOption === concept.strength));
});
ratingGroup.hidden = concept.status !== 'approved';
ratingButtons.forEach(button => {
button.setAttribute('aria-pressed', String((concept.review?.rating || 0) >= Number(button.dataset.ratingOption)));
});
// The decision-note field mirrors the current concept's saved note.
// Only refill when the concept changes so re-renders never clobber typing.
if (concept.id !== notedConceptId) {
decisionNoteInput.value = reviewNote;
notedConceptId = concept.id;
}
if (concept.card) {
cardWrapEl.hidden = false;
cardImgEl.src = concept.card.src;
cardImgEl.alt = `Design-system translation of ${concept.form}`;
cardLinkEl.href = concept.card.src;
cardStaleEl.hidden = !concept.card.stale;
} else {
cardWrapEl.hidden = true;
cardImgEl.removeAttribute('src');
cardLinkEl.removeAttribute('href');
}
if (concept.card?.heroSrc) {
heroWrapEl.hidden = false;
heroImgEl.src = concept.card.heroSrc;
heroImgEl.alt = `Landing page composed in the world of ${concept.form}`;
heroLinkEl.href = concept.card.heroSrc;
} else {
heroWrapEl.hidden = true;
heroImgEl.removeAttribute('src');
heroLinkEl.removeAttribute('href');
}
positionEl.textContent = String(globalIndex + 1).padStart(3, '0');
tagsEl.replaceChildren(...concept.tags.map(tag => {
const span = document.createElement('span');
@@ -498,11 +732,11 @@ const pageData = JSON.stringify({
if (focusQueue) queueList.querySelector('[aria-pressed="true"]')?.focus();
}
async function persistReview(concept, status) {
async function persistReview(concept, status, note = '') {
const response = await fetch('/__impeccable/worlds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'review', id: concept.id, status }),
body: JSON.stringify({ action: 'review', id: concept.id, status, ...(note ? { note } : {}) }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Review could not be saved');
@@ -517,25 +751,38 @@ const pageData = JSON.stringify({
async function review(status) {
const concept = currentConcept();
if (!canWrite || !concept || concept.status === status) return;
if (!canWrite || !concept) return;
const note = decisionNoteInput.value.trim();
const sameStatus = concept.status === status;
const savedNote = concept.review?.note || '';
// A same-status decision re-saves only when the note changed (added,
// edited, or cleared); otherwise it is a no-op. Backlog entries hold no
// review record, so notes cannot attach to them.
if (note && status === 'pending') {
showToast('Notes attach to approve or reject; clear the note first to return to backlog', 'error');
return;
}
if (sameStatus && note === savedNote) return;
const rowsBefore = filteredConcepts();
const indexBefore = rowsBefore.findIndex(item => item.id === concept.id);
const previousStatus = concept.status;
const buttons = [...document.querySelectorAll('[data-review-status]')];
buttons.forEach(button => { button.disabled = true; });
undoButton.disabled = true;
setReviewBusy(true, `Saving ${status} decision…`);
setReviewBusy(true, sameStatus ? 'Saving note…' : `Saving ${status} decision…`);
try {
const savedReview = await persistReview(concept, status);
const savedReview = await persistReview(concept, status, note);
concept.status = status;
concept.review = savedReview;
revealUndo({ id: concept.id, form: concept.form, previousStatus, nextStatus: status });
if (!sameStatus) revealUndo({ id: concept.id, form: concept.form, previousStatus, nextStatus: status });
const rowsAfter = filteredConcepts();
if (!rowsAfter.some(item => item.id === concept.id)) {
state.currentId = rowsAfter[Math.min(Math.max(indexBefore, 0), Math.max(0, rowsAfter.length - 1))]?.id || '';
}
render();
showToast(`${status === 'pending' ? 'Returned' : status === 'approved' ? 'Approved' : 'Rejected'}: ${concept.form.split(',')[0]}`);
showToast(sameStatus
? `${note ? 'Note saved' : 'Note removed'}: ${concept.form.split(',')[0]}`
: `${status === 'pending' ? 'Returned' : status === 'approved' ? 'Approved' : 'Rejected'}: ${concept.form.split(',')[0]}`);
} catch (error) {
showToast(error instanceof Error ? error.message : String(error), 'error');
} finally {
@@ -545,6 +792,56 @@ const pageData = JSON.stringify({
}
}
async function setStrength(strength) {
const concept = currentConcept();
if (!canWrite || !concept || concept.strength === strength) return;
try {
const response = await fetch('/__impeccable/worlds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'strength', id: concept.id, strength }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Strength could not be saved');
concept.strength = strength;
document.querySelector('[data-catalog-version]').textContent = result.result.catalogVersion;
render();
showToast(`Typed as ${strength}: ${concept.form.split(',')[0]}`);
} catch (error) {
showToast(error instanceof Error ? error.message : String(error), 'error');
}
}
async function setRating(value) {
const concept = currentConcept();
if (!canWrite || !concept || concept.status !== 'approved') return;
// Rating the current value again clears it.
const rating = concept.review?.rating === value ? null : value;
const rowsBefore = filteredConcepts();
const indexBefore = rowsBefore.findIndex(item => item.id === concept.id);
try {
const response = await fetch('/__impeccable/worlds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'rate', id: concept.id, rating }),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Rating could not be saved');
concept.review = result.result.review;
// In the unrated filter a rated item leaves the list; advance in place.
const rowsAfter = filteredConcepts();
if (!rowsAfter.some(item => item.id === concept.id)) {
state.currentId = rowsAfter[Math.min(Math.max(indexBefore, 0), Math.max(0, rowsAfter.length - 1))]?.id || concept.id;
}
render();
showToast(rating
? `Rated ${'★'.repeat(rating)}: ${concept.form.split(',')[0]}`
: `Rating cleared: ${concept.form.split(',')[0]}`);
} catch (error) {
showToast(error instanceof Error ? error.message : String(error), 'error');
}
}
async function undoLastReview() {
if (!canWrite || !lastReview) return;
const review = lastReview;
@@ -585,6 +882,13 @@ const pageData = JSON.stringify({
editForm.elements.tag1.value = concept.tags[0];
editForm.elements.tag2.value = concept.tags[1];
editForm.elements.tag3.value = concept.tags[2];
editForm.elements.system1.value = concept.system[0];
editForm.elements.system2.value = concept.system[1];
editForm.elements.system3.value = concept.system[2];
editForm.elements.system4.value = concept.system[3];
editForm.elements.system5.value = concept.system[4];
editForm.elements.spark.value = concept.spark;
editForm.elements.webLeverage.value = concept.webLeverage;
editorBaseline = editorSnapshot();
editForm.hidden = false;
editForm.elements.form.focus();
@@ -613,6 +917,9 @@ const pageData = JSON.stringify({
familyId: data.get('familyId'),
lineage: data.get('lineage'),
tags: [data.get('tag1'), data.get('tag2'), data.get('tag3')],
system: [data.get('system1'), data.get('system2'), data.get('system3'), data.get('system4'), data.get('system5')],
spark: data.get('spark'),
webLeverage: data.get('webLeverage'),
};
try {
const response = await fetch('/__impeccable/worlds', {
@@ -622,17 +929,35 @@ const pageData = JSON.stringify({
});
const result = await response.json();
if (!response.ok) throw new Error(result.error || 'Concept could not be saved');
concept.form = body.form;
concept.lineage = body.lineage;
concept.tags = body.tags;
if (concept.familyId !== body.familyId) {
concept.familyId = body.familyId;
concept.familyLabel = familyMap.get(body.familyId).label;
const previousStatus = concept.status;
const saved = result.result.concept;
concept.form = saved.form;
concept.lineage = saved.lineage;
concept.tags = saved.tags;
concept.system = saved.system;
concept.spark = saved.spark;
concept.webLeverage = saved.webLeverage;
concept.status = result.result.status;
concept.review = null;
if (concept.card) concept.card.stale = true;
if (concept.familyId !== result.result.familyId) {
const target = familyMap.get(result.result.familyId);
concept.familyId = result.result.familyId;
concept.familyLabel = target.label;
concept.wellId = target.wellId;
concept.wellLabel = target.wellLabel;
}
document.querySelector('[data-catalog-version]').textContent = result.result.catalogVersion;
if (lastReview?.id === concept.id) {
lastReview = null;
undoButton.hidden = true;
}
editorBaseline = editorSnapshot();
closeEditor(true);
render();
showToast('Concept source updated');
showToast(previousStatus === 'pending'
? 'Concept source updated'
: 'Concept updated and returned to the backlog');
} catch (error) {
showToast(error instanceof Error ? error.message : String(error), 'error');
} finally {
@@ -697,6 +1022,12 @@ const pageData = JSON.stringify({
});
document.querySelector('[data-previous]').addEventListener('click', () => move(-1));
document.querySelector('[data-next]').addEventListener('click', () => move(1));
strengthButtons.forEach(button => {
button.addEventListener('click', () => setStrength(button.dataset.strengthOption));
});
ratingButtons.forEach(button => {
button.addEventListener('click', () => setRating(Number(button.dataset.ratingOption)));
});
document.querySelectorAll('[data-review-status]').forEach(button => {
button.addEventListener('click', () => review(button.dataset.reviewStatus));
});
@@ -718,6 +1049,14 @@ const pageData = JSON.stringify({
closeEditor();
return;
}
if (target === decisionNoteInput && event.altKey) {
// Decision shortcuts stay live while typing a note: write the reason,
// then commit with the same keys. event.code sidesteps macOS Alt
// remapping inside text inputs.
if (event.code === 'KeyA') { event.preventDefault(); review('approved'); return; }
if (event.code === 'KeyR') { event.preventDefault(); review('rejected'); return; }
if (event.code === 'KeyU') { event.preventDefault(); review('pending'); return; }
}
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.isContentEditable) {
return;
}
@@ -728,6 +1067,12 @@ const pageData = JSON.stringify({
move(1, Boolean(target.closest?.('[data-queue-list]')));
} else if (event.key.toLowerCase() === 'k') {
move(-1, Boolean(target.closest?.('[data-queue-list]')));
} else if (event.key === 'ArrowDown' && !event.altKey && !event.metaKey && !event.ctrlKey) {
event.preventDefault();
move(1, Boolean(target.closest?.('[data-queue-list]')));
} else if (event.key === 'ArrowUp' && !event.altKey && !event.metaKey && !event.ctrlKey) {
event.preventDefault();
move(-1, Boolean(target.closest?.('[data-queue-list]')));
} else if (event.altKey && event.key.toLowerCase() === 'a') {
event.preventDefault();
review('approved');
@@ -742,6 +1087,8 @@ const pageData = JSON.stringify({
undoLastReview();
} else if (event.key.toLowerCase() === 'e') {
openEditor();
} else if (['1', '2', '3'].includes(event.key) && !event.altKey && !event.metaKey && !event.ctrlKey) {
setRating(Number(event.key));
}
});
window.addEventListener('beforeunload', event => {
+3
View File
@@ -0,0 +1,3 @@
---
return Astro.redirect('/labs/live-ui', 301);
---
File diff suppressed because it is too large Load Diff
+615
View File
@@ -0,0 +1,615 @@
/*
* Dev-only Impeccable Live state gallery.
*
* This is a static state harness, not a second implementation of Live. It
* mirrors the exact chrome vocabulary and state wording from:
* skill/scripts/live-browser.js
* skill/scripts/live/ui-core.mjs
* Command labels/icons are injected by LiveUiGallery.astro from the canonical
* skill/scripts/live/vocabulary.mjs export.
*/
const ICONS = Object.freeze({
pick: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/></svg>',
insert: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14"/><path d="M5 12h14"/></svg>',
detect: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>',
chat: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
voice: '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>',
submit: '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>',
tune: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><line x1="4" y1="8" x2="20" y2="8"/><circle cx="14" cy="8" r="2.4" fill="currentColor" stroke="none"/><line x1="4" y1="16" x2="20" y2="16"/><circle cx="10" cy="16" r="2.4" fill="currentColor" stroke="none"/></svg>',
edit: '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>',
trash: '<svg width="12" height="12" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h8"/><path d="M5 4V3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v1"/><path d="M4 4l.5 7a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1L10 4"/></svg>',
exit: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" aria-hidden="true"><line x1="3" y1="3" x2="11" y2="11"/><line x1="11" y1="3" x2="3" y2="11"/></svg>',
});
function parseJson(root, selector, fallback = []) {
try {
return JSON.parse(root.querySelector(selector)?.textContent || '[]');
} catch {
return fallback;
}
}
function escapeHtml(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#039;');
}
function brandMark() {
return '<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M5 2.5 L13.5 2.5 L5.5 21.5 L5 21.5 Q2.5 21.5 2.5 19 L2.5 5 Q2.5 2.5 5 2.5 Z"/><path d="M16.5 2.5 L19 2.5 Q21.5 2.5 21.5 5 L21.5 19 Q21.5 21.5 19 21.5 L8.5 21.5 Z"/></svg>';
}
function designMark() {
return '<span class="lvg-design-mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>';
}
function hostPage({ edited = false, insert = false } = {}) {
return `
<div class="lvg-host-page${insert ? ' has-insert' : ''}">
<p class="lvg-host-kicker">Northstar field journal · Edition 08</p>
<article class="lvg-host-target"${edited ? ' data-edited="true"' : ''}>
<h4>${edited ? 'Useful observations, refined.' : 'Useful observations from the long way around.'}</h4>
<p>Four routes, annotated maps, and practical details for unhurried weekends.</p>
</article>
</div>`;
}
function selectionOutline() {
return '<div class="lvg-selection-outline" aria-hidden="true"></div>';
}
function dots({ arrived = 0, visible = 0, expected = 3, clickable = false } = {}) {
let html = '<span class="lvg-dots" aria-label="Variant progress">';
for (let index = 1; index <= expected; index += 1) {
const active = index === visible;
const pending = index > arrived;
const className = `lvg-dot${active ? ' is-active' : ''}${pending ? ' is-pending' : ''}`;
if (clickable && !pending) {
const target = index === 1 ? 'cycling-progressive' : 'cycling-second';
html += `<button type="button" class="${className}" data-gallery-go="${target}" aria-label="Show variant ${index}"${active ? ' aria-current="true"' : ''}></button>`;
} else {
html += `<i class="${className}" aria-hidden="true"></i>`;
}
}
return html + '</span>';
}
function configureBar({ actionLabel, count, listening = false, locked = false, insert = false, picker = '' } = {}) {
const inputValue = insert
? 'Add a compact proof strip'
: listening
? 'Tighten the hierarchy'
: locked
? ''
: 'Make this feel more confident';
const actionControl = insert ? '' : `
<button type="button" class="lvg-configure-modifier" data-gallery-go="action-picker" aria-haspopup="listbox" aria-expanded="${picker ? 'true' : 'false'}">
${escapeHtml(actionLabel)} <span aria-hidden="true">▾</span>
</button>`;
return `
<div class="lvg-live-context is-configure${picker ? ' has-picker' : ''}"${locked ? ' data-locked="true"' : ''}>
<div class="lvg-configure-row">
<div class="lvg-configure-input-shell">
<button type="button" class="lvg-selection-pill" data-gallery-go="global-ready" aria-label="Clear selection">${insert ? 'slot' : 'article'}</button>
<input class="lvg-configure-input" aria-label="${insert ? 'Describe the new element' : 'Describe the change'}" value="${escapeHtml(inputValue)}"${locked ? ' placeholder="apply is running..." disabled' : ''} />
</div>
<div class="lvg-configure-trailing">
<div class="lvg-configure-modifiers">
${actionControl}
<button type="button" class="lvg-configure-modifier is-count" data-gallery-count aria-label="Change variant count">×${count}</button>
</div>
<button type="button" class="lvg-configure-voice" data-gallery-go="${listening ? 'configure-replace' : 'configure-listening'}" aria-label="${listening ? 'Stop voice input' : 'Voice input'}" aria-pressed="${listening}">${ICONS.voice}</button>
<button type="button" class="lvg-configure-submit" data-gallery-go="generating" aria-label="${insert ? 'Create variants' : 'Generate variants'}">${ICONS.submit}</button>
</div>
</div>
${picker}
</div>`;
}
function actionPicker(commands, selectedAction) {
return `
<div class="lvg-action-picker" role="listbox" aria-label="Design action">
<div class="lvg-action-grid">
${commands.map((command) => `
<button type="button" class="lvg-action-chip" role="option" data-gallery-action="${escapeHtml(command.value)}" aria-pressed="${command.value === selectedAction}">
<span>${command.icon}</span><span>${escapeHtml(command.label)}</span>
</button>`).join('')}
</div>
</div>`;
}
function generatingBar(recovery = false, actionLabel = 'Freeform') {
return `
<div class="lvg-live-context">
<div class="lvg-generation-row">
<span class="lvg-generation-label">${escapeHtml(actionLabel)}</span>
${dots({ expected: 3 })}
<span class="lvg-generation-status">${recovery ? 'Variants ready. Reveal the selected element to resume.' : 'Source ready. Generating...'}</span>
</div>
</div>`;
}
function tuneButton(open) {
return `
<button type="button" class="lvg-tune-button" data-gallery-go="${open ? 'cycling-second' : 'tune-open'}" aria-expanded="${open}">
${ICONS.tune}<span>Tune</span><span class="lvg-tune-badge">3</span>
</button>`;
}
function cyclingBar({ arrived = 1, visible = 1, tune = false } = {}) {
const remaining = 3 - arrived;
return `
<div class="lvg-live-context${tune ? ' has-tune' : ''}">
<div class="lvg-cycling-row">
<button type="button" class="lvg-nav-button" data-gallery-go="cycling-progressive" aria-label="Previous variant"${visible <= 1 ? ' disabled' : ''}>←</button>
${dots({ arrived, visible, expected: 3, clickable: true })}
<span class="lvg-variant-counter">${visible}/3</span>
<button type="button" class="lvg-nav-button" data-gallery-go="cycling-second" aria-label="Next variant"${visible >= arrived ? ' disabled' : ''}>→</button>
${tuneButton(tune)}
<span class="lvg-cycling-spacer"></span>
${remaining > 0 ? `<span class="lvg-arrival-progress">${remaining} more arriving...</span>` : ''}
<button type="button" class="lvg-accept" data-gallery-go="applying">✓ Accept</button>
<button type="button" class="lvg-discard" data-gallery-go="global-ready" aria-label="Discard all variants" title="Discard all variants">✕</button>
</div>
${tune ? tunePanel() : ''}
</div>`;
}
function tunePanel() {
return `
<div class="lvg-tune-panel">
<div class="lvg-tune-grid">
<div class="lvg-param">
<div class="lvg-param-header"><strong>Color amount</strong><output data-gallery-range-output>0.60</output></div>
<input type="range" min="0" max="1" step="0.05" value="0.6" data-gallery-range aria-label="Color amount" />
</div>
<div class="lvg-param">
<div class="lvg-param-header"><strong>Density</strong><output data-gallery-density-output>Snug</output></div>
<div class="lvg-param-steps" role="group" aria-label="Density">
<button type="button" data-gallery-density="Airy" aria-pressed="false">Airy</button>
<button type="button" data-gallery-density="Snug" aria-pressed="true">Snug</button>
<button type="button" data-gallery-density="Packed" aria-pressed="false">Packed</button>
</div>
</div>
<div class="lvg-param">
<div class="lvg-param-header"><strong>Motion</strong><output data-gallery-toggle-output>Off</output></div>
<button type="button" class="lvg-param-toggle" data-gallery-param-toggle aria-label="Motion" aria-pressed="false"></button>
</div>
</div>
</div>`;
}
function statusBar(kind) {
if (kind === 'confirmed') {
return '<div class="lvg-live-context is-confirmed"><div class="lvg-status-row"><span aria-hidden="true">✓</span><span>Variant applied</span></div></div>';
}
return '<div class="lvg-live-context"><div class="lvg-status-row"><i class="lvg-spinner" aria-hidden="true"></i><span>Applying variant...</span></div></div>';
}
function editBadge(editing = false) {
if (!editing) {
return `<div class="lvg-edit-badge"><button type="button" class="is-icon" data-gallery-go="edit-copy" aria-label="Edit copy" title="Edit copy">${ICONS.edit}</button></div>`;
}
return '<div class="lvg-edit-badge"><button type="button" data-gallery-go="configure-replace">Cancel</button><button type="button" class="is-primary" data-gallery-go="copy-pending">Save</button></div>';
}
function annotationLayer() {
return `
<div class="lvg-annotation-layer">
<svg viewBox="0 0 404 150" aria-hidden="true"><path d="M44 106 C82 74, 136 83, 175 105 S267 135, 322 90"/></svg>
<button type="button" class="lvg-annotation-clear" data-gallery-go="configure-replace">Clear</button>
<div class="lvg-annotation-pin"><span>Keep this line on one row</span></div>
</div>`;
}
function pendingDock(kind) {
if (kind === 'attention') {
return `
<div class="lvg-pending-dock">
<button type="button" class="lvg-pending-pill" disabled>Apply needs attention</button>
<button type="button" class="lvg-pending-decision is-primary" data-gallery-go="copy-applying">Keep fixing</button>
<button type="button" class="lvg-pending-decision" data-gallery-go="copy-pending">Rollback</button>
</div>`;
}
const applying = kind === 'applying';
return `
<div class="lvg-pending-dock">
<button type="button" class="lvg-pending-pill" data-gallery-go="${applying ? 'copy-attention' : 'copy-applying'}" aria-busy="${applying}"${applying ? ' disabled' : ''}>
${applying ? '<i class="lvg-pending-spinner" aria-hidden="true"></i><span>Applying 3 copy edits</span>' : '<span>Apply copy edits</span><span class="lvg-pending-count">3</span>'}
</button>
<button type="button" class="lvg-pending-trash" data-gallery-go="global-ready" aria-label="Discard copy edits on this page">${ICONS.trash}</button>
</div>`;
}
function designPanel(tab = 'visual') {
const raw = tab === 'raw';
return `
<aside class="lvg-design-panel" aria-label="DESIGN.md panel">
<header class="lvg-design-header">
<span class="lvg-design-title">DESIGN.md</span>
<div class="lvg-design-tabs" role="tablist" aria-label="Design system view">
<button type="button" role="tab" data-gallery-design-tab="visual" aria-selected="${!raw}">Visual</button>
<button type="button" role="tab" data-gallery-design-tab="raw" aria-selected="${raw}">Raw</button>
</div>
<button type="button" class="lvg-design-close" data-gallery-go="global-tools" aria-label="Close panel">✕</button>
</header>
<div class="lvg-design-body">
${raw ? `
<div class="lvg-design-tile">
<div class="lvg-design-meta"><strong>Neo Kinpaku</strong><span>Raw</span></div>
<p class="lvg-design-copy"># Design System: Impeccable<br><br>Dark lacquer, kinpaku gold, and precise technical geometry.</p>
</div>` : `
<div class="lvg-design-tile">
<div class="lvg-design-meta"><strong>Kinpaku Gold</strong><span>Primary</span></div>
<div class="lvg-design-swatch"></div>
<p class="lvg-design-copy">Primary accent for commitment, active controls, and the Impeccable mark.</p>
</div>
<div class="lvg-design-tile">
<div class="lvg-design-meta"><strong>Display</strong><span>Typography</span></div>
<p class="lvg-design-type">Useful observations</p>
<p class="lvg-design-copy">Alumni Sans · precise, light, and deliberately geometric.</p>
</div>`}
</div>
</aside>`;
}
function globalBar({ connected = true, workerFallback = false, active = 'pick', steer = 'collapsed', detectCount = 0, designActive = false } = {}) {
const modeButton = (key, icon, label, target, extra = '') => {
const isActive = active === key || (key === 'design' && designActive);
return `<button type="button" class="lvg-global-mode" data-active="${isActive}" data-gallery-go="${target}" aria-label="${escapeHtml(label)}">${icon}${isActive ? `<span>${escapeHtml(label)}</span>` : ''}${extra}</button>`;
};
const steerHtml = steer === 'processing'
? `<div class="lvg-steer" data-expanded="true" data-processing="true" aria-busy="true" aria-label="Processing steer request"><span class="lvg-steer-icon">${ICONS.chat}</span><span class="lvg-steer-dots" aria-hidden="true"><i></i><i></i><i></i></span></div>`
: steer === 'expanded'
? `<div class="lvg-steer" data-expanded="true"><span class="lvg-steer-icon">${ICONS.chat}</span><input type="text" value="Make the page hierarchy more decisive" data-gallery-steer-input aria-label="Steer the page"/><button type="button" class="lvg-steer-voice" aria-label="Voice input">${ICONS.voice}</button></div>`
: `<button type="button" class="lvg-steer" data-gallery-go="steer-expanded" aria-label="Steer the page"><span class="lvg-steer-icon">${ICONS.chat}</span><span class="lvg-steer-hint">Steer</span><span class="lvg-steer-voice">${ICONS.voice}</span></button>`;
return `
<div class="lvg-live-global">
<span class="lvg-live-brand" data-connected="${connected}" data-worker-fallback="${workerFallback}" role="img" aria-label="Impeccable live mode${!connected ? ' - agent not polling' : workerFallback ? ' - using foreground generation' : ''}">
${brandMark()}${connected && !workerFallback ? '' : '<i class="lvg-agent-dot" aria-hidden="true"></i>'}
</span>
<div class="lvg-global-inner">
${modeButton('pick', ICONS.pick, 'Pick', 'configure-replace')}
${modeButton('insert', ICONS.insert, 'Insert', 'insert-placeholder')}
${modeButton('detect', ICONS.detect, 'Detect', 'global-tools', detectCount ? `<span class="lvg-detect-badge">${detectCount}</span>` : '')}
${modeButton('design', designMark(), 'DESIGN.md', 'design-panel')}
${steerHtml}
<span class="lvg-global-divider" aria-hidden="true"></span>
<button type="button" class="lvg-global-exit" data-gallery-go="global-ready" aria-label="Exit live mode" title="Exit live mode">${ICONS.exit}</button>
</div>
</div>`;
}
function sceneFor(state, context) {
const actionLabel = context.commands.find((command) => command.value === context.selectedAction)?.label || 'Freeform';
const commonGlobal = (opts = {}) => globalBar({ active: 'pick', ...opts });
switch (state) {
case 'global-disconnected':
return hostPage() + '<div class="lvg-agent-tooltip" role="tooltip">Agent disconnected - run live-poll.mjs to connect</div>' + commonGlobal({ connected: false });
case 'global-foreground':
return hostPage()
+ '<div class="lvg-live-toast" role="status">Codex CLI is unavailable. Live is using the main agent, so generation may take longer. Install the CLI, run codex login, then restart Live.</div>'
+ commonGlobal({ workerFallback: true });
case 'global-tools':
return hostPage() + commonGlobal({ active: 'detect', detectCount: 7, designActive: true });
case 'steer-expanded':
return hostPage() + commonGlobal({ steer: 'expanded' });
case 'steer-processing':
return hostPage() + commonGlobal({ steer: 'processing' });
case 'configure-replace':
return hostPage() + selectionOutline() + editBadge(false) + configureBar({ actionLabel, count: context.count }) + commonGlobal();
case 'action-picker':
return hostPage() + selectionOutline() + editBadge(false) + configureBar({ actionLabel, count: context.count, picker: actionPicker(context.commands, context.selectedAction) }) + commonGlobal();
case 'configure-listening':
return hostPage() + selectionOutline() + editBadge(false) + configureBar({ actionLabel, count: context.count, listening: true }) + commonGlobal();
case 'configure-locked':
return hostPage({ edited: true }) + selectionOutline() + editBadge(false) + configureBar({ actionLabel, count: context.count, locked: true }) + pendingDock('applying') + commonGlobal();
case 'annotation':
return hostPage() + selectionOutline() + annotationLayer() + configureBar({ actionLabel, count: context.count }) + commonGlobal();
case 'insert-placeholder':
return hostPage({ insert: true }) + '<div class="lvg-insert-placeholder" aria-label="Insert placeholder"></div>' + configureBar({ count: context.count, insert: true }) + globalBar({ active: 'insert' });
case 'generating':
return hostPage() + selectionOutline() + generatingBar(false, actionLabel) + commonGlobal();
case 'generation-recovery':
return hostPage() + generatingBar(true, actionLabel) + commonGlobal();
case 'cycling-progressive':
return hostPage() + selectionOutline() + cyclingBar({ arrived: 1, visible: 1 }) + commonGlobal();
case 'cycling-second':
return hostPage() + selectionOutline() + cyclingBar({ arrived: 2, visible: 2 }) + commonGlobal();
case 'tune-open':
return hostPage() + selectionOutline() + cyclingBar({ arrived: 3, visible: 2, tune: true }) + commonGlobal();
case 'applying':
return hostPage() + selectionOutline() + statusBar('applying') + commonGlobal();
case 'confirmed':
return hostPage({ edited: true }) + statusBar('confirmed') + commonGlobal();
case 'edit-copy':
return hostPage({ edited: true }) + selectionOutline() + editBadge(true) + configureBar({ actionLabel, count: context.count }) + commonGlobal();
case 'copy-pending':
return hostPage({ edited: true }) + pendingDock('pending') + commonGlobal();
case 'copy-applying':
return hostPage({ edited: true }) + pendingDock('applying') + commonGlobal();
case 'copy-attention':
return hostPage({ edited: true }) + pendingDock('attention') + commonGlobal();
case 'design-panel':
return hostPage() + designPanel(context.designTab) + globalBar({ active: 'design', designActive: true });
case 'toast-error':
return hostPage() + '<div class="lvg-live-toast" role="alert">No variants were mounted. Please try again.</div>' + commonGlobal();
case 'global-ready':
default:
return hostPage() + commonGlobal();
}
}
function syncPreviewTargetGeometry(scope) {
scope.querySelectorAll('.live-ui-stage').forEach((stage) => {
const target = stage.querySelector('.lvg-host-target');
if (!target) return;
const stageRect = stage.getBoundingClientRect();
const targetRect = target.getBoundingClientRect();
const targetBox = {
top: targetRect.top - stageRect.top + stage.scrollTop,
left: targetRect.left - stageRect.left + stage.scrollLeft,
width: targetRect.width,
height: targetRect.height,
};
const outline = stage.querySelector('.lvg-selection-outline');
if (outline) {
Object.assign(outline.style, {
top: `${targetBox.top - 2}px`,
left: `${targetBox.left - 2}px`,
width: `${targetBox.width + 4}px`,
height: `${targetBox.height + 4}px`,
});
}
const annotationLayer = stage.querySelector('.lvg-annotation-layer');
if (annotationLayer) {
Object.assign(annotationLayer.style, {
top: `${targetBox.top}px`,
left: `${targetBox.left}px`,
width: `${targetBox.width}px`,
height: `${targetBox.height}px`,
});
}
const editBadge = stage.querySelector('.lvg-edit-badge');
if (editBadge) {
const badgeWidth = editBadge.getBoundingClientRect().width;
const outlineRight = targetBox.left + targetBox.width + 2;
Object.assign(editBadge.style, {
top: `${Math.max(4, targetBox.top - 28)}px`,
left: `${Math.min(stage.clientWidth - badgeWidth - 4, outlineRight - badgeWidth)}px`,
});
}
});
}
function initLiveUiGallery(root) {
if (!root || root.dataset.galleryReady === 'true') return;
root.dataset.galleryReady = 'true';
const groups = parseJson(root, '[data-live-gallery-groups]');
const commands = parseJson(root, '[data-live-gallery-vocabulary]');
if (groups.length === 0 || commands.length === 0) return;
const states = groups.flatMap((group) => group.states);
const cluster = root.querySelector('[data-gallery-cluster]');
const groupIndexReadout = root.querySelector('[data-gallery-group-index]');
const groupLabelReadout = root.querySelector('[data-gallery-group-label]');
const groupSummaryReadout = root.querySelector('[data-gallery-group-summary]');
let selectedAction = 'impeccable';
let count = 3;
let designTab = 'visual';
let host = 'light';
let geometryFrame = 0;
const scheduleGeometrySync = () => {
cancelAnimationFrame(geometryFrame);
geometryFrame = requestAnimationFrame(() => {
geometryFrame = 0;
syncPreviewTargetGeometry(root);
});
};
const geometryObserver = typeof ResizeObserver === 'function'
? new ResizeObserver(scheduleGeometrySync)
: null;
geometryObserver?.observe(root);
document.fonts?.ready?.then(scheduleGeometrySync);
const groupByKey = (key) => groups.find((group) => group.key === key);
const stateByKey = (key) => states.find((state) => state.key === key);
const groupForState = (key) => groups.find((group) => group.states.some((state) => state.key === key));
const currentGroup = () => groupByKey(root.dataset.galleryActiveGroup) || groups[0];
const currentTarget = () => root.dataset.galleryTarget || currentGroup().key;
const targetFromHash = () => {
let key = '';
try {
key = decodeURIComponent(window.location.hash.slice(1));
} catch {
return null;
}
return groupByKey(key) || stateByKey(key) ? key : null;
};
function renderGroup(groupKey, { targetState = null, updateHash = true, focusGroup = false } = {}) {
const group = groupByKey(groupKey) || groups[0];
const groupIndex = groups.indexOf(group);
const validTargetState = group.states.some((state) => state.key === targetState) ? targetState : null;
const target = validTargetState || group.key;
root.dataset.galleryActiveGroup = group.key;
root.dataset.galleryTarget = target;
root.dataset.galleryActiveHost = host;
root.querySelectorAll('[data-gallery-group]').forEach((button) => {
button.setAttribute('aria-current', button.dataset.galleryGroup === group.key ? 'true' : 'false');
});
root.querySelectorAll('[data-gallery-host]').forEach((button) => {
button.setAttribute('aria-pressed', button.dataset.galleryHost === host ? 'true' : 'false');
});
if (groupIndexReadout) groupIndexReadout.textContent = `Group ${String(groupIndex + 1).padStart(2, '0')}`;
if (groupLabelReadout) groupLabelReadout.textContent = group.label;
if (groupSummaryReadout) groupSummaryReadout.textContent = group.summary;
if (cluster) {
cluster.setAttribute('aria-label', `${group.label} states on ${host} host`);
cluster.innerHTML = group.states.map((state, index) => {
const targeted = state.key === validTargetState;
return `
<article class="live-ui-gallery__state-card${targeted ? ' is-targeted' : ''}" role="listitem" id="state-${escapeHtml(state.key)}" data-gallery-state-card="${escapeHtml(state.key)}">
<header>
<div>
<span>${String(index + 1).padStart(2, '0')}</span>
<h3>${escapeHtml(state.label)}</h3>
</div>
<code>${escapeHtml(state.surface)}</code>
</header>
<div class="live-ui-stage is-${host}" data-live-gallery-preview="${host}" data-gallery-state="${escapeHtml(state.key)}" aria-label="${escapeHtml(`${host} host — ${state.label}`)}">
${sceneFor(state.key, { commands, selectedAction, count, designTab })}
</div>
</article>`;
}).join('');
syncPreviewTargetGeometry(cluster);
}
if (updateHash && window.location.hash !== `#${target}`) {
window.history.replaceState(null, '', `#${target}`);
}
if (focusGroup) root.querySelector(`[data-gallery-group="${group.key}"]`)?.focus();
if (validTargetState) {
requestAnimationFrame(() => {
root.querySelector(`[data-gallery-state-card="${validTargetState}"]`)?.scrollIntoView({ block: 'nearest' });
});
}
}
function renderTarget(target, options = {}) {
const group = groupByKey(target);
if (group) {
renderGroup(group.key, options);
return;
}
const state = stateByKey(target);
const stateGroup = state ? groupForState(state.key) : null;
if (state && stateGroup) {
renderGroup(stateGroup.key, { ...options, targetState: state.key });
return;
}
renderGroup(groups[0].key, options);
}
root.addEventListener('click', (event) => {
const groupButton = event.target.closest('button[data-gallery-group]');
if (groupButton) {
renderGroup(groupButton.dataset.galleryGroup);
return;
}
const hostButton = event.target.closest('button[data-gallery-host]');
if (hostButton) {
host = hostButton.dataset.galleryHost === 'dark' ? 'dark' : 'light';
const target = currentTarget();
renderTarget(target, { updateHash: false });
return;
}
const actionButton = event.target.closest('[data-gallery-action]');
if (actionButton) {
selectedAction = actionButton.dataset.galleryAction || 'impeccable';
renderTarget('configure-replace');
return;
}
const countButton = event.target.closest('[data-gallery-count]');
if (countButton) {
count = count >= 4 ? 1 : count + 1;
renderTarget(currentTarget(), { updateHash: false });
return;
}
const densityButton = event.target.closest('[data-gallery-density]');
if (densityButton) {
const value = densityButton.dataset.galleryDensity;
root.querySelectorAll('[data-gallery-density]').forEach((button) => {
button.setAttribute('aria-pressed', button.dataset.galleryDensity === value ? 'true' : 'false');
});
root.querySelectorAll('[data-gallery-density-output]').forEach((output) => { output.textContent = value; });
return;
}
const toggleButton = event.target.closest('[data-gallery-param-toggle]');
if (toggleButton) {
const next = toggleButton.getAttribute('aria-pressed') !== 'true';
root.querySelectorAll('[data-gallery-param-toggle]').forEach((button) => button.setAttribute('aria-pressed', String(next)));
root.querySelectorAll('[data-gallery-toggle-output]').forEach((output) => { output.textContent = next ? 'On' : 'Off'; });
return;
}
const designTabButton = event.target.closest('[data-gallery-design-tab]');
if (designTabButton) {
designTab = designTabButton.dataset.galleryDesignTab === 'raw' ? 'raw' : 'visual';
renderTarget('design-panel');
return;
}
const trigger = event.target.closest('[data-gallery-go]');
if (trigger && !trigger.disabled) renderTarget(trigger.dataset.galleryGo);
});
root.addEventListener('input', (event) => {
const range = event.target.closest('[data-gallery-range]');
if (!range) return;
const value = Number(range.value).toFixed(2);
root.querySelectorAll('[data-gallery-range]').forEach((input) => { if (input !== range) input.value = range.value; });
root.querySelectorAll('[data-gallery-range-output]').forEach((output) => { output.textContent = value; });
});
root.addEventListener('keydown', (event) => {
if (event.target.matches('[data-gallery-steer-input]') && event.key === 'Enter') {
event.preventDefault();
renderTarget('steer-processing');
return;
}
const groupButton = event.target.closest('button[data-gallery-group]');
if (groupButton && ['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(event.key)) {
event.preventDefault();
const current = Math.max(0, groups.findIndex((group) => group.key === groupButton.dataset.galleryGroup));
const next = event.key === 'Home'
? 0
: event.key === 'End'
? groups.length - 1
: (current + (event.key === 'ArrowUp' ? -1 : 1) + groups.length) % groups.length;
renderGroup(groups[next].key, { focusGroup: true });
}
});
window.addEventListener('hashchange', () => {
const target = targetFromHash();
if (target) renderTarget(target, { updateHash: false });
});
renderTarget(targetFromHash() || groups[0].key);
}
function initLiveUiGalleries() {
document.querySelectorAll('[data-live-ui-gallery]').forEach(initLiveUiGallery);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initLiveUiGalleries, { once: true });
} else {
initLiveUiGalleries();
}
document.addEventListener('astro:page-load', initLiveUiGalleries);
File diff suppressed because it is too large Load Diff
+156
View File
@@ -0,0 +1,156 @@
@import "./sub-pages.css";
.live-ui-lab-page {
overflow: hidden;
background: var(--ks-lacquer);
}
.live-ui-lab {
display: grid;
grid-template-rows: 64px minmax(0, 1fr);
height: 100dvh;
min-height: 0;
color: var(--ks-text);
background: var(--ks-lacquer);
}
.live-ui-lab__bar {
position: relative;
z-index: 10;
display: grid;
grid-template-columns: auto auto minmax(12rem, 1fr) auto;
gap: var(--spacing-md);
align-items: center;
min-width: 0;
padding: 8px var(--spacing-sm);
background: var(--ks-lacquer-deep);
border-bottom: 1px solid var(--ks-rule);
}
.live-ui-lab__brand {
gap: 9px;
color: var(--ks-kinpaku);
text-decoration: none;
}
.live-ui-lab__brand .ks-mark {
width: 30px;
height: 30px;
}
.live-ui-lab__brand .ks-mark svg {
width: 26px;
height: 26px;
}
.live-ui-lab__heading {
min-width: 0;
padding-left: var(--spacing-md);
border-left: 1px solid var(--ks-rule);
}
.live-ui-lab__heading span {
display: block;
color: var(--ks-patina);
font-family: var(--ks-mono);
font-size: var(--ks-type-eyebrow-size);
font-weight: 500;
letter-spacing: 0.12em;
line-height: 1.2;
text-transform: uppercase;
}
.live-ui-lab__heading h1 {
margin-top: 3px;
color: var(--ks-champagne);
font-family: var(--ks-font);
font-size: var(--ks-type-title-size);
font-weight: 600;
line-height: 1.2;
}
.live-ui-lab__bar > p {
min-width: 0;
overflow: hidden;
color: var(--ks-text-muted);
font-size: var(--ks-type-mono-size);
line-height: 1.4;
text-overflow: ellipsis;
white-space: nowrap;
}
.live-ui-lab__back {
display: inline-flex;
gap: 8px;
align-items: center;
min-height: 36px;
padding: 0 12px;
color: var(--ks-kinpaku);
border: 1px solid var(--ks-rule);
border-radius: 3px;
font-size: var(--ks-type-mono-size);
font-weight: 600;
text-decoration: none;
}
.live-ui-lab__back:hover {
border-color: var(--ks-kinpaku);
}
.live-ui-lab__brand:focus-visible,
.live-ui-lab__back:focus-visible {
outline: 2px solid var(--ks-patina);
outline-offset: 3px;
}
.live-ui-lab__canvas {
min-width: 0;
min-height: 0;
padding: 8px;
overflow: hidden;
}
.live-ui-lab__canvas > .live-ui-gallery {
border: 1px solid var(--ks-rule);
border-radius: 4px;
}
@media (max-width: 900px) {
.live-ui-lab__bar {
grid-template-columns: auto minmax(0, 1fr) auto;
}
.live-ui-lab__bar > p {
display: none;
}
}
@media (max-width: 580px) {
.live-ui-lab {
grid-template-rows: 58px minmax(0, 1fr);
}
.live-ui-lab__bar {
gap: 10px;
padding-inline: 10px;
}
.live-ui-lab__brand .ks-wordmark,
.live-ui-lab__heading span,
.live-ui-lab__back span {
display: none;
}
.live-ui-lab__heading {
padding-left: 10px;
}
.live-ui-lab__back {
min-height: 34px;
padding-inline: 9px;
}
.live-ui-lab__canvas {
padding: 4px;
}
}
+414 -110
View File
@@ -97,7 +97,7 @@
.worlds-title-lockup p,
.worlds-concept-kicker,
.worlds-queue-head p,
.worlds-context-panel p,
.worlds-concept-meta p,
.worlds-edit-form label > span,
.worlds-edit-form legend {
color: var(--ks-text-muted);
@@ -116,6 +116,37 @@
line-height: var(--ks-type-title-line);
}
.worlds-catalog-switch {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: var(--spacing-md);
border: 1px solid var(--ks-rule);
border-radius: 3px;
overflow: hidden;
}
.worlds-catalog-switch a {
padding: 9px 10px;
color: var(--ks-text-muted);
font: 500 var(--ks-type-mono-size)/1 var(--ks-font);
text-align: center;
text-decoration: none;
}
.worlds-catalog-switch a + a {
border-left: 1px solid var(--ks-rule);
}
.worlds-catalog-switch a:hover {
background: var(--ks-graphite);
color: var(--ks-champagne);
}
.worlds-catalog-switch a[aria-current="page"] {
background: var(--ks-kinpaku);
color: var(--ks-dark-ink);
}
.worlds-progress-block {
margin-top: var(--spacing-md);
}
@@ -148,7 +179,7 @@
background: var(--ks-patina);
}
.worlds-count-grid {
.worlds-status-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-top: var(--spacing-sm);
@@ -156,44 +187,62 @@
border-bottom: 1px solid var(--ks-rule);
}
.worlds-count-grid div {
min-width: 0;
padding: 10px 5px;
border-right: 1px solid var(--ks-rule);
.worlds-status-grid button:last-child:nth-child(odd) {
grid-column: 1 / -1;
}
.worlds-count-grid div:nth-child(2n) {
.worlds-status-grid button {
display: block;
min-width: 0;
min-height: 44px;
padding: 10px 8px;
border: 0;
border-right: 1px solid var(--ks-rule);
background: transparent;
color: var(--ks-text-faint);
font: 500 var(--ks-type-eyebrow-size)/1.2 var(--ks-mono);
text-align: left;
cursor: pointer;
}
.worlds-status-grid button:nth-child(2n) {
border-right: 0;
}
.worlds-count-grid div:nth-child(-n + 2) {
.worlds-status-grid button:nth-child(-n + 2) {
border-bottom: 1px solid var(--ks-rule);
}
.worlds-count-grid dt {
.worlds-status-grid button span {
display: block;
overflow: hidden;
color: var(--ks-text-faint);
font-family: var(--ks-mono);
font-size: var(--ks-type-eyebrow-size);
text-overflow: ellipsis;
white-space: nowrap;
}
.worlds-count-grid dd {
.worlds-status-grid button strong {
display: block;
margin-top: 3px;
color: var(--ks-champagne);
font-family: var(--ks-mono);
font-size: var(--ks-type-mono-size);
font-weight: 500;
}
.worlds-status-tabs {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 4px;
margin-top: var(--spacing-sm);
.worlds-status-grid button:hover {
background: var(--ks-graphite);
color: var(--ks-text-muted);
}
.worlds-status-grid button[aria-pressed="true"] {
background: color-mix(in oklch, var(--ks-kinpaku), transparent 88%);
box-shadow: inset 0 -2px 0 var(--ks-kinpaku);
color: var(--ks-kinpaku);
}
.worlds-status-grid button[aria-pressed="true"] strong {
color: var(--ks-kinpaku);
}
.worlds-status-tabs button,
.worlds-family-button {
min-height: 44px;
border: 1px solid transparent;
@@ -204,17 +253,20 @@
cursor: pointer;
}
.worlds-status-tabs button:hover,
.worlds-family-button:hover {
background: var(--ks-graphite);
color: var(--ks-champagne);
}
.worlds-status-tabs button:focus-visible,
.worlds-status-grid button:focus-visible,
.worlds-decision-note:focus-visible,
.worlds-strength-toggle button:focus-visible,
.worlds-rating button:focus-visible,
.worlds-family-button:focus-visible,
.worlds-queue-item:focus-visible,
.worlds-review-actions button:focus-visible,
.worlds-context-panel button:focus-visible,
.worlds-concept-meta button:focus-visible,
.worlds-concept-card a:focus-visible,
.worlds-queue-foot button:focus-visible,
.worlds-edit-form button:focus-visible,
.worlds-edit-form input:focus-visible,
@@ -229,12 +281,6 @@
outline-offset: 2px;
}
.worlds-status-tabs button[aria-pressed="true"] {
border-color: var(--ks-kinpaku);
background: var(--ks-kinpaku);
color: var(--ks-dark-ink);
}
.worlds-family-scroll {
min-height: 0;
overflow: auto;
@@ -242,6 +288,19 @@
scrollbar-color: var(--ks-graphite-2) transparent;
}
.worlds-well-heading {
margin: var(--spacing-md) 0 5px;
padding: 0 10px;
color: var(--ks-text-faint);
font: 500 var(--ks-type-eyebrow-size)/1.2 var(--ks-mono);
letter-spacing: var(--ks-type-eyebrow-track);
text-transform: uppercase;
}
.worlds-well-heading:first-child {
margin-top: 0;
}
.worlds-family-search {
display: block;
flex: 0 0 auto;
@@ -279,7 +338,8 @@
gap: var(--spacing-sm);
align-items: center;
width: 100%;
padding: 8px 10px;
min-height: 0;
padding: 4px 10px;
text-align: left;
}
@@ -393,16 +453,14 @@
gap: 10px;
}
.worlds-runtime-state > span,
.worlds-action-note > span {
.worlds-runtime-state > span {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--ks-text-faint);
}
.worlds-runtime-state > span.is-live,
.worlds-action-note > span.is-live {
.worlds-runtime-state > span.is-live {
background: var(--ks-patina);
box-shadow: 0 0 16px color-mix(in oklch, var(--ks-patina), transparent 62%);
}
@@ -566,8 +624,13 @@
}
.worlds-queue-foot button {
width: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
min-width: 44px;
height: 44px;
padding: 0 8px;
border: 1px solid var(--ks-rule);
border-radius: 3px;
background: var(--ks-graphite);
@@ -593,6 +656,16 @@ kbd {
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
}
/* Inside a button the chip would nest box-in-box; the hint becomes quiet text. */
button kbd {
min-width: 0;
min-height: 0;
border: 0;
border-radius: 0;
background: transparent;
color: color-mix(in oklch, currentColor, transparent 35%);
}
.worlds-review-stage {
position: relative;
display: grid;
@@ -671,39 +744,112 @@ kbd {
.worlds-concept-body {
position: relative;
display: grid;
grid-template-columns: 64px minmax(0, 1fr) 260px;
gap: var(--spacing-lg);
grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
gap: clamp(28px, 3vw, 56px);
min-height: 0;
overflow: auto;
align-items: center;
padding: clamp(40px, 8vh, 104px) clamp(32px, 6vw, 96px);
align-items: start;
align-content: start;
padding: clamp(24px, 4vh, 48px) clamp(24px, 3vw, 56px);
}
.worlds-concept-index {
align-self: stretch;
.worlds-review-meta {
display: flex;
flex-direction: column;
flex: 0 0 auto;
align-items: center;
gap: 10px;
gap: var(--spacing-sm);
}
.worlds-review-folio {
color: var(--ks-text-faint);
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
writing-mode: vertical-rl;
letter-spacing: 0.08em;
text-transform: uppercase;
white-space: nowrap;
}
.worlds-concept-index i {
width: 1px;
flex: 1 1 auto;
background: linear-gradient(var(--ks-kinpaku), transparent);
.worlds-strength-toggle {
display: inline-flex;
border: 1px solid var(--ks-rule);
border-radius: 999px;
overflow: hidden;
}
.worlds-concept-index strong {
.worlds-rating {
display: inline-flex;
gap: 2px;
}
.worlds-rating button {
min-height: 26px;
padding: 0 3px;
border: 0;
background: transparent;
color: var(--ks-text-faint);
font-size: 15px;
line-height: 1;
cursor: pointer;
}
.worlds-rating button:hover:not(:disabled) {
color: var(--ks-text-muted);
}
.worlds-rating button[aria-pressed="true"],
.worlds-rating button[aria-pressed="true"]:hover:not(:disabled) {
color: var(--ks-patina);
}
.worlds-rating button[aria-pressed="true"]:hover:not(:disabled) {
opacity: 0.72;
}
.worlds-rating button:disabled {
cursor: default;
}
.worlds-strength-toggle button {
min-height: 26px;
padding: 0 10px;
border: 0;
background: transparent;
color: var(--ks-text-faint);
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
letter-spacing: 0.06em;
text-transform: uppercase;
white-space: nowrap;
cursor: pointer;
}
.worlds-strength-toggle button + button {
border-left: 1px solid var(--ks-rule);
}
.worlds-strength-toggle button:hover:not(:disabled) {
background: var(--ks-graphite);
color: var(--ks-text-muted);
}
.worlds-strength-toggle button[aria-pressed="true"] {
background: color-mix(in oklch, var(--ks-patina), transparent 86%);
color: var(--ks-patina);
}
.worlds-strength-toggle button:disabled {
cursor: not-allowed;
}
.worlds-strength-toggle button:disabled[aria-pressed="false"] {
color: var(--ks-text-mute-deep);
}
.worlds-review-folio strong {
color: var(--ks-kinpaku);
font: inherit;
}
.worlds-concept-copy {
min-width: 0;
max-width: 900px;
}
.worlds-concept-kicker {
@@ -711,22 +857,78 @@ kbd {
}
.worlds-concept-copy h2 {
max-width: 22ch;
margin-top: var(--spacing-sm);
max-width: 44ch;
margin-top: var(--spacing-xs);
color: var(--ks-champagne);
font-family: var(--ks-font);
font-size: var(--ks-type-headline-size);
font-size: calc(var(--ks-type-title-size) * 1.6);
font-weight: 400;
line-height: 1.12;
letter-spacing: -0.02em;
line-height: 1.3;
letter-spacing: -0.01em;
text-wrap: balance;
}
.worlds-creative-spark {
max-width: 74ch;
margin-top: var(--spacing-md);
padding: var(--spacing-sm) var(--spacing-md);
border-left: 2px solid var(--ks-kinpaku);
background: color-mix(in oklch, var(--ks-kinpaku), transparent 94%);
}
.worlds-creative-spark p {
color: var(--ks-kinpaku);
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
letter-spacing: var(--ks-type-eyebrow-track);
text-transform: uppercase;
}
.worlds-creative-spark strong {
display: block;
margin-top: var(--spacing-xs);
color: var(--ks-champagne);
font-size: var(--ks-type-body-size);
font-weight: 500;
line-height: var(--ks-type-body-line);
}
.worlds-concept-evidence {
min-width: 0;
}
.worlds-concept-card {
border: 1px solid var(--ks-rule);
background: var(--ks-lacquer-raised);
}
.worlds-concept-card + .worlds-concept-card {
margin-top: var(--spacing-sm);
}
.worlds-concept-card a {
display: block;
}
.worlds-concept-card img {
display: block;
width: 100%;
height: auto;
}
.worlds-concept-card figcaption {
padding: var(--spacing-xs) var(--spacing-sm);
border-top: 1px solid var(--ks-rule);
color: var(--ks-patina);
font: 500 var(--ks-type-eyebrow-size)/1.4 var(--ks-mono);
letter-spacing: var(--ks-type-eyebrow-track);
text-transform: uppercase;
}
.worlds-tag-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: var(--spacing-lg);
margin-top: var(--spacing-md);
}
.worlds-tag-row span {
@@ -738,18 +940,65 @@ kbd {
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
}
.worlds-context-panel {
align-self: center;
border-left: 1px solid var(--ks-rule);
background: color-mix(in oklch, var(--ks-lacquer-raised), transparent 8%);
}
.worlds-context-panel > div {
padding: var(--spacing-sm);
.worlds-system-contract {
display: grid;
grid-template-columns: 1fr;
margin-top: var(--spacing-md);
border-top: 1px solid var(--ks-rule);
border-bottom: 1px solid var(--ks-rule);
}
.worlds-context-panel strong {
.worlds-system-contract section {
padding: var(--spacing-sm) 0;
}
.worlds-system-contract section + section {
border-top: 1px solid var(--ks-rule);
}
.worlds-system-contract p {
color: var(--ks-kinpaku);
font: 500 var(--ks-type-eyebrow-size)/1 var(--ks-mono);
letter-spacing: 0.12em;
text-transform: uppercase;
}
.worlds-system-contract ol {
display: grid;
gap: 7px;
margin: 12px 0 0;
padding-left: 20px;
color: var(--ks-text-muted);
font-size: var(--ks-type-mono-size);
line-height: 1.55;
}
.worlds-system-contract li::marker {
color: var(--ks-patina);
font-family: var(--ks-mono);
}
.worlds-system-contract strong {
display: block;
margin-top: 12px;
color: var(--ks-text);
font-size: var(--ks-type-mono-size);
font-weight: 500;
line-height: 1.55;
}
.worlds-concept-meta {
max-width: 74ch;
margin-top: var(--spacing-lg);
border-top: 1px solid var(--ks-rule);
padding-top: var(--spacing-sm);
}
.worlds-concept-meta > div + div {
margin-top: var(--spacing-md);
}
.worlds-concept-meta strong {
display: block;
margin-top: var(--spacing-xs);
color: var(--ks-text);
@@ -758,21 +1007,38 @@ kbd {
line-height: 1.55;
}
.worlds-context-panel button {
width: 100%;
.worlds-concept-meta-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-sm);
margin-top: var(--spacing-sm);
color: var(--ks-text-faint);
font-size: var(--ks-type-mono-size);
}
.worlds-concept-meta-row em {
color: var(--ks-text-muted);
font-style: normal;
font-family: var(--ks-mono);
}
.worlds-concept-meta button {
min-height: 42px;
border: 0;
padding: 0 12px;
border: 1px solid var(--ks-rule);
border-radius: 3px;
background: transparent;
color: var(--ks-kinpaku);
font: 500 var(--ks-type-mono-size)/1 var(--ks-font);
cursor: pointer;
}
.worlds-context-panel button:hover:not(:disabled) {
.worlds-concept-meta button:hover:not(:disabled) {
background: var(--ks-graphite);
}
.worlds-context-panel button:disabled {
.worlds-concept-meta button:disabled {
color: var(--ks-text-mute-deep);
cursor: not-allowed;
}
@@ -877,6 +1143,16 @@ kbd {
margin-top: 0;
}
.worlds-edit-system {
display: grid;
gap: var(--spacing-xs);
}
.worlds-edit-system textarea {
min-height: 78px;
margin-top: 0;
}
.worlds-edit-form footer {
justify-content: flex-end;
margin-top: var(--spacing-lg);
@@ -918,16 +1194,40 @@ kbd {
background: var(--ks-lacquer-deep);
}
.worlds-decision-note {
flex: 1 1 auto;
min-width: 180px;
min-height: 44px;
padding: 0 12px;
border: 1px solid var(--ks-rule);
border-radius: 3px;
background: var(--ks-lacquer-deep);
color: var(--ks-champagne);
font-size: var(--ks-type-mono-size);
}
.worlds-decision-note::placeholder {
color: var(--ks-text-faint);
}
.worlds-decision-note:disabled {
background: var(--ks-graphite);
cursor: not-allowed;
}
.worlds-action-note {
display: flex;
align-items: center;
gap: 10px;
min-height: 1em;
color: var(--ks-text-faint);
font-size: var(--ks-type-mono-size);
}
.worlds-action-note:empty {
visibility: hidden;
}
.worlds-action-status {
display: flex;
flex: 1 1 auto;
min-width: 0;
align-items: center;
gap: var(--spacing-sm);
@@ -946,12 +1246,14 @@ kbd {
.worlds-action-buttons {
display: flex;
flex: 0 0 auto;
gap: var(--spacing-xs);
}
.worlds-review-actions button {
display: inline-flex;
min-height: 44px;
flex: 0 0 auto;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
@@ -961,6 +1263,7 @@ kbd {
background: var(--ks-lacquer-raised);
color: var(--ks-text);
font: 600 var(--ks-type-mono-size)/1 var(--ks-font);
white-space: nowrap;
cursor: pointer;
}
@@ -982,12 +1285,6 @@ kbd {
background: var(--ks-kinpaku-pale);
}
.worlds-review-actions .worlds-action-approve kbd {
border-color: var(--ks-dark-ink);
background: var(--ks-dark-ink);
color: var(--ks-kinpaku);
}
.worlds-review-actions button.is-current {
box-shadow: inset 0 -3px 0 currentColor;
}
@@ -1001,12 +1298,13 @@ kbd {
.worlds-toast {
position: fixed;
right: var(--spacing-md);
left: 50%;
bottom: var(--spacing-md);
transform: translateX(-50%);
z-index: 40;
max-width: min(420px, calc(100vw - 32px));
padding: 12px var(--spacing-sm);
border-left: 3px solid var(--ks-patina);
border: 1px solid color-mix(in oklch, var(--ks-patina), transparent 28%);
background: var(--ks-graphite-2);
color: var(--ks-champagne);
box-shadow: 0 18px 42px color-mix(in oklch, var(--ks-lacquer-deep), transparent 20%);
@@ -1014,7 +1312,7 @@ kbd {
}
.worlds-toast[data-kind="error"] {
border-left-color: var(--ks-vermilion);
border-color: color-mix(in oklch, var(--ks-vermilion), transparent 20%);
}
@media (max-width: 1500px) {
@@ -1027,28 +1325,18 @@ kbd {
}
.worlds-concept-body {
grid-template-columns: 48px minmax(0, 1fr);
gap: var(--spacing-md);
padding: var(--spacing-lg);
}
}
.worlds-concept-copy h2 {
max-width: 26ch;
font-size: calc(var(--ks-type-title-size) * 1.8);
@media (max-width: 1380px) {
.worlds-concept-body {
grid-template-columns: minmax(0, 1fr);
}
.worlds-context-panel {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
align-self: start;
border-top: 1px solid var(--ks-rule);
border-left: 0;
}
.worlds-context-panel > div {
border-right: 1px solid var(--ks-rule);
border-bottom: 0;
.worlds-concept-evidence {
max-width: 74ch;
}
}
@@ -1111,14 +1399,27 @@ kbd {
margin-top: var(--spacing-sm);
}
.worlds-progress-block {
.worlds-progress-copy,
.worlds-progress-track {
display: none;
}
.worlds-status-tabs {
.worlds-status-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.worlds-status-grid button:nth-child(2n) {
border-right: 1px solid var(--ks-rule);
}
.worlds-status-grid button:last-child {
border-right: 0;
}
.worlds-status-grid button:nth-child(-n + 2) {
border-bottom: 0;
}
.worlds-family-scroll {
padding: var(--spacing-xs) var(--spacing-sm);
grid-template-columns: 1fr;
@@ -1127,6 +1428,7 @@ kbd {
.worlds-family-button {
min-width: 0;
min-height: 44px;
}
.worlds-toolbar,
@@ -1145,6 +1447,11 @@ kbd {
max-width: none;
}
.worlds-decision-note {
flex-basis: auto;
width: 100%;
}
.worlds-runtime-state {
align-self: flex-start;
}
@@ -1169,19 +1476,16 @@ kbd {
padding: var(--spacing-lg) var(--spacing-md);
}
.worlds-concept-index {
.worlds-concept-evidence {
margin-top: var(--spacing-md);
}
.worlds-review-folio {
display: none;
}
.worlds-context-panel {
display: block;
margin-top: var(--spacing-lg);
border-top: 1px solid var(--ks-rule);
}
.worlds-context-panel > div {
border-right: 0;
border-bottom: 1px solid var(--ks-rule);
.worlds-strength-toggle {
display: none;
}
.worlds-action-buttons,
+7 -31
View File
@@ -20,6 +20,7 @@ Core principles:
1. Run `node {{scripts_path}}/context.mjs` once per session (if the runtime shows this skill's loaded base directory, run `node <skill-base-dir>/scripts/context.mjs`; keep cwd at the user's project). Pass a named source file or route as `--target <path>`. It loads PRODUCT.md, DESIGN.md, the matching surface brief, and native-platform guidance when applicable; follow its directives and do not rerun it. <!-- rule:skill-setup-context -->
2. Before acting, load the one playbook that owns the request: the Commands table's reference for an explicit or clearly implied sub-command, or [reference/new-work.md](reference/new-work.md) for a new surface or replacement visual world. Then inspect the target and at least one representative source of incumbent visual truth (tokens, theme, CSS, component, or asset) before editing. <!-- rule:skill-setup-command-ref --> <!-- rule:skill-setup-read-project -->
3. After analysis and direction are resolved, load [reference/build-floor.md](reference/build-floor.md) immediately before editing UI. Do not load it for planning-only work. <!-- rule:skill-build-floor-load -->
## How to design
@@ -29,39 +30,14 @@ Core principles:
## Modes
Choose the mode from the requested surface, not the product, and persist it only in that surface brief. A tool's landing page is still Persuade; a fashion house's documentation is still Read. See [new-work.md](reference/new-work.md) for new surfaces and [operate.md](reference/operate.md) for deeper Operate/Read guidance. <!-- rule:skill-visitor-mode -->
The mode names what the visitor's success looks like on this surface.
- **Persuade:** win someone over; design is the product. Earn attention and action. Ship real imagery when the brief needs it; follow the committed world, not category habit. <!-- rule:brand-register-core -->
- **Operate:** help someone do work. Scanability, consistency, native expectations, and the real usage scene outrank expression. Brand lives in precise details. <!-- rule:product-register-core -->
- **Read:** make something understood. Structure for comprehension, then make the reading experience worth staying in. <!-- rule:skill-read-register -->
- **Experience:** present a body of work. Let the artifact lead from the first viewport; the interface recedes. <!-- rule:skill-experience-register -->
- **Persuade:** the visitor decides and acts; design is the product. Landing pages, marketing, campaigns, pricing. Earn attention and action. Ship real imagery when the brief needs it; follow the committed world, not category habit. <!-- rule:brand-register-core -->
- **Operate:** the visitor completes a task. App UI, dashboards, editors, admin, settings, tools. Scanability, consistency, native expectations, and the real usage scene outrank expression. Brand lives in precise details. <!-- rule:product-register-core -->
- **Read:** the visitor understands something. Docs, articles, guides, help, changelogs. Structure for comprehension, then make the reading experience worth staying in. <!-- rule:skill-read-register -->
- **Experience:** the visitor is inside the work itself. Portfolios, galleries, showcases. Let the artifact lead from the first viewport; the interface recedes. <!-- rule:skill-experience-register -->
## Craft floor
Build to this floor without announcing it. <!-- rule:skill-craft-floor -->
- **Contrast:** body and placeholder text ≥4.5:1; large text ≥3:1. On colored surfaces, tint secondary text from that hue or the foreground instead of using gray. <!-- rule:skill-color-verify-contrast -->
- **Depth:** shadows describe light with offset and soft blur; zero-offset colored halos are decoration. <!-- rule:skill-color-no-glow-halo -->
- **Spacing:** tight groups, generous separation, no cramped containers; space above a heading exceeds space below. Verify computed values. <!-- rule:skill-layout-spacing-rhythm -->
- **Type:** body measure 6575ch; display max 6rem and tracking floor -0.04em; balance headings; use clear scale/weight contrast; test overflow at every breakpoint. <!-- rule:skill-typo-floor -->
- **Motion:** author one coherent moment instead of scattered effects. Use exponential ease-out and an already-visible default. Premium motion is not transform/opacity alone: it may add focus, depth, masks, light, or material change through blur/filter, backdrop-filter, clip-path/masks, or shadow when smooth. Always provide reduced motion. <!-- rule:skill-motion-floor --> <!-- rule:skill-motion-materials-palette -->
- **Shipping:** real content, working controls, responsive composition, keyboard focus, and the states users hit: hover, disabled, loading, error, and empty. <!-- rule:skill-floor-shipping -->
- **Copy:** use the product's language; controls name their action, errors name the problem and recovery. <!-- rule:skill-copy-design-material -->
- **Coverage:** every brief requirement must exist and be findable within seconds. <!-- rule:skill-floor-brief-coverage -->
Before finishing changed UI, follow the quality guidance supplied by `context.mjs` and hooks. Context requests a manual scan only when no automatic detector is active; never add a second detector pass. <!-- rule:skill-llm-only-slop-check --> <!-- rule:skill-detector-finish-mode -->
<codex>
Calibration for this provider:
- Display tracking stops at -0.04em; -0.02 to -0.03em is usually enough. <!-- rule:skill-typo-codex-tracking-repeat -->
- Declare elevation once: border or shadow, not both as decoration. Keep container radii modest; reserve pills for small controls. <!-- rule:skill-codex-elevation-radius -->
- Use real illustration or none. Treat backgrounds as surfaces and add texture only from the subject's world. Claims, evidence, and configuration come from supplied truth; label illustrative behavior and unresolved values honestly. <!-- rule:skill-codex-material-honesty -->
</codex>
<gemini>
Never animate `<img>` elements on hover, directly or through a parent. Give the card itself feedback instead. <!-- rule:skill-interaction-gemini-no-image-hover -->
</gemini>
Choose the mode from the requested surface, not the product, and persist it only in that surface brief. A tool's landing page is still Persuade; a fashion house's documentation is still Read; a docs index is Read, not Persuade. See [new-work.md](reference/new-work.md) for new surfaces and [operate.md](reference/operate.md) for deeper Operate/Read guidance. <!-- rule:skill-visitor-mode -->
## Commands
+2 -4
View File
@@ -29,7 +29,7 @@ Write a short plan before implementation:
- **Focal moment:** the one sequence or interaction that deserves authorship, if any.
- **Continuity:** the state, layout, or navigation changes that need explanation.
- **Feedback:** the controls and outcomes that need acknowledgment.
- **Budget:** which effects may be expensive, how often they run, and the reduced-motion equivalent.
- **Budget:** which effects may be expensive and how often they run.
The focal moment must come from this product and surface concept. A generic fade-and-rise, hover lift, parallax layer, or scroll reveal is not a thesis.
@@ -72,8 +72,6 @@ Keep content visible in the default state so failed scripts do not hide the page
## Accessibility and control
Honor `prefers-reduced-motion` with an intentional alternative. Preserve state change and hierarchy while removing travel, parallax, flashing, or prolonged sequences. Do not replace every animation globally with `0.01ms` if that destroys useful feedback. Motion must never block focus, interaction, reading, or task completion.
Respect autoplay and sound preferences. Any nonessential loop must stop when offscreen or hidden.
## Verify
@@ -81,7 +79,7 @@ Respect autoplay and sound preferences. Any nonessential loop must stop when off
- The focal motion is specific to the selected world and surface.
- Every supporting animation explains feedback, state, or relationship.
- Interruption and repeated use behave correctly.
- Desktop, mobile, keyboard, and reduced-motion paths remain usable.
- Desktop, mobile, and keyboard paths remain usable.
- Expensive effects stay smooth on the target device.
- Removing an animation would lose meaning or authored character, not merely decoration.
+1
View File
@@ -12,6 +12,7 @@ Run comprehensive checks across 5 dimensions. Score each dimension 0-4 using the
**Check for**:
- **Contrast issues**: Text contrast ratios < 4.5:1 (or 7:1 for AAA)
- **Motion sensitivity**: `prefers-reduced-motion` needs an intentional alternative that preserves state change and hierarchy; flag a global `0.01ms` kill that destroys useful feedback, flashing above threshold, and motion that blocks focus, reading, or task completion
- **Missing ARIA**: Interactive elements without proper roles, labels, or states
- **Keyboard navigation**: Missing focus indicators, illogical tab order, keyboard traps
- **Semantic HTML**: Improper heading hierarchy, missing landmarks, divs instead of buttons
+24
View File
@@ -0,0 +1,24 @@
# Build floor
Apply this only after analysis and direction are settled. Build without announcing the checklist. <!-- rule:skill-craft-floor -->
- **Contrast:** body and placeholder text ≥4.5:1; large text ≥3:1. On colored surfaces, tint secondary text from that hue or the foreground instead of using gray. <!-- rule:skill-color-verify-contrast -->
- **Depth:** shadows describe light with offset and soft blur; zero-offset colored halos are decoration. <!-- rule:skill-color-no-glow-halo -->
- **Spacing:** tight groups, generous separation, no cramped containers; space above a heading exceeds space below. Verify computed values. <!-- rule:skill-layout-spacing-rhythm -->
- **Type:** body measure 6575ch; display max 6rem and tracking floor -0.04em; balance headings; use clear scale/weight contrast; test overflow at every breakpoint. <!-- rule:skill-typo-floor -->
- **Motion:** author one coherent moment instead of scattered effects. Use exponential ease-out and an already-visible default. Premium motion may add focus, depth, masks, light, or material change through blur/filter, backdrop-filter, clip-path/masks, or shadow when smooth; do not rely on transform/opacity alone. <!-- rule:skill-motion-floor --> <!-- rule:skill-motion-materials-palette -->
- **Shipping:** real content, working controls, responsive composition, keyboard focus, and the states users hit: hover, disabled, loading, error, and empty. <!-- rule:skill-floor-shipping -->
- **Copy:** use the product's language; controls name their action, errors name the problem and recovery. <!-- rule:skill-copy-design-material -->
- **Coverage:** every brief requirement must exist and be findable within seconds. <!-- rule:skill-floor-brief-coverage -->
Before finishing changed UI, follow the quality guidance supplied by `context.mjs` and hooks. Context requests a manual scan only when no automatic detector is active; never add a second detector pass. <!-- rule:skill-llm-only-slop-check --> <!-- rule:skill-detector-finish-mode -->
<codex>
- Display tracking stops at -0.04em; -0.02 to -0.03em is usually enough. <!-- rule:skill-typo-codex-tracking-repeat -->
- Declare elevation once: border or shadow, not both as decoration. Keep container radii modest; reserve pills for small controls. <!-- rule:skill-codex-elevation-radius -->
- Use real illustration or none. Treat backgrounds as surfaces and add texture only from the subject's world. Claims, evidence, and configuration come from supplied truth; label illustrative behavior and unresolved values honestly. <!-- rule:skill-codex-material-honesty -->
</codex>
<gemini>
Never animate `<img>` elements on hover, directly or through a parent. Give the card itself feedback instead. <!-- rule:skill-interaction-gemini-no-image-hover -->
</gemini>
+4 -4
View File
@@ -19,11 +19,11 @@ Resolve one stable target, run two independent assessments, synthesize a design
- "the homepage" -> `site/pages/index.astro` or `index.html`
- "the settings modal" -> the primary component file
- "this page" -> the current URL or source file
2. **Compute the slug**:
2. **Confirm the target slugs cleanly**:
```bash
node {{scripts_path}}/critique-storage.mjs slug "<resolved-path-or-url>"
```
Keep it. If the command exits non-zero, skip persistence and trend for this run, but continue the critique.
Every later command also accepts the resolved target directly and derives the same slug internally; never hand-write a slug. If this exits non-zero, skip persistence and trend for this run, but continue the critique.
3. **Read `.impeccable/critique/ignore.md`** if it exists. Drop matching findings silently; it is the only prior-run input critique consumes.
### Assessment Orchestration
@@ -215,7 +215,7 @@ Skip this step if the Setup slug was null (vague or root-level target).
2. **Pass the structured metadata** through `IMPECCABLE_CRITIQUE_META` (JSON), then run the write command:
```bash
IMPECCABLE_CRITIQUE_META='{"target":"<user phrasing>","total_score":<n>,"p0_count":<n>,"p1_count":<n>}' \
node {{scripts_path}}/critique-storage.mjs write <slug> <body-file>
node {{scripts_path}}/critique-storage.mjs write "<resolved target>" <body-file>
```
The helper prints the absolute path it wrote.
@@ -223,7 +223,7 @@ Skip this step if the Setup slug was null (vague or root-level target).
4. **Read the trend** for context:
```bash
node {{scripts_path}}/critique-storage.mjs trend <slug> 5
node {{scripts_path}}/critique-storage.mjs trend "<resolved target>" 5
```
This returns a JSON array of the last 5 frontmatter entries (including the one you just wrote).
+2 -2
View File
@@ -56,7 +56,7 @@ Delight must not:
- become mandatory, unskippable, or exhausting on repeat;
- add a dependency or asset cost disproportionate to the moment.
For authored motion, load [animate.md](animate.md). Respect reduced motion, screen readers, keyboard use, touch, localization, and cultural context. Nonessential loops stop when hidden. Make celebration intensity proportional to frequency and consequence.
For authored motion, load [animate.md](animate.md). Respect screen readers, keyboard use, touch, localization, and cultural context. Nonessential loops stop when hidden. Make celebration intensity proportional to frequency and consequence.
## Verify
@@ -64,7 +64,7 @@ For authored motion, load [animate.md](animate.md). Respect reduced motion, scre
- It improves comprehension, confidence, motivation, or emotional recovery.
- The interface remains fast and obvious without the flourish.
- Repetition does not turn charm into friction.
- Reduced-motion, muted, keyboard, touch, and localized paths work.
- Muted, keyboard, touch, and localized paths work.
- The result feels like the selected world, not a generic “delight” treatment.
When the personality feels earned, hand off to `{{command_prefix}}impeccable polish` for the final pass.
-11
View File
@@ -261,17 +261,6 @@ t('items', { count }) // Handles complex plural rules
- Descriptive alt text
- Semantic HTML
**Motion sensitivity**:
```css
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
**High contrast mode**:
- Test in Windows high contrast mode
- Don't rely only on color
+91 -101
View File
@@ -2,6 +2,18 @@
This flow owns the durable visual world when authority is absent, expanding, or explicitly replaced, plus only as much task-level shaping as the requested scope needs. PRODUCT.md owns product truth, DESIGN.md confirmed visual truth, and `.impeccable/surfaces/` durable task strategy. Complete [init.md](init.md) first when PRODUCT.md is missing. Missing DESIGN.md does not route back to init.
## Vocabulary
Terms this file uses throughout, defined once:
- **World:** the durable visual identity: palette, materials, type voice, ornament logic, and component character that outlive any single surface.
- **Coupled pair:** one world joined to one concrete first-surface expression, selected together as a single decision, never as two tournaments.
- **Staging:** an identity-free structural idea for a surface (hierarchy, sequence, interaction). The seed may append one; it brings no palette, typeface, or material.
- **Candidate floor:** the four veto tests in section 4. A candidate passes all four or is discarded and replaced.
- **Direction contract:** six promise blocks written into the artifact's opening comment before code, audited against the render by the hooks.
- **Surface brief:** durable surface strategy persisted to `.impeccable/surfaces/` via `surface-brief.mjs`.
- **Attended run:** a user is present to answer questions. Unattended fallbacks apply only when no one can answer.
## 1. Name the intent
- **Greenfield:** no coherent visual implementation.
@@ -11,97 +23,101 @@ This flow owns the durable visual world when authority is absent, expanding, or
- **Redesign/rebrand:** replace the world while preserving unchanged product truth, content, function, native affordances, constraints, and brand commitments.
- **Refinement:** leave this flow for the scoped command; preserve the world and scope.
A plain redesign this page/site authorizes replacement. Redesign this within the current brand/system means extension or refinement. Ask once only when the wording is genuinely ambiguous.
Classify before ideation: work that must join an existing page is a local extension, and a novel layout it would benefit from does not promote it to a new surface. A plain "redesign this page/site" authorizes replacement. "Redesign this within the current brand/system" means extension or refinement. Ask once only when the wording is genuinely ambiguous.
## 2. Resolve visual authority
Read DESIGN.md and representative code, tokens, components, and assets. Choose one path:
### A. Explicit redesign
- **A. Explicit redesign.** The old DESIGN.md and implementation are not authority. Keep only unrevoked product facts, content, function, native expectations, constraints, and brand commitments. Establish a replacement world.
- **B. DESIGN.md covers this kind of surface.** Use its invariants and normative tokens; skip world-building and discover the surface.
- **C. Coherent implementation, no DESIGN.md.** Code, assets, tokens, type, and component behavior are incumbent authority. Run [document.md](document.md) in scan mode, confirm the extracted invariants, then write DESIGN.md. Do not offer replacement worlds unless the user asked for a redesign.
- **D. Brand exists, whole-surface family unresolved.** Preserve logo, color/type assets, voice, recognizable component/motion traits, and constraints. Ask what must carry and where expression may expand; offer two or three compatible ranges, not replacement identities, and merge the choice into DESIGN.md (child-app DESIGN.md when the range is local).
- **E. No confirmed visual authority.** Establish a world. Scaffolds, framework defaults, and stray utilities are not identity.
The old DESIGN.md and implementation are not authority. Keep only unrevoked product facts, content, function, native expectations, constraints, and brand commitments. Establish a replacement world.
### B. DESIGN.md covers this kind of surface
Use its invariants and normative tokens. Skip world-building and discover the surface.
### C. A coherent implementation exists but DESIGN.md does not
Code, assets, tokens, type, and component behavior are incumbent authority. Run [document.md](document.md) in scan mode and confirm extracted invariants before writing DESIGN.md. Do not offer replacement worlds unless the user asked for a redesign.
### D. The brand exists, but a whole-surface family is unresolved
Preserve logo, color/type assets, voice, recognizable component/motion traits, and constraints. Ask what must carry and where expression may expand. Offer two or three compatible ranges, not replacement identities, and merge the choice into DESIGN.md. Use a child-app DESIGN.md when the range is local.
A section, feature, component, or state inside a coherent existing surface stays on B or C. Its surrounding surface is authority even when DESIGN.md is incomplete.
### E. No confirmed visual authority exists
Establish a world. Scaffolds, framework defaults, and stray utilities are not identity.
A local extension stays on B or C: its surrounding surface is authority even when DESIGN.md is incomplete.
## 3. Discover the requested surface
Name this surface's audience, job, visitor mode, real content, primary action/task, evidence, constraints, and memorable moment. PRODUCT.md supplies truth and DESIGN.md the world; neither decides narrative or composition.
Ask one attended round of at most three material questions without repeating durable facts. CTA hierarchy, proof sequence, content gaps, and interaction outcomes belong here, not PRODUCT.md. For a fully specified narrow request, state the interpretation and invite correction.
In an attended run, ask one round of at most three material questions without repeating durable facts: CTA hierarchy, proof sequence, content gaps, and interaction outcomes belong here, not in PRODUCT.md. For a fully specified narrow request, state the interpretation and invite correction. When `shape` has already completed its discovery interview, reuse those confirmed answers and do not ask again.
When `shape` has already completed its discovery interview, reuse those confirmed answers and do not ask this round again.
## 4. The candidate floor
Classify the scope before ideation. A section, feature, component, or state that must join an existing page is a local extension. A page, route, screen, flow, or standalone experience may be a new surface. Do not inflate local work into a surface concept merely because it benefits from a novel layout.
When generating choices, veto rather than rationalize. Before a candidate reaches the user, discard and replace it if any floor fails:
## 4. Shape or select the direction
- **Truth:** every relationship it visualizes exists in product truth; resemblance is not evidence.
- **Consequence:** removing its bar-raiser materially weakens the surface's central argument or use. Otherwise it is ordinary craft.
- **Survival:** its signature remains compelling on the primary device and within the real asset and tool budget.
- **Fit:** its risk is an honest tradeoff, not a probable violation of the brief or audience.
Do not select a new world and its first surface concept in separate tournaments. That creates a safe global choice followed by a more interesting local choice whose “lineage” exists only in prose.
Passing some floors never rescues a failed one. (The bar-raiser is the contract block defined in section 6: the product-native move that lifts the surface above competent convention.)
### Local extension inside stable authority: shape, do not seed
## 5. Shape or select the direction
For a local extension on path B or C, inherit both the visual world and the surrounding surface's direction. Resolve only the decisions the addition actually introduces: purpose, content, hierarchy, state or interaction, and how it joins the existing sequence. Use short, related question rounds when those decisions are still open. Do not run `concept-seed.mjs`, generate competing surface metaphors, or offer alternate visual worlds. The result may still have an authored, surprising layout; its novelty must come from the material and the established grammar, not a new identity thesis.
Do not select a new world and its first surface concept in separate tournaments. That creates a safe global choice followed by a more interesting local choice whose "lineage" exists only in prose. Pick the one lane that matches the intent and authority path.
If a local request reveals a genuine gap in the brand system, name that gap and ask before treating it as path D. Do not silently turn a case-study section or feature into an expression-expansion exercise.
### Local extension (B or C): inherit, do not reseed
### A, D, or E: choose a coupled world and first expression
Inherit both the world and the surrounding surface's direction. Resolve only the decisions the addition actually introduces: purpose, content, hierarchy, state or interaction, and how it joins the existing sequence; in an attended run use short, related question rounds while those are open. Do not run `concept-seed.mjs`, generate competing surface metaphors, offer alternate worlds, write a direction contract, or change DESIGN.md without user approval. The result may still have an authored, surprising layout; its novelty must come from the material and the established grammar, not a new identity thesis. If the request reveals a genuine gap in the brand system, name the gap and ask before treating it as path D; never silently turn a section into an expression-expansion exercise.
For a replacement world, unresolved brand expansion, or no authority, make one coupled decision:
### New surface inside stable authority (B or C)
1. **Ground.** Use PRODUCT.md, incumbent commitments that still bind, and the surface discovery above. Name what this surface uniquely does, proves, or enables.
2. **Derive pairs.** Generate five to seven grounded directions and order them by product fit. Each joins a durable visual system to a concrete first-surface structure, focal moment, and implementation consequence. Different names or materials on the same experience are one candidate, not several.
3. **Break the ranking rut once.** Run `node {{scripts_path}}/concept-seed.mjs --scope direction`. Promote the assigned grounded pair. Translate each challenger into a coherent system and task solution before comparing it on audience identification and product clarity.
4. **Test at full strength.** Strip names and styling; survivors must still differ in structure, sequence, or interaction. Reject a pair if its surface swaps into another unchanged, its world cannot govern unlike future surfaces, or a competent default could satisfy its focal moment. A candidate's risk must be a real tradeoff, not a reason it violates the brief.
5. **Offer coupled choices.** Present two or three equally viable pairs without recommendation cues. For each, show the world rules, first-surface expression, cross-surface consequence, and risk. Ask what is closest, should combine, or feels wrong; rejection is allowed.
6. **Resolve once.** The user selects or revises the pair. Extract the durable rules into DESIGN.md and the task-specific strategy into the surface brief; do not reopen either half independently.
Without an answer mechanism, use the assigned grounded pair only if it survives product fit, coupling, and breadth; mark assumptions. This is fallback, not user choice.
### B or C, whole surface only: choose a direction inside stable authority
The visual world supplies the vocabulary; the task concept supplies the sentence.
The world supplies the vocabulary; the task concept supplies the sentence.
1. Derive five to seven structural candidates from the content, mechanism, audience, and confirmed authority. Translate its relationships and behavior, not just its styling.
2. Run `node {{scripts_path}}/concept-seed.mjs --scope surface` only when a whole page, route, screen, flow, or standalone experience calls for high-concept exploration. Otherwise shape the strongest grounded structure directly. Never run it for a local extension.
3. Name the habitual arrangement and predictable contrarian response. Judge candidates skin-blind: topology, sequence, or interaction must remain different after names and styling disappear.
4. When materially different whole-surface choices would help, present two or three neutral options with thesis, sequence, focal moment, signature, implementation consequence, and concrete inherited world rules. Let the user select or revise before code.
2. Run `node {{scripts_path}}/concept-seed.mjs --scope surface --mode <mode>` (the surface's mode: persuade, operate, read, or experience) only when a whole page, route, screen, flow, or standalone experience calls for high-concept exploration. Otherwise shape the strongest grounded structure directly. Never run it for a local extension. When the seed appends a STAGING CHALLENGER, it is identity-free structure: dress it in the committed visual identity before judging it against the grounded candidates.
3. Name the habitual arrangement and predictable contrarian response. Apply the candidate floor, then judge survivors skin-blind: topology, sequence, or interaction must remain different after names and styling disappear.
4. When materially different whole-surface choices would help, present two or three neutral options with thesis, sequence, focal moment, signature, implementation consequence, and concrete inherited world rules, following **Present, visualize, re-roll** below. Let the user select or revise before code.
Without an answer mechanism, use the promoted candidate when a roll ran; otherwise use the strongest grounded structure. It must survive both tests.
Unattended: use the promoted candidate when a roll ran, otherwise the strongest grounded structure; it must survive both tests.
For a substantial high-fidelity surface with native image generation, load [codex.md](codex.md) after selection. Probes stay inside the selected direction. For `shape`, stop after selection and continue in [shape.md](shape.md).
### New or replacement world (A, D, or E): choose a coupled pair
### Write or update DESIGN.md
1. **Ground.** Derive the product mechanism, user scene, audience's cultural home, and what this surface uniquely proves. Name the category default and its predictable contrarian response; neither may enter the shortlist unchanged.
2. **Derive pairs.** Generate five to seven grounded coupled pairs and order them by product fit. Each joins a durable system to a concrete first-surface structure, native behavior, non-routine bar-raiser, and implementation consequence. Different names or materials on the same experience are one candidate.
3. **Break the ranking rut once.** Run `node {{scripts_path}}/concept-seed.mjs --scope direction --mode <mode>`, where the mode is the first surface's: persuade, operate, read, or experience. The seed names a PROMOTED INDEX; elevate the pair at that position of your own ranked list into the serious shortlist and judge it as a peer of your top picks. Translate each challenger into a coherent system and task solution before comparing audience identification, product clarity, system leverage, and use of the medium. Keep the relationships, states, and affordances; discard the source carrier, materials, and vocabulary unless product evidence earns them. When the seed appends a FIRST-SURFACE STAGING, treat it as a candidate structure for the first surface: dress it in each shortlisted world before judging, and keep world and staging one coupled decision.
4. **Test at full strength.** Apply the candidate floor. Strip names, styling, and source carrier; survivors must still differ in structure, sequence, or interaction. Their world must also govern the whole product: the navigation, a dense surface, and a quiet surface, with the last two dissimilar.
5. **Offer coupled choices.** Present two or three equally viable pairs without recommendation cues, following **Present, visualize, re-roll** below. For each, show the world rules, first-surface expression, bar-raiser, cross-surface consequence, and risk. Ask what is closest, should combine, or feels wrong; rejection is allowed.
6. **Resolve once.** The user selects or revises the pair. Extract the durable rules into DESIGN.md and the task-specific strategy into the surface brief; do not reopen either half independently.
For A or E, write or replace DESIGN.md at the resolved project/app boundary using the [format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md). For D, merge only the approved expansion range. Record the chosen durable rules and add:
Unattended: use the assigned grounded pair only if it survives product fit, coupling, and breadth; mark assumptions. This is fallback, not user choice.
`<!-- STATUS: DIRECTIONAL SEED; exact tokens remain provisional until the first implementation pass. -->`
### Present, visualize, re-roll
Do not fabricate YAML tokens; add exact values only after the user, assets, or implementation establishes them. The selected pair authorizes its world and first expression together without another confirmation. A local extension does not change DESIGN.md unless the user approves a durable system addition.
These rules govern every candidate presentation above, in either lane.
## 5. Persist the surface brief
When the harness can generate images, visualizing the finalists is required, not decoration: render each presented candidate as a world board (palette, materials, type voice, component character) plus a first-surface mock at a realistic viewport; a surface-scope candidate inside a committed world gets the mock alone. Render designed artifacts: invented product names, real English interface copy, no instruction text transcribed onto the image, no dashboard or game chrome the concept itself did not earn. The images are selection aids only; the direction contract and DESIGN.md stay the sole authority, and no image persists as a project file.
Once the primary target or route is known, persist durable surface-level product/UX strategy separately from PRODUCT.md and DESIGN.md. Prefer a clone-stable source file; map routes and alternate entries as related targets. For a local extension, update the parent surface's record only when the work establishes durable product strategy; do not create a component-level brief by reflex. Read any record first:
Always offer **re-roll** beside the candidates, with an optional one-line steer for what is missing. Re-roll eliminates every candidate presented so far, grounded and challenger alike; none may return reworded. Derive genuinely new grounded candidates from unexplored angles of the same grounding, honor the steer, rerun the seed with `--from <key> --reroll <round>` so it draws challengers it has not yet shown, apply the candidate floor, rerank, and present again under these same rules. After two consecutive re-rolls, ask what quality is missing before rolling a third time; silent re-rolls converge on guessing.
An unattended run has no one to re-roll or eye-test. When image generation is available there, render one mock of the chosen direction and correct material drift between mock and build before finishing.
For a substantial high-fidelity surface with native image generation, load [codex.md](codex.md) after selection; probes stay inside the selected direction. For `shape`, stop after selection and continue in [shape.md](shape.md).
## 6. Write the direction contract for a whole surface
If a competent default could satisfy the concept, sharpen its focal moment until one product-specific move changes implementation and raises the experience above competent convention.
Before code, write a direction contract of at most 150 words in the artifact's opening HTML or framework comment; the first 200 characters must name `DIRECTION CONTRACT`. The artifact comment is the contract's only home: hooks audit the render against it, and the surface brief references it rather than duplicating it. <!-- rule:skill-decide-then-build -->
- `THESIS`: the product-specific idea and the category-default arrangement it refuses;
- `OWN-WORLD`: the confirmed DESIGN.md invariants, tokens, and materials it uses;
- `STORY`: what the visitor understands, believes, and does;
- `FIRST VIEWPORT`: exact composition, hierarchy, action, and where the concept exceeds competent convention;
- `BAR-RAISER`: the product-native use of interaction, motion, state, space, or computation that makes this more than a strong static composition; routine functional state alone does not clear the bar;
- `FORM`: chosen structure or behavior, signature, implementation consequence, and seed key.
The contract is task-scoped, inspectable, and subordinate to the user's choice. A local extension skips it unless the user explicitly wants the addition to become a distinct authored moment; the shaped decisions are its implementation plan instead.
## 7. Persist the surface brief
Once the primary target or route is known, persist durable surface-level product/UX strategy separately from PRODUCT.md and DESIGN.md. `<primary-target>` is a concrete repo-relative source file path or route URL, never a natural-language description; the script derives the clone-stable slug mechanically, so never hand-write one. Prefer a clone-stable source file; map routes and alternate entries as related targets. For a local extension, update the parent surface's record only when the work establishes durable product strategy; do not create a component-level brief by reflex. Read any record first:
`node {{scripts_path}}/surface-brief.mjs read <primary-target>`
Exit 0: preserve still-valid decisions and change only what the user changed. For redesign, retain valid product strategy, content, function, and open decisions; replace visual direction and contract. Exit 2: no brief. After the contract, write with `node {{scripts_path}}/surface-brief.mjs write <primary-target> <body-file> [related-target ...]`.
The body is concise and contains:
Exit 0: preserve still-valid decisions and change only what the user changed; for redesign, retain valid product strategy, content, function, and open decisions while replacing the visual direction. Exit 2: no brief exists. Write with `node {{scripts_path}}/surface-brief.mjs write <primary-target> <body-file> [related-target ...]`, which persists `.impeccable/surfaces/<target-slug>.md` as durable context for later sessions; leave version control to the user's normal flow. Exclude global truth, exact tokens, transient notes, and work logs. The concise body:
```markdown
# Surface brief: [name]
@@ -113,66 +129,40 @@ The body is concise and contains:
[Surface-specific audience and job, desired outcome, primary/secondary action or task, content and proof sequence, factual constraints, and explicitly open decisions.]
## Selected direction
[Reference to the applicable DESIGN.md world or expression range, selected surface concept, focal moment, narrative/interaction sequence, and implementation consequence.]
## Direction contract
[The six contract blocks below.]
[Reference to the applicable DESIGN.md world or expression range, selected surface concept, focal moment, narrative/interaction sequence, implementation consequence, and a pointer to the artifact carrying the direction contract.]
## Open decisions
[Only unresolved items that later work must not silently invent. Omit when empty.]
```
Commit `.impeccable/surfaces/<target-slug>.md` as stable later-work context. Exclude global truth, exact tokens, transient notes, and work logs.
## 8. Write or update DESIGN.md
## 6. Write the direction contract for a whole surface
For A or E, write or replace DESIGN.md at the resolved project/app boundary; for D, merge only the approved expansion range. The format, in brief: YAML frontmatter carrying machine-readable tokens (colors, typography, rounded, spacing, components), then the canonical markdown sections in fixed order, with tokens normative and prose as application context. [document.md](document.md) carries the full schema and examples; never fetch a remote spec. Record the chosen durable rules and add:
If a competent default could satisfy the concept, sharpen its focal moment until one product-specific move changes implementation. Difficulty must clarify the product, not add spectacle.
`<!-- STATUS: DIRECTIONAL SEED; exact tokens remain provisional until the first implementation pass. -->`
Before code, write a direction contract of at most 150 words in an opening HTML or framework comment. The first 200 characters must name `DIRECTION CONTRACT`.
Do not fabricate YAML tokens; add exact values only after the user, assets, or implementation establishes them. The selected pair authorizes its world and first expression together without another confirmation. A local extension does not change DESIGN.md unless the user approves a durable system addition.
- `UNIQUE`: the task thesis tied to the product mechanism;
- `NOT-TEMPLATE`: the category-default arrangement this structure refuses;
- `OWN-WORLD`: the confirmed DESIGN.md invariants, tokens, and materials it uses;
- `STORY`: what the visitor understands, believes, and does;
- `FIRST VIEWPORT`: exact composition, hierarchy, action, and where the concept exceeds competent convention;
- `FORM`: chosen structure or behavior, signature, implementation consequence, and seed key.
## 9. Plan and build
The contract is task-scoped, inspectable, and subordinate to the user's choice. Put the same six blocks in the surface brief and artifact comment. <!-- rule:skill-decide-then-build -->
Plan from the selected direction or local shape and real content, never a category skeleton. In redesign, remove inherited visual tokens. Load only needed specialist references; focal interaction or authored animation reads [animate.md](animate.md), even without the `animate` command.
A local extension skips this contract unless the user explicitly wants it to become a distinct authored moment. Use the shaped decisions as the implementation plan instead.
Build the strongest coherent direction once. Its grammar governs navigation, actions, controls, content, and transitions without disguising affordances. Give the focal form the scale that gives it force; do not trap it inside a conventional opening panel.
## 7. Plan, build, and commit
Plan from the selected direction or local shape and real content, never a category skeleton. In redesign, remove inherited visual tokens.
Load only needed specialist references. Focal interaction or authored animation reads [animate.md](animate.md), even without the `animate` command.
Build the strongest coherent direction once. Its grammar governs navigation, actions, controls, content, and transitions without disguising affordances. Give the focal form the scale that gives it force; do not trap it inside a standard hero panel.
**Make the opening a thesis.** Demonstrate the mechanism immediately; leave an idea, interaction, or evidence, not merely mood.
**Commit before correcting.** Land the hard move at full strength before refining it. In unattended work, safety is the known risk.
**Commit at page scale.** Let color, material, image, or type own a region when the world calls for it. Scattered signature decoration is not commitment.
**Pace the whole surface.** Vary density, scale, image, motion, and quiet inside one grammar. Cut repeated claims; prove the mechanism with real artifact, interaction, data, transformation, or content.
**Author motion as material.** Motion expresses the world and task. Premium moments go beyond repeated transform/opacity through earned focus, depth, masks, continuity, light, or material change. Bound expensive effects, test in-browser, keep content visible by default, and design reduced motion.
- **Make the opening a thesis.** Demonstrate the mechanism immediately; leave an idea, interaction, or evidence, not merely mood.
- **Commit before correcting.** Land the hard move at full strength before refining it. In unattended work, safety is the known risk.
- **Commit at page scale.** Let color, material, image, or type own a region when the world calls for it. Scattered signature decoration is not commitment.
- **Pace the whole surface.** Vary density, scale, image, motion, and quiet inside one grammar. Cut repeated claims; prove the mechanism with real artifact, interaction, data, transformation, or content.
- **Author motion as material.** Motion expresses the world and task. Premium moments go beyond repeated transform/opacity through earned focus, depth, masks, continuity, light, or material change. Bound expensive effects, test in-browser, and keep content visible by default.
Preserve semantics, affordances, accessibility, performance, responsiveness, and project conventions. Operate/Read express through topology, hierarchy, density, rhythm, and state; Persuade/Experience may earn drama.
## 8. Solidify the visual record
## 10. Solidify the visual record
After first implementation of a new/replacement world or approved expansion, refresh DESIGN.md from the build:
After first implementation of a new/replacement world or approved expansion, refresh DESIGN.md from the build: replace provisional direction with the exact type, color roles, tokens, spacing/radii, components, states, and motion that survived; add normative YAML tokens only for values the implementation actually uses; remove the directional-seed status once record and implementation agree; preserve broader world invariants and expression ranges. Do not promote the task's story, opening composition, or one-off motif into a global rule unless it is intentionally reusable. Ordinary extension does not rewrite DESIGN.md; only approved durable changes do.
- replace provisional direction with the exact type, color roles, tokens, spacing/radii, components, states, and motion that survived;
- add normative YAML tokens only for values the implementation actually uses;
- remove the directional-seed status once the record and implementation agree;
- preserve broader world invariants and expression ranges;
- do not promote the task's story, hero composition, or one-off motif into a global rule unless it is intentionally reusable.
## 11. Finish like a studio
Ordinary extension does not rewrite DESIGN.md; only approved durable changes do.
Inspect desktop and mobile; critique against the brief, DESIGN.md, and the applicable shape, concept, or contract; patch material defects; recheck skin-blind. Follow the quality guidance supplied by `context.mjs` and hooks. Context requests a manual scan only when no automatic detector is active; never add another detector pass. Fix real gaps and classify false positives until none remain. <!-- rule:skill-finish-like-studio -->
## 9. Finish like a studio
Inspect desktop and mobile; critique against the brief, DESIGN.md, and the applicable shape, concept, or contract; patch material defects; recheck skin-blind. Follow the quality guidance supplied by `context.mjs` and hooks. Context requests a manual scan only when no automatic detector is active; never add another detector pass. Fix real gaps and classify false positives until none remain. Add a reviewer only when risk earns it. <!-- rule:skill-finish-like-studio -->
When the harness can run a separate agent, this finishing review belongs there, not in the build thread: hand the reviewer the artifact path, its direction contract, and the detector command, and have it return a short list of material fixes; apply them and finish. A fresh reader catches what the builder's own eyes slide past. <!-- rule:skill-finish-separate-reviewer -->
-3
View File
@@ -102,7 +102,6 @@ else if (canvas.getContext('webgl2')) { /* WebGL2 fallback */ }
### Performance rules
- Target 60fps. If dropping below 50, simplify.
- Respect `prefers-reduced-motion`, always. Provide a beautiful static alternative.
- Lazy-initialize heavy resources (WebGL contexts, WASM modules) only when near viewport.
- Pause off-screen rendering. Kill what you can't see.
- Test on real mid-range devices, not just your development machine.
@@ -112,7 +111,6 @@ else if (canvas.getContext('webgl2')) { /* WebGL2 fallback */ }
The gap between "cool" and "extraordinary" is in the last 20% of refinement: the easing curve on a spring animation, the timing offset in a staggered reveal, the subtle secondary motion that makes a transition feel physical. Don't ship the first version that works; ship the version that feels inevitable.
**NEVER**:
- Ignore `prefers-reduced-motion`. This is an accessibility requirement, not a suggestion
- Ship effects that cause jank on mid-range devices
- Use bleeding-edge APIs without a functional fallback
- Add sound without explicit user opt-in
@@ -124,7 +122,6 @@ The gap between "cool" and "extraordinary" is in the last 20% of refinement: the
- **The wow test**: Show it to someone who hasn't seen it. Do they react?
- **The removal test**: Take it away. Does the experience feel diminished, or does nobody notice?
- **The device test**: Run it on a phone, a tablet, a Chromebook. Still smooth?
- **The accessibility test**: Enable reduced motion. Still beautiful?
- **The context test**: Does this make sense for THIS brand and audience?
"Technically extraordinary" isn't about using the newest API. It's about making an interface do something users didn't think a website could do.
+3 -4
View File
@@ -29,8 +29,7 @@ Use the feature yourself at representative desktop and mobile sizes. Determine:
If a prior critique exists, use it as one input:
```bash
slug=$(node {{scripts_path}}/critique-storage.mjs slug "<resolved target>")
node {{scripts_path}}/critique-storage.mjs latest "$slug"
node {{scripts_path}}/critique-storage.mjs latest "<resolved target>"
```
Exit 0 returns the latest snapshot; incorporate relevant P0/P1 findings and name the snapshot read. Exit 2 means none exists. Perform an independent pass either way.
@@ -73,7 +72,7 @@ Do not perfect one corner while leaving the rest below the same quality bar.
- Every control needs appropriate default, hover, focus, active, disabled, loading, error, and success behavior.
- Preserve visible keyboard focus, logical tab order, labels, and platform-appropriate touch targets.
- Keep motion coherent, interruptible, performant, and reduced-motion aware. Do not add animation merely to make polish visible.
- Keep motion coherent, interruptible, and performant. Do not add animation merely to make polish visible.
- Validate long, missing, localized, offline, slow, and permission-limited content where the product can encounter it.
### Content and code
@@ -89,7 +88,7 @@ Walk the complete path again with mouse, keyboard, and touch where applicable. C
- mobile, intermediate, and wide layouts;
- loading, empty, error, success, disabled, long-content, and missing-content states;
- zoom, contrast, focus, semantics, screen-reader names, and reduced motion;
- zoom, contrast, focus, semantics, and screen-reader names;
- console errors, layout shift, interaction latency, image loading, and supported browsers;
- agreement with DESIGN.md, neighboring features, and the user's scope.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+206 -34
View File
@@ -16,15 +16,29 @@
* - PROMOTED INDEX: which entry of the model's own resonance-ordered
* shortlist must be taken seriously beside its favorites. The dice never
* choose an ungrounded ingredient; they only refuse the argmax rut.
* - CHALLENGERS (3): outside forms from concept-ingredients.json, weighed
* against the derived candidates on exactly two axes — audience
* identification and product clarity. They win only when they beat the
* - CHALLENGERS (6): outside forms from concept-ingredients.json, two from
* each challenger tier (graphic system, instrument language, atmosphere
* world), weighed
* against the derived candidates on audience identification, product
* clarity, system leverage, and use of the medium. They win only when they beat the
* grounded list; measured behavior is that they lose to strong cultural
* material and win over thin categories, which is the intended shape.
* - RE-ROLL (--reroll <n>): round n of the same base key. The script
* recomputes what rounds 0..n-1 drew, excludes all of it, and rolls a
* fresh promoted index, challengers, and staging. One base key therefore
* reproduces the entire chain of rounds.
* - RATINGS: the reviewer's approval ratings weight the challenger draw
* (3-star doubles the odds, 1-star sits out); the approved pool itself
* is unchanged.
*
* Usage:
* node scripts/concept-seed.mjs --scope direction
* node scripts/concept-seed.mjs --scope surface --from <key>
* node scripts/concept-seed.mjs --scope direction --mode persuade
* node scripts/concept-seed.mjs --scope surface --mode operate --from <key>
* node scripts/concept-seed.mjs --scope direction --mode persuade --from <key> --reroll 1
*
* --mode names the requested surface's mode (persuade, operate, read,
* experience) so the appended staging matches its register of work; omitted,
* the staging rolls from the full approved pool.
*
* Env vars:
* IMPECCABLE_CONCEPT_SEED — same as --from; for reproducible eval runs.
@@ -37,33 +51,144 @@ import {
approvedPoolRevision,
deterministicRank,
readConceptCatalog,
validateConceptCatalog,
WELL_TIERS,
} from './lib/concept-catalog.mjs';
import { readCompositionCatalog } from './lib/composition-catalog.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const { concepts } = readConceptCatalog(
const catalogState = readConceptCatalog(
join(here, 'concept-ingredients.json'),
join(here, 'concept-reviews.json')
);
const catalogValidation = validateConceptCatalog(catalogState.catalog, catalogState.reviewData);
if (catalogValidation.errors.length > 0) {
throw new Error(`concept-seed: invalid catalog: ${catalogValidation.errors.join('; ')}`);
}
const { concepts } = catalogState;
const compositionState = readCompositionCatalog(
join(here, 'composition-ingredients.json'),
join(here, 'composition-reviews.json')
);
const compositions = compositionState.compositions;
export function selectApprovedChallengers({ scope, key, sourceConcepts = concepts }) {
export function renderChallenger(concept, index) {
const system = concept.system.map(rule => ` - ${rule}`).join('\n');
return ` ${index + 1}. ${concept.form}
CREATIVE SPARK: ${concept.spark}
SYSTEM GRAMMAR:
${system}
WEB LEVERAGE: ${concept.webLeverage}`;
}
export function renderStaging(composition) {
const grammar = composition.grammar.map(rule => ` - ${rule}`).join('\n');
return ` ${composition.form}
SPARK: ${composition.spark}
STAGING GRAMMAR:
${grammar}
WEB LEVERAGE: ${composition.webLeverage}`;
}
// One approved staging from the composition catalog, rolled deterministically.
// Returns null while the composition pool has no approved entry for the
// requested mode. Cross-mode fallback would turn an absent staging into a
// misleading one. A re-roll excludes earlier stagings until the pool runs out.
export function selectApprovedStaging({ scope, key, reroll = 0, mode = null, sourceCompositions = compositions }) {
let approved = sourceCompositions.filter(composition => composition.status === 'approved');
if (approved.length === 0) return null;
if (mode) {
const matching = approved.filter(composition => composition.surface === mode);
if (matching.length === 0) return null;
approved = matching;
}
const prior = new Set();
let pick = deterministicRank(approved, `${scope}:${key}:staging`)[0];
for (let round = 1; round <= reroll; round += 1) {
prior.add(pick.id);
const pool = approved.filter(composition => !prior.has(composition.id));
pick = deterministicRank(
pool.length > 0 ? pool : approved,
`${scope}:${key}:staging:reroll-${round}`
)[0];
}
return pick;
}
export function selectApprovedChallengers({ scope, key, reroll = 0, sourceConcepts = concepts }) {
const approved = sourceConcepts.filter(concept => concept.status === 'approved');
const approvedByFamily = new Map();
// Direction chooses a durable identity, so it draws worlds; surface designs
// one page inside a committed identity, so it draws stagings. Duals serve
// both. A tier with no matching-strength approvals falls back to its full
// approved pool rather than starving the roll.
const wanted = scope === 'direction'
? new Set(['world', 'dual'])
: new Set(['composition', 'dual']);
const approvedByTier = new Map();
for (const concept of approved) {
const family = approvedByFamily.get(concept.familyId) || [];
family.push(concept);
approvedByFamily.set(concept.familyId, family);
const tier = approvedByTier.get(concept.wellTier) || [];
tier.push(concept);
approvedByTier.set(concept.wellTier, tier);
}
if (approvedByFamily.size < 3) {
throw new Error('concept-seed: at least three families need approved concepts');
if (WELL_TIERS.some(tier => !(approvedByTier.get(tier) || []).length)) {
throw new Error('concept-seed: every challenger tier needs at least one approved concept');
}
for (const [tier, pool] of approvedByTier) {
const matching = pool.filter(concept => wanted.has(concept.strength));
if (matching.length > 0) approvedByTier.set(tier, matching);
}
// Two challengers per tier, so every roll carries near-zero-translation
// graphic systems beside instrument languages and atmosphere worlds, with
// the second pick preferring a different family for diversity. Tier order
// in the rendered list is rolled too, to avoid positional bias.
// Approval ratings weight the draw: a 3-star world earns a second ticket
// (roughly double odds), a 1-star keeps its approval for direct briefs but
// leaves the challenger pool unless a tier has nothing else.
const ticketsFor = pool => pool.flatMap(concept => {
const rating = concept.review?.rating;
if (rating === 1) return [];
return rating === 3
? [{ concept, ticket: 0 }, { concept, ticket: 1 }]
: [{ concept, ticket: 0 }];
});
const pickRound = (round, excluded) => {
const salt = round === 0 ? '' : `:reroll-${round}`;
const tierOrder = deterministicRank(
WELL_TIERS.map(id => ({ id })),
`${scope}:${key}:tiers${salt}`
).map(item => item.id);
return tierOrder.flatMap((tier, index) => {
let pool = approvedByTier.get(tier).filter(concept => !excluded.has(concept.id));
// A tier exhausted by prior rounds falls back to reuse over starvation.
if (pool.length === 0) pool = approvedByTier.get(tier);
let tickets = ticketsFor(pool);
if (tickets.length === 0) tickets = pool.map(concept => ({ concept, ticket: 0 }));
const ranked = deterministicRank(
tickets,
`${scope}:${key}:challenger-${index}${salt}`,
entry => `${entry.concept.id}#${entry.ticket}`
);
const order = [];
const seen = new Set();
for (const entry of ranked) {
if (seen.has(entry.concept.id)) continue;
seen.add(entry.concept.id);
order.push(entry.concept);
}
const first = order[0];
const second = order.find(concept => concept.familyId !== first.familyId)
|| order.find(concept => concept.id !== first.id);
return second ? [first, second] : [first];
});
};
// Round n of a re-roll chain excludes everything rounds 0..n-1 drew, so the
// same base key reproduces the whole chain.
const excluded = new Set();
let picks = pickRound(0, excluded);
for (let round = 1; round <= reroll; round += 1) {
for (const pick of picks) excluded.add(pick.id);
picks = pickRound(round, excluded);
}
const familyIds = deterministicRank(
[...approvedByFamily.keys()].map(id => ({ id })),
`${scope}:${key}:families`
).slice(0, 3).map(item => item.id);
const picks = familyIds.map((familyId, index) => deterministicRank(
approvedByFamily.get(familyId),
`${scope}:${key}:challenger-${index}`
)[0]);
return {
approved,
picks,
@@ -71,19 +196,30 @@ export function selectApprovedChallengers({ scope, key, sourceConcepts = concept
};
}
const SEED_MODES = new Set(['persuade', 'operate', 'read', 'experience']);
export function renderConceptSeed({
scope = 'surface',
key = process.env.IMPECCABLE_CONCEPT_SEED || crypto.randomBytes(4).toString('hex'),
reroll = 0,
mode = null,
} = {}) {
if (scope !== 'surface' && scope !== 'direction') {
throw new Error('concept-seed: --scope must be direction or surface');
}
if (!Number.isInteger(reroll) || reroll < 0) {
throw new Error('concept-seed: --reroll must be a non-negative integer');
}
if (mode !== null && !SEED_MODES.has(mode)) {
throw new Error('concept-seed: --mode must be persuade, operate, read, or experience');
}
const unit = (salt) => {
const h = crypto.createHash('sha256').update(`${scope}:${salt}:${key}`).digest();
return h.readUInt32BE(0) / 0xffffffff;
};
const buildIndex = 3 + Math.floor(unit('index') * 5); // 3..7
const { approved, picks, poolRevision } = selectApprovedChallengers({ scope, key });
const indexSalt = reroll === 0 ? 'index' : `index:reroll-${reroll}`;
const buildIndex = 3 + Math.floor(unit(indexSalt) * 5); // 3..7
const { approved, picks, poolRevision } = selectApprovedChallengers({ scope, key, reroll });
const promotedInstruction = scope === 'direction'
? `After ordering the grounded coupled directions by product fit, promote
@@ -96,15 +232,16 @@ export function renderConceptSeed({
promote candidate ${buildIndex} into the serious shortlist. In an attended
run, present it beside the strongest materially different candidates and
let the user select or revise the surface concept. In a truly unattended
run, use it when it survives audience identification and product clarity.`;
run, use it when it survives audience identification, product clarity,
system leverage, and use of the medium.`;
const challengerInstruction = scope === 'direction'
? `Translate each challenger's organizing logic into reusable identity grammar
and a strong first-surface structure before judging it. Noticeable form is
allowed when the product stays clear. Compare only audience identification
and product clarity.`
: `A challenger wins only when it beats the grounded list on both audience
identification and product clarity. It may change task topology or
allowed when the product stays clear. Compare audience identification,
product clarity, system leverage, and use of the medium.`
: `A challenger wins only when it beats the grounded list on audience
identification, product clarity, system leverage, and use of the medium. It may change task topology or
interaction, but never the committed visual identity.`;
const authorityInstruction = scope === 'direction'
@@ -117,17 +254,48 @@ vocabulary; they do not cancel task-level composition. The seed never
authorizes a new palette, type system, material world, or unfamiliar control
behavior.`;
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; approved pool: ${poolRevision}; ${approved.length}/${concepts.length} human-approved; rerun with --scope ${scope} --from ${key} to reproduce this roll against this catalog revision)
PROMOTED INDEX: ${buildIndex}
const richnessInstruction = `The CREATIVE SPARK is a visual world, artifact, or graphic tradition people
would genuinely choose to enter, study, or explore, and whose palette,
materials, type voice, and component grammar a designer could sketch on
sight, not decorative art direction. The challengers are drawn two from each
translation tier: graphic systems that map to interface almost directly,
instrument or display languages that carry interaction physics, and material
or performed worlds that need the largest translation step; judge
each in its own register and pay that translation cost honestly. Translate
its scale, material, spatial or compositional law, tension, rhythm, and
memorable human experience into product structure. Inherit a movement's or
artifact's rules, never just its name: grid, geometry, ornament logic,
material behavior, and information structure become the interface's. Preserve
the spark's imaginative distance: do not collapse a galaxy into a mission
dashboard, a forest into a taxonomy app, or a performance into a control
console. Use Three.js, generative motion, film language, typography, craft,
or another ambitious medium when it materially strengthens the task; keep
semantic structure and graceful fallbacks fully capable.`;
const staging = selectApprovedStaging({ scope, key, reroll, mode });
const stagingBlock = staging
? `\n${scope === 'direction' ? 'FIRST-SURFACE STAGING (identity-free; pair it with the chosen world and judge the pair as one decision):' : 'STAGING CHALLENGER (identity-free; dress it in the committed visual identity before judging):'}
${renderStaging(staging)}
A staging organizes attention, sequence, and manipulation; it never brings a
palette, typeface, or material. It competes on structure alone and loses to a
grounded structure that fits the product better.\n`
: '';
const rerollBlock = reroll > 0
? `RE-ROLL ROUND ${reroll}: every candidate presented in earlier rounds, grounded
and challenger alike, is eliminated and may not return reworded. Derive
genuinely new grounded candidates from unexplored angles before judging
these fresh challengers.\n`
: '';
return `${scope.toUpperCase()} CONCEPT SEED (key: ${key}; mode: ${mode ?? 'unscoped'}; approved pool: ${poolRevision}; ${approved.length}/${concepts.length} human-approved; rerun with --scope ${scope}${mode ? ` --mode ${mode}` : ''} --from ${key}${reroll > 0 ? ` --reroll ${reroll}` : ''} to reproduce this roll against this catalog revision)
${rerollBlock}PROMOTED INDEX: ${buildIndex}
${promotedInstruction}
The promotion exists to refuse the model's ranking rut, not to outrank the
user or the brief. Never expose promotion metadata in choice labels or order.
CHALLENGERS:
1. ${picks[0].form}
2. ${picks[1].form}
3. ${picks[2].form}
${challengerInstruction}
${picks.map(renderChallenger).join('\n')}
${stagingBlock}${challengerInstruction}
${authorityInstruction}
${richnessInstruction}
A user- or brief-pinned decision beats the roll, always.
`;
}
@@ -136,12 +304,16 @@ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.ur
const args = process.argv.slice(2);
const fromIdx = args.indexOf('--from');
const scopeIdx = args.indexOf('--scope');
const rerollIdx = args.indexOf('--reroll');
const modeIdx = args.indexOf('--mode');
try {
process.stdout.write(renderConceptSeed({
scope: scopeIdx !== -1 ? args[scopeIdx + 1] : 'surface',
key: fromIdx !== -1
? args[fromIdx + 1]
: (process.env.IMPECCABLE_CONCEPT_SEED || crypto.randomBytes(4).toString('hex')),
reroll: rerollIdx !== -1 ? Number(args[rerollIdx + 1]) : 0,
mode: modeIdx !== -1 ? args[modeIdx + 1] : null,
}));
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
+14 -4
View File
@@ -141,6 +141,15 @@ export function readTrend(slug, { limit = 5, cwd = process.cwd() } = {}) {
// ---- CLI ---------------------------------------------------------------
// Accept either a ready slug or a concrete target (path/URL) everywhere, so
// callers never have to run the slug step separately. Anything containing a
// path or URL marker is resolved through slugFromTarget.
function coerceSlug(value) {
if (!value) return null;
if (/^[a-z0-9-]+$/.test(value) && !value.includes('/')) return value;
return slugFromTarget(value);
}
function main(argv) {
const [cmd, ...args] = argv;
switch (cmd) {
@@ -151,8 +160,9 @@ function main(argv) {
return;
}
case 'write': {
const [slug, bodyFile] = args;
if (!slug || !bodyFile) { process.stderr.write('usage: write <slug> <body-file>\n'); process.exit(1); }
const [slugArg, bodyFile] = args;
const slug = coerceSlug(slugArg);
if (!slug || !bodyFile) { process.stderr.write('usage: write <slug-or-target> <body-file>\n'); process.exit(1); }
const raw = fs.readFileSync(bodyFile, 'utf-8');
// The body file may be a full report. The caller passes the meta as
// a JSON object on stdin if it wants structured frontmatter; otherwise
@@ -167,13 +177,13 @@ function main(argv) {
return;
}
case 'latest': {
const latest = readLatestSnapshot(args[0]);
const latest = readLatestSnapshot(coerceSlug(args[0]));
if (!latest) { process.exit(2); }
process.stdout.write(latest.body);
return;
}
case 'trend': {
const rows = readTrend(args[0], { limit: args[1] ? Number(args[1]) : 5 });
const rows = readTrend(coerceSlug(args[0]), { limit: args[1] ? Number(args[1]) : 5 });
process.stdout.write(JSON.stringify(rows, null, 2) + '\n');
return;
}
+3 -3
View File
@@ -1846,8 +1846,8 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
// ── Direction-contract audit ─────────────────────────────────────────────
// The skill's decide-then-build step opens the built HTML artifact with a
// DIRECTION CONTRACT comment (UNIQUE / NOT-TEMPLATE / OWN-WORLD / STORY /
// FIRST VIEWPORT / FORM blocks). At Stop time the deep pass extracts that
// DIRECTION CONTRACT comment (THESIS / OWN-WORLD / STORY / FIRST VIEWPORT /
// BAR-RAISER / FORM blocks). At Stop time the deep pass extracts that
// comment and feeds it back so the model audits the render against its own
// promises. Proven in the eval harness: sample contracts promised radical
// compositions and the build shipped the standard template anyway, because
@@ -1870,7 +1870,7 @@ export const CONTRACT_MAX_CHARS = 1800;
// stack an unbounded message.
export const CONTRACT_AUDIT_MAX_FILES = 3;
export const CONTRACT_EXTS = new Set(['.html', '.htm', '.astro', '.svelte', '.vue', '.jsx', '.tsx']);
export const CONTRACT_REQUIRED_FIELDS = ['UNIQUE', 'NOT-TEMPLATE', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'FORM'];
export const CONTRACT_REQUIRED_FIELDS = ['THESIS', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER', 'FORM'];
/**
* Extract the artifact's own direction-contract comment from the head of an
+165
View File
@@ -0,0 +1,165 @@
import crypto from 'node:crypto';
import { readFileSync } from 'node:fs';
import { CONCEPT_STATUSES, normalizeConceptForm } from './concept-catalog.mjs';
// Catalog B: stagings rather than styles. A composition organizes attention,
// sequence, or manipulation on a surface and must survive being dressed in
// any committed visual identity; it deliberately carries no palette or type
// half. Surface-scope seeds draw from here (plus catalog A duals); direction
// seeds pair one composition with a chosen world for the first surface.
export const COMPOSITION_GRAMMAR_PREFIXES = [
'Staging/hierarchy:',
'Sequence/attention:',
'Controls/state:',
'Adaptation:',
];
// Surfaces align with the skill's modes: a persuade staging and an operate
// staging are different species, and read/experience surfaces get their own.
export const COMPOSITION_SURFACES = new Set(['persuade', 'operate', 'read', 'experience']);
export function compositionContentHash(composition) {
const payload = [
composition?.form ?? '',
composition?.lineage ?? '',
JSON.stringify(composition?.tags ?? []),
JSON.stringify(composition?.grammar ?? []),
composition?.spark ?? '',
composition?.webLeverage ?? '',
].join('\n');
return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12);
}
export function validateCompositionEntry(composition, { existingForms = new Map() } = {}) {
const errors = [];
const id = composition?.id || '(unknown)';
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(composition?.id || '')) {
errors.push(`invalid composition id: ${String(composition?.id)}`);
}
const normalized = normalizeConceptForm(composition?.form);
if (!normalized) {
errors.push(`composition ${id} needs a form`);
} else if (existingForms.has(normalized)) {
errors.push(`duplicate composition form: ${id} and ${existingForms.get(normalized)}`);
}
if (typeof composition?.form !== 'string'
|| composition.form.trim().length < 40
|| composition.form.trim().length > 360
|| !composition.form.includes(',')) {
errors.push(`composition ${id} must name a staging and its structural mechanism after a comma`);
}
if (typeof composition?.lineage !== 'string'
|| composition.lineage.trim().length < 12
|| composition.lineage.trim().length > 200) {
errors.push(`composition ${id} needs lineage metadata of 12200 characters`);
}
if (!COMPOSITION_SURFACES.has(composition?.surface)) {
errors.push(`composition ${id} needs a surface of ${[...COMPOSITION_SURFACES].join(', ')}`);
}
if (!Array.isArray(composition?.tags)
|| composition.tags.length !== 3
|| composition.tags.some(tag => typeof tag !== 'string' || !tag.trim())) {
errors.push(`composition ${id} must have exactly three structural tags`);
}
if (!Array.isArray(composition?.grammar)
|| composition.grammar.length !== COMPOSITION_GRAMMAR_PREFIXES.length
|| composition.grammar.some(rule => typeof rule !== 'string' || rule.trim().length < 12 || rule.trim().length > 180)) {
errors.push(`composition ${id} needs grammar with exactly four rules of 12180 characters`);
} else {
const unique = new Set(composition.grammar.map(normalizeConceptForm));
if (unique.size !== COMPOSITION_GRAMMAR_PREFIXES.length) {
errors.push(`composition ${id} has duplicate grammar rules`);
}
if (composition.grammar.some((rule, index) => !rule.startsWith(COMPOSITION_GRAMMAR_PREFIXES[index]))) {
errors.push(`composition ${id} grammar must use staging, sequence, controls, and adaptation prefixes in order`);
}
}
if (typeof composition?.spark !== 'string'
|| composition.spark.trim().length < 80
|| composition.spark.trim().length > 320) {
errors.push(`composition ${id} needs a vivid spark of 80320 characters`);
}
if (typeof composition?.webLeverage !== 'string'
|| composition.webLeverage.trim().length < 20
|| composition.webLeverage.trim().length > 240) {
errors.push(`composition ${id} needs web leverage of 20240 characters`);
}
return errors;
}
export function readCompositionCatalog(catalogPath, reviewsPath) {
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
const reviewData = JSON.parse(readFileSync(reviewsPath, 'utf8'));
const reviews = reviewData.reviews || {};
const familiesById = new Map((catalog.families || []).map(family => [family.id, family]));
const compositions = (catalog.compositions || []).map(composition => ({
...composition,
familyLabel: familiesById.get(composition.familyId)?.label || null,
status: reviews[composition.id]?.status || 'pending',
review: reviews[composition.id] || null,
}));
return { catalog, reviewData, reviews, compositions };
}
export function validateCompositionCatalog(catalog, reviewData, { minimumTotal } = {}) {
const errors = [];
const familyIds = new Set();
const ids = new Set();
const forms = new Map();
if (!Number.isInteger(catalog?.schemaVersion) || catalog.schemaVersion < 1) {
errors.push('composition catalog schemaVersion must be a positive integer');
}
if (typeof catalog?.qualityBar?.principle !== 'string' || catalog.qualityBar.principle.trim().length < 80) {
errors.push('composition qualityBar.principle must define the staging bar');
}
if (!Array.isArray(catalog?.families) || catalog.families.length < 4) {
errors.push('composition catalog needs at least four families');
}
for (const family of catalog?.families || []) {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(family.id || '')) errors.push(`invalid composition family id: ${String(family.id)}`);
if (familyIds.has(family.id)) errors.push(`duplicate composition family id: ${family.id}`);
familyIds.add(family.id);
if (typeof family.description !== 'string' || family.description.trim().length < 40) {
errors.push(`composition family ${family.id || '(unknown)'} needs a description`);
}
}
for (const composition of catalog?.compositions || []) {
if (ids.has(composition.id)) errors.push(`duplicate composition id: ${composition.id}`);
ids.add(composition.id);
if (!familyIds.has(composition.familyId)) {
errors.push(`composition ${composition.id} must belong to a declared family, got: ${String(composition.familyId)}`);
}
errors.push(...validateCompositionEntry(composition, { existingForms: forms }));
const normalized = normalizeConceptForm(composition.form);
if (normalized) forms.set(normalized, composition.id);
}
if (minimumTotal !== undefined && (catalog?.compositions || []).length < minimumTotal) {
errors.push(`expected at least ${minimumTotal} compositions, found ${(catalog?.compositions || []).length}`);
}
for (const [id, review] of Object.entries(reviewData?.reviews || {})) {
if (!ids.has(id)) errors.push(`composition review references missing entry: ${id}`);
if (!CONCEPT_STATUSES.has(review?.status)) errors.push(`invalid composition review status for ${id}`);
if (typeof review?.formHash !== 'string' || !review.formHash.trim()) {
errors.push(`composition review ${id} needs a formHash`);
} else {
const entry = (catalog?.compositions || []).find(composition => composition.id === id);
if (entry && review.formHash !== compositionContentHash(entry)) {
errors.push(`composition review ${id} is stale: content changed since review`);
}
}
if (review?.note !== undefined && (typeof review.note !== 'string' || !review.note.trim() || review.note.length > 500)) {
errors.push(`composition review ${id} note must be a non-empty string of 500 characters or fewer`);
}
}
return {
errors,
stats: {
families: familyIds.size,
compositions: (catalog?.compositions || []).length,
approved: Object.values(reviewData?.reviews || {}).filter(review => review?.status === 'approved').length,
rejected: Object.values(reviewData?.reviews || {}).filter(review => review?.status === 'rejected').length,
},
};
}
+201 -31
View File
@@ -3,6 +3,30 @@ import { readFileSync } from 'node:fs';
export const CONCEPT_STATUSES = new Set(['approved', 'rejected']);
// What a concept is actually strong at. Worlds carry a durable visual
// identity (their palette/type half is the magnet); compositions carry a
// staging or interaction idea (their topology half is the magnet) that can be
// dressed in any committed identity; duals fuse both inseparably. Direction
// seeds draw world|dual, surface seeds draw composition|dual.
export const CONCEPT_STRENGTHS = new Set(['world', 'composition', 'dual']);
// Challenger tiers, ordered by translation cost: graphic grammars map to
// interface almost directly, instrument languages carry interaction physics,
// atmosphere worlds need the largest translation step. Every seed roll draws
// one challenger from each tier so at least one directly-usable graphic
// system is always on the table.
export const WELL_TIERS = ['graphic', 'interaction', 'atmosphere'];
const WEB_LEVERAGE_RE = /(?:\b3d\b|\badaptive\b|\banimat(?:e|ed|ion)\b|\bapi\b|\baria\b|\baudio\b|\bautomated?\b|\bbarcode\b|\bbroadcastchannel\b|\bbrowser\b|\bcamera\b|canvas\b|\bcaption\b|\bcollaborat(?:e|ive|ion)\b|\bcompar(?:e|ison)\b|\bcomput(?:e|ed|ation)\b|\bcomputer[- ]vision\b|\bconstraint[- ]solving\b|\bcryptographic?\b|\bcss\b|\bdeep[- ]link(?:ing)?\b|\bdirect manipulation\b|\bdom\b|\bdrag\b|\bfilter\b|\bfocus\b|\bgenerative\b|\bgeolocat(?:e|ed|ion)\b|\bgesture\b|\bgpu\b|\bgraph\b|\bhistory\b|\bindexeddb\b|\binteractive\b|\bintersectionobserver\b|\bkeyboard\b|\blive\b|\blocal\b|\bmicrophone\b|\bmotion\b|\bmultiplayer\b|\bnative\b|\bnotification\b|\boffline\b|\bpersonaliz(?:e|ed|ation)\b|\bplayable\b|\bpointer\b|\bprocedural\b|\bprovenance\b|\breal[- ]?time\b|\bresizeobserver\b|\bresponsive\b|\breveal\b|\bscrub\b|\bsearch\b|\bsearchparams\b|\bsensor\b|\bserver[- ]sent\b|\bservice worker\b|\bshader\b|\bsimulat(?:e|ed|ion|or)\b|\bspatial\b|\bstate\b|\bstream(?:ing)?\b|\bsvg\b|\bsynchroniz(?:e|ed|ation)\b|\btimeline\b|\btouch\b|\burl|\bvideo\b|\bweb(?:gl|socket|vtt)?\b|\bworker\b|\bzoom\b)/i;
export const SYSTEM_PREFIXES = [
'Palette/material:',
'Type/composition:',
'Topology/navigation:',
'Controls/state:',
'Responsive/motion:',
];
const BLAND_FORM_RE = /\b(?:control room|command center|operations center|dispatch desk|review queue|speaker queue|management console|admin console|operator loop|coordination system|tracking system|planning system|software platform|digital platform|operations cockpit|app portal|web portal|data hub|dashboard|workflow|planner|tracker|orchestrator)\b/i;
export function normalizeConceptForm(value) {
return String(value || '')
.normalize('NFKD')
@@ -12,10 +36,93 @@ export function normalizeConceptForm(value) {
.trim();
}
export function validateConceptEntry(concept, { existingForms = new Map() } = {}) {
const errors = [];
const id = concept?.id || '(unknown)';
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(concept?.id || '')) {
errors.push(`invalid concept id: ${String(concept?.id)}`);
}
const normalized = normalizeConceptForm(concept?.form);
if (!normalized) {
errors.push(`concept ${id} needs a form`);
} else if (existingForms.has(normalized)) {
errors.push(`duplicate concept form: ${id} and ${existingForms.get(normalized)}`);
}
if (typeof concept?.form !== 'string'
|| concept.form.trim().length < 40
|| concept.form.trim().length > 360
|| !concept.form.includes(',')) {
errors.push(`concept ${id} must name a form and inherited structure after a comma`);
}
if (typeof concept?.lineage !== 'string'
|| concept.lineage.trim().length < 12
|| concept.lineage.trim().length > 200) {
errors.push(`concept ${id} needs specific lineage metadata of 12200 characters`);
}
if (!CONCEPT_STRENGTHS.has(concept?.strength)) {
errors.push(`concept ${id} needs a strength of ${[...CONCEPT_STRENGTHS].join(', ')}`);
}
if (!Array.isArray(concept?.tags)
|| concept.tags.length !== 3
|| concept.tags.some(tag => typeof tag !== 'string' || !tag.trim())) {
errors.push(`concept ${id} must have exactly three structural tags`);
}
if (!Array.isArray(concept?.system)
|| concept.system.length !== SYSTEM_PREFIXES.length
|| concept.system.some(rule => typeof rule !== 'string' || rule.trim().length < 12 || rule.trim().length > 180)) {
errors.push(`concept ${id} needs system grammar with exactly five rules of 12180 characters`);
} else {
const uniqueRules = new Set(concept.system.map(normalizeConceptForm));
if (uniqueRules.size !== SYSTEM_PREFIXES.length) {
errors.push(`concept ${id} has duplicate system grammar rules`);
}
if (concept.system.some((rule, index) => !rule.startsWith(SYSTEM_PREFIXES[index]))) {
errors.push(`concept ${id} system grammar must use palette, type, topology, controls, and responsive prefixes in order`);
}
}
if (typeof concept?.spark !== 'string'
|| concept.spark.trim().length < 80
|| concept.spark.trim().length > 320) {
errors.push(`concept ${id} needs a vivid creative spark of 80320 characters`);
}
if (typeof concept?.webLeverage !== 'string'
|| concept.webLeverage.trim().length < 20
|| concept.webLeverage.trim().length > 240) {
errors.push(`concept ${id} needs web leverage of 20240 characters`);
}
if (/\b(?:live digital system|shared participatory system) modeled on\b/i.test(concept?.form || '')) {
errors.push(`concept ${id} is a generic wrapper around another artifact`);
}
if (/\b(?:in the style of|styled like|copy of)\b/i.test(concept?.form || '')) {
errors.push(`concept ${id} contains imitation language`);
}
if (BLAND_FORM_RE.test(concept?.form || '')) {
errors.push(`concept ${id} is framed as a literal software or operations archetype instead of an inspiring visual world`);
}
return errors;
}
// Fingerprint of everything a reviewer judged. Reviews carry this hash so an
// approval cannot silently survive a content edit: the validator rejects any
// review whose hash no longer matches the concept it points at.
export function conceptContentHash(concept) {
const payload = [
concept?.form ?? '',
concept?.lineage ?? '',
JSON.stringify(concept?.tags ?? []),
JSON.stringify(concept?.system ?? []),
concept?.spark ?? '',
concept?.webLeverage ?? '',
].join('\n');
return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12);
}
export function readConceptCatalog(catalogPath, reviewsPath) {
const catalog = JSON.parse(readFileSync(catalogPath, 'utf8'));
const reviewData = JSON.parse(readFileSync(reviewsPath, 'utf8'));
const reviews = reviewData.reviews || {};
const wellsById = new Map((catalog.wells || []).map(well => [well.id, well]));
const concepts = [];
for (const family of catalog.families || []) {
@@ -24,6 +131,9 @@ export function readConceptCatalog(catalogPath, reviewsPath) {
...concept,
familyId: family.id,
familyLabel: family.label,
wellId: family.well || null,
wellLabel: wellsById.get(family.well)?.label || null,
wellTier: wellsById.get(family.well)?.tier || null,
status: reviews[concept.id]?.status || 'pending',
review: reviews[concept.id] || null,
});
@@ -33,7 +143,11 @@ export function readConceptCatalog(catalogPath, reviewsPath) {
return { catalog, reviewData, reviews, concepts };
}
export function validateConceptCatalog(catalog, reviewData, { expectedTotal, minimumTotal } = {}) {
export function validateConceptCatalog(catalog, reviewData, {
expectedTotal,
minimumTotal,
requireApprovedMinimum = true,
} = {}) {
const errors = [];
const warnings = [];
const familyIds = new Set();
@@ -41,16 +155,54 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
const normalizedForms = new Map();
const concepts = [];
if (!Number.isInteger(catalog?.schemaVersion) || catalog.schemaVersion < 1) {
errors.push('catalog.schemaVersion must be a positive integer');
if (!Number.isInteger(catalog?.schemaVersion) || catalog.schemaVersion < 7) {
errors.push('catalog.schemaVersion must be 7 or newer');
}
if (typeof catalog?.catalogVersion !== 'string' || !catalog.catalogVersion.trim()) {
errors.push('catalog.catalogVersion must be a non-empty string');
}
if (typeof catalog?.qualityBar?.principle !== 'string' || catalog.qualityBar.principle.trim().length < 80) {
errors.push('catalog.qualityBar.principle must define the universal creative bar');
}
if (!Array.isArray(catalog?.qualityBar?.rejectIf) || catalog.qualityBar.rejectIf.length < 5) {
errors.push('catalog.qualityBar.rejectIf must define at least five rejection gates');
}
if (!Array.isArray(catalog?.qualityBar?.reviewAxes) || catalog.qualityBar.reviewAxes.length < 8) {
errors.push('catalog.qualityBar.reviewAxes must define at least eight review axes');
}
if (!Array.isArray(catalog?.families) || catalog.families.length < 3) {
errors.push('catalog.families must contain at least three families');
}
const wellIds = new Set();
if (!Array.isArray(catalog?.wells) || catalog.wells.length < 5) {
errors.push('catalog.wells must define at least five inspiration wells');
}
for (const well of catalog?.wells || []) {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(well.id || '')) {
errors.push(`invalid well id: ${String(well.id)}`);
} else if (wellIds.has(well.id)) {
errors.push(`duplicate well id: ${well.id}`);
}
wellIds.add(well.id);
if (typeof well.label !== 'string' || !well.label.trim()) {
errors.push(`well ${well.id || '(unknown)'} needs a label`);
}
if (typeof well.description !== 'string' || well.description.trim().length < 40) {
errors.push(`well ${well.id || '(unknown)'} needs a description of at least 40 characters`);
}
if (!WELL_TIERS.includes(well.tier)) {
errors.push(`well ${well.id || '(unknown)'} needs a tier of ${WELL_TIERS.join(', ')}, got: ${String(well.tier)}`);
}
}
const tiersPresent = new Set((catalog?.wells || []).map(well => well.tier).filter(tier => WELL_TIERS.includes(tier)));
for (const tier of WELL_TIERS) {
if ((catalog?.wells || []).length > 0 && !tiersPresent.has(tier)) {
errors.push(`no well declares the ${tier} tier`);
}
}
const populatedWells = new Set();
for (const family of catalog?.families || []) {
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(family.id || '')) {
errors.push(`invalid family id: ${String(family.id)}`);
@@ -61,6 +213,11 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
if (typeof family.label !== 'string' || !family.label.trim()) {
errors.push(`family ${family.id || '(unknown)'} needs a label`);
}
if (!wellIds.has(family.well)) {
errors.push(`family ${family.id || '(unknown)'} must belong to a declared well, got: ${String(family.well)}`);
} else {
populatedWells.add(family.well);
}
if (!Array.isArray(family.concepts) || family.concepts.length === 0) {
errors.push(`family ${family.id || '(unknown)'} has no concepts`);
continue;
@@ -68,33 +225,22 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
for (const concept of family.concepts) {
concepts.push(concept);
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(concept.id || '')) {
errors.push(`invalid concept id: ${String(concept.id)}`);
} else if (conceptIds.has(concept.id)) {
if (conceptIds.has(concept.id)) {
errors.push(`duplicate concept id: ${concept.id}`);
}
errors.push(...validateConceptEntry(concept, { existingForms: normalizedForms }));
conceptIds.add(concept.id);
const normalized = normalizeConceptForm(concept.form);
if (!normalized) {
errors.push(`concept ${concept.id || '(unknown)'} needs a form`);
} else if (normalizedForms.has(normalized)) {
errors.push(`duplicate concept form: ${concept.id} and ${normalizedForms.get(normalized)}`);
if (normalized) normalizedForms.set(normalized, concept.id);
if (typeof concept.webLeverage === 'string' && !WEB_LEVERAGE_RE.test(concept.webLeverage)) {
warnings.push(`concept ${concept.id} web leverage should be checked for a specific browser-native capability`);
}
normalizedForms.set(normalized, concept.id);
}
}
if (typeof concept.form !== 'string' || !concept.form.includes(',')) {
errors.push(`concept ${concept.id || '(unknown)'} must name a form and inherited structure after a comma`);
}
if (typeof concept.lineage !== 'string' || !concept.lineage.trim()) {
errors.push(`concept ${concept.id || '(unknown)'} needs lineage metadata`);
}
if (!Array.isArray(concept.tags) || concept.tags.length !== 3 || concept.tags.some(tag => typeof tag !== 'string' || !tag.trim())) {
errors.push(`concept ${concept.id || '(unknown)'} must have exactly three structural tags`);
}
if (/\b(?:in the style of|styled like|copy of)\b/i.test(concept.form)) {
errors.push(`concept ${concept.id || '(unknown)'} contains imitation language`);
}
for (const well of catalog?.wells || []) {
if (well.id && !populatedWells.has(well.id)) {
errors.push(`well ${well.id} has no families`);
}
}
@@ -105,9 +251,10 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
errors.push(`expected at least ${minimumTotal} concepts, found ${concepts.length}`);
}
if (!Number.isInteger(reviewData?.schemaVersion) || reviewData.schemaVersion < 1) {
errors.push('reviews.schemaVersion must be a positive integer');
if (!Number.isInteger(reviewData?.schemaVersion) || reviewData.schemaVersion < 2) {
errors.push('reviews.schemaVersion must be 2 or newer');
}
const conceptsById = new Map(concepts.map(concept => [concept.id, concept]));
for (const [id, review] of Object.entries(reviewData?.reviews || {})) {
if (!conceptIds.has(id)) errors.push(`review references missing concept: ${id}`);
if (!CONCEPT_STATUSES.has(review?.status)) errors.push(`invalid review status for ${id}: ${String(review?.status)}`);
@@ -117,21 +264,44 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
if (typeof review?.reviewedAt !== 'string' || Number.isNaN(Date.parse(review.reviewedAt))) {
errors.push(`review ${id} needs an ISO reviewedAt timestamp`);
}
if (typeof review?.formHash !== 'string' || !review.formHash.trim()) {
errors.push(`review ${id} needs a formHash of the reviewed content`);
} else if (conceptsById.has(id) && review.formHash !== conceptContentHash(conceptsById.get(id))) {
errors.push(`review ${id} is stale: concept content changed since it was reviewed; reset or re-review it`);
}
if (review?.note !== undefined && (typeof review.note !== 'string' || !review.note.trim() || review.note.length > 500)) {
errors.push(`review ${id} note must be a non-empty string of 500 characters or fewer`);
}
// Rating grades how strong an approved concept is (3 exceptional, 2 solid,
// 1 marginal keep). Optional, approved-only, and read as a calibration
// signal for future authoring rounds.
if (review?.rating !== undefined) {
if (![1, 2, 3].includes(review.rating)) {
errors.push(`review ${id} rating must be 1, 2, or 3`);
} else if (review.status !== 'approved') {
errors.push(`review ${id} rating only applies to approved concepts`);
}
}
}
const wellTierById = new Map((catalog?.wells || []).map(well => [well.id, well.tier]));
const approved = concepts.filter(concept => reviewData?.reviews?.[concept.id]?.status === 'approved');
const approvedFamilies = new Set(
const approvedTiers = new Set(
(catalog?.families || [])
.filter(family => family.concepts?.some(concept => reviewData?.reviews?.[concept.id]?.status === 'approved'))
.map(family => family.id)
.map(family => wellTierById.get(family.well))
.filter(tier => WELL_TIERS.includes(tier))
);
if (approved.length < 3) errors.push('at least three concepts must be approved');
if (approvedFamilies.size < 3) errors.push('approved concepts must span at least three families');
if (requireApprovedMinimum && approved.length < 3) errors.push('at least three concepts must be approved');
if (requireApprovedMinimum && approvedTiers.size < WELL_TIERS.length) {
errors.push('approved concepts must cover every challenger tier');
}
return {
errors,
warnings,
stats: {
wells: wellIds.size,
families: familyIds.size,
concepts: concepts.length,
approved: approved.length,
@@ -144,7 +314,7 @@ export function validateConceptCatalog(catalog, reviewData, { expectedTotal, min
export function approvedPoolRevision(concepts) {
const payload = concepts
.filter(concept => concept.status === 'approved')
.map(concept => `${concept.familyId}:${concept.id}:${concept.form}`)
.map(concept => `${concept.familyId}:${concept.id}:${concept.strength}:${concept.form}:${concept.spark}:${JSON.stringify(concept.system)}:${concept.webLeverage}`)
.sort()
.join('\n');
return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12);
+21 -3
View File
@@ -3,20 +3,38 @@
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { readConceptCatalog, validateConceptCatalog } from './lib/concept-catalog.mjs';
import { readCompositionCatalog, validateCompositionCatalog } from './lib/composition-catalog.mjs';
const here = dirname(fileURLToPath(import.meta.url));
const { catalog, reviewData } = readConceptCatalog(
join(here, 'concept-ingredients.json'),
join(here, 'concept-reviews.json')
);
const result = validateConceptCatalog(catalog, reviewData, { minimumTotal: 2304 });
const result = validateConceptCatalog(catalog, reviewData, { minimumTotal: 260 });
const compositionState = readCompositionCatalog(
join(here, 'composition-ingredients.json'),
join(here, 'composition-reviews.json')
);
const compositionResult = validateCompositionCatalog(compositionState.catalog, compositionState.reviewData);
let failed = false;
if (result.errors.length > 0) {
for (const error of result.errors) process.stderr.write(`concept-catalog: ${error}\n`);
process.exitCode = 1;
failed = true;
} else {
process.stdout.write(
`concept-catalog: ${result.stats.concepts} concepts across ${result.stats.families} families; ` +
`concept-catalog: ${result.stats.concepts} concepts across ${result.stats.families} families in ${result.stats.wells} wells; ` +
`${result.stats.approved} approved, ${result.stats.pending} pending, ${result.stats.rejected} rejected\n`
);
}
if (compositionResult.errors.length > 0) {
for (const error of compositionResult.errors) process.stderr.write(`composition-catalog: ${error}\n`);
failed = true;
} else {
process.stdout.write(
`composition-catalog: ${compositionResult.stats.compositions} compositions across ${compositionResult.stats.families} families; ` +
`${compositionResult.stats.approved} approved, ${compositionResult.stats.rejected} rejected\n`
);
}
if (failed) process.exitCode = 1;
+353 -13
View File
@@ -1,17 +1,46 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { readConceptCatalog, validateConceptCatalog } from '../skill/scripts/lib/concept-catalog.mjs';
import { selectApprovedChallengers } from '../skill/scripts/concept-seed.mjs';
import {
conceptContentHash,
readConceptCatalog,
validateConceptCatalog,
validateConceptEntry,
} from '../skill/scripts/lib/concept-catalog.mjs';
import { renderChallenger, selectApprovedChallengers, selectApprovedStaging } from '../skill/scripts/concept-seed.mjs';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const SCRIPT = path.join(ROOT, 'skill', 'scripts', 'concept-seed.mjs');
function run(scope) {
return spawnSync(process.execPath, [SCRIPT, '--scope', scope, '--from', 'stable-test'], {
const TEST_WELLS = [
{ id: 'well-one', label: 'Well One', tier: 'graphic', description: 'Graphic systems whose compositional laws transfer to interface almost directly.' },
{ id: 'well-two', label: 'Well Two', tier: 'atmosphere', description: 'Material worlds with their own physics, topology, and memorable human experience.' },
{ id: 'well-three', label: 'Well Three', tier: 'interaction', description: 'Instruments and display hardware that already solved showing state to people.' },
{ id: 'well-four', label: 'Well Four', tier: 'atmosphere', description: 'Performed time: sequence and choreography of attention through time and space.' },
{ id: 'well-five', label: 'Well Five', tier: 'interaction', description: 'The browser and computation treated as the design material in its own right.' },
];
const TEST_SYSTEM = [
'Palette/material: ink-dark ground, unbleached paper field, and one saturated signal color reserved for the active decision',
'Type/composition: compact grotesk labels over generous editorial serif, ranked on a strict modular grid',
'Topology/navigation: move between the whole field and one consequential detail',
'Controls/state: reveal compare and preserve every meaningful authored state',
'Responsive/motion: recompose the spatial law while retaining orientation',
];
function reviewFor(concept, status = 'approved') {
return {
status,
reviewedBy: 'pbakaus',
reviewedAt: '2026-07-18T00:00:00.000Z',
formHash: conceptContentHash(concept),
};
}
function run(scope, extraArgs = []) {
return spawnSync(process.execPath, [SCRIPT, '--scope', scope, '--from', 'stable-test', ...extraArgs], {
cwd: ROOT,
encoding: 'utf-8',
});
@@ -29,6 +58,10 @@ describe('concept seed scopes', () => {
assert.match(directionA.stdout, /selected\s+independently/);
assert.match(directionA.stdout, /substantially different future surface/);
assert.match(directionA.stdout, /Never expose promotion metadata/);
assert.match(directionA.stdout, /SYSTEM GRAMMAR:/);
assert.match(directionA.stdout, /CREATIVE SPARK:/);
assert.match(directionA.stdout, /WEB LEVERAGE:/);
assert.doesNotMatch(directionA.stdout, /undefined/);
assert.match(surface.stdout, /SURFACE CONCEPT SEED/);
assert.match(surface.stdout, /committed visual identity/);
});
@@ -39,30 +72,337 @@ describe('concept seed scopes', () => {
assert.match(result.stderr, /direction or surface/);
});
it('validates the expanded catalog and human review gate', () => {
it('keeps staging challengers inside the requested surface mode', () => {
const pool = [
{ id: 'persuade-stage', surface: 'persuade', status: 'approved' },
{ id: 'experience-stage', surface: 'experience', status: 'approved' },
];
const experience = selectApprovedStaging({
scope: 'direction',
key: 'mode-match',
mode: 'experience',
sourceCompositions: pool,
});
const read = selectApprovedStaging({
scope: 'direction',
key: 'mode-missing',
mode: 'read',
sourceCompositions: pool,
});
assert.equal(experience?.id, 'experience-stage');
assert.equal(read, null, 'a missing mode must not borrow an unrelated staging');
const rendered = run('direction', ['--mode', 'experience']);
assert.equal(rendered.status, 0);
assert.match(rendered.stdout, /mode: experience/);
assert.match(rendered.stdout, /--scope direction --mode experience --from stable-test/);
});
it('validates the web-generative catalog and human review gate', () => {
const catalogPath = path.join(ROOT, 'skill', 'scripts', 'concept-ingredients.json');
const reviewsPath = path.join(ROOT, 'skill', 'scripts', 'concept-reviews.json');
const { catalog, reviewData, concepts } = readConceptCatalog(catalogPath, reviewsPath);
const result = validateConceptCatalog(catalog, reviewData, { minimumTotal: 2304 });
const result = validateConceptCatalog(catalog, reviewData, { minimumTotal: 260 });
assert.deepEqual(result.errors, []);
assert.equal(result.stats.families, 36);
assert.equal(result.stats.concepts >= 2304, true);
assert.equal(result.stats.families, 26);
assert.equal(result.stats.wells, 7);
assert.deepEqual(
catalog.wells.map(well => well.id),
[
'graphic-systems',
'canon-movements',
'vernacular-ephemera',
'instruments-signals',
'medium-native',
'material-worlds',
'performed-time',
]
);
assert.deepEqual(
catalog.families.map(family => family.id),
[
'graphic-worlds-publishing',
'posters-covers-sleeves',
'notation-diagram-systems',
'maps-journeys',
'design-canon',
'digital-design-canon',
'brand-identity-canon',
'vernacular-ephemera',
'pop-culture-shelf',
'signals-instruments',
'machines-contraptions',
'medium-native',
'space',
'earth-water-worlds',
'living-worlds',
'light-hidden-forces',
'patterns-motion',
'craft-making',
'fashion-folded-worlds',
'food-potioncraft',
'mythical-worlds',
'performance-spectacle',
'architecture-places',
'games-toys-physics-play',
'festivals-public-life',
'memory-ruins',
]
);
// The five high-transfer wells together must not be a minority of the corpus.
const wellTotals = new Map();
for (const family of catalog.families) {
wellTotals.set(family.well, (wellTotals.get(family.well) || 0) + family.concepts.length);
}
const highTransfer = ['graphic-systems', 'canon-movements', 'vernacular-ephemera', 'instruments-signals', 'medium-native']
.reduce((sum, well) => sum + (wellTotals.get(well) || 0), 0);
assert.equal(highTransfer >= concepts.length * 0.45, true, `high-transfer wells hold ${highTransfer}/${concepts.length}`);
const wellIds = new Set(catalog.wells.map(well => well.id));
assert.equal(catalog.families.every(family => wellIds.has(family.well)), true);
for (const well of catalog.wells) {
assert.equal(catalog.families.some(family => family.well === well.id), true, `well ${well.id} has families`);
}
assert.equal(result.stats.concepts >= 260, true);
assert.equal(catalog.families.every(family => family.concepts.length >= 2), true);
assert.equal(result.stats.approved >= 3, true);
assert.equal(result.stats.pending + result.stats.approved + result.stats.rejected, result.stats.concepts);
assert.equal(fs.statSync(catalogPath).size > 500_000, true);
assert.equal(concepts.every(concept => concept.system.length === 5 && concept.spark.length >= 80 && concept.webLeverage.length >= 20), true);
assert.equal(
concepts.filter(concept => concept.status === 'approved').length,
result.stats.approved
);
});
it('selects three approved challengers from distinct families', () => {
it('rejects concepts that name a motif without system grammar and web leverage', () => {
const catalog = {
schemaVersion: 7,
catalogVersion: 'test',
wells: TEST_WELLS,
families: ['one', 'two', 'three'].map((family, index) => ({
id: family,
label: family,
well: TEST_WELLS[index].id,
concepts: [{
id: `${family}-newspaper`,
form: 'a newspaper front page, with headline hierarchy and columns',
lineage: 'editorial publishing',
tags: ['hierarchy', 'columns', 'serial'],
strength: 'dual',
...(index === 0 ? {} : {
system: TEST_SYSTEM,
spark: 'Ink-dark columns break open into a field of live scenes, unfolding evidence, and reader-controlled depth while the edition retains one unmistakable rhythm.',
webLeverage: 'stream live updates into addressable sections with reader-controlled pacing',
}),
}],
})),
};
const reviewData = {
schemaVersion: 2,
reviews: Object.fromEntries(catalog.families.map(family => [family.concepts[0].id, reviewFor(family.concepts[0])])),
};
const result = validateConceptCatalog(catalog, reviewData);
assert.equal(result.errors.some(error => error.includes('one-newspaper') && error.includes('system grammar')), true);
assert.equal(result.errors.some(error => error.includes('one-newspaper') && error.includes('web leverage')), true);
});
it('rejects literal operations archetypes even when their UI grammar is complete', () => {
const catalog = {
schemaVersion: 7,
catalogVersion: 'test',
wells: TEST_WELLS,
families: ['one', 'two', 'three'].map((family, index) => ({
id: family,
label: family,
well: TEST_WELLS[index].id,
concepts: [{
id: `${family}-concept`,
form: index === 0
? 'a mission control room, where panels, alerts, and operator stations coordinate a launch'
: `a tidal cavern ${family}, where changing water levels reveal chambers, thresholds, and remembered routes`,
lineage: 'immersive environmental experience and tidal geology',
tags: ['depth', 'threshold', 'rhythm'],
strength: 'dual',
spark: 'Cold reflected light moves across the cavern ceiling as the tide opens one route, closes another, and leaves a bright mineral trace of every recent high-water mark.',
system: [
'Palette/material: wet basalt grays, cold green water light, and one warm mineral seam marking the active route',
'Type/composition: narrow engraved capitals for chamber names over a quiet cartographic body voice',
'Topology/navigation: move by chamber, waterline, and revealed threshold',
'Controls/state: inspect exposed, flooding, submerged, and remembered states',
'Responsive/motion: turn depth into a stepwise route and honor reduced motion',
],
webLeverage: 'WebGL depth rendering with a complete keyboard-readable route index',
}],
})),
};
const reviewData = {
schemaVersion: 2,
reviews: Object.fromEntries(catalog.families.map(family => [family.concepts[0].id, reviewFor(family.concepts[0])])),
};
const result = validateConceptCatalog(catalog, reviewData);
assert.equal(result.errors.some(error => error.includes('one-concept') && error.includes('operations archetype')), true);
});
it('welcomes materially specific craft tools instead of confusing them with operational software', () => {
const errors = validateConceptEntry({
id: 'cabinetmaker-workbench',
form: "a cabinetmaker's workbench, where holdfasts, planing stops, shavings, and old cuts turn careful joinery into visible memory",
lineage: 'Cabinetmaking benches, workholding craft, hand-planing practice, and tool-wear conservation',
tags: ['workholding', 'joinery', 'patina'],
strength: 'world',
spark: 'Morning light crosses a blackened bench top as pale curls gather behind a plane, a holdfast rings into place, and fresh dovetails rise from a century of cuts.',
system: [
'Palette/material: blackened beech, pale shaving curls, and brass holdfast glints over a workshop-gray ground',
'Type/composition: stamped maker marks and penciled layout lines ranked against a strong horizontal bench datum',
'Topology/navigation: follow stock from reference face through marked joints and fitted assemblies',
'Controls/state: clamp, mark, plane, pare, dry-fit, revise, and preserve grain direction',
'Responsive/motion: unfold the bench into a project sequence and follow each hand gesture',
],
webLeverage: 'Grain-aware direct manipulation, reversible project history, and a keyboard-readable construction diagram',
});
assert.deepEqual(errors, []);
});
it('selects six approved challengers, two from every translation tier', () => {
for (let index = 0; index < 200; index += 1) {
const { picks } = selectApprovedChallengers({ scope: 'surface', key: `coverage-${index}` });
assert.equal(picks.length, 3);
assert.equal(new Set(picks.map(pick => pick.familyId)).size, 3);
assert.equal(picks.length, 6);
for (const tier of ['graphic', 'interaction', 'atmosphere']) {
assert.equal(picks.filter(pick => pick.wellTier === tier).length, 2);
}
assert.equal(new Set(picks.map(pick => pick.id)).size, 6);
assert.equal(new Set(picks.map(pick => pick.familyId)).size, 6);
assert.equal(picks.every(pick => pick.status === 'approved'), true);
}
});
it('re-rolls draw disjoint challengers and stay reproducible from the base key', () => {
const rounds = [0, 1, 2].map(reroll =>
selectApprovedChallengers({ scope: 'direction', key: 'reroll-chain', reroll })
);
const again = selectApprovedChallengers({ scope: 'direction', key: 'reroll-chain', reroll: 2 });
assert.deepEqual(again.picks.map(pick => pick.id), rounds[2].picks.map(pick => pick.id));
const seen = new Set();
for (const round of rounds) {
assert.equal(round.picks.length, 6);
assert.equal(round.picks.every(pick => !seen.has(pick.id)), true);
for (const pick of round.picks) seen.add(pick.id);
for (const tier of ['graphic', 'interaction', 'atmosphere']) {
assert.equal(round.picks.filter(pick => pick.wellTier === tier).length, 2);
}
}
});
it('renders re-roll rounds with elimination framing and a chained reproduction key', () => {
const round0 = run('direction');
const round1A = run('direction', ['--reroll', '1']);
const round1B = run('direction', ['--reroll', '1']);
assert.equal(round1A.status, 0);
assert.equal(round1A.stdout, round1B.stdout);
assert.notEqual(round1A.stdout, round0.stdout);
assert.match(round1A.stdout, /RE-ROLL ROUND 1/);
assert.match(round1A.stdout, /may not return reworded/);
assert.match(round1A.stdout, /--from stable-test --reroll 1/);
assert.doesNotMatch(round0.stdout, /RE-ROLL ROUND/);
const invalid = run('direction', ['--reroll', 'nope']);
assert.notEqual(invalid.status, 0);
assert.match(invalid.stderr, /non-negative integer/);
});
it('filters challengers by strength per scope and falls back when a tier has no match', () => {
const make = (id, tier, strength) => ({
id,
familyId: `${id}-family`,
wellId: `${id}-well`,
wellTier: tier,
strength,
status: 'approved',
form: `${id} form`,
spark: `${id} spark`,
system: [],
webLeverage: `${id} web`,
});
const pool = [
make('poster', 'graphic', 'world'),
make('flipbook', 'graphic', 'composition'),
make('radar', 'interaction', 'dual'),
make('cavern', 'atmosphere', 'world'),
];
for (let index = 0; index < 40; index += 1) {
const direction = selectApprovedChallengers({ scope: 'direction', key: `d-${index}`, sourceConcepts: pool });
assert.equal(direction.picks.every(pick => pick.strength !== 'composition'), true);
const surface = selectApprovedChallengers({ scope: 'surface', key: `s-${index}`, sourceConcepts: pool });
const surfaceGraphic = surface.picks.find(pick => pick.wellTier === 'graphic');
assert.equal(surfaceGraphic.id, 'flipbook');
// atmosphere has no composition|dual entries, so surface falls back to its full pool
const surfaceAtmosphere = surface.picks.find(pick => pick.wellTier === 'atmosphere');
assert.equal(surfaceAtmosphere.id, 'cavern');
}
});
it('weights challenger draws by approval rating without shrinking the pool', () => {
const make = (id, rating) => ({
id,
familyId: `${id}-family`,
wellId: `${id}-well`,
wellTier: 'graphic',
strength: 'world',
status: 'approved',
form: `${id} form`,
spark: `${id} spark`,
system: [],
webLeverage: `${id} web`,
review: rating ? { status: 'approved', rating } : { status: 'approved' },
});
const filler = (tier, id) => ({
...make(id, undefined),
wellTier: tier,
});
const pool = [
make('flagship', 3),
make('solid-a', 2),
make('solid-b', undefined),
make('marginal', 1),
filler('interaction', 'radar'),
filler('atmosphere', 'cavern'),
];
const counts = { flagship: 0, 'solid-a': 0, 'solid-b': 0, marginal: 0 };
for (let index = 0; index < 300; index += 1) {
const { picks } = selectApprovedChallengers({ scope: 'direction', key: `weight-${index}`, sourceConcepts: pool });
const graphicFirst = picks.find(pick => pick.wellTier === 'graphic');
counts[graphicFirst.id] += 1;
}
assert.equal(counts.marginal, 0);
// Two tickets should put the flagship on top roughly twice as often as an
// unrated peer; a generous margin keeps the assertion deterministic-safe.
assert.equal(counts.flagship > counts['solid-b'] * 1.3, true,
`flagship ${counts.flagship} vs solid-b ${counts['solid-b']}`);
// A tier holding only 1-star approvals still yields challengers.
const onlyMarginal = [
make('lone-marginal', 1),
filler('interaction', 'radar2'),
filler('atmosphere', 'cavern2'),
];
const { picks } = selectApprovedChallengers({ scope: 'direction', key: 'lone', sourceConcepts: onlyMarginal });
assert.equal(picks.some(pick => pick.id === 'lone-marginal'), true);
});
it('renders the vivid spark before system and browser leverage', () => {
const output = renderChallenger({
form: 'a spiral galaxy, where gravity, orbit, density, and darkness organize attention across radical scales',
spark: 'A brilliant core holds the central promise while related ideas travel through spiral arms and distant fragments wait at the edge of perception.',
system: [
'Palette/material: deep space black, star-white points, and one warm core glow reserved for the focus',
'Type/composition: hairline astronomical labels orbiting a monumental numeral voice',
'Topology/navigation: orbit a stable core and travel by arm or scale',
'Controls/state: focus, compare, capture, and release orbiting material',
'Responsive/motion: collapse depth into a radial sequence and honor reduced motion',
],
webLeverage: 'WebGL semantic zoom with a complete keyboard-readable DOM index',
}, 0);
assert.match(output, /spiral galaxy/);
assert.match(output, /CREATIVE SPARK: A brilliant core/);
assert.equal(output.indexOf('CREATIVE SPARK:') < output.indexOf('SYSTEM GRAMMAR:'), true);
});
});
+34 -6
View File
@@ -95,6 +95,7 @@ describe('docs integrity', () => {
const shape = fs.readFileSync(path.join(ROOT, 'skill/reference/shape.md'), 'utf8');
const craft = fs.readFileSync(path.join(ROOT, 'skill/reference/craft.md'), 'utf8');
const skill = fs.readFileSync(path.join(ROOT, 'skill/SKILL.src.md'), 'utf8');
const buildFloor = fs.readFileSync(path.join(ROOT, 'skill/reference/build-floor.md'), 'utf8');
const document = fs.readFileSync(path.join(ROOT, 'skill/reference/document.md'), 'utf8');
const codex = fs.readFileSync(path.join(ROOT, 'skill/reference/codex.md'), 'utf8');
const typeset = fs.readFileSync(path.join(ROOT, 'skill/reference/typeset.md'), 'utf8');
@@ -108,17 +109,43 @@ describe('docs integrity', () => {
expect(newWork).toContain('Missing DESIGN.md does not route back to init');
expect(newWork).toContain('Shape or select the direction');
expect(newWork).toContain('concept-seed.mjs --scope direction');
expect(newWork).toContain('concept-seed.mjs --scope surface');
expect(newWork).toContain('Local extension inside stable authority: shape, do not seed');
expect(newWork).toContain('concept-seed.mjs --scope direction --mode');
expect(newWork).toContain('concept-seed.mjs --scope surface --mode');
expect(newWork).toContain('persuade, operate, read, or experience');
expect(skill).toContain('what the visitor\'s success looks like');
expect(skill).toContain('a docs index is Read, not Persuade');
expect(newWork).toContain('Local extension (B or C): inherit, do not reseed');
expect(newWork).toContain('Never run it for a local extension');
expect(newWork).toContain('Do not select a new world and its first surface concept in separate tournaments');
expect(newWork).toContain('do not reopen either half independently');
expect(newWork).toContain("audience's cultural home");
expect(newWork).toContain('category default and its predictable contrarian response');
expect(newWork).toContain('system leverage, and use of the medium');
expect(newWork).toContain('discard the source carrier, materials, and vocabulary unless product evidence earns them');
expect(newWork).toContain('.impeccable/surfaces/<target-slug>.md');
expect(newWork).toContain('DIRECTION CONTRACT');
expect(newWork).toContain('`THESIS`: the product-specific idea');
expect(newWork).toContain('`BAR-RAISER`: the product-native use');
expect(newWork).toContain('routine functional state alone does not clear the bar');
expect(newWork).toContain('non-routine bar-raiser');
expect(newWork).toContain('first-surface expression, bar-raiser, cross-surface consequence');
expect(newWork).toContain('veto rather than rationalize');
expect(newWork).toContain('every relationship it visualizes exists in product truth; resemblance is not evidence');
expect(newWork).toContain("removing its bar-raiser materially weakens the surface's central argument or use");
expect(newWork).toContain('on the primary device and within the real asset and tool budget');
expect(newWork).toContain('not a probable violation of the brief or audience');
expect(newWork).toContain('Passing some floors never rescues a failed one');
expect(newWork.match(/Apply the candidate floor/g)?.length).toBe(2);
expect(newWork).toContain('[codex.md](codex.md)');
expect(newWork).toContain('Commit before correcting');
expect(newWork).toContain('Judge candidates skin-blind');
expect(newWork).toContain('judge survivors skin-blind');
expect(newWork).toContain('Present, visualize, re-roll');
expect(newWork).toContain('When the harness can generate images');
expect(newWork).toContain('no image persists as a project file');
expect(newWork).toContain('eliminates every candidate presented so far');
expect(newWork).toContain('optional one-line steer');
expect(newWork).toContain('--from <key> --reroll <round>');
expect(newWork).toContain('ask what quality is missing before rolling a third time');
expect(newWork).not.toContain('palette.mjs');
expect(shape).toContain('follow [new-work.md](new-work.md)');
@@ -126,7 +153,8 @@ describe('docs integrity', () => {
expect(shape).toContain('shape never writes code or a direction contract');
expect(craft).toContain('deprecated alias');
expect(craft).toContain('adds no setup, interview, checkpoint, tool, or quality behavior');
expect(skill).toContain('Premium motion is not transform/opacity alone');
expect(skill).toContain('[reference/build-floor.md](reference/build-floor.md) immediately before editing UI');
expect(buildFloor).toContain('do not rely on transform/opacity alone');
expect(newWork).toContain('The old DESIGN.md and implementation are not authority');
expect(newWork).toContain('Do not offer replacement worlds unless the user asked for a redesign');
expect(document).toContain('Run **Select one direction**');
@@ -135,7 +163,7 @@ describe('docs integrity', () => {
expect(codex).toContain('Do not generate a palette artifact');
expect(typeset).toContain('route through [new-work.md](new-work.md)');
expect(skill).not.toContain('reference/finish.md');
expect(skill).toContain('manual scan only when no automatic detector is active');
expect(buildFloor).toContain('manual scan only when no automatic detector is active');
expect(context).toContain('renderLlmOnlySlopReview');
expect(context).toContain('automaticHookMode');
});
+9
View File
@@ -112,6 +112,10 @@
border-radius: 10px;
}
.pass-status-alert {
border-left: 4px solid #2563eb;
}
.flag-square-top {
border-top: 4px solid #7c3aed;
border-radius: 0;
@@ -201,6 +205,11 @@
<p>A 1px colored rule is below the decorative side-tab threshold.</p>
</div>
<div class="case pass-status-alert" role="status">
<h3>Status toast with severity accent</h3>
<p>A live status region carries a colored side bar as a severity accent, not a decorative side tab.</p>
</div>
<div class="case flag-square-top">
<h3>Square top border</h3>
<p>A chromatic top stripe on a card is the horizontal variant of the side tab.</p>
+12 -12
View File
@@ -2790,8 +2790,7 @@ describe('extractDirectionContract()', () => {
const contractComment = [
'<!--',
'DIRECTION CONTRACT',
'UNIQUE: the page is a boarding pass, read top to bottom like a gate agent would.',
'NOT-TEMPLATE: no centered hero, no three-card feature row.',
'THESIS: the page is a boarding pass and refuses the centered travel hero.',
'-->',
].join('\n');
@@ -2810,18 +2809,18 @@ describe('extractDirectionContract()', () => {
});
it('extracts a JSX direction-contract block', () => {
const jsx = `{/*\nDIRECTION CONTRACT\nUNIQUE: the timeline folds around the evidence.\n*/}\nexport default function Page() { return <main />; }`;
const jsx = `{/*\nDIRECTION CONTRACT\nTHESIS: the timeline folds around the evidence.\n*/}\nexport default function Page() { return <main />; }`;
assert.match(extractDirectionContract(jsx) || '', /timeline folds/);
});
it('reports missing contract fields deterministically', () => {
assert.deepEqual(
missingDirectionContractFields('DIRECTION CONTRACT\nUNIQUE: own idea\nFORM: evidence timeline'),
['NOT-TEMPLATE', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT'],
missingDirectionContractFields('DIRECTION CONTRACT\nTHESIS: own idea\nFORM: evidence timeline'),
['OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER'],
);
assert.deepEqual(missingDirectionContractFields([
'UNIQUE: x', 'NOT-TEMPLATE: x', 'OWN-WORLD: x',
'STORY: x', 'FIRST VIEWPORT: x', 'FORM: x',
'THESIS: x', 'OWN-WORLD: x', 'STORY: x',
'FIRST VIEWPORT: x', 'BAR-RAISER: x', 'FORM: x',
].join('\n')), []);
});
@@ -2873,9 +2872,9 @@ describe('extractDirectionContract()', () => {
});
it('renderContractAudit flags an incomplete promise contract', () => {
const text = renderContractAudit([{ filePath: '/tmp/page.html', contract: 'DIRECTION CONTRACT\nUNIQUE: one idea' }], { cwd: '/tmp' });
const text = renderContractAudit([{ filePath: '/tmp/page.html', contract: 'DIRECTION CONTRACT\nTHESIS: one idea' }], { cwd: '/tmp' });
assert.match(text, /Contract integrity defect/);
assert.match(text, /NOT-TEMPLATE/);
assert.match(text, /BAR-RAISER/);
});
});
@@ -2887,8 +2886,9 @@ describe('runStopHook() — direction-contract audit', () => {
const CONTRACT_HTML = [
'<!--',
'DIRECTION CONTRACT',
'UNIQUE: the page is a boarding pass.',
'THESIS: the page is a boarding pass.',
'FIRST VIEWPORT: gate number dominates.',
'BAR-RAISER: live gate status changes the reading path.',
'-->',
'<!doctype html><html><body><h1>Gate 12</h1></body></html>',
].join('\n');
@@ -2969,7 +2969,7 @@ describe('runStopHook() — direction-contract audit', () => {
for (const ext of htmlFamily) {
const sid = `contract-${ext}`;
const file = write(`src/Page.${ext}`, `<!--\nDIRECTION CONTRACT\nUNIQUE: ${ext} evidence ribbon.\n-->\n<main />`);
const file = write(`src/Page.${ext}`, `<!--\nDIRECTION CONTRACT\nTHESIS: ${ext} evidence ribbon.\n-->\n<main />`);
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.match(stop.stdout, new RegExp(`${ext} evidence ribbon`));
@@ -2977,7 +2977,7 @@ describe('runStopHook() — direction-contract audit', () => {
for (const ext of jsxFamily) {
const sid = `contract-${ext}`;
const file = write(`src/Page.${ext}`, `{/*\nDIRECTION CONTRACT\nUNIQUE: ${ext} evidence ribbon.\n*/}\nexport default function Page() { return <main />; }`);
const file = write(`src/Page.${ext}`, `{/*\nDIRECTION CONTRACT\nTHESIS: ${ext} evidence ribbon.\n*/}\nexport default function Page() { return <main />; }`);
await touch(file, sid, fakeDetector([]));
const stop = await runStopHook({ stdinJson: JSON.stringify(stopEvent(sid)), env: {}, cwd, detector: fakeDetector([]) });
assert.match(stop.stdout, new RegExp(`${ext} evidence ribbon`));
+61
View File
@@ -0,0 +1,61 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { LIVE_UI_SURFACES } from '../skill/scripts/live/ui-core.mjs';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const read = (file) => fs.readFileSync(path.join(ROOT, file), 'utf8');
test('Live UI lab is a focused state catalog', () => {
const page = read('site/pages/labs/live-ui/index.astro');
assert.match(page, /<LiveUiGallery\s*\/>/);
assert.match(page, /canonicalPath="\/labs\/live-ui"/);
assert.match(page, /noIndex/);
assert.doesNotMatch(page, /benchmark|performance|provider/i);
});
test('Live UI gallery covers every visible production surface', () => {
const gallery = read('site/components/LiveUiGallery.astro');
const visibleSurfaces = LIVE_UI_SURFACES
.map((surface) => surface.key)
.filter((key) => key !== 'css-isolation-boundary');
for (const surface of visibleSurfaces) {
assert.match(gallery, new RegExp(`['"]${surface}['"]`), `${surface} needs a gallery snapshot`);
}
assert.match(gallery, /uncoveredSurfaces/);
assert.match(gallery, /LIVE_COMMANDS/);
});
test('Live UI gallery organizes states into browsable workflow clusters', () => {
const gallery = read('site/components/LiveUiGallery.astro');
const runtime = read('site/scripts/live-ui-gallery.js');
const styles = read('site/styles/live-ui-gallery.css');
assert.match(gallery, /data-gallery-group=/);
assert.match(gallery, /data-gallery-cluster/);
assert.match(gallery, /data-gallery-host="light"/);
assert.doesNotMatch(gallery, /<select|live-ui-gallery__quick-nav/);
assert.match(runtime, /groups\.flatMap/);
assert.match(runtime, /function renderGroup/);
assert.match(runtime, /data-gallery-state-card/);
assert.match(runtime, /button\[data-gallery-group\]/);
assert.match(runtime, /button\[data-gallery-host\]/);
assert.match(runtime, /galleryActiveHost/);
assert.match(runtime, /function syncPreviewTargetGeometry/);
assert.match(runtime, /targetRect\.top - stageRect\.top/);
assert.match(runtime, /targetBox\.top - 28/);
assert.doesNotMatch(styles, /\.lvg-selection-outline\s*\{[^}]*top:\s*70px/s);
assert.match(styles, /\.live-ui-gallery :where\(button, input, select\)/);
assert.match(styles, /\.lvg-configure-modifier:not\(\.is-count\) > span/);
assert.match(styles, /\.lvg-pending-pill > span:not\(\.lvg-pending-count\)/);
});
test('legacy Live Lab URL redirects to the labs namespace', () => {
const redirect = read('site/pages/live-lab/index.astro');
assert.match(redirect, /Astro\.redirect\('\/labs\/live-ui', 301\)/);
});
@@ -101,7 +101,7 @@ for (const modelId of resolveModelList()) {
assert.ok(implementation > question, `implementation began before the attended concept checkpoint.\n${workflowTraceMessage(trace)}`);
const artifact = fs.readFileSync(path.join(workspace, 'index.html'), 'utf8');
assert.match(artifact.slice(0, 1400), /DIRECTION CONTRACT/i);
for (const field of ['UNIQUE', 'NOT-TEMPLATE', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'FORM']) {
for (const field of ['THESIS', 'OWN-WORLD', 'STORY', 'FIRST VIEWPORT', 'BAR-RAISER', 'FORM']) {
assert.match(artifact.slice(0, 1800), new RegExp(`${field}:`, 'i'));
}
} finally {
+294 -9
View File
@@ -5,24 +5,125 @@ import path from 'node:path';
import { Readable } from 'node:stream';
import { describe, it } from 'node:test';
import { worldsReviewPlugin } from '../scripts/worlds-review-vite-plugin.mjs';
import { conceptContentHash } from '../skill/scripts/lib/concept-catalog.mjs';
const FIVE_RULE_SYSTEM = [
'Palette/material: ink-dark ground, unbleached paper field, and one saturated signal color for the active decision',
'Type/composition: compact grotesk labels over a generous editorial serif, ranked on a strict modular grid',
'Topology/navigation: move between the whole field and one consequential detail',
'Controls/state: reveal compare and preserve every meaningful authored state',
'Responsive/motion: recompose the spatial law while retaining orientation',
];
function concept(id, form, spark) {
return {
id,
form,
lineage: 'A specific lineage of visual craft and spatial experience',
tags: ['spatial-law', 'material-state', 'responsive-motion'],
strength: 'dual',
system: FIVE_RULE_SYSTEM,
spark,
webLeverage: 'WebGL rendering, direct manipulation, responsive motion, and deep-linked state',
};
}
async function fixtureRoot() {
const root = await mkdtemp(path.join(os.tmpdir(), 'impeccable-worlds-'));
const scripts = path.join(root, 'skill', 'scripts');
await mkdir(scripts, { recursive: true });
await writeFile(path.join(scripts, 'concept-ingredients.json'), JSON.stringify({
schemaVersion: 2,
schemaVersion: 7,
catalogVersion: 'test',
qualityBar: {
principle: 'Every concept must create an immediate mental image and turn its source world into meaningful interface structure.',
rejectIf: ['generic software', 'decorative metaphor', 'weak causality', 'ordinary chrome', 'inaccessible spectacle'],
reviewAxes: ['specificity', 'laws', 'yield', 'inevitability', 'interaction', 'legibility', 'elasticity', 'execution'],
},
wells: [
{ id: 'well-one', label: 'Well One', tier: 'graphic', description: 'Graphic systems whose compositional laws transfer to interface almost directly.' },
{ id: 'well-two', label: 'Well Two', tier: 'atmosphere', description: 'Material worlds with their own physics, topology, and memorable human experience.' },
{ id: 'well-three', label: 'Well Three', tier: 'interaction', description: 'Instruments and display hardware that already solved showing state to people.' },
{ id: 'well-four', label: 'Well Four', tier: 'atmosphere', description: 'Performed time: sequence and choreography of attention through time and space.' },
{ id: 'well-five', label: 'Well Five', tier: 'interaction', description: 'The browser and computation treated as the design material in its own right.' },
],
families: [
{
id: 'editorial',
label: 'Editorial',
concepts: [{ id: 'editorial-field-guide', form: 'a field guide, with plates, keys, and indexes', lineage: 'reference publishing', tags: ['plates', 'keys', 'index'] }],
well: 'well-one',
concepts: [
concept(
'editorial-field-guide',
'a field guide, with specimen plates, branching keys, and index tabs',
'Pressed specimens unfold into a moonlit field of branching veins, shifting habitats, and magnified traits that make every identification choice visibly consequential.'
),
concept(
'editorial-living-broadsheet',
'a living broadsheet, with moving photographs, sectional rhythm, and spatial story layers',
'A monumental front page wakes like a stage as photographs become moving windows and each opened headline carries the reader into a layered scene without losing the edition.'
),
],
},
{
id: 'mapping',
label: 'Mapping',
well: 'well-three',
concepts: [concept(
'mapping-celestial-instrument',
'a celestial instrument, with a dark horizon, rotating stars, and resolving position',
'A dark horizon locks beneath a rotating field of stars; aligning one bright body with time and altitude slowly resolves uncertainty into a precise point.'
)],
},
{
id: 'material',
label: 'Material',
well: 'well-two',
concepts: [concept(
'material-blown-glass',
'a blown-glass vessel, with molten breath, colored layers, and refracted light',
'Molten glass gathers on a pipe and inflates with each breath while ribbons of color stretch through its walls and the whole room bends inside the cooling vessel.'
)],
},
{
id: 'performance',
label: 'Performance',
well: 'well-four',
concepts: [concept(
'performance-choreographic-field',
'a choreographic field, with bodies, counterweight, rhythm, and charged negative space',
'Bodies cross a dark stage as if joined by invisible tension; one gesture sends a delayed answer through the ensemble and makes the empty space between them feel alive.'
)],
},
{
id: 'dream',
label: 'Dream',
well: 'well-five',
concepts: [concept(
'dream-impossible-stair',
'an impossible stair, with folding gravity, recursive rooms, and reversible thresholds',
'A stair turns upward and arrives beneath itself; rooms fold through gravity while every crossed threshold quietly reverses what the visitor believed was inside and outside.'
)],
},
{ id: 'mapping', label: 'Mapping', concepts: [] },
],
}, null, 2));
await writeFile(path.join(scripts, 'concept-reviews.json'), JSON.stringify({ schemaVersion: 1, reviews: {} }, null, 2));
const catalog = JSON.parse(await readFile(path.join(scripts, 'concept-ingredients.json'), 'utf8'));
const conceptById = new Map(catalog.families.flatMap(family => family.concepts.map(entry => [entry.id, entry])));
const approvedReview = id => ({
status: 'approved',
reviewedBy: 'tester',
reviewedAt: '2026-07-18T00:00:00.000Z',
formHash: conceptContentHash(conceptById.get(id)),
});
await writeFile(path.join(scripts, 'concept-reviews.json'), JSON.stringify({
schemaVersion: 2,
reviews: {
'editorial-living-broadsheet': approvedReview('editorial-living-broadsheet'),
'mapping-celestial-instrument': approvedReview('mapping-celestial-instrument'),
'material-blown-glass': approvedReview('material-blown-glass'),
'performance-choreographic-field': approvedReview('performance-choreographic-field'),
},
}, null, 2));
return root;
}
@@ -74,6 +175,9 @@ describe('worlds review dev middleware', () => {
const reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].status, 'approved');
assert.equal(reviews.reviews['editorial-field-guide'].reviewedBy, 'pbakaus');
const writtenCatalog = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-ingredients.json'), 'utf8'));
const reviewedConcept = writtenCatalog.families.flatMap(family => family.concepts).find(entry => entry.id === 'editorial-field-guide');
assert.equal(reviews.reviews['editorial-field-guide'].formHash, conceptContentHash(reviewedConcept));
const pending = await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'pending' });
assert.equal(pending.status, 200);
@@ -81,7 +185,112 @@ describe('worlds review dev middleware', () => {
assert.equal(cleared.reviews['editorial-field-guide'], undefined);
});
it('updates concept content without changing its stable id', async () => {
it('stores an optional decision note and omits empty ones', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const noted = await request(handler, {
action: 'review',
id: 'editorial-field-guide',
status: 'rejected',
note: ' too abstract, no design culture behind it ',
});
assert.equal(noted.status, 200);
const reviewsPath = path.join(root, 'skill', 'scripts', 'concept-reviews.json');
let reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].note, 'too abstract, no design culture behind it');
const unnoted = await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'approved', note: ' ' });
assert.equal(unnoted.status, 200);
reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].note, undefined);
const tooLong = await request(handler, {
action: 'review',
id: 'editorial-field-guide',
status: 'rejected',
note: 'x'.repeat(501),
});
assert.equal(tooLong.status, 400);
assert.match(tooLong.body.error, /500 characters/);
});
it('refuses to approve a composition-typed world entry', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
await request(handler, { action: 'strength', id: 'editorial-field-guide', strength: 'composition' });
const approved = await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'approved' });
assert.equal(approved.status, 400);
assert.match(approved.body.error, /composition catalog/);
const rejected = await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'rejected', note: 'staging, mine it' });
assert.equal(rejected.status, 200);
});
it('rates approved concepts, clears with null, and survives a re-approve', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const reviewsPath = path.join(root, 'skill', 'scripts', 'concept-reviews.json');
const early = await request(handler, { action: 'rate', id: 'editorial-field-guide', rating: 3 });
assert.equal(early.status, 400);
assert.match(early.body.error, /only applies to approved/);
await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'approved' });
const rated = await request(handler, { action: 'rate', id: 'editorial-field-guide', rating: 3 });
assert.equal(rated.status, 200);
let reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].rating, 3);
// A re-approve (e.g. saving a note) must not drop the rating.
await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'approved', note: 'still great' });
reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].rating, 3);
const invalid = await request(handler, { action: 'rate', id: 'editorial-field-guide', rating: 5 });
assert.equal(invalid.status, 400);
assert.match(invalid.body.error, /1, 2, or 3/);
const cleared = await request(handler, { action: 'rate', id: 'editorial-field-guide', rating: null });
assert.equal(cleared.status, 200);
reviews = JSON.parse(await readFile(reviewsPath, 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'].rating, undefined);
});
it('updates concept content without changing its stable id and returns it to the backlog', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const approved = await request(handler, { action: 'review', id: 'editorial-field-guide', status: 'approved' });
assert.equal(approved.status, 200);
const response = await request(handler, {
action: 'update',
id: 'editorial-field-guide',
form: 'an expanded field guide, with specimen plates, branching keys, and index tabs',
familyId: 'mapping',
lineage: 'field reference publishing',
tags: ['specimen-plate', 'branching-key', 'index-tab'],
system: [
'Palette/material: cream plate stock, botanical greens, and one wine-red accent reserved for the confirmed match',
'Type/composition: italic latin binomials under compact caption labels, plates ranked on a fixed specimen grid',
'Topology/navigation: route by habitat and specimen family',
'Controls/state: carry observed, compared, and verified states',
'Responsive/motion: reflow from overview atlas to focused identification key',
],
spark: 'Pressed specimens unfold into a moonlit field of branching veins, shifting habitats, and magnified traits that make every identification choice visibly consequential.',
webLeverage: 'let readers zoom, compare, and filter live specimen observations without losing place',
});
assert.equal(response.status, 200);
const catalog = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-ingredients.json'), 'utf8'));
assert.equal(catalog.families[0].concepts.length, 1);
assert.equal(catalog.families[1].concepts[0].id, 'editorial-field-guide');
assert.match(catalog.families[1].concepts[0].form, /expanded field guide/);
assert.equal(catalog.families[1].concepts[0].system.length, 5);
assert.match(catalog.families[1].concepts[0].spark, /moonlit field/);
assert.match(catalog.families[1].concepts[0].webLeverage, /zoom/);
assert.equal(response.body.result.status, 'pending');
const reviews = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-reviews.json'), 'utf8'));
assert.equal(reviews.reviews['editorial-field-guide'], undefined);
});
it('rejects edits that erase system grammar, creative spark, or web leverage', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const response = await request(handler, {
@@ -91,12 +300,48 @@ describe('worlds review dev middleware', () => {
familyId: 'mapping',
lineage: 'field reference publishing',
tags: ['specimen-plate', 'branching-key', 'index-tab'],
system: [],
spark: '',
webLeverage: '',
});
assert.equal(response.status, 200);
assert.equal(response.status, 400);
assert.match(response.body.error, /system grammar/i);
const missingSpark = await request(handler, {
action: 'update',
id: 'editorial-field-guide',
form: 'an expanded field guide, with specimen plates, branching keys, and index tabs',
familyId: 'mapping',
lineage: 'field reference publishing',
tags: ['specimen-plate', 'branching-key', 'index-tab'],
system: [
'Palette/material: cream plate stock, botanical greens, and one wine-red accent reserved for the confirmed match',
'Type/composition: italic latin binomials under compact caption labels, plates ranked on a fixed specimen grid',
'Topology/navigation: route by habitat and specimen family',
'Controls/state: carry observed, compared, and verified states',
'Responsive/motion: reflow from overview atlas to focused identification key',
],
spark: '',
webLeverage: 'let readers zoom, compare, and filter live specimen observations without losing place',
});
assert.equal(missingSpark.status, 400);
assert.match(missingSpark.body.error, /creative spark/i);
});
it('retypes strength without invalidating the review', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const retyped = await request(handler, { action: 'strength', id: 'mapping-celestial-instrument', strength: 'composition' });
assert.equal(retyped.status, 200);
const catalog = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-ingredients.json'), 'utf8'));
assert.equal(catalog.families[0].concepts.length, 0);
assert.equal(catalog.families[1].concepts[0].id, 'editorial-field-guide');
assert.match(catalog.families[1].concepts[0].form, /expanded field guide/);
const entry = catalog.families.flatMap(family => family.concepts).find(item => item.id === 'mapping-celestial-instrument');
assert.equal(entry.strength, 'composition');
const reviews = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-reviews.json'), 'utf8'));
assert.equal(reviews.reviews['mapping-celestial-instrument'].status, 'approved');
const invalid = await request(handler, { action: 'strength', id: 'mapping-celestial-instrument', strength: 'vibe' });
assert.equal(invalid.status, 400);
assert.match(invalid.body.error, /world, composition, or dual/);
});
it('rejects cross-origin writes', async () => {
@@ -110,4 +355,44 @@ describe('worlds review dev middleware', () => {
assert.equal(response.status, 403);
assert.match(response.body.error, /Cross-origin/);
});
it('protects the approved-well floor on destructive review changes', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const response = await request(handler, {
action: 'review',
id: 'mapping-celestial-instrument',
status: 'rejected',
});
assert.equal(response.status, 400);
assert.match(response.body.error, /covering every challenger tier/i);
});
it('rejects a move that would empty a family or a literal software archetype', async () => {
const root = await fixtureRoot();
const handler = handlerFor(root);
const solo = JSON.parse(await readFile(path.join(root, 'skill', 'scripts', 'concept-ingredients.json'), 'utf8'))
.families.find(family => family.id === 'dream').concepts[0];
const emptyFamily = await request(handler, {
action: 'update',
...solo,
familyId: 'mapping',
});
assert.equal(emptyFamily.status, 400);
assert.match(emptyFamily.body.error, /has no concepts/i);
const bland = await request(handler, {
action: 'update',
id: 'editorial-field-guide',
form: 'a digital platform and management console, with ordinary cards and status rows',
familyId: 'editorial',
lineage: 'Generic enterprise product design conventions',
tags: ['cards', 'filters', 'status'],
system: FIVE_RULE_SYSTEM,
spark: 'A conventional application frame presents rows of tasks, colored status chips, and restrained panels with no distinctive material or spatial behavior of its own.',
webLeverage: 'Responsive filtering, live state, keyboard navigation, and deep-linked views',
});
assert.equal(bland.status, 400);
assert.match(bland.body.error, /literal software or operations archetype/i);
});
});
+7
View File
@@ -1,3 +1,10 @@
name = "impeccable"
compatibility_date = "2024-12-01"
pages_build_output_dir = "./build"
# World design-system cards (site/public/worlds/cards) live in R2, served by
# functions/worlds/cards/[[file]].js. Create once with:
# wrangler r2 bucket create impeccable-world-cards
[[r2_buckets]]
binding = "WORLD_CARDS"
bucket_name = "impeccable-world-cards"