Skip to content

Adding a component ​

bash
npm run new:component -- ribbon

The scaffold creates src/components/ribbon.ts and its test file, and registers the component. The contract tests then fail until every placeholder is filled in, so npm test tells you what's left.

The definition ​

ts
import type { ComponentDef } from "sayshape";

export const ribbon: ComponentDef = {
  type: "ribbon",
  category: "content",
  description: "A full-width colored strip with a short notice.",
  text: { description: "The notice.", required: true },
  props: {
    tone: {
      type: "enum",
      values: ["accent", "good", "warn", "bad"],
      default: "accent",
      description: "Color role.",
    },
  },
  example: `ribbon "Free shipping this week" tone=good`,

  layout(box) {
    box.h = 32;
  },

  paint(box, ctx) {
    const p = ctx.props(box.node);
    const color = ctx.theme.colors[p.oneOf("tone", ["accent", "good", "warn", "bad"] as const, "accent")];
    const { g } = ctx;
    ctx.roundRect(box.x, box.y, box.w, box.h, ctx.theme.radius.sm);
    g.fillStyle = color;
    g.fill();
    g.font = ctx.font(600, 13);
    g.fillStyle = ctx.theme.colors.onAccent;
    g.textAlign = "center";
    g.textBaseline = "middle";
    g.fillText(p.text(), box.x + box.w / 2, box.y + box.h / 2 + 1, box.w - 16);
  },
};

Fill in everything a model needs: a plain description of when to use it, quoted text if it takes any, every prop with its type, description and default or required, children and parents if relevant, and a short example that parses with no diagnostics. Interactive components register hit regions while painting; components that write a bind= value use ctx.set(p.raw("bind"), value) so paths like t.done work inside each. src/components/input.ts, navigation.ts and map/index.ts are good references.

Tests ​

Add parsing tests with good and bad props, layout at narrow and wide widths, and interaction tests with render(). If it binds, add it to the table in test/write-data.test.ts. The contract tests already check documentation, layout bounds, theme-only colors and keyboard reach.

How documentation stays in sync ​

There's one source for each component, its ComponentDef. Everything else is generated from it:

OutputGenerated byChecked by
Parser validationThe registry, at runtimeUnit and contract tests
The model guidelanguageGuide(), at runtimetest/prompt.test.ts and a fixture of the full guide
The README's component referencenpm run docs:componentsnpm run docs:check in CI
This website's component referencenpm run site:docs, before every site buildtest/site.test.ts parses every example

So after changing a definition, run npm run docs:components for the README; the website regenerates its pages on its next build. Never edit the generated reference by hand: edit the definition.

SayShape v0.1 is an early prototype under the MIT License. Status · GitHub