From 5fb30e03e6ca0fa826c0b3eff0d1402fce871818 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Fri, 5 Jun 2026 13:58:14 -0700 Subject: [PATCH] Live picker: derive the command vocabulary from one canonical source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verbs/labels/icons were copied three ways: live-browser.js (ICONS + ACTIONS), VISUAL_ACTIONS in live-event-validation.mjs, and the marketing demo. Collapse them to one source, skill/scripts/live-vocabulary.mjs (LIVE_COMMANDS + derived VISUAL_ACTIONS). - live-event-validation.mjs imports VISUAL_ACTIONS from it. - live-server.mjs serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ when it serves /live.js, next to the token/port. live-browser.js (served raw, can't import at runtime) builds its ICONS + ACTIONS from that injected vocab instead of an inline copy — byte-identical icons, zero behaviour change. - site/components/LiveDemoPalette.astro imports the same module at build time, so the demo and the real picker can no longer drift. Adds a /live.js test asserting the injected vocab deep-equals the canonical list. Harness skill dirs refreshed via build. (Pre-existing, unrelated: `bun run build:site` fails on an htmlparser2 import in the CLI detector.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .pi/skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .pi/skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ .../skills/impeccable/scripts/live-browser.js | 45 +++++-------------- .../scripts/live-event-validation.mjs | 9 ++-- .../skills/impeccable/scripts/live-server.mjs | 4 ++ .../impeccable/scripts/live-vocabulary.mjs | 36 +++++++++++++++ site/components/LiveDemoPalette.astro | 31 ++++--------- skill/scripts/live-browser.js | 45 +++++-------------- skill/scripts/live-event-validation.mjs | 9 ++-- skill/scripts/live-server.mjs | 4 ++ skill/scripts/live-vocabulary.mjs | 36 +++++++++++++++ tests/live-server.test.mjs | 11 +++++ 58 files changed, 817 insertions(+), 541 deletions(-) create mode 100644 .agents/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .claude/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .cursor/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .gemini/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .github/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .kiro/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .opencode/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .pi/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .qoder/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .rovodev/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .trae-cn/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 .trae/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 plugin/skills/impeccable/scripts/live-vocabulary.mjs create mode 100644 skill/scripts/live-vocabulary.mjs diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.agents/skills/impeccable/scripts/live-event-validation.mjs b/.agents/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.agents/skills/impeccable/scripts/live-event-validation.mjs +++ b/.agents/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.agents/skills/impeccable/scripts/live-server.mjs b/.agents/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.agents/skills/impeccable/scripts/live-server.mjs +++ b/.agents/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.agents/skills/impeccable/scripts/live-vocabulary.mjs b/.agents/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.agents/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.claude/skills/impeccable/scripts/live-event-validation.mjs b/.claude/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.claude/skills/impeccable/scripts/live-event-validation.mjs +++ b/.claude/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.claude/skills/impeccable/scripts/live-server.mjs b/.claude/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.claude/skills/impeccable/scripts/live-server.mjs +++ b/.claude/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.claude/skills/impeccable/scripts/live-vocabulary.mjs b/.claude/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.claude/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.cursor/skills/impeccable/scripts/live-event-validation.mjs b/.cursor/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.cursor/skills/impeccable/scripts/live-event-validation.mjs +++ b/.cursor/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.cursor/skills/impeccable/scripts/live-server.mjs b/.cursor/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.cursor/skills/impeccable/scripts/live-server.mjs +++ b/.cursor/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.cursor/skills/impeccable/scripts/live-vocabulary.mjs b/.cursor/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.cursor/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.gemini/skills/impeccable/scripts/live-event-validation.mjs b/.gemini/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.gemini/skills/impeccable/scripts/live-event-validation.mjs +++ b/.gemini/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.gemini/skills/impeccable/scripts/live-server.mjs b/.gemini/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.gemini/skills/impeccable/scripts/live-server.mjs +++ b/.gemini/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.gemini/skills/impeccable/scripts/live-vocabulary.mjs b/.gemini/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.gemini/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.github/skills/impeccable/scripts/live-browser.js b/.github/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.github/skills/impeccable/scripts/live-browser.js +++ b/.github/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.github/skills/impeccable/scripts/live-event-validation.mjs b/.github/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.github/skills/impeccable/scripts/live-event-validation.mjs +++ b/.github/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.github/skills/impeccable/scripts/live-server.mjs b/.github/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.github/skills/impeccable/scripts/live-server.mjs +++ b/.github/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.github/skills/impeccable/scripts/live-vocabulary.mjs b/.github/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.github/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.kiro/skills/impeccable/scripts/live-event-validation.mjs b/.kiro/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.kiro/skills/impeccable/scripts/live-event-validation.mjs +++ b/.kiro/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.kiro/skills/impeccable/scripts/live-server.mjs b/.kiro/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.kiro/skills/impeccable/scripts/live-server.mjs +++ b/.kiro/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.kiro/skills/impeccable/scripts/live-vocabulary.mjs b/.kiro/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.kiro/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.opencode/skills/impeccable/scripts/live-event-validation.mjs b/.opencode/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.opencode/skills/impeccable/scripts/live-event-validation.mjs +++ b/.opencode/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.opencode/skills/impeccable/scripts/live-server.mjs b/.opencode/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.opencode/skills/impeccable/scripts/live-server.mjs +++ b/.opencode/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.opencode/skills/impeccable/scripts/live-vocabulary.mjs b/.opencode/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.opencode/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.pi/skills/impeccable/scripts/live-event-validation.mjs b/.pi/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.pi/skills/impeccable/scripts/live-event-validation.mjs +++ b/.pi/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.pi/skills/impeccable/scripts/live-server.mjs b/.pi/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.pi/skills/impeccable/scripts/live-server.mjs +++ b/.pi/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.pi/skills/impeccable/scripts/live-vocabulary.mjs b/.pi/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.pi/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.qoder/skills/impeccable/scripts/live-browser.js b/.qoder/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.qoder/skills/impeccable/scripts/live-browser.js +++ b/.qoder/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.qoder/skills/impeccable/scripts/live-event-validation.mjs b/.qoder/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.qoder/skills/impeccable/scripts/live-event-validation.mjs +++ b/.qoder/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.qoder/skills/impeccable/scripts/live-server.mjs b/.qoder/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.qoder/skills/impeccable/scripts/live-server.mjs +++ b/.qoder/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.qoder/skills/impeccable/scripts/live-vocabulary.mjs b/.qoder/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.qoder/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.rovodev/skills/impeccable/scripts/live-event-validation.mjs b/.rovodev/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.rovodev/skills/impeccable/scripts/live-event-validation.mjs +++ b/.rovodev/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.rovodev/skills/impeccable/scripts/live-server.mjs b/.rovodev/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.rovodev/skills/impeccable/scripts/live-server.mjs +++ b/.rovodev/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.rovodev/skills/impeccable/scripts/live-vocabulary.mjs b/.rovodev/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.rovodev/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.trae-cn/skills/impeccable/scripts/live-event-validation.mjs b/.trae-cn/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.trae-cn/skills/impeccable/scripts/live-event-validation.mjs +++ b/.trae-cn/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.trae-cn/skills/impeccable/scripts/live-server.mjs b/.trae-cn/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.trae-cn/skills/impeccable/scripts/live-server.mjs +++ b/.trae-cn/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.trae-cn/skills/impeccable/scripts/live-vocabulary.mjs b/.trae-cn/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.trae-cn/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/.trae/skills/impeccable/scripts/live-event-validation.mjs b/.trae/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/.trae/skills/impeccable/scripts/live-event-validation.mjs +++ b/.trae/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/.trae/skills/impeccable/scripts/live-server.mjs b/.trae/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/.trae/skills/impeccable/scripts/live-server.mjs +++ b/.trae/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/.trae/skills/impeccable/scripts/live-vocabulary.mjs b/.trae/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/.trae/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/plugin/skills/impeccable/scripts/live-browser.js b/plugin/skills/impeccable/scripts/live-browser.js index d7c580e2f..7bf6ef31c 100644 --- a/plugin/skills/impeccable/scripts/live-browser.js +++ b/plugin/skills/impeccable/scripts/live-browser.js @@ -81,39 +81,18 @@ 'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr', ]); - // SVG icons stack above each chip label. All strokes use currentColor so the - // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox, - // 1.5 stroke - visually consistent with the Foundation grid on the homepage. - const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; - const ICONS = { - impeccable: ``, - bolder: ``, - quieter: ``, - distill: ``, - polish: ``, - typeset: ``, - colorize: ``, - layout: ``, - adapt: ``, - animate: ``, - delight: ``, - overdrive: ``, - }; - - const ACTIONS = [ - { value: 'impeccable', label: 'Freeform' }, - { value: 'bolder', label: 'Bolder' }, - { value: 'quieter', label: 'Quieter' }, - { value: 'distill', label: 'Distill' }, - { value: 'polish', label: 'Polish' }, - { value: 'typeset', label: 'Typeset' }, - { value: 'colorize', label: 'Colorize' }, - { value: 'layout', label: 'Layout' }, - { value: 'adapt', label: 'Adapt' }, - { value: 'animate', label: 'Animate' }, - { value: 'delight', label: 'Delight' }, - { value: 'overdrive', label: 'Overdrive' }, - ]; + // Command vocabulary (values + labels + icons) comes from the canonical source, + // skill/scripts/live-vocabulary.mjs, which live-server.mjs serializes into + // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as + // the token/port above, so it is always present here). The icons stack above + // each chip label and recolor to C.brand when selected (strokes use + // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg. + const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : []; + const ICONS = {}; + const ACTIONS = VOCAB.map((c) => { + ICONS[c.value] = c.icon; + return { value: c.value, label: c.label }; + }); const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions']; const LIVE_UI_SURFACES = [ diff --git a/plugin/skills/impeccable/scripts/live-event-validation.mjs b/plugin/skills/impeccable/scripts/live-event-validation.mjs index 3046f612d..15ae3265d 100644 --- a/plugin/skills/impeccable/scripts/live-event-validation.mjs +++ b/plugin/skills/impeccable/scripts/live-event-validation.mjs @@ -5,10 +5,11 @@ import { canCreateInsert } from './live-insert-ui.mjs'; -export const VISUAL_ACTIONS = [ - 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset', - 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive', -]; +// The accepted visual action values come from the canonical vocabulary so the +// validator, the picker UI, and the marketing demo never drift. Imported (not +// just re-exported) so it is also in scope for the validators below. +import { VISUAL_ACTIONS } from './live-vocabulary.mjs'; +export { VISUAL_ACTIONS }; const ID_PATTERN = /^[0-9a-f]{8}$/; const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/; diff --git a/plugin/skills/impeccable/scripts/live-server.mjs b/plugin/skills/impeccable/scripts/live-server.mjs index cd1091b88..d9e06f449 100644 --- a/plugin/skills/impeccable/scripts/live-server.mjs +++ b/plugin/skills/impeccable/scripts/live-server.mjs @@ -24,6 +24,7 @@ import { parseDesignMd } from './design-parser.mjs'; import { resolveContextDir } from './context.mjs'; import { createLiveSessionStore } from './live-session-store.mjs'; import { validateEvent } from './live-event-validation.mjs'; +import { LIVE_COMMANDS } from './live-vocabulary.mjs'; import { getDesignSidecarPath, getLiveDir, @@ -1272,6 +1273,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) { const body = `window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` + `window.__IMPECCABLE_PORT__ = ${state.port};\n` + + // Canonical command vocabulary (values + labels + icons). live-browser.js + // builds its action picker from this instead of an inline copy. + `window.__IMPECCABLE_VOCAB__ = ${JSON.stringify(LIVE_COMMANDS)};\n` + sessionScript + '\n' + liveScript; res.writeHead(200, { diff --git a/plugin/skills/impeccable/scripts/live-vocabulary.mjs b/plugin/skills/impeccable/scripts/live-vocabulary.mjs new file mode 100644 index 000000000..1123d41c4 --- /dev/null +++ b/plugin/skills/impeccable/scripts/live-vocabulary.mjs @@ -0,0 +1,36 @@ +/** + * Canonical design-command vocabulary for Live Mode: each command's value, human + * label, and SVG icon. Icons stack above the chip label; strokes use currentColor + * so the icon recolors when its chip is selected. + * + * Single source of truth, consumed by: + * - skill/scripts/live-event-validation.mjs — re-exports VISUAL_ACTIONS. + * - skill/scripts/live-browser.js — the real picker. It is served raw and + * injected as an IIFE, so it cannot import this at runtime; live-server.mjs + * serializes LIVE_COMMANDS into window.__IMPECCABLE_VOCAB__ alongside the + * token/port, and live-browser.js builds its ICONS + ACTIONS from that. + * - site/components/LiveDemoPalette.astro — the marketing demo palette (imported + * at build time). + * + * Add, rename, or reorder a verb here and all three follow. + */ + +const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"'; + +export const LIVE_COMMANDS = [ + { value: 'impeccable', label: 'Freeform', icon: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +// Action values accepted by the live event protocol, in palette order. +export const VISUAL_ACTIONS = LIVE_COMMANDS.map((c) => c.value); diff --git a/site/components/LiveDemoPalette.astro b/site/components/LiveDemoPalette.astro index b551b2281..cb00374f9 100644 --- a/site/components/LiveDemoPalette.astro +++ b/site/components/LiveDemoPalette.astro @@ -1,27 +1,12 @@ --- -// Command-palette switcher for the marketing live-demo loop. Renders the -// design-command vocabulary as a 4-column icon grid; the demo loop in -// live-demo.js opens it and lands on the `pick` verb. +// Command-palette switcher for the marketing live-demo loop. Renders the design +// vocabulary as a 4-column icon grid; the demo loop in live-demo.js opens it and +// lands on the `pick` verb. // -// The values/labels/icons mirror the real picker's ICONS + ACTIONS in -// skill/scripts/live-browser.js (which is served raw and injected as an IIFE, so -// it can't import shared modules at runtime — see the chat thread for the -// proposed convergence). Keep this list in sync if a verb is added or reordered. -const A = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"'; -const COMMANDS = [ - { value: 'impeccable', label: 'Freeform', icon: `` }, - { value: 'bolder', label: 'Bolder', icon: `` }, - { value: 'quieter', label: 'Quieter', icon: `` }, - { value: 'distill', label: 'Distill', icon: `` }, - { value: 'polish', label: 'Polish', icon: `` }, - { value: 'typeset', label: 'Typeset', icon: `` }, - { value: 'colorize', label: 'Colorize', icon: `` }, - { value: 'layout', label: 'Layout', icon: `` }, - { value: 'adapt', label: 'Adapt', icon: `` }, - { value: 'animate', label: 'Animate', icon: `` }, - { value: 'delight', label: 'Delight', icon: `` }, - { value: 'overdrive', label: 'Overdrive', icon: `` }, -]; +// Values/labels/icons come from the same canonical source the real picker uses +// (skill/scripts/live-vocabulary.mjs), imported at build time, so the demo and +// the product never drift. +import { LIVE_COMMANDS } from '../../skill/scripts/live-vocabulary.mjs'; interface Props { pick: string; @@ -31,7 +16,7 @@ const { pick } = Astro.props;