- | Vessel |
+ Vessel |
-
+
- | Payment receipt |
+ Payment receipt |
| Your payment for the Team plan has been received. A copy of this receipt is available in billing settings. |
-
+
| Receipt number |
VS-1048 |
@@ -46,6 +58,7 @@
+
|
View receipt
|
diff --git a/packages/vessel-light/.ghost/materials/tokens.css b/packages/vessel-light/.ghost/materials/tokens.css
index 2cd75191..3f389f08 100644
--- a/packages/vessel-light/.ghost/materials/tokens.css
+++ b/packages/vessel-light/.ghost/materials/tokens.css
@@ -87,10 +87,22 @@
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
- --radius-card: 20px;
- --radius-pill: 999px;
+ --radius-surface: 20px;
+ --radius-control: 999px;
--radius-input: 999px;
+ --gap-xs: 4px;
+ --gap-sm: 8px;
+ --gap-md: 16px;
+ --gap-lg: 24px;
+ --gap-xl: 32px;
+
+ --text-body-size: 14px;
+ --text-title-size: 18px;
+ --text-headline-size: 24px;
+
+ --control-height: 36px;
+
--background: var(--color-white);
--foreground: var(--color-gray-900);
--card: var(--background);
diff --git a/packages/vessel-light/.ghost/pattern.conversation.md b/packages/vessel-light/.ghost/pattern.conversation.md
deleted file mode 100644
index 86d619b4..00000000
--- a/packages/vessel-light/.ghost/pattern.conversation.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-description: Build AI conversations with plain assistant text, compact user surfaces, collapsed tool calls, and one structured prompt input.
-materials:
- - materials/ref/composition.conversation.html
- - "**/*.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/pattern.data-density.md b/packages/vessel-light/.ghost/pattern.data-density.md
deleted file mode 100644
index dbce3abf..00000000
--- a/packages/vessel-light/.ghost/pattern.data-density.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-description: "Data-dense consoles — gather for tables, dashboards, logs, and monitoring; compresses product rhythm to 4/8px, mono numerals, muted-first."
-materials:
- - materials/ref/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 4px and 8px gaps where forms use 16px. 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 20px-radius table cell is costume.
-
-Status is a text label with at most one functional color family per view.
-
-Charts inside a console follow the product carve-out: 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 `--duration-fast`. 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/pattern.editorial.md b/packages/vessel-light/.ghost/pattern.editorial.md
deleted file mode 100644
index dcd214f2..00000000
--- a/packages/vessel-light/.ghost/pattern.editorial.md
+++ /dev/null
@@ -1,26 +0,0 @@
----
-description: "Editorial and landing surfaces — gather for heroes, marketing pages, pull quotes, and full-bleed dark moments; inverts product type restraint."
-materials:
- - materials/ref/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 `--heading-display-font-size: clamp(64px, 8vw, 96px)`, `--heading-display-line-height: 0.88`, and `--heading-display-font-weight: 900` when the words are the composition.
-
-Section rhythm is `--section-padding-vertical: 100px`, not a 16px 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: 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 remains a pill. 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/pattern.email.md b/packages/vessel-light/.ghost/pattern.email.md
deleted file mode 100644
index f3e09a75..00000000
--- a/packages/vessel-light/.ghost/pattern.email.md
+++ /dev/null
@@ -1,27 +0,0 @@
----
-description: "Transactional email — gather ONLY for email; inverts the token contract: inline styles, hardcoded hex, table layout, system fonts."
-materials:
- - materials/ref/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.
-
-Use `#1a1a1a` for foreground, `#999999` for muted text, and `#e8e8e8` for borders when the email needs the Vessel palette.
-
-Keep the 20px card radius and the pill button 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`). 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/primitive.control.md b/packages/vessel-light/.ghost/primitive.control.md
deleted file mode 100644
index ece934e7..00000000
--- a/packages/vessel-light/.ghost/primitive.control.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-description: Use quiet fields, focused rings, factual errors, and one primary action per view.
-materials:
- - materials/primitives.css
- - materials/ref/composition.form.html
- - "**/*.html"
- - "**/*.css"
----
-
-Controls make decisions obvious without making the whole view loud. One primary action per view is the rule; everything else steps down to secondary, outline, ghost, or link.
-
-Buttons and inputs are pills. The pill radius is Vessel's most visible control signature; a rectangular button is not a variant, it is a different design system.
-
-Primary is for the action the screen exists to complete. If two buttons both look primary, the hierarchy failed.
-
-Destructive is red and rare. It must name the destructive act directly. Do not use red as 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.
-
-The form reference shows the intended density: 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/primitive.stack.md b/packages/vessel-light/.ghost/primitive.stack.md
deleted file mode 100644
index 678a1405..00000000
--- a/packages/vessel-light/.ghost/primitive.stack.md
+++ /dev/null
@@ -1,19 +0,0 @@
----
-description: Compose layout with stack direction, gap, alignment, justification, and wrap; never ad-hoc sibling margins.
-materials:
- - materials/primitives.css
- - "**/*.html"
- - "**/*.css"
----
-
-All layout is stacks. Vessel rhythm comes from relationships between siblings, not from isolated margins pasted onto whichever element was last touched.
-
-The gap scale is the spacing language: 4, 8, 16, 24, and 32. 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 Vessel's 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/primitive.surface.md b/packages/vessel-light/.ghost/primitive.surface.md
deleted file mode 100644
index 3c9630e4..00000000
--- a/packages/vessel-light/.ghost/primitive.surface.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-description: Backgrounds, borders, radius, shadow, and overlays — gather for any card, popover, modal, dialog, or scrim; the only route to elevation.
-materials:
- - materials/primitives.css
- - materials/ref/composition.overlay.html
- - "**/*.html"
- - "**/*.css"
----
-
-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. Vessel does not outline everything to prove layout exists.
-
-Elevation implies hierarchy. A card sits in the document flow. A popover floats above the flow. A modal interrupts the task. Do not pair a low-shadow treatment with an interrupting role or give a routine card modal gravity.
-
-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.
diff --git a/packages/vessel-light/.ghost/primitive.text.md b/packages/vessel-light/.ghost/primitive.text.md
deleted file mode 100644
index e1e7288a..00000000
--- a/packages/vessel-light/.ghost/primitive.text.md
+++ /dev/null
@@ -1,17 +0,0 @@
----
-description: Pick text from the closed variant and tone set; build hierarchy through tone and weight before size.
-materials:
- - materials/primitives.css
----
-
-Every piece of text chooses a variant: display, headline, title, body, label, or mono. Text outside the scale is a broken primitive.
-
-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. 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. Success, warning, info, and destructive 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.
-
-Balanced text is for headings and compact marketing-like statements. Do not balance long body copy into jagged reading.
-
-If the prose needs emphasis, improve the sentence before adding a style.
diff --git a/packages/vessel-light/.ghost/register.data-density.md b/packages/vessel-light/.ghost/register.data-density.md
new file mode 100644
index 00000000..85adfcaf
--- /dev/null
+++ b/packages/vessel-light/.ghost/register.data-density.md
@@ -0,0 +1,39 @@
+---
+description: "Data-dense consoles — gather for tables, dashboards, logs, and monitoring; compresses product rhythm to the two smallest gap steps, mono numerals, muted-first."
+materials:
+ - materials/ref/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
new file mode 100644
index 00000000..27fd2602
--- /dev/null
+++ b/packages/vessel-light/.ghost/register.editorial.md
@@ -0,0 +1,41 @@
+---
+description: "Editorial and landing surfaces — gather for heroes, marketing pages, pull quotes, and full-bleed dark moments; inverts product type restraint."
+materials:
+ - materials/ref/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
new file mode 100644
index 00000000..3517f07e
--- /dev/null
+++ b/packages/vessel-light/.ghost/register.email.md
@@ -0,0 +1,43 @@
+---
+description: "Transactional email — gather ONLY for email; inverts the token contract: inline styles, hardcoded hex transcribed from tokens.css, table layout, system fonts."
+materials:
+ - materials/ref/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
new file mode 100644
index 00000000..1669e57e
--- /dev/null
+++ b/packages/vessel-light/.ghost/signature.palette.md
@@ -0,0 +1,45 @@
+---
+description: "Gather whenever color beyond the base roles is in question, in any register. The palette dial: a monochrome spine plus a closed expression set whose volume rises with the register — this brand's current answer is gray plus five named hues."
+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
new file mode 100644
index 00000000..1ccd7f0f
--- /dev/null
+++ b/packages/vessel-light/.ghost/signature.shape.md
@@ -0,0 +1,27 @@
+---
+description: "Gather before setting any radius or corner treatment. The shape dial: controls take --radius-control, surfaces take --radius-surface — this brand's current answer is pills on controls and a 20px signature radius on surfaces."
+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/vessel-light/.ghost/signature.temperature.md b/packages/vessel-light/.ghost/signature.temperature.md
new file mode 100644
index 00000000..88e7c498
--- /dev/null
+++ b/packages/vessel-light/.ghost/signature.temperature.md
@@ -0,0 +1,37 @@
+---
+description: "Gather for any copy and for motion character. The temperature dial: how the brand sounds and how it moves — this brand's current answer is factual, quiet, never celebratory, with resolved spring motion."
+materials:
+ - materials/tokens.css
+---
+
+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".
+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.
+
+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
+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
+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.
diff --git a/packages/vessel-light/.ghost/signature.type.md b/packages/vessel-light/.ghost/signature.type.md
new file mode 100644
index 00000000..f5d59003
--- /dev/null
+++ b/packages/vessel-light/.ghost/signature.type.md
@@ -0,0 +1,36 @@
+---
+description: "Gather for any text, and for heroes, landing pages, and editorial moments. The type dial: one voice typeface plus a mono for machine detail, with an editorial heading scale kept separate from product text — this brand's current answer is HK Grotesk."
+materials:
+ - materials/tokens.css
+ - materials/fonts/*.woff2
+---
+
+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,
+everywhere, with a mono variant only for code, tool detail, and machine
+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, sub, and card each carry
+their own rhythm (`--heading-display-*`, `--heading-section-*`,
+`--heading-sub-*`, `--heading-card-*`). Display sizes use tight tracking and
+sub-1 line heights because the words behave like composition, not paragraph
+text.
+
+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.
+
+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
+not a poster. When in doubt, make type quieter and layout clearer.
+
+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.
diff --git a/packages/vessel-light/README.md b/packages/vessel-light/README.md
index 21fe2388..7df8c5dc 100644
--- a/packages/vessel-light/README.md
+++ b/packages/vessel-light/README.md
@@ -23,6 +23,10 @@ ghost pull [...]
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`.
+
## Curation
-Refs exist only where freehand agents drift: form composition, conversation grammar, and overlays. Primitives are the steering layer. Promote a new ref when repeated work proves a gap; do not add coverage upfront because a surface might exist someday.
+Each ref carries an annotation header declaring what it is normative for and what is incidental; together the refs demonstrate every closed set the grammar enumerates (the emphasis ladder, text variants, tones, elevation tiers). Regenerate refs whenever tokens or signature nodes change — a stale exemplar steers harder than any prose. Promote a new ref when repeated work proves a gap; do not add coverage upfront because a surface might exist someday.
diff --git a/packages/vessel-light/demo/.gitignore b/packages/vessel-light/demo/.gitignore
index ba1fd25c..e362c4c5 100644
--- a/packages/vessel-light/demo/.gitignore
+++ b/packages/vessel-light/demo/.gitignore
@@ -1,3 +1,3 @@
-# Eval run artifacts are not committed on mainline; the 2026-07-08 matrix run
-# is parked on the `vessel-eval-artifacts` branch. Regenerate via steering-eval.
+# Eval run artifacts are never committed; regenerate via steering-eval
+# (prompt → generate → shoot → score → report) from asks.md + ballast.md.
out/
diff --git a/packages/vessel-light/demo/README.md b/packages/vessel-light/demo/README.md
index bf8588c0..7f7280f3 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 `pattern.email` — discount them there.
+stack is mandated by `register.email` — discount them there.
## Files
diff --git a/packages/vessel-light/demo/asks.md b/packages/vessel-light/demo/asks.md
index fdf0b540..b600c5bd 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: foundation.tokens, primitive.control, primitive.stack
-poison: pattern.email, pattern.editorial
+expect: grammar.hierarchy, grammar.rhythm, signature.shape
+poison: register.email, register.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: pattern.editorial, foundation.expression, foundation.type
-poison: pattern.email
+expect: register.editorial, signature.palette, signature.type
+poison: register.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: pattern.email, foundation.voice
-poison: pattern.editorial, pattern.data-density
+expect: register.email, signature.temperature
+poison: register.editorial, register.data-density
discount: inter-font-default, segoe-font-default
diff --git a/scripts/check-packed-package.mjs b/scripts/check-packed-package.mjs
index 0b28b049..c6745704 100644
--- a/scripts/check-packed-package.mjs
+++ b/scripts/check-packed-package.mjs
@@ -148,6 +148,39 @@ try {
}
run("pnpm", ["exec", "ghost", "lint", ".ghost"], { cwd: consumerDir });
+ // The vessel-light body must survive packing: corpus, binary fonts, checks.
+ // Run from the consumer root (where the tarball is installed) and target a
+ // separate package dir — `pnpm exec` from a bare subdirectory would escape
+ // to any globally installed ghost.
+ const bodyPackageDir = join("body-consumer", ".ghost");
+ const bodyInit = run(
+ "pnpm",
+ [
+ "exec",
+ "ghost",
+ "init",
+ "--body",
+ "vessel-light",
+ "--package",
+ bodyPackageDir,
+ "--format",
+ "json",
+ ],
+ { cwd: consumerDir },
+ );
+ const bodyOutput = JSON.parse(bodyInit);
+ for (const required of [
+ "manifest.yml",
+ "anti-goal.median.md",
+ "materials/fonts/HKGrotesk-Regular.woff2",
+ "checks/values.md",
+ ]) {
+ if (!bodyOutput.written?.includes(required)) {
+ fail(`packed ghost init --body vessel-light did not write ${required}`);
+ }
+ }
+ run("pnpm", ["exec", "ghost", "lint", bodyPackageDir], { cwd: consumerDir });
+
for (const specifier of PUBLIC_IMPORTS) {
runNode(`await import(${JSON.stringify(specifier)});`, consumerDir);
}
diff --git a/scripts/check-release-tarball.mjs b/scripts/check-release-tarball.mjs
index 9af27706..ed8aa742 100644
--- a/scripts/check-release-tarball.mjs
+++ b/scripts/check-release-tarball.mjs
@@ -7,6 +7,7 @@ import {
readdirSync,
readFileSync,
rmSync,
+ statSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
@@ -65,6 +66,10 @@ try {
"package.json",
"dist/bin.js",
"dist/cli.js",
+ "dist/init-payloads/median/anti-goal.median.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",
"node_modules/cac",
"node_modules/yaml",
"node_modules/zod",
@@ -76,6 +81,20 @@ try {
}
}
+ // Binary payloads must survive packing byte-for-byte.
+ const fontRelative = "materials/fonts/HKGrotesk-Regular.woff2";
+ const packedFont = statSync(
+ join(packageDir, "dist", "init-payloads", "vessel-light", fontRelative),
+ );
+ const sourceFont = statSync(
+ join(ROOT, "packages", "vessel-light", ".ghost", fontRelative),
+ );
+ if (packedFont.size !== sourceFont.size) {
+ fail(
+ `packed font size ${packedFont.size} does not match source ${sourceFont.size}`,
+ );
+ }
+
const packedPkg = JSON.parse(
readFileSync(join(packageDir, "package.json"), "utf8"),
);
@@ -111,6 +130,34 @@ try {
);
}
+ // The vessel-light body installs from the standalone tarball too.
+ const bodyDir = join(tmpRoot, "body-consumer");
+ mkdirSync(bodyDir, { recursive: true });
+ const bodyInit = run(
+ "node",
+ [
+ join(packageDir, "dist", "bin.js"),
+ "init",
+ "--body",
+ "vessel-light",
+ "--format",
+ "json",
+ ],
+ { cwd: bodyDir },
+ );
+ const bodyOutput = JSON.parse(bodyInit);
+ for (const required of [
+ "anti-goal.median.md",
+ "materials/fonts/HKGrotesk-Regular.woff2",
+ "checks/values.md",
+ ]) {
+ if (!bodyOutput.written?.includes(required)) {
+ fail(
+ `release tarball ghost init --body vessel-light did not write ${required}`,
+ );
+ }
+ }
+
const topLevelEntries = readdirSync(extractDir);
if (topLevelEntries.length !== 1 || topLevelEntries[0] !== "package") {
fail("release tarball must extract to a single package/ directory");
diff --git a/scripts/sync-init-payloads.mjs b/scripts/sync-init-payloads.mjs
new file mode 100644
index 00000000..ef9b7871
--- /dev/null
+++ b/scripts/sync-init-payloads.mjs
@@ -0,0 +1,39 @@
+#!/usr/bin/env node
+import { existsSync } from "node:fs";
+import { cp, rm } from "node:fs/promises";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+const ROOT = fileURLToPath(new URL("..", import.meta.url));
+const distInitPayloadsDir = join(
+ ROOT,
+ "packages",
+ "ghost",
+ "dist",
+ "init-payloads",
+);
+
+await rm(distInitPayloadsDir, { recursive: true, force: true });
+
+const sourceInitPayloadsDir = join(
+ ROOT,
+ "packages",
+ "ghost",
+ "src",
+ "init-payloads",
+);
+if (existsSync(sourceInitPayloadsDir)) {
+ await cp(sourceInitPayloadsDir, distInitPayloadsDir, { recursive: true });
+}
+
+await cp(
+ join(ROOT, "packages", "vessel-light", ".ghost"),
+ join(distInitPayloadsDir, "vessel-light"),
+ {
+ recursive: true,
+ filter(source) {
+ const name = source.split(/[\\/]/).at(-1);
+ return name !== ".events" && name !== ".gitignore";
+ },
+ },
+);
| | | |