@timurproko/a1 0.2.7-dev.1252 → 0.2.7-dev.1264

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 (296) hide show
  1. package/bin/ui.js +3 -3
  2. package/dist/app/session-shell/clipboard-diagnostics.d.ts +2 -2
  3. package/dist/app/session-shell/clipboard-image.d.ts +0 -1
  4. package/dist/app/session-shell/clipboard-image.js +1 -1
  5. package/dist/app/session-shell/dialog-coordinator.d.ts +6 -5
  6. package/dist/app/session-shell/dialog-coordinator.js +3 -4
  7. package/dist/app/session-shell/dock-chrome.d.ts +11 -6
  8. package/dist/app/session-shell/dock-chrome.js +5 -8
  9. package/dist/app/session-shell/editor-hyperlink-budget.d.ts +0 -2
  10. package/dist/app/session-shell/editor-hyperlink-budget.js +1 -1
  11. package/dist/app/session-shell/exit-notice.d.ts +2 -2
  12. package/dist/app/session-shell/exit-notice.js +1 -1
  13. package/dist/app/session-shell/extension-surface.d.ts +8 -7
  14. package/dist/app/session-shell/helper-pool.d.ts +3 -4
  15. package/dist/app/session-shell/helper-pool.js +1 -1
  16. package/dist/app/session-shell/image-preparation-client.d.ts +4 -3
  17. package/dist/app/session-shell/image-preparation.d.ts +0 -2
  18. package/dist/app/session-shell/image-preparation.js +2 -2
  19. package/dist/app/session-shell/image-source.d.ts +2 -3
  20. package/dist/app/session-shell/image-source.js +2 -2
  21. package/dist/app/session-shell/input-surface-switch.d.ts +9 -7
  22. package/dist/app/session-shell/input-surface-switch.js +1 -2
  23. package/dist/app/session-shell/login-interaction.d.ts +5 -4
  24. package/dist/app/session-shell/login-interaction.js +2 -3
  25. package/dist/app/session-shell/paste-executor.d.ts +4 -8
  26. package/dist/app/session-shell/paste-executor.js +2 -2
  27. package/dist/app/session-shell/paste-protocol.d.ts +2 -2
  28. package/dist/app/session-shell/path-chip-presentation.d.ts +0 -5
  29. package/dist/app/session-shell/path-chip-presentation.js +3 -3
  30. package/dist/app/session-shell/prompt-chips.d.ts +8 -7
  31. package/dist/app/session-shell/prompt-history-controller.d.ts +3 -3
  32. package/dist/app/session-shell/prompt-submission.d.ts +5 -2
  33. package/dist/app/session-shell/prompt-submission.js +13 -14
  34. package/dist/app/session-shell/quit-outro-effects.d.ts +3 -3
  35. package/dist/app/session-shell/quit-outro-effects.js +1 -4
  36. package/dist/app/session-shell/quit-outro.d.ts +9 -12
  37. package/dist/app/session-shell/quit-outro.js +11 -11
  38. package/dist/app/session-shell/resource-labels.d.ts +2 -0
  39. package/dist/app/session-shell/resource-labels.js +158 -0
  40. package/dist/app/session-shell/response-copy-protocol.d.ts +3 -2
  41. package/dist/app/session-shell/response-copy-transport.d.ts +2 -14
  42. package/dist/app/session-shell/response-copy-transport.js +1 -1
  43. package/dist/app/session-shell/session-lifecycle.d.ts +2 -1
  44. package/dist/app/session-shell/session-prompt-history.d.ts +29 -0
  45. package/dist/app/session-shell/session-prompt-history.js +80 -0
  46. package/dist/app/session-shell/session-selectors.d.ts +46 -0
  47. package/dist/app/session-shell/session-selectors.js +269 -0
  48. package/dist/app/session-shell/session-shell-root.d.ts +76 -113
  49. package/dist/app/session-shell/session-shell-root.js +103 -329
  50. package/dist/app/session-shell/session-shell.d.ts +14 -37
  51. package/dist/app/session-shell/session-shell.js +177 -612
  52. package/dist/app/session-shell/session-viewport-controller.d.ts +9 -16
  53. package/dist/app/session-shell/session-viewport-controller.js +42 -320
  54. package/dist/app/session-shell/settings-bindings.d.ts +25 -3
  55. package/dist/app/session-shell/settings-bindings.js +26 -2
  56. package/dist/app/session-shell/share-operation.d.ts +3 -3
  57. package/dist/app/session-shell/share-operation.js +4 -6
  58. package/dist/app/session-shell/shell-clipboard.d.ts +59 -0
  59. package/dist/app/session-shell/shell-clipboard.js +119 -0
  60. package/dist/app/session-shell/shell-editor-factory.d.ts +8 -4
  61. package/dist/app/session-shell/shell-editor-factory.js +13 -15
  62. package/dist/app/session-shell/shell-host.d.ts +16 -17
  63. package/dist/app/session-shell/shell-host.js +18 -19
  64. package/dist/app/session-shell/shell-layout.d.ts +2 -2
  65. package/dist/app/session-shell/shell-prompt-suggestions.d.ts +26 -0
  66. package/dist/app/session-shell/shell-prompt-suggestions.js +48 -0
  67. package/dist/app/session-shell/suggestion-surface.d.ts +1 -6
  68. package/dist/app/session-shell/suggestion-surface.js +1 -1
  69. package/dist/app/session-shell/system-clipboard.d.ts +2 -14
  70. package/dist/app/session-shell/system-clipboard.js +2 -7
  71. package/dist/app/session-shell/transcript-document.d.ts +11 -8
  72. package/dist/app/session-shell/transcript-document.js +13 -14
  73. package/dist/app/session-shell/viewport-layout.d.ts +2 -2
  74. package/dist/app/session-shell/viewport-pointer.d.ts +83 -0
  75. package/dist/app/session-shell/viewport-pointer.js +361 -0
  76. package/dist/app/session-shell/workflow-output.d.ts +5 -3
  77. package/dist/app/session-shell/workflow-output.js +13 -15
  78. package/dist/cli/package-messages.d.ts +0 -1
  79. package/dist/cli/package-messages.js +1 -1
  80. package/dist/cli/session-context.d.ts +1 -1
  81. package/dist/cli/uninstall.d.ts +3 -2
  82. package/dist/composition/owned-settings.d.ts +2 -1
  83. package/dist/composition/owned-settings.js +0 -3
  84. package/dist/composition/owned-ui.d.ts +1 -1
  85. package/dist/composition/owned-ui.js +77 -73
  86. package/dist/composition/reference-selection-options.d.ts +1 -1
  87. package/dist/composition/reference-selection-options.js +1 -1
  88. package/dist/composition/session-runtime-registry.d.ts +2 -1
  89. package/dist/composition/settings-route-host.js +3 -10
  90. package/dist/composition/settings-search-input.d.ts +2 -1
  91. package/dist/composition/startup-route.d.ts +2 -2
  92. package/dist/composition/startup-route.js +2 -2
  93. package/dist/contracts/owned-ui/extension-ui.d.ts +2 -1
  94. package/dist/contracts/owned-ui/index.d.ts +2 -1
  95. package/dist/contracts/owned-ui/index.js +1 -1
  96. package/dist/contracts/owned-ui/session-presenters.d.ts +3 -0
  97. package/dist/contracts/owned-ui/shell-components.d.ts +543 -0
  98. package/dist/contracts/owned-ui/validation.d.ts +1 -11
  99. package/dist/contracts/owned-ui/validation.js +8 -104
  100. package/dist/contracts/presentation/component.d.ts +17 -0
  101. package/dist/contracts/presentation/component.js +8 -0
  102. package/dist/contracts/presentation/index.d.ts +6 -15
  103. package/dist/contracts/presentation/index.js +2 -0
  104. package/dist/contracts/presentation/terminal.d.ts +284 -0
  105. package/dist/contracts/presentation/terminal.js +1 -0
  106. package/dist/contracts/presentation/theme.d.ts +46 -0
  107. package/dist/contracts/presentation/theme.js +1 -0
  108. package/dist/features/launch/development-launch.js +1 -1
  109. package/dist/features/launch/runtime-selection.d.ts +2 -2
  110. package/dist/features/launch/runtime-selection.js +2 -2
  111. package/dist/features/owned-ui/project-trust-prompt.d.ts +2 -2
  112. package/dist/features/owned-ui/project-trust-prompt.js +1 -1
  113. package/dist/features/owned-ui/release-note-state.d.ts +2 -1
  114. package/dist/features/owned-ui/settings-app.js +49 -425
  115. package/dist/features/owned-ui/settings-edits.d.ts +31 -0
  116. package/dist/features/owned-ui/settings-edits.js +139 -0
  117. package/dist/features/owned-ui/settings-model-dialog.d.ts +41 -0
  118. package/dist/features/owned-ui/settings-model-dialog.js +194 -0
  119. package/dist/features/owned-ui/settings-scroll-rail.d.ts +29 -0
  120. package/dist/features/owned-ui/settings-scroll-rail.js +79 -0
  121. package/dist/features/owned-ui/settings-search-input.d.ts +2 -1
  122. package/dist/features/prompt-history/service.d.ts +3 -2
  123. package/dist/foundation/lifecycle/claim-gate.d.ts +2 -1
  124. package/dist/foundation/release/automatic-update.d.ts +5 -14
  125. package/dist/foundation/release/automatic-update.js +15 -22
  126. package/dist/foundation/release/bootstrap.js +4 -4
  127. package/dist/foundation/release/dependency-certification.d.ts +0 -1
  128. package/dist/foundation/release/dependency-certification.js +1 -1
  129. package/dist/foundation/release/dependency-layer.js +3 -3
  130. package/dist/foundation/release/early-accent.d.ts +2 -2
  131. package/dist/foundation/release/early-accent.js +1 -1
  132. package/dist/foundation/release/index.d.ts +2 -2
  133. package/dist/foundation/release/index.js +1 -1
  134. package/dist/foundation/release/latest-release.d.ts +3 -18
  135. package/dist/foundation/release/latest-release.js +11 -11
  136. package/dist/foundation/release/managed-path.d.ts +10 -0
  137. package/dist/foundation/release/managed-path.js +26 -0
  138. package/dist/foundation/release/ordinary-launch-selection.d.ts +1 -1
  139. package/dist/foundation/release/ordinary-launch-selection.js +1 -1
  140. package/dist/foundation/release/release-gc.d.ts +3 -3
  141. package/dist/foundation/release/release-gc.js +10 -12
  142. package/dist/foundation/release/{cohort-state.d.ts → release-state.d.ts} +23 -23
  143. package/dist/foundation/release/{cohort-state.js → release-state.js} +8 -8
  144. package/dist/foundation/release/restart-certification.js +3 -4
  145. package/dist/foundation/release/update-activation.js +3 -3
  146. package/dist/foundation/release/update-execution-lease.d.ts +2 -3
  147. package/dist/foundation/release/update-execution-lease.js +2 -8
  148. package/dist/foundation/release/update-installer.d.ts +45 -0
  149. package/dist/foundation/release/update-installer.js +57 -0
  150. package/dist/foundation/release/update-launch.d.ts +2 -2
  151. package/dist/foundation/release/update-recovery.js +2 -9
  152. package/dist/foundation/release/update.d.ts +4 -25
  153. package/dist/foundation/release/update.js +164 -183
  154. package/dist/foundation/startup/startup-budget.d.ts +5 -5
  155. package/dist/foundation/startup/startup-budget.js +2 -2
  156. package/dist/integrations/pi/components/bracketed-paste.d.ts +33 -0
  157. package/dist/integrations/pi/components/bracketed-paste.js +123 -0
  158. package/dist/integrations/pi/components/editor-interaction.d.ts +4 -12
  159. package/dist/integrations/pi/components/index.d.ts +15 -9
  160. package/dist/integrations/pi/components/index.js +9 -5
  161. package/dist/integrations/pi/components/lazy-selectors.d.ts +2 -10
  162. package/dist/integrations/pi/components/lazy-selectors.js +1 -1
  163. package/dist/integrations/pi/components/modal-frame.d.ts +5 -22
  164. package/dist/integrations/pi/components/modal-frame.js +3 -23
  165. package/dist/integrations/pi/components/owned-editor-ux.d.ts +4 -3
  166. package/dist/integrations/pi/components/owned-editor-ux.js +46 -140
  167. package/dist/integrations/pi/components/prompt-input-port.d.ts +2 -19
  168. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -1
  169. package/dist/integrations/pi/components/shell-editor-autocomplete.d.ts +3 -2
  170. package/dist/integrations/pi/components/shell-footer-status.d.ts +2 -2
  171. package/dist/integrations/pi/components/shell-footer-status.js +3 -2
  172. package/dist/integrations/pi/components/shell-presenters-info.d.ts +6 -5
  173. package/dist/integrations/pi/components/shell-presenters-info.js +2 -1
  174. package/dist/integrations/pi/components/shell-session-info-reference.d.ts +2 -1
  175. package/dist/integrations/pi/components/shell-shared-facade.d.ts +33 -227
  176. package/dist/integrations/pi/components/shell-shared-facade.js +8 -1
  177. package/dist/integrations/pi/components/skills-command.d.ts +1 -9
  178. package/dist/integrations/pi/components/skills-command.js +5 -5
  179. package/dist/integrations/pi/components/theme.d.ts +9 -2
  180. package/dist/integrations/pi/components/theme.js +17 -2
  181. package/dist/integrations/pi/components/thinking-selector-dialog.d.ts +2 -1
  182. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
  183. package/dist/integrations/pi/engine/adapter.d.ts +4 -13
  184. package/dist/integrations/pi/engine/adapter.js +9 -61
  185. package/dist/integrations/pi/engine/changelog.d.ts +2 -2
  186. package/dist/integrations/pi/engine/changelog.js +3 -3
  187. package/dist/integrations/pi/engine/compaction-progress.d.ts +2 -3
  188. package/dist/integrations/pi/engine/compaction-progress.js +1 -1
  189. package/dist/integrations/pi/engine/conformance.d.ts +2 -1
  190. package/dist/integrations/pi/engine/default-model-setting.d.ts +0 -2
  191. package/dist/integrations/pi/engine/default-model-setting.js +1 -2
  192. package/dist/integrations/pi/engine/event-delivery.d.ts +2 -1
  193. package/dist/integrations/pi/engine/extension-ui-binding.d.ts +2 -1
  194. package/dist/integrations/pi/engine/host.js +3 -3
  195. package/dist/integrations/pi/engine/message-values.d.ts +0 -1
  196. package/dist/integrations/pi/engine/message-values.js +1 -1
  197. package/dist/integrations/pi/engine/pending-delivery.d.ts +0 -4
  198. package/dist/integrations/pi/engine/pending-delivery.js +3 -3
  199. package/dist/integrations/pi/engine/ports.d.ts +7 -1
  200. package/dist/integrations/pi/engine/ports.js +4 -0
  201. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +2 -1
  202. package/dist/integrations/pi/engine/prompt-suggestions.d.ts +3 -2
  203. package/dist/integrations/pi/engine/provider-authentication.d.ts +1 -1
  204. package/dist/integrations/pi/engine/public-main-entry.d.ts +1 -5
  205. package/dist/integrations/pi/engine/public-main-entry.js +1 -1
  206. package/dist/integrations/pi/engine/repository-metadata.d.ts +49 -0
  207. package/dist/integrations/pi/engine/repository-metadata.js +189 -0
  208. package/dist/integrations/pi/engine/repository-pr-rest.d.ts +1 -9
  209. package/dist/integrations/pi/engine/repository-pr-rest.js +2 -2
  210. package/dist/integrations/pi/engine/repository-pr.d.ts +1 -3
  211. package/dist/integrations/pi/engine/repository-pr.js +1 -1
  212. package/dist/integrations/pi/engine/resource-catalog.d.ts +2 -1
  213. package/dist/integrations/pi/engine/session-integration.d.ts +1 -1
  214. package/dist/integrations/pi/engine/session-runtime.d.ts +7 -12
  215. package/dist/integrations/pi/engine/session-runtime.js +21 -178
  216. package/dist/integrations/pi/engine/settings-bridge.d.ts +0 -2
  217. package/dist/integrations/pi/engine/settings-bridge.js +1 -1
  218. package/dist/integrations/pi/engine/settings-port.d.ts +3 -2
  219. package/dist/integrations/pi/engine/transcript-projection.d.ts +2 -1
  220. package/dist/integrations/pi/engine/view-state.d.ts +21 -2
  221. package/dist/integrations/pi/engine/view-state.js +129 -1
  222. package/dist/integrations/pi/engine/workflow-contexts.d.ts +1 -1
  223. package/dist/integrations/pi/engine/workflow-runner.d.ts +1 -2
  224. package/dist/integrations/pi/engine/workflow-runner.js +254 -365
  225. package/dist/integrations/pi/engine/workflow-share.d.ts +9 -0
  226. package/dist/integrations/pi/engine/workflow-share.js +168 -0
  227. package/dist/integrations/pi/engine/workflow-support.d.ts +0 -20
  228. package/dist/integrations/pi/engine/workflow-support.js +4 -65
  229. package/dist/integrations/pi/engine/workflows.d.ts +5 -5
  230. package/dist/integrations/pi/session-presenters/components.d.ts +3 -0
  231. package/dist/integrations/pi/session-presenters/components.js +75 -0
  232. package/dist/integrations/pi/session-presenters/index.d.ts +1 -0
  233. package/dist/integrations/pi/session-presenters/index.js +1 -0
  234. package/dist/integrations/pi/session-presenters/presentation.d.ts +3 -0
  235. package/dist/integrations/pi/session-presenters/presentation.js +10 -0
  236. package/dist/integrations/pi/session-presenters/presenters.d.ts +2 -1
  237. package/dist/integrations/pi/session-presenters/presenters.js +3 -1
  238. package/dist/integrations/pi/tui-runtime/adapter.d.ts +9 -19
  239. package/dist/integrations/pi/tui-runtime/adapter.js +56 -174
  240. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +9 -44
  241. package/dist/integrations/pi/tui-runtime/index.d.ts +1 -1
  242. package/dist/integrations/pi/tui-runtime/index.js +1 -0
  243. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +4 -6
  244. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +4 -10
  245. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +3 -9
  246. package/dist/integrations/pi/tui-runtime/presentation-adapter.d.ts +2 -2
  247. package/dist/integrations/pi/tui-runtime/runtime-factory.d.ts +3 -0
  248. package/dist/integrations/pi/tui-runtime/runtime-factory.js +11 -0
  249. package/dist/ui/components/index.d.ts +2 -2
  250. package/dist/ui/components/index.js +1 -1
  251. package/dist/ui/components/selection-copy.d.ts +0 -1
  252. package/dist/ui/components/selection-copy.js +0 -12
  253. package/dist/ui/components/theme.d.ts +4 -19
  254. package/dist/ui/components/theme.js +6 -0
  255. package/dist/ui/components/transcript-viewport-frame.d.ts +120 -0
  256. package/dist/ui/components/transcript-viewport-frame.js +314 -0
  257. package/dist/ui/components/transcript-viewport-types.d.ts +147 -0
  258. package/dist/ui/components/transcript-viewport-types.js +1 -0
  259. package/dist/ui/components/transcript-viewport.d.ts +2 -147
  260. package/dist/ui/components/transcript-viewport.js +87 -315
  261. package/dist/ui/settings/declarations.d.ts +2 -3
  262. package/dist/ui/settings/declarations.js +2 -3
  263. package/dist/ui/settings/early-accent.js +1 -1
  264. package/dist/ui/settings/index.d.ts +3 -5
  265. package/dist/ui/settings/index.js +2 -3
  266. package/dist/ui/settings/manager.d.ts +0 -4
  267. package/dist/ui/settings/manager.js +6 -62
  268. package/dist/ui/settings/resolution.d.ts +6 -18
  269. package/dist/ui/settings/resolution.js +4 -103
  270. package/dist/ui/settings/sections.d.ts +0 -4
  271. package/dist/ui/settings/sections.js +1 -1
  272. package/docs/architecture/boundaries.md +2 -2
  273. package/docs/architecture/internal-naming.md +2 -0
  274. package/docs/architecture/project-structure.md +4 -2
  275. package/docs/validation.md +1 -1
  276. package/package.json +3 -4
  277. package/dist/integrations/pi/components/components.d.ts +0 -4
  278. package/dist/integrations/pi/components/components.js +0 -48
  279. package/dist/integrations/pi/components/conformance.d.ts +0 -18
  280. package/dist/integrations/pi/components/conformance.js +0 -70
  281. package/dist/integrations/pi/components/shell-components.d.ts +0 -7
  282. package/dist/integrations/pi/components/shell-components.js +0 -7
  283. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +0 -33
  284. package/dist/integrations/pi/components/upstream/theme/theme-controller.js +0 -114
  285. package/dist/integrations/pi/engine/diagnostics-state.d.ts +0 -9
  286. package/dist/integrations/pi/engine/diagnostics-state.js +0 -34
  287. package/dist/integrations/pi/engine/usage-view.d.ts +0 -4
  288. package/dist/integrations/pi/engine/usage-view.js +0 -56
  289. package/dist/integrations/pi/tui-runtime/contracts.d.ts +0 -166
  290. package/dist/ui/settings/migrations.d.ts +0 -19
  291. package/dist/ui/settings/migrations.js +0 -225
  292. package/dist/ui/settings/progress-palette.generated.d.ts +0 -139
  293. package/dist/ui/settings/progress-palette.generated.js +0 -139
  294. /package/dist/{integrations/pi/tui-runtime/contracts.js → contracts/owned-ui/shell-components.js} +0 -0
  295. /package/dist/{foundation/release → contracts/presentation}/progress-palette.generated.d.ts +0 -0
  296. /package/dist/{foundation/release → contracts/presentation}/progress-palette.generated.js +0 -0
