docs/CONFORMANCE.mdVoir la source ↗
Sur cette page

Corta — Feature Conformance

Documentation index · Project overview

What Corta must implement, in what order, and how completeness is measured. Priorities:

  • P0 — without it the terminal is unusable; vim/tmux/htop visibly break, or programs hang.
  • P1 — expected by any modern terminal; needed to be a daily driver.
  • P2 — enhancement, deferrable, or explicitly declined.

A note on "features". Coloured git output, SSH and plain tmux are not features. They are consequences of a correct parser and a correct grid. Nothing in this repository is written "for git". Build the primitives; the applications light up on their own.


1. VT Parsing and Screen Model

1.1 Rendering correctness

CapabilityTierNotes
UTF-8 byte stream decodingP0The foundation
SGR: ANSI / 256 / 24-bit true colour (semicolon and ITU T.416 colon forms), bold, underline (single, double 21 / 4:2, curly 4:3), reverseP0
Cursor movement, absolute positioning, save/restoreP0
CHA / HPA / HPR / VPA / VPR — absolute and relative positionP0A TUI that lays a line out in segments jumps between columns rather than writing spaces; without these the whole screen collapses
Erase display / line, insert / delete lines and columnsP0
Alternate screen (?1049)P0vim/less/htop depend on it entirely
Scroll region (DECSTBM)P0tmux and vim status lines depend on it
Character width: CJK wide, emoji, combining, zero-widthP0Wrong widths mean misaligned CJK text; wide pairs draw scaled into their two-cell box (M3.5)
ScrollbackP0Ring buffer, variable-length rows
Soft-wrap flag per lineP0Required by reflow, selection and search — see DECISIONS.md D03
Reflow on resizeP1Must be incremental; live window drag fires continuously
Synchronized output (?2026)P1Neovim, tmux ≥ 3.4 and fzf use it; absence causes visible tearing
Cursor style (DECSCUSR)P1
Cursor visibility (?25, DECTCEM)P0vim, htop, fzf and Ink programs (Claude Code) hide the cursor while they draw; terminal-wide, kept across the alternate screen, shown again by RIS and DECSTR
Autowrap (?7, DECAWM) and origin mode (?6, DECOM)P1Without autowrap the last column is overwritten and a wide character that would cross the margin is not written; under DECOM, CUP/HVP/VPA and the position reports count from the scroll region's top, and DECSC saves the mode
OSC 0 / 2 — set window and tab titleP1Set only. Query is never implemented, see SECURITY.md §2.2
OSC 7 — report working directoryP1Prerequisite for new tabs and splits inheriting the cwd
OSC 4 / 104 — indexed palette query, set, reset (B06)P1256-entry palette, seeded from the theme (0–15) and xterm's cube/greyscale (16–255); an override now repaints, not just answers a query — docs/DESIGN.md §7
OSC 5 / 105 — special colours query, set, reset (B06)P2Five fixed slots (bold/underline/blink/reverse/italic), no themed default — see SpecialColors; docs/DESIGN.md §7
CSI s / u (SCOSC/SCORC) — cursor save/restore alias (B06)P1Corta has no DECLRMM, so these are unconditional aliases for DECSC/DECRC, matching xterm without left/right margins
?45 — reverse-wraparound mode (B06)P2BS/CUB cross onto the row above when it auto-wrapped into this one; off by default, matching xterm. Not DECBKM, which is the separate ?67 backarrow-key mode
Bracketed paste (?2004)P0A safety feature, not a convenience — see SECURITY.md §2.3
Mouse reporting (?1000/?1002/?1003, SGR ?1006)P1Press/release, wheel, cell-coalesced drag and motion; configurable local-selection override
Focus reporting (?1004)P2Implemented; Neovim autoread and tmux focus events
Alternate scroll (?1007)P1The wheel sends arrow keys on the alternate screen when mouse reporting is off, so less, man and git log scroll; on by default, as in Terminal.app and iTerm2 (xterm's is off)
OSC 8 — hyperlinksP2Implemented; display text and target may differ — see SECURITY.md §2.4
OSC 1, title push/pop (CSI 22/23 ; 0/1/2 t)P1Icon name retained separately; independent stacks keep the newest ten entries; empty pops do nothing. Title reporting remains refused
OSC 10–12 / 110–112P1Set/query dynamic foreground, background and cursor; resets restore the live theme. Overrides survive theme changes until reset
OSC 17/19, 117/119P1Set/query/reset selection background and real selection-text colour. Without OSC 19, selected glyphs keep their own colours; without OSC 17, the theme's translucent highlight applies; with it, the program's colour is drawn at the same translucency, so box glyphs and underlines under the selection stay visible. Queries return opaque RGB, with theme selection / live foreground as defaults
OSC 13–16, 18P2Accepted and ignored: pointer and Tektronix colours have no corresponding surface
SGR 58 / 59P1Indexed/RGB underline colour and reset, colon or semicolon forms; independent of glyph and strikeout colour. Stored in a bounded side table without enlarging Cell
Colour scheme (?2031, CSI ? 996 n)P1CSI ? 997 ; 1/2 n reports dark/light from the live theme; a subscriber receives one report per actual variant change. DECRQM reports subscription state
XTGETTCAP (DCS + q)P1Fixed table: TN/name, Co/colors, RGB, Ss/Se, Smulx, Setulc, cursor, keypad, backtab, editing, navigation and F1–F12 keys. Unsupported/malformed names get DCS 0 + r ST, with no echo. Each supported name gets its own fixed reply
DECRQSS SGR (DCS $ q m ST)P1Reports numeric pen rendition, including underline style/colour, so Neovim can detect curly underlines. Other status strings receive a constant negative reply
DCS and rare CSI sequencesP2The long tail

OSC verdict table

This is the complete deliberate OSC dispatcher surface. OSCCoverageTests compares numeric codes in both directions and tests that removing a row is caught. Unknown codes and unknown sub-forms are silently dropped. “Refused” means no state change and no reply for that sub-form. Security section numbers refer to SECURITY.md.

CodeSub-formVerdictReasonRecord
OSC 0title setterimplementedSeparate icon/window names; no title reporting§2.2; #407
OSC 1title setterimplementedSeparate icon/window names; no title reporting§2.2; #407
OSC 2title setterimplementedSeparate icon/window names; no title reporting§2.2; #407
OSC 3allignoredX properties do not exist on macOS#411
OSC 4set/queryimplementedIndexed palette / five special slotsB06
OSC 5set/queryimplementedIndexed palette / five special slotsB06
OSC 6allignoredxterm colour-mode switches have no effect#411
OSC 7file URLimplementedHost-aware local cwd or informational remote context§2.8
OSC 8hyperlinkimplementedInterned URI; user opens it§2.4
OSC 9notification; 4 progress; 12 promptimplementedBounded notice / progress; 12 aliases OSC 133 A#409; #411
OSC 96 macro; 7 execrefusedNo stream-selected execution§2.9; #409
OSC 99 cwd; other defined numeric subcommands (1–12)ignoredNo host; cannot distinguish a remote path§2.8; #411
OSC 10set/queryimplementedForeground/background/cursor/selection state#407
OSC 11set/queryimplementedForeground/background/cursor/selection state#407
OSC 12set/queryimplementedForeground/background/cursor/selection state#407
OSC 13set/resetignoredPointer/Tektronix colours have no surface#407
OSC 14set/resetignoredPointer/Tektronix colours have no surface#407
OSC 15set/resetignoredPointer/Tektronix colours have no surface#407
OSC 16set/resetignoredPointer/Tektronix colours have no surface#407
OSC 17set/queryimplementedForeground/background/cursor/selection state#407
OSC 18set/resetignoredPointer/Tektronix colours have no surface#407
OSC 19set/queryimplementedForeground/background/cursor/selection state#407
OSC 21fixed keys / palette 0–255implementedSet/query/reset shared colours; unknown keys dropped; cursor_text drawn#411
OSC 22set, push, pop, queryimplementedFixed CSS/X11 vocabulary; 16 entries per screen; closest public NSCursor#411
OSC 46allrefusedWould open a stream-named log file§2.10; #411
OSC 50allrefusedFonts come from verified configuration (D10/D12)§2.10; #411
OSC 51allignoredEmacs-specific, no consumer#411
OSC 52writeimplementedSanitised text; allow-clipboard-write gate§2.6
OSC 52read/clearrefusedNo clipboard disclosure or silent clearing§2.6
OSC 66text sizingnot yetMulti-cell glyph scaling needs a renderer decision (D05)#411
OSC 99notice / capability queryimplementedBounded metadata and fixed capabilities#409
OSC 104all / named resetsimplementedIndexed palette / special colour resetsB06
OSC 105all / named resetsimplementedIndexed palette / special colour resetsB06
OSC 106allignoredxterm colour-mode switches have no effect#411
OSC 110resetimplementedRestore live theme / selection defaults#407
OSC 111resetimplementedRestore live theme / selection defaults#407
OSC 112resetimplementedRestore live theme / selection defaults#407
OSC 113set/resetignoredPointer/Tektronix colours have no surface#407
OSC 114set/resetignoredPointer/Tektronix colours have no surface#407
OSC 115set/resetignoredPointer/Tektronix colours have no surface#407
OSC 116set/resetignoredPointer/Tektronix colours have no surface#407
OSC 117resetimplementedRestore live theme / selection defaults#407
OSC 118set/resetignoredPointer/Tektronix colours have no surface#407
OSC 119resetimplementedRestore live theme / selection defaults#407
OSC 133A/B/C/DimplementedCommand boundaries and exit status§2.1
OSC 134tokened directory completionimplementedPane-owned token required§2.7
OSC 633A/B/C/D; P;CwdimplementedSame command records and host-aware cwd as native hooks#411
OSC 633E command/nonce; other PignoredGrid owns command text; no VS Code trust model#411
OSC 777notifyimplementedSanitised desktop notification#409
OSC 1337CurrentDir; RemoteHost; SetMark; ClearScrollbackimplementedHost-aware metadata, mark that ⌘↑/⌘↓ stop at, ED 3 semantics#411
OSC 1337CursorShape; RequestAttention; ReportCellSize; CopyimplementedTemporary cursor; background Dock attention; numeric metrics; gated clipboard#411
OSC 1337File; MultipartFile; FilePart; FileEndimplementedinline=1 only; PNG/GIF first frame in sandbox; shared placements/budgets#411
OSC 1337SetUserVar; ShellIntegrationVersion; HighlightCursorLine; UnicodeVersion; SetKeyLabel; PushKeyLabels; PopKeyLabels; ClearCapturedOutputignoredNo consumer or Touch Bar; Unicode rules stay fixed#411
OSC 1337StealFocus; SetProfile; SetBadgeFormat; ReportVariable; Custom; inline=0refusedNo focus theft, config mutation, persistent badges, value echo, scripts or downloads§2.10; D09/D10
OSC 3008allignoredsystemd context hierarchy, no consumer#411
OSC 5113allrefusedFile transfer writes stream-named files§2.10; #411
OSC 5522text/plain write/wdataimplementedBounded sanitised text transaction, gated like OSC 52; partial protocol. Replies a fixed status=DONE, EPERM (writes not allowed) or EIO (pasteboard failed)#411
OSC 5522read, MIME aliases, non-text, primary selectionrefusedNo clipboard disclosure or extra clipboard surfaces§2.6; §2.10
OSC 7501status / probeimplementedBounded task state; constant probe reply#409
OSC 21337tab metadataimplementedBounded self-reported metadata#409

OSC 21 accepts the same RGB hex specifications as OSC 4/10–12; an empty value or a bare key resets. The protocol's unknown-key echo is deliberately omitted. OSC 22 returns boolean lists or fixed-table names for __current__, __default__, __grabbed__; unknown setters choose default. macOS lacks public wait/progress cursors, so these use the arrow fallback. Link hover takes precedence.

OSC 5522 supports only text writes, under OSC 52's 3072 decoded-byte cap. It does not claim full kitty extended clipboard conformance (64 MiB minimum, MIME aliases, primary selection or paste events). A successful permitted system write receives only the fixed type=write:status=DONE acknowledgement. Reads and unsupported packets are silent, and a bad packet invalidates the whole transaction. allow-clipboard-write is checked by the app before any pending copy reaches the system pasteboard.

Protocol references: iTerm2 extensions, iTerm2 images, kitty colours, kitty pointers, kitty clipboard.

1.2 Query / response sequences

These are P0 and are the most commonly missed requirement. They are not about rendering: a program that asks a question and receives no answer either waits for a timeout or misdetects the terminal's capabilities.

SequenceAsked byConsequence if unanswered
DA1 (ESC [ c)vim / Neovim at startupStartup stalls until timeout
DA2 (ESC [ > c)Capability detectionFeature misdetection
DSR-CPR (ESC [ 6 n)zsh, starship and similar promptsPrompt corrupts or hangs
DECRQM (ESC [ Ps $ p)tmux, Neovim capability probesConservative fallback behaviour
XTVERSION (ESC [ > 0 q)Modern TUIsFeature misdetection
Dynamic colour (OSC 10/11/12 query form)Themes and TUI capability probesProbe times out
Kitty keyboard (ESC [ ? u)Editors that bind Ctrl+I and Tab apartBoth keys arrive as 0x09

All four shipped in M6 (M6.5, M6.6, M6.9), plus DSR operating status (ESC [ 5 n → ESC [ 0 n) and DECXCPR (ESC [ ? 6 n), both of which went unanswered until M8. A status query is the one sequence where silence is read as "the terminal is dead", and the programs that wait for it without a timeout wait forever.

DECRQM answers 0 — "not recognised" — for the modes Corta does not track, which is the honest reply and the one that unblocks the probe; the failure mode this table is about is silence, not a negative answer.

A mode reported as supported has to behave. DECRQM's four-state answer is a contract: a program that is told a mode is set lays its next screen out against that behaviour, so answering 1 or 2 for a bit nothing acts on is worse than answering 0. The four modifiable ANSI modes therefore split:

ModeAnswerWhy
IRM (4)1 / 2Implemented (Grid.insertMode) — a print inserts and shifts the row right
LNM (20)1 / 2Implemented (PerformerState.newLineModeEnabled) — LF/VT/FF also return the carriage, and Return sends CR LF
KAM (2)4Deliberately not implemented: a terminal that stops accepting input on a byte from the child is one a runaway program can wedge, with no way for the user to tell it from a hang
SRM (12)4Deliberately not implemented: Corta never echoes keystrokes itself, so there is no local echo to switch off

The same rule covers the private modes: DECOM (6), DECAWM (7) and DECTCEM (25) report their live state. Until they were implemented, 7 and 25 answered 3, "permanently set", while ?7l still wrapped and ?25l still drew the cursor — the contract above, broken.

4 is "permanently reset", which is a stronger answer than 0 — a program learns not to ask again.

Colour-space specifications (P2). OSC 10/11/12 accept #RGB through #RRRRGGGGBBBB and rgb:R/G/B. rgbi:, CIELab:, CIEuvY:, CIExyY:, CIEXYZ: and TekHVC: are refused: the sequence is parsed, no colour is changed, and nothing is written back. Each of those is a colour-space conversion needing a white point and a gamma curve — the answer depends on the display profile, so an implementation is either colour-managed properly or it is a wrong number dressed as a right one. Refusing is also the safe direction: a program that sets a colour and sees no change keeps legible text, whereas a mis-converted CIELab black-on-black is a terminal you cannot read. The corresponding esctest cases are recorded as expected failures for this reason (0.1 roadmap), and Performer+Query.parseColorSpecification carries the policy in full.

DECSCL gates DECRQM. A program that announced VT200 with ESC [ 62 ; 0 " p has asked to be talked to as an older terminal, and DECRQM arrived at VT300. Answering anyway is the terminal ignoring what it was told.

Every response is written to the child's stdin. Responses must therefore be fixed-format and never echo attacker-controlled text — see SECURITY.md §2.2.


2. Input, Rendering, Windows

2.1 Input

CapabilityTierNotes
Keyboard → PTY, including control and function keysP0
CJK IME (NSTextInputClient)P0Harder than it looks — DESIGN.md §7.1. Composition, candidate window and commit verified in the launched app (§4.4)
Copy / paste with bracketed pasteP0Copy joins soft-wrapped lines into one and trims trailing blanks; ⌘C / Edit ▸ Copy
Keyboard and mouse text selectionP0Drag, double-click word, triple-click logical line, ⇧-click extend; document-anchored — DESIGN.md §3.1
Configurable key bindingsP1bind.<command> in the config file, one table for menus, palette and file — CONFIGURATION.md §5
Click-to-position, drag-to-selectP1Drag selects; a TUI that owns the mouse is overridden with mouse-override-modifier
⌘-click to open a URLP1Scheme allowlist required — SECURITY.md §2.4
Kitty keyboard protocolP2Implemented; disambiguate re-encodes the colliding keys only — Ctrl+I/M/H/[, Escape (CSI 27 u), ⌥ with Return, Tab or Backspace, and ⌥-as-Meta text keys — not the whole keyboard; progressive enhancement flags and protocol stack, one per screen (the alternate screen's is dropped when it closes), and the main screen's cleared when a command finishes (OSC 133 D)
Tab / Shift-Tab through a candidate UIP0A completion menu or IME that resolves Tab as a command sends insertTab(_:) / insertBacktab(_:) to doCommand(by:); both are forwarded to the child (B02) — DESIGN.md §7.1, TerminalViewIMETests.doCommandForwardsTabAndBacktab

The candidate-UI row is the one whose evidence is incomplete. The code gap was real and is closed, but a live confirmation that Claude Code's slash-command menu accepts Tab inside a built Corta, compared side by side with a reference terminal under an English input source, a CJK source with no marked text and an active composition, has not been collected: the original report's environment cannot be recovered (CHANGELOG 1.0.0). The 2026-09-17 record's A1 items are the closest evidence since.

2.2 Rendering

CapabilityTierNotes
GPU glyph atlas + instanced quadsP0One draw call per screen
Foreground / background, bold, italic, underlineP0Real faces where the family has them; synthetic oblique and stroked weight where it does not
Missing glyph is visible, not blankP0Hollow box for a scalar no font in the cascade covers
Every glyph clipped to its cell boxP0Overwide ink is scaled to fit rather than painted into the next column
Cursor: block / bar / underline, blinkP0Configurable shapes and blinking; DECSCUSR overrides (see below)
Selection highlightP0Document-anchored quads; follows its text as output scrolls
Retina / HiDPI scalingP0Grid laid out in pixels, not points — one of §4.4's shipped bugs
Font fallback for CJK and emojiP0Core Text cascade list; the shaped run's font rasterises the glyph (M3.5)
Atlas eviction (LRU or multi-page)P0Full-page reset on exhaustion + generation rebuild (M3, DESIGN.md §7.4)
Glyph weight under grayscale antialiasingP1Otherwise light-on-dark text looks too thin; a coverage curve over sRGB blending — DESIGN.md §7.5
Runtime font scaling (⌘+ / ⌘−)P1Per-window, temporary; never writes font-size
LigaturesP2Conflicts with the cell grid — DESIGN.md §7.3
Background transparency, blur, paddingP2

Runtime font scaling landed at M3 (⌘= bigger, ⌘- smaller, ⌘0 reset). The glyph atlas is rasterised for one size and scale, so a change rebuilds the renderer and re-fits the window around the unchanged grid. Measured on a 2x display (Menlo): 14pt → 9x17pt cell, 1100x554pt content (120x30 grid); 15pt → 10x18pt cell, 1220x584pt content; the 13pt step was re-measured live (980x524pt) when fixing the ⌘- shadowing below. The stock storyboard's Format menu (font panel, rich-text traits) was removed at the same time: its Font ▸ Smaller item claimed ⌘- first in menu order and routed it to modifyFont:, which a terminal never implements — the shortcut arrived dead. MenuShortcutTests pins the invariant: no keystroke is claimed by two menu items. The cursor uses the configured block, bar or underline shape, with optional blinking (off by default). DECSCUSR can temporarily override shape and blink; parameter 0 and terminal resets return to the configured defaults. Only the visible, focused live pane blinks; once its cursor is idle the blink is handed to a Core Animation opacity animation, so it draws no Metal frame every half second (DESIGN.md §7.10). Inactive, occluded and scrolled-back panes do not blink.

2.3 Windows and sessions

CapabilityTierNotes
Single window, single terminalP0M1
Multiple windows (⌘N), each its own sessionP1Landed at M3; composition, not new mechanism
PTY lifecycle: spawn, read/write, TIOCSWINSZ, SIGCHLDP0Resize must be reported or remote vim and htop desynchronise
Resize debouncingP1A live window drag otherwise hammers the child
TabsP1Native window tabs; a restored group keeps its order and selection (§4.4 item 6)
Split panes (layout tree + focus routing)P1Renderer and input are multi-viewport from M1
Search scrollback (⌘F)P1Must match across soft-wrapped lines
Scrolling (wheel, ⌘↑↓, page)P0⌘↑↓ jump between commands once shell integration reports them; ⇧Page/Home/End scroll
Bell (audible / visual / mute)P1bell in the config file
Settings pageP1Native page in the menu bar next to Edit/Shell, backed by one text file needing no third-party parser — M6.1
Multiplexing—Not doing; use tmux

2.4 Shrinking the screen

Reducing the row count moves rows off the top, into scrollback, until the cursor fits. Only rows below the cursor — which are blank — come off the bottom. Truncating from the bottom instead destroys the newest output and does not preserve it in history, so making a window smaller silently ate the last commands that ran.

3. The Daily-Driver Checklist

The operational definition of success from DESIGN.md §8. If all ten hold, Corta has replaced Terminal.app for this repository's own use. Each row names the record that last verified it by hand; an automated suite is listed only where one pins the behaviour afterwards. A row whose record is a roadmap tick has not been re-run since it landed.

#Holds whenLast verified by handPinned by
1Opens to a working shell with correct colour output2026-09-17 pass, H31 and G29 (record)§4.4's launched-app check
2vim / less / htop render without artifacts2026-09-17 pass: less and git log clean (G29); tmux + htop left residue after a window shrink (G25) — ECH was unimplemented, fixed the same day (§4.4.2)EditingTests, golden fixtures
3Chinese input works, displays, and never drifts out of alignment2026-09-17 pass, A1 on a physical keyboard, both panes of a splitTerminalViewIMETests, WideGlyphRenderTests
4Copy and paste work; pasting multi-line code does not auto-execute2026-09-17 pass, G28 (the multi-line warning appeared for a REPL without bracketed paste) and 2026-09-19 (Option-drag selection to the clipboard)PasteTests, MouseReportingTests
5Scrollback holds a long training run and scrolls smoothly2026-09-17 pass, G27: 120 s of continuous output, the other window still responsive; the top of the run was past the 10,000-line default cap, as documentedcorta-bench write-backpressure
6⌘F searches scrollbackM4.4 roadmap tick (record); not re-run by hand sinceSearchTests, SearchAndTabUITests
7New tab and split pane2026-09-17 pass, C11: a restored two-pane window and a three-tab groupSplitPaneUITests, SearchAndTabUITests
8⌘+ / ⌘− resize the font2026-09-17 pass, C12 and H31FontSizeZoomTests
9Resizing the window resizes the program inside it2026-09-17 pass, D16: vim through full screen and back, 120×30 → 207×62 → 120×30PTYWindowSizeTests, ResizeDebouncerTests
10⌘-clicking a localhost: URL opens the browserM4.6 roadmap tick (record); not re-run by hand sinceLinkDetectionTests

4. Measuring Completeness

Built during M1. Fixing the long tail without these is guesswork.

4.1 Golden-file grid tests

The primary harness. Feed a recorded byte stream to a TerminalSession, serialise the resulting grid to plain text (characters plus attributes), and diff against a checked-in expectation.

This is how a change to one CSI handler is prevented from silently breaking vim. Written once, useful for the life of the project.

4.2 External suites

SuiteWhat it coversUse
esctestxterm's own conformance suiteThe objective completeness number
vttestClassic VT100/VT220 behaviourManual sanity pass at each milestone

Record the pass rate at each milestone. "Conformance improved" is only meaningful against a previous number.

How the esctest run is done. esctest2 (ThomasDickey/esctest2), run as the child of a live Corta window — it drives the terminal through its own tty, so it has to be the shell:

# A wrapper passed only in the environment of the launch you control.
# Never via `launchctl setenv` (§4.5).
cat > /tmp/corta-esctest.sh <<'EOF'
#!/bin/bash
export TERM=xterm-256color
cd /path/to/esctest2/esctest
python3 esctest.py --expected-terminal xterm --max-vt-level 3 \
  --logfile /tmp/esctest-run.log
exec /bin/zsh -l
EOF
chmod +x /tmp/corta-esctest.sh
SHELL=/tmp/corta-esctest.sh Corta.app/Contents/MacOS/Corta

Results, one row per run. "xterm-compatibility" is passes plus "known bugs" — the number esctest reports and the one comparable across terminals; it is not a pass rate, and every row states all three counts. The failing names of each run are kept in esctest/ so the next run is a diff rather than a re-reading.

RunBuildPassedKnown bugsFailedTotalxterm-compat.Against the previous row
M2 (2026-09-02)M2 closeout5033418456867.6%Baseline
M6 (2026-09-03)M6 closeout10633512756877.6%57 failures fixed, none introduced: programmable tabs, CNL/CPL/CHT/CBT, IND/NEL/RI, RIS and DECSTR, including the soft-reset isolation esctest itself relies on
0.1.1 (2026-09-08)v0.1.111233512156878.7%Six more passing, none regressed; classified by application impact in the quality plan Q01; names in esctest/0.1.1-results.txt. 45 of the 121 were OSC 4/5 palette set and query, then unimplemented
2026-09-17main, esctest2 2798f1212633410756781.1%14 tests moved to pass — B06's OSC 4/104 and 5/105 set/query/reset, SCORC, and the multi-column reverse-wraparound case — none regressed; the suite itself lost one test and one known bug, so the totals are not identical columns. Names in esctest/2026-09-17-results.txt; XtermWinops (28) is still the largest class and still deliberate
1.1.0 (2026-10-03)main at ef11a93, esctest2 2798f1212633410756781.1%Identical failing list, name for name: nothing regressed through the 1.1.0 milestone and nothing moved — its terminal-side changes (Kitty o=z, emoji drawing, bounded glyph clusters) have no esctest case. Names in esctest/2026-10-03-results.txt
1.1.5 (2026-10-07)main at c9432a9, esctest2 2798f1213133410256782.0%Six moved to pass: #244 made autowrap off (CSI ? 7 l) overwrite the last column instead of wrapping, origin mode address from the region's top, and DECRQM report DECAWM, DECOM and DECTCEM as they are. One moved to fail without regressing: XtermSave_SaveSetState had passed only because ASCII text wrapped with autowrap off — XTSAVE/XTRESTORE are still not implemented. Names in esctest/2026-10-07-results.txt

1.1.1 (2026-10-03): the empty-Return patch adds real-shell PTY regression coverage and was confirmed in the launched development app after updating the installed integration. Full core and Unit suites passed; esctest2 was not re-run because this patch changes shell hooks rather than terminal escape-sequence handling. Evidence: 1.1.1 release checks.

1.1.5 (2026-10-07): esctest2 re-run on the release tree: 131 passed, 334 known bugs, 102 failed (82.0%); the row above has the per-test difference. The scroll-region-on-resize (#256) and reflow-cursor (#257) fixes have no esctest case; their own core tests and a replay of Claude Code's recorded output cover them. Evidence: 1.1.5 release checks.

1.1.6 (2026-10-07): draws the rounded box corners (╭ ╮ ╰ ╯) as geometry joined to the grid-drawn borders (#264). A renderer change with no escape- sequence handling in it, so esctest2 was not re-run; render tests, the launched app and a Release frame-CPU A/B cover it. Evidence: 1.1.6 release checks.

4.3 Fuzzing

The parser consumes untrusted bytes and must never crash, hang, or allocate without bound. The harness is the corta-fuzz target in the CortaTerminal package (M6.11); it asserts the SECURITY.md §3 caps on every input rather than waiting for a sanitizer to notice — grid dimensions, the scrollback ring, cursor bounds, row width, the interned grapheme and hyperlink tables, and the size of the response one input can queue.

libFuzzer is not available on macOS with the current toolchain. Xcode 26 ships no libclang_rt.fuzzer_osx.a, and swiftc -sanitize=fuzzer is rejected outright for arm64-apple-macosx — verified, not assumed. What runs instead is a deterministic mutation driver over a checked-in corpus:

swift build --package-path CortaTerminal -c release --product corta-fuzz
CortaTerminal/.build/release/corta-fuzz --fuzz 500000 --seed 1 \
  CortaTerminal/Tests/Fuzz/corpus

It has no coverage feedback, so it explores far less per iteration than libFuzzer would. What it does have is a fixed seed, so a failure is reproducible from the command line that found it. Recorded at M6: 2.5M mutated inputs across seeds 1–5, clean. A separate 100,000-input run under AddressSanitizer (seed 3735928559) was also clean.

The corpus itself replays inside the test suite (FuzzCorpusTests), so a crash found by a long run is fixed by adding its input to CortaTerminal/Tests/Fuzz/corpus — from then on it is a regression test.

Targets beyond the feed (2026-10-08). --target points the same driver at the other inputs a hostile peer controls, each with its own corpus under CortaTerminal/Tests/Fuzz: osc frames every input as one OSC sequence whose code the first byte picks from the ones Corta handles (titles, OSC 4/5/10–12 colours, 7, 8, 52, 104/105, 133, 134); kitty frames it as one APC graphics command, two thirds of them as an o=z transmission whose zlib stream is the input, so the inflate ceiling, header and Adler-32 checks are what it reaches; sftp decodes it as one frame body (SFTPCodec.decodeFrame, the statvfs reply and the length check) and requires every frame that decodes to decode to the same message after encodeFrame. The terminal caps above hold for osc and kitty. SFTPFuzzCorpusTests replays the SFTP corpus in the test suite. PNG decoding is not a target: it is ImageIO behind the app's signature and dimension checks, and the SwiftPM harness cannot reach the app layer.

4.4 App-layer verification requires a launched app

Offscreen render tests assert pixel coverage: that a cell with a known background produces that colour, that a glyph produces non-background pixels inside its cell. Every one of the following passed those tests and still shipped a blank or unusable window:

  • a view whose frame did not track its superview, so most of the grid was drawn outside the visible area,
  • glyphs rasterised upside down,
  • the grid laid out in points against a pixel coordinate space, so everything rendered at half size on a Retina display,
  • a transient window size at startup that the shell laid its early output out against,
  • a transient winsize delivered at startup: the first layout ran at the content-rect height (window frame minus titlebar, before .fullSizeContentView took effect) and shrank the session 30 → 28 rows, then grew it back — stranding two blank rows under the prompt while stty size and the render rect both reported the correct 30 (M2 closeout, D.1; first fixed by gating session resizes on the view filling its window's frame, now by creating the window at its final size — DECISIONS.md, "What replaced D15 and D16").

These are properties of the live view hierarchy, of orientation, and of startup ordering — none of which a texture readback can see. Any change under Corta/ is therefore verified by launching the app and checking:

  1. the window's content size matches columns x rows x point metrics,
  2. stty size in the child agrees with it,
  3. a screenshot shows text upright, full size, and filling the window,
  4. output longer than the screen scrolls and uses every row,
  5. gestures and menu actions reach the pane — the terminal view is first responder, so keyDown fires and First-Responder menu items (⌘V, ⌘=) are not dead,
  6. a restored tabbed window (three tabs, one of them split and not selected, quit, relaunch) comes back as one window, tabs in saved order, the saved tab selected, and the background tab's split and stacked icon there before it is first shown — the arrangement whose layout only AppDelegate.restoredWindows and applyPendingRestore drive (D30),
  7. in a non-English locale (-AppleLanguages '(zh-Hans)' on the launch), the menu bar is translated, not only the menus beneath it.

The app that gets launched is the development build — the Corta (Dev) scheme, CortaDev.app, bundle identifier dev.noahqin.Corta.dev (D22). It can run beside an installed Corta, so identify the one under test by bundle identifier or by PID; "the frontmost Corta" is ambiguous by design, and a check that reads the wrong window is a check that proves nothing. To run these against a Release build instead, stage it with CORTA_STAGE_DIR.

Secure Keyboard Entry is checked by hand. Shell ▸ Secure Keyboard Entry shows the titlebar lock only while a terminal window is key, and the lock clears when another app comes forward. Test the engaged state without recording input from another application. Historical system-entry-point checks are retained in the dated test-results records below.

Remote workflows (B13/B14) have a launched-app check of their own, CortaUITests/RemoteWorkflowUITests, which is the shape every remote-path change is verified in. CORTA_STAGE_DIR (AppPaths) moves the config file and Application Support under a throwaway directory — $HOME moves neither on macOS — SHELL names a script called ssh standing in for the remote shell (banner, a remote OSC 7, then /bin/sh -i), and CORTA_SFTP_SSH points the SFTP channel at a script that execs the real /usr/libexec/sftp-server. The UI-test runner is sandboxed (it reads all of /, writes only its container, which the app cannot read), so the app builds the stage itself: the test's first launch types stage-remote-ui.sh into an ordinary terminal. The walk: Remote: badge → keystrokes reach the pane → Browse Remote Files… (asked first, host prefilled, nothing spawned before Connect) → a listing answered by OpenSSH's own server → Edit → the "editor" rewrites the managed copy → the upload prompt → the remote file changed with no partial left → the fake shell exiting → badge gone → Reconnect → badge back, keyboard live → the stage removed through the same terminal. Nothing on the machine is changed and no network is used. Its first run found six defects the unit suites had passed over (a spawn that never returned, a write size the server rejected, a SIGPIPE that killed the app, a consent step that auto-connected, an untitled window, a badge that outlived its connection) — which is the argument for running it. What it cannot cover, and a human still judges against a real host: authentication and host keys (there is no ssh in the loop), and how the panes look — they are Metal surfaces the accessibility tree cannot read. SFTPRealServerTests in the CortaSFTP package target drives the same server without the app. The test selects the ABC keyboard layout for its own duration and restores the previous input source afterwards — a CJK input method would otherwise compose every typed line into candidates instead of delivering it to the terminal.

4.4.1 IME verification

IME behaviour additionally requires driving a real input method in the launched app; no offscreen or in-suite test can do it (DESIGN.md §7.1 — synthetic events with baked characters bypass composition). Procedure used at M3:

  1. Launch the app with a wrapper $SHELL that records stty size and ends with exec cat > capture-file (a backgrounded reader is stopped by SIGTTIN; the capture must own the terminal).
  2. Select an enabled Chinese input source for the session via TIS (TISSelectInputSource, e.g. com.apple.inputmethod.SCIM.ITABC) and restore the previous source afterwards.
  3. Inject key events at HID level (CGEvent.post(tap: .cghidEventTap)) with key codes only — setting a unicode string on the event makes the IME treat it as plain text. Requires the injecting process to be accessibility-trusted; without that grant this is a manual test.
  4. Verify: typing pinyin opens a composition (marked text overlay at the cursor, underlined; nothing reaches the child), the candidate window appears under the cursor cell, selecting a candidate writes it to the child as UTF-8, and ⌃C / ⌃D / ⌃Z and the arrows behave identically with and without the IME selected.

4.4.2 Real-program verification (P0)

Render tests and golden-file grid tests both assert that a byte stream produces a grid. Neither can tell you that vim is usable. The five P0 areas below are behavioural, and every one of them has a failure mode that a passing grid test is compatible with:

AreaVerified withWhat to look forResult
Cursor movement, shape, blink, mode switchesvim, nvim, htop, lessThe cursor sits where the program thinks it does after a mode change; DECSCUSR shapes take effect; no ghost cursor in an unfocused panePass
Scroll regions and the viewporttmux with several panes, less on a long fileA region scroll does not disturb rows outside it; scrollback follows the bottom; scrolling back and returning lands where it startedPass; see the note below
Left/right margins and wide charactersvim with a CJK file, tmux split narrowA wide glyph never straddles the right margin; a resize rewraps without stranding rowsPass
Insert and delete (ICH/IL/DCH/DL)vim editing mid-line, readline with IRMThe redraw range matches the edit; nothing is left behind to the right of an insertPass
Erase (ED/EL/ECH)clear, htop redraw, tmux window switch, tmux window shrinkNo residue from the previous screen, and the cursor ends where the sequence saysPass for ED/EL. ECH was not implemented at all until 2026-09-17 — this row said Pass on the strength of clear/htop, which never send it; tmux's status line (left part, CSI n X, right part) kept the previous screen's cells in the gap after a window shrink. Now implemented and covered by EditingTests.

Run interactively on the release build (vim ~/.zshrc; tmux split %/" and window switch n/p; htop refresh; less on /var/log/system.log with /search, n, g/G; a CJK file in vim and in a narrow tmux split; a mid-line edit in vim; clear), judged by eye — not something a test target can assert. esctest (§4.2) covers the sequences; this covers the programs.

An earlier run of this table recorded less's search-match highlight (reverse video on a / hit) as never rendering, with the fault not isolated. The 2026-09-17 pass (G29) could not reproduce it: the highlight was visible on the same command. It is not tracked as open; a fresh reproduction with a grid dump of what less sends would reopen it.

4.5 Test fixtures must not outlive the test

A fixture shell was wired in with launchctl setenv SHELL, which applies to every GUI application the user launches afterwards. Corta then started under zsh -f — no rc files, so no PATH — and a demo banner printed on every launch. Both looked like defects in the terminal.

Pass fixtures in the environment of a launch you control:

SHELL=/path/to/fixture.zsh Corta.app/Contents/MacOS/Corta

Never launchctl setenv, never the user's rc files, never defaults write. Delete the fixture when finished.

4.6 Manual scenario pass

Run at every milestone, because these are the actual workload:

  1. tmux with a split running htop, resize the window
  2. Neovim editing a UTF-8 file with mixed CJK and emoji
  3. ssh to a remote host, run vim, resize the window
  4. A long training run producing continuous output for minutes
  5. A Python REPL, paste a multi-line function
  6. git log --graph --color through a pager

B01 additions — the real-workflow matrix. The issue asked for zsh, fish, ssh, tmux, Neovim, fzf, CJK input, long output, sleep/wake, restoration and AI CLI applications. zsh/tmux/Neovim/ssh/long-output are items 1–4 above; fzf and fish were covered by the 0.1.1 audit's real-program harness (U10 in the quality plan, which records what it proved and what it could not); it was a one-time verification and was removed in 1.1.0. The remainder, added here rather than to the scriptable harness because none of them are drivable through a PTY alone:

  1. CJK input — an actual input source composing (not just CJK text, which item 2 already covers) through a live candidate window. Manual only — DESIGN.md §7.1's verification caveat: synthetic key events carry baked characters, so composition never opens for them.
  2. Sleep/wake — the machine actually sleeping and waking with Corta running. Manual only, deliberately not scripted: sleep/wake and low-power mode change machine-wide state other processes depend on, and need a dedicated session on an idle machine, not a CI-style script (D13).
  3. Restoration — force-quit or crash Corta with a multi-pane layout and scrollback, relaunch, confirm the arrangement and directories return (U07). Terminal text and running child processes must not return. Partly scriptable: a staged CORTA_STAGE_DIR with restore-windows = true and a written state.json drives the mechanism without touching global machine state — but a human still has to judge whether what came back looks right.
  4. An AI CLI application — Claude Code (or a similar TUI-driven AI CLI) run as the child, covering ordinary interactive use including tab-completion and its own keyboard handling. Manual only, and the origin of B02's reported regression — see that issue's entry under §2.1 above; cross-referenced rather than duplicated here.

Items 7, 8 and 10 above have no automated substitute and are recorded with an explicit not judged state when a pass has not actually run one (precedent: the 0.1.1 manual verification's VoiceOver read-through, marked "not judged" rather than silently skipped when the tester could not follow spoken English closely enough to have an opinion). "Not judged" means exactly that — no automated check stands in for it, and no claim of success is made in its place.

4.7 The records

Up to 1.2.0, every pass of §4.4, §4.4.2 and §4.6 was written up as a dated file under test-results/ — what was checked, what passed, what failed and what was not judged — and those records are preserved at commit f632d59, listed below. Since then a pass is recorded in the pull request or issue it verifies, in the same terms; the tree carries no dated records.

RecordWhat it covers
2026-09-02 — M2 closeout§4.6 items 1 and 3: tmux + htop under resize; no reachable ssh host
2026-09-03 — M6 closeoutTabs, the settings page and theme switching live; focus reporting end to end; the esctest re-run; the gesture items left to a human
2026-09-12 — B10 passThe text-selection API decision, the accessibility tree audit, the translation audit, and what stayed not judged
2026-09-16/17 — interactive passThe first full sweep of §4.6 since 0.1.1, by tool and then by hand
2026-09-18 — release checksWhat the 09-17 pass changed; the six human and hardware items for 1.0.0, including the Quick Terminal probe
2026-09-18/19 — issues #88–#90Mouse tracking, shared coordinates and the toolchain decision
2026-09-21 — 1.0.1 release checks§4.4 points 1–5 for the patch release, the themed cursor on screen, what was not re-run and why
2026-10-01 — 1.1.0 audit closeoutThe 27-finding review accounted for by #189–#194, with its automated and launched-app evidence
2026-10-02 — audit follow-upSearch, SFTP lifecycle, local storage and reflow follow-ups, with the A/B measurements
2026-10-03 — 1.1.0 release checks§4.4 points 1–5 and 7 on the merged tree, the esctest re-run, what was not judged
2026-10-03 — 1.1.1 release checksReal-shell empty-Return regression tests, full core/Unit suites and user-confirmed launched-app check
2026-10-07 — 1.1.5 release checksFull core/Unit suites, fuzz, esctest re-run, core benchmark and Release frame CPU, and the user-confirmed resize check
2026-10-07 — 1.1.6 release checksRounded box corners: render tests failing on 1.1.5, the launched app, and a Release frame-CPU A/B
2026-10-09 — Mac audit closeout§4.4 points 1–5, native session lifecycle, Quick Terminal and paste checks; explicit UI, VoiceOver, Shortcuts and remote limits

Program status and desktop notifications

Claude Code is currently the only CLI with a dedicated task-state integration. General protocol parsing and process-name detection do not constitute task-state integration for other CLIs. No Codex, Gemini, pi, Kimi, Grok Build, Qwen, OpenCode or Aider state adapter is shipped.

OSC 7501 replies to probes with the constant OSC 7501 ; ? ST and accepts working/blocked/done/idle/error and clear, per optional bounded id (one main slot plus 32 subtask slots). done is a completed turn; idle is a separate state (including startup and stop). Prompt redraws and ordinary notifications do not change reported task state. Corta's internal unknown is used for process-only detection and OSC 21337 metadata, which do not establish an agent's task state. It is not an extra OSC 7501 wire state. OSC 21337 accepts escaped indicator/status/status-color metadata in a separate bounded slot. It decorates explicit state instead of competing with it; status text fills an empty task title. Native text uses system colours; status-color is retained, not rendered. OSC 9 distinguishes numeric ConEmu subcommands from notification text. OSC 9;4 supplies progress; a bare OSC 9;12 supplies a shell prompt mark. Neither establishes an agent's typed task state. OSC 9;6/9;7 never execute; OSC 9;9 directory reports and other unimplemented ConEmu commands are inert. OSC 777 accepts only notify.

OSC 99 supports title/body/close, bounded chunking and base64, occasion, urgency metadata, focus, expiry and silent/system sounds. Urgency never bypasses the OS's notification policy. The capability reply echoes only a charset-checked query id, as required by the kitty specification. The default kitty occasion is always; other protocols default to hidden panes. Corta coalesces all program notices to one mutable notice per pane. Up to eight unfinished chunked messages are kept, a ninth replacing the oldest; a close is applied to its own id only and never drops another id's notice. An explicit notice delayed beyond coalescing does not re-alert a pane whose current blocked episode was already announced; leaving blocked resets that suppression. BEL and kind changes use the same episode suppression. Fixed attention is cancelled when its wait ends; unrelated program messages are preserved. Visibility/occasion is rechecked before submission, including after OS authorization. OS delivery can occur after that final check.

Unsupported: a=report, c=1 event reports, p=alive, icons (n, g, p=icon) and buttons. These generate no stdin reports. Close requests remove only the pane's matching delivered/pending id; closing one cannot discard a newer pending id or reset a blocked episode. Program-chosen ids never enter click routing; clicks use Corta's pane id, window number and session generation.

ProgramSignalsTests covers byte-split sequences, bad fields/base64, entry and chunk exhaustion/recovery, sanitisation, refused ConEmu actions, progress, capability reply allowlisting and tab-status escaping. Launched-app acceptance also needs a real Claude startup/permission/completion, notification click routing and each explicit channel (iterm2, ghostty, kitty). Process fallback uses process changes, prompt return and exit; an active process alone means Unknown, a clean session end means Idle, and a known failed exit means Failed. OSC notifications and BEL never imply blocked, done or working; no idle timer is added. Accepted OSC 133 command boundaries clear stale children, waiting, progress and tab metadata, even if the CLI never sent clear. A command started on main may end while a crashed CLI leaves the alternate screen active: lifecycle metadata finishes against the parked main grid without switching or marking the alternate canvas. On the alternate screen a D takes effect only when the shell's next prompt A follows it, as every integration script sends them; a stray D printed by a live full-screen program (a log in less -R) leaves its paste and keyboard modes alone. A clean boundary preserves explicit done/error; otherwise it means Idle. Status 130 or a direct session terminated by SIGINT means interrupted/Idle, other known nonzero statuses mean Failed. The next command clears this summary. No shell boundary means no inferred CLI exit. Flood delivery is bounded to 0.2 notices/second/pane after coalescing.

RealClaudeStatusTests is opt-in: set CORTA_REAL_CLAUDE to the installed executable's absolute path and run the core test of that name. It starts the real program under TerminalSession in a fresh temporary configuration and checks that Claude accepts the constant probe reply. It submits no model prompt. It isolates file configuration and removes inherited API/OAuth tokens; it is not an assertion about the CLI's internal keychain access. It does not replace the permission, completion or Notification Center checks above.

CLI signal compatibility

Development and acceptance version: Claude Code 2.1.310 (Claude Code), native macOS arm64. Dedicated task-state integration: Claude Code only. The adapter was developed against this installed version and exercised with real interactive sessions identifying the terminal as Corta (TERM=xterm-256color). The checked-in capture records the executable's SHA-256 alongside its version. This is a verified reference version, not a promise of compatibility with every Claude Code release.

Support categoryWhat Corta supportsBoundary
Dedicated Claude task stateOSC 7501 working, blocked, done, idle, error and clear; permission/question/auth reasons; one main slot and 32 subtask slotsOnly explicitly reported state; output, silence, BEL and notification prose do not establish an outcome.
Verified real 2.1.310 flowsProbe, normal completion, permission waiting/cancellation, invalid-model failure, isolated invalid-token auth waiting, Agent child id/clear and abrupt exit to an integrated shellThese concrete flows do not cover every failure cause or auth recovery. Question kind and capacity limits also have parser/policy coverage.
Verified native notificationsDefault auto plus fixed 7501 attention; explicit iterm2/ghostty/kitty text, notification clicks and waiting/completed Dock badge screenshotsAuto does not select a Claude text channel for Corta; OS authorization and delivery policy still apply.
Generic protocols onlyOSC 9/777/99 notifications, OSC 9;4 progress and OSC 21337 metadataClaude 2.1.310 does not enable native progress for Corta's normal identity; its OSC 21337 sender is disabled. Generic support is not evidence of Claude emission.
Not implementedOther CLIs' dedicated state adapters; OSC 99 activation/close/alive reports, icons and buttons; ConEmu execution commandsOther recognised tools have process detection and generic protocols only. No hooks or tool configuration are installed.
Not guaranteedOther Claude versions; 7501 through tmux, Claude background sessions or disabled terminal-title reportingThe upstream minimum 2.1.295 is not a Corta-tested version range. Live regroup/restart click permutations and auth recovery need separate acceptance; routing policy has unit coverage.

The native OSC 7501 vocabulary above was checked against that installed version, including a captured idle → working → done turn. The opt-in core test verifies the probe; app fixtures verify state, layout and notification routing. The separate opt-in real-Claude UI acceptance checks background Bash permission, Dock tile screenshots, notification clicks, completion and the three explicit notification channels. See running the acceptance test. That UI test does not establish failure, cancellation or auth-required sessions. RealClaudeLifecycleTests separately exercises real permission cancellation, invalid-model failure, isolated invalid-token auth waiting, an actual Agent subtask id/clear, and abrupt exit back to an integrated shell with the alternate screen still active. These checks are opt-in and billable; their existence is not proof of a pass, and auth recovery remains separate. Newer versions must be checked independently. The vocabulary here is based on the installed CLI and captured output; it is not a claim of an officially documented shared standard or a guarantee for all Claude versions. The following upstream-source and interface references describe candidate integrations, not runtime-tested version ranges or adapters already shipped by Corta. Record exact CLI versions, source revisions and acceptance results in a change's PR when an adapter is added; an untested update is not a verified compatible version.

The official terminal guide and official changelog explicitly describe OSC 7501 support since 2.1.295. That minimum is not a Corta-tested version range. Full wire details below come from the installed 2.1.310 distribution; undocumented fields are not evidence of a defect.

ChannelEvidence and actual 2.1.310 sending conditionsCorta contract
OSC 7501Official feature; distribution probes 7501;? and accepts same-code replies beginning with ?. Interactive sessions only; disabled by CLAUDE_CODE_DISABLE_TERMINAL_TITLE, background sessions and terminals known to print unknown sequences. In tmux the probe targets tmux, not passthrough.Constant 7501;? ST reply; typed states, optional reason, bounded id/base64 title/message and progress.
done/idle/error/blockedDistribution maps completed to done; startup/stopped to idle; ordinary failures to error; selected unauthenticated failures to blocked/auth. A blocked dialog may omit kind.Done means completed turn, not a claim that every tool succeeded. Clear removes one id or all 7501 entries; it is not an outcome.
OSC 9 / 777 / 99Distribution selects explicit iterm2 / ghostty / kitty; auto recognises those terminals, not Corta. No OSC 99 capability query is used to select a notification channel.Generic bounded notification parsers; no typed task-state inference.
BELterminal_bell, or added by iterm2_with_bell. notifications_disabled disables Claude's message channel only.Existing bell policy; attention uses the same blocked-episode deduplication.
OSC 9;4Distribution requires terminalProgressBarEnabled plus recognised progress support: attachment capability, ConEmu, Ghostty ≥1.2.0 or iTerm2 ≥3.6.6; excludes its Windows Terminal branch. Normal Corta identity is not selected.Generic progress only; no claim of native Claude-to-Corta progress emission.
OSC 21337Distribution has an encoder but its enablement function returns false.Generic metadata only; no claim that Claude enables it.

Claude's notification timing is independent of status: permission/elicitation usually waits about six seconds without typing; idle reminders usually wait about 60 seconds, with no running background agent. A notification is not proof of completion. The distribution normally emits BEL-terminated OSC; identification as kitty selects ST. Tests replay both. Its kitty body packet omits d=0, completes the accumulated title/body, and is followed by an empty focus packet; tests assert exactly one resulting notice.

ProgramSignalsTests's distribution-derived wire fixtures are not captured lifecycle sessions. The checked-in, text-redacted claude-2.1.310-signals.json replays actual PTY captures for cancellation, failure, auth waiting, a subtask and abrupt exit, plus actual OSC 9/777/99 idle notices. It retains field order, ids and terminators. Mocked transitions also exercise stop/failure/auth parsing, not Claude's actual execution of those flows. The separate opt-in RealClaudeLifecycleTests runs selected real flows; it does not infer their results from mock transitions. ProgramNotificationTests injects authorization/submission/removal to test cancellation, visibility, retry and late submissions without touching system permissions. The opt-in UI test checks a later idle notification's real text separately from fixed 7501 permission attention, so a fixed notice cannot stand in for a channel. Dock screenshots still require visual review; taking them is not an assertion that the badge or progress is correct. Foreground acceptance and live model lifecycles remain separate checks, not implied by a background unit pass.

Tool / referenceSignals in the checked interfaceCurrent Corta integration scope
Claude Code 2.1.310OSC 7501 working, blocked, done, idle, error, clear; blocked kind question, permission, authExplicit task state; clear removes state
Codex terminal backendOSC 9 message or BEL; neither has a typed task stateAttention only; no completion/permission inference
Gemini terminal notificationsOSC 9/777 text or BEL for attention/session-complete eventsAttention only; the event type is not retained in the text protocol
pi extensionsagent_start, agent_end, agent_before_settle, agent_settledExtension events need explicit bridging; agent_end may be followed by retries/queued work, so notification-only work belongs at agent_settled
Kimi hooksUserPromptSubmit, Stop, StopFailure, NotificationHook events need explicit bridging; a stop hook can cause continuation
Grok Build hooksUserPromptSubmit, Stop, StopFailure, NotificationHook events need explicit bridging
Qwen hooksUserPromptSubmit, Stop, StopFailure, NotificationHook events need explicit bridging
OpenCode attention APITerminal notification title/message; semantic sounds include done/error/question/permissionNotification text is not a typed task state
Aider notificationsOS notification command, or BEL on return to inputNo typed task state in the checked notification path

A StopFailure or tool-failure hook must not be confused with a normal completion; a failed individual tool can also be recovered within a turn.

Hooks, RPC events and application-internal events are not automatically visible in an ordinary terminal PTY. No tool configuration is installed or changed automatically, and Corta never classifies notification prose as success, failure or permission. Gemini's AfterAgent and Kimi/Qwen's Stop can request retries; observing either hook alone is not proof that automatic processing has stopped.