@leanandmean/coding-agent 0.74.1-scramjet.6 → 0.74.1-scramjet.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +202 -0
  3. package/README.md +21 -9
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +1 -1
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/initial-message.d.ts +1 -0
  8. package/dist/cli/initial-message.d.ts.map +1 -1
  9. package/dist/cli/initial-message.js +3 -0
  10. package/dist/cli/initial-message.js.map +1 -1
  11. package/dist/cli/list-models.d.ts +2 -0
  12. package/dist/cli/list-models.d.ts.map +1 -1
  13. package/dist/cli/list-models.js +4 -1
  14. package/dist/cli/list-models.js.map +1 -1
  15. package/dist/config.d.ts +9 -2
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +33 -22
  18. package/dist/config.js.map +1 -1
  19. package/dist/core/agent-session-runtime.d.ts +12 -3
  20. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  21. package/dist/core/agent-session-runtime.js +64 -38
  22. package/dist/core/agent-session-runtime.js.map +1 -1
  23. package/dist/core/agent-session-services.d.ts +1 -0
  24. package/dist/core/agent-session-services.d.ts.map +1 -1
  25. package/dist/core/agent-session-services.js +2 -0
  26. package/dist/core/agent-session-services.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +86 -16
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +865 -181
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  32. package/dist/core/compaction/branch-summarization.js +21 -17
  33. package/dist/core/compaction/branch-summarization.js.map +1 -1
  34. package/dist/core/compaction/compaction.d.ts +3 -5
  35. package/dist/core/compaction/compaction.d.ts.map +1 -1
  36. package/dist/core/compaction/compaction.js +43 -10
  37. package/dist/core/compaction/compaction.js.map +1 -1
  38. package/dist/core/exec.d.ts +12 -0
  39. package/dist/core/exec.d.ts.map +1 -1
  40. package/dist/core/exec.js +74 -28
  41. package/dist/core/exec.js.map +1 -1
  42. package/dist/core/export-html/template.js +4 -0
  43. package/dist/core/extensions/index.d.ts +1 -1
  44. package/dist/core/extensions/index.d.ts.map +1 -1
  45. package/dist/core/extensions/index.js.map +1 -1
  46. package/dist/core/extensions/loader.d.ts.map +1 -1
  47. package/dist/core/extensions/loader.js +7 -0
  48. package/dist/core/extensions/loader.js.map +1 -1
  49. package/dist/core/extensions/runner.d.ts +9 -5
  50. package/dist/core/extensions/runner.d.ts.map +1 -1
  51. package/dist/core/extensions/runner.js +162 -3
  52. package/dist/core/extensions/runner.js.map +1 -1
  53. package/dist/core/extensions/types.d.ts +110 -6
  54. package/dist/core/extensions/types.d.ts.map +1 -1
  55. package/dist/core/extensions/types.js.map +1 -1
  56. package/dist/core/keybindings.d.ts +8 -0
  57. package/dist/core/keybindings.d.ts.map +1 -1
  58. package/dist/core/model-registry.d.ts +4 -0
  59. package/dist/core/model-registry.d.ts.map +1 -1
  60. package/dist/core/model-registry.js +179 -28
  61. package/dist/core/model-registry.js.map +1 -1
  62. package/dist/core/model-resolver.d.ts.map +1 -1
  63. package/dist/core/model-resolver.js +24 -6
  64. package/dist/core/model-resolver.js.map +1 -1
  65. package/dist/core/output-throughput-history.d.ts +25 -0
  66. package/dist/core/output-throughput-history.d.ts.map +1 -0
  67. package/dist/core/output-throughput-history.js +240 -0
  68. package/dist/core/output-throughput-history.js.map +1 -0
  69. package/dist/core/output-throughput.d.ts +43 -0
  70. package/dist/core/output-throughput.d.ts.map +1 -0
  71. package/dist/core/output-throughput.js +161 -0
  72. package/dist/core/output-throughput.js.map +1 -0
  73. package/dist/core/resource-loader.d.ts +6 -0
  74. package/dist/core/resource-loader.d.ts.map +1 -1
  75. package/dist/core/resource-loader.js +76 -21
  76. package/dist/core/resource-loader.js.map +1 -1
  77. package/dist/core/scramjet-command-parser.d.ts +4 -0
  78. package/dist/core/scramjet-command-parser.d.ts.map +1 -1
  79. package/dist/core/scramjet-command-parser.js +72 -1
  80. package/dist/core/scramjet-command-parser.js.map +1 -1
  81. package/dist/core/sdk.d.ts +2 -0
  82. package/dist/core/sdk.d.ts.map +1 -1
  83. package/dist/core/sdk.js +61 -10
  84. package/dist/core/sdk.js.map +1 -1
  85. package/dist/core/session-manager.d.ts +11 -3
  86. package/dist/core/session-manager.d.ts.map +1 -1
  87. package/dist/core/session-manager.js +46 -11
  88. package/dist/core/session-manager.js.map +1 -1
  89. package/dist/core/settings-manager.d.ts +18 -6
  90. package/dist/core/settings-manager.d.ts.map +1 -1
  91. package/dist/core/settings-manager.js +84 -16
  92. package/dist/core/settings-manager.js.map +1 -1
  93. package/dist/core/skills.d.ts.map +1 -1
  94. package/dist/core/skills.js +6 -2
  95. package/dist/core/skills.js.map +1 -1
  96. package/dist/core/system-prompt.d.ts +2 -0
  97. package/dist/core/system-prompt.d.ts.map +1 -1
  98. package/dist/core/system-prompt.js +5 -2
  99. package/dist/core/system-prompt.js.map +1 -1
  100. package/dist/core/tools/bash.d.ts.map +1 -1
  101. package/dist/core/tools/bash.js +4 -3
  102. package/dist/core/tools/bash.js.map +1 -1
  103. package/dist/index.d.ts +4 -3
  104. package/dist/index.d.ts.map +1 -1
  105. package/dist/index.js +3 -2
  106. package/dist/index.js.map +1 -1
  107. package/dist/main.d.ts +16 -0
  108. package/dist/main.d.ts.map +1 -1
  109. package/dist/main.js +38 -10
  110. package/dist/main.js.map +1 -1
  111. package/dist/modes/interactive/components/armin.d.ts +4 -2
  112. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  113. package/dist/modes/interactive/components/armin.js +14 -8
  114. package/dist/modes/interactive/components/armin.js.map +1 -1
  115. package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  116. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  117. package/dist/modes/interactive/components/assistant-message.js +19 -8
  118. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  119. package/dist/modes/interactive/components/daxnuts.d.ts +4 -1
  120. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/daxnuts.js +14 -6
  122. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  123. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  124. package/dist/modes/interactive/components/extension-editor.js +5 -3
  125. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  126. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  127. package/dist/modes/interactive/components/extension-selector.js +4 -3
  128. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  129. package/dist/modes/interactive/components/footer.d.ts +4 -0
  130. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  131. package/dist/modes/interactive/components/footer.js +22 -18
  132. package/dist/modes/interactive/components/footer.js.map +1 -1
  133. package/dist/modes/interactive/components/model-selector.d.ts +3 -1
  134. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  135. package/dist/modes/interactive/components/model-selector.js +9 -3
  136. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  137. package/dist/modes/interactive/components/scoped-models-selector.d.ts +3 -1
  138. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  139. package/dist/modes/interactive/components/scoped-models-selector.js +8 -2
  140. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  141. package/dist/modes/interactive/components/settings-selector.d.ts +17 -3
  142. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  143. package/dist/modes/interactive/components/settings-selector.js +87 -16
  144. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  145. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  146. package/dist/modes/interactive/components/thinking-selector.js +2 -1
  147. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.d.ts +19 -1
  149. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/tool-execution.js +129 -36
  151. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  152. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  153. package/dist/modes/interactive/components/user-message.js +3 -2
  154. package/dist/modes/interactive/components/user-message.js.map +1 -1
  155. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/visual-truncate.js +2 -2
  157. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  158. package/dist/modes/interactive/interactive-mode.d.ts +37 -1
  159. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  160. package/dist/modes/interactive/interactive-mode.js +876 -175
  161. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  162. package/dist/modes/interactive/spellcheck.d.ts +26 -0
  163. package/dist/modes/interactive/spellcheck.d.ts.map +1 -0
  164. package/dist/modes/interactive/spellcheck.js +144 -0
  165. package/dist/modes/interactive/spellcheck.js.map +1 -0
  166. package/dist/modes/interactive/theme/{dark.json → pi-dark.json} +3 -2
  167. package/dist/modes/interactive/theme/{light.json → pi-light.json} +27 -26
  168. package/dist/modes/interactive/theme/theme-schema.json +6 -1
  169. package/dist/modes/interactive/theme/theme.d.ts +24 -4
  170. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  171. package/dist/modes/interactive/theme/theme.js +123 -22
  172. package/dist/modes/interactive/theme/theme.js.map +1 -1
  173. package/dist/modes/print-mode.d.ts.map +1 -1
  174. package/dist/modes/print-mode.js +5 -0
  175. package/dist/modes/print-mode.js.map +1 -1
  176. package/dist/modes/rpc/rpc-client.d.ts +3 -1
  177. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  178. package/dist/modes/rpc/rpc-client.js.map +1 -1
  179. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  180. package/dist/modes/rpc/rpc-mode.js +6 -1
  181. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  182. package/dist/package-manager-cli.d.ts.map +1 -1
  183. package/dist/package-manager-cli.js +33 -14
  184. package/dist/package-manager-cli.js.map +1 -1
  185. package/dist/utils/clipboard-native.d.ts +1 -0
  186. package/dist/utils/clipboard-native.d.ts.map +1 -1
  187. package/dist/utils/clipboard-native.js.map +1 -1
  188. package/dist/utils/clipboard.d.ts +6 -0
  189. package/dist/utils/clipboard.d.ts.map +1 -1
  190. package/dist/utils/clipboard.js +278 -10
  191. package/dist/utils/clipboard.js.map +1 -1
  192. package/dist/utils/tools-manager.d.ts +1 -1
  193. package/dist/utils/tools-manager.d.ts.map +1 -1
  194. package/dist/utils/tools-manager.js +7 -6
  195. package/dist/utils/tools-manager.js.map +1 -1
  196. package/dist/utils/version-check.d.ts +17 -5
  197. package/dist/utils/version-check.d.ts.map +1 -1
  198. package/dist/utils/version-check.js +88 -21
  199. package/dist/utils/version-check.js.map +1 -1
  200. package/docs/compaction.md +9 -3
  201. package/docs/custom-provider.md +13 -4
  202. package/docs/extensions.md +139 -21
  203. package/docs/keybindings.md +25 -1
  204. package/docs/models.md +34 -2
  205. package/docs/packages.md +2 -0
  206. package/docs/prompt-templates.md +2 -0
  207. package/docs/quickstart.md +1 -4
  208. package/docs/rpc.md +8 -6
  209. package/docs/sdk.md +22 -3
  210. package/docs/session-format.md +2 -1
  211. package/docs/settings.md +13 -3
  212. package/docs/skills.md +7 -5
  213. package/docs/terminal-setup.md +79 -2
  214. package/docs/terminal-verification.md +71 -0
  215. package/docs/themes.md +63 -10
  216. package/docs/tui.md +120 -25
  217. package/docs/usage.md +6 -4
  218. package/examples/extensions/border-status-editor.ts +2 -3
  219. package/examples/extensions/doom-overlay/doom-component.ts +3 -3
  220. package/examples/extensions/mac-system-theme.ts +3 -2
  221. package/examples/extensions/modal-editor.ts +13 -8
  222. package/examples/extensions/overlay-qa-tests.ts +22 -9
  223. package/examples/extensions/overlay-test.ts +5 -3
  224. package/examples/extensions/snake.ts +3 -1
  225. package/examples/extensions/space-invaders.ts +6 -3
  226. package/examples/extensions/subagent/index.ts +20 -17
  227. package/package.json +69 -58
