{
  "name": "ConsoleMessage",
  "type": "component",
  "description": "One line of a line-numbered log: a gutter number and a monospaced message.",
  "export": "@gtmi/strix-react/atoms/console-message",
  "path": "@gtmi/strix-react/atoms/console-message",
  "props": [
    {
      "name": "lineNumber",
      "type": "number",
      "required": true,
      "description": "The gutter number. Rendered aria-hidden and select-none."
    },
    {
      "name": "state",
      "type": "active | loading",
      "default": "active",
      "description": "loading sets aria-busy and changes nothing visually, because Figma binds identical values for both."
    },
    {
      "name": "children",
      "type": "ReactNode",
      "required": true,
      "description": "The line, rendered in a <code>."
    },
    {
      "name": "className",
      "type": "string",
      "description": "Merged through cn(), so a caller's utility wins."
    }
  ],
  "parts": [
    {
      "name": "console-message",
      "description": "Root, via data-slot. Carries data-state and aria-busy when loading."
    },
    {
      "name": "console-message-line-number",
      "description": "The gutter number, aria-hidden and select-none"
    },
    {
      "name": "console-message-text",
      "description": "The line itself, a <code>"
    }
  ],
  "ai_tags": ["console", "log", "code", "line-number", "gutter", "monospace"],
  "since": "0.2.0",
  "figma": {
    "file": "zT3vGm4QlFEI2Jp9nxyFKI",
    "fileName": "Demo components library",
    "node": "22:11297",
    "component": ".console-message"
  },
  "tokens_used": [
    "--color-text-accent-strong",
    "--spacing-gap-200",
    "--text-size-mono-large",
    "--leading-height-mono-large"
  ],
  "notes": "DELIBERATELY ONE LINE, AND NOT A CODE VIEWER. This is the scope decision, and it is what makes the previous decline reversible without inventing anything. The atom supplies the type ramp (Twilio Sans Mono 16/20), the row height (20px) and the two gaps (8px to the body, 12px between numbers) -- and NOTHING about scrolling, wrapping, selection, a copy affordance or how a long line behaves. The earlier decline was right that a code viewer cannot be designed from this; it was wrong that therefore nothing could be built. One row is exactly the part the atom specifies. A caller stacks them and owns the scroll container, which is the same call ChatLog makes about who owns the viewport.\n\nFigma's gutter is seventeen separate hand-drawn text layers, so the atom is a specimen of a log rather than a component with a line-count prop. `lineNumber` is therefore the caller's, not derived.\n\nTHIS IS ONE OF ONLY TWO ATOMS IN THE FILE THAT BIND THE SYNTAX PALETTE, AND BOTH SYNTAX COLOURS ARE DECLINED. It inks the gutter colors/code/3 #7688c2 and the body blue-200 #3acefa. Both map 1:1 onto Strix tokens -- --color-code-3 and --color-code-7 / --color-highlight -- and all eight --color-code-* are declared once in @theme and never re-declared per theme. So they are theme-invariant: all eight pass 4.5:1 on a dark page and ALL EIGHT FAIL on a light one, code-3 at 3.46:1 and the body cyan at 1.84:1. A viewer built from this atom verbatim would be unreadable in light mode rather than merely low-contrast. That is open question 7 in docs/DESIGN_DECISIONS_NEEDED.md and it stays open: authoring a light syntax ramp by eye is how a palette stops being coherent. --color-text-accent-strong (6.98:1 / 12.12:1) carries both roles until it is settled, which is the same substitution ToolCall made for the same token. The contrast test names all three rejected tokens, so reinstating any of them fails.\n\nTHE GUTTER IS aria-hidden, AND THAT IS THE ONE ACCESSIBILITY DECISION HERE. A line number is position rather than content: a screen reader reading \"one GET slash Lookup\" for every row turns a log into noise, and the number conveys nothing the reading order does not already give. It is also select-none, so copying a block of lines does not carry the numbers into the clipboard -- which is what makes a copied log paste back as runnable text.\n\nSTATE IS NOT A VISUAL VARIANT. Active and Loading bind identical fills, inks and geometry in every layer, so whatever distinguishes Loading in the design is not expressed in any bound value -- most likely a caret or an animation the static payload cannot carry. It is expressed as aria-busy rather than invented as a treatment, and a test asserts the two render byte-identical class strings so nobody later 'fixes' it by dimming one.\n\nNo surface, no border, no radius: Figma draws this straight on the page, so the measured pairing is against the page it actually sits on. The atom assumes the page is already dark rather than making it so, which is why the dark-only-surface option in question 7 is not settled by this evidence."
}
