Files
whynot-design/designbook/_ds_bundle.js
tegwick 0d688ca94a feat(designbook): technology-neutral IR + stack-adapter pipeline (WHYNOT-WP-0002 T01-T06)
Author the design language once in the canonical React designbook and project it
one-way onto each stack: React -> designbook/ -> ir/ -> adapters/<stack>/.

Phase 0 — contracts & governance (T01-T03):
- ir/SCHEMA.md + ir/schema/{component,tokens}.schema.json — neutral IR contract
  (W3C DTCG tokens; React prop -> HTML attribute mapping; non-portable props flagged).
- adapters/ADAPTER_CONTRACT.md — inputs, drift-report + parity-result shapes,
  idempotency rules, CI exit codes (0 ok / 2 usage / 3 drift / 4 parity / 5 internal).
- .claude/rules/designbook-propagation.md + DesignSystemIntroduction.md §5.1 —
  one-way directionality + drift-resolution workflow.

T04 — canonical React designbook + the missing pull tool:
- The bundled /design-sync skill only PUSHES repo->cloud; it cannot populate
  designbook/. Added scripts/designbook_pull.py + `make designbook-pull`, which drives
  the local claude binary headless (acceptEdits) so DesignSync fetch+write runs in a
  subprocess (contents never hit the orchestrator's context). Pulled 44 files;
  excludes the _whynot-design-seed/ self-copy. Corrected the docs that wrongly called
  /design-sync the pull.

T05 — IR extractor (scripts/ir-extract.mjs + `make ir`):
- ir/tokens.json (80 tokens, DTCG, var() -> {ref} alias resolution); ir/components/*.json
  (10 contracts parsed from .jsx signatures: enum/boolean/number inference, prop->attr
  map, style/callback marked non-portable); ir/exemplars/*.

T06 — Lit token adapter (adapters/lit/ + `make adapt-lit`):
- Full-gen tokens into src/styles/colors_and_type.css :root (marker-bounded, idempotent
  no-op on re-run; hand-authored type CSS preserved).

NOTE: token regen synced Lit to canonical React — fonts IBM Plex -> system stacks and 8
status tokens added. This is a VISUAL change: review and run `pnpm test:visual:update`
before merge. Remaining: T07 scaffold+drift, T08 parity, T09 runbook, T10 2nd-adapter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 12:36:24 +02:00

1397 lines
41 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* @ds-bundle: {"format":3,"namespace":"WhyNotDesignSystem_fb2eef","components":[],"sourceHashes":{"ui_kits/whynot-control/Atoms.jsx":"79a33f57ef80","ui_kits/whynot-control/Chrome.jsx":"31e0ba69d22d","ui_kits/whynot-control/DocView.jsx":"636cbc6e3422","ui_kits/whynot-control/Screens.jsx":"c21f0206c6c7","ui_kits/whynot-control/data.jsx":"16b3caf0bced"},"inlinedExternals":[],"unexposedExports":[]} */
(() => {
const __ds_ns = (window.WhyNotDesignSystem_fb2eef = window.WhyNotDesignSystem_fb2eef || {});
const __ds_scope = {};
(__ds_ns.__errors = __ds_ns.__errors || []);
// ui_kits/whynot-control/Atoms.jsx
try { (() => {
// =============================================================
// Atoms — Eyebrow, Tag, Button, StageDot, Stamp, IconBtn
// =============================================================
function Eyebrow({
children,
style
}) {
return /*#__PURE__*/React.createElement("span", {
style: {
font: '500 11px/1.2 var(--ff-mono)',
letterSpacing: '0.08em',
textTransform: 'uppercase',
color: 'var(--fg-3)',
...style
}
}, children);
}
function Tag({
children,
active,
draft,
style
}) {
const base = {
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.1em',
textTransform: 'uppercase',
padding: '5px 10px',
borderRadius: 'var(--r-pill)',
border: '1px solid var(--border)',
color: 'var(--fg-2)',
background: 'var(--paper)',
display: 'inline-block'
};
if (active) Object.assign(base, {
background: 'var(--ink)',
color: 'var(--paper)',
borderColor: 'var(--ink)'
});
if (draft) Object.assign(base, {
background: 'var(--hi)',
color: 'var(--hi-ink)',
borderColor: 'transparent'
});
return /*#__PURE__*/React.createElement("span", {
style: {
...base,
...style
}
}, children);
}
function Button({
children,
variant = 'secondary',
onClick,
style,
icon
}) {
const base = {
font: '500 13px var(--ff-sans)',
letterSpacing: '-0.005em',
padding: '9px 14px',
borderRadius: 'var(--r-2)',
border: '1px solid var(--border)',
background: 'var(--paper)',
color: 'var(--ink)',
cursor: 'pointer',
display: 'inline-flex',
alignItems: 'center',
gap: 8,
whiteSpace: 'nowrap',
transition: 'background 120ms ease, border-color 120ms ease'
};
if (variant === 'primary') Object.assign(base, {
background: 'var(--ink)',
color: 'var(--paper)',
borderColor: 'var(--ink)'
});
if (variant === 'ghost') Object.assign(base, {
background: 'transparent',
borderColor: 'transparent',
padding: '7px 10px'
});
return /*#__PURE__*/React.createElement("button", {
onClick: onClick,
style: {
...base,
...style
}
}, icon && /*#__PURE__*/React.createElement("i", {
"data-lucide": icon,
style: {
width: 14,
height: 14,
strokeWidth: 1.5
}
}), children);
}
const STAGE_COLORS = {
S0: '#B5B5B3',
S1: '#8A8A8A',
S2: '#5C5C5C',
S3: '#0A0A0A',
S4: '#FFD400'
};
function StageDot({
level = 'S2',
label,
style
}) {
return /*#__PURE__*/React.createElement("span", {
style: {
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.1em',
textTransform: 'uppercase',
color: 'var(--fg-2)',
display: 'inline-flex',
alignItems: 'center',
gap: 6,
...style
}
}, /*#__PURE__*/React.createElement("span", {
style: {
width: 8,
height: 8,
borderRadius: 999,
background: STAGE_COLORS[level]
}
}), label || level);
}
function Stamp({
children,
style
}) {
return /*#__PURE__*/React.createElement("span", {
style: {
display: 'inline-block',
background: 'var(--hi)',
color: 'var(--hi-ink)',
padding: '5px 10px 3px',
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.12em',
textTransform: 'uppercase',
transform: 'rotate(-1.5deg)',
...style
}
}, children);
}
function Icon({
name,
size = 16,
style
}) {
return /*#__PURE__*/React.createElement("i", {
"data-lucide": name,
style: {
width: size,
height: size,
strokeWidth: 1.5,
...style
}
});
}
Object.assign(window, {
Eyebrow,
Tag,
Button,
StageDot,
Stamp,
Icon,
STAGE_COLORS
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/whynot-control/Atoms.jsx", error: String((e && e.message) || e) }); }
// ui_kits/whynot-control/Chrome.jsx
try { (() => {
// =============================================================
// Chrome — TopNav, Sidebar, PageHeader, PipelineStrip
// =============================================================
function TopNav({
onNew
}) {
return /*#__PURE__*/React.createElement("nav", {
style: {
height: 56,
background: 'rgba(255,255,255,0.92)',
borderBottom: '1px solid var(--border)',
display: 'flex',
alignItems: 'center',
gap: 28,
padding: '0 24px',
position: 'sticky',
top: 0,
zIndex: 10
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 10
}
}, /*#__PURE__*/React.createElement("img", {
src: "../../assets/whynot-logo.png",
alt: "",
style: {
width: 22,
height: 22
}
}), /*#__PURE__*/React.createElement("span", {
style: {
font: '500 14px var(--ff-sans)'
}
}, "whynot"), /*#__PURE__*/React.createElement("span", {
style: {
font: '400 12px var(--ff-mono)',
color: 'var(--fg-3)',
letterSpacing: '0.04em'
}
}, "/ control")), /*#__PURE__*/React.createElement("div", {
style: {
marginLeft: 'auto',
display: 'flex',
alignItems: 'center',
gap: 12
}
}, /*#__PURE__*/React.createElement("div", {
style: {
font: '400 12px var(--ff-mono)',
color: 'var(--fg-3)',
border: '1px solid var(--border)',
padding: '6px 10px',
borderRadius: 'var(--r-1)',
display: 'flex',
alignItems: 'center',
gap: 10,
minWidth: 240
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "search",
size: 14
}), /*#__PURE__*/React.createElement("span", null, "Search ideas, prototypes, signals…"), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
padding: '1px 5px',
border: '1px solid var(--border)',
borderRadius: 2,
fontSize: 10
}
}, "⌘ K")), /*#__PURE__*/React.createElement(Button, {
variant: "primary",
icon: "plus",
onClick: onNew
}, "New idea")));
}
const NAV_ITEMS = [{
key: 'inbox',
label: 'Inbox',
icon: 'inbox',
count: 7
}, {
key: 'prototypes',
label: 'Prototypes',
icon: 'flask-conical',
count: 4
}, {
key: 'signals',
label: 'Signals',
icon: 'activity',
count: 12
}, {
key: 'betas',
label: 'Betas',
icon: 'users',
count: 1
}, {
key: 'decisions',
label: 'Decisions',
icon: 'check-square',
count: 3
}];
const DOC_ITEMS = [{
key: 'intent',
label: 'INTENT.md'
}, {
key: 'scope',
label: 'SCOPE.md'
}, {
key: 'operating',
label: 'OPERATING_MODEL.md'
}, {
key: 'pipeline',
label: 'PROTOTYPE_PIPELINE.md'
}, {
key: 'agent',
label: 'AGENT_RULES.md'
}];
function Sidebar({
current,
onNav
}) {
const itemStyle = active => ({
display: 'flex',
alignItems: 'center',
gap: 10,
padding: '6px 10px',
color: active ? 'var(--fg-1)' : 'var(--fg-3)',
background: 'transparent',
borderLeft: active ? '2px solid var(--ink)' : '2px solid transparent',
paddingLeft: active ? 10 : 12,
font: active ? '500 13px var(--ff-sans)' : '400 13px var(--ff-sans)',
cursor: 'pointer',
textDecoration: 'none',
transition: 'color 120ms ease, border-color 120ms ease'
});
return /*#__PURE__*/React.createElement("aside", {
style: {
width: 200,
flex: 'none',
background: 'transparent',
borderRight: 'none',
padding: '32px 0 32px 8px',
display: 'flex',
flexDirection: 'column',
gap: 32,
height: 'calc(100vh - 56px)',
position: 'sticky',
top: 56,
overflowY: 'auto'
}
}, /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Eyebrow, {
style: {
paddingLeft: 12,
marginBottom: 10,
display: 'block',
opacity: 0.7
}
}, "Work"), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 1
}
}, NAV_ITEMS.map(item => /*#__PURE__*/React.createElement("a", {
key: item.key,
onClick: () => onNav(item.key),
style: itemStyle(current === item.key)
}, /*#__PURE__*/React.createElement("span", null, item.label), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
font: '400 11px var(--ff-mono)',
color: 'var(--ink-5)'
}
}, item.count))))), /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(Eyebrow, {
style: {
paddingLeft: 12,
marginBottom: 10,
display: 'block',
opacity: 0.7
}
}, "Control docs"), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 1
}
}, DOC_ITEMS.map(item => /*#__PURE__*/React.createElement("a", {
key: item.key,
onClick: () => onNav('doc:' + item.key),
style: {
...itemStyle(current === 'doc:' + item.key),
font: current === 'doc:' + item.key ? '500 12px var(--ff-mono)' : '400 12px var(--ff-mono)'
}
}, /*#__PURE__*/React.createElement("span", null, item.label))))), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: 'auto',
padding: '0 12px'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("span", {
style: {
width: 5,
height: 5,
borderRadius: 999,
background: 'var(--ink-4)'
}
}), /*#__PURE__*/React.createElement("span", {
style: {
font: '400 11px var(--ff-mono)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
color: 'var(--fg-3)'
}
}, "A1 \xB7 Incubating"))));
}
function PageHeader({
eyebrow,
title,
lede,
actions
}) {
return /*#__PURE__*/React.createElement("header", {
style: {
marginBottom: 48,
display: 'flex',
flexDirection: 'column',
gap: 10
}
}, eyebrow && /*#__PURE__*/React.createElement(Eyebrow, null, eyebrow), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'flex-end',
gap: 24
}
}, /*#__PURE__*/React.createElement("h1", {
style: {
font: '400 36px/1.1 var(--ff-sans)',
letterSpacing: '-0.02em',
margin: 0,
flex: 1
}
}, title), actions && /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 8
}
}, actions)), lede && /*#__PURE__*/React.createElement("p", {
style: {
font: '400 16px/1.6 var(--ff-sans)',
color: 'var(--fg-2)',
margin: '4px 0 0',
maxWidth: '56ch'
}
}, lede));
}
function PipelineStrip({
activeIdx = 3
}) {
const stages = [{
num: 'Stage 0',
name: 'Raw idea',
meta: 'inbox/'
}, {
num: 'Stage 1',
name: 'Triage',
meta: '2026-02-12'
}, {
num: 'Stage 2',
name: 'Prototype card',
meta: 'prototypes/'
}, {
num: 'Stage 3',
name: 'Experiment',
meta: 'ends 2026-04-01'
}, {
num: 'Stage 4',
name: 'Signal review',
meta: '— pending'
}];
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: 'repeat(5, 1fr)',
gap: 0,
position: 'relative',
margin: '0 0 32px'
}
}, stages.map((s, i) => {
const state = i < activeIdx ? 'done' : i === activeIdx ? 'active' : 'pending';
const topColor = state === 'done' ? 'var(--ink)' : state === 'active' ? 'var(--hi-2)' : 'var(--border)';
return /*#__PURE__*/React.createElement("div", {
key: i,
style: {
padding: '10px 12px 14px',
borderTop: `2px solid ${topColor}`,
display: 'flex',
flexDirection: 'column',
gap: 4,
position: 'relative'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.1em',
textTransform: 'uppercase',
color: state === 'pending' ? 'var(--fg-3)' : 'var(--fg-1)'
}
}, s.num), /*#__PURE__*/React.createElement("span", {
style: {
font: '500 14px/1.25 var(--ff-sans)',
color: state === 'pending' ? 'var(--fg-3)' : 'var(--fg-1)'
}
}, s.name), /*#__PURE__*/React.createElement("span", {
style: {
font: '400 11px/1.35 var(--ff-mono)',
color: 'var(--fg-3)'
}
}, s.meta), i > 0 && /*#__PURE__*/React.createElement("span", {
style: {
position: 'absolute',
top: -8,
right: -7,
font: '400 14px var(--ff-mono)',
color: state === 'pending' ? 'var(--ink-5)' : 'var(--ink)'
}
}, "→"));
}));
}
Object.assign(window, {
TopNav,
Sidebar,
PageHeader,
PipelineStrip,
NAV_ITEMS,
DOC_ITEMS
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/whynot-control/Chrome.jsx", error: String((e && e.message) || e) }); }
// ui_kits/whynot-control/DocView.jsx
try { (() => {
// =============================================================
// Document viewer — renders one of the control docs
// =============================================================
const DOC_CONTENT = {
intent: {
title: 'INTENT.md',
eyebrow: 'whynot-control · control document',
sections: [{
h: 'Purpose',
p: 'whynot-control exists to serve as the control repository for the whynot organisation: a prototype, feedback, and market-signal space for discovering the weird and the useful.'
}, {
h: 'Primary utility',
list: ['capture unusual but potentially useful ideas;', 'distinguish curiosity from commitment;', 'shape rough ideas into testable prototypes;', 'collect early feedback and market signals;', 'run closed beta concepts in a controlled way;', 'identify which ideas should move toward Helix, Coulomb, Sloppers, Plenitude, Binky, or Tegwick;', 'prevent premature productisation.']
}, {
h: 'Operating principle',
quote: 'A prototype is a question made tangible. The purpose of a prototype is not to prove that an idea is brilliant. The purpose is to learn what is actually useful, desirable, feasible, or irrelevant.'
}]
},
scope: {
title: 'SCOPE.md',
eyebrow: 'whynot-control · control document',
sections: [{
h: 'Current reality',
p: 'whynot-control is the control repository for organising prototype exploration and early market-signal capture.'
}, {
h: 'In scope',
list: ['Prototype idea capture.', 'Prototype classification.', 'Early user feedback notes.', 'Market-signal tracking.', 'Closed beta planning.', 'Experiment records.', 'Promotion recommendations.', 'Agent-assisted drafting and analysis.']
}, {
h: 'Out of scope',
list: ['Production implementation.', 'Long-term product maintenance.', 'Payment processing.', 'Legal investment documentation.', 'Public launch operations.', 'Binding financial, legal, or tax conclusions.']
}, {
h: 'Scope guardrail',
quote: 'whynot-control explores and validates. It does not absorb all product development.'
}]
},
operating: {
title: 'OPERATING_MODEL.md',
eyebrow: 'whynot-control · control document',
sections: [{
h: 'Core rules',
list: ['Prototypes are questions. Each prototype should express a question about usefulness, desirability, feasibility, or willingness to pay.', 'Signal beats enthusiasm. An idea should not be promoted only because it is exciting.', 'Low-cost learning first. Prefer sketches, mockups, demos, landing pages, conversations.', 'Closed beta before broad launch.', 'Promotion requires criteria.']
}, {
h: 'Burnout guardrail',
quote: 'A prototype can be interesting and still be parked. whynot exists to reduce uncertainty, not to create more obligations.'
}]
},
pipeline: {
title: 'PROTOTYPE_PIPELINE.md',
eyebrow: 'whynot-control · control document',
sections: [{
h: 'Stage 0 — Raw capture',
p: 'Capture ideas without judging them immediately. Located in inbox/. Done when the idea is saved and no longer needs to be held in memory.'
}, {
h: 'Stage 1 — Triage',
p: 'Decide whether an idea deserves a prototype card. Outcomes: create card, park, merge, reject.'
}, {
h: 'Stage 2 — Prototype card',
p: 'Turn the idea into a structured prototype candidate. Located in prototypes/.'
}, {
h: 'Stage 3 — Experiment',
p: 'Test the idea with minimal cost: concept note, landing page, clickable mockup, CLI/demo script, Wizard-of-Oz, manual concierge test, closed conversation, private beta.'
}, {
h: 'Stage 4 — Signal review',
p: 'Evaluate what was learned. Interest, usefulness, retention, referral, payment, contribution, strategic fit.'
}, {
h: 'Stage 5 — Decision',
p: 'Park, iterate, promote, reject, or merge. Promotion requires an explicit record in DECISIONS.md.'
}]
},
agent: {
title: 'AGENT_RULES.md',
eyebrow: 'whynot-control · control document',
sections: [{
h: 'General principle',
p: 'Agents may help clarify, structure, draft, compare, and analyse prototype ideas. They must not silently turn experiments into product commitments.'
}, {
h: 'Allowed',
list: ['draft prototype cards', 'classify ideas by lifecycle stage', 'propose smallest useful tests', 'summarise feedback', 'compare prototype candidates', 'improve wording and structure']
}, {
h: 'Forbidden',
list: ['create artificial urgency', 'treat all prototype ideas as products', 'infer willingness to pay without evidence', 'present weak signals as strong validation', 'create legal, financial, or investment commitments']
}, {
h: 'Preferred output style',
quote: 'Agent outputs should be concise, evidence-oriented, explicit about uncertainty, and careful to separate idea, hypothesis, signal, and decision.'
}]
}
};
function DocView({
docKey
}) {
const doc = DOC_CONTENT[docKey];
if (!doc) return /*#__PURE__*/React.createElement("div", null, "Doc not found.");
return /*#__PURE__*/React.createElement("article", {
style: {
maxWidth: 680
}
}, /*#__PURE__*/React.createElement(Eyebrow, null, doc.eyebrow), /*#__PURE__*/React.createElement("h1", {
style: {
font: '600 36px/1.1 var(--ff-mono)',
letterSpacing: '-0.01em',
margin: '12px 0 28px'
}
}, doc.title), doc.sections.map((s, i) => /*#__PURE__*/React.createElement("section", {
key: i,
style: {
marginBottom: 36
}
}, /*#__PURE__*/React.createElement("h2", {
style: {
font: '500 22px/1.25 var(--ff-sans)',
letterSpacing: '-0.005em',
margin: '0 0 14px'
}
}, s.h), s.p && /*#__PURE__*/React.createElement("p", {
style: {
margin: 0,
font: '400 15px/1.65 var(--ff-sans)',
color: 'var(--fg-1)'
}
}, s.p), s.list && /*#__PURE__*/React.createElement("ul", {
style: {
margin: 0,
paddingLeft: 18,
color: 'var(--fg-1)',
font: '400 15px/1.7 var(--ff-sans)'
}
}, s.list.map((li, j) => /*#__PURE__*/React.createElement("li", {
key: j,
style: {
marginBottom: 6
}
}, li))), s.quote && /*#__PURE__*/React.createElement("blockquote", {
style: {
margin: 0,
paddingLeft: 16,
borderLeft: '1px solid var(--border-strong)'
}
}, /*#__PURE__*/React.createElement("p", {
style: {
margin: 0,
font: '400 italic 17px/1.55 var(--ff-serif)',
color: 'var(--fg-2)'
}
}, s.quote)))), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: 48,
padding: '14px 0',
borderTop: '1px solid var(--border)',
display: 'flex',
justifyContent: 'space-between',
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, /*#__PURE__*/React.createElement("span", null, "whynot-control / ", doc.title), /*#__PURE__*/React.createElement("span", null, "A1 \xB7 Incubating \xB7 2026")));
}
Object.assign(window, {
DocView,
DOC_CONTENT
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/whynot-control/DocView.jsx", error: String((e && e.message) || e) }); }
// ui_kits/whynot-control/Screens.jsx
try { (() => {
// =============================================================
// Screens — Inbox, PrototypesIndex, PrototypeDetail, SignalsIndex, DocView, BetasIndex, DecisionsIndex
// =============================================================
function Inbox({
onCapture
}) {
const [draft, setDraft] = React.useState('');
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: "whynot-control / inbox",
title: "Inbox",
lede: "Temporary capture for rough ideas, weird observations, user comments, market hints, and product fragments. Capture is not commitment."
}), /*#__PURE__*/React.createElement("div", {
style: {
border: '1px solid var(--border)',
borderRadius: 'var(--r-2)',
padding: 16,
background: 'var(--paper)',
marginBottom: 28,
display: 'flex',
flexDirection: 'column',
gap: 10
}
}, /*#__PURE__*/React.createElement(Eyebrow, null, "Capture"), /*#__PURE__*/React.createElement("textarea", {
value: draft,
onChange: e => setDraft(e.target.value),
placeholder: "An idea, an observation, a fragment. No filter, no judgement, no commitment.",
style: {
font: '400 14px/1.5 var(--ff-sans)',
border: 'none',
outline: 'none',
resize: 'none',
minHeight: 64,
padding: 0,
background: 'transparent',
color: 'var(--fg-1)'
}
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 8
}
}, /*#__PURE__*/React.createElement("span", {
style: {
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)',
marginRight: 'auto'
}
}, "⌘ ↵ to capture \xB7 stored in ", /*#__PURE__*/React.createElement("code", {
className: "mono"
}, "inbox/")), /*#__PURE__*/React.createElement(Button, {
variant: "ghost",
onClick: () => setDraft('')
}, "Discard"), /*#__PURE__*/React.createElement(Button, {
variant: "primary",
icon: "inbox",
onClick: () => {
onCapture && onCapture(draft);
setDraft('');
}
}, "Capture"))), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
alignItems: 'center',
gap: 12,
marginBottom: 14
}
}, /*#__PURE__*/React.createElement(Eyebrow, null, "Recent \xB7 7"), /*#__PURE__*/React.createElement("div", {
style: {
flex: 1,
borderTop: '1px solid var(--border-soft)'
}
}), /*#__PURE__*/React.createElement("span", {
style: {
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, "↓ newest first")), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column'
}
}, INBOX.map(item => /*#__PURE__*/React.createElement("div", {
key: item.id,
style: {
display: 'grid',
gridTemplateColumns: '120px 1fr',
gap: '4px 24px',
padding: '20px 0',
borderBottom: '1px solid var(--border-soft)',
alignItems: 'baseline'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, item.ts), /*#__PURE__*/React.createElement("span", {
style: {
font: '500 10px/1 var(--ff-mono)',
letterSpacing: '0.08em',
textTransform: 'uppercase',
color: 'var(--fg-3)'
}
}, item.from), /*#__PURE__*/React.createElement("p", {
style: {
gridColumn: '1 / -1',
margin: '4px 0 0',
font: '400 15px/1.6 var(--ff-sans)',
color: 'var(--fg-1)',
maxWidth: '60ch'
}
}, item.text)))));
}
function PrototypeListCard({
p,
onOpen
}) {
const [hover, setHover] = React.useState(false);
return /*#__PURE__*/React.createElement("article", {
onClick: () => onOpen(p.id),
onMouseEnter: () => setHover(true),
onMouseLeave: () => setHover(false),
style: {
background: 'var(--paper)',
borderTop: '1px solid var(--border-soft)',
padding: '24px 0 28px',
display: 'flex',
flexDirection: 'column',
gap: 12,
position: 'relative',
cursor: 'pointer',
transition: 'background 120ms ease'
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'baseline'
}
}, /*#__PURE__*/React.createElement(Eyebrow, {
style: {
color: hover ? 'var(--fg-1)' : 'var(--fg-3)'
}
}, p.id, " \xB7 Prototype"), /*#__PURE__*/React.createElement(StageDot, {
level: p.signal,
label: p.stageLabel
})), /*#__PURE__*/React.createElement("h3", {
style: {
font: '400 22px/1.3 var(--ff-sans)',
letterSpacing: '-0.01em',
margin: '2px 0 8px',
color: 'var(--fg-1)',
maxWidth: '52ch'
}
}, p.pitch), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '140px 1fr',
gap: '10px 16px',
fontSize: 14,
color: 'var(--fg-2)',
maxWidth: '60ch'
}
}, /*#__PURE__*/React.createElement("span", {
style: {
font: '500 11px/1.7 var(--ff-mono)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
color: 'var(--fg-3)'
}
}, "Learning q."), /*#__PURE__*/React.createElement("span", {
style: {
lineHeight: 1.55
}
}, p.learning), /*#__PURE__*/React.createElement("span", {
style: {
font: '500 11px/1.7 var(--ff-mono)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
color: 'var(--fg-3)'
}
}, "Smallest test"), /*#__PURE__*/React.createElement("span", {
style: {
lineHeight: 1.55
}
}, p.test)), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 24,
marginTop: 4,
font: '500 11px var(--ff-mono)',
letterSpacing: '0.06em',
textTransform: 'uppercase',
color: 'var(--fg-3)'
}
}, /*#__PURE__*/React.createElement("span", null, "→ ", p.target), /*#__PURE__*/React.createElement("span", null, p.signal, " signal"), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
color: hover ? 'var(--fg-1)' : 'var(--fg-3)'
}
}, "Open →")));
}
function PrototypesIndex({
onOpen
}) {
const [filter, setFilter] = React.useState('All');
const filters = ['All', 'Experiment', 'Signal review', 'Parked'];
const list = filter === 'All' ? PROTOTYPES : PROTOTYPES.filter(p => p.stageLabel === filter);
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: "whynot-control / prototypes",
title: "Prototypes",
lede: "Structured prototype cards. A prototype card defines a learning question and the smallest useful test.",
actions: /*#__PURE__*/React.createElement(Button, {
variant: "primary",
icon: "plus"
}, "New prototype")
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
gap: 10,
marginBottom: 8,
alignItems: 'center'
}
}, filters.map(f => /*#__PURE__*/React.createElement(Tag, {
key: f,
active: filter === f,
style: {
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement("span", {
onClick: () => setFilter(f)
}, f))), /*#__PURE__*/React.createElement("span", {
style: {
marginLeft: 'auto',
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, list.length, " of ", PROTOTYPES.length)), /*#__PURE__*/React.createElement("div", null, list.map(p => /*#__PURE__*/React.createElement(PrototypeListCard, {
key: p.id,
p: p,
onOpen: onOpen
}))));
}
function PrototypeDetail({
id,
onBack
}) {
const p = PROTOTYPES.find(p => p.id === id) || PROTOTYPES[0];
const stageIdx = {
'parked': 0,
'experiment': 3,
'signal': 4,
'experiment-active': 3
}[p.stage] ?? 3;
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("a", {
onClick: onBack,
style: {
display: 'inline-flex',
alignItems: 'center',
gap: 6,
font: '400 12px var(--ff-mono)',
color: 'var(--fg-2)',
textDecoration: 'none',
marginBottom: 18,
cursor: 'pointer'
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "arrow-left",
size: 14
}), " Back to prototypes"), /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: `${p.id} · Prototype`,
title: p.pitch,
actions: /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Button, {
variant: "secondary",
icon: "archive"
}, "Park"), /*#__PURE__*/React.createElement(Button, {
variant: "primary",
icon: "arrow-right"
}, "Promote → ", p.target))
}), /*#__PURE__*/React.createElement(PipelineStrip, {
activeIdx: stageIdx
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'grid',
gridTemplateColumns: '1.4fr 1fr',
gap: 32
}
}, /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 22
}
}, /*#__PURE__*/React.createElement(Field, {
label: "Learning question",
value: p.learning
}), /*#__PURE__*/React.createElement(Field, {
label: "Smallest useful test",
value: p.test
}), /*#__PURE__*/React.createElement(Field, {
label: "Expected signal",
value: "At least one person asks for a concrete next step, gives specific use-case feedback, or identifies a realistic context where the idea would matter."
}), /*#__PURE__*/React.createElement(Field, {
label: "Risks",
value: p.risks
})), /*#__PURE__*/React.createElement("aside", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 18
}
}, /*#__PURE__*/React.createElement(SidebarField, {
label: "Stage",
value: /*#__PURE__*/React.createElement(Tag, {
active: true
}, p.stageLabel)
}), /*#__PURE__*/React.createElement(SidebarField, {
label: "Signal",
value: /*#__PURE__*/React.createElement(StageDot, {
level: p.signal
})
}), /*#__PURE__*/React.createElement(SidebarField, {
label: "Target",
value: /*#__PURE__*/React.createElement("code", {
className: "mono"
}, "→ ", p.target)
}), /*#__PURE__*/React.createElement(SidebarField, {
label: "Audience",
value: "Potential early users, collaborators, or customers."
}), /*#__PURE__*/React.createElement(SidebarField, {
label: "Agentic suitability",
value: "Agents may help turn rough notes into a sharper prototype card."
}), /*#__PURE__*/React.createElement("div", {
style: {
marginTop: 6,
border: '1px dashed var(--border-strong)',
borderRadius: 4,
padding: 14
}
}, /*#__PURE__*/React.createElement(Eyebrow, {
style: {
display: 'block',
marginBottom: 8
}
}, "Caveat"), /*#__PURE__*/React.createElement("p", {
style: {
margin: 0,
font: '400 13px/1.55 var(--ff-sans)',
color: 'var(--fg-2)'
}
}, "A prototype can be interesting and still be parked. ", /*#__PURE__*/React.createElement("code", {
className: "mono"
}, "whynot"), " exists to reduce uncertainty, not create more obligations.")))));
}
function Field({
label,
value
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 6
}
}, /*#__PURE__*/React.createElement(Eyebrow, null, label), /*#__PURE__*/React.createElement("p", {
style: {
margin: 0,
font: '400 15px/1.55 var(--ff-sans)',
color: 'var(--fg-1)'
}
}, value));
}
function SidebarField({
label,
value
}) {
return /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column',
gap: 6
}
}, /*#__PURE__*/React.createElement(Eyebrow, null, label), /*#__PURE__*/React.createElement("div", {
style: {
font: '400 13px/1.5 var(--ff-sans)',
color: 'var(--fg-1)'
}
}, value));
}
function SignalsIndex() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: "whynot-control / signals",
title: "Signals",
lede: "Market-signal and feedback records. A signal is evidence. Record what happened, who did it, and how strong the evidence is.",
actions: /*#__PURE__*/React.createElement(Button, {
variant: "primary",
icon: "plus"
}, "Record signal")
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column'
}
}, SIGNALS.map(s => /*#__PURE__*/React.createElement("div", {
key: s.id,
style: {
display: 'grid',
gridTemplateColumns: '90px 90px 1fr',
gap: '6px 24px',
padding: '20px 0',
borderBottom: '1px solid var(--border-soft)',
alignItems: 'baseline'
}
}, /*#__PURE__*/React.createElement("code", {
className: "mono",
style: {
background: 'none',
padding: 0,
color: 'var(--fg-3)',
font: '400 11px var(--ff-mono)'
}
}, s.id), /*#__PURE__*/React.createElement("code", {
className: "mono",
style: {
background: 'none',
padding: 0,
color: 'var(--fg-2)',
font: '400 11px var(--ff-mono)'
}
}, s.proto), /*#__PURE__*/React.createElement(StageDot, {
level: s.level
}), /*#__PURE__*/React.createElement("p", {
style: {
gridColumn: '1 / -1',
margin: '4px 0 0',
font: '400 14px/1.55 var(--ff-sans)',
color: 'var(--fg-1)',
maxWidth: '60ch'
}
}, s.what), /*#__PURE__*/React.createElement("span", {
style: {
gridColumn: '1 / -1',
font: '400 11px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, s.source, " \xB7 ", s.date)))));
}
function BetasIndex() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: "whynot-control / betas",
title: "Betas",
lede: "Closed beta plans and beta review notes. A beta should have a clear learning question, entry criteria, and exit outcome."
}), /*#__PURE__*/React.createElement("div", {
style: {
border: '1px dashed var(--border-strong)',
padding: 32,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 8,
textAlign: 'center',
color: 'var(--fg-3)',
borderRadius: 4
}
}, /*#__PURE__*/React.createElement(Icon, {
name: "users",
size: 20
}), /*#__PURE__*/React.createElement("div", {
style: {
font: '500 14px var(--ff-sans)',
color: 'var(--fg-2)'
}
}, "One beta plan in draft."), /*#__PURE__*/React.createElement("div", {
style: {
font: '400 12px var(--ff-mono)',
color: 'var(--fg-3)'
}
}, "WNO-021 \xB7 Concierge triage \xB7 pending Binky approval"), /*#__PURE__*/React.createElement("a", {
href: "#",
style: {
font: '500 12px var(--ff-mono)',
color: 'var(--fg-1)',
marginTop: 4
}
}, "Open draft →")));
}
function DecisionsIndex() {
const decisions = [{
id: 'DEC-001',
title: 'Shorten organisation name from whywhynot to whynot.',
status: 'Accepted',
date: '2026-01-08'
}, {
id: 'DEC-002',
title: 'Maintain A1 Incubating until first prototype candidates review.',
status: 'Open',
date: '—'
}, {
id: 'DEC-003',
title: 'Initial promotion targets: Helix, Coulomb, Sloppers, Plenitude, Binky, Tegwick.',
status: 'Open',
date: '—'
}];
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement(PageHeader, {
eyebrow: "whynot-control / decisions",
title: "Decisions",
lede: "A promotion record is required before any prototype moves to Helix, Coulomb, Sloppers, Plenitude, Binky, or Tegwick."
}), /*#__PURE__*/React.createElement("div", {
style: {
display: 'flex',
flexDirection: 'column'
}
}, decisions.map(d => /*#__PURE__*/React.createElement("div", {
key: d.id,
style: {
display: 'grid',
gridTemplateColumns: '90px 1fr 130px 100px',
gap: 20,
alignItems: 'baseline',
padding: '16px 4px',
borderBottom: '1px solid var(--border-soft)'
}
}, /*#__PURE__*/React.createElement("code", {
className: "mono",
style: {
background: 'none',
padding: 0,
color: 'var(--fg-1)'
}
}, d.id), /*#__PURE__*/React.createElement("span", {
style: {
font: '500 15px var(--ff-sans)',
color: 'var(--fg-1)'
}
}, d.title), /*#__PURE__*/React.createElement(Tag, {
active: d.status === 'Accepted',
draft: d.status === 'Open'
}, d.status), /*#__PURE__*/React.createElement("span", {
style: {
font: '400 12px var(--ff-mono)',
color: 'var(--fg-3)',
textAlign: 'right'
}
}, d.date)))));
}
Object.assign(window, {
Inbox,
PrototypesIndex,
PrototypeDetail,
SignalsIndex,
BetasIndex,
DecisionsIndex,
Field,
SidebarField,
PrototypeListCard
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/whynot-control/Screens.jsx", error: String((e && e.message) || e) }); }
// ui_kits/whynot-control/data.jsx
try { (() => {
// =============================================================
// Sample data — prototypes, signals, inbox items
// =============================================================
const PROTOTYPES = [{
id: 'WNO-014',
pitch: 'A field-notebook for catching weird ideas before they evaporate.',
learning: 'Do people return to capture more than once?',
test: 'One-page landing + email capture, 14 days.',
target: 'Coulomb',
stage: 'experiment',
stageLabel: 'Experiment',
signal: 'S1',
risks: 'Confused with note-taking apps.'
}, {
id: 'WNO-017',
pitch: 'A LEGO-brick mood board for engineers who dont think in mood boards.',
learning: 'Will engineers attach metaphors to their tickets?',
test: 'Slack bot, three teams, two weeks.',
target: 'Helix',
stage: 'signal',
stageLabel: 'Signal review',
signal: 'S3',
risks: 'Cute but unused after a week.'
}, {
id: 'WNO-021',
pitch: 'Concierge-style “prototype triage” for indie hackers.',
learning: 'Will three founders pay for a one-hour triage call?',
test: 'Offer beta · 3 calls · listed price.',
target: 'Plenitude',
stage: 'experiment',
stageLabel: 'Experiment',
signal: 'S2',
risks: 'Time-cost outruns signal value.'
}, {
id: 'WNO-024',
pitch: 'A relevant-#CoronaPolitics timeline, re-released with one editor.',
learning: 'Is there residual demand five years on?',
test: 'Static preview page, 30 days, count returns.',
target: 'None yet',
stage: 'parked',
stageLabel: 'Parked',
signal: 'S0',
risks: 'Topical relevance has clearly faded.'
}];
const INBOX = [{
id: 1,
ts: '2026-03-02 14:21',
text: 'Idea: “subway map” view of the prototype pipeline. People understand transit maps; they dont understand kanban boards.',
from: 'Tegwick'
}, {
id: 2,
ts: '2026-03-01 09:08',
text: 'Weird observation from yesterdays call: three founders independently asked for “something to capture the half-formed stuff”.',
from: 'note-to-self'
}, {
id: 3,
ts: '2026-02-28 23:55',
text: 'Could the LEGO-brick metaphor extend to a public “build log” format? One brick = one decision.',
from: 'Tegwick'
}, {
id: 4,
ts: '2026-02-27 11:34',
text: 'Park idea: realtime sentiment dashboard for prototype landing pages. Probably worse than reading the comments.',
from: 'note-to-self'
}, {
id: 5,
ts: '2026-02-26 17:02',
text: 'Conversation with R. about closed-beta etiquette. Useful: pre-write the exit email before the beta opens.',
from: 'note-to-self'
}, {
id: 6,
ts: '2026-02-25 08:12',
text: 'fuerindifferenz shirts: residual interest from old whywhynot.de page. Could a yearly drop work?',
from: 'Tegwick'
}, {
id: 7,
ts: '2026-02-24 15:40',
text: 'Tiny idea: a “reject log” that publishes the ideas you said no to, with one-sentence reasons.',
from: 'note-to-self'
}];
const SIGNALS = [{
id: 'SIG-031',
proto: 'WNO-017',
level: 'S3',
what: 'Two teams shipped public README sections labelled “brick: scope” after using the bot for a week.',
source: 'usage log',
date: '2026-03-04'
}, {
id: 'SIG-030',
proto: 'WNO-017',
level: 'S2',
what: 'Three engineers DMd asking for an export-to-Notion option.',
source: 'Slack',
date: '2026-03-03'
}, {
id: 'SIG-029',
proto: 'WNO-014',
level: 'S1',
what: 'Landing page: 34 visits, 7 emails, 0 returns in week 1.',
source: 'Plausible',
date: '2026-03-01'
}, {
id: 'SIG-028',
proto: 'WNO-021',
level: 'S2',
what: 'First triage call booked at listed price; second declined on price.',
source: 'Stripe / email',
date: '2026-02-28'
}, {
id: 'SIG-027',
proto: 'WNO-021',
level: 'S1',
what: '“Interesting but Id want a free first one” ×2.',
source: 'interview',
date: '2026-02-26'
}, {
id: 'SIG-026',
proto: 'WNO-024',
level: 'S0',
what: 'Static preview: 12 visits in 30 days, 0 returns.',
source: 'Plausible',
date: '2026-02-24'
}];
Object.assign(window, {
PROTOTYPES,
INBOX,
SIGNALS
});
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/whynot-control/data.jsx", error: String((e && e.message) || e) }); }
})();