Skip to content

feat: color-encoding tiers derived from capability evidence - #148

Draft
natemoo-re wants to merge 2 commits into
mainfrom
feat/color-encoding-modes
Draft

natemoo-re wants to merge 2 commits into
mainfrom
feat/color-encoding-modes

Conversation

@natemoo-re

Copy link
Copy Markdown
Member

Closes #60.

What

The renderer now resolves a color tier from its capability snapshot instead of hardcoding 24-bit truecolor:

trueColor      → "truecolor"   (RGB/Tc, COLORTERM, max_colors ≥ 2²⁴, probe)
colors ≤ 16    → "16"          (same boundary as terminfo-spec §6.3 denial mapping)
otherwise      → "256"
  • No colorMode override — capability evidence is the single lever (TINV-3); an override would be a second source of truth that can contradict probe responses. Callers control the encoding the same way they control every other capability: detectTerminal() or folding probe responses through term.update().
  • Downmapping lives in emit_attr (where the color is known): nominal-16 exact match → exact cube → grayscale ramp → nearest cube; ties resolve to the lowest index. Standard palette colors keep their classic indices — rgba(255,0,0) → \x1b[31m / \x1b[41m under "16", \x1b[38;5;1m under "256" — exactly the pins from 💡 Color-encoding modes (16-color / 256-color) derived from terminfo #60's repro.
  • Capabilities live in WASM (resolves the open question from feat: term.update() folds CapabilityEvents into runtime capabilities #133 / Charles' review): the colors value and truecolor flag are pushed once at creation and after capability folds, never copied per render. Resizes must not reset them to baseline.
  • Byte-identity guarantee (CI-2): the truecolor tier is byte-identical to the historical renderer.

Spec

New focused feature spec specs/color-encoding-spec.md (renderer-spec §7.8 explicitly defers color encoding to "a focused feature specification"). Conforming edits: §7.8 drops color encoding from its deferred list; §8.1 cross-references the new spec from the terminfo parameter. No public API signatures change.

⚠️ Default-behavior change

With no color evidence, the baseline {colors: 256, trueColor: false} now resolves to "256" — the renderer emits 256-color SGR where it previously hardcoded truecolor. This is the terminfo-spec §7.1 evidence model applied to emission ("Truecolor is not assumed at baseline; it requires positive evidence") and matches the TermOptions doc from #133 ("the renderer uses the 256-color baseline"). The issue's "byte-identical to today" guarantee holds for truecolor-evidenced terminals (CI-2). The three files that pin \x1b[38;2; bytes (term/border/color tests) now inject a shared truecolor-evidence fixture so they continue to exercise the truecolor tier explicitly; the new default is pinned once in test/color-encoding-modes.test.ts.

Tests

  • test/color-encoding-modes.test.ts: all three tiers, tier-resolution boundaries (8/16/17, colors ≥ 2²⁴, granted flag), bg codes, palette round-trip (exhaustive 256 sweep), defaults (CI-6), truecolor byte-identity
  • Existing suites green: 27 tests / 328 steps → 27+1 passed, 0 failed
  • deno task verify clean; changeset included

Refs:

The renderer resolves an emission tier (truecolor / 256 / 16) from its
capability snapshot instead of hardcoding 24-bit truecolor, per
specs/color-encoding-spec.md. Capability state is authoritative in the
WASM instance; the host pushes it at creation and after capability
folds, and a tier change forces a complete redraw. Standard palette
colors keep their classic indices when downmapped.

Closes #60
@bombshell-cooper

bombshell-cooper Bot commented Oct 12, 2026 •

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 bump does not match the consumer-visible impact. Changeset does not name the affected public API confidently. Changeset contains implementation or roadmap noise. Changeset contains unsupported consumer-facing claims.

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

Changes the renderer's SGR color encoding to derive from terminal capability evidence instead of always emitting 24-bit truecolor sequences.

Terminals with truecolor evidence (`RGB`/`Tc` terminfo extension, `COLORTERM`, `max_colors ≥ 2²⁴`, or a `colordepth` probe response via `term.update()`) receive byte-identical truecolor output. Without evidence, cells emit 256-color SGR (`\x1b[38;5;Nm`) when the resolved palette exceeds 16 colors, or 4-bit SGR (`\x1b[31m`) at or below 16. Standard palette colors keep their classic indices; grays use the 240-step grayscale ramp; everything else rounds to the 6×6×6 cube. A tier change between frames forces a complete redraw.

**The default behavior changed:** a `Term` created without color evidence now emits 256-color bytes where it previously emitted truecolor. Run `detectTerminal()` and pass its result to `createTerm`, or fold probe responses with `term.update()`, to restore truecolor output on terminals that support it.

#### Migration

```diff
  import { createTerm } from "@bomb.sh/tty";
+ import { detectTerminal } from "@bomb.sh/tty";

- const term = await createTerm({ width, height });
+ const terminfo = await detectTerminal();
+ const term = await createTerm({ width, height, terminfo });
```

Review this changeset manually

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

Draft updated. Remove and re-add your 🚀 reaction to approve this version.

@@ -0,0 +1,20 @@
---

@bombshell-cooper bombshell-cooper Bot Oct 12, 2026 •

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 bump does not match the consumer-visible impact. Changeset does not name the affected public API confidently. Changeset contains implementation or roadmap noise. Changeset contains unsupported consumer-facing claims.

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

Changes the renderer's SGR color encoding to derive from terminal capability evidence instead of always emitting 24-bit truecolor sequences.

Terminals with truecolor evidence (`RGB`/`Tc` terminfo extension, `COLORTERM`, `max_colors ≥ 2²⁴`, or a `colordepth` probe response via `term.update()`) receive byte-identical truecolor output. Without evidence, cells emit 256-color SGR (`\x1b[38;5;Nm`) when the resolved palette exceeds 16 colors, or 4-bit SGR (`\x1b[31m`) at or below 16. Standard palette colors keep their classic indices; grays use the 240-step grayscale ramp; everything else rounds to the 6×6×6 cube. A tier change between frames forces a complete redraw.

**The default behavior changed:** a `Term` created without color evidence now emits 256-color bytes where it previously emitted truecolor. Run `detectTerminal()` and pass its result to `createTerm`, or fold probe responses with `term.update()`, to restore truecolor output on terminals that support it.

#### Migration

```diff
  import { createTerm } from "@bomb.sh/tty";
+ import { detectTerminal } from "@bomb.sh/tty";

- const term = await createTerm({ width, height });
+ const terminfo = await detectTerminal();
+ const term = await createTerm({ width, height, terminfo });
```

@github-actions

Copy link
Copy Markdown

Size Increased — +2.9 KB

125.5 KB unpacked

@pkg-pr-new

pkg-pr-new Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 23559ee

CI's lint job flagged the unused InputEvent import and the two
control-character regexes (the same no-control-regex suppression
term.test.ts uses). Local verify runs fmt only, which is why lint
slipped; deno lint is now clean on this branch.
@natemoo-re
natemoo-re force-pushed the feat/color-encoding-modes branch from 262c219 to 23559ee Compare October 12, 2026 01:17
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.

💡 Color-encoding modes (16-color / 256-color) derived from terminfo

1 participant