diff --git a/.changeset/semantic-model-starter.md b/.changeset/semantic-model-starter.md new file mode 100644 index 00000000..7b9530b0 --- /dev/null +++ b/.changeset/semantic-model-starter.md @@ -0,0 +1,5 @@ +--- +"@design-intelligence/ghost": minor +--- + +The starter package follows the semantic model: kinds are `standard`, `foundation`, and `context`; the median floor ships as `standard.model-defaults` with rules labeled as shared Defaults; foundation and context bodies use `Usage`/`Rules`/`Never` sections; `ghost checks init` pairs the median check with the new node id and still supports legacy `cliche.median` packages. diff --git a/CLAUDE.md b/CLAUDE.md index e91a8c84..5099cd25 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -103,7 +103,7 @@ Core workflow: | Command | Description | | --- | --- | -| `ghost init` | Scaffold `.ghost/` with the starter package: manifest, glossary, a `brand.md` cover, foundation chapters, context nodes, and the cliche floor. `--body vessel-light` installs a full inhabited package instead. `--with checks` also adds the checks directory. | +| `ghost init` | Scaffold `.ghost/` with the starter package: manifest, glossary, a `brand.md` cover, foundation chapters, context nodes, and the shared model-defaults floor. `--body vessel-light` installs a full inhabited package instead. `--with checks` also adds the checks directory. | | `ghost checks init` | Scaffold `.ghost/checks/` with an example review assertion. | | `ghost validate` | Validate the package: manifest shape, node validity, material locators, check references, and glossary kind prefixes. | | `ghost gather [ask…]` | Emit the complete guidance menu so the agent can pull applicable nodes. | diff --git a/packages/context-control/demo/asks.md b/packages/context-control/demo/asks.md index 83f40270..9e2c0bbf 100644 --- a/packages/context-control/demo/asks.md +++ b/packages/context-control/demo/asks.md @@ -16,7 +16,7 @@ poison: context.conversation Build a pricing landing page with a hero, three plan tiers, a customer quote, and a closing call to action. -expect: foundation.composition, foundation.color, foundation.type, cliche.median +expect: foundation.composition, foundation.color, foundation.type, standard.model-defaults poison: context.conversation ## Ask 3 — assistant conversation diff --git a/packages/ghost/src/init-payloads/median/median-tells.md b/packages/ghost/src/init-payloads/median/median-tells.md index 2762827b..b9693e87 100644 --- a/packages/ghost/src/init-payloads/median/median-tells.md +++ b/packages/ghost/src/init-payloads/median/median-tells.md @@ -3,45 +3,45 @@ name: Median tells description: Flags the measured defaults of unsteered generation, the deterministic floor, and current model-signature tells — hover-lift, default accents, unprompted dark theme, gradient text, contrast, frequency tells, and per-model signatures. severity: high references: - - cliche.median > Hover-lift - - cliche.median > Indigo accent - - cliche.median > Dark theme - - cliche.median > Gradients - - cliche.median > Glassmorphism - - cliche.median > Side-stripe - - cliche.median > Cream surface - - cliche.median > Chat bubbles - - cliche.median > Stock copy - - cliche.median > Celebration - - cliche.median > Hero metric - - cliche.median > Eyebrow kicker + - standard.model-defaults > Hover-lift + - standard.model-defaults > Indigo accent + - standard.model-defaults > Dark theme + - standard.model-defaults > Gradients + - standard.model-defaults > Glassmorphism + - standard.model-defaults > Side-stripe + - standard.model-defaults > Cream surface + - standard.model-defaults > Chat bubbles + - standard.model-defaults > Stock copy + - standard.model-defaults > Celebration + - standard.model-defaults > Hero metric + - standard.model-defaults > Eyebrow kicker --- These flags target the measured convergence patterns of unsteered model generation, the deterministic floor the median node licenses, and tells specific to individual models. Each is mechanically detectable in a diff. -Pruning a rule from `cliche.median` orphans its paired reference here — +Pruning a rule from `standard.model-defaults` orphans its paired reference here — `ghost validate` warns; delete the flag and its reference together. Flag `transform` with `translateY` inside a `:hover` rule on cards, buttons, or list items, especially paired with a shadow increase. Hover confirmation in this package is color and background change, not lift. -(`cliche.median > Hover-lift`) +(`standard.model-defaults > Hover-lift`) Flag accent values in the indigo/blue/purple default family (`#4f46e5`, `#6366f1`, `#2563eb`, `#3b82f6`, `#8b5cf6`, and close neighbors) unless the diff shows the user asked for them. They are model defaults, not -palette members. (`cliche.median > Indigo accent`) +palette members. (`standard.model-defaults > Indigo accent`) Flag whole-page dark backgrounds when the ask did not request dark mode. Dark surfaces are a declared brand choice or an explicit theme, never an -unprompted default. (`cliche.median > Dark theme`) +unprompted default. (`standard.model-defaults > Dark theme`) Flag `linear-gradient` or `radial-gradient` as page or section -backgrounds, and gradient-filled buttons. (`cliche.median > Gradients`) +backgrounds, and gradient-filled buttons. (`standard.model-defaults > Gradients`) Flag `backdrop-filter: blur` used for glassmorphism cards. -(`cliche.median > Glassmorphism`) +(`standard.model-defaults > Glassmorphism`) Flag `background-clip: text` (with or without the `-webkit-` prefix) paired with a gradient. Emphasis comes from weight or size in a single @@ -49,14 +49,14 @@ solid color. Flag a thick colored border on one side of an element (`border-left` or a `border-l-*` utility at 2px or more in a non-neutral color) while the -other sides stay thin. (`cliche.median > Side-stripe`) +other sides stay thin. (`standard.model-defaults > Side-stripe`) Flag warm off-white page backgrounds in the cream/sand/beige band, and token names like `--cream`, `--sand`, `--parchment`, `--linen` introduced -by the diff. (`cliche.median > Cream surface`) +by the diff. (`standard.model-defaults > Cream surface`) Flag assistant messages rendered as bubbles with initials-circle avatars. -(`cliche.median > Chat bubbles`) +(`standard.model-defaults > Chat bubbles`) Flag emoji used as icons or imagery in interface chrome. Text labels carry meaning. @@ -64,16 +64,16 @@ meaning. Flag stock template copy in headings: "Simple, transparent pricing", "Welcome back", and interchangeable-with-a-competitor phrasing. Recommend copy that states what this product specifically does. -(`cliche.median > Stock copy`) +(`standard.model-defaults > Stock copy`) Flag exclamation-marked success copy, confetti language, and celebratory UI ("You did it!", "Awesome!"). Confirmation is quiet and factual. -(`cliche.median > Celebration`) +(`standard.model-defaults > Celebration`) Flag the hero-metric template — a big number, small label, and supporting stats as default proof — unless the metric shows real user data. Recommend evidence specific to the product, or nothing. -(`cliche.median > Hero metric`) +(`standard.model-defaults > Hero metric`) Deterministic floor — licensed by the median node, verified here, never steered in prose: @@ -95,7 +95,7 @@ Frequency tells — the crime is repetition, not the move (advisory): Flag three or more uppercase, tracked eyebrow kickers above section headings in one page. One named kicker is voice; a kicker on every section is model -grammar. (`cliche.median > Eyebrow kicker`) +grammar. (`standard.model-defaults > Eyebrow kicker`) Flag five or more em-dashes in body copy in one view. @@ -108,7 +108,8 @@ Model-signature tells — skip any block whose model did not produce the diff Codex: flag a 1px border paired with a box-shadow of 16px blur or more on the same element — pick a solid border or a tight shadow, not both. Flag border-radius of 32px or more on cards, sections, or inputs unless an -answered shape dial (`signature.shape`) sanctions large radii — then it is +answered shape foundation (for example, `foundation.shape`) sanctions large +radii — then it is fidelity, not a tell. Flag 1px linear-gradient grid or repeating-stripe backgrounds used as decoration. diff --git a/packages/vessel-light/.ghost/anti-goal.median.md b/packages/ghost/src/init-payloads/median/standard.model-defaults.md similarity index 87% rename from packages/vessel-light/.ghost/anti-goal.median.md rename to packages/ghost/src/init-payloads/median/standard.model-defaults.md index 302393f7..5ae11a94 100644 --- a/packages/vessel-light/.ghost/anti-goal.median.md +++ b/packages/ghost/src/init-payloads/median/standard.model-defaults.md @@ -2,9 +2,13 @@ for: Any greenfield visual surface or first-draft copy. --- -This is the model's median, not your brand. Each rule is reject→replace. -Delete every line your brand legitimately violates — `ghost validate` will -warn on any check reference the deletion orphans — delete the paired flag too. +Shared defaults: the model's median, not your brand. Every rule below is a +**Default** — a starting position that protects unsteered work from generic +model behavior, never an obligation. Explicit brand guidance in the cover, a +foundation, or a matching context may deliberately replace any of them. +Each rule is reject→replace. Delete every line your brand legitimately +violates — `ghost validate` will warn on any check reference the deletion +orphans — delete the paired flag too. These are not aesthetic opinions. Where a count is given, it is the measured convergence of 300 unsteered generations across three frontier models (the diff --git a/packages/ghost/src/init-payloads/skeleton/brand.md b/packages/ghost/src/init-payloads/skeleton/brand.md index 647d45f2..74d5da9d 100644 --- a/packages/ghost/src/init-payloads/skeleton/brand.md +++ b/packages/ghost/src/init-payloads/skeleton/brand.md @@ -6,9 +6,8 @@ This cover is unwritten. ghost gather always places this page in an agent's context; that is delivery status, not a claim that every sentence applies to every task. Use it for what cannot be retrieved by task: what this brand is about, in one paragraph, in the brand's own voice. The temperature its words and -motion share. And the refusals only this brand makes — not generic don'ts -(those live in each foundation's misuse list, and the model's cliches live -in the cliche nodes), but the lines this brand alone draws. +motion share. And the refusals only this brand makes — not generic don'ts, +but the lines this brand alone draws. Until a human writes it, the working stance is: quiet, precise, content first, decoration never. Treat that as provisional and say so in your @@ -19,5 +18,10 @@ single element, it belongs in that element's chapter. In a single view, it belongs in the composition foundation. Only across the whole body of work — temperature, density, restraint — it belongs here. +Generic don'ts do not live here either: element-scoped rejections live in +each foundation's Never section, and the model's measured defaults live in +the shared `standard.model-defaults` node. This page holds only the refusals +this brand alone makes. + The budget is one screen. When this page is real, delete every sentence of scaffolding above — including this one. diff --git a/packages/ghost/src/init-payloads/skeleton/context.conversation.md b/packages/ghost/src/init-payloads/skeleton/context.conversation.md index b1247769..b64441f7 100644 --- a/packages/ghost/src/init-payloads/skeleton/context.conversation.md +++ b/packages/ghost/src/init-payloads/skeleton/context.conversation.md @@ -5,20 +5,29 @@ for: Chat threads, agent consoles, and prompt composers. In this context: AI conversation threads, agent consoles, review assistants, and prompt composers. Elsewhere, the defaults hold. +## Usage + Conversation UI is not chat cosplay. The assistant speaks on the page -surface as plain text: no bubble, no border, no fill. Wrapping assistant -messages in cards makes the system look defensive and wastes density — -hierarchy comes from prose, spacing, and type. +surface as plain text — hierarchy comes from prose, spacing, and type. +Wrapping assistant messages in cards makes the system look defensive and +wastes density. Tool calls are operational evidence, not conversation. + +## Rules -User turns are compact muted surfaces aligned right. They mark authorship -without turning the thread into alternating balloons. +- Assistant turns render as plain text on the page surface: no bubble, no + border, no fill. +- User turns are compact muted surfaces aligned right — authorship marked + without turning the thread into alternating balloons. +- Tool calls collapse to a labeled one-line summary with status; expand only + when the user asks, then show mono content inside the disclosed area. +- The prompt input is one bordered surface. The textarea stays empty of + controls; attachments, model choice, and send live in a single row below + it. +- Stop and send are mutually exclusive states of the same action area. -Tool calls are operational evidence. Collapse them to a labeled one-line -summary with status; expand only when the user asks, then show mono content -inside the disclosed area. +## Never -The prompt input is one bordered surface. The textarea stays empty of -controls so writing remains the focus; attachments, model choice, and send -live in a single row below it. There is one primary send action — stop and -send are mutually exclusive states of the same action area, never two -competing primaries. +- Never wrap assistant messages in cards or bubbles — plain text on the + page surface. +- Never render two competing primary actions in the composer — stop and + send share one action area. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.color.md b/packages/ghost/src/init-payloads/skeleton/foundation.color.md index 274d1ca0..bd960c31 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.color.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.color.md @@ -9,34 +9,39 @@ until a human edits or confirms this chapter. Color is assigned by role, not by taste. The roles are the source of truth; raw color values are implementation detail, never product language. If a -container needs a color, it first needs a role: `background`, `foreground`, -`card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `border`, -`input`, `ring`, and the status roles. - -The status roles — destructive, success, warning, info — exist only when -meaning demands them. Destructive means destructive or error. Success means -success. None of them are brand accents. - -One view does not perform a color palette. If a status color is present, the -rest of the view stays on the base roles. Richness beyond this comes from a -closed expression set (`--expression-*`), used at the volume the situation +container needs a color, it first needs a role. Richness beyond the base +spine comes from a closed expression set, used at the volume the situation allows — a marketing page may turn it up; a settings form stays quiet. -## Palette - -Open — ask the human; do not freehand. The fixed relationship: a quiet base -spine is the default atmosphere in every medium, and a closed expression set -supplies the rest, its volume set by situation, never by taste. The open -question: what is this brand's base spine, and what are the named hues of -its expression set — how many, and which? When the human answers, restate -this section as the brand's current answer and record the values where your -materials live. Until then, proceed with a quiet provisional spine and label -it provisional. - -## Misuse - -- Status colors never moonlight as atmosphere, in any context. -- Expression never touches what you click. Buttons, inputs, and links stay - on the base roles everywhere. A colored control is a different design +The status roles exist only when meaning demands them. None of them are +brand accents. + +## Rules + +- Every colored element uses a named role: `background`, `foreground`, + `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `border`, + `input`, `ring`, or a status role. +- Status roles carry their meaning exactly: destructive means destructive or + error, success means success, warning warns, info informs. +- When a status color is present, the rest of the view stays on the base + roles. One view does not perform a color palette. +- Expression color comes only from the closed `--expression-*` set, at the + volume the situation allows. +- Known gap — the palette is unanswered. The fixed relationship: a quiet + base spine is the default atmosphere in every medium, and a closed + expression set supplies the rest, its volume set by situation, never by + taste. The open question: what is this brand's base spine, and what are + the named hues of its expression set — how many, and which? Ask the human; + do not invent values. Until answered, proceed with a quiet provisional + spine and label it provisional. When answered, restate this rule as the + brand's current answer and record the values where your materials live. + +## Never + +- Never use status colors as atmosphere, in any context — status color + appears only where its meaning applies. +- Never put expression color on what you click. Buttons, inputs, and links + stay on the base roles everywhere; a colored control is a different design system. -- No one-off hex values. If a color has no role, it has no place. +- Never use a one-off hex value — if a color has no role, it has no place; + give it a role or delete it. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.composition.md b/packages/ghost/src/init-payloads/skeleton/foundation.composition.md index d3e576d3..d542460c 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.composition.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.composition.md @@ -9,25 +9,24 @@ until a human edits or confirms this chapter. Composition is the layer above layout: layout gives the stacks and gaps; composition decides what dominates, what recedes, and what comes last. +Primacy comes from scale, position, and space. Actions land last — the user +reads, then decides. -Every view has exactly one focal point — one element at the largest scale. -If two elements compete for primacy, demote or delete one; never resolve -the tie by making both big. +## Rules -One primary action per view: the action the view exists to complete. -Everything else steps down the control ladder (see the controls -foundation). If two buttons both look primary, the composition failed. +- Every view has exactly one focal point — one element at the largest scale. + If two elements compete for primacy, demote or delete one. +- One primary action per view: the action the view exists to complete. + Everything else steps down the control ladder (see the controls + foundation). +- Calls to action render at the end of reading order. +- Separation escalates: whitespace first, then a divider, then a container — + in that order, never skipping a step. -Actions land last. Calls to action render at the end of reading order — -the user reads, then decides. +## Never -Separation escalates: whitespace first, then a divider, then a container — -in that order, never skipping a step. - -## Misuse - -- Never fake a focal point with decoration. Primacy comes from scale, +- Never fake a focal point with decoration — primacy comes from scale, position, and space. - Never promote a second action to primary because something wants - emphasis; step the other one down instead. + emphasis — step the other one down instead. - Never separate with a container when whitespace would do. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.controls.md b/packages/ghost/src/init-payloads/skeleton/foundation.controls.md index 42a2485c..340d4d6a 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.controls.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.controls.md @@ -15,18 +15,23 @@ may hold is a composition rule — see the composition foundation: one. Destructive is a meaning, not a rung. It is rare, it names the destructive act directly, and it is never borrowed for urgency, emphasis, or brand heat. -Fields are quiet until active: hairline border, muted placeholder, clear -label, and a focus ring when the user engages. The ring is guidance, not -decoration. Errors state facts next to the field that caused them — never -hidden in modals, toasts, or generic banners when the user needs to fix one -input. - -Controls take the control radius; see the layout foundation for the two -radius roles. - -## Misuse - -- Never two competing primary buttons. -- Never destructive styling as attention-getting. -- Never expression color on a control — what you click stays on the base - roles everywhere. +Fields are quiet until active. The focus ring is guidance, not decoration. + +## Rules + +- Every action picks a rung from the five-rung ladder; no invented emphasis + levels. +- Fields render with a hairline border, muted placeholder, clear label, and + a focus ring when the user engages. +- Errors state facts next to the field that caused them — never hidden in + modals, toasts, or generic banners when the user needs to fix one input. +- Controls take the control radius; see the layout foundation for the two + radius roles. + +## Never + +- Never render two competing primary buttons — step one down the ladder. +- Never use destructive styling as attention-getting — destructive names a + destructive act, nothing else. +- Never put expression color on a control — what you click stays on the + base roles everywhere. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.layout.md b/packages/ghost/src/init-payloads/skeleton/foundation.layout.md index e7a2ff50..c91a8229 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.layout.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.layout.md @@ -8,41 +8,42 @@ until a human edits or confirms this chapter. ## Usage All layout is stacks. Rhythm comes from relationships between siblings, not -from isolated margins pasted onto whichever element was last touched. The -gap steps are exactly five: `--gap-xs` through `--gap-xl`. Choose the gap -that states the relationship; do not tune by single pixels to make a +from isolated margins pasted onto whichever element was last touched. Choose +the gap that states the relationship; do not tune by single pixels to make a screenshot pass. The default is column, medium gap. Columns are for almost everything — forms, cards, modal bodies, settings, page sections. Rows are conditional: controls, metadata lines, paired label/value moments. If a row wraps awkwardly, it wanted to be a column. Surface is the only way an element gets a background, border, radius, or -shadow. The vocabulary is closed: role, padding, radius, border, elevation. -The default surface is flat — no border, no shadow. We do not outline -everything to prove layout exists. - -The elevation tiers are exactly three: card, popover, modal. Elevation -implies hierarchy — a card sits in flow, a popover floats above it, a modal -interrupts the task. Pick the tier that matches the interaction. +shadow. The default surface is flat — no border, no shadow. We do not +outline everything to prove layout exists. Elevation implies hierarchy — a +card sits in flow, a popover floats above it, a modal interrupts the task. Borders are structural — inputs and overlays — not decorative frames around ordinary text. Use space, tone, and type hierarchy instead. -## Radius - -Open — ask the human; do not freehand. The fixed relationship: controls and -surfaces carry different radius roles, and the two never swap. Buttons and -inputs take `--radius-control`; cards and containers take -`--radius-surface`. One radius for what you click, one for what contains. -The open question: how round is a control, and how round is a surface? When -answered, restate this section as the brand's current answer. Until then, -choose provisional values and label them. - -## Misuse - -- No ad-hoc margins between siblings — they hide the rhythm. Change the - stack gap or split the stack. -- No custom shadows because a composition feels flat, and no modal gravity - on a routine card. Component shadows belong to the primitives that own - them. -- Never one radius everywhere by reflex. +## Rules + +- The gap steps are exactly five: `--gap-xs` through `--gap-xl`. +- The surface vocabulary is closed: role, padding, radius, border, + elevation. +- The elevation tiers are exactly three: card, popover, modal. Pick the + tier that matches the interaction. +- Buttons and inputs take `--radius-control`; cards and containers take + `--radius-surface`. The two never swap. +- Known gap — the radius values are unanswered. The fixed relationship: one + radius for what you click, one for what contains. The open question: how + round is a control, and how round is a surface? Ask the human; do not + invent values. Until answered, choose provisional values and label them. + When answered, restate this rule as the brand's current answer. + +## Never + +- Never add ad-hoc margins between siblings — they hide the rhythm; change + the stack gap or split the stack. +- Never add a custom shadow because a composition feels flat, and never give + modal gravity to a routine card — component shadows belong to the + primitives that own them. +- Never apply one radius everywhere by reflex — the control and surface + roles exist to differ. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.motion.md b/packages/ghost/src/init-payloads/skeleton/foundation.motion.md index 8759b8c4..3ea8dee8 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.motion.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.motion.md @@ -9,29 +9,32 @@ until a human edits or confirms this chapter. Motion is evidence of a state change. It confirms hover, press, reveal, collapse, entrance, exit, and spatial movement. It does not entertain. - -The entire vocabulary is three duration roles and one ease. Fast is for -hover and press. Normal is for reveals, fades, and small state changes. Slow -is reserved for spatial transitions where the user needs to understand -movement. Prefer opacity and small transforms. +Prefer opacity and small transforms. In editorial and marketing contexts, entrances may be staged — scroll reveals and section transitions are part of editorial rhythm, still built from the three durations and the one ease. In product UI the same staging is decoration. -## Character +## Rules -Open — ask the human; do not freehand. Motion and voice share one -temperature: however warm or cool the brand's words are, its movement -matches. See the voice foundation for the other half of this answer. The -open question here: what character does the one ease carry? When answered, -restate this section. Until then, use a quiet standard ease and label it -provisional. +- The entire vocabulary is three duration roles and one ease. +- Fast is for hover and press. Normal is for reveals, fades, and small + state changes. Slow is reserved for spatial transitions where the user + needs to understand movement. +- Known gap — the ease's character is unanswered. Motion and voice share + one temperature: however warm or cool the brand's words are, its movement + matches; see the voice foundation for the other half. The open question: + what character does the one ease carry? Ask the human; do not invent an + answer. Until answered, use a quiet standard ease and label it + provisional. When answered, restate this rule as the brand's current + answer. -## Misuse +## Never -- Nothing loops except explicit loading states. Decorative pulsing, +- Never loop anything except explicit loading states — decorative pulsing, floating, glowing, and attention-seeking keyframes are off-language. -- No novelty easings because a surface feels static. -- If removing an animation does not reduce comprehension, it was decoration. +- Never reach for a novelty easing because a surface feels static — fix + spacing, copy, and hierarchy first. +- Never keep an animation whose removal would not reduce comprehension — it + was decoration. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.type.md b/packages/ghost/src/init-payloads/skeleton/foundation.type.md index 1d1c5594..3b6b7d6a 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.type.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.type.md @@ -9,39 +9,43 @@ until a human edits or confirms this chapter. Hierarchy is a closed vocabulary, not a size slider. Every piece of text picks from a named set; anything outside the set is a broken primitive, not -a variant. +a variant. Variant names are jobs: display leads a rare editorial moment, +headline names a section, title anchors a card, dialog, or compact region, +body carries reading, label names structure — field labels, category tags, +metadata, compact status — and mono carries code and machine detail. -The text variants are exactly six: display, headline, title, body, label, -mono. Variant names are jobs. Display leads a rare editorial moment. -Headline names a section. Title anchors a card, dialog, or compact region. -Body carries reading. Label names structure — field labels, category tags, -metadata, compact status. Mono carries code and machine detail. - -The tones are exactly seven: default, muted, inverse, success, warning, -info, destructive. Tone is part of the message. Hierarchy starts with tone -and weight before size — a secondary note usually wants muted body or label, -not a smaller custom font. If prose needs emphasis, improve the sentence -before adding a style. +Tone is part of the message. Hierarchy starts with tone and weight before +size — a secondary note usually wants muted body or label, not a smaller +custom font. If prose needs emphasis, improve the sentence before adding a +style. The heading scale is editorial: it exists for pages composed outside the text variants — heroes and editorial moments. Product UI never mixes the two vocabularies in one view. A modal title is not a poster. -Balanced text is for headings and compact statements, never long body copy. - -## Typeface - -Open — ask the human; do not freehand. The fixed relationship: one typeface -is the voice of the interface, everywhere, with a mono partner only for -code, tool detail, and machine output. The open question: what typeface is -this brand's voice, what mono partners it, and what rhythm does the -editorial heading scale carry? When answered, restate this section as the -brand's current answer. Until then, choose a quiet provisional pair and -label it provisional. +## Rules -## Misuse - -- No faked hierarchy with arbitrary font sizes. Choose the variant that - matches the job. +- The text variants are exactly six: display, headline, title, body, label, + mono. +- The tones are exactly seven: default, muted, inverse, success, warning, + info, destructive. - Status tones appear only when the words carry that state meaning. -- Never a second display face to make a view feel branded. +- Balanced text is for headings and compact statements, never long body + copy. +- Known gap — the typeface is unanswered. The fixed relationship: one + typeface is the voice of the interface, everywhere, with a mono partner + only for code, tool detail, and machine output. The open question: what + typeface is this brand's voice, what mono partners it, and what rhythm + does the editorial heading scale carry? Ask the human; do not invent + values. Until answered, choose a quiet provisional pair and label it + provisional. When answered, restate this rule as the brand's current + answer. + +## Never + +- Never fake hierarchy with arbitrary font sizes — choose the variant that + matches the job. +- Never apply a status tone to words that do not carry that state meaning — + use default or muted instead. +- Never add a second display face to make a view feel branded — brand + expression comes from the one voice, scale, and composition. diff --git a/packages/ghost/src/init-payloads/skeleton/foundation.voice.md b/packages/ghost/src/init-payloads/skeleton/foundation.voice.md index ede2e9a8..5bf723cd 100644 --- a/packages/ghost/src/init-payloads/skeleton/foundation.voice.md +++ b/packages/ghost/src/init-payloads/skeleton/foundation.voice.md @@ -15,18 +15,20 @@ Voice and motion carry the same temperature: a brand whose copy is warm does not move coldly, and a brand whose copy is spare does not bounce. When you set one, check the other. -## Warmth +## Rules -Open — ask the human; do not freehand. The open question: how warm or cool -does this brand sound — and, by the shared temperature, how does it move? -See the motion foundation for the other half. When answered, restate this -section as the brand's current answer. Until then, write plainly and label -the tone provisional. +- Error and failure copy states the fact and the next step. +- Known gap — the warmth is unanswered. The open question: how warm or cool + does this brand sound — and, by the shared temperature, how does it move? + See the motion foundation for the other half. Ask the human; do not invent + an answer. Until answered, write plainly and label the tone provisional. + When answered, restate this rule as the brand's current answer. -## Misuse +## Never -- No exclamation points as a substitute for having something to say. -- No apologizing for the interface ("Oops!"). State the fact and the next +- Never use exclamation points as a substitute for having something to + say — state the fact. +- Never apologize for the interface ("Oops!") — state the fact and the next step. -- Never restate this section as answered when it was your own provisional - choice. +- Never restate an open question as answered when the answer was your own + provisional choice — label it provisional until a human confirms it. diff --git a/packages/ghost/src/init-payloads/skeleton/glossary.md b/packages/ghost/src/init-payloads/skeleton/glossary.md index ec74254b..11e180ee 100644 --- a/packages/ghost/src/init-payloads/skeleton/glossary.md +++ b/packages/ghost/src/init-payloads/skeleton/glossary.md @@ -1,39 +1,51 @@ --- kinds: + - name: standard - name: foundation - name: context - - name: cliche --- +# standard + +Shared guidance that is not specific to this brand. Every rule carries one of +two authority labels. An **Obligation** is a requirement brand preference +cannot waive — accessibility, safety, honesty, functional integrity; the +brand controls how it is expressed, not whether it holds. A **Default** is a +recommended starting position that protects unsteered work from generic model +behavior; explicit brand guidance in the cover, a foundation, or a matching +context may deliberately replace it. When a default with a paired check is +overridden, adapt or remove the check flag in the same change. + # foundation -The core elements — color, type, controls, layout, motion, voice — plus -composition, the rules for assembling them into a view. Each foundation -node is a chapter: usage rules that hold no matter what the brand values -turn out to be, the brand's open questions (unanswered in this starter, -marked as decisions only a human can make), and the chapter's misuse list. -Follow the usage rules as written. Never fill in an open value and present -it as the brand's. +The brand's load-bearing decisions — color, type, controls, layout, motion, +voice, and composition, the rules for assembling them into a view. Each +foundation node is a chapter: usage law that holds no matter what the brand +values turn out to be, the brand's open questions (unanswered in this +starter, marked as decisions only a human can make), and the chapter's +rejected moves. Follow the rules as written. Never fill in an open value and +present it as the brand's. A brand carries only the foundations its evidence +supports; these chapters are subjects, not mandatory slots. # context Where the defaults bend: a context names a situation — an AI conversation thread, a data-dense console, a transactional email — and states only what -inverts there. Read a context only when its situation matches the task. -Rules from the wrong context are contamination, not guidance. - -# cliche - -The defaults a generative model falls back on when no brand is steering it. -These are nobody's brand. Each entry pairs the tired move with the honest -replacement. Gather before styling anything greenfield; enforced by paired -checks at review. +inverts there. A situation may combine surface, channel, modality, audience, +or moment. Read a context only when its situation matches the task. Rules +from the wrong context are contamination, not guidance. --- +Node bodies in this package follow one shape: `## Usage` carries the +worldview and decision logic; `## Rules` carries observable requirements a +reviewer can assess in the finished artifact; `## Never` pairs each rejected +move with its replacement; an optional `## Skeleton` (always last) carries +literal opening structure. A node includes only the sections it needs. + The cover node (`brand.md`, declared in the manifest) is the one page always in context: what the brand is, its temperature, and the refusals only it -makes. The ghost skill's `authoring` recipe explains how to grow this skeleton -into a real ghost package: write the cover, answer each -chapter's open question, prune the cliches your chapters absorb, then add -materials and examples. +makes. The ghost skill's `authoring` recipe explains how to grow this starter +into a real ghost package: write the cover, answer each chapter's open +question, prune the shared defaults your chapters absorb, then add materials +and examples. diff --git a/packages/ghost/src/scan/check-scaffold.ts b/packages/ghost/src/scan/check-scaffold.ts index 86335871..5b4278bd 100644 --- a/packages/ghost/src/scan/check-scaffold.ts +++ b/packages/ghost/src/scan/check-scaffold.ts @@ -7,6 +7,14 @@ import { loadPayloadFile } from "./packed-payloads.js"; const EXAMPLE_CHECK_FILENAME = "example.md.example"; const MEDIAN_TELLS_FILENAME = "median-tells.md"; +/** + * Node ids that carry the measured model-defaults floor. The packed + * median-tells check references the current id; when a package still uses a + * legacy id, the references are rewritten so the scaffolded check resolves. + */ +const MODEL_DEFAULTS_NODE_ID = "standard.model-defaults"; +const LEGACY_MODEL_DEFAULTS_NODE_IDS = ["cliche.median"]; + const EXAMPLE_CHECK_CONTENT = `--- name: logo-clearspace-holds description: Logo usage preserves clearspace, lockup integrity, and glyph rules. @@ -39,15 +47,18 @@ export async function addChecksDir( const skipped: string[] = []; await mkdir(checksDir, { recursive: true }); - if (await exists(join(packageDir, "cliche.median.md"))) { - await writeFile( - join(checksDir, MEDIAN_TELLS_FILENAME), - await loadPayloadFile("median", MEDIAN_TELLS_FILENAME), - "utf-8", - ); + const modelDefaultsId = await findModelDefaultsNode(packageDir); + if (modelDefaultsId !== undefined) { + let check = await loadPayloadFile("median", MEDIAN_TELLS_FILENAME); + if (modelDefaultsId !== MODEL_DEFAULTS_NODE_ID) { + check = check.replaceAll(MODEL_DEFAULTS_NODE_ID, modelDefaultsId); + } + await writeFile(join(checksDir, MEDIAN_TELLS_FILENAME), check, "utf-8"); written.push(MEDIAN_TELLS_FILENAME); } else { - skipped.push(`${MEDIAN_TELLS_FILENAME} (no cliche.median node)`); + skipped.push( + `${MEDIAN_TELLS_FILENAME} (no ${MODEL_DEFAULTS_NODE_ID} node)`, + ); } await writeFile( @@ -64,6 +75,18 @@ export async function addChecksDir( }; } +async function findModelDefaultsNode( + packageDir: string, +): Promise { + for (const id of [ + MODEL_DEFAULTS_NODE_ID, + ...LEGACY_MODEL_DEFAULTS_NODE_IDS, + ]) { + if (await exists(join(packageDir, `${id}.md`))) return id; + } + return undefined; +} + async function exists(path: string): Promise { try { await access(path); diff --git a/packages/ghost/src/scan/templates.ts b/packages/ghost/src/scan/templates.ts index 828f246c..db0aed66 100644 --- a/packages/ghost/src/scan/templates.ts +++ b/packages/ghost/src/scan/templates.ts @@ -46,7 +46,7 @@ const SKELETON_FILE_ORDER = new Map( [ "glossary.md", "brand.md", - "cliche.median.md", + "standard.model-defaults.md", "foundation.composition.md", "foundation.color.md", "foundation.type.md", @@ -61,7 +61,7 @@ const SKELETON_FILE_ORDER = new Map( const SKELETON_TEMPLATE: GhostInitTemplate = { name: "skeleton", description: - "Naked skeleton: a brand cover, foundation chapters with open questions, and the cliche floor.", + "Naked skeleton: a brand cover, foundation chapters with open questions, and the shared model-defaults floor.", async files() { const skeletonFiles = [ ...(await loadPackedPayload("skeleton")), @@ -80,8 +80,8 @@ const SKELETON_TEMPLATE: GhostInitTemplate = { async function medianTemplateFile(): Promise { return { - relativePath: "cliche.median.md", - content: await loadPayloadFile("median", "cliche.median.md"), + relativePath: "standard.model-defaults.md", + content: await loadPayloadFile("median", "standard.model-defaults.md"), }; } diff --git a/packages/ghost/src/skill-bundle/references/ground.md b/packages/ghost/src/skill-bundle/references/ground.md index e02a109f..d42fbf36 100644 --- a/packages/ghost/src/skill-bundle/references/ground.md +++ b/packages/ghost/src/skill-bundle/references/ground.md @@ -41,8 +41,9 @@ Do not call it a pull packet or review packet. Keep it to three parts: -1. Up to five non-negotiables, each cited to a pulled node id. Anti-goals state - the positive replacement, never just the rejection. Include conditional +1. Up to five non-negotiables, each cited to a pulled node id. Guidance from a + `Never` section states the positive replacement, never just the rejection. + Include conditional guidance only when its stated situation actually holds, including guidance whose kind has scoped meaning in the glossary. 2. One readiness color: Green when the surface is covered by inspected concrete diff --git a/packages/ghost/src/skill-bundle/references/nodes.md b/packages/ghost/src/skill-bundle/references/nodes.md index a1340a82..0143ed7b 100644 --- a/packages/ghost/src/skill-bundle/references/nodes.md +++ b/packages/ghost/src/skill-bundle/references/nodes.md @@ -19,6 +19,27 @@ the guidance is for, never an audience. Read it alone: if it fits every brand or almost every task, it will not help selection. Put what to do and why in the body. +## The default body shape + +Unless the package's glossary declares another vocabulary, structure a node +body with three semantic sections, each a home for one type of claim, plus an +optional `## Skeleton` (always last): + +- `## Usage` — the worldview and decision logic that help the model handle + decisions the author did not anticipate. +- `## Rules` — observable requirements a reviewer can assess in the finished + artifact, one per bullet. Exact values where useful. Put a known gap inside + the rule it affects: name the gap and state that the model must not invent + a value. +- `## Never` — selective, high-value failure modes, each paired with its + replacement: "never X — instead Y." + +Route each claim to one home. Can a reviewer observe it in the artifact? +Rules. Does it reject a plausible move and name the replacement? Never. Does +it shape decisions not covered by either? Usage. None of these? Cut it. +A claim gets one home; do not repeat it as worldview, rationale, and rule. +Include only the sections the node needs; a short node may be plain prose. + Altitude lives in prose: - State universal guidance plainly. @@ -60,11 +81,13 @@ Everything bound is a template. Nothing bound is vibes. Use a Skeleton only when the opening structure itself must arrive verbatim; see [materials.md](materials.md). -## Anti-goals replace +## Never sections replace -A strong anti-goal says **not X; instead Y; recognize the switch by Z**. +A strong `Never` entry says **not X; instead Y; recognize the switch by Z**. Negation alone makes the rejected default more salient without committing the -replacement. +replacement. Put shared, measured model behavior in `standard.model-defaults`; +put a brand-specific rejection in the applicable foundation, context, or +pattern node. ```markdown --- @@ -78,7 +101,7 @@ SaaS dashboard behind. ``` Purge the rejected pattern from examples and starter structures. Use checks to -catch the hard regression; do not make anti-goal prose carry review alone. +catch the hard regression; do not make `Never` prose carry review alone. ## Explain examples @@ -124,7 +147,7 @@ canonical; human curation does. | --- | --- | | missing guidance | sharper `for` payload; universal guidance may belong on the cover | | inventing values | a material-backed node with exact vocabulary | -| producing generic output | replacement anti-goal plus a well-explained example | +| producing generic output | a reject-and-replace `Never` entry plus a well-explained example | | choosing the wrong structure | bound/open pattern and, when needed, a Skeleton | | crossing a hard line | invariant prose plus a review check | | applying guidance too broadly | a condition or reversal condition | @@ -136,4 +159,4 @@ canonical; human curation does. - Never make a node a container for observations or implementation inventory. - Never duplicate API documentation unless the API itself is the guidance. - Never use a broad `for` payload to compensate for unrelated decisions in one body. -- Never ship a blacklist-only anti-goal. +- Never ship a blacklist-only `Never` section; name the replacement. diff --git a/packages/ghost/src/skill-bundle/references/schema.md b/packages/ghost/src/skill-bundle/references/schema.md index ceb501fd..31a6ebaa 100644 --- a/packages/ghost/src/skill-bundle/references/schema.md +++ b/packages/ghost/src/skill-bundle/references/schema.md @@ -34,6 +34,11 @@ cover as a warning, and a cover body over 1500 bytes as a warning. node's id is its filename minus `.md`; its kind is the first dotted filename segment. A bare filename has no kind. Undeclared kind prefixes warn. +The starter vocabulary is `standard` (shared guidance, each rule an +Obligation or a replaceable Default), `foundation` (the brand's load-bearing +decisions), and `context` (what bends in a named situation). A package may +declare any vocabulary; the glossary is the only kind authority. + ## Nodes ```markdown diff --git a/packages/ghost/src/skill-bundle/references/steering-audit.md b/packages/ghost/src/skill-bundle/references/steering-audit.md index fc34f1e8..9fec17f3 100644 --- a/packages/ghost/src/skill-bundle/references/steering-audit.md +++ b/packages/ghost/src/skill-bundle/references/steering-audit.md @@ -38,9 +38,9 @@ Report first: | --- | --- | --- | --- | | Retrieval | strong / weak | `for` payloads, ids, cover | sharpen `for` payloads or move universal guidance to the cover | | Concreteness | strong / thin | materials, fenced examples, Skeletons | add concrete locators, examples, or opening structures | -| Anti-goals | present / missing / vague | `anti-goal.*`, review packet | write not-X-instead-Y replacements and material locators | +| Replacements | present / missing / vague | `Never` sections, `standard.model-defaults`, review packet | write not-X-instead-Y replacements and material locators | | Consistency | clean / conflicting | guidance vs concrete material | update or remove stale material | -| Stance | present / missing | cover, `principle.*` | write forced-choice principles | +| Stance | present / missing | cover, `foundation.*` | write forced-choice foundations | | Materials | present / missing | `materials`, inspect-pointers | point at real assets/components/tokens | | Patterns | bound-open / loose / missing | `pattern.*`, Skeletons | state applies / bound / open and add a Skeleton when opening structure matters | | Checks | covered / partial / missing | checks/, review packet | add checks for high-risk invariants | diff --git a/packages/ghost/test/cli.test.ts b/packages/ghost/test/cli.test.ts index c8e3152a..d33f3662 100644 --- a/packages/ghost/test/cli.test.ts +++ b/packages/ghost/test/cli.test.ts @@ -28,15 +28,15 @@ async function writeBareTestPackage(dir: string): Promise { ), writeFile( join(packageDir, "glossary.md"), - "---\nkinds:\n - name: principle\n - name: condition\n - name: anti-goal\n - name: cliche\n - name: asset\n - name: pattern\n---\n", + "---\nkinds:\n - name: principle\n - name: condition\n - name: anti-goal\n - name: standard\n - name: asset\n - name: pattern\n---\n", ), writeFile( join(packageDir, "index.md"), "---\nfor: Test package cover.\n---\n\nTest package.\n", ), writeFile( - join(packageDir, "cliche.median.md"), - "---\nfor: Test cliche floor.\n---\n\nAvoid generic defaults.\n", + join(packageDir, "standard.model-defaults.md"), + "---\nfor: Test shared defaults floor.\n---\n\nAvoid generic defaults.\n", ), ]); } @@ -162,7 +162,7 @@ describe("ghost CLI", () => { ".gitignore", "glossary.md", "brand.md", - "cliche.median.md", + "standard.model-defaults.md", "foundation.composition.md", "foundation.color.md", "foundation.type.md", @@ -174,10 +174,10 @@ describe("ghost CLI", () => { ]; async function expectSkeletonPackage(written: string[]) { - // Exact file inventory: no anti-goal.tells, no register.*, no materials/. + // Exact file inventory: no vessel-light body files, no materials/. expect([...written].sort()).toEqual([...SKELETON_FILES].sort()); - expect(written).not.toContain("anti-goal.tells.md"); - expect(written.some((f: string) => f.startsWith("register."))).toBe(false); + expect(written).not.toContain("foundation.tells.md"); + expect(written).not.toContain("context.email.md"); expect(written.some((f: string) => f.startsWith("materials/"))).toBe(false); // Core init is fingerprint-only: checks are opt-in via --with / checks init. expect(written).not.toContain("checks/example.md.example"); @@ -208,7 +208,7 @@ describe("ghost CLI", () => { ); expect(byId.get("brand")).toBeUndefined; expect(byId.has("brand")).toBe(false); - expect(byId.get("cliche.median")).toBe("cliche"); + expect(byId.get("standard.model-defaults")).toBe("standard"); for (const slug of [ "composition", "color", @@ -223,19 +223,17 @@ describe("ghost CLI", () => { expect(byId.get("context.conversation")).toBe("context"); // The median floor survives intact: prune header + rule anchors. - const median = await runCli(["pull", "cliche.median"], dir); + const median = await runCli(["pull", "standard.model-defaults"], dir); expect(median.code).toBe(0); - expect(median.stdout).toContain( - "This is the model's median, not your brand.", - ); + expect(median.stdout).toContain("the model's median, not your brand"); expect(median.stdout).toContain("### Side-stripe"); // The open questions ship unanswered and forbid freehanding. const layout = await runCli(["pull", "foundation.layout"], dir); expect(layout.code).toBe(0); expect(layout.stdout).toContain("has not yet reviewed"); - expect(layout.stdout).toContain("Open — ask the human"); - expect(layout.stdout).toContain("freehand"); + expect(layout.stdout).toContain("Known gap"); + expect(layout.stdout).toContain("invent values"); // No Vessel strings anywhere in the scaffolded package. const forbidden = [ @@ -302,10 +300,10 @@ describe("ghost CLI", () => { // The body is the inhabited package: corpus + tells + registers + // materials tree + its own checks. No .events tape. expect(written).toContain("manifest.yml"); - expect(written).toContain("anti-goal.median.md"); - expect(written).toContain("anti-goal.tells.md"); - expect(written).toContain("register.email.md"); - expect(written).toContain("signature.shape.md"); + expect(written).toContain("standard.model-defaults.md"); + expect(written).toContain("foundation.tells.md"); + expect(written).toContain("context.email.md"); + expect(written).toContain("foundation.shape.md"); expect(written).toContain("materials/tokens.css"); expect(written).toContain("materials/fonts/HKGrotesk-Regular.woff2"); expect(written).toContain("materials/examples/composition.form.html"); @@ -693,12 +691,14 @@ describe("ghost CLI", () => { const foundation = menu.kinds.find( (k: { name: string }) => k.name === "foundation", ); - expect(foundation.purpose).toContain("core elements"); + expect(foundation.purpose).toContain("load-bearing decisions"); // Markdown renders the same legend above the node list. const markdown = await runCli(["gather"], dir); expect(markdown.stdout).toContain("Kinds:"); - expect(markdown.stdout).toContain("- **foundation** — The core elements"); + expect(markdown.stdout).toContain( + "- **foundation** — The brand's load-bearing decisions", + ); // A missing glossary degrades to no legend, not an error. await rm(join(dir, ".ghost", "glossary.md")); @@ -2108,13 +2108,13 @@ describe("ghost CLI", () => { readFile(join(dir, ".ghost", "checks", "example.md.example"), "utf-8"), ).resolves.toContain("references:"); - // The live median check pairs with the skeleton's cliche.median node. + // The live median check pairs with the skeleton's standard.model-defaults node. const median = await readFile( join(dir, ".ghost", "checks", "median-tells.md"), "utf-8", ); - expect(median).toContain("cliche.median"); - expect(median).toContain("cliche.median > Hover-lift"); + expect(median).toContain("standard.model-defaults"); + expect(median).toContain("standard.model-defaults > Hover-lift"); expect(median).toContain("prefers-reduced-motion"); expect(median).toContain( "`ghost validate` warns; delete the flag and its reference together.", @@ -2127,7 +2127,7 @@ describe("ghost CLI", () => { expect(again.stderr).toContain("already exists"); // The scaffold validates cleanly on the default skeleton: median-tells - // references resolve against cliche.median. + // references resolve against standard.model-defaults. const validate = await runCli(["validate", "--format", "json"], dir); expect(validate.code).toBe(0); const report = JSON.parse(validate.stdout); @@ -2137,14 +2137,34 @@ describe("ghost CLI", () => { expect(unresolved).toEqual([]); }); + it("checks init rewrites median-tells references for a legacy cliche.median package", async () => { + await writeBareTestPackage(dir); + await rm(join(dir, ".ghost", "standard.model-defaults.md")); + await writeFile( + join(dir, ".ghost", "cliche.median.md"), + "---\nfor: Legacy cliche floor.\n---\n\nAvoid generic defaults.\n", + ); + + const add = await runCli(["checks", "init", "--format", "json"], dir); + expect(add.code).toBe(0); + expect(JSON.parse(add.stdout).written).toContain("median-tells.md"); + + const median = await readFile( + join(dir, ".ghost", "checks", "median-tells.md"), + "utf-8", + ); + expect(median).toContain("cliche.median > Hover-lift"); + expect(median).not.toContain("standard.model-defaults"); + }); + it("checks init skips median tells when the median node is absent", async () => { await writeBareTestPackage(dir); - await rm(join(dir, ".ghost", "cliche.median.md")); + await rm(join(dir, ".ghost", "standard.model-defaults.md")); const add = await runCli(["checks", "init"], dir); expect(add.code).toBe(0); expect(add.stdout).toContain( - "skipped median-tells.md (no cliche.median node)", + "skipped median-tells.md (no standard.model-defaults node)", ); await expect( @@ -2161,7 +2181,7 @@ describe("ghost CLI", () => { it("validate warns when a pruned median heading orphans its paired check", async () => { await runCli(["init"], dir); await runCli(["checks", "init"], dir); - const path = join(dir, ".ghost", "cliche.median.md"); + const path = join(dir, ".ghost", "standard.model-defaults.md"); const median = await readFile(path, "utf-8"); await writeFile( path, @@ -2176,7 +2196,9 @@ describe("ghost CLI", () => { expect.objectContaining({ severity: "warning", rule: "check-reference-heading-missing", - message: expect.stringContaining("cliche.median > Side-stripe"), + message: expect.stringContaining( + "standard.model-defaults > Side-stripe", + ), }), ]); expect(report.issues[0].message).toContain( diff --git a/packages/ghost/test/ghost-core/node-schema.test.ts b/packages/ghost/test/ghost-core/node-schema.test.ts index fe9156ae..a1ef0012 100644 --- a/packages/ghost/test/ghost-core/node-schema.test.ts +++ b/packages/ghost/test/ghost-core/node-schema.test.ts @@ -210,7 +210,7 @@ describe("ghost.node/v1 schema", () => { it("retains materials when serializing a parsed real fixture", () => { const raw = readFileSync( - resolve(REPO_ROOT, "packages/vessel-light/.ghost/signature.shape.md"), + resolve(REPO_ROOT, "packages/vessel-light/.ghost/foundation.shape.md"), "utf8", ); const { node: doc, report } = parseNode(raw); diff --git a/packages/vessel-light/.ghost/anti-goal.tells.md b/packages/vessel-light/.ghost/anti-goal.tells.md deleted file mode 100644 index d3e5bc6d..00000000 --- a/packages/vessel-light/.ghost/anti-goal.tells.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -for: Any work applying this brand's signature. ---- - -These are the near-misses of Vessel's own signature: outputs that got close -enough to cite the brand and still missed it. Each reject names its -replacement. When a tell fires, the fix direction is always toward the -token, the role, or deletion — never toward adding a compensating style on -top of the miss. - -Reject rectangular buttons. A rectangular button is not a variant; it is a -different design system → controls take `--radius-control`, the pill. - -Reject swapped radius roles: the surface radius on a control, the pill on a -surface, or 20px cells in a data table → `--radius-control` on what you -click, `--radius-surface` on what contains, small radius on data cells. - -Reject expression hues on interactive controls, an invented sixth hue, or a -tinted gray posing as monochrome → the five named expression hues at their -register's volume, and a true gray spine. An invented hue is not a bolder -Vessel; it is a different brand. - -Reject custom shadows that sit near a tier → the exact tier — card, popover, -or modal — chosen by interaction importance. - -Reject borrowing `--shadow-btn` for layout → component shadows belong to the -primitives that own them; layout elevation comes from the three tiers. - -Reject editorial display type in product UI, and product type timidity in -editorial → each register keeps its own vocabulary; a modal title is not a -poster, and a hero at product scale reads as timid. - -Reject borders around assistant text → the assistant speaks as plain text on -the page surface; use space, tone, and type hierarchy instead. - -Fidelity note: pills and fluid clamp display headings are medians Vessel -deliberately shares — their presence is fidelity, not drift. Convergence is -not the crime; surrendering the choice is. diff --git a/packages/vessel-light/.ghost/checks/conversation-grammar.md b/packages/vessel-light/.ghost/checks/conversation-grammar.md index 938e77ff..434f5ac7 100644 --- a/packages/vessel-light/.ghost/checks/conversation-grammar.md +++ b/packages/vessel-light/.ghost/checks/conversation-grammar.md @@ -3,8 +3,8 @@ name: Conversation grammar description: Flags conversation UI that breaks plain assistant text, prompt-input structure, primary-action discipline, or collapsed tool output. severity: high references: - - grammar.conversation - - grammar.surfaces + - context.conversation + - foundation.surfaces --- Apply this check to diffs that touch AI threads, agent consoles, prompt composers, chat messages, or tool-call rendering. diff --git a/packages/vessel-light/.ghost/checks/median-tells.md b/packages/vessel-light/.ghost/checks/median-tells.md index c15e44e5..46b3aabf 100644 --- a/packages/vessel-light/.ghost/checks/median-tells.md +++ b/packages/vessel-light/.ghost/checks/median-tells.md @@ -3,60 +3,60 @@ name: Median tells description: Flags the measured defaults of unsteered generation and current model-signature tells — hover-lift, default accents, unprompted dark theme, gradient text, frequency tells, and per-model signatures. severity: high references: - - anti-goal.median > Hover-lift - - anti-goal.median > Indigo accent - - anti-goal.median > Dark theme - - anti-goal.median > Gradients - - anti-goal.median > Glassmorphism - - anti-goal.median > Side-stripe - - anti-goal.median > Cream surface - - anti-goal.median > Chat bubbles - - anti-goal.median > Stock copy - - anti-goal.median > Celebration - - anti-goal.median > Hero metric - - anti-goal.median > Eyebrow kicker + - standard.model-defaults > Hover-lift + - standard.model-defaults > Indigo accent + - standard.model-defaults > Dark theme + - standard.model-defaults > Gradients + - standard.model-defaults > Glassmorphism + - standard.model-defaults > Side-stripe + - standard.model-defaults > Cream surface + - standard.model-defaults > Chat bubbles + - standard.model-defaults > Stock copy + - standard.model-defaults > Celebration + - standard.model-defaults > Hero metric + - standard.model-defaults > Eyebrow kicker --- This is Vessel's adaptation of the shared median check; the fidelity carve-outs below are the body's own. These flags target the measured convergence patterns of unsteered model generation, plus tells specific to individual models. Each -is mechanically detectable in a diff. Pruning a rule from `anti-goal.median` +is mechanically detectable in a diff. Pruning a rule from `standard.model-defaults` orphans its paired reference here — `ghost validate` warns; delete the flag and its reference together. Flag `transform` with `translateY` inside a `:hover` rule on cards, buttons, or list items, especially paired with a shadow increase. Hover confirmation in Vessel is color and background change, not lift. -(`anti-goal.median > Hover-lift`) +(`standard.model-defaults > Hover-lift`) Flag accent values in the indigo/blue/purple default family (`#4f46e5`, `#6366f1`, `#2563eb`, `#3b82f6`, `#8b5cf6`, and close neighbors) unless the diff shows the user asked for them. They are model defaults, not palette -members. (`anti-goal.median > Indigo accent`) +members. (`standard.model-defaults > Indigo accent`) Flag whole-page dark backgrounds when the ask did not request dark mode. Dark surfaces in Vessel are the editorial dark band or an explicit `.dark` -theme, never an unprompted default. (`anti-goal.median > Dark theme`) +theme, never an unprompted default. (`standard.model-defaults > Dark theme`) Flag `linear-gradient` or `radial-gradient` as page or section backgrounds, -and gradient-filled buttons. (`anti-goal.median > Gradients`) +and gradient-filled buttons. (`standard.model-defaults > Gradients`) Flag `backdrop-filter: blur` used for glassmorphism cards. -(`anti-goal.median > Glassmorphism`) +(`standard.model-defaults > Glassmorphism`) Flag `background-clip: text` (with or without the `-webkit-` prefix) paired with a gradient. Emphasis comes from weight or size in a single solid color. Flag a thick colored border on one side of an element (`border-left` or a `border-l-*` utility at 2px or more in a non-neutral color) while the other -sides stay thin. (`anti-goal.median > Side-stripe`) +sides stay thin. (`standard.model-defaults > Side-stripe`) Flag warm off-white page backgrounds in the cream/sand/beige band, and token names like `--cream`, `--sand`, `--parchment`, `--linen` introduced by the -diff. (`anti-goal.median > Cream surface`) +diff. (`standard.model-defaults > Cream surface`) Flag assistant messages rendered as bubbles with initials-circle avatars. -(`anti-goal.median > Chat bubbles`) +(`standard.model-defaults > Chat bubbles`) Flag emoji used as icons or imagery in interface chrome. Text labels carry meaning. @@ -64,22 +64,22 @@ meaning. Flag stock template copy in headings: "Simple, transparent pricing", "Welcome back", and interchangeable-with-a-competitor phrasing. Recommend copy that states what this product specifically does. -(`anti-goal.median > Stock copy`) +(`standard.model-defaults > Stock copy`) Flag exclamation-marked success copy, confetti language, and celebratory UI ("You did it!", "Awesome!"). Confirmation is quiet and factual. -(`anti-goal.median > Celebration`) +(`standard.model-defaults > Celebration`) Flag the hero-metric template — a big number, small label, and supporting stats as default proof — unless the metric shows real user data. Recommend evidence specific to the product, or nothing. -(`anti-goal.median > Hero metric`) +(`standard.model-defaults > Hero metric`) Frequency tells — the crime is repetition, not the move (advisory): Flag three or more uppercase, tracked eyebrow kickers above section headings in one page. One named kicker is voice; a kicker on every section is model -grammar. (`anti-goal.median > Eyebrow kicker`) +grammar. (`standard.model-defaults > Eyebrow kicker`) Flag five or more em-dashes in body copy in one view. diff --git a/packages/vessel-light/.ghost/checks/motion-restraint.md b/packages/vessel-light/.ghost/checks/motion-restraint.md index f32bdc89..13c8854b 100644 --- a/packages/vessel-light/.ghost/checks/motion-restraint.md +++ b/packages/vessel-light/.ghost/checks/motion-restraint.md @@ -3,8 +3,8 @@ name: Motion restraint description: Flags non-token motion, looping decoration, and keyframes that do not explain state change. severity: medium references: - - grammar.motion - - signature.temperature + - foundation.motion + - foundation.temperature --- Review changed transitions and animations for vocabulary first. diff --git a/packages/vessel-light/.ghost/checks/relationships.md b/packages/vessel-light/.ghost/checks/relationships.md index 1dfe6391..fdda02dc 100644 --- a/packages/vessel-light/.ghost/checks/relationships.md +++ b/packages/vessel-light/.ghost/checks/relationships.md @@ -3,10 +3,10 @@ name: Relationship discipline description: Flags structural violations of the grammar — emphasis-ladder breaks, sibling margins, decorative borders, nested cards. These rules survive any adaptation. severity: high references: - - grammar.hierarchy - - grammar.rhythm - - grammar.surfaces - - anti-goal.median > Nested cards + - foundation.hierarchy + - foundation.rhythm + - foundation.surfaces + - standard.model-defaults > Nested cards --- These assertions test relationships between token roles, not the values behind diff --git a/packages/vessel-light/.ghost/checks/shape-matches-job.md b/packages/vessel-light/.ghost/checks/shape-matches-job.md index b5ce82b6..9783164b 100644 --- a/packages/vessel-light/.ghost/checks/shape-matches-job.md +++ b/packages/vessel-light/.ghost/checks/shape-matches-job.md @@ -3,9 +3,9 @@ name: Shape matches the job description: Flags views composed for the wrong reader job — a form styled as a table, an announcement headline over a reviewable artifact, a live state presented as settled fact, a second primary, or a skeleton loader standing in for content. severity: high references: - - grammar.job - - grammar.deletion - - grammar.hierarchy + - foundation.job + - foundation.deletion + - foundation.hierarchy --- Apply this check to diffs that add or restructure a view. Classify the @@ -17,27 +17,27 @@ not fit. Flag a view whose job routes to one example but whose composition imitates another — a submit-and-done task laid out as a dashboard of cards, a records-scanning task rendered as prose sections, a stop-and-decide moment -inlined into the page instead of interrupting it. (`grammar.job`) +inlined into the page instead of interrupting it. (`foundation.job`) Flag an announcement headline in display or headline type above an artifact the reader will review — "Here's your draft", "Your plan is ready", "All set". The artifact opens the view; status lives in quiet metadata or -nowhere. (`grammar.job`) +nowhere. (`foundation.job`) Flag a live or ongoing value presented without its freshness — no timestamp or "updated" line in muted metadata near the figure. A moving value styled -as a settled fact misleads. (`grammar.job`) +as a settled fact misleads. (`foundation.job`) Flag recommendation or verdict framing where the decision belongs to the reader and the system cannot honestly rank the options. Present the -material; withhold the verdict. (`grammar.job`) +material; withhold the verdict. (`foundation.job`) Flag a second primary-variant action in a view, and flag a promoted primary on a view that honestly earns none — a steady status view, an open comparison. Zero is a valid spend of `--primary-budget`. -(`grammar.hierarchy`) +(`foundation.hierarchy`) Flag skeleton loaders, placeholder shimmer, or staged construction where content will land — and any element that cannot name what breaks if it is removed. The fix is demotion or deletion, never more emphasis. -(`grammar.deletion`) +(`foundation.deletion`) diff --git a/packages/vessel-light/.ghost/checks/values.md b/packages/vessel-light/.ghost/checks/values.md index 8587251c..ae6d8342 100644 --- a/packages/vessel-light/.ghost/checks/values.md +++ b/packages/vessel-light/.ghost/checks/values.md @@ -3,12 +3,12 @@ name: Value discipline description: Flags off-signature values — non-pill controls, off-palette hues, raw color literals, expression over budget. Adapting the dials rewrites this check alongside the signature nodes. severity: high references: - - signature.shape - - signature.palette - - grammar.color-roles - - register.data-density - - register.editorial - - register.email + - foundation.shape + - foundation.palette + - foundation.color-roles + - context.data-density + - context.editorial + - context.email --- These assertions test Vessel's current answers to the signature dials. A diff --git a/packages/vessel-light/.ghost/context.conversation.md b/packages/vessel-light/.ghost/context.conversation.md new file mode 100644 index 00000000..018eccb7 --- /dev/null +++ b/packages/vessel-light/.ghost/context.conversation.md @@ -0,0 +1,53 @@ +--- +for: Any AI thread, agent console, review assistant, or prompt composer. +materials: + - materials/examples/composition.conversation.html +--- + +## Usage + +Conversation UI is not chat cosplay. The assistant speaks on the page surface +as plain text: no bubble, no border, no fill. + +Assistant hierarchy comes from prose, spacing, and type. Wrapping assistant +messages in cards makes the system look defensive and wastes density. + +Tool calls are operational evidence. The prompt input is one bordered +surface; the textarea region stays empty of controls so writing remains the +focus. + +Use the conversation reference when building any AI thread, agent console, +review assistant, or prompt composer. It carries the grammar that agents most +often get wrong. + +## Rules + +- The assistant speaks on the page surface as plain text: no bubble, no + border, no fill. +- User turns are compact muted surfaces aligned right. They mark authorship + without turning the thread into alternating balloons. +- Tool calls collapse to a labeled one-line summary with status. Expand only + when the user asks for detail, then show mono content inside the disclosed + area. +- The prompt input is one bordered surface, with the textarea region empty + of controls. Attachments, model choices, secondary tools, and send live in + a single row below it. +- There is one primary send action. Stop and send are mutually exclusive + states of the same action area. + +## Never + +- Never wrap assistant messages in cards — it makes the system look + defensive and wastes density; instead draw assistant hierarchy from prose, + spacing, and type. +- Never turn the thread into alternating balloons — instead mark authorship + with compact muted user surfaces aligned right and plain-text assistant + turns. +- Never expand tool calls by default — instead collapse them to a labeled + one-line summary with status, expanding only when the user asks for + detail. +- Never put controls inside the textarea region — instead keep writing the + focus and place attachments, model choices, secondary tools, and send in a + single row below it. +- Never show stop and send as two competing primary buttons — instead treat + them as mutually exclusive states of the same action area. diff --git a/packages/vessel-light/.ghost/context.data-density.md b/packages/vessel-light/.ghost/context.data-density.md new file mode 100644 index 00000000..fb3eb17e --- /dev/null +++ b/packages/vessel-light/.ghost/context.data-density.md @@ -0,0 +1,51 @@ +--- +for: Tables, dashboards, logs, monitoring, or other data-dense consoles. +materials: + - materials/examples/composition.table.html + - materials/primitives.css +--- + +Condition: this node applies to data-dense surfaces — tables, dashboards, +transaction logs, and admin consoles. + +## Usage + +Data density inverts the settings-page rhythm. Operators need scan speed +before they need breathing room. + +Tight adjacency is meaning when rows compare against rows. A ragged amount +column is a broken instrument. Hierarchy is muted-first: data is the default +plane, labels are muted, and emphasis is rare enough to stay useful. + +Generous whitespace is drift here. Wasted density makes operators scroll. + +## Rules + +- Use the two smallest gap steps (`--gap-xs`, `--gap-sm`) where forms use + the medium step. +- Numerals are mono so columns align. +- Data is the default plane, labels are muted, and emphasis is rare enough + to stay useful. +- Data surfaces take the small radius. +- Status is a text label with at most one functional color family per view. +- Charts inside a console follow the product carve-out from the palette + signature: expression hues live inside the plot area only. Outside the + plot, the one-status-hue cap holds. +- Hairline borders carry rows. +- Hover confirmation uses the fast duration. It should acknowledge + targeting, not animate the table. + +## Never + +- Never give a table cell the surface radius — a surface-radius table cell + is costume; the signature radius (see foundation.shape) belongs on cards, + not cells — instead data surfaces take the small radius. +- Never let chart color leak into rows, badges, or headers — more status + color turns monitoring into confetti; instead keep expression hues inside + the plot area and hold the one-status-hue cap outside it. +- Never zebra stripe — instead let hairline borders carry rows with less + noise and more trust. +- Never animate the table on hover — instead acknowledge targeting with the + fast duration. +- Never spend generous whitespace here — it is drift, and wasted density + makes operators scroll; instead use the two smallest gap steps. diff --git a/packages/vessel-light/.ghost/context.editorial.md b/packages/vessel-light/.ghost/context.editorial.md new file mode 100644 index 00000000..18310eb6 --- /dev/null +++ b/packages/vessel-light/.ghost/context.editorial.md @@ -0,0 +1,53 @@ +--- +for: Heroes, marketing pages, pull quotes, or full-bleed dark moments. +materials: + - materials/examples/composition.editorial.html + - materials/tokens.css +--- + +Condition: this node applies to marketing, landing, and editorial surfaces — +never to product UI. + +## Usage + +Editorial surfaces invert the product type rules. Display scale is not an +indulgence here; it is the job. + +Pull quotes are visual punctuation for longform. They interrupt reading with +verdicts, not decoration. The tracked uppercase label is the kicker grammar. +It gives the page a hard editorial edge before the headline lands. + +Monochrome remains the spine, but editorial is the loud end of the expression +ladder (see foundation.palette). Commit to few colors at scale; variety is +where expression collapses into decoration. + +A hero built at product scale reads as timid. Timidity is drift here, exactly +as spectacle is drift in product UI. + +## Rules + +- Use the `--heading-display-*` tokens — size, line height, and weight — + when the words are the composition. The display tier belongs to this + context (see foundation.type for the scale's character). +- Section rhythm uses `--section-padding-vertical`. +- The sanctioned dramatic moment is the full-bleed dark section. Use the + `--surface-dark-*` family and let contrast carry the scene. +- Up to two expression hues per page as atmosphere — a tinted dark section, + a colored pull-quote accent, a duotone image moment. +- There is one primary action per page, and it keeps the control radius from + foundation.shape. + +## Never + +- Never stretch a product stack until it looks important — instead use + `--section-padding-vertical` for section rhythm. +- Never use pull quotes as decoration — instead let them interrupt reading + with a verdict; they are visual punctuation for longform. +- Never let variety carry the expression — that is where expression + collapses into decoration; instead commit to few colors at scale, up to + two hues per page. +- Never allow competing calls to action — editorial confidence does not + excuse them; instead keep one primary action per page. +- Never build a hero at product scale — it reads as timid, and timidity is + drift here exactly as spectacle is drift in product UI; instead let the + display scale do its job. diff --git a/packages/vessel-light/.ghost/context.email.md b/packages/vessel-light/.ghost/context.email.md new file mode 100644 index 00000000..85e99282 --- /dev/null +++ b/packages/vessel-light/.ghost/context.email.md @@ -0,0 +1,53 @@ +--- +for: Transactional email only. +materials: + - materials/examples/email.html +--- + +Condition: this node applies only to email. In any other medium, everything +below is a violation. + +## Usage + +Email clients do not honor the web contract. Custom properties, external +stylesheets, flex layout, and webfonts are unreliable materials here. + +Email inverts the material contract deliberately. Transcribe token values by +hand instead of referencing tokens — every hex below is a transcription of +`materials/tokens.css`, which stays the single source; if a token changes, +re-transcribe. Hardcoding hex here is fidelity, not drift. The check +exemption is the condition itself. + +Email fidelity is made from boring structure. The soul survives the body +swap: monochrome spine, quiet factual copy, one primary action, no +celebration. A receipt is allowed one degree of warmth; it is not allowed a +palette. + +## Rules + +- Use `#1a1a1a` (`--color-gray-900`, foreground) for text, `#999999` + (`--color-gray-500`, muted) for muted text, and `#e8e8e8` + (`--color-gray-200`, border) for borders when the email needs the Vessel + palette. +- Keep the surface radius on cards and the control radius on buttons (see + foundation.shape). The values survive even when the token names cannot + travel. +- Build with table layout, a 600px wrapper, predictable cells, and + bulletproof buttons. +- HK Grotesk falls back to the system stack. The voice must survive without + the font file. +- Email gets exactly one expressive moment: a header band or the figure that + matters, in one expression hue, transcribed by hand like every value + here — amber is `#f6b44a` (`--expression-1`). One moment, one hue. + +## Never + +- Never reference tokens directly in email — custom properties are + unreliable materials here; instead transcribe token values by hand from + `materials/tokens.css`, and re-transcribe if a token changes. +- Never add further color to compensate for email constraints — constraint + is not permission to perform; instead hold to the one expressive moment in + one hue. +- Never apply anything in this node outside email — in any other medium it + is a violation; instead use the token-referencing contract the other + contexts require. diff --git a/packages/vessel-light/.ghost/foundation.color-roles.md b/packages/vessel-light/.ghost/foundation.color-roles.md new file mode 100644 index 00000000..d4ca9a7d --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.color-roles.md @@ -0,0 +1,41 @@ +--- +for: Choosing or applying color. +materials: + - materials/tokens.css +--- + +## Usage + +The token file is the source of truth. An agent may combine tokens, but it +may not author around them. Raw color values are implementation detail, never +product language. + +If a container needs a color, it first needs a role. The status roles — +destructive, success, warning, info — exist only when meaning demands them. +None of them are brand accents. + +One view should not perform a color palette. If a status color is present, +let the rest of the view stay on the base roles. Richness beyond this is +context-gated: a closed expression set (`--expression-*`) exists, but its +size, members, and volume ladder are a brand answer — see the palette +foundation — and each context caps how loud they may be. + +## Rules + +- Author with semantic roles: `background`, `foreground`, `card`, `popover`, + `primary`, `secondary`, `muted`, `border`, `input`, `ring`, and the status + roles. +- Destructive means destructive or error. Success means success. Warning + means warning. Info means information. +- The constant that holds across every context: expression never touches + what you click. Buttons, inputs, and links stay on the base roles + everywhere. + +## Never + +- Never let a status role moonlight as atmosphere, in any context — status + colors keep their meanings everywhere; use the expression set at the + context's sanctioned volume instead. +- Never author around the tokens with raw color values — combine tokens. +- Never put a color on a colored control — a colored control is a different + design system; controls stay on the base roles. diff --git a/packages/vessel-light/.ghost/foundation.deletion.md b/packages/vessel-light/.ghost/foundation.deletion.md new file mode 100644 index 00000000..905bc29f --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.deletion.md @@ -0,0 +1,53 @@ +--- +for: The final pass over any composition, or whenever a view feels crowded, busy, or dressed up. +--- + +## Usage + +Restraint is not a mood; it is a test every element has to pass. Before a +view ships, run the pass: each label, divider, icon, caption, and helper +line must name what breaks if it is removed. If nothing breaks, delete it. +Convention is not a reason — "forms usually have this" keeps nothing. + +The test has a direction. When a view is crowded, or two elements compete +for attention, the fix is always demotion or deletion — never adding +emphasis to the loser. Raising the volume of one element to beat another +starts an arms race the composition always loses; removing the weaker claim +ends it. + +Do: a settings page whose every row survives the what-breaks question, with +one primary action and gaps doing the dividing. + +After everything deletable is gone, whatever remains is unmistakably the +point. + +## Rules + +- Every label, divider, icon, caption, and helper line must name what breaks + if it is removed; if nothing breaks, delete it. +- A view arrives settled: when content lands, it lands in its final + position — no reflow, no staggered construction, no element arriving late + to shift its neighbors. +- While work is genuinely pending, show the smallest true statement (a quiet + loading state, per the motion doctrine's loop exception) and nothing else. + +## Never + +- Never fix a crowded view or an attention contest by adding emphasis to the + loser — instead demote or delete the weaker claim. +- Never add a decorative divider where a gap step states the relationship — + instead let the gap do the dividing. +- Never add an icon that restates its adjacent label — instead delete the + icon. +- Never add a tooltip explaining an obvious control — instead delete the + tooltip. +- Never add onboarding chrome to routine views — instead delete it. +- Never dress a view in credibility costume — instead use a small factual + source or timestamp line; it beats a dashboard of gauges. +- Never ship skeleton loaders or placeholder shimmer promising content that + is not there — a fake page is hedging rendered as UI; instead show the + smallest true statement while work is pending. +- Never ship the settings page with icon-per-row decoration, a divider under + every group, a progress shimmer on load, and a bolded second CTA competing + for the eye — instead run the deletion pass until every row survives the + what-breaks question. diff --git a/packages/vessel-light/.ghost/foundation.hierarchy.md b/packages/vessel-light/.ghost/foundation.hierarchy.md new file mode 100644 index 00000000..cca36767 --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.hierarchy.md @@ -0,0 +1,73 @@ +--- +for: Any view containing text or actions. +materials: + - materials/primitives.css + - materials/examples/composition.form.html +--- + +## Usage + +Hierarchy is a closed vocabulary, not a size slider. Every piece of text and +every control picks from a named set; anything outside the set is a broken +primitive, not a variant. + +Variant names are jobs, not decoration. Display leads a rare editorial +moment. Headline names a section. Title anchors a card, dialog, or compact +region. Body carries reading. Label names structure — category tags, field +labels, bylines, metadata, compact status. Mono carries code and machine +detail. + +Tone is part of the message. Default is the main reading plane. Muted carries +secondary information. Inverse is for dark or primary surfaces. Hierarchy +starts with tone and weight before size — a secondary note usually wants +muted body or label, not a smaller custom font. If the prose needs emphasis, +improve the sentence before adding a style. + +Primary is for the action the screen exists to complete — if two buttons +both look primary, the hierarchy failed. And some views honestly earn none: a +steady status view or an open comparison has no action the screen exists to +complete, and promoting one anyway puts the system's thumb on the scale. +Zero is a valid spend. + +Destructive is a meaning, not a rung: it is rare, and it names the +destructive act directly. + +The focus ring is guidance, not decoration. Proportional figures in a data +column wobble, and a wobbling column reads as sloppy arithmetic. Mono already +carries tabular alignment for machine detail; the rule extends to any numeric +data in product UI. + +The form reference shows the intended decision order: stacked labels and +fields, compact helper text, one submit, secondary escape. Copy the decision +order before adjusting surface detail. + +## Rules + +- The text variants are exactly six: display, headline, title, body, label, + mono. +- The tones are exactly seven: default, muted, inverse, success, warning, + info, destructive. +- The four status tones appear only when the words have that state meaning. +- The control emphasis ladder is exactly five rungs: primary, secondary, + outline, ghost, link. +- At most one primary action per view (`--primary-budget: 1`); everything + else steps down the ladder. +- Fields are quiet until they are active: hairline border, muted + placeholder, clear label, and a focused ring when the user engages. +- Errors state facts next to the field that caused them. +- Figures that will be compared or scanned — amounts, counts, dates in + columns, any metric — set in tabular numerals (`--numeric-tabular`), with + the unit tight to the number. +- Balanced text is for headings and compact statements. + +## Never + +- Never fake hierarchy with arbitrary font sizes — instead choose the tier + that matches the job, then use tone, weight, and spacing for the rest. +- Never borrow destructive for urgency, emphasis, or brand heat — instead + reserve it for the destructive act it names. +- Never hide field errors in modals, toasts, or generic banners when the + user needs to fix one input — instead state the facts next to the field + that caused them. +- Never balance long body copy into jagged reading — instead reserve + balanced text for headings and compact statements. diff --git a/packages/vessel-light/.ghost/grammar.job.md b/packages/vessel-light/.ghost/foundation.job.md similarity index 51% rename from packages/vessel-light/.ghost/grammar.job.md rename to packages/vessel-light/.ghost/foundation.job.md index fc6d3d51..04059bd0 100644 --- a/packages/vessel-light/.ghost/grammar.job.md +++ b/packages/vessel-light/.ghost/foundation.job.md @@ -9,6 +9,8 @@ materials: - materials/examples/email.html --- +## Usage + Before any structure exists, name what the reader does next with the view. The topic never picks the shape; the reader's job does. A billing question is not "a billing page" — it is a form to fill, a table to scan, a decision @@ -16,39 +18,48 @@ to confirm, or a thread to continue, and each of those is a different composition. A view built for the wrong job does not merely look off; it misleads. -The jobs and where they route: - -- **Fill something in** — settings, signup, any submit-and-done task → the - form example. Stacked labels and fields, one submit, secondary escape. -- **Scan and compare records** — lists, audits, anything with rows and - figures → the table example, in the data-density register. -- **Read a narrative** — a landing page, an announcement, a story → the - editorial example, in the editorial register. -- **Stop and decide** — a confirmation, a destructive act, a choice that - interrupts the task → the overlay example. The decision is the view. -- **Converse with the system** — an AI thread, an agent console, a prompt - composer → the conversation example. -- **Be notified elsewhere** — receipts, alerts, anything that lands in an - inbox → the email example, in the email register. - Read the request's verbs, not its nouns: "edit / set up / submit" routes to form; "compare / list / review the records" to table; "announce / tell the story" to editorial; "confirm / are you sure" to overlay; "ask / draft with me" to conversation. When two jobs fold into one request, build for the job the reader performs on this view, now — the other job is the next view. When -a job fits none of these, compose from the grammar and say so; do not force -the nearest example. +a job fits none of these, compose from the grammar and say so. -Three shapes lie about the job, and we refuse them everywhere: +Three shapes lie about the job, and we refuse them everywhere; they are +listed under Never. -- A view whose content is an artifact the reader will review — a draft, a - filled form, a generated plan — leads with the artifact, never with an - announcement headline. "Here's your draft" in display type demotes the - actual deliverable; the artifact sitting there with its actions is the - statement that it is ready. +## Rules + +- **Fill something in** — settings, signup, any submit-and-done task — + routes to the form example: stacked labels and fields, one submit, + secondary escape. +- **Scan and compare records** — lists, audits, anything with rows and + figures — routes to the table example, in the data-density context. +- **Read a narrative** — a landing page, an announcement, a story — routes + to the editorial example, in the editorial context. +- **Stop and decide** — a confirmation, a destructive act, a choice that + interrupts the task — routes to the overlay example. The decision is the + view. +- **Converse with the system** — an AI thread, an agent console, a prompt + composer — routes to the conversation example. +- **Be notified elsewhere** — receipts, alerts, anything that lands in an + inbox — routes to the email example, in the email context. - A view reporting a live or ongoing state carries its freshness — "updated - 2 min ago" — in quiet metadata. Presenting a moving value as a settled - fact is a small lie the reader eventually catches. -- A view where the decision belongs to the reader presents the material and - withholds the verdict. Recommendation framing on a choice the system - cannot honestly make is manufactured confidence, and it reads that way. + 2 min ago" — in quiet metadata. + +## Never + +- Never force the nearest example when a job fits none of these — instead + compose from the grammar and say so. +- Never lead with an announcement headline when the view's content is an + artifact the reader will review — a draft, a filled form, a generated + plan. "Here's your draft" in display type demotes the actual deliverable — + instead lead with the artifact; the artifact sitting there with its + actions is the statement that it is ready. +- Never present a moving value as a settled fact — it is a small lie the + reader eventually catches; instead carry the view's freshness — "updated + 2 min ago" — in quiet metadata. +- Never put recommendation framing on a choice the system cannot honestly + make — it is manufactured confidence, and it reads that way; instead + present the material and withhold the verdict when the decision belongs to + the reader. diff --git a/packages/vessel-light/.ghost/foundation.motion.md b/packages/vessel-light/.ghost/foundation.motion.md new file mode 100644 index 00000000..ba6be2ad --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.motion.md @@ -0,0 +1,46 @@ +--- +for: Any transition, animation, or hover treatment. +materials: + - materials/tokens.css +--- + +## Usage + +Motion is evidence of a state change. It confirms hover, press, reveal, +collapse, entrance, exit, and spatial movement. It does not entertain. + +The entire vocabulary is three duration roles and one ease. The ease's +character is a brand answer — see the temperature signature. + +Prefer opacity and small transform changes. If removing an animation does not +reduce comprehension, the animation was decoration. + +Condition: marketing and editorial surfaces may stage entrances — scroll +reveals and section transitions are part of editorial rhythm, still built +from the three durations and the one ease. In product UI the same staging is +decoration. + +## Rules + +- The vocabulary is exactly three duration roles and one ease. +- Fast is for hover and press. +- Normal is for reveals, fades, and small state changes. +- Slow is reserved for spatial transitions where the user needs to + understand movement. +- Nothing loops except explicit loading states. A spinner may continue + because work continues. + +## Never + +- Never introduce novelty easings because a surface feels static — instead + stay on the one ease; its character is a brand answer (see the temperature + signature). +- Never ship decorative pulsing, floating, glowing, or attention-seeking + keyframes — they are off-language; instead loop nothing except explicit + loading states. +- Never keep an animation whose removal does not reduce comprehension — it + was decoration; instead delete it and prefer opacity and small transform + changes where motion earns its place. +- Never stage entrances in product UI — that staging is decoration there; + instead reserve scroll reveals and section transitions for marketing and + editorial surfaces. diff --git a/packages/vessel-light/.ghost/foundation.palette.md b/packages/vessel-light/.ghost/foundation.palette.md new file mode 100644 index 00000000..9b837d3b --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.palette.md @@ -0,0 +1,57 @@ +--- +for: Color beyond the base roles, in any register. +materials: + - materials/tokens.css +--- + +## Usage + +This is Vessel's answer to palette — it stands until you replace it. + +The relationship is fixed: one brand at different volumes. A monochrome spine +is the default atmosphere in every medium — calm, legible, and resistant to +novelty — and a closed expression set supplies the atmosphere, with volume +set by context, never by taste. + +Expressive color outside the closed set is not expression; it is another +brand. An invented hue is not a bolder Vessel; it is a different brand. + +Loudness comes from commitment to few colors at scale, not variety. + +To adapt: edit the `--expression-*` values (and the gray ramp, if the spine +changes) in `materials/tokens.css` and restate this node's current answer. +The ladder — a quiet spine, a closed hue set, context-gated volume — is the +part worth keeping. + +## Rules + +- The base palette is monochrome gray. +- The expression palette is five named hues and only these — amber + (`--expression-1`), periwinkle (`--expression-2`), clay + (`--expression-3`), orchid (`--expression-4`), sage (`--expression-5`). +- Product UI: expression lives only in data visualization. A chart may use + the hues; the interface around it stays monochrome. +- Data-dense consoles: one hue family may mark status. Nothing atmospheric. + Charts inside a console keep the product carve-out — hues stay inside the + plot area and never leak into rows, badges, or headers. +- Email: exactly one expressive moment per message — a header band or the + figure that matters. One hue, quiet everywhere else. +- Editorial: expression is sanctioned atmosphere — a tinted dark section, a + colored pull-quote accent, a duotone moment. Never more than two hues per + page. +- Two constants that outrank the ladder: expression never touches what you + click — buttons, inputs, and links stay monochrome in every context — and + the status roles are not expression; they keep their meanings everywhere. + +## Never + +- Never use expressive color outside the five named hues — an invented hue + is not a bolder Vessel; it is a different brand; instead stay inside the + `--expression-*` set at the context's sanctioned volume. +- Never put expression on what you click — instead keep buttons, inputs, and + links monochrome in every context. +- Never let the status roles moonlight as atmosphere — they are not + expression; instead keep their meanings everywhere and draw atmosphere + from the expression set. +- Never set expression volume by taste — instead set it by context, per the + ladder above. diff --git a/packages/vessel-light/.ghost/foundation.rhythm.md b/packages/vessel-light/.ghost/foundation.rhythm.md new file mode 100644 index 00000000..742889f3 --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.rhythm.md @@ -0,0 +1,39 @@ +--- +for: Laying out any view. +materials: + - materials/primitives.css +--- + +## Usage + +All layout is stacks. Rhythm comes from relationships between siblings, not +from isolated margins pasted onto whichever element was last touched. + +Choose the gap that states the relationship. Ad-hoc margins between siblings +are forbidden because they hide the rhythm. When spacing feels wrong, change +the stack gap or split the stack. + +Columns are for almost everything: forms, cards, message lists, modal bodies, +settings, empty states, and page sections. A column lets the user scan. + +Rows are conditional. Use them for controls, metadata lines, compact status, +and paired label/value moments. If a row starts wrapping awkwardly, it +probably wanted to be a column. + +## Rules + +- The gap steps are exactly five: `--gap-xs`, `--gap-sm`, `--gap-md`, + `--gap-lg`, `--gap-xl`. +- The default is column, medium gap, stretch alignment, start justification. + That is the ordinary reading rhythm. +- Rows are for controls, metadata lines, compact status, and paired + label/value moments. + +## Never + +- Never place ad-hoc margins between siblings — they hide the rhythm; + instead change the stack gap or split the stack. +- Never tune by single pixels to make a screenshot pass — instead choose the + gap step that states the relationship. +- Never keep a row that starts wrapping awkwardly — it probably wanted to be + a column; instead convert it. diff --git a/packages/vessel-light/.ghost/foundation.shape.md b/packages/vessel-light/.ghost/foundation.shape.md new file mode 100644 index 00000000..3a4949a8 --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.shape.md @@ -0,0 +1,39 @@ +--- +for: Choosing or implementing any radius or corner treatment. +materials: + - materials/tokens.css + - materials/primitives.css +--- + +## Usage + +This is Vessel's answer to shape — it stands until you replace it. + +The relationship is fixed: controls and surfaces carry different radius +roles, and the two never swap. The pill is Vessel's most visible control +signature; a rectangular button is not a variant, it is a different design +system. + +20px is a considered position: rounder than default shadcn, but not bubbly. +Use the radius system as restraint, not as decoration. + +To adapt: edit the `--radius-control` and `--radius-surface` values in +`materials/tokens.css` and restate this node's current answer. The +role split — one radius for what you click, one for what contains — is the +part worth keeping. + +## Rules + +- Buttons and text inputs use `--radius-control`; cards and other surfaces + use `--radius-surface`. +- Controls are pills (`--radius-control: 999px`). +- Surfaces take the 20px signature radius (`--radius-surface: 20px`). + +## Never + +- Never give a button the surface radius — a rectangular button is the + fastest tell that the output is not Vessel; instead controls take + `--radius-control`, the pill. +- Never swap the two radius roles — instead keep `--radius-control` on what + you click and `--radius-surface` on what contains. +- Never use the radius system as decoration — instead use it as restraint. diff --git a/packages/vessel-light/.ghost/foundation.surfaces.md b/packages/vessel-light/.ghost/foundation.surfaces.md new file mode 100644 index 00000000..7826bb47 --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.surfaces.md @@ -0,0 +1,45 @@ +--- +for: Any card, popover, modal, dialog, scrim, or bordered container. +materials: + - materials/primitives.css + - materials/examples/composition.overlay.html +--- + +## Usage + +Surface is the only way an element gets a background, border, radius, or +shadow. If a container needs visual treatment, it first needs a surface role. + +The vocabulary is closed: role, padding, radius, border, and elevation. These +axes create enough range for page flow, cards, popovers, muted blocks, +accents, and dark moments without inventing one-off boxes. + +Elevation implies hierarchy — a card sits in the document flow, a popover +floats above the flow, a modal interrupts the task. Pick the tier that +matches the interaction. + +Borders are structural, especially for inputs and overlays. They are not +decorative frames around ordinary text. + +The overlay reference shows the interrupting end of the system: background +scrim, modal radius, modal shadow, compact header, clear footer. Copy its +hierarchy when a user must stop and decide. + +## Rules + +- The default surface is flat: no border, no shadow. We do not outline + everything to prove layout exists. +- The elevation tiers are exactly three: card, popover, modal. +- Component shadows belong to the primitives that own them. +- Borders are structural, especially for inputs and overlays. + +## Never + +- Never write a custom shadow because the composition feels flat — instead + pick the elevation tier that matches the interaction. +- Never pair a low tier with an interrupting role or give a routine card + modal gravity — instead match the tier to the interaction's hierarchy. +- Never borrow component shadows for layout — they belong to the primitives + that own them; instead take layout elevation from the three tiers. +- Never use borders as decoration, especially around assistant text — + instead use space, tone, and type hierarchy. diff --git a/packages/vessel-light/.ghost/foundation.tells.md b/packages/vessel-light/.ghost/foundation.tells.md new file mode 100644 index 00000000..a67427db --- /dev/null +++ b/packages/vessel-light/.ghost/foundation.tells.md @@ -0,0 +1,39 @@ +--- +for: Any work applying this brand's signature. +--- + +## Usage + +These are the near-misses of Vessel's own signature: outputs that got close +enough to cite the brand and still missed it. Each reject names its +replacement. When a tell fires, the fix direction is always toward the +token, the role, or deletion — never toward adding a compensating style on +top of the miss. + +Fidelity note: pills and fluid clamp display headings are medians Vessel +deliberately shares — their presence is fidelity, not drift. Convergence is +not the crime; surrendering the choice is. + +## Never + +- Never ship rectangular buttons. A rectangular button is not a variant; it + is a different design system — instead controls take `--radius-control`, + the pill. +- Never swap radius roles: the surface radius on a control, the pill on a + surface, or 20px cells in a data table — instead `--radius-control` on + what you click, `--radius-surface` on what contains, small radius on data + cells. +- Never put expression hues on interactive controls, invent a sixth hue, or + pass off a tinted gray as monochrome — instead use the five named + expression hues at the context's sanctioned volume, and a true gray spine. + An invented hue is not a bolder Vessel; it is a different brand. +- Never write custom shadows that sit near a tier — instead use the exact + tier — card, popover, or modal — chosen by interaction importance. +- Never borrow `--shadow-btn` for layout — component shadows belong to the + primitives that own them; instead take layout elevation from the three + tiers. +- Never use editorial display type in product UI, or product type timidity + in editorial — instead each context keeps its own vocabulary; a modal + title is not a poster, and a hero at product scale reads as timid. +- Never put borders around assistant text — instead the assistant speaks as + plain text on the page surface; use space, tone, and type hierarchy. diff --git a/packages/vessel-light/.ghost/signature.temperature.md b/packages/vessel-light/.ghost/foundation.temperature.md similarity index 50% rename from packages/vessel-light/.ghost/signature.temperature.md rename to packages/vessel-light/.ghost/foundation.temperature.md index 92e67053..525e4715 100644 --- a/packages/vessel-light/.ghost/signature.temperature.md +++ b/packages/vessel-light/.ghost/foundation.temperature.md @@ -4,19 +4,15 @@ materials: - materials/tokens.css --- +## Usage + This is Vessel's answer to temperature — it stands until you replace it. The relationship is fixed: voice and motion carry the same temperature. Copy states what happened, what is possible, or what the user must decide — it does not perform personality — and motion confirms rather than entertains. -Vessel's current answer in words: product UI is the default register — -factual, quiet, sentence case, and free of applause. No exclamation marks; -celebration is not reassurance. Confirmations are quiet: "Changes saved", -never "Awesome!" The system should sound reliable, not excited by basic -competence. Errors name the problem and the fix in the place where the user -can act; a vague failure banner is evasion. Buttons name the act — "Delete -account", "Save changes", "Invite member", not "Do it" or "Let's go". +The system should sound reliable, not excited by basic competence. Destructive copy is direct because the risk is direct; softening the verb makes the interface less honest. Vessel never celebrates at the user. Trust comes from precision, restraint, and naming the truth plainly. @@ -28,12 +24,7 @@ card expired". "You might want to consider updating your billing info" → "Update your billing info". Strip the apology, the applause, and the hedge; keep the fact and the fix. -Vessel's current answer in motion: three durations — `--duration-fast`, -`--duration-normal`, `--duration-slow` — and one ease, `--ease-spring`, which -should feel resolved without feeling elastic. Hover confirms with color and -background shifts at the fast duration, not levitation. - -Condition: editorial surfaces switch register. Short declarative confident +Condition: editorial surfaces switch context. Short declarative confident fragments are correct in heroes and section headings. That same editorial confidence is wrong in a settings form — product copy serves the task before it serves the voice. Email copy uses the product voice: factual, not @@ -42,3 +33,30 @@ campaign-like. To adapt: rewrite this node's current answers in your brand's voice and edit the duration and ease values in `materials/tokens.css`. The coupling — words and motion sharing one temperature — is the part worth keeping. + +## Rules + +- Product UI is the default context — factual, quiet, sentence case, and + free of applause. +- No exclamation marks; celebration is not reassurance. +- Confirmations are quiet: "Changes saved", never "Awesome!" +- Errors name the problem and the fix in the place where the user can act. +- Buttons name the act — "Delete account", "Save changes", "Invite member", + not "Do it" or "Let's go". +- Motion carries three durations — `--duration-fast`, `--duration-normal`, + `--duration-slow` — and one ease, `--ease-spring`, which should feel + resolved without feeling elastic. +- Hover confirms with color and background shifts at the fast duration. + +## Never + +- Never ship a vague failure banner — it is evasion; instead name the + problem and the fix in the place where the user can act. +- Never soften the verb on destructive copy — it makes the interface less + honest; instead name the destructive act directly. +- Never celebrate at the user — instead let trust come from precision, + restraint, and naming the truth plainly. +- Never confirm hover with levitation — instead confirm with color and + background shifts at the fast duration. +- Never carry editorial confidence into a settings form — instead let + product copy serve the task before it serves the voice. diff --git a/packages/vessel-light/.ghost/signature.type.md b/packages/vessel-light/.ghost/foundation.type.md similarity index 54% rename from packages/vessel-light/.ghost/signature.type.md rename to packages/vessel-light/.ghost/foundation.type.md index 14aab8c6..e1a50906 100644 --- a/packages/vessel-light/.ghost/signature.type.md +++ b/packages/vessel-light/.ghost/foundation.type.md @@ -11,6 +11,8 @@ materials: - materials/fonts/HKGrotesk-SemiBold.woff2 --- +## Usage + This is Vessel's answer to type — it stands until you replace it. The relationship is fixed: one typeface is the voice of the interface, @@ -19,19 +21,9 @@ output. The heading scale is editorial — its tokens exist for pages composed outside the text variants — and product UI never mixes the two vocabularies in one view. -Vessel's current answer: HK Grotesk. vessel-light ships it as its embedded -voice — here, `tokens.css` is canonical. - -The heading scale is editorial: display, section, and sub each carry their -own rhythm (`--heading-display-*`, `--heading-section-*`, `--heading-sub-*`). Display sizes use tight tracking and sub-1 line heights because the words -behave like composition, not paragraph text. Card-level headings inside -editorial features use the product `title` variant — the scale does not reach -below sub. - -Labels are small, semibold, and tracked wide. They should feel precise, never -loud. Body text keeps the established reading sizes with relaxed line height; -do not shrink important prose until it becomes legal copy. +behave like composition, not paragraph text. Labels should feel precise, +never loud. Condition: display and section scale are for editorial or hero moments. Product UI lives in headline, title, body, label, and mono. A modal title is @@ -41,3 +33,26 @@ To adapt: swap the font files, edit `--font-sans`, `--font-mono`, and the heading tokens in `materials/tokens.css`, and restate this node's current answer. The one-voice rule and the editorial/product vocabulary split are the parts worth keeping. + +## Rules + +- The typeface is HK Grotesk. vessel-light ships it as its embedded voice — + here, `tokens.css` is canonical. +- The heading scale is editorial: display, section, and sub each carry their + own rhythm (`--heading-display-*`, `--heading-section-*`, + `--heading-sub-*`). +- Card-level headings inside editorial features use the product `title` + variant — the scale does not reach below sub. +- Labels are small, semibold, and tracked wide. +- Body text keeps the established reading sizes with relaxed line height. +- Product UI lives in headline, title, body, label, and mono. + +## Never + +- Never mix the editorial heading scale and the product text variants in one + view — instead keep each vocabulary in its own context; a modal title is + not a poster. +- Never shrink important prose until it becomes legal copy — instead keep + body text at the established reading sizes with relaxed line height. +- Never make labels loud — instead keep them small, semibold, and tracked + wide so they feel precise. diff --git a/packages/vessel-light/.ghost/glossary.md b/packages/vessel-light/.ghost/glossary.md index d524df21..27bcc585 100644 --- a/packages/vessel-light/.ghost/glossary.md +++ b/packages/vessel-light/.ghost/glossary.md @@ -1,44 +1,41 @@ --- kinds: - - name: grammar - - name: signature - - name: register - - name: anti-goal + - name: standard + - name: foundation + - name: context --- -# grammar +# standard -The brand's decision logic: closed sets, role vocabularies, and assembly -rules, stated in token roles and never in literal values. Grammar survives an -adaptation unchanged — adapt the package, swap every value, and these nodes still -hold. Gather grammar before inventing structure. +Shared guidance that is not specific to this brand. Every rule carries one of +two authority labels. An **Obligation** is a requirement brand preference +cannot waive. A **Default** is a starting position that protects unsteered +work from generic model behavior; explicit brand guidance may deliberately +replace it. When a default with a paired check is overridden, adapt or remove +the check flag in the same change. -# signature +# foundation -The dials: the choices that make this brand this brand, each stated as a -current answer that stands until you replace it. Signature nodes name real -values because they are the values — on adaptation, edit the token roles in -`materials/tokens.css` and restate the node. Gather signature before setting -any value a dial governs. +Vessel's load-bearing decisions: the closed sets, role vocabularies, and +assembly rules, plus the answered identity dials — shape, palette, type, +temperature — each stating Vessel's current answer, which stands until you +replace it. The role logic survives an adaptation unchanged; the answered +values do not — on adaptation, edit the token roles in `materials/tokens.css` +and restate each answer. Gather foundations before inventing structure or +setting any value a dial governs. -# register +# context -A condition-scoped contract: a situation — data-dense consoles, editorial -pages, transactional email — where parts of the default rules invert. Each -register names its condition first. Truths from the wrong register are -poison, not context; gather a register only when its condition matches the +A named situation — data-dense consoles, editorial pages, transactional +email, conversation UI — where parts of the default rules bend or invert. +Each context names its condition first. Truths from the wrong context are +poison, not guidance; read a context only when its condition matches the task. -# anti-goal - -What this package refuses, each reject paired with its replacement. -`anti-goal.median` is the model's floor, not the brand's taste; -`anti-goal.tells` is the near-miss map of this brand's own signature. Gather -anti-goals before styling anything greenfield. - --- To adapt this package to another brand, follow the starter section of the -`authoring` recipe in the ghost skill bundle. In short: new manifest id, prune the -median rules your brand legitimately violates, answer each signature node, -edit `materials/tokens.css`, regenerate the examples, rewrite the tells. +`authoring` recipe in the ghost skill bundle. In short: new manifest id, prune +the shared model-defaults rules your brand legitimately violates, restate each +answered foundation dial, edit `materials/tokens.css`, regenerate the +examples, rewrite the tells. diff --git a/packages/vessel-light/.ghost/grammar.color-roles.md b/packages/vessel-light/.ghost/grammar.color-roles.md deleted file mode 100644 index 29d0280c..00000000 --- a/packages/vessel-light/.ghost/grammar.color-roles.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -for: Choosing or applying color. -materials: - - materials/tokens.css ---- - -The token file is the source of truth. An agent may combine tokens, but it -may not author around them. Raw color values are implementation detail, never -product language. - -Author with semantic roles: `background`, `foreground`, `card`, `popover`, -`primary`, `secondary`, `muted`, `border`, `input`, `ring`, and the status -roles. If a container needs a color, it first needs a role. - -The status roles — destructive, success, warning, info — exist only when -meaning demands them. Destructive means destructive or error. Success means -success. Warning means warning. Info means information. None of them are -brand accents, and they never moonlight as atmosphere, in any register. - -One view should not perform a color palette. If a status color is present, -let the rest of the view stay on the base roles. Richness beyond this is -register-gated: a closed expression set (`--expression-*`) exists, but its -size, members, and volume ladder are a brand answer — see the palette -signature — and each register caps how loud they may be. - -The constant that holds across every register: expression never touches what -you click. Buttons, inputs, and links stay on the base roles everywhere. A -colored control is a different design system. diff --git a/packages/vessel-light/.ghost/grammar.conversation.md b/packages/vessel-light/.ghost/grammar.conversation.md deleted file mode 100644 index 253b5ba2..00000000 --- a/packages/vessel-light/.ghost/grammar.conversation.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -for: Any AI thread, agent console, review assistant, or prompt composer. -materials: - - materials/examples/composition.conversation.html ---- - -Conversation UI is not chat cosplay. The assistant speaks on the page surface -as plain text: no bubble, no border, no fill. - -User turns are compact muted surfaces aligned right. They mark authorship -without turning the thread into alternating balloons. - -Assistant hierarchy comes from prose, spacing, and type. Wrapping assistant -messages in cards makes the system look defensive and wastes density. - -Tool calls are operational evidence. Collapse them to a labeled one-line -summary with status. Expand only when the user asks for detail, then show -mono content inside the disclosed area. - -The prompt input is one bordered surface. The textarea region stays empty of -controls so writing remains the focus. Attachments, model choices, secondary -tools, and send live in a single row below it. - -There is one primary send action. Stop and send are mutually exclusive states -of the same action area, not two competing primary buttons. - -Use the conversation reference when building any AI thread, agent console, -review assistant, or prompt composer. It carries the grammar that agents most -often get wrong. diff --git a/packages/vessel-light/.ghost/grammar.deletion.md b/packages/vessel-light/.ghost/grammar.deletion.md deleted file mode 100644 index bd5ed3bc..00000000 --- a/packages/vessel-light/.ghost/grammar.deletion.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -for: The final pass over any composition, or whenever a view feels crowded, busy, or dressed up. ---- - -Restraint is not a mood; it is a test every element has to pass. Before a -view ships, run the pass: each label, divider, icon, caption, and helper -line must name what breaks if it is removed. If nothing breaks, delete it. -Convention is not a reason — "forms usually have this" keeps nothing. - -The test has a direction. When a view is crowded, or two elements compete -for attention, the fix is always demotion or deletion — never adding -emphasis to the loser. Raising the volume of one element to beat another -starts an arms race the composition always loses; removing the weaker claim -ends it. - -Some deletions we have already made, permanently: no decorative dividers -where a gap step states the relationship; no icons that restate their -adjacent label; no tooltips explaining obvious controls; no onboarding -chrome on routine views; no credibility costume — a small factual source or -timestamp line beats a dashboard of gauges. - -A view also arrives settled. No skeleton loaders, no placeholder shimmer -promising content that is not there — a fake page is hedging rendered as UI. -While work is genuinely pending, show the smallest true statement (a quiet -loading state, per the motion doctrine's loop exception) and nothing else. -When content lands, it lands in its final position: no reflow, no staggered -construction, no element arriving late to shift its neighbors. - -Do: a settings page whose every row survives the what-breaks question, with -one primary action and gaps doing the dividing. Never: the same page with -icon-per-row decoration, a divider under every group, a progress shimmer on -load, and a bolded second CTA competing for the eye. - -After everything deletable is gone, whatever remains is unmistakably the -point. diff --git a/packages/vessel-light/.ghost/grammar.hierarchy.md b/packages/vessel-light/.ghost/grammar.hierarchy.md deleted file mode 100644 index 63a9b57b..00000000 --- a/packages/vessel-light/.ghost/grammar.hierarchy.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -for: Any view containing text or actions. -materials: - - materials/primitives.css - - materials/examples/composition.form.html ---- - -Hierarchy is a closed vocabulary, not a size slider. Every piece of text and -every control picks from a named set; anything outside the set is a broken -primitive, not a variant. - -The text variants are exactly six: display, headline, title, body, label, -mono. Variant names are jobs, not decoration. Display leads a rare editorial -moment. Headline names a section. Title anchors a card, dialog, or compact -region. Body carries reading. Label names structure — category tags, field -labels, bylines, metadata, compact status. Mono carries code and machine -detail. Do not fake hierarchy with arbitrary font sizes; choose the tier that -matches the job, then use tone, weight, and spacing for the rest. - -The tones are exactly seven: default, muted, inverse, success, warning, info, -destructive. Tone is part of the message. Default is the main reading plane. -Muted carries secondary information. Inverse is for dark or primary surfaces. -The four status tones appear only when the words have that state meaning. -Hierarchy starts with tone and weight before size — a secondary note usually -wants muted body or label, not a smaller custom font. If the prose needs -emphasis, improve the sentence before adding a style. - -The control emphasis ladder is exactly five rungs: primary, secondary, -outline, ghost, link. At most one primary action per view is the rule -(`--primary-budget: 1`); everything else steps down the ladder. Primary is -for the action the screen exists to complete — if two buttons both look -primary, the hierarchy failed. And some views honestly earn none: a steady -status view or an open comparison has no action the screen exists to -complete, and promoting one anyway puts the system's thumb on the scale. -Zero is a valid spend. -Destructive is a meaning, not a rung: it is rare, it names the destructive -act directly, and it is never borrowed for urgency, emphasis, or brand heat. - -Fields are quiet until they are active: hairline border, muted placeholder, -clear label, and a focused ring when the user engages. The focus ring is -guidance, not decoration. Errors state facts next to the field that caused -them — do not hide field errors in modals, toasts, or generic banners when -the user needs to fix one input. - -Figures that will be compared or scanned — amounts, counts, dates in -columns, any metric — set in tabular numerals (`--numeric-tabular`), with -the unit tight to the number. Proportional figures in a data column wobble, -and a wobbling column reads as sloppy arithmetic. Mono already carries this -for machine detail; the rule extends to any numeric data in product UI. - -Balanced text is for headings and compact statements. Do not balance long -body copy into jagged reading. - -The form reference shows the intended decision order: stacked labels and -fields, compact helper text, one submit, secondary escape. Copy the decision -order before adjusting surface detail. diff --git a/packages/vessel-light/.ghost/grammar.motion.md b/packages/vessel-light/.ghost/grammar.motion.md deleted file mode 100644 index 141e0770..00000000 --- a/packages/vessel-light/.ghost/grammar.motion.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -for: Any transition, animation, or hover treatment. -materials: - - materials/tokens.css ---- - -Motion is evidence of a state change. It confirms hover, press, reveal, -collapse, entrance, exit, and spatial movement. It does not entertain. - -The entire vocabulary is three duration roles and one ease. Fast is for hover -and press. Normal is for reveals, fades, and small state changes. Slow is -reserved for spatial transitions where the user needs to understand movement. -Do not introduce novelty easings because a surface feels static; the ease's -character is a brand answer — see the temperature signature. - -Nothing loops except explicit loading states. A spinner may continue because -work continues. Decorative pulsing, floating, glowing, and attention-seeking -keyframes are off-language. - -Prefer opacity and small transform changes. If removing an animation does not -reduce comprehension, the animation was decoration. - -Condition: marketing and editorial surfaces may stage entrances — scroll reveals and -section transitions are part of editorial rhythm, still built from the three -durations and the one ease. In product UI the same staging is decoration. diff --git a/packages/vessel-light/.ghost/grammar.rhythm.md b/packages/vessel-light/.ghost/grammar.rhythm.md deleted file mode 100644 index 2cc4c92e..00000000 --- a/packages/vessel-light/.ghost/grammar.rhythm.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -for: Laying out any view. -materials: - - materials/primitives.css ---- - -All layout is stacks. Rhythm comes from relationships between siblings, not -from isolated margins pasted onto whichever element was last touched. - -The gap steps are exactly five: `--gap-xs`, `--gap-sm`, `--gap-md`, -`--gap-lg`, `--gap-xl`. Choose the gap that states the relationship. Do not -tune by single pixels to make a screenshot pass. - -The default is column, medium gap, stretch alignment, start justification. -That is the ordinary reading rhythm. - -Columns are for almost everything: forms, cards, message lists, modal bodies, -settings, empty states, and page sections. A column lets the user scan. - -Rows are conditional. Use them for controls, metadata lines, compact status, -and paired label/value moments. If a row starts wrapping awkwardly, it -probably wanted to be a column. - -Ad-hoc margins between siblings are forbidden because they hide the rhythm. -When spacing feels wrong, change the stack gap or split the stack. diff --git a/packages/vessel-light/.ghost/grammar.surfaces.md b/packages/vessel-light/.ghost/grammar.surfaces.md deleted file mode 100644 index 0393d2f1..00000000 --- a/packages/vessel-light/.ghost/grammar.surfaces.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -for: Any card, popover, modal, dialog, scrim, or bordered container. -materials: - - materials/primitives.css - - materials/examples/composition.overlay.html ---- - -Surface is the only way an element gets a background, border, radius, or -shadow. If a container needs visual treatment, it first needs a surface role. - -The vocabulary is closed: role, padding, radius, border, and elevation. These -axes create enough range for page flow, cards, popovers, muted blocks, -accents, and dark moments without inventing one-off boxes. - -The default surface is flat: no border, no shadow. We do not outline -everything to prove layout exists. - -The elevation tiers are exactly three: card, popover, modal. Elevation -implies hierarchy — a card sits in the document flow, a popover floats above -the flow, a modal interrupts the task. Pick the tier that matches the -interaction; never write a custom shadow because the composition feels flat, -and never pair a low tier with an interrupting role or give a routine card -modal gravity. Component shadows belong to the primitives that own them; -never borrow them for layout. - -Borders are structural, especially for inputs and overlays. They are not -decorative frames around ordinary text — reject borders as decoration, -especially around assistant text, and use space, tone, and type hierarchy -instead. - -The overlay reference shows the interrupting end of the system: background -scrim, modal radius, modal shadow, compact header, clear footer. Copy its -hierarchy when a user must stop and decide. diff --git a/packages/vessel-light/.ghost/index.md b/packages/vessel-light/.ghost/index.md index d94320e9..512ce7ea 100644 --- a/packages/vessel-light/.ghost/index.md +++ b/packages/vessel-light/.ghost/index.md @@ -7,22 +7,25 @@ materials: vessel-light is Vessel without the React package: the design language as a steering packet for agents writing raw HTML and CSS. -Style only with the tokens. Compose only with the closed sets the grammar -enumerates. Use the examples when the task matches; they are not a framework. +Style only with the tokens. Compose only with the closed sets the foundations +enumerate. Use the examples when the task matches; they are not a framework. -Grammar and the median floor are safe verbatim, unchanged by any adaptation. -Signature nodes state Vessel's current answer and stand until you replace -them. Registers — editorial, email, and data-density — name the situations -where parts of the default contract invert; pull only the nodes whose -conditions match the task, because truths from the wrong register are -poison, not context. +Foundations carry Vessel's load-bearing decisions. Their role logic survives +adaptation; the answered shape, palette, type, and temperature values stand +until you replace them. `standard.model-defaults` is shared guidance: prune a +Default only when explicit brand guidance replaces it, then adapt its paired +check in the same change. Contexts — editorial, email, data-density, and +conversation — name the situations where parts of the default contract bend; +pull only the nodes whose conditions match the task, because guidance from the +wrong context is contamination. -Monochrome comes first. Status color is functional language — danger, -success, warning, information — never atmosphere. Atmosphere is catered: a -five-hue expression palette whose volume rises with the register, from -charts-only in product to two hues in editorial, and never on controls. +Monochrome comes first. Status color is functional language — danger, success, +warning, information — never atmosphere. Atmosphere is catered: a five-hue +expression palette whose volume rises with the context, from charts-only in +product to two hues in editorial, and never on controls. Name the reader's job before composing anything. The job picks the example and -the register, never the topic. When no example fits, compose from the grammar; -do not invent a new styling system. Before anything ships, run the deletion -pass: every element names what breaks if it goes, and the view arrives settled. +the context, never the topic. When no example fits, compose from the +foundations; do not invent a new styling system. Before anything ships, run the +deletion pass: every element names what breaks if it goes, and the view arrives +settled. diff --git a/packages/vessel-light/.ghost/register.data-density.md b/packages/vessel-light/.ghost/register.data-density.md deleted file mode 100644 index b45808a4..00000000 --- a/packages/vessel-light/.ghost/register.data-density.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -for: Tables, dashboards, logs, monitoring, or other data-dense consoles. -materials: - - materials/examples/composition.table.html - - materials/primitives.css ---- - -Condition: this node applies to data-dense surfaces — tables, dashboards, -transaction logs, and admin consoles. - -Data density inverts the settings-page rhythm. Operators need scan speed -before they need breathing room. - -Use the two smallest gap steps (`--gap-xs`, `--gap-sm`) where forms use the -medium step. Tight adjacency is meaning when rows compare against rows. - -Numerals are mono so columns align. A ragged amount column is a broken -instrument. - -Hierarchy is muted-first. Data is the default plane, labels are muted, and -emphasis is rare enough to stay useful. - -Data surfaces take the small radius. A surface-radius table cell is costume — -the signature radius (see signature.shape) belongs on cards, not cells. - -Status is a text label with at most one functional color family per view. - -Charts inside a console follow the product carve-out from the palette -signature: expression hues live inside the plot area only. Outside the plot, -the one-status-hue cap holds — chart color never leaks into rows, badges, or -headers. More status color turns monitoring into confetti. - -No zebra striping. Hairline borders carry rows with less noise and more -trust. - -Hover confirmation uses the fast duration. It should acknowledge targeting, -not animate the table. - -Generous whitespace is drift here. Wasted density makes operators scroll. diff --git a/packages/vessel-light/.ghost/register.editorial.md b/packages/vessel-light/.ghost/register.editorial.md deleted file mode 100644 index 6eecb702..00000000 --- a/packages/vessel-light/.ghost/register.editorial.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -for: Heroes, marketing pages, pull quotes, or full-bleed dark moments. -materials: - - materials/examples/composition.editorial.html - - materials/tokens.css ---- - -Condition: this node applies to marketing, landing, and editorial surfaces — -never to product UI. - -Editorial surfaces invert the product type rules. Display scale is not an -indulgence here; it is the job. - -Use the `--heading-display-*` tokens — size, line height, and weight — when -the words are the composition. The display tier belongs to this register (see -signature.type for the scale's character). - -Section rhythm uses `--section-padding-vertical`, not a product stack -stretched until it looks important. - -The sanctioned dramatic moment is the full-bleed dark section. Use the -`--surface-dark-*` family and let contrast carry the scene. - -Pull quotes are visual punctuation for longform. They interrupt reading with -judgment, not decoration. - -The tracked uppercase label is the kicker grammar. It gives the page a hard -editorial edge before the headline lands. - -Monochrome remains the spine, but editorial is the loud end of the expression -ladder (see signature.palette): up to two expression hues per page as -atmosphere — a tinted dark section, a colored pull-quote accent, a duotone -image moment. Commit to few colors at scale; variety is where expression -collapses into decoration. - -There is one primary action per page, and it keeps the control radius from -signature.shape. Editorial confidence does not excuse competing calls to -action. - -A hero built at product scale reads as timid. Timidity is drift here, exactly -as spectacle is drift in product UI. diff --git a/packages/vessel-light/.ghost/register.email.md b/packages/vessel-light/.ghost/register.email.md deleted file mode 100644 index 37681aa3..00000000 --- a/packages/vessel-light/.ghost/register.email.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -for: Transactional email only. -materials: - - materials/examples/email.html ---- - -Condition: this node applies only to email. In any other medium, everything -below is a violation. - -Email clients do not honor the web contract. Custom properties, external -stylesheets, flex layout, and webfonts are unreliable materials here. - -Email inverts the material contract deliberately. Transcribe token values by -hand instead of referencing tokens — every hex below is a transcription of -`materials/tokens.css`, which stays the single source; if a token changes, -re-transcribe. - -Use `#1a1a1a` (`--color-gray-900`, foreground) for text, `#999999` -(`--color-gray-500`, muted) for muted text, and `#e8e8e8` (`--color-gray-200`, -border) for borders when the email needs the Vessel palette. - -Keep the surface radius on cards and the control radius on buttons (see -signature.shape). The values survive even when the token names cannot travel. - -Build with table layout, a 600px wrapper, predictable cells, and bulletproof -buttons. Email fidelity is made from boring structure. - -HK Grotesk falls back to the system stack. The voice must survive without the -font file. - -The soul survives the body swap: monochrome spine, quiet factual copy, one -primary action, no celebration. - -Email gets exactly one expressive moment: a header band or the figure that -matters, in one expression hue, transcribed by hand like every value here — -amber is `#f6b44a` (`--expression-1`). One moment, one hue. A receipt is -allowed one degree of warmth; it is not allowed a palette. - -Do not add further color to compensate for email constraints. Constraint is -not permission to perform. - -Hardcoding hex here is fidelity, not drift. The check exemption is the -condition itself. diff --git a/packages/vessel-light/.ghost/signature.palette.md b/packages/vessel-light/.ghost/signature.palette.md deleted file mode 100644 index 90229b6f..00000000 --- a/packages/vessel-light/.ghost/signature.palette.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -for: Color beyond the base roles, in any register. -materials: - - materials/tokens.css ---- - -This is Vessel's answer to palette — it stands until you replace it. - -The relationship is fixed: one brand at different volumes. A monochrome spine -is the default atmosphere in every medium — calm, legible, and resistant to -novelty — and a closed expression set supplies the atmosphere, with volume -set by register, never by taste. - -Vessel's current answer: the base palette is monochrome gray, and the -expression palette is five named hues and only these — amber -(`--expression-1`), periwinkle (`--expression-2`), clay (`--expression-3`), -orchid (`--expression-4`), sage (`--expression-5`). Expressive color outside -this set is not expression; it is another brand. An invented hue is not a -bolder Vessel; it is a different brand. - -The volume ladder, by register: - -Product UI: expression lives only in data visualization. A chart may use the -hues; the interface around it stays monochrome. - -Data-dense consoles: one hue family may mark status. Nothing atmospheric. -Charts inside a console keep the product carve-out — hues stay inside the -plot area and never leak into rows, badges, or headers. - -Email: exactly one expressive moment per message — a header band or the -figure that matters. One hue, quiet everywhere else. - -Editorial: expression is sanctioned atmosphere — a tinted dark section, a -colored pull-quote accent, a duotone moment. Never more than two hues per -page. Loudness comes from commitment to few colors at scale, not variety. - -Two constants that outrank the ladder: expression never touches what you -click — buttons, inputs, and links stay monochrome in every register — and -the status roles are not expression; they keep their meanings everywhere and -never moonlight as atmosphere. - -To adapt: edit the `--expression-*` values (and the gray ramp, if the spine -changes) in `materials/tokens.css` and restate this node's current answer. -The ladder — a quiet spine, a closed hue set, register-gated volume — is the -part worth keeping. diff --git a/packages/vessel-light/.ghost/signature.shape.md b/packages/vessel-light/.ghost/signature.shape.md deleted file mode 100644 index 0e752096..00000000 --- a/packages/vessel-light/.ghost/signature.shape.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -for: Choosing or implementing any radius or corner treatment. -materials: - - materials/tokens.css - - materials/primitives.css ---- - -This is Vessel's answer to shape — it stands until you replace it. - -The relationship is fixed: controls and surfaces carry different radius -roles, and the two never swap. Buttons and text inputs use -`--radius-control`; cards and other surfaces use `--radius-surface`. - -Vessel's current answer: controls are pills (`--radius-control: 999px`) and -surfaces take the 20px signature radius (`--radius-surface: 20px`). The pill -is Vessel's most visible control signature; a rectangular button is not a -variant, it is a different design system. Never give a button the surface -radius — a rectangular button is the fastest tell that the output is not -Vessel. - -20px is a considered position: rounder than default shadcn, but not bubbly. -Use the radius system as restraint, not as decoration. - -To adapt: edit the `--radius-control` and `--radius-surface` values in -`materials/tokens.css` and restate this node's current answer. The -role split — one radius for what you click, one for what contains — is the -part worth keeping. diff --git a/packages/ghost/src/init-payloads/median/cliche.median.md b/packages/vessel-light/.ghost/standard.model-defaults.md similarity index 100% rename from packages/ghost/src/init-payloads/median/cliche.median.md rename to packages/vessel-light/.ghost/standard.model-defaults.md diff --git a/packages/vessel-light/README.md b/packages/vessel-light/README.md index 7864b727..214d14a9 100644 --- a/packages/vessel-light/README.md +++ b/packages/vessel-light/README.md @@ -25,8 +25,8 @@ ghost review ## Structure -The corpus is factored by rate of change under adaptation: `grammar.*` nodes are value-free decision logic that survives any adaptation; `signature.*` nodes are the identity dials (shape, palette, type, temperature), each stating Vessel's current answer; `register.*` nodes are named conditions that re-tune the contract; `anti-goal.median` is the model's measured defaults (prune lines your brand legitimately violates) and `anti-goal.tells` guards near-misses of Vessel's own signature. Every literal value lives in `materials/tokens.css`. +The corpus is factored by rate of change under adaptation: `foundation.*` nodes carry Vessel's load-bearing decisions — value-free decision logic that survives any adaptation, plus the identity dials (shape, palette, type, temperature), each stating Vessel's current answer; `context.*` nodes are named situations that re-tune the contract; `standard.model-defaults` is the model's measured shared defaults (prune lines your brand legitimately violates) and `foundation.tells` guards near-misses of Vessel's own signature. Every literal value lives in `materials/tokens.css`. ## Curation -Each example says what to keep and what to change. Together they demonstrate every closed set the grammar enumerates: the emphasis ladder, text variants, tones, and elevation tiers. Regenerate examples whenever tokens or signature nodes change. Add one when repeated work proves a gap, not because a surface might exist someday. +Each example says what to keep and what to change. Together they demonstrate every closed set the foundations enumerate: the emphasis ladder, text variants, tones, and elevation tiers. Regenerate examples whenever tokens or answered foundation values change. Add one when repeated work proves a gap, not because a surface might exist someday. diff --git a/packages/vessel-light/demo/README.md b/packages/vessel-light/demo/README.md index 6be225ac..5dbd4989 100644 --- a/packages/vessel-light/demo/README.md +++ b/packages/vessel-light/demo/README.md @@ -74,7 +74,7 @@ HTML artifact. Calibration on real data: Score every arm's artifacts; report the mean per arm alongside `ghost review` results. Known caveat: the font tells (`inter-font-default`, `segoe-font-default`) legitimately fire on email artifacts, where the system -stack is mandated by `register.email` — discount them there. +stack is mandated by `context.email` — discount them there. ## Files diff --git a/packages/vessel-light/demo/asks.md b/packages/vessel-light/demo/asks.md index b600c5bd..71a94925 100644 --- a/packages/vessel-light/demo/asks.md +++ b/packages/vessel-light/demo/asks.md @@ -10,16 +10,16 @@ Build a billing settings page for the Meridian workspace: plan summary, payment method on file, billing email, invoice history, and a way to cancel the subscription. Single HTML file. -expect: grammar.hierarchy, grammar.rhythm, signature.shape -poison: register.email, register.editorial +expect: foundation.hierarchy, foundation.rhythm, foundation.shape +poison: context.email, context.editorial ## Ask 2 — pricing landing page Build a pricing landing page for Meridian: hero, three plan tiers, one customer quote, closing call to action. Single HTML file. -expect: register.editorial, signature.palette, signature.type -poison: register.email +expect: context.editorial, foundation.palette, foundation.type +poison: context.email discount: unprompted-dark-theme ## Ask 3 — payment-receipt email @@ -28,6 +28,6 @@ Build the payment-receipt email Meridian sends after a successful invoice payment: amount, plan, date, card last-4, link to the invoice. Single HTML file, must render in Outlook and Gmail. -expect: register.email, signature.temperature -poison: register.editorial, register.data-density +expect: context.email, foundation.temperature +poison: context.editorial, context.data-density discount: inter-font-default, segoe-font-default diff --git a/packages/vessel-react/.ghost/asset.registry.md b/packages/vessel-react/.ghost/asset.registry.md index 19e7f820..f8fe2eee 100644 --- a/packages/vessel-react/.ghost/asset.registry.md +++ b/packages/vessel-react/.ghost/asset.registry.md @@ -6,22 +6,32 @@ materials: - packages/vessel-react/.shadcn/skills.md --- +## Usage + Vessel is distributed as a generated shadcn registry, not an npm package. Consumers copy components into their repo and own them from there — the escape path is visible by design. The registry is also an agent-facing API, not just a file listing. Registry -items may carry namespaced `meta` fields, and high-impact items should: +items may carry namespaced `meta` fields. + +## Rules + +- High-impact registry items carry `meta.agent_decision` — the decision + packet an agent reads before source: intent, when to use, when not to use, + safe variants, common misuses, and token roles. +- High-impact registry items carry `meta.fingerprint_dimensions` — which + dimensions a component primarily expresses (`palette`, `spacing`, + `typography`, `surfaces`), for higher-confidence per-component attribution + by downstream tooling. +- High-impact registry items carry `meta.response_shapes` — the composed + shapes an example demonstrates, such as `article`, `tracker`, `comparison`, + or `card`. +- When adding or reworking a component, regenerate the registry output; the + generated artifacts are derived, the source components and `registry.json` + are canonical. -- `meta.agent_decision` — the decision packet an agent reads before source: - intent, when to use, when not to use, safe variants, common misuses, and - token roles. -- `meta.fingerprint_dimensions` — which dimensions a component primarily - expresses (`palette`, `spacing`, `typography`, `surfaces`), for - higher-confidence per-component attribution by downstream tooling. -- `meta.response_shapes` — the composed shapes an example demonstrates, such - as `article`, `tracker`, `comparison`, or `card`. +## Never -When adding or reworking a component, regenerate the registry output rather -than hand-editing `public/r/`; the generated artifacts are derived, the -source components and `registry.json` are canonical. +- Never hand-edit the generated `public/r/` artifacts — regenerate them from + the source components and `registry.json`. diff --git a/packages/vessel-react/.ghost/asset.tokens.md b/packages/vessel-react/.ghost/asset.tokens.md index 39c2e575..5c98cb0f 100644 --- a/packages/vessel-react/.ghost/asset.tokens.md +++ b/packages/vessel-react/.ghost/asset.tokens.md @@ -5,6 +5,8 @@ materials: - packages/vessel-react/src/styles/font-faces.css --- +## Usage + The token contract has a fixed shape that all token work must preserve: ```text @@ -14,23 +16,32 @@ primitive values -> Tailwind utility bridge ``` +## Rules + - Primitive values (the gray scale, utility colors) are the only broad place for literal color material. - Shared UI authors against shadcn semantic roles first: `background`, `foreground`, `card`, `popover`, `muted`, `accent`, `primary`, `secondary`, `destructive`, `border`, `input`, `ring`, and the sidebar roles. In component code that means `bg-background`, `text-muted-foreground`, - `border-border` — never raw palette utilities. + `border-border`. - Vessel extensions must be narrow and job-named: composer surfaces, message surfaces, tool/reasoning/status affordances, chips, canvas, code/terminal. - Do not reintroduce broad duplicate aliases such as `background-alt`, - `text-alt`, `border-strong`, or `surface-card`. The legacy `background-*`, - `text-*`, and `border-*` families that remain are deprecated compatibility - aliases; new component code must not use them. - Bridge a token into Tailwind only when component code should author it as a utility class. Raw-CSS-only hooks stay raw CSS variables. +- The token layer carries fixed non-color decisions: a four-tier shadow + hierarchy (`mini`, `card`, `elevated`, `modal`), pill-forward geometry + (999px radius on buttons, inputs, and pills; named radius roles for + contained surfaces), and a system font stack. + +## Never -Fixed non-color decisions carried by the token layer: a four-tier shadow -hierarchy (`mini`, `card`, `elevated`, `modal`), pill-forward geometry -(999px radius on buttons, inputs, and pills; named radius roles for contained -surfaces), and a system font stack. +- Never author component code against raw palette utilities — use the + semantic role utilities (`bg-background`, `text-muted-foreground`, + `border-border`). +- Never reintroduce broad duplicate aliases such as `background-alt`, + `text-alt`, `border-strong`, or `surface-card` — add narrow, job-named + extensions instead. +- Never use the legacy `background-*`, `text-*`, and `border-*` families in + new component code — they are deprecated compatibility aliases; author + against the semantic roles. diff --git a/packages/vessel-react/.ghost/checks/escape-hatches-governed.md b/packages/vessel-react/.ghost/checks/escape-hatches-governed.md index 6db16b7a..f2848e99 100644 --- a/packages/vessel-react/.ghost/checks/escape-hatches-governed.md +++ b/packages/vessel-react/.ghost/checks/escape-hatches-governed.md @@ -3,12 +3,12 @@ name: escape-hatches-governed description: New escape hatches in component source are justified, minimal, and visible. severity: medium references: - - condition.escape-hatches + - context.escape-hatches - principle.named-decisions --- Grade whether the change follows the escape-hatch policy in -`condition.escape-hatches`. Flag: +`context.escape-hatches`. Flag: - arbitrary Tailwind values, inline `style`, or raw palette utilities in normal component source without a documented technical reason; diff --git a/packages/vessel-react/.ghost/condition.escape-hatches.md b/packages/vessel-react/.ghost/condition.escape-hatches.md deleted file mode 100644 index 039697b7..00000000 --- a/packages/vessel-react/.ghost/condition.escape-hatches.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -for: Component work needs className, inline style, arbitrary values, or a local primitive fork. -materials: - - packages/vessel-react/scripts/audit-agent-safety.mjs ---- - -Condition: you are writing or reviewing Vessel component code and the change -reaches for `className` passthrough, inline `style`, arbitrary Tailwind -values, or a local fork of a primitive. - -Escape hatches are governed, not banned — Vessel is source-owned and -shadcn-compatible, so they are sometimes necessary. In order of preference: - -1. Reach for variants, slots, tokens, and safe primitives before `className`. -2. When the same override recurs, add a named decision to Vessel (a variant, - a token role, a prop) instead of repeating the hatch. -3. Keep arbitrary values, inline styles, and raw palette utilities out of - normal component source unless there is a documented technical reason. -4. Whatever hatch survives must stay easy to grep, count, and review — that - visibility is the contract that makes the hatch acceptable. - -Prefer a deterministic check that can count or reject the unsafe path over -review prose that discourages it. diff --git a/packages/vessel-react/.ghost/condition.upstream-sync.md b/packages/vessel-react/.ghost/condition.upstream-sync.md deleted file mode 100644 index a16230d6..00000000 --- a/packages/vessel-react/.ghost/condition.upstream-sync.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -for: Reconciling Vessel with upstream shadcn or evaluating an upstream change. ---- - -Condition: you are syncing Vessel components against newer upstream shadcn -sources, or evaluating an upstream change for adoption. - -Upstream shadcn is raw material, not authority over Vessel's visual language. -Syncing is upstream hygiene, not visual direction. Triage every upstream -change into one of three moves: - -1. **Adopt** mechanical improvements outright: accessibility fixes, Radix - wiring, ARIA, keyboard behavior, `data-slot` conventions, React - compatibility, and Tailwind-4-compatible structure. -2. **Adapt** useful anatomy through Vessel's token contract and component - API — the structure can come in, but it authors against semantic roles. -3. **Reject** generic visual decisions that widen the authoring surface: raw - palette classes, arbitrary values, broad aliases, component-local theme - hacks, or styling that bypasses Vessel's semantic roles. - -The migration order to preserve: upstream hygiene first, then the agnostic -token contract, then Vessel's own restrained reference stance, then agent -safety (checks, metadata, safer APIs), then selective mining of downstream -forks. Never invert this into "accept latest shadcn wholesale" or "copy a -product fork" — either would erase the reason Vessel exists. diff --git a/packages/vessel-react/.ghost/context.escape-hatches.md b/packages/vessel-react/.ghost/context.escape-hatches.md new file mode 100644 index 00000000..6b86eb7f --- /dev/null +++ b/packages/vessel-react/.ghost/context.escape-hatches.md @@ -0,0 +1,32 @@ +--- +for: Component work needs className, inline style, arbitrary values, or a local primitive fork. +materials: + - packages/vessel-react/scripts/audit-agent-safety.mjs +--- + +In this context: you are writing or reviewing Vessel component code and the +change reaches for `className` passthrough, inline `style`, arbitrary +Tailwind values, or a local fork of a primitive. + +## Usage + +Escape hatches are governed, not banned — Vessel is source-owned and +shadcn-compatible, so they are sometimes necessary. Whatever hatch survives +must stay easy to grep, count, and review — that visibility is the contract +that makes the hatch acceptable. + +## Rules + +- Reach for variants, slots, tokens, and safe primitives before `className`. +- When the same override recurs, add a named decision to Vessel (a variant, + a token role, a prop) instead of repeating the hatch. +- Keep arbitrary values, inline styles, and raw palette utilities out of + normal component source unless there is a documented technical reason. +- Prefer a deterministic check that can count or reject the unsafe path over + review prose that discourages it. + +## Never + +- Never write a hatch in a way that is hard to grep, count, or review — + keep the raw value visible so the override stays a named, countable + decision. diff --git a/packages/vessel-react/.ghost/context.upstream-sync.md b/packages/vessel-react/.ghost/context.upstream-sync.md new file mode 100644 index 00000000..982b5f2e --- /dev/null +++ b/packages/vessel-react/.ghost/context.upstream-sync.md @@ -0,0 +1,34 @@ +--- +for: Reconciling Vessel with upstream shadcn or evaluating an upstream change. +--- + +In this context: you are syncing Vessel components against newer upstream +shadcn sources, or evaluating an upstream change for adoption. + +## Usage + +Upstream shadcn is raw material, not authority over Vessel's visual language. +Syncing is upstream hygiene, not visual direction. + +## Rules + +- Triage every upstream change into one of three moves: adopt, adapt, or + reject. +- **Adopt** mechanical improvements outright: accessibility fixes, Radix + wiring, ARIA, keyboard behavior, `data-slot` conventions, React + compatibility, and Tailwind-4-compatible structure. +- **Adapt** useful anatomy through Vessel's token contract and component + API — the structure can come in, but it authors against semantic roles. +- **Reject** generic visual decisions that widen the authoring surface: raw + palette classes, arbitrary values, broad aliases, component-local theme + hacks, or styling that bypasses Vessel's semantic roles. +- Preserve the migration order: upstream hygiene first, then the agnostic + token contract, then Vessel's own restrained reference stance, then agent + safety (checks, metadata, safer APIs), then selective mining of downstream + forks. + +## Never + +- Never invert the migration order into "accept latest shadcn wholesale" or + "copy a product fork" — either would erase the reason Vessel exists; triage + each change through adopt/adapt/reject instead. diff --git a/packages/vessel-react/.ghost/glossary.md b/packages/vessel-react/.ghost/glossary.md index 909ec0db..c409a89c 100644 --- a/packages/vessel-react/.ghost/glossary.md +++ b/packages/vessel-react/.ghost/glossary.md @@ -1,18 +1,18 @@ --- kinds: - name: principle - - name: condition + - name: context - name: asset - name: pattern --- # principle -Durable stance: true across media unless a narrower condition explicitly limits it. +Durable stance: true across media unless a narrower context explicitly limits it. -# condition +# context -Situational truth: fires only when the stated situation holds. +A named situation where the defaults bend — read only when it matches the task. # asset @@ -20,4 +20,14 @@ Material truth about concrete brand assets such as logos, illustrations, motion, # pattern -Reusable composition or product pattern whose purpose is distinguishable from neighboring patterns. +Reusable composition or product pattern whose purpose is distinguishable from +neighboring patterns. Declared to reserve the scope; this package currently +carries no pattern nodes. + +--- + +Node bodies in this package follow one shape: `## Usage` carries the +worldview and decision logic; `## Rules` carries observable requirements; +`## Never` pairs each rejected move with its replacement; an optional +`## Skeleton` (always last) carries literal opening structure. A node +includes only the sections it needs. diff --git a/packages/vessel-react/.ghost/index.md b/packages/vessel-react/.ghost/index.md index b66ee4cb..e742f882 100644 --- a/packages/vessel-react/.ghost/index.md +++ b/packages/vessel-react/.ghost/index.md @@ -6,11 +6,11 @@ Vessel is ghost's reference body: an agnostic, agent-safe shadcn-compatible component registry that a product's own ghost fingerprint can inhabit. This fingerprint governs Vessel the workspace — its token contract, authoring discipline, registry shape, and the boundary between reference vocabulary and -product brand truth. +product-specific brand guidance. -Read `principle.reference-not-brand` first; it is the seam every other truth +Read `principle.reference-not-brand` first; it is the seam every other node respects. The `asset.tokens` node describes the token contract that component -work must preserve. The `condition.*` nodes fire in specific situations — +work must preserve. The `context.*` nodes apply in specific situations — escape hatches and upstream shadcn syncs — and stay silent otherwise. This fingerprint deliberately does not carry product stance, flows, copy, or diff --git a/packages/vessel-react/.ghost/manifest.yml b/packages/vessel-react/.ghost/manifest.yml index 2d2f2f4e..b4f28195 100644 --- a/packages/vessel-react/.ghost/manifest.yml +++ b/packages/vessel-react/.ghost/manifest.yml @@ -1,2 +1,3 @@ -schema: ghost.fingerprint-package/v1 +schema: ghost.package/v1 id: vessel-react +cover: index diff --git a/packages/vessel-react/.ghost/principle.named-decisions.md b/packages/vessel-react/.ghost/principle.named-decisions.md index 4de957d8..2f59c7ee 100644 --- a/packages/vessel-react/.ghost/principle.named-decisions.md +++ b/packages/vessel-react/.ghost/principle.named-decisions.md @@ -4,25 +4,32 @@ materials: - packages/vessel-react/scripts/audit-agent-safety.mjs --- +## Usage + Vessel's agent-safety discipline: **make off-system output hard to express, -not merely discouraged in prose.** +not merely discouraged in prose.** Decision names beat values. Docs are +probability; checks are contracts. + +## Rules -- Decision names beat values. Prefer props, variants, tokens, and registry - metadata that name intent — `surface=card`, `tone=muted`, `density=compact` - — over open-ended class strings and raw values. Components use CVA variants - and `data-slot` attributes so an agent chooses a named decision instead of - inventing local styling. -- Docs are probability; checks are contracts. When a rule can be - deterministic — no raw palette utilities, no deleted token aliases, no - unapproved theme bridge names — encode it as a script or check rather than - relying on review prose. +- Prefer props, variants, tokens, and registry metadata that name intent — + `surface=card`, `tone=muted`, `density=compact` — over open-ended class + strings and raw values. +- Components use CVA variants and `data-slot` attributes so an agent chooses + a named decision instead of inventing local styling. +- When a rule can be deterministic — no raw palette utilities, no deleted + token aliases, no unapproved theme bridge names — encode it as a script or + check rather than relying on review prose. - Theme behavior belongs in tokens. Components consume semantic roles; - light/dark differences live in the token/theme layer wherever possible, - never as component-local theme hacks. + light/dark differences live in the token/theme layer wherever possible. - Registry metadata is part of the API. High-impact registry items carry decision metadata — intent, when to use, when not to use, safe variants, common misuses, token roles — because agents need the decision, not just the source. -When the same override recurs across consumers, the fix is to add a named -decision to Vessel, not to normalize the escape hatch. +## Never + +- Never implement light/dark differences as component-local theme hacks — + they live in the token/theme layer. +- Never normalize a recurring escape hatch — when the same override recurs + across consumers, the fix is to add a named decision to Vessel. diff --git a/packages/vessel-react/.ghost/principle.reference-not-brand.md b/packages/vessel-react/.ghost/principle.reference-not-brand.md index e1d47a0a..b8de1c9c 100644 --- a/packages/vessel-react/.ghost/principle.reference-not-brand.md +++ b/packages/vessel-react/.ghost/principle.reference-not-brand.md @@ -2,31 +2,39 @@ for: Any Vessel change that could encode product-specific brand truth. --- +## Usage + Vessel provides a coherent implementation vocabulary — tokens, primitives, AI elements, interaction conventions — that a consuming product's own ghost fingerprint can select, interpret, and review against. It must not become that product's brand law. The seam: Vessel makes low-level decisions hard to get wrong; the consumer's -fingerprint owns the high-altitude truths that cannot be compiled into +fingerprint owns the high-altitude guidance that cannot be compiled into component props or lint rules. A consuming repo owns its product stance, flows, copy, trust obligations, and visual-language decisions through its local `.ghost/` package. Vessel supplies reusable materials and safe authoring paths, nothing more. -What this obligates when changing Vessel: +When a Vessel default and a consumer's fingerprint conflict, the consumer's +fingerprint wins in the consumer's repo. Vessel's job is to make that +override a named, visible decision rather than a silent fork. + +## Rules -- Never encode product-specific flows, copy, trust obligations, or business - intent into components, tokens, or registry metadata as universal law. - Keep the baseline language restrained, legible, and workbench-like: a safe default for dense AI/product interfaces, with taste but without a brand. -- Consumers bring their own typeface; Vessel stays on a system font stack and - never bakes in a brand or licensed font without an explicit distribution - decision. +- Consumers bring their own typeface; Vessel stays on a system font stack. - Downstream product forks are evidence and stress tests, never the visual - source of truth. Mine them for reusable discipline; do not copy app chrome, - desktop-shell assumptions, or product surfaces wholesale. + source of truth. Mine them for reusable discipline. -When a Vessel default and a consumer's fingerprint conflict, the consumer's -fingerprint wins in the consumer's repo. Vessel's job is to make that -override a named, visible decision rather than a silent fork. +## Never + +- Never encode product-specific flows, copy, trust obligations, or business + intent into components, tokens, or registry metadata as universal law — + consumers author those in their own `.ghost/` packages. +- Never bake in a brand or licensed font without an explicit distribution + decision — stay on the system font stack. +- Never copy app chrome, desktop-shell assumptions, or product surfaces + wholesale from a downstream fork — mine forks for reusable discipline + instead. diff --git a/packages/vessel-react/fingerprint/checks/median-tells.md b/packages/vessel-react/fingerprint/checks/median-tells.md index 6fa8b394..192f5ce4 100644 --- a/packages/vessel-react/fingerprint/checks/median-tells.md +++ b/packages/vessel-react/fingerprint/checks/median-tells.md @@ -3,7 +3,7 @@ name: Median tells description: Flags the measured defaults of unsteered generation — hover-lift, default accents, unprompted dark theme, gradients, chat bubbles, emoji icons, stock copy. severity: high references: - - anti-goal.median + - standard.model-defaults - pattern.conversation --- diff --git a/packages/vessel-react/fingerprint/contract.theming.md b/packages/vessel-react/fingerprint/contract.theming.md index b54cf06d..73d9f65b 100644 --- a/packages/vessel-react/fingerprint/contract.theming.md +++ b/packages/vessel-react/fingerprint/contract.theming.md @@ -4,25 +4,27 @@ materials: - packages/vessel-react/src/styles/main.css --- +## Usage + Vessel ships vanilla: a deliberate default binding, not the language itself. A theme rebinds values at the seams. The grammar beneath them is invariant. +Free seams rebind without ceremony; a theme that breaks the invariant +grammar is a fork, not a theme. If a theme needs a hook the seams don't +expose, add a narrow job-named token — that is a contract change and should +be reviewed as one. -Free seams — rebind without ceremony: - -- The gray ramp, and the values behind every semantic role. -- The radius values: `--radius-pill`, `--radius-card`, `--radius-modal`, - `--radius-dropdown`, and friends. Pill-first is the vanilla default with - taste, not law; a theme may flatten controls. -- The shadow values inside each tier. -- The font stack. Vanilla stays on system fonts so no brand is baked in; - a theme brings its own typeface. -- The chart hues. - -Invariant grammar — a theme that breaks these is a fork, not a theme: +## Rules +- Free seam: the gray ramp, and the values behind every semantic role. +- Free seam: the radius values: `--radius-pill`, `--radius-card`, + `--radius-modal`, `--radius-dropdown`, and friends. Pill-first is the + vanilla default with taste, not law; a theme may flatten controls. +- Free seam: the shadow values inside each tier. +- Free seam: the font stack. Vanilla stays on system fonts so no brand is + baked in; a theme brings its own typeface. +- Free seam: the chart hues. - Radius coherence: controls share one radius role and contained surfaces - share another, and the system stays ordered. Rebinding buttons to a new - radius while inputs keep the old one is drift, not theming. + share another, and the system stays ordered. - Tier ordering: card reads below popover reads below modal in every theme. Elevation must keep meaning interaction, whatever the shadow values are. - Status semantics: red is destructive/error, green success, yellow warning, @@ -34,8 +36,11 @@ Invariant grammar — a theme that breaks these is a fork, not a theme: stays inside data visualization. - Controls confirm with color and background shifts, not levitation, in every theme. +- Rebind seams in the theme layer (`:root` / `.dark` overrides). + +## Never -Rebind seams in the theme layer (`:root` / `.dark` overrides), never by -editing component classnames to route around a token. If a theme needs a hook -the seams don't expose, add a narrow job-named token — that is a contract -change and should be reviewed as one. +- Never rebind buttons to a new radius while inputs keep the old one — that + is drift, not theming; keep controls on one shared radius role. +- Never edit component classnames to route around a token — rebind the seam + in the theme layer (`:root` / `.dark` overrides). diff --git a/packages/vessel-react/fingerprint/contract.tokens.md b/packages/vessel-react/fingerprint/contract.tokens.md index baaafe78..e6192bbc 100644 --- a/packages/vessel-react/fingerprint/contract.tokens.md +++ b/packages/vessel-react/fingerprint/contract.tokens.md @@ -9,6 +9,8 @@ materials: - packages/vessel-react/src/components/ui/text.tsx --- +## Usage + The token layer has a fixed shape that all component and styling work must preserve: @@ -19,28 +21,37 @@ primitive values (gray scale, utility colors) -> Tailwind utility bridge ``` -Author against semantic roles first: `background`, `foreground`, `card`, -`popover`, `muted`, `accent`, `primary`, `secondary`, `destructive`, `border`, -`input`, `ring`, and the sidebar roles. In component code that means -`bg-background`, `text-muted-foreground`, `border-border` — never raw palette -utilities like `bg-gray-100`, and never literal hex, rgb, or hsl in component -code or inline styles. - The base palette is monochrome gray. Red, green, yellow, and blue exist only when meaning demands them: destructive/error, success, warning, information. -None of them are accents. The chart roles (`chart-1` through `chart-5`) are -the only sanctioned expressive hues, and they live inside data visualization — -they never leak into controls, badges, or chrome. - -Extensions to the token set must be narrow and job-named (composer surfaces, -message surfaces, tool/status affordances) — never broad duplicate aliases -such as `background-alt`, `text-alt`, `border-strong`, or `surface-card`. -Bridge a token into Tailwind only when component code should author it as a -utility class. - -Elevation is an interaction statement, not decoration. Surfaces pick a tier: -`--shadow-card` sits in flow, `--shadow-popover` floats above it, -`--shadow-modal` interrupts the task. Component shadows (`--shadow-btn`, -`--shadow-mini`, `--shadow-kbd`) belong to the components that own them. Never -write a custom `box-shadow` because a composition feels flat — fix the -structure instead. +None of them are accents. Elevation is an interaction statement, not +decoration. + +## Rules + +- Author against semantic roles first: `background`, `foreground`, `card`, + `popover`, `muted`, `accent`, `primary`, `secondary`, `destructive`, + `border`, `input`, `ring`, and the sidebar roles. In component code that + means `bg-background`, `text-muted-foreground`, `border-border`. +- The chart roles (`chart-1` through `chart-5`) are the only sanctioned + expressive hues, and they live inside data visualization. +- Extensions to the token set must be narrow and job-named (composer + surfaces, message surfaces, tool/status affordances). +- Bridge a token into Tailwind only when component code should author it as + a utility class. +- Surfaces pick a tier: `--shadow-card` sits in flow, `--shadow-popover` + floats above it, `--shadow-modal` interrupts the task. +- Component shadows (`--shadow-btn`, `--shadow-mini`, `--shadow-kbd`) belong + to the components that own them. + +## Never + +- Never use raw palette utilities like `bg-gray-100`, and never literal hex, + rgb, or hsl in component code or inline styles — author against the + semantic role utilities. +- Never let the chart hues leak into controls, badges, or chrome — they + live inside data visualization. +- Never add broad duplicate aliases such as `background-alt`, `text-alt`, + `border-strong`, or `surface-card` — extensions must be narrow and + job-named. +- Never write a custom `box-shadow` because a composition feels flat — fix + the structure instead. diff --git a/packages/vessel-react/fingerprint/glossary.md b/packages/vessel-react/fingerprint/glossary.md index a3a47957..b0238d84 100644 --- a/packages/vessel-react/fingerprint/glossary.md +++ b/packages/vessel-react/fingerprint/glossary.md @@ -3,7 +3,7 @@ kinds: - name: contract - name: primitive - name: pattern - - name: anti-goal + - name: standard --- # contract @@ -22,7 +22,9 @@ vocabulary, its defaults, and the composition decisions the source encodes. Assembly grammar that spans components — the arrangements that make a screen feel intentional instead of a pile of imported parts. -# anti-goal +# standard -What this component set never looks like: the measured defaults of unsteered -generation, rejected with replacements. +Shared guidance that is not specific to this component set's identity. Every +rule is an **Obligation** brand preference cannot waive or a **Default** that +explicit brand guidance may deliberately replace. The measured defaults of +unsteered generation live here as Defaults, each rejected with a replacement. diff --git a/packages/vessel-react/fingerprint/index.md b/packages/vessel-react/fingerprint/index.md index 8827e175..161a0b25 100644 --- a/packages/vessel-react/fingerprint/index.md +++ b/packages/vessel-react/fingerprint/index.md @@ -5,8 +5,8 @@ materials: --- This ghost package ships with the Vessel component set. It is not a brand. It is -the taste floor: the contracts, usage grammar, and anti-goals that keep the -vendored components coherent as this repo builds on and modifies them. +the taste floor: the contracts, usage grammar, and shared model-defaults that +keep the vendored components coherent as this repo builds on and modifies them. The components are owned-after-copy, and so is this package. Edit these nodes when this repo's decisions diverge; delete nodes that stop being true. diff --git a/packages/vessel-react/fingerprint/manifest.yml b/packages/vessel-react/fingerprint/manifest.yml index 84f1ecbb..c3c5067b 100644 --- a/packages/vessel-react/fingerprint/manifest.yml +++ b/packages/vessel-react/fingerprint/manifest.yml @@ -1,2 +1,3 @@ schema: ghost.package/v1 id: vessel +cover: index diff --git a/packages/vessel-react/fingerprint/pattern.conversation.md b/packages/vessel-react/fingerprint/pattern.conversation.md index 7a6e5ec3..0b415e50 100644 --- a/packages/vessel-react/fingerprint/pattern.conversation.md +++ b/packages/vessel-react/fingerprint/pattern.conversation.md @@ -9,29 +9,44 @@ materials: - packages/vessel-react/src/components/ai-elements/chain-of-thought.tsx --- -A conversation is a document being written, not a chat skin. The grammar the -components encode: +## Usage -Assistant turns render as plain prose on the page surface — no bubble, no -border, no initials-circle avatar. The assistant is the document's author; -containing it in a balloon demotes it to a participant. +A conversation is a document being written, not a chat skin. The assistant +is the document's author. The asymmetry between plain assistant prose and +contained user turns is the whole message-identity system. Machinery is +subordinate to prose. The composer is the one persistent control surface. +Streaming is choreography, not spectacle. The components encode this +grammar. -User turns get quiet containment: a muted secondary container, right-aligned, -so the reader can scan whose words are whose. That asymmetry is the whole -message-identity system. Do not "balance" it by boxing both sides. +## Rules -Machinery is subordinate to prose. Reasoning, tool calls, chain-of-thought, -and sources render as collapsed, muted affordances the reader can open — never -as loud cards competing with the answer. Status is text and subtle motion -(shimmer), not colored badges per step. +- Assistant turns render as plain prose on the page surface — no bubble, no + border, no initials-circle avatar. +- User turns get quiet containment: a muted secondary container, + right-aligned, so the reader can scan whose words are whose. +- Reasoning, tool calls, chain-of-thought, and sources render as collapsed, + muted affordances the reader can open. +- Status is text and subtle motion (shimmer), not colored badges per step. +- The composer owns the bottom edge, carries its own elevation, and holds + the view's primary action. +- Streaming text appears in place, the scroll follows the newest content + until the user takes the wheel, and layout never jumps as parts resolve. +- Empty conversation states say what this product does, in this product's + words. -The composer is the one persistent control surface. It owns the bottom edge, -carries its own elevation, and holds the view's primary action. Nothing else -in the conversation column should compete with it for weight. +## Never -Streaming is choreography, not spectacle: text appears in place, the scroll -follows the newest content until the user takes the wheel, and layout never -jumps as parts resolve. A stream that reflows the page is a broken stream. - -Empty conversation states say what this product does, in this product's -words — not "Start a conversation to see messages here" restyled. +- Never contain the assistant in a balloon — that demotes the document's + author to a participant; render assistant turns as plain prose on the + page surface. +- Never "balance" the asymmetry by boxing both sides — keep user turns + contained and assistant turns plain. +- Never render machinery as loud cards competing with the answer — collapse + it into muted affordances the reader can open. +- Never let anything else in the conversation column compete with the + composer for weight — the composer holds the view's primary action. +- Never ship a stream that reflows the page — a stream that reflows the + page is a broken stream; text appears in place and layout stays put as + parts resolve. +- Never ship "Start a conversation to see messages here" restyled — say + what this product does, in this product's words. diff --git a/packages/vessel-react/fingerprint/primitive.composition.md b/packages/vessel-react/fingerprint/primitive.composition.md index f02717f4..861e4067 100644 --- a/packages/vessel-react/fingerprint/primitive.composition.md +++ b/packages/vessel-react/fingerprint/primitive.composition.md @@ -9,33 +9,45 @@ materials: - packages/vessel-react/src/components/ui/skeleton.tsx --- +## Usage + Three components carry most of a screen's composure: Stack owns rhythm, Surface owns ground, Text owns hierarchy. Reach for them before writing -layout classnames by hand. - -Rhythm comes from Stack's gap scale, never from margins on siblings. When -spacing feels wrong, change the stack's gap or split into nested stacks with -different gaps — a margin patched onto a child is a rhythm leak, and a -one-off `gap-[13px]` is a scale violation. +layout classnames by hand. The elevation tier is the interaction statement. +Empty states, loading states, and skeletons are part of the composition, +not afterthoughts. The default temperature is quiet, and when a composition +feels flat, the fix is structure — grouping, gap contrast, tone contrast. -Surface makes ground explicit: a semantic role, a padding step, a radius -role, and an elevation tier, chosen together. The elevation tier is the -interaction statement — `card` sits in flow, `popover` floats above it, -`modal` interrupts. Do not stack elevation on nested surfaces; one shadow -per compositional idea. A view that needs three shadows needs fewer -surfaces. +## Rules -Text's variant vocabulary is the type scale: `display`, `headline`, `title`, -`body`, `label`, `mono`, with tone carried by the `tone` prop. Hierarchy -comes from variant and tone, not from ad-hoc `text-[17px]` or bolding body -copy. Muted tone is the workhorse for secondary information; if everything -is muted, nothing is. +- Rhythm comes from Stack's gap scale, never from margins on siblings. +- When spacing feels wrong, change the stack's gap or split into nested + stacks with different gaps. +- Surface makes ground explicit: a semantic role, a padding step, a radius + role, and an elevation tier, chosen together. +- Elevation tiers: `card` sits in flow, `popover` floats above it, `modal` + interrupts. +- One shadow per compositional idea. A view that needs three shadows needs + fewer surfaces. +- Text's variant vocabulary is the type scale: `display`, `headline`, + `title`, `body`, `label`, `mono`, with tone carried by the `tone` prop. +- Hierarchy comes from variant and tone. +- Muted tone is the workhorse for secondary information; if everything is + muted, nothing is. +- A skeleton mirrors the layout it replaces — same stacks, same gaps — so + the load resolves without reflow. +- The quiet default: white ground, hairline borders, generous gaps, one + idea per surface. -Empty states, loading states, and skeletons are part of the composition, not -afterthoughts. A skeleton mirrors the layout it replaces — same stacks, same -gaps — so the load resolves without reflow. +## Never -The default temperature is quiet: white ground, hairline borders, generous -gaps, one idea per surface. When a composition feels flat, the fix is -structure — grouping, gap contrast, tone contrast — never a decorative -border, tint, or shadow. +- Never patch a margin onto a child to fix spacing — that is a rhythm leak; + change the stack's gap or split into nested stacks. +- Never use a one-off `gap-[13px]` — that is a scale violation; use Stack's + gap scale. +- Never stack elevation on nested surfaces — one shadow per compositional + idea. +- Never build hierarchy from ad-hoc `text-[17px]` or bolding body copy — + use variant and tone. +- Never fix a flat composition with a decorative border, tint, or shadow — + the fix is structure: grouping, gap contrast, tone contrast. diff --git a/packages/vessel-react/fingerprint/primitive.controls.md b/packages/vessel-react/fingerprint/primitive.controls.md index 517e57ed..7911c75f 100644 --- a/packages/vessel-react/fingerprint/primitive.controls.md +++ b/packages/vessel-react/fingerprint/primitive.controls.md @@ -11,30 +11,42 @@ materials: - packages/vessel-react/src/components/ui/label.tsx --- -Controls make decisions obvious without making the view loud. +## Usage -One primary action per view. The `default` button variant is for the action -the screen exists to complete; everything else steps down the vocabulary — -`secondary`, `outline`, `ghost`, `link`. If two buttons both look primary, -the hierarchy failed. Do not invent variants outside this vocabulary; if a -new one is genuinely needed, it comes from repeated observed need and gets -added to the shared component, not inlined as one-off classnames. +Controls make decisions obvious without making the view loud. One primary +action per view. If two buttons both look primary, the hierarchy failed. +Fields are quiet until active, and the focus ring is guidance, not +decoration. -`destructive` is red and rare. It names the destructive act directly. Red is -never urgency, emphasis, or heat. +## Rules -Hover confirms with color and background shifts at the component's built-in -transition — never with `translateY` lift or a growing shadow. The source -encodes this; do not add motion on top. +- The `default` button variant is for the action the screen exists to + complete; everything else steps down the vocabulary — `secondary`, + `outline`, `ghost`, `link`. +- `destructive` is red and rare. It names the destructive act directly. +- Hover confirms with color and background shifts at the component's + built-in transition. The source encodes this. +- Fields are quiet until active: hairline border, muted placeholder, clear + label, focus ring on engagement. +- Errors state facts next to the field that caused them, through the form + component's invalid states (`aria-invalid`, field messages). +- Compose control rows with the group components, so spacing and radius + joining stay owned by the system. -Fields are quiet until active: hairline border, muted placeholder, clear -label, focus ring on engagement. The ring is guidance, not decoration — do -not restyle it away or amplify it. +## Never -Errors state facts next to the field that caused them, through the form -component's invalid states (`aria-invalid`, field messages). Do not move -field errors into modals, toasts, or generic banners when the user needs to -fix one input. - -Compose control rows with the group components rather than ad-hoc flex -wrappers, so spacing and radius joining stay owned by the system. +- Never invent variants outside this vocabulary — if a new one is genuinely + needed, it comes from repeated observed need and gets added to the shared + component, not inlined as one-off classnames. +- Never use red as urgency, emphasis, or heat — `destructive` names the + destructive act directly. +- Never confirm hover with `translateY` lift or a growing shadow — use the + component's built-in color and background shifts; do not add motion on + top. +- Never restyle the focus ring away or amplify it — the ring is guidance, + not decoration. +- Never move field errors into modals, toasts, or generic banners when the + user needs to fix one input — state them next to the field through the + form component's invalid states. +- Never compose control rows with ad-hoc flex wrappers — use the group + components so spacing and radius joining stay owned by the system. diff --git a/packages/vessel-react/fingerprint/anti-goal.median.md b/packages/vessel-react/fingerprint/standard.model-defaults.md similarity index 82% rename from packages/vessel-react/fingerprint/anti-goal.median.md rename to packages/vessel-react/fingerprint/standard.model-defaults.md index 374a7693..f78b195a 100644 --- a/packages/vessel-react/fingerprint/anti-goal.median.md +++ b/packages/vessel-react/fingerprint/standard.model-defaults.md @@ -2,11 +2,13 @@ for: Any build using the vendored component set. --- -These are not aesthetic opinions. They are the measured convergence of 300 -unsteered generations across three frontier models (the antimedian +Shared defaults, not aesthetic opinions. They are the measured convergence +of 300 unsteered generations across three frontier models (the antimedian experiment): the defaults a model reaches for when nobody hands it a stance. -An output showing several of these tells reads as generated, whatever else -it does right. +Every rule here is a **Default** — explicit brand guidance may deliberately +replace it; adapt or remove the paired check flag in the same change. An +output showing several of these tells reads as generated, whatever else it +does right. Reject hover-lift as the default interaction: cards, buttons, or anything else rising on `translateY` with a growing shadow. The single most diff --git a/scripts/check-packed-package.mjs b/scripts/check-packed-package.mjs index 78e21f39..902bab1e 100644 --- a/scripts/check-packed-package.mjs +++ b/scripts/check-packed-package.mjs @@ -174,7 +174,7 @@ try { const bodyOutput = JSON.parse(bodyInit); for (const required of [ "manifest.yml", - "anti-goal.median.md", + "standard.model-defaults.md", "materials/fonts/HKGrotesk-Regular.woff2", "checks/values.md", ]) { diff --git a/scripts/check-release-tarball.mjs b/scripts/check-release-tarball.mjs index 5921cd75..098073c1 100644 --- a/scripts/check-release-tarball.mjs +++ b/scripts/check-release-tarball.mjs @@ -66,7 +66,7 @@ try { "package.json", "dist/bin.js", "dist/cli.js", - "dist/init-payloads/median/cliche.median.md", + "dist/init-payloads/median/standard.model-defaults.md", "dist/init-payloads/median/median-tells.md", "dist/init-payloads/vessel-light/manifest.yml", "dist/init-payloads/vessel-light/materials/fonts/HKGrotesk-Regular.woff2", @@ -147,7 +147,7 @@ try { ); const bodyOutput = JSON.parse(bodyInit); for (const required of [ - "anti-goal.median.md", + "standard.model-defaults.md", "materials/fonts/HKGrotesk-Regular.woff2", "checks/values.md", ]) { diff --git a/scripts/check-vessel-light-closure.mjs b/scripts/check-vessel-light-closure.mjs index 85934bf2..c7dd21a4 100644 --- a/scripts/check-vessel-light-closure.mjs +++ b/scripts/check-vessel-light-closure.mjs @@ -11,7 +11,7 @@ const MATERIALS_DIR = "packages/vessel-light/.ghost/materials"; const EXAMPLE_DIR = join(MATERIALS_DIR, "examples"); // Declared inventory: the closed five-hue expression set is the brand answer; -// no register renders all five at once (see signature.palette). +// no register renders all five at once (see foundation.palette). const TOKEN_ALLOWLIST = new Set([ "--expression-3", "--expression-4",