Skip to content

feat: CSS system colors map to the ANSI 16-color palette - #157

Draft
natemoo-re wants to merge 4 commits into
feat/hex-colorsfrom
feat/ansi16-system-colors
Draft

natemoo-re wants to merge 4 commits into
feat/hex-colorsfrom
feat/ansi16-system-colors

Conversation

@natemoo-re

Copy link
Copy Markdown
Member
  • Stacked on feat: hex color strings for directive color fields #146.
  • Color fields accept CSS system colors like Canvas, CanvasText, LinkText, and Highlight, case-insensitive.
  • They render with the terminal's own palette (SGR 30–37 / 90–97 and the bg equivalents), so they follow the user's terminal theme instead of a fixed RGB.
  • Canvas/CanvasText and friends mean "terminal default". Used in the other slot (bg: "CanvasText"), they paint the real default color the terminal reported in its theme probe. With no probe answer, they fall back to that slot's default.
  • Plain RGB colors are byte-for-byte unchanged on the wire. Terminal colors reuse the "default color" bit in the existing 32-bit color words, so cells don't grow.
  • A bg transition to or from a system color switches halfway instead of blending, the same way CSS handles values it can't interpolate.
  • Spec commits are separate from the implementation commits, so the specs can be reviewed first.

…§8.5)

System color keywords stop being reserved and resolve to terminal-themed
colors: the terminal default foreground/background, or palette entries
0-15, per a fixed keyword table. currentcolor stays reserved.

The six default keywords (Canvas, CanvasText, ButtonFace, ButtonText,
Field, FieldText) all mean "the terminal default for this attribute".
CanvasText in a bg field is the default background, and Canvas in a fg
field the default foreground. SGR has no way to paint the default
foreground as a background, and approximating it with a palette entry is
wrong on either light or dark themes, so the field decides. Rejecting
the cross-field keywords was the alternative; it would need a role-aware
resolver, validator, and literal checker for little gain.

Encoding (§12.1, descriptive). Cells and the text/border color words
share one encoding: bit 31 clear is 0x00RRGGBB, bit 31 set is
terminal-themed, with 0x80000000 the default and 0x80000100|n palette n.
bit 31 is already ATTR_DEFAULT and the fg high byte has no free bits, so
reusing the low bits under it costs no cell width and keeps diffing an
exact compare (each color has one canonical word). It forces the
wire-only "keep existing bg" sentinel off 0x80000000 (now 0x40000000),
since an explicit Canvas bg must overwrite while an omitted bg keeps.

Element bg keeps its full ARGB word, since alpha 0 suppresses the fill
and every bit is in use, so a terminal-themed element bg sets a new
property-mask bit instead. sRGB colors stay bit-identical on the wire.
Widening the cell with a separate flags word was the other option; it
grows every cell by 4 bytes for information that fits in unused bits.

Transitions can't interpolate a color the renderer has no RGB for, so a
terminal-themed endpoint switches discretely at eased progress 0.5,
matching CSS discrete animation.
resolveColor() checks a lowercase keyword table before hueful and
returns a TerminalColor ({ ansi: index | "default" }) for system colors,
so validate() accepts them through the existing css-color format and the
CheckedColor literal check gains them via the same table.

The packer turns a TerminalColor into a color word: 0x80000000 for the
default, 0x80000100|n for palette n. Text fg keeps bit 31 in the attrs
byte as before, so an omitted color and a default keyword pack
identically. The omitted-bg sentinel for text and border words moves to
BG_KEEP (0x40000000) so an explicit Canvas bg can be 0x80000000 and
overwrite. Element bg keeps its ARGB word; PROP_BG_TERMINAL (0x100) marks
the word as a color word instead.

Element bg reaches render_rect through Clay_Color floats, so the C side
carries a terminal color as r = -1 with g = palette index (or -1 for the
default) and a = 255, which survives the float round trip and keeps
Clay's a > 0 rectangle check. color() maps it back to the cell word.
lerp_color() switches discretely at 0.5 when either endpoint carries
that marker, instead of interpolating a meaningless channel.

emit_attr() emits SGR 30-37/90-97 and 40-47/100-107 for palette words
and leaves defaults to the leading SGR reset, as before. Cells compare
with the same integer equality; each color has one canonical word.
A default keyword named in the other attribute's field (CanvasText in a bg
field, Canvas in a fg field, including border fg) now resolves to the
OSC 10/11-probed theme color for the attribute it names, as an ordinary
opaque sRGB color that packs and interpolates like rgba(). Falls back to the
field's own terminal default when the theme hasn't reported that color.
pack() gains an optional trailing theme parameter; term.render() passes it.
pack() gains an optional trailing theme parameter (RuntimeCapabilities.theme
shape); term.render() passes currentCaps.theme every frame. color.ts's
systemColors now tags each default keyword with which attribute it names
(fg-default/bg-default) instead of a single undifferentiated "default", and
resolveForField() picks the terminal default for a same-field keyword or the
probed theme color for a cross-field one, falling back to the field's own
default when that color hasn't been probed yet.