package/docs/settings.md CHANGED
@@ -38,7 +38,7 @@ Edit directly or use `/settings` for common options.
38
38
 
39
39
  | Setting | Type | Default | Description |
40
40
  |---------|------|---------|-------------|
41
- | `theme` | string | `"dark"` | Theme name (`"dark"`, `"light"`, or custom) |
41
+ | `theme` | string | auto-detected | Theme name (`"dark"`, `"light"`, or custom). When unset, detected from terminal background on each startup (see [themes.md](themes.md#automatic-theme-detection)) |
42
42
  | `quietStartup` | boolean | `false` | Hide startup header |
43
43
  | `collapseChangelog` | boolean | `false` | Show condensed changelog after updates |
44
44
  | `enableInstallTelemetry` | boolean | `true` | Send an anonymous install/update version ping after first install or changelog-detected updates. This does not control update checks |
@@ -47,6 +47,17 @@ Edit directly or use `/settings` for common options.
47
47
  | `editorPaddingX` | number | `0` | Horizontal padding for input editor (0-3) |
48
48
  | `autocompleteMaxVisible` | number | `5` | Max visible items in autocomplete dropdown (3-20) |
49
49
  | `showHardwareCursor` | boolean | `false` | Show terminal cursor |
50
+ | `tuiMode` | string | `"retained"` | Startup-only `"retained"` or `"committed"` renderer; changes require restarting Scramjet |
51
+ | `dockEditor` | boolean | `true` | Keep input, adjacent widgets and footer docked in retained mode; live `/settings` control |
52
+ | `retainTranscriptOnExit` | boolean | `false` | Leave the full current styled transcript, widgets, rendered editor and footer in terminal scrollback on final exit from retained mode; live `/settings` control |
53
+ | `editorMaxHeightPercent` | number | `30` | Maximum wrapped input-text rows as 10–50% of terminal height; further reduced to fit the available space |
54
+ | `scrollWheelStep` | number | `3` | Retained transcript rows per wheel event, 1–20; live `/settings` control |
55
+
56
+ Exit retention preserves the current presentation, including offscreen transcript rows, colors, styling and collapsed/expanded state. It includes the rendered editor contents, not draft rows hidden by its height limit, and does not expand compacted or other-branch history. Images become labelled placeholders; selection highlighting, overlays, the scrollbar and browsing hints are excluded. This exposes rendered session and draft text to native terminal history, whose own scrollback capacity still applies. Suspension, external-editor handoffs and crash cleanup never request retention. Committed compatibility mode already writes native history and does not replay a duplicate. Session saving and `/export` are unchanged.
57
+
58
+ The editor-height percentage is a ceiling, not blank reserved space. Borders, autocomplete, widgets and the footer also require room; the input window shrinks before docking is suspended. Oversized extension content remains reachable in the undocked retained flow with a visible explanation. Undocking does not change renderer or terminal-buffer ownership. `committed` is an explicit compatibility choice with the older tail-windowed mutable-output limitation, not equivalent live browsing or a verified screen-reader mode; dock and wheel controls are unavailable there.
59
+
60
+ Numeric layout preferences are floored and clamped to their stated ranges; invalid types/non-finite values use safe defaults with settings diagnostics. Invalid explicit `tuiMode` values are rejected. Invalid explicit `retainTranscriptOnExit` values fall back to `false` with a diagnostic. These controls preserve global-write/project-override semantics: the selector identifies project overrides and shows the effective value even when editing the global preference. Failed writes show an unsaved notice and diagnostic; an in-memory value alone does not establish successful persistence. A malformed global settings file is preserved, and every blocked save attempt reports its load error even after startup diagnostics have been consumed.
50
61
 
51
62
  ### Telemetry and update checks
52
63
 
@@ -125,7 +136,7 @@ When a provider requests a retry delay longer than `retry.provider.maxRetryDelay
125
136
 
126
137
  | Setting | Type | Default | Description |
127
138
  |---------|------|---------|-------------|
128
- | `steeringMode` | string | `"one-at-a-time"` | How steering messages are sent: `"all"` or `"one-at-a-time"` |
139
+ | `steeringMode` | string | `"all"` | How steering messages are sent: `"all"` or `"one-at-a-time"` |
129
140
  | `followUpMode` | string | `"one-at-a-time"` | How follow-up messages are sent: `"all"` or `"one-at-a-time"` |
130
141
  | `transport` | string | `"sse"` | Preferred transport for providers that support multiple transports: `"sse"`, `"websocket"`, or `"auto"` |
131
142
 
@@ -135,7 +146,6 @@ When a provider requests a retry delay longer than `retry.provider.maxRetryDelay
135
146
  |---------|------|---------|-------------|
136
147
  | `terminal.showImages` | boolean | `true` | Show images in terminal (if supported) |
137
148
  | `terminal.imageWidthCells` | number | `60` | Preferred inline image width in terminal cells |
138
- | `terminal.clearOnShrink` | boolean | `false` | Clear empty rows when content shrinks (can cause flicker) |
139
149
  | `images.autoResize` | boolean | `true` | Resize images to 2000x2000 max |
140
150
  | `images.blockImages` | boolean | `false` | Block all images from being sent to LLM |
141
151
 
package/docs/skills.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  Skills are self-contained capability packages that the agent loads on-demand. A skill provides specialized workflows, setup instructions, helper scripts, and reference documentation for specific tasks.
6
6
 
7
+ A skill is supporting material, not a Scramjet command. Loading a skill—including through `/skill:name`—does not wrap it in `<scramjet-command>`, associate it with Scramjet's command lifecycle, give it declared next-step behavior, or guarantee that it is an actively invoked executable task. When an active command uses a skill, the command's explicit Goals (or inferred outcomes for a legacy command), user decisions, trust boundaries, consumer contracts, and required side effects remain controlling; the skill supplies capabilities or guidance in support of them. Prompt templates are different again: they expand into ordinary user prompts. See the [Scramjet command authoring guide](../../scramjet/docs/command-authoring.md) for the command contract.
8
+
7
9
  Scramjet implements the [Agent Skills standard](https://agentskills.io/specification), warning about most violations but remaining lenient. Scramjet allows skill names to differ from their parent directory even though the standard disallows it; that rule is suboptimal for shared skill directories used across multiple agent harnesses.
8
10
 
9
11
  ## Table of Contents
@@ -75,11 +77,11 @@ This is progressive disclosure: only descriptions are always in context, full in
75
77
  Skills register as `/skill:name` commands:
76
78
 
77
79
  ```bash
78
- /skill:brave-search # Load and execute the skill
79
- /skill:pdf-tools extract # Load skill with arguments
80
+ /skill:brave-search # Load the skill instructions
81
+ /skill:pdf-tools extract # Load skill instructions with arguments
80
82
  ```
81
83
 
82
- Arguments after the command are appended to the skill content as `User: <args>`.
84
+ Arguments after the command are appended to the expanded skill instructions in the same ordinary user prompt.
83
85
 
84
86
  Toggle skill commands via `/settings` in interactive mode or in `settings.json`:
85
87
 
@@ -141,7 +143,7 @@ Per the [Agent Skills specification](https://agentskills.io/specification#frontm
141
143
  | Field | Required | Description |
142
144
  |-------|----------|-------------|
143
145
  | `name` | Yes | Max 64 chars. Lowercase a-z, 0-9, hyphens. Unlike the standard, Scramjet does not require this to match the parent directory because that standard requirement is suboptimal for shared skill directories. |
144
- | `description` | Yes | Max 1024 chars. What the skill does and when to use it. |
146
+ | `description` | Yes | The Agent Skills specification maximum is 1024 chars. Scramjet warns above this limit but retains the complete description in the generated model-visible skills list unless model invocation is disabled. |
145
147
  | `license` | No | License name or reference to bundled file. |
146
148
  | `compatibility` | No | Max 500 chars. Environment requirements. |
147
149
  | `metadata` | No | Arbitrary key-value mapping. |
@@ -179,7 +181,7 @@ Scramjet validates skills against the Agent Skills standard. Most issues produce
179
181
 
180
182
  - Name exceeds 64 characters or contains invalid characters
181
183
  - Name starts/ends with hyphen or has consecutive hyphens
182
- - Description exceeds 1024 characters
184
+ - Description exceeds the Agent Skills specification maximum of 1024 characters. This warning is advisory: Scramjet retains the complete description in the generated model-visible skills list unless `disable-model-invocation` is `true`.
183
185
 
184
186
  Unknown frontmatter fields are ignored.
185
187
 
@@ -2,9 +2,84 @@
2
2
 
3
3
  Scramjet uses the [Kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) for reliable modifier key detection. Most modern terminals support this protocol, but some require configuration.
4
4
 
5
- ## Kitty, iTerm2
5
+ ## Transcript browsing and copying
6
6
 
7
- Work out of the box.
7
+ **Retained mode is the interactive default.** Scramjet owns the live transcript, scrollbar and selection instead of using terminal scrollback. Your terminal's Find feature cannot search retained history that exists only in Scramjet's memory; searching the currently painted screen, where supported, is not a full-transcript search. Use `/export` for a durable HTML view.
8
+
9
+ To use the older native-history renderer, set `"tuiMode": "committed"` in your [settings file](settings.md#ui--display) and restart Scramjet. This is a compatibility choice, not equivalent live browsing: mutable output taller than the live canvas is tail-windowed until finalized. Changing docking in `/settings` does not change renderer. The interactions below describe retained mode.
10
+
11
+ During an interactive session, Scramjet owns the alternate screen, mouse reporting, the **rightmost transcript scrollbar**, and drag selection. Wheel/trackpad input and scrollbar dragging browse retained output, including running cards. The terminal's native scrollbar/history is not the live transcript. Returning to the document bottom resumes following; passive updates do not pull you away from what you are reading. The input area is docked by default; typing there preserves the transcript anchor. `/settings` can change docking, the input-text height ceiling (30%, range 10–50%), and wheel step (3, range 1–20). Input shrinks around adjacent widgets before an oversized band falls back to explained, undocked retained browsing. Selection remains live during output and layout changes. Endpoints track component-local positions on a best-effort basis; the displayed highlight, not an earlier intended range, determines copying.
12
+
13
+ The leading dock gap shows `Session: N lines below · Ctrl+End: latest` when rendered session rows remain below the viewport. It uses existing whitespace, not another rule or the editor's hidden-draft indicators, and returns to a blank row at the tail. Narrow layouts shorten the hint while retaining **Session**, or show `Session ↓…` when the exact number cannot fit. It is not an unread count and is excluded from application copying. Scramjet choice controls have paired rules and row-based selection-following windows; partial neighboring choices remain visible, while arrows still select whole options. These changes do not alter selector cancellation, countdowns, transcript wheel ownership or approval safeguards.
14
+
15
+ Double-click to select a word or triple-click to select one displayed line, not adjacent wrapped lines. Clicks must land on the same cell within 500 ms of the preceding press; whitespace and punctuation select their own contiguous runs. Select longer ranges by ordinary dragging, including across a screen edge. After starting a drag, keep the left button held and use the wheel to extend the transcript selection, either with a stationary pointer or while adjusting its horizontal and vertical position; reversing the wheel adjusts the endpoint. Release before browsing if you want to keep the selection unchanged. A press without an initial drag does not start wheel selection. Drags cross between transcript and dock in either direction: a transcript-origin downward drag autoscrolls to the bottom before entering the dock; a dock-origin upward drag selects the displayed transcript portion without including the hidden gap below it. Wheel scrolling does not extend a selection confined to the dock. Right-click a nonempty selection or use `tui.input.copy` (Ctrl+C by default) to copy displayed text without ANSI controls or scrollbar cells. Built-in text, Markdown and editor copying omit known component padding and rejoin soft wraps, retaining genuine indentation, hard line breaks and structural boundaries. Tables, repeated quote prefixes and unannotated custom renderers retain their physical layout rather than guessing at their source. Output and waiting controls continue updating while selected. Copy snapshots the last painted selection immediately, before clipboard delivery; unpainted pointer changes cannot change that copy. A new selection must have painted before it can be copied. Resizing ends an active drag and remaps its selection; successful copy or Escape clears selection. Copy failures retain the selection and temporarily display a diagnostic on the last screen row without changing geometry; clicking that diagnostic dismisses it without selecting the covered text. Without selection, Ctrl+C retains its normal application behavior. Right-clicking any content area—including transcript, blank space, widgets, footer or editor—reads local clipboard text into the visible, focused editor at its caret without submitting. The scrollbar, overlays, selectors and approval controls do not paste. A pending repaint is flushed before reading; changing the input owner while waiting cancels the request. Repeated eligible right-clicks during one clipboard read share that read; only the latest still-current request inserts text. Other intervening input, focus loss, editing or session changes cancel a pending paste; focus-in, terminal cell-size notifications and unmodified right-button motion do not count as editing input. WSL prewarms one PowerShell process while the retained TUI runs and uses direct Windows Unicode clipboard access on explicit paste requests. It does not poll or cache clipboard contents. Startup and each read have five-second timeouts and text is limited to 10 MiB of UTF-8; brief clipboard-lock contention has a bounded acquisition retry, but failed reads are not automatically repeated. The reader closes on exit, suspension or external-editor handoff and prewarms again on resume. This avoids per-click process startup; clipboard contention and initial startup can still add latency. macOS, Wayland/X11 and Termux use their local backends. Unsupported or remote clipboard reads show an actionable error; use your terminal's Paste command instead.
16
+
17
+ Terminal-owned Copy/context menus cannot see application selection. A terminal or multiplexer that intercepts pointer input must be configured to forward it. In tmux, enable `set -g mouse on` for the application's wheel/drag path; tmux's own copy mode is a separate interaction. See [tmux.md](tmux.md) for modified-key setup.
18
+
19
+ Home/End retain editor line-start/end behavior at every scroll position. Ctrl+Home navigates to the transcript beginning; Ctrl+End navigates to the bottom and resumes following. Submitting nonempty editor input with Enter or Alt+Enter resumes following immediately, before routing bash/slash commands or queueing messages. New user-message delivery also resumes following, while passive output preserves browsing. Alt+PageUp/Alt+PageDown provide keyboard-only entry into transcript browsing; when detached, PageUp/PageDown browse and Escape returns to the tail. Docked editing and presentation toggles keep the reading anchor, while ordinary undocked editing reveals its cursor. At the tail, normal editor/list bindings apply, and focused overlays retain keyboard precedence. See [keybindings.md](keybindings.md#transcript-browsing-precedence).
20
+
21
+ Clipboard delivery uses the existing platform backend (for example `pbcopy`, `wl-copy`, `xclip`/`xsel`) or OSC 52 where appropriate. Wayland copying awaits stdin completion and a successful `wl-copy` parent exit, with a five-second subprocess timeout; observable failure tries the existing X11/OSC 52 fallbacks before releasing selection. Terminals can reject OSC 52 without acknowledgement: a successful request alone is not proof that a desktop clipboard changed. Remote sessions, clipboard security policies and other profiles require their own verification.
22
+
23
+ Orderly exit restores the shell's normal buffer without printing a transcript or clearing prior shell output by default. Enable **Retain transcript on exit** in `/settings` (`retainTranscriptOnExit: true`) to append the full current styled transcript, including offscreen rows, widgets, rendered editor contents and footer. It preserves collapsed/expanded state, excludes viewport decorations and overlays, and replaces images with labelled placeholders. Hidden draft rows and earlier compacted/other-branch content are not reconstructed; native scrollback capacity still applies. Model-scope and tool-provisioning startup notices belong to the TUI rather than leaking into the shell. Session saving is unchanged. Suspension and external-editor handoffs restore normal terminal modes without dumping transcript copies, regardless of the setting. Crash cleanup prioritizes mode restoration; terminal loss cannot guarantee a flush. For a durable rich view, use `/export`—HTML rendering is unchanged. A retained terminal smaller than 12 columns or three rows shows a resize notice and blocks ordinary input until a usable frame is restored; configured interrupt and empty-draft exit remain available.
24
+
25
+ Built-in Kitty/iTerm2 images fit the viewport without changing retained source data. Partially visible placements and images behind overlays/selection show placeholders rather than painting through other regions; scroll to reveal the full placement or clear selection. Images remain disabled inside tmux. Custom graphics wrappers must forward the optional image-height bound described in [tui.md](tui.md); arbitrary graphics envelopes are not proven compatible.
26
+
27
+ After an expired raw Escape/CSI prefix, active mouse reporting recognizes one immediately following mouse-shaped suffix even after a long idle gap. Matching suffixes are discarded rather than inserted or acted on; other typing is replayed, with incomplete candidates released after 10 ms. Consequently, identical mouse-shaped literal text in that position is consumed, and mouse suffixes themselves fragmented beyond 10 ms are not guaranteed recovery. Bracketed paste is kept separate. This bounded ambiguity policy avoids indefinitely withholding ordinary typing; it is not universal lossless framing over arbitrarily delayed connections.
28
+
29
+ ### Native compatibility evidence
30
+
31
+ Native checks cover browsing, copying, live updates, resizing, approval controls, terminal handoffs and restoration; separate pixel checks cover built-in Kitty/iTerm2 images. These configurations describe earlier retained-renderer journeys, not acceptance of every subsequent change. See [Terminal verification](terminal-verification.md) for revision-specific evidence, driver details and historical limitations.
32
+
33
+ Recorded native configurations (2026-09-21):
34
+
35
+ | Path | Tested version and configuration |
36
+ |------|----------------------------------|
37
+ | Windows Terminal / WSL | Terminal 1.24.11911.0, Windows 11 build 22631, WSL2 5.15.153.1, Ubuntu 20.04.6; 120×30; existing profile unchanged |
38
+ | Apple Terminal | 2.14 / 455.1, macOS 15.7.9 / 24G830 ARM64; 80×24 |
39
+ | iTerm2 | 3.6.11 on that macOS; 80×25; `ReportRightClick=true`; inline-image consent for graphics checks |
40
+ | Linux VTE | Xfce Terminal 1.1.3, Ubuntu 24.04.5, X11/Xvfb/Openbox; DejaVu Sans Mono 12, 80×24, native scrollbar hidden |
41
+ | tmux | 3.4 on the preceding VTE path; isolated config with mouse on and status off |
42
+ | xterm | XTerm 390 on Ubuntu 24.04.5/X11; 80×24; `selectToClipboard=true` with its native Shift+Insert paste binding |
43
+ | Kitty | 0.32.2 on Ubuntu 24.04.5/X11; 80×24 interaction and 80×30 graphics profiles |
44
+
45
+ xterm's native paste binding/source differ from VTE's Ctrl+Shift+V; the recorded paste configuration is not a new Scramjet shortcut. Native xterm evidence does not verify VS Code's integrated terminal, nor does Kitty verify every Kitty-family emulator.
46
+
47
+ These checks do not establish every emulator/profile, physical trackpad's gesture characteristics, remote desktop, multiplexer, custom extension or graphics renderer. OS-generated wheel events are not physical trackpad testing; headless tests and ordinary CI do not substitute for native desktop/clipboard evidence.
48
+
49
+ ### Native selection differences — deferred
50
+
51
+ Terminal-owned selection is separate from Scramjet selection. Windows Terminal reserves Shift-mouse for its own screen-cell selection; [iTerm2 uses Option to bypass mouse reporting](https://iterm2.com/faq.html). These modifiers are terminal/profile-specific, not a universal Scramjet shortcut. They do not expose unpainted retained history or extend an application selection, and native copying can include visible padding or scrollbar cells. Scramjet's copy cleanup applies only to its ordinary drag/Ctrl+C/right-click path.
52
+
53
+ Native-selection parity remains deferred. Retained edge scrolling uses a fixed speed, not the native distance-dependent acceleration demonstrated on Windows Terminal. Changing modifiers during an active edge drag can withhold its release from the application; clear the application selection to stop a stranded gesture. See the [historical comparison](terminal-verification.md#historical-windows-selection-comparison) for the observations and their limits.
54
+
55
+ ## Apple Terminal
56
+
57
+ In the tested Apple Terminal 2.14 profile, Option+PageUp arrived as unmodified PageUp. Scramjet cannot distinguish those identical bytes without stealing ordinary editor paging. Use an explicit application keybinding profile instead:
58
+
59
+ ```json
60
+ {
61
+ "tui.viewport.pageUp": "f8",
62
+ "tui.viewport.pageDown": "f9"
63
+ }
64
+ ```
65
+
66
+ Set this in `~/.scramjet/agent/keybindings.json`; keyboards with media-key defaults may require Fn+F8/Fn+F9. The Apple native journey declares and exercises this temporary profile. Other tested terminal paths exercise the default Alt+PageUp/Alt+PageDown bindings. This is not an automatic runtime terminal allowlist or a claim about every keyboard layout.
67
+
68
+ ## Kitty
69
+
70
+ Keyboard reporting works out of the box.
71
+
72
+ ## iTerm2
73
+
74
+ Keyboard reporting works out of the box. **Right-click copying requires iTerm2's `ReportRightClick` preference** so mouse-reporting applications receive right-clicks instead of iTerm2's native context menu. The default-off profile was tested and does not support Scramjet's right-click-copy path. This is an explicit setup requirement, not a runtime allowlist or automatic fallback.
75
+
76
+ Enable the right-click-reporting option in iTerm2's Pointer settings, or quit iTerm2 and set its existing preference from another terminal:
77
+
78
+ ```sh
79
+ defaults write com.googlecode.iterm2 ReportRightClick -bool true
80
+ ```
81
+
82
+ Restart iTerm2 afterward. This changes right-click behavior for mouse-reporting applications, not just Scramjet; Scramjet never changes the preference for you. Ctrl+C remains the application-selection copy alternative. Native iTerm2 menus still cannot access application-owned selection.
8
83
 
9
84
  ## Ghostty
10
85
 
@@ -63,6 +138,8 @@ Add to `keybindings.json` to enable `Shift+Enter` for multi-line input:
63
138
 
64
139
  ## Windows Terminal
65
140
 
141
+ With mouse reporting active, Windows Terminal 1.24.11911.0 delivers Ctrl+wheel to the application instead of applying its font-zoom shortcut. Ignoring the received event cannot hand it back to the terminal. Use the terminal's Ctrl+plus/Ctrl+minus shortcuts to zoom; these were verified on the same Windows Terminal/WSL path. Scramjet does not change your terminal preferences or synthesize desktop shortcuts. This remains a Ctrl+wheel limitation of retained mode.
142
+
66
143
  Add to `settings.json` (Ctrl+Shift+, or Settings → Open JSON file) to forward the modified Enter keys Scramjet uses:
67
144
 
68
145
  ```json
@@ -0,0 +1,71 @@
1
+ # Terminal verification
2
+
3
+ Maintainer notes for the retained viewport. User operation, compatibility choices and terminal configuration belong in [Terminal Setup](terminal-setup.md). The records below preserve historical observations; moving them here does not establish acceptance of a newer revision.
4
+
5
+ ## Evidence boundaries
6
+
7
+ | Check | What it establishes | What it does not establish |
8
+ |-------|---------------------|----------------------------|
9
+ | Headless TUI and production-composition tests | Parsed emitted text/cells, cursor position, intermediate frames, application input dispatch and flush ordering | Native input delivery, desktop clipboard contents or graphics pixels |
10
+ | Transport tests | Framing, timeout and keyboard/mouse mode sequences | Every terminal's delivery behavior |
11
+ | Native interaction journeys | Delivered desktop input, clipboard equality, configured terminal behavior and restoration | Every profile, physical trackpad, remote connection or custom component |
12
+ | Native graphics journeys | Fresh image pixels, confinement, suppression and restoration on the tested path | Arbitrary custom graphics envelopes |
13
+ | Driver self-tests | Required-check sets, failure detection, action receipts and cleanup predicates | A fresh native acceptance run |
14
+
15
+ The shared headless terminal feeds actual TUI writes into `@xterm/headless`; its `sendInput()` calls the application handler directly. A headless assertion and a desktop check with similar wording can therefore establish different things. Do not remove a native check without identifying the exact duplicate predicate, execution boundary and any calibration, sequencing or cleanup role.
16
+
17
+ The native journey fixture is `packages/scramjet/tests/fixtures/interactive-viewport.mjs`. Hosted interaction and graphics drivers live in `.github/scripts/terminal-probe.py` and `.github/scripts/terminal-safety.py`; `.github/scripts/test-terminal-fixtures.py` exercises their evidence predicates. `.github/workflows/terminal-feasibility.yml` owns the hosted matrix. Build the runtime before running fixtures, because they load compiled packages. Windows desktop/clipboard execution through `.github/scripts/windows-terminal-probe.sh` requires separate explicit permission.
18
+
19
+ ## Exit retention and right-click latency — 2026-09-28
20
+
21
+ A user-authorized targeted Windows Terminal 1.24.11911.0 / Windows build 22631 / WSL Ubuntu journey exercised rebuilt packages on the dirty working tree based on `6c06a61812dcc9fc324b570eef35d6bbbd487a43`. An isolated production InteractiveMode used a nonempty model scope, 180 numbered styled history rows, above/below widgets, an editor draft and an underlined footer. It loaded no personal extensions and made no model calls. This was a targeted check, not full-matrix acceptance.
22
+
23
+ All 12 native predicates passed: calibrated desktop targeting, retention default off, first right-click paste, held-right-button motion paste, terminal-native paste, selector suppression, the actual `/settings` retention toggle, external-editor return, paste after that handoff, orderly exit and termios restoration. Five single-gesture motion pastes inserted exactly once in 13–25 ms from received right press to completed editor insertion; the first paste took about 36 ms. Native paste took about 0.36 ms from received payload to insertion. These are small-sample application-boundary timings, not physical-input-to-pixel latency guarantees. The fixture verified stable synthetic clipboard contents before each gesture; clipboard contention immediately after a change can still add latency.
24
+
25
+ Inspection of native screenshots confirmed styled history and the complete editor/widget/footer suffix in the restored shell. Parsing the captured application stdout with headless xterm additionally verified every numbered row and the model-scope header exactly once, their ANSI cell attributes, dock order, clean shell styles, no transcript replay between alternate-screen exit/reentry during the external-editor handoff, and zero submissions. Desktop window closure, pointer/focus restoration and absence of leftover matching Windows clipboard workers were independently verified. Deterministic tests cover default-off startup stdout isolation, both docking layouts, settings persistence/overrides/invalid values, graphics placeholders, failed snapshot restoration and clipboard lifecycle/stale-input boundaries.
26
+
27
+ The latency investigation first measured roughly 440–540 ms of per-request PowerShell startup/setup using clipboard-free output. Reusing PowerShell removed that cost, but the Forms-based reader still took roughly 150–180 ms in the native fixture. Direct Windows Unicode clipboard access with bounded lock acquisition reduced the measured delay without polling or caching clipboard contents. Preliminary runs also exposed a fixture import error, unstable synthetic clipboard seeding (including an empty terminal-native control paste), a focus-loss safety abort and an immediate native clipboard-lock rejection; those failed runs are not acceptance evidence. The final reader bounds acquisition retries before reading data and never repeats an ambiguously failed read.
28
+
29
+ Reproduction: start an isolated freshly built InteractiveMode with scoped startup and several screens of numbered styled text; calibrate desktop cells, seed and verify synthetic clipboard text, perform one stationary right-click and five press/move/release gestures while recording input and insertion timestamps, compare a native paste, exercise a real confirmation and `/settings` toggle, round-trip an external editor, then exit while scrolled into history. Check the emitted normal buffer and screenshots, termios, owned-window/pointer/focus cleanup and worker termination. Other operating systems, the full native matrix, native image pixels, both external-editor variants together, and job-control suspension were not rerun for this change; images deliberately become labelled placeholders in retained exit history.
30
+
31
+ ## Paste, submission and underline diagnostics — 2026-09-28
32
+
33
+ A user-authorized Windows Terminal 1.24.11911.0 / Windows build 22631 / WSL Ubuntu journey exercised rebuilt packages on the dirty working tree based on `90b71a225075af2015338a8c31d35c79cc60a403`. An isolated production fixture used synthetic clipboard text, no personal extensions and no model calls. This was a targeted diagnostic, not full-matrix acceptance.
34
+
35
+ Before production edits, test-designer-guided regressions reproduced eight editor-submission failures, cancellation of pending paste by focus-in/cell-size notifications, ignored no-selection clicks outside the editor, a lost click during a pending repaint, and curly-underline padding/continuation errors. Actual editor spellcheck replacement and scrolling cleared underline attributes in headless tests before the changes; that control did not reproduce the reported persistent native marks.
36
+
37
+ The final native journey passed eight predicates: first-click paste over transcript, editor-click paste, selector paste suppression, Ctrl+wheel delivered without zoom, keyboard zoom, keyboard zoom restoration, bash submission following, and termios restoration. Clipboard insertion was exact and did not submit. Window closure, pointer restoration and foreground restoration were independently verified. Reproduction uses the real production composition: seed synthetic clipboard text, right-click transcript before other input, repeat over the editor, open a confirmation and verify right-click neither pastes nor answers, then browse away and submit `!printf NATIVE-SUBMIT-OUTPUT`. The pre-fix bash journey left output offscreen; the rebuilt journey followed it.
38
+
39
+ For zoom, hold Ctrl and wheel upward: stdin received `ESC[<80;11;3M` while terminal geometry remained unchanged. Ctrl+plus reduced the cell grid; Ctrl+minus restored it. This confirms terminal mouse routing precedes zoom on this path; swallowing that packet is not a zoom implementation. No terminal preferences were changed.
40
+
41
+ Native screenshots showed deterministic curly spellcheck underlines disappear after replacement with plain text and remain absent after scrolling. The separate fixed-coordinate persistent-underline report remains unresolved; the wrapping fix is not claimed as its root cause. The final focused run passed 479 TypeScript tests and 75 native-driver self-tests. Linux/macOS/tmux, physical trackpads, native graphics and the full desktop journey were not rerun. Two preliminary disposable-fixture attempts failed on fixture import/provider setup and supplied no acceptance evidence.
42
+
43
+ ## Dock presentation check — 2026-09-25
44
+
45
+ A user-authorized, targeted Windows Terminal/WSL desktop journey passed **28/28 checks** against rebuilt packages on the uncommitted working tree based on `762822b4bb456bc32f79512a009ae97f6d98a4b0`. The recorded candidate digest was `2e6b47dac18baf1ef518f980dc2dd6b1c00f62e5c123fc9279a247164c13a022` (SHA-256 over sorted changed/untracked repository paths and their contents, each NUL-delimited, before this evidence-record edit). Environment: Windows Terminal 1.24.11911.0, Windows build 22631, WSL Ubuntu, Node 25.2.1; initial/restored geometry 120×30, fitting resize 102×25, plus a smaller unfit-selector case. The run deliberately recorded dirty-tree provenance; it was not the clean-checkout full-PR matrix.
46
+
47
+ The real production fixture hosted all four Scramjet selector variants, not lookalike components. Delivered desktop arrows/Enter verified framing, choices and model preview; mixed-height options exposed partial neighboring choices. Further checks covered resize/overlay restoration, genuine insufficient-space fallback, cancellation/draft restoration, explicit undocking/redocking, live continuation counts and tail blanking. Desktop clipboard reads independently matched displayed selector fragments and a transcript/editor seam selection without copying the Session decoration. Screenshots were inspected. Termios, owned-window closure, pointer position and foreground restoration all passed.
48
+
49
+ The targeted journey reused the Windows driver's owned-window/calibration/cleanup helpers and the fixture's command receipts. Reproduction uses a freshly built `interactive-viewport.mjs --production --journey`, its `selector-confirm`, `selector-select`, `selector-next`, `selector-model`, `overlay`, `close-overlay`, `update` and `copy-seam` actions, and real desktop navigation, resizing and copying. Opening receipts acknowledge mounting/flushing without awaiting an answer; `selector.result` records the later outcome. The permanent interaction drivers now require the product-selector/count checks and reject missing evidence.
50
+
51
+ Two earlier attempts exposed fixture-oracle limitations rather than production defects: uniform-height options aligned exactly with the viewport and did not exercise partial neighbors, then trailing spaces in synthetic descriptions disagreed with the trimmed screen-recording oracle. The final fixture uses mixed heights and trims its synthetic descriptions; assertions were retained. Those failed attempts are not acceptance evidence.
52
+
53
+ This verifies the declared Windows journey only. The hosted Linux/macOS/tmux interaction matrix, native Kitty/iTerm2 graphics pixels, physical trackpads and arbitrary extensions were not rerun. Deterministic tests cover image-covered/placeholder gaps, but are not native graphics acceptance. Existing broader PR verification obligations remain separate.
54
+
55
+ ## Historical native journey evidence
56
+
57
+ Detailed successes, failed attempts and exact source commits are recorded in [issue #551](https://github.com/LeanAndMean/scramjet/issues/551) and [PR #560](https://github.com/LeanAndMean/scramjet/pull/560). The [recorded configuration table](terminal-setup.md#native-compatibility-evidence) describes earlier retained-renderer journeys, not blanket acceptance of subsequent changes. Match each claim to the source revision and check names in its report.
58
+
59
+ The hosted drivers require a zero fixture-exit receipt and terminal restoration; both macOS interaction and graphics drivers separately own and verify closure of their terminal process, windows and shell. The Windows driver independently attempts exit and window closure, then verifies window disappearance, pointer position and foreground restoration; a failed or unverified cleanup cannot pass. Graphics confinement compares fresh image pixels with independent single-row color bands in the safety fixture's above-editor widget and last footer row, preserving their heights and accounting for the leading dock spacer. Calibration uses screenshot pixels rather than default cell dimensions, so display scaling is measured rather than assumed. Graphics suppression checks require post-render action receipts and at least two fresh screenshots over a bounded stability interval. The representative VTE journey includes narrow wrapped editing, autocomplete acceptance and a separate committed-mode startup/promotion, approval, external-editor and suspend/resume journey. No-selection right-click checks outside the editor seed synthetic clipboard text, wait for acknowledged input, and require exact insertion without submission or changes to copy/error/terminal-paste counters over a stability interval; intercepted terminal paste remains payload-free. The current journeys also seed synthetic clipboard text and right-click the actual editor, checking exact insertion and no submission. Selection checks require an updated live footer while selection remains active, exact wrapped-editor copying, and exact copying across the transcript/editor seam in both directions. These predicates replace the older whole-frame freeze expectation; historical successes do not certify them. Linux interaction startup retains its 15-second visible-window requirement and records the owned process, pre-cleanup exit status, last window-search result and bounded failure diagnostics; terminal stdout/stderr and Openbox logs are retained with the artifact. Diagnostic observations cannot turn a failed startup into a pass. Windows desktop/clipboard execution requires separate explicit permission.
60
+
61
+ ## Historical Windows selection comparison
62
+
63
+ A local comparison on 2026-09-23 used Windows Terminal 1.24.11911.0, Windows 11 build 22631 and WSL Ubuntu, with the existing terminal profile unchanged. A disposable fixture loaded the real compiled InteractiveMode with 1,500 numbered synthetic rows and isolated in-memory settings; no models or personal extensions were loaded. The installed baseline was `b2bb160d7e9f11670915c2f568bc91e3707e3ca7`; the candidate was `575f90bfd847d302eae7f52f901879920f0b8397`, rebuilt only in the development clone. Native desktop events dragged to an edge and then 16 or 100 pixels beyond the window, held there, and released outside. Screenshots, raw fixture stdin, synthetic clipboard reads and terminal-state receipts supplied the observations.
64
+
65
+ - The installed baseline and candidate `committed` mode accelerated with outside distance. For example, baseline upward screenshots advanced 112 rows over approximately 638 ms at 16 pixels, versus 584 rows over approximately 607 ms at 100 pixels. These are bounded observations, not a calibrated speed curve.
66
+ - Candidate `retained` mode stayed near its fixed 12.5 rows/second at both distances. Upward motion reached `ESC[<32;11;1M`, with no additional motion packet revealing outside distance; release arrived as `ESC[<0;11;1m`. Downward motion/release likewise used the bottom cell, row 21. This input path does not supply the distance needed to reproduce native acceleration merely by changing the scroll-rate formula.
67
+ - Retained scrolling stopped after outside release. Separate short-selection checks showed highlight persistence through a wheel-scroll round trip, right-click delivery of selected synthetic rows, and left-click clearing in all three modes. They do not establish byte-for-byte copy parity or the entire combined long-selection journey. All three successful runs restored their initial termios state.
68
+
69
+ An initial accessibility `TextPatternRange.GetText` call crashed the desktop driver; its owned window/button were cleaned up, and the replacement used screenshots rather than that API. A subsequent attempt coalesced mouse movement and did not establish selection; only the paced native-event run supports the observations above. Evidence stayed local. No terminal preferences or installed baseline files were changed. This comparison is not a complete acceptance suite, and retained/native selection parity remains unverified and demonstrably different in acceleration. `committed` remains the existing compatibility option, with its separate mutable-output browsing limitation.
70
+
71
+ The held-button wheel interaction was compared separately on the same Windows Terminal/WSL path. A press alone followed by stationary wheel input did not start native selection; after a small initial drag, the installed baseline and candidate `committed` mode extended selection on wheel input, while pre-fix retained mode kept only the initial range. The baseline checkout had advanced to `49c1f1209f8129a6dffc84c534da5b1c364ceb14` without changing its TUI implementation. The retained fix now preserves that initial-drag distinction and updates only active transcript selection endpoints. Deterministic checks cover both directions, reversal across the origin, events before repaint, release, boundaries and dock confinement. Three Windows driver cases use wheel-only desktop events and reject intervening drag-motion packets; three more mix horizontal/vertical motion with scrolling in both event orders, reverse direction, and perform a final column adjustment immediately followed by release. All six scroll again after release and independently compare exact Unicode clipboard text. The mixed-input deterministic checks also cover bursts before repaint, dragging after wheel input, final wide/combining-grapheme boundaries, and edge-timer ticks between pointer updates. They exposed and now prevent stale painted-coordinate endpoints and stale edge-timer pointer state, while retaining initial painted-row targeting and dock confinement. All six native cases passed against the rebuilt working tree in a targeted subset with explicit dirty-tree provenance, startup/calibration and restoration checks; the full native journey and other terminal paths were not rerun. These results do not resolve the deferred native-selection differences.
package/docs/themes.md CHANGED
@@ -12,13 +12,14 @@ Themes are JSON files that define colors for the TUI.
12
12
  - [Theme Format](#theme-format)
13
13
  - [Color Tokens](#color-tokens)
14
14
  - [Color Values](#color-values)
15
+ - [Terminal Background](#terminal-background)
15
16
  - [Tips](#tips)
16
17
 
17
18
  ## Locations
18
19
 
19
20
  Scramjet loads themes from:
20
21
 
21
- - Built-in: `dark`, `light`
22
+ - Built-in: `pi-dark`, `pi-light`, and `scramjet-dark` (the bundled default for dark-mode terminals)
22
23
  - Global: `~/.scramjet/agent/themes/*.json`
23
24
  - Project: `.scramjet/themes/*.json`
24
25
  - Packages: `themes/` directories or `pi.themes` entries in `package.json`
@@ -37,7 +38,25 @@ Select a theme via `/settings` or in `settings.json`:
37
38
  }
38
39
  ```
39
40
 
40
- On first run, scramjet detects your terminal background and defaults to `dark` or `light`.
41
+ An explicit `theme` setting is always authoritative — automatic detection never overrides it.
42
+
43
+ ### Automatic Theme Detection
44
+
45
+ When no explicit theme is configured, scramjet classifies the terminal as light or dark on each interactive startup, then maps that classification to a theme. The classification uses the following precedence chain:
46
+
47
+ 1. **`COLORFGBG` environment variable** — If set (e.g., `0;15` or `15;0;0`), the final semicolon-delimited field is interpreted as an xterm-256 color index. Its RGB approximation is classified by WCAG relative luminance (threshold 0.2) as light or dark.
48
+ 2. **Apple Terminal heuristic** — If `TERM_PROGRAM=Apple_Terminal` and no valid `COLORFGBG` is present, classifies as `light`. Users with custom dark profiles can set `theme: "scramjet-dark"` explicitly.
49
+ 3. **OSC 11 terminal query** — On terminals that support it (iTerm2, Kitty, WezTerm, Windows Terminal, Alacritty), scramjet queries the actual background color via the OSC 11 escape sequence. The query has a 100 ms timeout to avoid perceptible startup delay.
50
+ 4. **Fallback** — If all of the above fail or time out, classifies as `dark`.
51
+
52
+ The classification then maps to a theme name:
53
+
54
+ - **dark** → `scramjet-dark`, scramjet's bundled default. When `scramjet-dark` is not registered — e.g. Pi used without scramjet — this falls back to `pi-dark`.
55
+ - **light** → `pi-light`.
56
+
57
+ Automatic detection runs on every interactive startup. The result is never persisted to settings — terminal profiles can change between launches, and detection will adapt.
58
+
59
+ > **Note:** macOS Terminal.app does not support OSC 11 queries (nor truecolor), but the Apple Terminal heuristic (step 2) correctly selects the light theme for its default configuration.
41
60
 
42
61
  ## Creating a Custom Theme
43
62
 
@@ -139,13 +158,13 @@ vim ~/.scramjet/agent/themes/my-theme.json
139
158
 
140
159
  - `name` is required and must be unique.
141
160
  - `vars` is optional. Define reusable colors here, then reference them in `colors`.
142
- - `colors` must define all 51 required tokens.
161
+ - `colors` must define all 52 required tokens.
143
162
 
144
163
  The `$schema` field enables editor auto-completion and validation.
145
164
 
146
165
  ## Color Tokens
147
166
 
148
- Every theme must define all 51 color tokens. There are no optional colors.
167
+ Every theme must define all 52 color tokens. There are no optional colors.
149
168
 
150
169
  ### Core UI (11 colors)
151
170
 
@@ -235,9 +254,15 @@ Editor border colors indicating thinking level (visual hierarchy from subtle to
235
254
  |-------|---------|
236
255
  | `bashMode` | Editor border in bash mode (`!` prefix) |
237
256
 
257
+ ### Spellcheck (1 color)
258
+
259
+ | Token | Purpose |
260
+ |-------|---------|
261
+ | `spellcheckError` | Misspelled-word foreground in the editor; the underline is applied independently |
262
+
238
263
  ### HTML Export (optional)
239
264
 
240
- The `export` section controls colors for `/export` HTML output. If omitted, colors are derived from `userMessageBg`.
265
+ The `export` section controls colors for `/export` HTML output only. If omitted, colors are derived from `userMessageBg`.
241
266
 
242
267
  ```json
243
268
  {
@@ -249,6 +274,8 @@ The `export` section controls colors for `/export` HTML output. If omitted, colo
249
274
  }
250
275
  ```
251
276
 
277
+ > **Note:** `export.pageBg` controls the HTML export background, not the terminal background. Scramjet cannot set the terminal's background color — that is always owned by the terminal emulator. See [Terminal Background](#terminal-background) below.
278
+
252
279
  ## Color Values
253
280
 
254
281
  Four formats are supported:
@@ -258,7 +285,16 @@ Four formats are supported:
258
285
  | Hex | `"#ff0000"` | 6-digit hex RGB |
259
286
  | 256-color | `39` | xterm 256-color palette index (0-255) |
260
287
  | Variable | `"primary"` | Reference to a `vars` entry |
261
- | Default | `""` | Terminal's default color |
288
+ | Default | `""` | Terminal's default foreground/background |
289
+
290
+ ### Empty-String Terminal Defaults
291
+
292
+ When a color token is set to `""`, scramjet emits `\x1b[39m` (reset foreground) or `\x1b[49m` (reset background), deferring to the terminal's configured default colors. This means:
293
+
294
+ - The theme cannot guarantee contrast for empty-string tokens, because the terminal's actual foreground/background is unknown at theme time.
295
+ - Tokens like `text`, `userMessageText`, `customMessageText`, and `toolOutput` use `""` in the built-in themes to inherit the terminal's default foreground.
296
+ - The built-in `pi-light` theme also sets `spellcheckError` to `""`, preserving the terminal foreground for readable misspelled text while the editor applies the spell-check underline independently.
297
+ - If your terminal's default foreground creates poor contrast against themed element backgrounds, you can override these tokens with explicit colors in a custom theme.
262
298
 
263
299
  ### 256-Color Palette
264
300
 
@@ -276,20 +312,37 @@ Check truecolor support:
276
312
  echo $COLORTERM # Should output "truecolor" or "24bit"
277
313
  ```
278
314
 
315
+ ## Terminal Background
316
+
317
+ Scramjet styles individual UI elements (message boxes, tool boxes, selected items) with themed backgrounds, but it does **not** control the terminal's overall background color. The space between and around themed elements shows the terminal emulator's own background.
318
+
319
+ This is by design — setting the terminal background via escape sequences (OSC 11 mutation) is invasive, not universally supported, and affects scrollback behavior. Instead:
320
+
321
+ - The built-in `pi-light` theme is designed for white or near-white terminal backgrounds.
322
+ - The `scramjet-dark` theme (and Pi's `pi-dark`) is designed for dark terminal backgrounds.
323
+ - Automatic detection selects the appropriate theme based on your terminal's actual background.
324
+ - For best results, ensure your terminal profile's background color matches the theme family (light background → light theme, dark background → dark theme).
325
+
326
+ ### Light Theme Contrast
327
+
328
+ The built-in light theme guarantees WCAG AA contrast (4.5:1) for explicit text-bearing foreground colors against their actual rendering surfaces (element backgrounds and white canvas). Terminal-default foregrounds, including `spellcheckError`, depend on the terminal profile and cannot carry a static theme contrast guarantee. Element backgrounds (`userMessageBg`, `toolPendingBg`, etc.) maintain at least 1.30:1 contrast against white for visual separation. These guarantees hold in both truecolor and 256-color rendering modes.
329
+
279
330
  ## Tips
280
331
 
281
332
  **Dark terminals:** Use bright, saturated colors with higher contrast.
282
333
 
283
- **Light terminals:** Use darker, muted colors with lower contrast.
334
+ **Light terminals:** Use darker, muted colors. The built-in light theme targets 4.5:1+ contrast against white.
284
335
 
285
336
  **Color harmony:** Start with a base palette (Nord, Gruvbox, Tokyo Night), define it in `vars`, and reference consistently.
286
337
 
287
- **Testing:** Check your theme with different message types, tool states, markdown content, and long wrapped text.
338
+ **Testing:** Check your theme with different message types, tool states, markdown content, and long wrapped text. Test in both truecolor and 256-color mode if your users may have older terminals.
288
339
 
289
340
  **VS Code:** Set `terminal.integrated.minimumContrastRatio` to `1` for accurate colors.
290
341
 
291
342
  ## Examples
292
343
 
293
344
  See the built-in themes:
294
- - [dark.json](../src/modes/interactive/theme/dark.json)
295
- - [light.json](../src/modes/interactive/theme/light.json)
345
+ - [pi-dark.json](../src/modes/interactive/theme/pi-dark.json)
346
+ - [pi-light.json](../src/modes/interactive/theme/pi-light.json)
347
+
348
+ Scramjet's bundled default dark theme lives at [`packages/scramjet/themes/scramjet-dark.json`](../../scramjet/themes/scramjet-dark.json).