Skip to content

feat(term): synchronized output (DEC mode 2026) via the sync-output capability - #139

Open
natemoo-re wants to merge 6 commits into
mainfrom
feat/sync-output
Open

natemoo-re wants to merge 6 commits into
mainfrom
feat/sync-output

Conversation

@natemoo-re

@natemoo-re natemoo-re commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Implements synchronized output (DEC mode 2026), previously deferred in renderer-spec §7.8.

When RuntimeCapabilities.syncOutput is true, each full-screen frame is wrapped in BSU/ESU (CSI ? 2026 h / CSI ? 2026 l) so the terminal presents it as one atomic repaint, which prevents tearing mid-frame.

… capability

Full-screen frames wrap in BSU/ESU when RuntimeCapabilities.syncOutput is
true; the flag rides reduce() per frame so a capability flip takes effect
on the next rendered frame with no invalidation signal. Zero-diff frames
emit zero bytes even when wrapped, and line mode is never wrapped.

Renderer-spec 7.9 discharges the deferral recorded in 7.8.
@bombshell-cooper

bombshell-cooper Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Changeset suggestion

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

Why: Changeset package scope does not match the affected packages confidently. Changeset bump does not match the consumer-visible impact.

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

Adds synchronized output (DEC mode 2026) driven by the `sync-output` capability.

When `RuntimeCapabilities.syncOutput` is true, full-screen frames are wrapped in Begin/End Synchronized Update (`CSI ? 2026 h` / `CSI ? 2026 l`) so the terminal presents each frame as one atomic repaint without tearing. The capability is resolved from three channels: a DECRPM 2026 probe reply, an XTGETTCAP `Sync` reply, and detection-time evidence from the `Sync` extended capability in the terminfo entry or the `TMUX` environment variable. Probe replies outrank detection-time evidence in both directions. No-op frames emit nothing regardless of wrapping state; line mode output is never wrapped.

#### Migration

`Capabilities` gains a required `syncOutput` field. Object literals passed to `createTerm` as `terminfo.capabilities` must include it:

```diff
  terminfo: {
    capabilities: {
      colors: 256,
      trueColor: false,
      bce: true,
      autoMargin: true,
      xenl: true,
      altScreen: true,
      styledUnderline: false,
+     syncOutput: false,
    },
  }
```

Review this changeset manually

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

@@ -0,0 +1,7 @@
---

@bombshell-cooper bombshell-cooper Bot Oct 9, 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 package scope does not match the affected packages confidently. Changeset bump does not match the consumer-visible impact.

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

Adds synchronized output (DEC mode 2026) driven by the `sync-output` capability.

When `RuntimeCapabilities.syncOutput` is true, full-screen frames are wrapped in Begin/End Synchronized Update (`CSI ? 2026 h` / `CSI ? 2026 l`) so the terminal presents each frame as one atomic repaint without tearing. The capability is resolved from three channels: a DECRPM 2026 probe reply, an XTGETTCAP `Sync` reply, and detection-time evidence from the `Sync` extended capability in the terminfo entry or the `TMUX` environment variable. Probe replies outrank detection-time evidence in both directions. No-op frames emit nothing regardless of wrapping state; line mode output is never wrapped.

#### Migration

`Capabilities` gains a required `syncOutput` field. Object literals passed to `createTerm` as `terminfo.capabilities` must include it:

```diff
  terminfo: {
    capabilities: {
      colors: 256,
      trueColor: false,
      bce: true,
      autoMargin: true,
      xenl: true,
      altScreen: true,
      styledUnderline: false,
+     syncOutput: false,
    },
  }
```

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Took the Capabilities.syncOutput migration note in df2169b. It's a public type with a newly required field, so the "no new API" line was wrong. I left out the createTermNative item because that function isn't exported from mod.ts, so it's not a consumer-facing change. Package scope is unchanged (@bomb.sh/tty).


Generated by Claude Code

@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Size Increased — +1.6 KB

124.2 KB unpacked

@pkg-pr-new

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

Copy link
Copy Markdown

Open in StackBlitz

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

commit: 3ed7347

Per the approved spec amendment (terminfo-spec §6.1/§6.3/§7.1/§7.2/§8/§9.1,
renderer-spec §7.9): syncOutput is raised only on positive evidence from
three channels.

- Probe row 8: XTGETTCAP Sync (DCS + q 53796e63) as its own request —
  some terminals honor only one capability per XTGETTCAP request, so it
  must not merge into the RGB;Tc query. A value-carrying reply confirms;
  a reply naming Sync without a value denies. Sync replies never touch
  the colordepth tally.
- Static Capabilities gains syncOutput, seeded from the Sync extended
  capability in the parsed terminfo entry and from TMUX environment
  evidence (tmux consumes mode 2026 from inner applications).
- TINV-3 precedence: probe replies outrank detect-time seeding in both
  directions; runtimeFromStatic now flows the seeded field through.

The reply's sequence value is discarded in v1: emission remains the
DEC mode 2026 form, unchanged from §7.9's normative rules.
@natemoo-re
natemoo-re marked this pull request as ready for review October 12, 2026 01:04
Comment thread term.ts Outdated
mode,
row,
dt,
currentCaps.syncOutput ? 1 : 0,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I thought we were going to move capabilities into the native state rather than copy them over on every call.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That was indeed the direction we discussed! Oversight on my part, I'll take another pass at this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 3142ef2: capabilities now live in native renderer state. set_capabilities(ct, flags) stores a CAP_* bitfield on the Clayterm instance, and reduce() reads it from there instead of taking it as an argument. The host pushes flags once at creation and again from term.update() only when a folded event changes them. The native wrapper re-pushes after re-init, because init() zeroes instance state. 34ab9ca then prefixed the renderer bits to avoid colliding with CAP_SYNC_OUTPUT in input.h.


Generated by Claude Code

Capabilities were copied into reduce() on every render. The renderer now
owns them: set_capabilities(ct, flags) stores a CAP_* bitfield on the
Clayterm instance, and reduce() reads it from there.

The host pushes flags at creation and again from term.update() only when
a folded event changes them. init() zeroes instance state, so the native
wrapper re-pushes after every re-init; a resize keeps the wrap.
input.h already defines CAP_SYNC_OUTPUT as a CapabilityEvent key (5).
Any translation unit including both headers silently redefined it.
…put field

Capabilities is a public export and syncOutput is required, so object
literals passed as terminfo.capabilities stop type-checking without it.
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.

2 participants