@@ -1,6 +1,9 @@
1
- import { DIALOG_BACK_SHORTCUT_HINT, DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
2
- import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, configureDialogChoiceCapacity, configureDialogTextRows, configureDialogWidth, numericValues, RAIL_COLUMNS, renderCenteredDialogOverlay, renderDialogPanel, renderSteppedDialogPanel, renderEmptyState, renderGroupHeader, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, handleLineInputKey, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, ScrollbarRails, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
2
+ import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, configureDialogChoiceCapacity, configureDialogTextRows, configureDialogWidth, numericValues, RAIL_COLUMNS, renderCenteredDialogOverlay, renderDialogPanel, renderEmptyState, renderGroupHeader, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
3
+ import { SettingsScrollRail } from "./settings-scroll-rail.js";
4
+ import { SettingsEdits, replaceRecord, sameRecord } from "./settings-edits.js";
3
5
  import { SETTINGS_APP_ID } from "./settings-route.js";
6
+ import { dialogWindow, modelDialogKey, modelDialogLines } from "./settings-model-dialog.js";
4
7
  export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
5
8
  const SCOPE = SETTINGS_APP_ID;
6
9
  const SETTINGS_TOP_RULE_ROWS = 1;
@@ -11,14 +14,7 @@ const SETTINGS_STATUS_ROWS = 1;
11
14
  const SETTINGS_CONTENT_INSET = 1;
12
15
  /** The panel a setting with parts opens: its own keys, its own hint. */
13
16
  const DIALOG_SCOPE = `${SETTINGS_APP_ID}-parts`;
14
- const MODEL_SCOPE = `${SETTINGS_APP_ID}-models`;
15
- const MODEL_LEVEL_SCOPE = `${SETTINGS_APP_ID}-model-levels`;
16
17
  const SCROLLBAR_TOP_INSET = 0;
17
- /** Identity of the settings list rail in the shared rail state. */
18
- const RAIL_KEY = "settings";
19
- // Compatibility: the transcript rail stays lit this long after a scroll, and repaints just after.
20
- const SCROLL_LINGER_MS = 900;
21
- const SCROLL_LINGER_REPAINT_MS = 925;
22
18
  /** What a structured value offers instead of printing itself. */
23
19
  const CONFIGURE = "configure";
24
20
  export const SETTINGS_SHORTCUTS = new ShortcutRegistry();
@@ -48,17 +44,6 @@ SETTINGS_SHORTCUTS.declare({
48
44
  hint: { keys: DIALOG_CLOSE_SHORTCUT_HINT.key, does: DIALOG_CLOSE_SHORTCUT_HINT.action },
49
45
  }, "close");
50
46
  assertNoShortcutConflicts(SETTINGS_SHORTCUTS.assemble());
51
- const MODEL_SHORTCUTS = new ShortcutRegistry();
52
- MODEL_SHORTCUTS.declare({ key: "edit", scope: MODEL_SCOPE, description: "Search models", section: "Choose", hint: { keys: "Type", does: "search" } }, "edit-filter");
53
- MODEL_SHORTCUTS.declare({ key: "up", scope: MODEL_SCOPE, description: "Previous model", section: "Choose" }, "previous");
54
- MODEL_SHORTCUTS.declare({ key: "down", scope: MODEL_SCOPE, description: "Next model", section: "Choose" }, "next");
55
- MODEL_SHORTCUTS.declare({ key: "enter", scope: MODEL_SCOPE, description: "Select model", section: "Choose", hint: { keys: "Enter", does: "select" } }, "select");
56
- MODEL_SHORTCUTS.declare({ key: "escape", scope: MODEL_SCOPE, description: "Close selector", section: "Choose", hint: { keys: DIALOG_CLOSE_SHORTCUT_HINT.key, does: DIALOG_CLOSE_SHORTCUT_HINT.action } }, "close");
57
- MODEL_SHORTCUTS.declare({ key: "up", scope: MODEL_LEVEL_SCOPE, description: "Previous level", section: "Choose", hint: { keys: "↑↓", does: "navigate" } }, "previous");
58
- MODEL_SHORTCUTS.declare({ key: "down", scope: MODEL_LEVEL_SCOPE, description: "Next level", section: "Choose", hint: { keys: "↑↓", does: "navigate" } }, "next");
59
- MODEL_SHORTCUTS.declare({ key: "enter", scope: MODEL_LEVEL_SCOPE, description: "Select level", section: "Choose", hint: { keys: "Enter", does: "select" } }, "select");
60
- MODEL_SHORTCUTS.declare({ key: "escape", scope: MODEL_LEVEL_SCOPE, description: "Return to models", section: "Choose", hint: { keys: DIALOG_BACK_SHORTCUT_HINT.key, does: DIALOG_BACK_SHORTCUT_HINT.action } }, "back");
61
- assertNoShortcutConflicts(MODEL_SHORTCUTS.assemble());
62
47
  const KEYS = {
63
48
  "\u001b[A": "up",
64
49
  "\u001b[B": "down",
@@ -96,12 +81,7 @@ export class SettingsApp {
96
81
  #menu = null;
97
82
  #structured = null;
98
83
  // Invariant: pending values remain visible until the source reflects them.
99
- #pending = new Map();
100
- #undoHistory = [];
101
- #editSequence = 0;
102
- #undoRequests = 0;
103
- #undoActive = false;
104
- #closed = false;
84
+ #edits;
105
85
  #bodyTopForFrame = SETTINGS_TOP_RULE_ROWS + SETTINGS_TITLE_ROWS;
106
86
  #bodyHeightForFrame = 0;
107
87
  #panelTopForFrame = 0;
@@ -110,16 +90,12 @@ export class SettingsApp {
110
90
  #frameRows = [];
111
91
  #menuFrame = null;
112
92
  // Invariant: rail hover and drag live in the shared keyed state, as the transcript's do.
113
- #rails = new ScrollbarRails();
93
+ #rail = new SettingsScrollRail();
114
94
  // Rationale: the rail as drawn in the last frame, or null when nothing can be pointed at.
115
- #railFrame = null;
116
- // Invariant: a scroll lights the rail until this time; the timer repaints once it has passed.
117
- #activeUntil = 0;
118
- #activityTimer;
119
- #renderedScroll;
120
95
  #options;
121
96
  constructor(session, options) {
122
97
  this.#session = session;
98
+ this.#edits = new SettingsEdits(session, () => this.#structured);
123
99
  this.#options = {
124
100
  dialogPanel: options.dialogPanel ?? null,
125
101
  };
@@ -131,17 +107,14 @@ export class SettingsApp {
131
107
  };
132
108
  }
133
109
  onActivate(host) {
134
- this.#closed = false;
110
+ this.#edits.open();
135
111
  void this.#session.load().then(() => host.requestRender());
136
112
  }
137
113
  onClose(_host) {
138
- this.#closed = true;
139
- this.#undoHistory.length = 0;
140
- this.#undoRequests = 0;
114
+ this.#edits.close();
141
115
  this.#search.input?.setFocused(false);
142
116
  this.#search.input = null;
143
- this.#clearActivityTimer();
144
- this.#rails.clear();
117
+ this.#rail.dispose();
145
118
  }
146
119
  render(rect, host) {
147
120
  const theme = host.theme ?? PLAIN_THEME;
@@ -174,11 +147,7 @@ export class SettingsApp {
174
147
  this.#panelTopForFrame = topRows + contentHeight + dividerRows;
175
148
  this.#scroll = layout.scroll;
176
149
  const now = Date.now();
177
- // Rationale: every way of scrolling ends in this frame, so a moved list is noticed here
178
- // once rather than at each wheel, drag, key, and search branch.
179
- if (this.#renderedScroll !== undefined && this.#renderedScroll !== layout.scroll)
180
- this.#noteScrollActivity(host, now);
181
- this.#renderedScroll = layout.scroll;
150
+ this.#rail.noteRenderedScroll(layout.scroll, now, () => host.requestRender());
182
151
  // Invariant: auto and always keep the rail columns while the list fits, so a revealed
183
152
  // rail never reflows the rows; hidden gives the columns back to the rows.
184
153
  const appearance = this.#scrollbarAppearance();
@@ -191,22 +160,10 @@ export class SettingsApp {
191
160
  scroll: layout.scroll,
192
161
  trackHeight: Math.max(0, titleRows + bodyHeight - SCROLLBAR_TOP_INSET),
193
162
  });
194
- const presentation = scrollbarPresentation({
195
- geometry,
196
- appearance,
197
- style: this.#scrollbarStyle(),
198
- hovered: this.#rails.isHovered(RAIL_KEY),
199
- dragging: this.#rails.isDragging(RAIL_KEY),
200
- activeUntil: this.#activeUntil,
201
- now,
202
- });
203
- this.#railFrame = reservesRail && geometry !== null
204
- ? {
205
- rail: { key: RAIL_KEY, column: rect.width, rowStart: topRows + SCROLLBAR_TOP_INSET, trackHeight: geometry.trackHeight },
206
- geometry,
207
- page: layout.visible,
208
- }
209
- : null;
163
+ const presentation = this.#rail.presentation({ geometry, appearance, style: this.#scrollbarStyle(), now });
164
+ this.#rail.setFrame(reservesRail && geometry !== null
165
+ ? { rail: { column: rect.width, rowStart: topRows + SCROLLBAR_TOP_INSET, trackHeight: geometry.trackHeight }, geometry, page: layout.visible }
166
+ : null);
210
167
  const body = [];
211
168
  this.#frameRows = [];
212
169
  if (rows.length === 0) {
@@ -275,7 +232,7 @@ export class SettingsApp {
275
232
  const row = rows[selected];
276
233
  if (row?.kind === "element" && (row.value.structured || isDefaultModelEntry(row.value)))
277
234
  this.#openStructured(row.value);
278
- else if (row?.kind === "element" && typeof this.#shownValue(row.value) === "number")
235
+ else if (row?.kind === "element" && typeof this.#edits.shown(row.value) === "number")
279
236
  this.#cycle(rows, selected, 1);
280
237
  else
281
238
  this.#openMenu(rows, selected, true);
@@ -313,7 +270,7 @@ export class SettingsApp {
313
270
  this.#cycle(rows, selected, action === "next-value" ? 1 : -1);
314
271
  return { consumed: true };
315
272
  case "undo":
316
- this.#undo();
273
+ this.#edits.undo();
317
274
  return { consumed: true };
318
275
  default:
319
276
  return { consumed: false };
@@ -355,7 +312,7 @@ export class SettingsApp {
355
312
  const value = menu.choices[overRow];
356
313
  this.#closeMenu(menu);
357
314
  if (value !== undefined)
358
- this.#apply(menu.entry, value);
315
+ this.#edits.apply(menu.entry, value);
359
316
  return { consumed: true };
360
317
  }
361
318
  if (event.kind === "wheel-up" || event.kind === "wheel-down") {
@@ -380,7 +337,14 @@ export class SettingsApp {
380
337
  // cannot change its selection or act on the Settings list behind it.
381
338
  return { consumed: true, render: false };
382
339
  }
383
- const rail = this.#railPointer(event);
340
+ const rail = this.#rail.pointer(event, this.#scroll);
341
+ if (rail.scroll !== undefined)
342
+ this.#scroll = rail.scroll;
343
+ // Invariant: the rail is not a row: pointing at it lights nothing in the list.
344
+ if (rail.overRail) {
345
+ this.#hoverKey = null;
346
+ this.#hoverRegion = "label";
347
+ }
384
348
  if (rail.owned)
385
349
  return { consumed: true };
386
350
  const row = this.#frameRows.find(candidate => candidate.screenRow === event.row - 1);
@@ -410,52 +374,6 @@ export class SettingsApp {
410
374
  const changed = previousKey !== this.#hoverKey || previousRegion !== this.#hoverRegion || rail.changed;
411
375
  return { consumed: event.kind !== "motion", render: changed };
412
376
  }
413
- // Rationale: the rail takes its share of a pointer report first: hover, a thumb drag, or a
414
- // track page. Owned means the list must not see the report; changed means the rail looks different.
415
- #railPointer(event) {
416
- const frame = this.#railFrame;
417
- const wasHovered = this.#rails.isHovered(RAIL_KEY);
418
- if (frame === null) {
419
- this.#rails.clear();
420
- return { owned: false, changed: wasHovered };
421
- }
422
- const pointer = { column: event.column, row: event.row - 1 };
423
- if (this.#rails.isDragging(RAIL_KEY)) {
424
- // Invariant: a drag keeps the pointer wherever it goes until the button comes up.
425
- if (event.kind === "release")
426
- this.#rails.endDrag();
427
- else {
428
- const target = this.#rails.dragTo(frame.rail, frame.geometry, pointer);
429
- if (target !== null)
430
- this.#scroll = target;
431
- }
432
- return { owned: true, changed: true };
433
- }
434
- const over = this.#rails.notePointer([frame.rail], pointer) !== null;
435
- if (!over)
436
- return { owned: false, changed: wasHovered };
437
- // Invariant: the rail is not a row: pointing at it lights nothing in the list.
438
- this.#hoverKey = null;
439
- this.#hoverRegion = "label";
440
- if (event.kind === "press" && !this.#rails.beginDrag(frame.rail, frame.geometry, pointer)) {
441
- this.#scroll = scrollForTrackPage(frame.geometry, pointer.row - frame.rail.rowStart, this.#scroll, frame.page);
442
- }
443
- return { owned: true, changed: true };
444
- }
445
- #noteScrollActivity(host, now) {
446
- this.#activeUntil = Math.max(this.#activeUntil, now + SCROLL_LINGER_MS);
447
- this.#clearActivityTimer();
448
- this.#activityTimer = setTimeout(() => {
449
- this.#activityTimer = undefined;
450
- host.requestRender();
451
- }, SCROLL_LINGER_REPAINT_MS);
452
- this.#activityTimer.unref?.();
453
- }
454
- #clearActivityTimer() {
455
- if (this.#activityTimer !== undefined)
456
- clearTimeout(this.#activityTimer);
457
- this.#activityTimer = undefined;
458
- }
459
377
  #openMenu(rows, selected, selectCurrent = false) {
460
378
  const row = rows[selected];
461
379
  if (row === undefined || row.kind !== "element")
@@ -465,7 +383,7 @@ export class SettingsApp {
465
383
  this.#openStructured(entry);
466
384
  return;
467
385
  }
468
- const shown = this.#shownValue(entry);
386
+ const shown = this.#edits.shown(entry);
469
387
  // Rationale: a number is stepped, not picked from a list: it has its own two controls,
470
388
  // and pointing at it is not a request for anything else.
471
389
  if (typeof shown === "number")
@@ -522,7 +440,7 @@ export class SettingsApp {
522
440
  // thing under the pointer, so it stops looking like it.
523
441
  this.#hoverKey = null;
524
442
  this.#hoverRegion = "label";
525
- this.#rails.clear();
443
+ this.#rail.clear();
526
444
  const currentDefault = typeof entry.rawValue === "string" ? entry.rawValue : null;
527
445
  this.#structured = {
528
446
  entry,
@@ -541,8 +459,14 @@ export class SettingsApp {
541
459
  const open = this.#structured;
542
460
  if (open === null)
543
461
  return { consumed: false };
544
- if (open.modelDialog !== null)
545
- return this.#modelDialogKey(open, data);
462
+ if (open.modelDialog !== null) {
463
+ return modelDialogKey(open, data, KEYS[data] ?? data, {
464
+ session: this.#session,
465
+ close: () => { this.#structured = null; },
466
+ reopen: reopened => { this.#structured = reopened; },
467
+ save: (edit, previous, forward) => { this.#saveStructured(edit, previous, forward); },
468
+ });
469
+ }
546
470
  switch (SETTINGS_SHORTCUTS.resolve(KEYS[data] ?? data, DIALOG_SCOPE)) {
547
471
  case "close":
548
472
  this.#structured = null;
@@ -557,120 +481,12 @@ export class SettingsApp {
557
481
  this.#toggleFlag(open.index);
558
482
  return { consumed: true };
559
483
  case "undo":
560
- this.#undo();
484
+ this.#edits.undo();
561
485
  return { consumed: true };
562
486
  default:
563
487
  return { consumed: true, render: false };
564
488
  }
565
489
  }
566
- #modelDialogKey(open, data) {
567
- const key = KEYS[data] ?? data;
568
- if (open.modelDialog === "default") {
569
- const flags = this.#filteredModelFlags(open);
570
- const action = MODEL_SHORTCUTS.resolve(key, MODEL_SCOPE) ?? "edit-filter";
571
- if (action === "close") {
572
- this.#structured = null;
573
- return { consumed: true };
574
- }
575
- if (action === "previous" || action === "next") {
576
- open.index = Math.min(Math.max(0, flags.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
577
- return { consumed: true };
578
- }
579
- if (action === "select") {
580
- const selected = flags[open.index];
581
- if (selected === undefined)
582
- return { consumed: true, render: false };
583
- open.error = null;
584
- this.#structured = null;
585
- void this.#session.change(open.entry.backend, open.entry.id, selected.key).then(outcome => {
586
- const failure = changeFailure(outcome);
587
- if (failure !== null) {
588
- open.error = `Could not save: ${failure}`;
589
- this.#structured = open;
590
- }
591
- });
592
- return { consumed: true };
593
- }
594
- if (open.filter === null)
595
- return { consumed: true, render: false };
596
- handleLineInputKey(open.filter, data);
597
- open.index = 0;
598
- return { consumed: true };
599
- }
600
- if (open.step === "model") {
601
- const flags = this.#filteredModelFlags(open);
602
- const action = MODEL_SHORTCUTS.resolve(key, MODEL_SCOPE) ?? "edit-filter";
603
- if (action === "close") {
604
- this.#structured = null;
605
- return { consumed: true };
606
- }
607
- if (action === "previous" || action === "next") {
608
- open.index = Math.min(Math.max(0, flags.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
609
- return { consumed: true };
610
- }
611
- if (action === "select") {
612
- const selected = flags[open.index];
613
- if (selected === undefined)
614
- return { consumed: true, render: false };
615
- open.modelIndex = open.index;
616
- open.modelKey = selected.key;
617
- open.step = "level";
618
- const options = this.#modelLevelOptions(open, selected);
619
- const current = structuredValue(open.entry, open.record)[selected.key];
620
- open.index = Math.max(0, options.findIndex(option => option.value === current));
621
- return { consumed: true };
622
- }
623
- const input = open.filter;
624
- if (input === null)
625
- return { consumed: true, render: false };
626
- handleLineInputKey(input, data);
627
- open.index = 0;
628
- return { consumed: true };
629
- }
630
- const selected = open.entry.flags.find(flag => flag.key === open.modelKey);
631
- const choices = selected === undefined ? [] : this.#modelLevelOptions(open, selected);
632
- const action = MODEL_SHORTCUTS.resolve(key, MODEL_LEVEL_SCOPE);
633
- if (action === "back") {
634
- open.step = "model";
635
- open.modelKey = null;
636
- open.index = open.modelIndex;
637
- return { consumed: true };
638
- }
639
- if (action === "previous" || action === "next") {
640
- open.index = Math.min(Math.max(0, choices.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
641
- return { consumed: true };
642
- }
643
- if (action === "select") {
644
- const choice = choices[open.index];
645
- if (selected === undefined || choice === undefined)
646
- return { consumed: true, render: false };
647
- const previous = { ...open.record };
648
- open.record[selected.key] = choice.value;
649
- const forward = { ...open.record };
650
- this.#saveStructured(open, previous, forward);
651
- open.step = "model";
652
- open.modelKey = null;
653
- open.index = open.modelIndex;
654
- return { consumed: true };
655
- }
656
- return { consumed: true, render: false };
657
- }
658
- #filteredModelFlags(open) {
659
- const needle = open.filter?.value.trim().toLowerCase() ?? "";
660
- return open.entry.flags.filter(flag => needle.length === 0
661
- || (flag.label ?? humanizeLabel(flag.key)).toLowerCase().includes(needle)
662
- || flag.description.toLowerCase().includes(needle)
663
- || flag.key.toLowerCase().includes(needle));
664
- }
665
- #modelLevelOptions(open, selected) {
666
- const fallback = String(selected.fallback);
667
- const supported = (selected.choices ?? [])
668
- .filter(choice => choice !== fallback)
669
- .map(choice => ({ value: choice, label: choice, description: thinkingLevelDescription(choice) }));
670
- return structuredValue(open.entry, open.record)[selected.key] === undefined
671
- ? supported
672
- : [...supported, { value: fallback, label: "(clear override)", description: "Revert to global default" }];
673
- }
674
490
  #toggleFlag(index) {
675
491
  const open = this.#structured;
676
492
  const flag = open?.flags[index];
@@ -689,14 +505,8 @@ export class SettingsApp {
689
505
  this.#saveStructured(open, previous, forward);
690
506
  }
691
507
  #saveStructured(open, previous, forward) {
692
- const sequence = ++this.#editSequence;
693
- void this.#session.changeStructured(open.entry.backend, open.entry.id, structuredValue(open.entry, forward)).then(outcome => {
694
- const failure = changeFailure(outcome);
695
- if (failure === null) {
696
- this.#recordUndo({ kind: "structured", sequence, entry: open.entry, previous, forward });
697
- return;
698
- }
699
- // Concurrency: only roll back the dialog if no later press has moved the record on.
508
+ // Concurrency: only roll back the dialog if no later press has moved the record on.
509
+ this.#edits.saveStructured(open.entry, previous, forward, () => {
700
510
  if (this.#structured === open && sameRecord(open.record, forward))
701
511
  replaceRecord(open.record, previous);
702
512
  });
@@ -708,7 +518,7 @@ export class SettingsApp {
708
518
  const key = KEYS[data] ?? data;
709
519
  if (SETTINGS_SHORTCUTS.resolve(key, SCOPE) === "undo") {
710
520
  this.#closeMenu(menu);
711
- this.#undo();
521
+ this.#edits.undo();
712
522
  return { consumed: true };
713
523
  }
714
524
  if (key === "escape") {
@@ -725,7 +535,7 @@ export class SettingsApp {
725
535
  const value = menu.choices[menu.index];
726
536
  this.#closeMenu(menu);
727
537
  if (value !== undefined)
728
- this.#apply(menu.entry, value);
538
+ this.#edits.apply(menu.entry, value);
729
539
  }
730
540
  return { consumed: true };
731
541
  }
@@ -785,13 +595,13 @@ export class SettingsApp {
785
595
  const entry = row.value;
786
596
  if (!entry.editable)
787
597
  return;
788
- const shown = this.#shownValue(entry);
598
+ const shown = this.#edits.shown(entry);
789
599
  if (typeof shown === "number") {
790
600
  // Rationale: at the end of the range there is nothing to say: the arrow already reads
791
601
  // as unavailable, so a message would only repeat it.
792
602
  const next = steppedValue(rangeOf(entry), shown, delta);
793
603
  if (next !== null)
794
- this.#apply(entry, next);
604
+ this.#edits.apply(entry, next);
795
605
  return;
796
606
  }
797
607
  const choices = entry.choices;
@@ -802,82 +612,7 @@ export class SettingsApp {
802
612
  ? (delta > 0 ? 0 : choices.length - 1)
803
613
  : (current + delta + choices.length) % choices.length];
804
614
  if (next !== undefined)
805
- this.#apply(entry, next);
806
- }
807
- #apply(entry, value) {
808
- const key = `${entry.backend}:${entry.id}`;
809
- const previous = this.#shownValue(entry);
810
- const sequence = ++this.#editSequence;
811
- // Invariant: shown immediately so the row never lags a keypress, and so the next press
812
- // steps from here rather than from a value the source has not caught up to.
813
- this.#pending.set(key, value);
814
- void this.#session.change(entry.backend, entry.id, value).then(outcome => {
815
- const failure = changeFailure(outcome);
816
- if (failure !== null) {
817
- if (this.#pending.get(key) === value)
818
- this.#pending.delete(key);
819
- return;
820
- }
821
- if (previous !== null && previous !== value)
822
- this.#recordUndo({ kind: "scalar", sequence, entry, previous });
823
- // Concurrency: a later press may have moved on; only the last request clears itself.
824
- if (this.#pending.get(key) === value)
825
- this.#pending.delete(key);
826
- });
827
- }
828
- #shownValue(entry) {
829
- return this.#pending.get(`${entry.backend}:${entry.id}`) ?? entry.value;
830
- }
831
- #recordUndo(record) {
832
- if (this.#closed)
833
- return;
834
- const after = this.#undoHistory.findIndex(candidate => candidate.sequence > record.sequence);
835
- if (after < 0)
836
- this.#undoHistory.push(record);
837
- else
838
- this.#undoHistory.splice(after, 0, record);
839
- }
840
- #undo() {
841
- this.#undoRequests++;
842
- this.#drainUndo();
843
- }
844
- #drainUndo() {
845
- if (this.#undoActive || this.#undoRequests === 0)
846
- return;
847
- const record = this.#undoHistory.pop();
848
- if (record === undefined) {
849
- this.#undoRequests = 0;
850
- return;
851
- }
852
- this.#undoRequests--;
853
- this.#undoActive = true;
854
- if (record.kind === "scalar") {
855
- const key = `${record.entry.backend}:${record.entry.id}`;
856
- this.#pending.set(key, record.previous);
857
- void this.#session.change(record.entry.backend, record.entry.id, record.previous).then(outcome => {
858
- if (this.#pending.get(key) === record.previous)
859
- this.#pending.delete(key);
860
- this.#finishUndo(record, outcome);
861
- });
862
- return;
863
- }
864
- const open = this.#structured;
865
- if (open !== null && sameEntry(open.entry, record.entry))
866
- replaceRecord(open.record, record.previous);
867
- void this.#session.changeStructured(record.entry.backend, record.entry.id, structuredValue(record.entry, record.previous)).then(outcome => {
868
- const failure = changeFailure(outcome);
869
- if (failure !== null && this.#structured === open && open !== null && sameRecord(open.record, record.previous)) {
870
- replaceRecord(open.record, record.forward);
871
- }
872
- this.#finishUndo(record, outcome);
873
- });
874
- }
875
- #finishUndo(record, outcome) {
876
- const failure = changeFailure(outcome);
877
- if (failure !== null)
878
- this.#recordUndo(record);
879
- this.#undoActive = false;
880
- this.#drainUndo();
615
+ this.#edits.apply(entry, next);
881
616
  }
882
617
  #scrollbarSpeed() {
883
618
  const value = this.#scrollSetting("scrollbarSpeed");
@@ -895,7 +630,7 @@ export class SettingsApp {
895
630
  const entry = this.#session.sections()
896
631
  .flatMap(section => section.entries)
897
632
  .find(candidate => candidate.backend === "a1" && candidate.id === id);
898
- return entry === undefined ? this.#session.value(id) : this.#shownValue(entry);
633
+ return entry === undefined ? this.#session.value(id) : this.#edits.shown(entry);
899
634
  }
900
635
  #jump(rows, target) {
901
636
  this.#select(rows, target);
@@ -967,7 +702,7 @@ export class SettingsApp {
967
702
  }
968
703
  #viewRow(entry) {
969
704
  const key = `${entry.backend}:${entry.id}`;
970
- const shown = this.#shownValue(entry);
705
+ const shown = this.#edits.shown(entry);
971
706
  const value = entry.structured || isDefaultModelEntry(entry)
972
707
  ? CONFIGURE
973
708
  : shown === null
@@ -993,7 +728,7 @@ export class SettingsApp {
993
728
  ...(menu.entry.id === "accentColor" ? {
994
729
  previews: menu.choices.map(choice => theme.accentPreview?.(String(choice), "■") ?? ""),
995
730
  } : {}),
996
- current: this.#shownValue(menu.entry) === null ? null : displaySettingValue(menu.entry, this.#shownValue(menu.entry)),
731
+ current: this.#edits.shown(menu.entry) === null ? null : displaySettingValue(menu.entry, this.#edits.shown(menu.entry)),
997
732
  index: menu.index,
998
733
  };
999
734
  const frame = valueMenuFrame(state, {
@@ -1010,7 +745,7 @@ export class SettingsApp {
1010
745
  }
1011
746
  #dialogLines(open, width, height, theme) {
1012
747
  if (open.modelDialog !== null)
1013
- return this.#modelDialogLines(open, width, height, theme);
748
+ return modelDialogLines(open, width, height, theme);
1014
749
  const rows = open.flags.map(key => {
1015
750
  const declared = open.entry.flags.find(flag => flag.key === key);
1016
751
  return {
@@ -1023,64 +758,6 @@ export class SettingsApp {
1023
758
  const visible = dialogWindow(rows, open.index, configureDialogChoiceCapacity(height, false, configureDialogTextRows(rows[open.index]?.description ?? "", width)));
1024
759
  return renderDialogPanel({ title: labelOf(open.entry), rows: visible.rows, index: visible.index, hint: SETTINGS_SHORTCUTS.hintEntries(DIALOG_SCOPE) }, width, theme);
1025
760
  }
1026
- #modelDialogLines(open, width, height, theme) {
1027
- if (open.modelDialog === "default") {
1028
- const flags = this.#filteredModelFlags(open);
1029
- const current = typeof open.entry.rawValue === "string" ? open.entry.rawValue : null;
1030
- const rows = flags.length === 0
1031
- ? [{ label: "No models found", value: "" }]
1032
- : flags.map(flag => ({
1033
- ...modelLabelParts(flag.label ?? humanizeLabel(flag.key)),
1034
- ...(flag.key === current ? { successSuffix: "✓" } : {}),
1035
- value: "",
1036
- }));
1037
- const description = open.error ?? "Select the model used for new sessions";
1038
- const visible = dialogWindow(rows, open.index, configureDialogChoiceCapacity(height, true, configureDialogTextRows(description, width)));
1039
- return renderSteppedDialogPanel({
1040
- title: "Default Model", description, input: open.filter ?? new LineInput(""),
1041
- rows: visible.rows, index: visible.index, hint: MODEL_SHORTCUTS.hintEntries(MODEL_SCOPE),
1042
- }, width, theme);
1043
- }
1044
- if (open.step === "model") {
1045
- const flags = this.#filteredModelFlags(open);
1046
- const stored = structuredValue(open.entry, open.record);
1047
- const allRows = flags.length === 0
1048
- ? [{ label: "No models found", value: "" }]
1049
- : flags.map(flag => ({
1050
- ...modelLabelParts(flag.label ?? humanizeLabel(flag.key)),
1051
- value: stored[flag.key] === undefined ? "" : String(stored[flag.key]),
1052
- }));
1053
- const description = "Select a model to configure";
1054
- const visible = dialogWindow(allRows, open.index, configureDialogChoiceCapacity(height, true, configureDialogTextRows(description, width)));
1055
- return renderSteppedDialogPanel({
1056
- title: "Thinking Level",
1057
- step: 1,
1058
- steps: 2,
1059
- description,
1060
- ...(open.filter === null ? {} : { input: open.filter }),
1061
- rows: visible.rows,
1062
- index: visible.index,
1063
- hint: MODEL_SHORTCUTS.hintEntries(MODEL_SCOPE),
1064
- }, width, theme);
1065
- }
1066
- const selected = open.entry.flags.find(flag => flag.key === open.modelKey);
1067
- const current = selected === undefined ? undefined : structuredValue(open.entry, open.record)[selected.key];
1068
- const allRows = (selected === undefined ? [] : this.#modelLevelOptions(open, selected)).map(choice => ({
1069
- label: choice.value === current ? `✓ ${choice.label}` : choice.label,
1070
- value: choice.description,
1071
- }));
1072
- const description = `Select default thinking level for ${selected?.label ?? open.modelKey ?? "model"}`;
1073
- const visible = dialogWindow(allRows, open.index, configureDialogChoiceCapacity(height, false, configureDialogTextRows(description, width)));
1074
- return renderSteppedDialogPanel({
1075
- title: "Thinking Level",
1076
- step: 2,
1077
- steps: 2,
1078
- description,
1079
- rows: visible.rows,
1080
- index: visible.index,
1081
- hint: MODEL_SHORTCUTS.hintEntries(MODEL_LEVEL_SCOPE),
1082
- }, width, theme);
1083
- }
1084
761
  #footerLines(width, theme) {
1085
762
  // Rationale: standing guidance is more useful than transient feedback; failures still
1086
763
  // roll back without displacing the Settings interaction contract.
@@ -1132,59 +809,6 @@ function orderDefaultModelBeforeThinking(entries, sectionId) {
1132
809
  ordered.splice(thinking < 0 ? ordered.length : thinking, 0, defaultModel);
1133
810
  return ordered;
1134
811
  }
1135
- function modelLabelParts(label) {
1136
- const match = /^(.*?)(\s+\[[^\]]+\])$/u.exec(label);
1137
- return match === null ? { label } : { label: match[1] ?? label, labelSuffix: match[2] ?? "" };
1138
- }
1139
- function dialogWindow(rows, index, maximum) {
1140
- if (rows.length <= maximum)
1141
- return { rows, index: Math.min(Math.max(0, rows.length - 1), Math.max(0, index)) };
1142
- const start = Math.min(rows.length - maximum, Math.max(0, index - Math.floor(maximum / 2)));
1143
- return { rows: rows.slice(start, start + maximum), index: index - start };
1144
- }
1145
- function thinkingLevelDescription(level) {
1146
- switch (level) {
1147
- case "default": return "global default";
1148
- case "off": return "No reasoning";
1149
- case "minimal": return "Very brief reasoning (~1k tokens)";
1150
- case "low": return "Light reasoning (~2k tokens)";
1151
- case "medium": return "Moderate reasoning (~8k tokens)";
1152
- case "high": return "Deep reasoning (~16k tokens)";
1153
- case "xhigh": return "Extra-high reasoning (~32k tokens)";
1154
- case "max": return "Maximum reasoning";
1155
- default: return "";
1156
- }
1157
- }
1158
- function structuredValue(entry, record) {
1159
- // Invariant: a choice part at its fallback is unset, so the engine's default applies.
1160
- return Object.fromEntries(Object.entries(record).filter(([key, value]) => {
1161
- const part = entry.flags.find(candidate => candidate.key === key);
1162
- return part?.choices === undefined || value !== part.fallback;
1163
- }));
1164
- }
1165
- function sameEntry(left, right) {
1166
- return left.backend === right.backend && left.id === right.id;
1167
- }
1168
- function sameRecord(left, right) {
1169
- const leftEntries = Object.entries(left);
1170
- const rightEntries = Object.entries(right);
1171
- return leftEntries.length === rightEntries.length
1172
- && leftEntries.every(([key, value]) => right[key] === value);
1173
- }
1174
- function replaceRecord(target, source) {
1175
- for (const key of Object.keys(target))
1176
- delete target[key];
1177
- Object.assign(target, source);
1178
- }
1179
- function changeFailure(outcome) {
1180
- if (outcome.failure !== null)
1181
- return outcome.failure;
1182
- if (outcome.status === "failed")
1183
- return "the effect failed";
1184
- if (outcome.status === "unavailable")
1185
- return outcome.limitationReason ?? "the setting is unavailable";
1186
- return outcome.limitationReason;
1187
- }
1188
812
  function isDefaultModelEntry(entry) {
1189
813
  return entry.backend === "agent" && entry.id === "defaultModel";
1190
814
  }