Covers pack-level byte identity against rgba(), the no-theme fallback, and
render-through-Term cases (text bg, element bg, border side fg) that pick up
a theme color after term.update() folds in an OSC 10/11 CapabilityEvent.
@bombshell-cooper

Copy link
Copy Markdown
Contributor

Changeset suggestion

The current changeset no longer matches this PR. This review contains a corrected replacement.

Why: Changeset should start with an approved active present-tense verb. Changeset package scope does not match the affected packages confidently. Changeset does not name the affected public API confidently.

View the proposed changeset
---
'@bomb.sh/tty': minor
---

Adds support for CSS Color 4 system color keywords (`Canvas`, `CanvasText`, `LinkText`, `GrayText`, `Highlight`, `AccentColor`, and others) in all color fields, case-insensitively.

Each keyword maps to a terminal-themed color rather than a fixed sRGB value, so output follows the user's light or dark terminal theme. Background-default keywords (`Canvas`, `ButtonFace`, `Field`) and foreground-default keywords (`CanvasText`, `ButtonText`, `FieldText`) resolve to the terminal's own default colors when used in their matching field. Palette keywords (`LinkText` → blue, `GrayText` → bright black, etc.) emit ANSI 16-color SGR codes so the terminal's theme palette applies.

Used in the opposite field, a default keyword resolves to the terminal's probed color (from the OSC 10/11 theme report) as an opaque truecolor value — for example, `text({ bg: "CanvasText" })` paints the terminal's actual default foreground as a truecolor background once the theme report arrives, and falls back to the terminal default background until then.

Transitions between a terminal-themed color and any other color change discretely at 50% progress rather than interpolating.

#### Migration

`Canvas` and `CanvasText` were previously rejected as invalid color strings. Any code that relied on that rejection — for example, treating them as errors in validation — will now accept them as valid system colors.

```diff
-// Previously threw / failed validation:
-text("Hi", { color: "Canvas" });
-open("box", { bg: "CanvasText" });
+// Now valid — resolves to the terminal's default foreground/background:
+text("Hi", { color: "Canvas" });   // terminal default foreground
+open("box", { bg: "CanvasText" }); // terminal default bg, or probed fg as truecolor
```

`currentcolor` remains reserved and invalid.

Review this changeset manually

If this draft is correct, react with 🚀 and Cooper will commit it to this branch.

@@ -0,0 +1,19 @@
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changeset needs revision.

Changeset should start with an approved active present-tense verb. Changeset package scope does not match the affected packages confidently. Changeset does not name the affected public API confidently.

View the proposed replacement
---
'@bomb.sh/tty': minor
---

Adds support for CSS Color 4 system color keywords (`Canvas`, `CanvasText`, `LinkText`, `GrayText`, `Highlight`, `AccentColor`, and others) in all color fields, case-insensitively.

Each keyword maps to a terminal-themed color rather than a fixed sRGB value, so output follows the user's light or dark terminal theme. Background-default keywords (`Canvas`, `ButtonFace`, `Field`) and foreground-default keywords (`CanvasText`, `ButtonText`, `FieldText`) resolve to the terminal's own default colors when used in their matching field. Palette keywords (`LinkText` → blue, `GrayText` → bright black, etc.) emit ANSI 16-color SGR codes so the terminal's theme palette applies.

Used in the opposite field, a default keyword resolves to the terminal's probed color (from the OSC 10/11 theme report) as an opaque truecolor value — for example, `text({ bg: "CanvasText" })` paints the terminal's actual default foreground as a truecolor background once the theme report arrives, and falls back to the terminal default background until then.

Transitions between a terminal-themed color and any other color change discretely at 50% progress rather than interpolating.

#### Migration

`Canvas` and `CanvasText` were previously rejected as invalid color strings. Any code that relied on that rejection — for example, treating them as errors in validation — will now accept them as valid system colors.

```diff
-// Previously threw / failed validation:
-text("Hi", { color: "Canvas" });
-open("box", { bg: "CanvasText" });
+// Now valid — resolves to the terminal's default foreground/background:
+text("Hi", { color: "Canvas" });   // terminal default foreground
+open("box", { bg: "CanvasText" }); // terminal default bg, or probed fg as truecolor
```

`currentcolor` remains reserved and invalid.

@github-actions

Copy link
Copy Markdown

Size Increased — +5.8 KB

138.2 KB unpacked

@pkg-pr-new

pkg-pr-new Bot commented Oct 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@bomb.sh/tty@157

commit: d9caccc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant