Skip to content

Fix device attributes report for VT100 features - #64

Merged
tomlm merged 8 commits into
mainfrom
copilot/fix-device-attributes-report
Aug 28, 2026
Merged

Fix device attributes report for VT100 features#64
tomlm merged 8 commits into
mainfrom
copilot/fix-device-attributes-report

Conversation

Copilot AI commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Device attributes now describe this emulator instead of copying xterm's answer.

A DA reply is a promise: every attribute in it names a sequence the program will now go ahead and send. The previous list claimed capabilities this tree doesn't implement, so those sequences went out and nothing happened — the screen the program believed it drew wasn't the screen that was there.

What it reports now

  • DA1CSI ? 62 ; 22 c, or CSI ? 62 ; 4 ; 22 c with Sixel enabled. 62 is service class 2 (VT220), the level whose core this code does implement: scrolling regions, insert/delete line and character, erase character, the alternate buffer, DECSC/DECRC.
  • DA2CSI > 1 ; 200 ; 0 c. Terminal type 1 is a VT220, agreeing with the 62 rather than contradicting it the way the old 0 (VT100) did. Pv is the package version flattened to one number.

Attributes 1 (DECCOLM), 2 (printer port), 6 (DECSCA), 9 (NRCS), 15 (technical characters) and 21 (DECSLRM) were each checked against the tree and dropped, because the code behind them is absent. Two of those had teeth: a program reading 21 sets left and right margins and draws inside them, and one reading 2 pushes a print job through a printer-controller mode this emulator never enters, which puts the job on the screen.

Two more fixes in the same handler

  • A DA carrying a non-zero parameter is a reply, not a request, and is now ignored. Two of these wired together used to answer each other forever.
  • CSI ? c was answered with the secondary reply? sets the same coarse isPrivate flag that > does, so the program got the answer to a question it hadn't asked while still waiting for the one it had. Dispatch is now on the identifier.

Seven tests cover the replies, the non-zero parameter, the private marker and the tertiary request. Full suite: 1218/1218.

Fixes #51

Agent-Logs-Url: https://github.com/tomlm/XTerm.NET/sessions/ce8fccb7-68cb-4a0f-8f1d-40cb56a1cb7c

Co-authored-by: tomlm <17789481+tomlm@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Perf comparison

3 run(s) of each side, alternating on one machine. Allocation is a count and is gated exactly. Time is a measurement, so its gate is derived from the spread this job just observed in itself rather than fixed in advance.

corpus bytes/char gen0/Mchar ns/char Δ time noise gate
scroll-ascii 0.00 → 0.00 0.00 → 0.00 3.35 → 3.36 +0.4% ±3% 8%
sgr-churn 0.00 → 0.00 0.00 → 0.00 8.81 → 8.77 -0.5% ±2% 7%
truecolor 0.00 → 0.00 0.00 → 0.00 9.61 → 9.56 -0.5% ±2% 6%
alt-redraw 0.00 → 0.00 0.00 → 0.00 13.65 → 13.63 -0.2% ±0% 5%
unicode 7.66 → 7.66 0.45 → 0.45 33.74 → 34.11 +1.1% ±2% 6%
flood 0.00 → 0.00 0.00 → 0.00 94.59 → 94.21 -0.4% ±1% 5%

Each corpus is gated at max(5%, 3 × its own noise). A wide noise column means this runner was busy and the timing half of the table should be read as advisory; the allocation half is exact either way.

assemblies measured
  • base: XTerm.NET 2.0.0.0 mvid:77b5c893-a742-4df4-bd13-b392ce48fff0
  • head: XTerm.NET 2.0.0.0 mvid:acaf5fd5-97a2-4548-a219-da97016612c4

@tomlm

tomlm commented Aug 28, 2026

Copy link
Copy Markdown
Owner

@claude, take over this issue/pr, it's yours now

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown

Claude finished @tomlm's task in 12m 37s —— View job


Took over PR #64 — device attributes now describe this emulator

  • Gather context (repo layout, current diff, DA code paths)
  • Audit which DA attributes this emulator actually implements
  • Rewrite the DA implementation
  • Update/extend tests
  • Build + run tests locallycould not, see below
  • Commit and push (266078f)

What was wrong with the WIP reply

