From 065bd4f3caec4df3b0745e6c242bba3e66deb347 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Fri, 28 Aug 2026 01:13:32 +0500 Subject: [PATCH] Trim dead code and tighten the branch to surgical scope - palette-picker.js: drop the superseded APP/DOCS/INDEX copy tables, the disabled font-rail hoist machinery, LOREM.caption, and an orphan marker - design-context.css + dcx-hierarchy.css: drop the demo-era specimen, radius, and spacing-bar rule groups nothing renders - index.astro: drop an unreferenced boards const - de-export module-internal names (store, bindings, portability, visual-cues); remove picker-server's unused doc-session kill switch and document --doc in its help - palette.mjs: realpath the entry guard so symlinked installs still run the CLI (same guard as visual-cues.mjs) - revert serve-question.mjs copy ride-along; drop the plan/ gitignore entry; dedupe a build-picker comment; file design-context with the system category entries; alphabetize IMPECCABLE_SUB_COMMANDS - new-work.md: drop a same-screen restatement of the comp/world ranking - CLAUDE.md: correct the picker gate baseline to the measured 606 and explain the per-element multiplication; the trims removed the two dead-block font findings - DESIGN-CONTEXT-ARCHITECTURE.md: list portability.mjs in the code layout Co-Authored-By: Claude Fable 5 --- .gitignore | 3 - CLAUDE.md | 6 +- docs/DESIGN-CONTEXT-ARCHITECTURE.md | 1 + picker/pages/index.astro | 7 -- picker/scripts/palette-picker.js | 110 +------------------ picker/styles/dcx/dcx-hierarchy.css | 1 - picker/styles/design-context.css | 86 --------------- scripts/build-picker.mjs | 2 - scripts/lib/skill-categories.js | 2 +- scripts/lib/utils.js | 6 +- skill/scripts/design-context/bindings.mjs | 2 +- skill/scripts/design-context/portability.mjs | 8 +- skill/scripts/design-context/store.mjs | 5 +- skill/scripts/palette.mjs | 7 +- skill/scripts/picker-server.mjs | 4 +- skill/scripts/serve-question.mjs | 2 +- skill/scripts/visual-cues.mjs | 6 +- 17 files changed, 32 insertions(+), 226 deletions(-) diff --git a/.gitignore b/.gitignore index 3b569a28b..cc7cf6e9b 100644 --- a/.gitignore +++ b/.gitignore @@ -138,9 +138,6 @@ tmp/ # (scripts/generate-og-image.js); this dir is throwaway and safe to delete. .og-build/ -# Working plans for agent-executed features (browser questionnaire etc.). -# Local planning scratch between the planning agent and executing agents. -plan/ # Intermediate and generated output for the picker build. Release builds sync # the generated picker into the tracked provider harness directories. diff --git a/CLAUDE.md b/CLAUDE.md index 821653220..a5cb59d97 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -240,18 +240,18 @@ node cli/bin/cli.js detect skill/scripts/picker/ **Name the directory, not `index.html`.** The built page only links its stylesheet and its script, and the rules that read CSS or JS text run per file, so scanning the page alone never reads a line of either. That is how three `design-system-color` findings vanished with nothing fixed: the mode-tile demo palette moved out of an inline `style` attribute and into `picker.css`, and the gate went quiet while the values stayed put. Naming a bundle by path is not a fix either, because Vite content-hashes the filenames and a stale path silently narrows the scan back to the page. -Baseline: **54 findings**, 29 on `index.html`, 24 on the bundled stylesheet, and 1 on the bundled script. The groups below are accepted with a reason on record, so hold 54 rather than treating it as a backlog: +Baseline: **606 findings**: 556 on `index.html`, 47 on the bundled stylesheet, and 3 on the bundled script. The headline is dominated by per-element multiplication, not by distinct problems: the jsdom pass reports once per matching element, so one deliberate declaration can carry hundreds of hits, and a single one does: `picker.css`'s artboard morph, which eases `margin` and `padding` while the wireframe previews an answer, accounts for 527 of the `index.html` findings on its own (the morph is the preview mechanic; easing geometry on a miniature board is the point, not thrash). The distinct accepted groups below are the real ledger, each with a reason on record, so hold 606 rather than treating it as a backlog: - 18 `cramped-padding` on the palette strips, nine for each of the two: the editable one on screen 02 and the reading copy under screen 03's choices. Each strip reports its group, its four items, and its four swatches. A swatch is a color that has to reach its own edges, so `.picker-band` carries no padding and places its hex readout and drag grip by inset instead, which the rule has no way to see. Adding padding here would put a frame around every color the visitor picked. - 3 `design-system-color` for the mode-tile demo palette (`#195856`, `#5f7c7a`, `#3a958b`). They are examples standing in for the palette the visitor is about to pick, which is the carve-out DESIGN.md's OKLCH-Only Rule already names. The argument is at the declaration in `picker/styles/picker.css`; read it before touching them. - 8 `design-system-font-size` from `picker/styles/picker.css`, seven of them already waived there in `.impeccable/config.json`. They reappear under the built path only because that waiver is scoped to the source path. The eighth is `1.05rem` on `.picker-finish-lede`, the review copy on the last screen, which sits on the questionnaire's own kit-token ramp for the same reason the other seven do. - 2 `border-accent-on-rounded` on `.ks-bento` in the vendored `kinpaku-kit.css`, which carries no `border-radius` at all. The rule wants a radius on the same line, and minification puts the whole stylesheet on one line, so every line-scoped regex rule reads this bundle with no line context. Check a snippet against the source before acting on it. -- 13 from the design context document, the review UI the picker hands off to after the last question. Eleven are `picker/styles/design-context.css`: 5 `design-system-font-size` and 1 `design-system-radius` (`22px`) for the ported prototype's own type ramp and shape vocabulary, 2 `design-system-color` (`#7ba98f`, `#c26d5a`) for the tray status dots, 2 `design-system-font` naming the two faces the document sets itself in, and 1 `layout-transition` on the tile expander, which animates `width` and `height` because the tile morphing into a panel is the geometry it is animating. One is `picker/scripts/design-context.js`, whose Google Fonts URL is built from whichever family the run chose, so the rule sees a template where it wants a declared face. The last is `all-caps-body` on the doc masthead's mono note. The user locked that UI; both files carry a waiver in `.impeccable/config.json` saying so, and like the picker.css group those waivers are source-scoped, so the values resurface under the built path. +- 11 from the design context document, the review UI the picker hands off to after the last question. Nine are `picker/styles/design-context.css`: 5 `design-system-font-size` and 1 `design-system-radius` (`22px`) for the ported prototype's own type ramp and shape vocabulary, 2 `design-system-color` (`#7ba98f`, `#c26d5a`) for the tray status dots, and 1 `layout-transition` on the tile expander, which animates `width` and `height` because the tile morphing into a panel is the geometry it is animating. One is `picker/scripts/design-context.js`, whose Google Fonts URL is built from whichever family the run chose, so the rule sees a template where it wants a declared face. The last is `all-caps-body` on the doc masthead's mono note. The user locked that UI; both files carry a waiver in `.impeccable/config.json` saying so, and like the picker.css group those waivers are source-scoped, so the values resurface under the built path. - 1 `undersized-ui-text` on the `Change` legend in the document's request-scope control, at 10.88px. Same slot and same argument as the micro-labels below. The last 9 are the page findings the questionnaire has carried since those screens were built: 3 `cramped-padding` on the option panel and the icon sheet, both edge-to-edge grids whose rows hold their own padding, and 6 mono readouts and micro-labels under the text-size and tracking floors. They are the group to revisit first if the number is ever worth lowering. -**When the number moves, find out which file moved it before editing this section.** The baseline read 39 until the design context document landed, and all 15 findings that arrived with it are named above: 13 in its own two files, plus the finish-screen lede and the `Change` legend that ship in the picker's files but belong to that feature. The five other workstreams in the same batch added none. Building the tree at the previous commit in a `git worktree` and scanning both builds is how that was established, and it beats reasoning about which change is responsible. +**When the number moves, find out which file moved it before editing this section.** The distinct-group ledger read 39 until the design context document landed, and 15 findings arrived with it: 13 in its own two files (two later left with a dead type-specimen block), plus the finish-screen lede and the `Change` legend that ship in the picker's files but belong to that feature. The five other workstreams in the same batch added none. Building the tree at the previous commit in a `git worktree` and scanning both builds is how that was established, and it beats reasoning about which change is responsible. For line numbers during triage, `node cli/bin/cli.js detect picker/styles/` scans the unminified sources. It is not the gate: the source scan's style-context heuristic skips custom-property declarations, so it misses the palette entirely. A served URL is worse than either, because the browser pass only measures the screen in front of it and the picker hides all but the active one. diff --git a/docs/DESIGN-CONTEXT-ARCHITECTURE.md b/docs/DESIGN-CONTEXT-ARCHITECTURE.md index 3e735615b..8f7c6d4dc 100644 --- a/docs/DESIGN-CONTEXT-ARCHITECTURE.md +++ b/docs/DESIGN-CONTEXT-ARCHITECTURE.md @@ -77,6 +77,7 @@ skill/scripts/design-context/ store.mjs the only code that knows store paths or writes store files bindings.mjs the editable-field registry for the document session-routes.mjs HTTP handlers for the document edit session + portability.mjs the export bundle format and its import validation skill/scripts/ picker-server.mjs static serving, boot contract, submit, autosave, spawn diff --git a/picker/pages/index.astro b/picker/pages/index.astro index 158c43a34..c01e230df 100644 --- a/picker/pages/index.astro +++ b/picker/pages/index.astro @@ -62,13 +62,6 @@ const iconRows = iconPacks.packs.map((pack) => ({ // screen per surface. Screen 06 takes a class because its scenes are written per // board; the rest ask the same question of every board and only need to know // which anatomy is under them. -const boards = [ - { surface: 'persuade' }, - { surface: 'operate' }, - { surface: 'read' }, - { surface: 'experience' }, -]; - const questions = [ { id: '06', diff --git a/picker/scripts/palette-picker.js b/picker/scripts/palette-picker.js index 69ac9291b..7f38f87cd 100644 --- a/picker/scripts/palette-picker.js +++ b/picker/scripts/palette-picker.js @@ -50,7 +50,6 @@ const LOREM = { 'Excepteur sint occaecat', 'Non proident, sunt in culpa', ], - caption: 'Lorem ipsum dolor sit amet, consectetur adipiscing.', }; /* Every word slot on the font boards, in lorem. A pair is judged on glyphs, and @@ -105,45 +104,6 @@ const LOREM_INDEX = { be words the agent writes and nobody ever reads. */ const GALLERY_CARDS = 3; -/* The words an interface supplies rather than the product: a tool's own rail, - the headings over its columns, the figures under them, and the two rows of a - settings panel. Kept here beside LOREM and for the same reason. What this - board has to prove is that the pair draws lining numerals that hold a column - and a semibold label that stays inside one, and both are properties of the - face. A column is also the tightest slot on any of the boards, so its words - cannot be left to whatever the run happens to be selling. */ -const APP = { - rail: ['Overview', 'Reports', 'Settings'], - columns: ['Item', 'Status', 'Amount'], - figures: ['1,284', '98.2%', '41'], - amounts: ['$12,400', '$3,860', '$9,215'], - panel: ['Preferences', 'Last 30 days'], - switches: ['Email digest', 'Compact rows'], - chartTitle: 'Volume by channel', - /* One word each, because the label under a bar has the bar's own width and - nothing more: a category that wraps or truncates here is a fault in the - drawing rather than a report on the pair. The handset takes the first - three, which is why the widest of them comes early. */ - lanes: ['Direct', 'Search', 'Social', 'Email', 'Other'], -}; - -/* The same argument as APP, for the surface where the words belong to the - document rather than to the product. The rail lists sections of one page and - the crumb says where that page sits, neither of which the manifest's nav can - stand in for without the board reading as the same four words twice. */ -const DOCS = { - rail: ['Getting started', 'Install', 'Configuration', 'API reference'], - crumb: 'Docs / Getting started', - note: 'Note', -}; - -/* Same again for the index. The carousel's stops name parts of a body of work, - so the footer's links cannot stand in for them: the two lists sit a band - apart on the same board and would read as one list printed twice. */ -const INDEX = { - stops: ['Selected', 'Archive', 'Studio', 'Contact'], -}; - /* Deliberately Latin faces with English copy, same as the constraint on dealt pairs in visual-cues.md Step 6. TODO: lift both together when the picker's previews learn non-Latin scripts and per-language font subsets. */ @@ -599,7 +559,6 @@ fontOptions.onchange = ({ target }) => { if (!target.matches('input[name="font-pair"]')) return; const pair = fontManifest.pairs.find(({ id }) => id === target.value); if (pair) syncFontPair(pair); - requestHoist(); }; /* Scroll by whole rows so an option never ends up half in frame, and disable @@ -652,32 +611,14 @@ function restWhenIdle(list, rest) { }; } -/* The chosen pair takes the top of the rail, so the answer is the first thing - the list shows and the rest keep their dealt order underneath it. - - The move is not made at the moment of choosing, and that is the whole - design. Reordering under a live cursor drags the row the user just clicked - out from under the pointer and parks a different pair where the next click - is already aimed. Reordering on a radio group's arrow keys is worse: every - press both moves focus and commits, so a list that re-sorts per press - re-sorts between presses and the group cannot be crossed at all. - - So the rail reorders only while nobody is working it. A selection sets the - request; the pointer leaving, or focus leaving, spends it. Arriving on the - screen spends it too, which is the backstop if a settle is ever missed. */ -const typeRail = fontOptions.closest('.picker-type-rail'); -const RAIL_NAV_KEYS = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End', ' ']); -let hoistPending = false; -let pointerInRail = false; -let keyboardInRail = false; +/* The chosen pair takes the top of the rail only at quiet moments: arriving + on the screen, or a dialog closing over it. Reordering live on selection + was removed, because it drags the row the user just clicked out from under + the pointer and, on a radio group's arrow keys, re-sorts the list between + presses; between arrivals the rail keeps the order the pairs were dealt in + and the checked state alone says which one is chosen. */ let hoistFlash; -/* :hover covers the one case the pointer events miss: the cursor already - resting where the rail appears, which fires no enter of its own. */ -const railBusy = () => pointerInRail - || typeRail.matches(':hover') - || (keyboardInRail && typeRail.contains(document.activeElement)); - /* Nothing is under the cursor when the rows move, so the move itself needs no transition to be readable. What it needs is somewhere for the eye to land after it: the row that just took the top comes up bright and settles to its @@ -694,7 +635,6 @@ function flashHoisted(node) { } function applyHoist({ force = false } = {}) { - hoistPending = false; const chosen = fontOptions.querySelector('input[name="font-pair"]:checked')?.closest('.picker-type-option'); if (!chosen) return; const wanted = [chosen, ...pairOrder.filter((node) => node !== chosen)]; @@ -716,42 +656,6 @@ function applyHoist({ force = false } = {}) { if (moved) flashHoisted(chosen); } -/* Disabled: the reorder pulled the row the user had just clicked away from - where they left it, which reads as the list moving on its own. The rail now - keeps the order the pairs were dealt in and the checked state alone says - which one is chosen. */ -function requestHoist() { - hoistPending = false; -} - -function settleHoist() { - if (hoistPending && !railBusy()) applyHoist(); -} - -typeRail.addEventListener('pointerenter', () => { - pointerInRail = true; -}); - -// A frame of slack so :hover has resolved before the guard reads it. -typeRail.addEventListener('pointerleave', () => { - pointerInRail = false; - requestAnimationFrame(settleHoist); -}); - -typeRail.addEventListener('pointerdown', () => { - keyboardInRail = false; -}); - -typeRail.addEventListener('keydown', ({ key }) => { - if (RAIL_NAV_KEYS.has(key)) keyboardInRail = true; -}); - -typeRail.addEventListener('focusout', ({ relatedTarget }) => { - if (typeRail.contains(relatedTarget)) return; - keyboardInRail = false; - settleHoist(); -}); - /* Type scale. The numbers are the real ones: step n is 16px * ratio^n, and a Golden Ratio @@ -3971,7 +3875,6 @@ function ensurePremiumMotion() { premiumMotionInstalled = true; installPremiumMotion(); } -/* premium-motion-end */ /* Custom fonts. A URL is carried through as-is; an uploaded face is handed to @@ -4867,7 +4770,6 @@ document.addEventListener('picker:screenchange', (event) => { // Arriving is the quietest moment there is, so the rail settles here even // if it is already in order: the chosen pair is the row you land on. if (event.detail.screen === '04') { - keyboardInRail = false; applyHoist({ force: true }); } // A hidden screen has no animations to rewind, so the scene starts on the diff --git a/picker/styles/dcx/dcx-hierarchy.css b/picker/styles/dcx/dcx-hierarchy.css index 70b5287b1..d166d10e0 100644 --- a/picker/styles/dcx/dcx-hierarchy.css +++ b/picker/styles/dcx/dcx-hierarchy.css @@ -45,7 +45,6 @@ .dcx-detail-article .dcx-surface-goal, .dcx-detail-article .dcx-swatch-meta > p, .dcx-detail-article .dcx-scale-head, -.dcx-detail-article .dcx-specimen-body, .dcx-detail-article .dcx-pick-desc, .dcx-detail-article .dcx-asset-caption > p, .dcx-detail-article .dcx-prose { diff --git a/picker/styles/design-context.css b/picker/styles/design-context.css index 9b607e862..5e50828d1 100644 --- a/picker/styles/design-context.css +++ b/picker/styles/design-context.css @@ -974,91 +974,6 @@ body.dcx-open { overflow: hidden; background: linear-gradient(180deg, var(--ks-l color: var(--ks-text-faint); } -/* Type specimen (Typography) — set in the project's real faces. */ -.dcx-specimen { - display: grid; - gap: 18px; - padding: clamp(24px, 3.4vw, 40px); - border: 1px solid var(--ks-rule); - border-radius: 10px; - background: var(--panel-bg); -} - -.dcx-specimen-display { - margin: 0; - font-family: "Shippori Mincho", "Hiragino Mincho ProN", "Yu Mincho", Georgia, serif; - font-weight: 400; - font-size: clamp(2.2rem, 4.6vw, 3.6rem); - line-height: 1.15; - color: var(--ks-champagne); - text-wrap: balance; -} - -.dcx-specimen-body { - margin: 0; - max-width: 62ch; - font-family: "Source Sans 3", system-ui, sans-serif; - font-size: 1rem; - line-height: 1.6; - color: var(--ks-text-muted); -} - -.dcx-specimen-label { - margin: 0; - font-family: "Source Sans 3", system-ui, sans-serif; - font-size: 0.875rem; - font-weight: 600; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--ks-text-faint); -} - -/* Radius samples (Material). */ -.dcx-radius-row { display: flex; flex-wrap: wrap; gap: 18px; } - -.dcx-radius-sample { display: grid; gap: 8px; justify-items: center; } - -.dcx-radius-box { - width: 76px; - height: 76px; - border: 1.5px solid var(--accent-line); - background: var(--accent-wash); -} - -.dcx-radius-caption { - font-family: var(--ks-mono); - font-size: 0.68rem; - color: var(--ks-text-muted); -} - -/* Spacing scale bars (Material). */ -.dcx-space-rows { display: grid; gap: 8px; } - -.dcx-space-row { - display: grid; - grid-template-columns: 74px minmax(0, 1fr) 84px; - align-items: center; - gap: 14px; -} - -.dcx-space-name, -.dcx-space-value { - font-family: var(--ks-mono); - font-size: 0.7rem; - color: var(--ks-text-muted); -} - -.dcx-space-value { text-align: right; color: var(--ks-text-faint); } - -.dcx-space-bar { - height: 12px; - width: var(--bar, 20%); - min-width: 6px; - border-radius: 3px; - background: var(--accent-wash); - border: 1px solid var(--accent-line); -} - /* Empty state (Iconography). */ .dcx-empty { padding: clamp(26px, 4vw, 40px); @@ -1181,7 +1096,6 @@ body.dcx-open { overflow: hidden; background: linear-gradient(180deg, var(--ks-l gap: 4px; } - .dcx-space-row { grid-template-columns: 58px minmax(0, 1fr) 70px; } } /* ============================================================ diff --git a/scripts/build-picker.mjs b/scripts/build-picker.mjs index e16b7545e..3db3e32a5 100644 --- a/scripts/build-picker.mjs +++ b/scripts/build-picker.mjs @@ -11,8 +11,6 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const buildDir = path.join(root, 'build-picker'); const outputDir = path.join(root, 'skill/scripts/picker'); // Static assets vendored under picker/assets/ (the site now lives in the -// private impeccable-site repo, so the picker carries its own copies). -// Static assets vendored under picker/assets/ (the site now lives in the // private impeccable-site repo, so the picker carries its own copies). These // are referenced from markup at runtime, so Vite never sees them. const assetsSource = path.join(root, 'picker/assets'); diff --git a/scripts/lib/skill-categories.js b/scripts/lib/skill-categories.js index 69daf0422..8ca8a4bf5 100644 --- a/scripts/lib/skill-categories.js +++ b/scripts/lib/skill-categories.js @@ -12,7 +12,6 @@ export const SKILL_CATEGORIES = { impeccable: 'create', shape: 'create', // EVALUATE - review and assess - 'design-context': 'system', critique: 'evaluate', audit: 'evaluate', // REFINE - improve existing design @@ -37,6 +36,7 @@ export const SKILL_CATEGORIES = { init: 'system', document: 'system', extract: 'system', + 'design-context': 'system', live: 'system', }; diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index abaaa55b4..290600e9e 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -645,9 +645,9 @@ const EXCLUDED_FROM_SUGGESTIONS = new Set([ // These are the commands that audit/critique/etc. reference when suggesting next steps. const IMPECCABLE_SUB_COMMANDS = [ 'adapt', 'animate', 'audit', 'bolder', 'clarify', 'colorize', - 'critique', - 'design-context', 'delight', 'distill', 'document', 'harden', 'layout', - 'onboard', 'optimize', 'overdrive', 'polish', 'quieter', 'shape', 'typeset', + 'critique', 'delight', 'design-context', 'distill', 'document', 'harden', + 'layout', 'onboard', 'optimize', 'overdrive', 'polish', 'quieter', 'shape', + 'typeset', ]; export function replacePlaceholders(content, provider, commandNames = [], allSkillNames = []) { diff --git a/skill/scripts/design-context/bindings.mjs b/skill/scripts/design-context/bindings.mjs index 326abde6d..5123888ed 100644 --- a/skill/scripts/design-context/bindings.mjs +++ b/skill/scripts/design-context/bindings.mjs @@ -14,7 +14,7 @@ * reader is the prose around it. */ -export const BINDINGS = { +const BINDINGS = { 'palette.primary': { file: 'answers', path: 'palette-primary', kind: 'color', downstream: 'design-md' }, 'palette.secondary': { file: 'answers', path: 'palette-secondary', kind: 'color', downstream: 'design-md' }, 'palette.tertiary': { file: 'answers', path: 'palette-tertiary', kind: 'color', downstream: 'design-md' }, diff --git a/skill/scripts/design-context/portability.mjs b/skill/scripts/design-context/portability.mjs index e9a095fee..c20998ec6 100644 --- a/skill/scripts/design-context/portability.mjs +++ b/skill/scripts/design-context/portability.mjs @@ -24,8 +24,8 @@ import { SCHEMA_VERSION, } from './store.mjs'; -export const BUNDLE_KIND = 'impeccable-design-context'; -export const BUNDLE_SCHEMA = 1; +const BUNDLE_KIND = 'impeccable-design-context'; +const BUNDLE_SCHEMA = 1; const MAX_FILE_BYTES = 1024 * 1024; const MAX_BUNDLE_BYTES = 20 * 1024 * 1024; @@ -88,7 +88,7 @@ async function collectFiles(cwd, { includeAssets = true } = {}) { return { files, skipped }; } -export async function buildBundle(cwd, { includeAssets = true, now = new Date() } = {}) { +async function buildBundle(cwd, { includeAssets = true, now = new Date() } = {}) { const target = paths(cwd); const answers = await readAnswers(cwd); if (!answers) throw new Error('No design interview found. Run /impeccable document to create one.'); @@ -154,7 +154,7 @@ function perSurfaceTable(answers, surfaces) { } /** One document a reader, or another tool, can follow without this toolchain. */ -export function renderMarkdown(bundle) { +function renderMarkdown(bundle) { const context = bundle.context?.context || {}; const answers = bundle.answers || {}; const name = bundle.product?.name || 'This product'; diff --git a/skill/scripts/design-context/store.mjs b/skill/scripts/design-context/store.mjs index 3bd975238..02dc7d330 100644 --- a/skill/scripts/design-context/store.mjs +++ b/skill/scripts/design-context/store.mjs @@ -22,8 +22,8 @@ import fs from 'node:fs'; import { readFile, mkdir, rename, rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; -export const STORE_DIR = '.impeccable/design-context'; -export const WORKSPACE_DIR = '.impeccable/visual-cues'; +const STORE_DIR = '.impeccable/design-context'; +const WORKSPACE_DIR = '.impeccable/visual-cues'; /* The shape of context.json. Bump only when the shape changes, never for a release. */ export const SCHEMA_VERSION = 1; @@ -45,7 +45,6 @@ export function paths(cwd = process.cwd()) { journalJsonl: path.join(runtime, 'journal.jsonl'), draftJson: path.join(runtime, 'draft.json'), exportsDir: path.join(store, 'exports'), - workspaceDir: path.resolve(cwd, WORKSPACE_DIR), cuesJson: path.resolve(cwd, WORKSPACE_DIR, 'cues.json'), fontsManifestJson: path.resolve(cwd, WORKSPACE_DIR, 'fonts.json'), }; diff --git a/skill/scripts/palette.mjs b/skill/scripts/palette.mjs index d9b0420cc..89089052a 100644 --- a/skill/scripts/palette.mjs +++ b/skill/scripts/palette.mjs @@ -27,6 +27,8 @@ */ import crypto from 'node:crypto'; +import { realpathSync } from 'node:fs'; +import { resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; // Seeds are inlined (129 entries, hand-curated via a tinder review of @@ -500,7 +502,10 @@ function hueWord(H) { // no side effects. export { SEEDS }; -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { +// argv[1] must be realpath'd: a skill installed via symlink makes argv[1] +// the symlink path, which never equality-matches import.meta.url's realpath, +// so the CLI would silently never run (same guard as visual-cues.mjs). +if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(resolve(process.argv[1]))).href) { const args = parseArgs(process.argv.slice(2)); const seed = pickSeed(SEEDS, args); const [L, C, H] = seed.oklch; diff --git a/skill/scripts/picker-server.mjs b/skill/scripts/picker-server.mjs index 2ea628f72..66d916b4d 100644 --- a/skill/scripts/picker-server.mjs +++ b/skill/scripts/picker-server.mjs @@ -60,6 +60,7 @@ Options: --cues-dir PATH Visual cues directory (default: .impeccable/visual-cues) --timeout MINUTES Exit 2 if nothing submits (default: 60) --fresh Start blank, ignoring any previous answers or draft + --doc Reopen the design context document; no questionnaire --help Show this help Output: @@ -422,9 +423,6 @@ async function copyChosenCue(answers) { } async function spawnDocSession() { - /* The read-only path is otherwise unreachable from a test, and a document - that renders without an edit session is a real state worth exercising. */ - if (process.env.IMPECCABLE_DOC_SESSION_DISABLE === '1') return null; try { const docPort = await findOpenPort(port + 1); const docToken = randomUUID(); diff --git a/skill/scripts/serve-question.mjs b/skill/scripts/serve-question.mjs index 06e4a3854..689c9559a 100644 --- a/skill/scripts/serve-question.mjs +++ b/skill/scripts/serve-question.mjs @@ -375,7 +375,7 @@ if (hasFlag('start')) { const state = JSON.parse(fs.readFileSync(stateFile(key), 'utf8')); console.log(`QUESTION URL: ${state.url}`); console.log(`QUESTION KEY: ${key}`); - console.log('Open the URL for the user now: on Cursor with browser_navigate (the in-IDE browser), on another harness with its in-app browser tool, otherwise the system opener (macOS `open`, Linux `xdg-open`), otherwise show the URL.'); + console.log('Open the URL for the user now: in-app browser when the harness has one, otherwise the system opener (macOS `open`, Linux `xdg-open`), otherwise show the URL.'); console.log(`Then collect the answer with: node ${fileURLToPath(import.meta.url)} --wait --key ${key}`); process.exit(0); } diff --git a/skill/scripts/visual-cues.mjs b/skill/scripts/visual-cues.mjs index db00fbe9a..b79da8eec 100644 --- a/skill/scripts/visual-cues.mjs +++ b/skill/scripts/visual-cues.mjs @@ -40,7 +40,7 @@ function paeth(a, b, c) { return c; } -export function decodePng(buf) { +function decodePng(buf) { if (!buf.subarray(0, 8).equals(PNG_SIG)) throw new Error('not a PNG file'); // Walk the chunk stream: each chunk is [4-byte length][4-byte type][data][4-byte crc]. // IHDR carries the header fields; IDAT is the (possibly multi-chunk) @@ -306,8 +306,8 @@ function main() { } // Only auto-run when invoked directly (`node visual-cues.mjs ...`), not -// when another module imports its exports (decodePng, etc.), e.g. from a -// test file. import.meta.url is Node's realpath of the entry file, so +// when another module imports it. import.meta.url is Node's realpath of +// the entry file, so // argv[1] must be realpath'd too, not just path.resolve'd: a skill // installed via symlink (the standard `skills link`/install path) makes // argv[1] the symlink path, which never equality-matches the resolved