CSI ? 64 ; 1 ; 2 ; 4 ; 6 ; 9 ; 15 ; 21 ; 22 c is xterm's attribute list, not this code's. A DA reply is a promise: every attribute in it names a sequence the program will now go ahead and send. Listing one we don't implement doesn't flatter the emulator, it breaks it — the sequence goes out, nothing happens, and the screen the program believes it drew isn't the screen that's there. That's the same failure the existing Sixel comment in this method warns about, pointed the other way.

Checked one at a time against the tree:

attr meaning in this repo?
1 132 columns (DECCOLM) TerminalMode.ColumnMode is in the enum, SetCSIMode has no case for it
2 printer port (MC) ❌ no media copy command in CsiCommand
4 Sixel ✅ option-gated
6 selective erase (DECSCA)
9 national replacement sets Charsets holds only the default, the line drawing set and UK
15 technical characters
21 horizontal scrolling (DECSLRM) ❌ no left/right margins
22 ANSI colour

Two of the absent ones have teeth. A program that reads 21 sets left and right margins and then draws inside them. One that reads 2 pushes a print job through printer-controller mode, which an emulator that never enters that mode prints onto the screen instead. 1 and 2 were claimed by main too, so this is a fix there as well, not just to the WIP.

What it reports now

  • DA1CSI ? 62 ; 22 c, and CSI ? 62 ; 4 ; 22 c with Sixel on. 62 is service class 2 (VT220) — the level whose core this code does implement: scrolling regions, insert/delete line and character, erase character, the alternate buffer, DECSC/DECRC. (Same reply foot sends, for what it's worth.)
  • DA2CSI > 1 ; <version> ; 0 c. Terminal type 1 is a VT220, so it agrees with the 62 instead of contradicting it the way the old 0 (VT100) did. Pv is the package version flattened to one number (2.0 → 200).

Two more things that fell out of reading the same handler

  • A DA with a non-zero parameter is a reply, not a request. It was answered anyway; two terminals wired together would answer each other forever. Now ignored. (InputHandler.cs:2938)
  • CSI = c, the tertiary DA, was answered with the primary reply"=" isn't ">", and those were the only two cases, so it fell through. The program got the answer to a question it hadn't asked while still waiting for the one it had. There's no unit ID to report, so it now goes unanswered, like every terminal that doesn't implement DECRPTUI. Dispatch is on the identifier rather than the coarse isPrivate flag, which "?", "=" and ">" all set.

Tests updated for the new replies, plus two new ones covering the non-zero parameter and the tertiary request.

⚠️ Not verified locally

I couldn't run dotnet build or dotnet test — every invocation was refused by this job's tool permissions, so CI on this push is the first real compile. The changes are small and self-contained (one method, two new members, one call site), but I'm flagging it rather than claiming a green run I didn't get. If you want me to iterate on a CI failure, re-trigger me on this PR.
· branch copilot/fix-device-attributes-report

github-actions Bot and others added 2 commits August 28, 2026 18:45
The DA reply is a promise. Every attribute in it names a sequence the
program will now go ahead and send, so listing one the emulator does not
implement does not flatter it, it breaks it: the sequence goes out,
nothing happens, and the screen the program believes it drew is not the
screen that is there.

The WIP reply, CSI ? 64 ; 1 ; 2 ; 4 ; 6 ; 9 ; 15 ; 21 ; 22 c, is xterm's
list rather than this code's. Checked one at a time against the tree:
132 columns is not implemented (TerminalMode.ColumnMode is in the enum,
SetCSIMode has no case for it), there is no media copy command, no
DECSCA, no national replacement sets (Charsets holds the default, the
line drawing set and UK), no technical characters, and no left and right
margins. Two of those have teeth -- a program that reads 21 sets margins
and draws inside them, and one that reads 2 pushes a print job through a
printer-controller mode we never enter, so it lands on the screen.

What is left is what is real: CSI ? 62 ; 22 c, a VT220 with ANSI colour,
plus 4 when Sixel is switched on, which is the one attribute programs
visibly act on. Secondary DA now says terminal type 1, a VT220, so it
agrees with the 62 instead of contradicting it as the old 0 did, and
carries the package version as Pv.

Two smaller things fall out of reading the same handler. A DA with a
non-zero parameter is a reply, not a request, and answering it means two
terminals wired together answer each other forever. And "=c", the
tertiary DA, used to be answered with the primary reply, because "=" is
not ">" and those were the only two cases; there is no unit ID to give,
so it now goes unanswered like every terminal without DECRPTUI.

Fixes #51

Co-authored-by: Tom Laird-McConnell <17789481+tomlm@users.noreply.github.com>
@tomlm

tomlm commented Aug 28, 2026

Copy link
Copy Markdown
Owner

@claude I have enabled dotnet tool and when you are done, change title to remove [WIP] and remove Draft status

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown

Claude finished @tomlm's task in 4m 37s —— View job


Verified — and one comment in the last push was telling the wrong story

  • Read repo instructions + current diff
  • Verify the build and the full test suite
  • Correct two comments that misdescribed the bug being fixed
  • Push (750f511) and confirm CI green
  • Remove [WIP] from the title, mark ready for review — I can't, see below

Verification

dotnet is still refused in this job — .claude/settings.json on the branch allows Bash(dotnet:*), but the action doesn't appear to pick project settings up; dotnet --version came back "requires approval" like everything else. The fix is claude_args: '--allowed-tools Bash(dotnet:*),Bash(gh pr *)' in .github/workflows/claude.yml (the file already has that line commented out at :49). I can't edit it myself — the GitHub App can't touch .github/workflows.

So I verified through CI instead, which is a real run of the real commit rather than a claim:

build tests
77b60c4 (last push) 0 warnings, 0 errors 1217 / 1217
750f511 (this push) 0 warnings, 0 errors 1218 / 1218

All seven DA tests pass by name in the second log. Claude Code Review is red on both, for a reason that has nothing to do with the code: Workflow initiated by non-human actor: claude (type: Bot). Add bot to allowed_bots list. It refuses to review a bot's push.

The correction

Reading the dispatch path with the code in front of me, the claim in my last comment about CSI = c was wrong. ToCsiCommand (Common/CommandExtensions.cs:55) strips only ? and > before the lookup, so "=c" never resolves to a command at all — it lands on CsiCommand.Unknown and always has. It was not being answered with the primary reply, and "=" does not set IsPrivateMode either.

The identifier that was mishandled is "?c". It is not the secondary DA, but ? sets the same coarse isPrivate flag that > does, so it used to be handed the secondary reply — the answer to a question the program hadn't asked, while it was still waiting for the one it had. Dispatching on the identifier fixes that; the comments now say so, and HandleCsi_DA_PrivateMarker_IsNotAnsweredWithTheSecondaryReply pins it. The tertiary test stays as a guard on the observable behaviour, with its reasoning rewritten to match what the code does.

Nothing about the reply strings changed. The substance of the PR still stands:

  • DA1CSI ? 62 ; 22 c, or CSI ? 62 ; 4 ; 22 c with Sixel on. The list is the intersection of the DA attribute numbers with what this tree implements, and nothing else — 1, 2, 6, 9, 15 and 21 were checked one at a time and are absent because the code behind them is. A DA reply is a promise: a program that reads 21 sets left and right margins and draws inside them, and one that reads 2 pushes a print job through a printer-controller mode this emulator never enters, which puts the job on the screen.
  • DA2CSI > 1 ; 200 ; 0 c. Type 1 is a VT220, agreeing with the 62 rather than contradicting it the way the old 0 did.
  • A DA carrying a non-zero parameter is a reply, not a request, and is now ignored — two of these wired together used to answer each other forever.

What I could not do

I'm unable to change the title or clear the draft flag: gh isn't in this job's allowed tools and I have no PR-edit tool, so both need doing by hand or by adding Bash(gh pr *) to claude_args. Everything else asked for is done and green.
· branch copilot/fix-device-attributes-report

github-actions Bot and others added 2 commits August 28, 2026 18:57
The tertiary DA never reached DeviceAttributes: ToCsiCommand strips only
"?" and ">" before the lookup, so "=c" resolves to Unknown. The comments
claimed it fell through to the primary reply, which was the wrong story
about the wrong identifier.

The identifier that did go wrong is "?c" -- it sets isPrivate the same as
">c", so it used to be handed the secondary reply. Pinned with a test.

Co-authored-by: Tom Laird-McConnell <17789481+tomlm@users.noreply.github.com>
@tomlm

tomlm commented Aug 28, 2026

Copy link
Copy Markdown
Owner

@claude I fixed permissions for gh

@claude

claude Bot commented Aug 28, 2026

Copy link
Copy Markdown

Claude finished @tomlm's task in 1m 40s —— View job


Done — title cleared, draft lifted, and verified locally at last

  • Confirm gh works in this job
  • Remove [WIP] from the title
  • Mark the PR ready for review
  • Rewrite the WIP checklist body as a description of the change
  • Build + run the suite locally — this worked too

gh and dotnet are both live now, so I finally ran the thing I'd only been able to infer from CI:

Passed!  -  Failed: 0, Passed: 1218, Skipped: 0, Total: 1218

All seven DA tests pass by name locally (..._Primary_ReportsDeviceAttributes, ..._Primary_OmitsSixelWhenDisabled, ..._Secondary_ReportsTerminalId, ..._IgnoresNonZeroParameter ×2, ..._Tertiary_IsNotAnswered, ..._PrivateMarker_IsNotAnsweredWithTheSecondaryReply). CI on the current head is green across all three workflows, including Claude Code Review, which was previously refusing to run on a bot's push.

Two judgment calls worth flagging

I rewrote the PR body. It was still the WIP task checklist with most boxes unticked, which reads oddly on a PR that's now asking for review. It's replaced with a description of what the change does and why those six attributes were dropped; Fixes #51 is preserved. Revert it if you'd rather keep the original.

I left the title's wording alone — it's now Fix device attributes report for VT100 features, with only the prefix removed, as you asked. But VT100 is the one word in it that the change contradicts: the point of the PR is that this emulator reports as a VT220 (62 / type 1), and the old VT100-flavoured 0 in DA2 is part of what got fixed. Something like Report the device attributes this emulator actually implements would match the diff. Say the word and I'll change it — I didn't want to edit past what you'd asked for.

One thing I noticed but did not touch

The build emits a single warning, pre-existing and unrelated to this PR:

SelectionTests.cs(109,17): warning xUnit1013: Public method
'SelectionText_UsesLineFeedLineEndings' on test class 'SelectionTests'
should be marked as a Fact.

That test has no [Fact], so it has never run — it's a silent hole in the suite rather than a style nit. Out of scope here; worth a separate issue. Fix this →

No code changed in this run — the branch is unchanged since 750f511.

--- · branch copilot/fix-device-attributes-report

@claude claude Bot changed the title [WIP] Fix device attributes report for VT100 features Fix device attributes report for VT100 features Aug 28, 2026
@claude
claude Bot marked this pull request as ready for review August 28, 2026 19:16
@tomlm
tomlm merged commit ec8e32d into main Aug 28, 2026
3 checks passed
@tomlm
tomlm deleted the copilot/fix-device-attributes-report branch August 28, 2026 20:04
tomlm added a commit that referenced this pull request Aug 28, 2026
"What is mapped now" left >q out of the list of explicitly mapped private
identifiers, while the Summary says everything not listed falls out as
Unknown and is ignored. >q is in the map, resolves to SelectCursorStyle,
and InputHandler splits it back out to XTVERSION -- so a reader following
this document concluded CSI > Ps q is ignored when it is answered, on the
one row the table above says this change exists to fix. It is now listed,
and called out as the one deliberate shared entry.

"the dispatcher no longer needs to re-decide which command it is holding"
was true of XTSMGRAPHICS and false as the general claim it read as, since
>q re-decides on identifier.PrivateMarker() in this same branch. The
sentence now says what the difference is: an isPrivate flag cannot tell '?'
from '>', while the marker is the same byte the map matched on.

Also stale or missing:
- "left the other six" predates main fixing XTVERSION (#63) and the ?c/=c
  device attributes case (#64) in the dispatcher. Three of the seven had
  been patched one at a time, which is a better version of the same point.
- The table omitted CSI > Pm T (XTRESTTITLE), which this change also stops
  running as SCROLL DOWN.
- The mapped list said nothing about the bare "q" now being absent, which
  is the intermediate-byte half of the same exactness rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015yLt7uosRs4EnvTpR3MSRD
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Device attributes report VT100 (DA1 ?1;2c, DA2 >0;10;0c) — too low for the features actually implemented

2 participants