@timurproko/a1 0.2.7-dev.1252 → 0.2.7-dev.1255
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.
- package/bin/ui.js +3 -3
- package/dist/app/session-shell/clipboard-diagnostics.d.ts +2 -2
- package/dist/app/session-shell/clipboard-image.d.ts +0 -1
- package/dist/app/session-shell/clipboard-image.js +1 -1
- package/dist/app/session-shell/dialog-coordinator.d.ts +6 -5
- package/dist/app/session-shell/dialog-coordinator.js +3 -4
- package/dist/app/session-shell/dock-chrome.d.ts +11 -6
- package/dist/app/session-shell/dock-chrome.js +5 -8
- package/dist/app/session-shell/editor-hyperlink-budget.d.ts +0 -2
- package/dist/app/session-shell/editor-hyperlink-budget.js +1 -1
- package/dist/app/session-shell/exit-notice.d.ts +2 -2
- package/dist/app/session-shell/exit-notice.js +1 -1
- package/dist/app/session-shell/extension-surface.d.ts +8 -7
- package/dist/app/session-shell/helper-pool.d.ts +3 -4
- package/dist/app/session-shell/helper-pool.js +1 -1
- package/dist/app/session-shell/image-preparation-client.d.ts +4 -3
- package/dist/app/session-shell/image-preparation.d.ts +0 -2
- package/dist/app/session-shell/image-preparation.js +2 -2
- package/dist/app/session-shell/image-source.d.ts +2 -3
- package/dist/app/session-shell/image-source.js +2 -2
- package/dist/app/session-shell/input-surface-switch.d.ts +9 -7
- package/dist/app/session-shell/input-surface-switch.js +1 -2
- package/dist/app/session-shell/login-interaction.d.ts +5 -4
- package/dist/app/session-shell/login-interaction.js +2 -3
- package/dist/app/session-shell/paste-executor.d.ts +4 -8
- package/dist/app/session-shell/paste-executor.js +2 -2
- package/dist/app/session-shell/paste-protocol.d.ts +2 -2
- package/dist/app/session-shell/path-chip-presentation.d.ts +0 -5
- package/dist/app/session-shell/path-chip-presentation.js +3 -3
- package/dist/app/session-shell/prompt-chips.d.ts +8 -7
- package/dist/app/session-shell/prompt-history-controller.d.ts +3 -3
- package/dist/app/session-shell/prompt-submission.d.ts +5 -2
- package/dist/app/session-shell/prompt-submission.js +4 -5
- package/dist/app/session-shell/quit-outro-effects.d.ts +3 -3
- package/dist/app/session-shell/quit-outro-effects.js +1 -4
- package/dist/app/session-shell/quit-outro.d.ts +9 -12
- package/dist/app/session-shell/quit-outro.js +11 -11
- package/dist/app/session-shell/response-copy-protocol.d.ts +3 -2
- package/dist/app/session-shell/response-copy-transport.d.ts +2 -14
- package/dist/app/session-shell/response-copy-transport.js +1 -1
- package/dist/app/session-shell/session-lifecycle.d.ts +2 -1
- package/dist/app/session-shell/session-shell-root.d.ts +74 -89
- package/dist/app/session-shell/session-shell-root.js +81 -113
- package/dist/app/session-shell/session-shell.d.ts +13 -16
- package/dist/app/session-shell/session-shell.js +144 -268
- package/dist/app/session-shell/session-viewport-controller.d.ts +7 -6
- package/dist/app/session-shell/session-viewport-controller.js +2 -2
- package/dist/app/session-shell/settings-bindings.d.ts +24 -2
- package/dist/app/session-shell/settings-bindings.js +24 -0
- package/dist/app/session-shell/share-operation.d.ts +3 -3
- package/dist/app/session-shell/share-operation.js +4 -6
- package/dist/app/session-shell/shell-clipboard.d.ts +59 -0
- package/dist/app/session-shell/shell-clipboard.js +119 -0
- package/dist/app/session-shell/shell-editor-factory.d.ts +8 -4
- package/dist/app/session-shell/shell-editor-factory.js +13 -15
- package/dist/app/session-shell/shell-host.d.ts +16 -17
- package/dist/app/session-shell/shell-host.js +18 -19
- package/dist/app/session-shell/shell-layout.d.ts +2 -2
- package/dist/app/session-shell/shell-prompt-suggestions.d.ts +26 -0
- package/dist/app/session-shell/shell-prompt-suggestions.js +48 -0
- package/dist/app/session-shell/suggestion-surface.d.ts +1 -6
- package/dist/app/session-shell/suggestion-surface.js +1 -1
- package/dist/app/session-shell/system-clipboard.d.ts +2 -14
- package/dist/app/session-shell/system-clipboard.js +2 -7
- package/dist/app/session-shell/transcript-document.d.ts +11 -8
- package/dist/app/session-shell/transcript-document.js +13 -14
- package/dist/app/session-shell/viewport-layout.d.ts +2 -2
- package/dist/app/session-shell/workflow-output.d.ts +5 -3
- package/dist/app/session-shell/workflow-output.js +13 -15
- package/dist/cli/package-messages.d.ts +0 -1
- package/dist/cli/package-messages.js +1 -1
- package/dist/cli/session-context.d.ts +1 -1
- package/dist/cli/uninstall.d.ts +3 -2
- package/dist/composition/owned-settings.d.ts +2 -1
- package/dist/composition/owned-settings.js +0 -3
- package/dist/composition/owned-ui.d.ts +1 -1
- package/dist/composition/owned-ui.js +27 -27
- package/dist/composition/reference-selection-options.d.ts +1 -1
- package/dist/composition/reference-selection-options.js +1 -1
- package/dist/composition/session-runtime-registry.d.ts +2 -1
- package/dist/composition/settings-route-host.js +3 -10
- package/dist/composition/settings-search-input.d.ts +2 -1
- package/dist/composition/startup-route.d.ts +2 -2
- package/dist/composition/startup-route.js +2 -2
- package/dist/contracts/owned-ui/extension-ui.d.ts +2 -1
- package/dist/contracts/owned-ui/index.d.ts +1 -0
- package/dist/contracts/owned-ui/session-presenters.d.ts +3 -0
- package/dist/contracts/owned-ui/shell-components.d.ts +543 -0
- package/dist/contracts/owned-ui/shell-components.js +1 -0
- package/dist/contracts/presentation/component.d.ts +17 -0
- package/dist/contracts/presentation/component.js +8 -0
- package/dist/contracts/presentation/index.d.ts +6 -15
- package/dist/contracts/presentation/index.js +2 -0
- package/dist/contracts/presentation/terminal.d.ts +290 -0
- package/dist/contracts/presentation/terminal.js +1 -0
- package/dist/contracts/presentation/theme.d.ts +46 -0
- package/dist/contracts/presentation/theme.js +1 -0
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/launch/runtime-selection.d.ts +2 -2
- package/dist/features/launch/runtime-selection.js +2 -2
- package/dist/features/owned-ui/project-trust-prompt.d.ts +2 -2
- package/dist/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/release-note-state.d.ts +2 -1
- package/dist/features/owned-ui/settings-app.js +38 -219
- package/dist/features/owned-ui/settings-edits.d.ts +31 -0
- package/dist/features/owned-ui/settings-edits.js +139 -0
- package/dist/features/owned-ui/settings-scroll-rail.d.ts +29 -0
- package/dist/features/owned-ui/settings-scroll-rail.js +79 -0
- package/dist/features/owned-ui/settings-search-input.d.ts +2 -1
- package/dist/features/prompt-history/service.d.ts +3 -2
- package/dist/foundation/lifecycle/claim-gate.d.ts +2 -1
- package/dist/foundation/release/automatic-update.d.ts +5 -14
- package/dist/foundation/release/automatic-update.js +14 -14
- package/dist/foundation/release/bootstrap.js +4 -4
- package/dist/foundation/release/dependency-certification.d.ts +0 -1
- package/dist/foundation/release/dependency-certification.js +1 -1
- package/dist/foundation/release/early-accent.d.ts +2 -2
- package/dist/foundation/release/early-accent.js +1 -1
- package/dist/foundation/release/index.d.ts +2 -2
- package/dist/foundation/release/index.js +1 -1
- package/dist/foundation/release/latest-release.d.ts +3 -18
- package/dist/foundation/release/latest-release.js +10 -10
- package/dist/foundation/release/ordinary-launch-selection.d.ts +1 -1
- package/dist/foundation/release/ordinary-launch-selection.js +1 -1
- package/dist/foundation/release/release-gc.d.ts +3 -3
- package/dist/foundation/release/release-gc.js +4 -4
- package/dist/foundation/release/{cohort-state.d.ts → release-state.d.ts} +23 -23
- package/dist/foundation/release/{cohort-state.js → release-state.js} +8 -8
- package/dist/foundation/release/update-activation.js +2 -2
- package/dist/foundation/release/update-execution-lease.d.ts +2 -3
- package/dist/foundation/release/update-execution-lease.js +1 -1
- package/dist/foundation/release/update-launch.d.ts +2 -2
- package/dist/foundation/release/update.d.ts +0 -2
- package/dist/foundation/release/update.js +14 -14
- package/dist/foundation/startup/startup-budget.d.ts +5 -5
- package/dist/foundation/startup/startup-budget.js +2 -2
- package/dist/integrations/pi/components/editor-interaction.d.ts +4 -12
- package/dist/integrations/pi/components/index.d.ts +15 -9
- package/dist/integrations/pi/components/index.js +9 -5
- package/dist/integrations/pi/components/lazy-selectors.d.ts +2 -10
- package/dist/integrations/pi/components/lazy-selectors.js +1 -1
- package/dist/integrations/pi/components/modal-frame.d.ts +5 -22
- package/dist/integrations/pi/components/modal-frame.js +3 -23
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +4 -3
- package/dist/integrations/pi/components/prompt-input-port.d.ts +2 -19
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -1
- package/dist/integrations/pi/components/shell-editor-autocomplete.d.ts +3 -2
- package/dist/integrations/pi/components/shell-footer-status.d.ts +2 -2
- package/dist/integrations/pi/components/shell-footer-status.js +3 -2
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +6 -5
- package/dist/integrations/pi/components/shell-presenters-info.js +2 -1
- package/dist/integrations/pi/components/shell-session-info-reference.d.ts +2 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +33 -227
- package/dist/integrations/pi/components/shell-shared-facade.js +8 -1
- package/dist/integrations/pi/components/skills-command.d.ts +1 -9
- package/dist/integrations/pi/components/skills-command.js +5 -5
- package/dist/integrations/pi/components/theme.d.ts +9 -2
- package/dist/integrations/pi/components/theme.js +17 -2
- package/dist/integrations/pi/components/thinking-selector-dialog.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/engine/adapter.d.ts +3 -5
- package/dist/integrations/pi/engine/adapter.js +3 -12
- package/dist/integrations/pi/engine/changelog.d.ts +2 -2
- package/dist/integrations/pi/engine/changelog.js +3 -3
- package/dist/integrations/pi/engine/compaction-progress.d.ts +2 -3
- package/dist/integrations/pi/engine/compaction-progress.js +1 -1
- package/dist/integrations/pi/engine/conformance.d.ts +2 -1
- package/dist/integrations/pi/engine/default-model-setting.d.ts +0 -2
- package/dist/integrations/pi/engine/default-model-setting.js +1 -2
- package/dist/integrations/pi/engine/event-delivery.d.ts +2 -1
- package/dist/integrations/pi/engine/extension-ui-binding.d.ts +2 -1
- package/dist/integrations/pi/engine/host.js +2 -2
- package/dist/integrations/pi/engine/message-values.d.ts +0 -1
- package/dist/integrations/pi/engine/message-values.js +1 -1
- package/dist/integrations/pi/engine/pending-delivery.d.ts +0 -4
- package/dist/integrations/pi/engine/pending-delivery.js +3 -3
- package/dist/integrations/pi/engine/ports.d.ts +7 -1
- package/dist/integrations/pi/engine/ports.js +4 -0
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +2 -1
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +3 -2
- package/dist/integrations/pi/engine/provider-authentication.d.ts +1 -1
- package/dist/integrations/pi/engine/public-main-entry.d.ts +1 -5
- package/dist/integrations/pi/engine/public-main-entry.js +1 -1
- package/dist/integrations/pi/engine/repository-pr-rest.d.ts +1 -9
- package/dist/integrations/pi/engine/repository-pr-rest.js +2 -2
- package/dist/integrations/pi/engine/repository-pr.d.ts +1 -3
- package/dist/integrations/pi/engine/repository-pr.js +1 -1
- package/dist/integrations/pi/engine/resource-catalog.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -1
- package/dist/integrations/pi/engine/session-runtime.d.ts +4 -3
- package/dist/integrations/pi/engine/settings-bridge.d.ts +0 -2
- package/dist/integrations/pi/engine/settings-bridge.js +1 -1
- package/dist/integrations/pi/engine/settings-port.d.ts +3 -2
- package/dist/integrations/pi/engine/transcript-projection.d.ts +2 -1
- package/dist/integrations/pi/engine/workflow-contexts.d.ts +1 -1
- package/dist/integrations/pi/engine/workflow-runner.d.ts +1 -1
- package/dist/integrations/pi/engine/workflow-support.d.ts +0 -6
- package/dist/integrations/pi/engine/workflow-support.js +4 -18
- package/dist/integrations/pi/engine/workflows.d.ts +5 -5
- package/dist/integrations/pi/session-presenters/components.d.ts +3 -0
- package/dist/integrations/pi/session-presenters/components.js +75 -0
- package/dist/integrations/pi/session-presenters/index.d.ts +1 -0
- package/dist/integrations/pi/session-presenters/index.js +1 -0
- package/dist/integrations/pi/session-presenters/presentation.d.ts +3 -0
- package/dist/integrations/pi/session-presenters/presentation.js +10 -0
- package/dist/integrations/pi/session-presenters/presenters.d.ts +2 -1
- package/dist/integrations/pi/session-presenters/presenters.js +3 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +21 -30
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +22 -166
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +5 -40
- package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
- package/dist/integrations/pi/tui-runtime/index.js +1 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +4 -6
- package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +2 -8
- package/dist/integrations/pi/tui-runtime/overlay-geometry.js +3 -9
- package/dist/integrations/pi/tui-runtime/runtime-factory.d.ts +3 -0
- package/dist/integrations/pi/tui-runtime/runtime-factory.js +11 -0
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/selection-copy.d.ts +0 -1
- package/dist/ui/components/selection-copy.js +0 -12
- package/dist/ui/components/theme.d.ts +4 -19
- package/dist/ui/components/theme.js +6 -0
- package/dist/ui/settings/declarations.d.ts +2 -3
- package/dist/ui/settings/declarations.js +2 -3
- package/dist/ui/settings/early-accent.js +1 -1
- package/dist/ui/settings/index.d.ts +3 -5
- package/dist/ui/settings/index.js +2 -3
- package/dist/ui/settings/manager.d.ts +0 -4
- package/dist/ui/settings/manager.js +6 -62
- package/dist/ui/settings/resolution.d.ts +6 -18
- package/dist/ui/settings/resolution.js +4 -103
- package/dist/ui/settings/sections.d.ts +0 -4
- package/dist/ui/settings/sections.js +1 -1
- package/docs/architecture/boundaries.md +2 -2
- package/docs/architecture/internal-naming.md +2 -0
- package/docs/architecture/project-structure.md +4 -2
- package/docs/validation.md +1 -1
- package/package.json +3 -4
- package/dist/integrations/pi/components/components.d.ts +0 -4
- package/dist/integrations/pi/components/components.js +0 -48
- package/dist/integrations/pi/components/conformance.d.ts +0 -18
- package/dist/integrations/pi/components/conformance.js +0 -70
- package/dist/integrations/pi/components/shell-components.d.ts +0 -7
- package/dist/integrations/pi/components/shell-components.js +0 -7
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +0 -33
- package/dist/integrations/pi/components/upstream/theme/theme-controller.js +0 -114
- package/dist/ui/settings/migrations.d.ts +0 -19
- package/dist/ui/settings/migrations.js +0 -225
- package/dist/ui/settings/progress-palette.generated.d.ts +0 -139
- package/dist/ui/settings/progress-palette.generated.js +0 -139
- /package/dist/{foundation/release → contracts/presentation}/progress-palette.generated.d.ts +0 -0
- /package/dist/{foundation/release → contracts/presentation}/progress-palette.generated.js +0 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
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,
|
|
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, scrollbarGeometry, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
|
|
3
|
+
import { SettingsScrollRail } from "./settings-scroll-rail.js";
|
|
4
|
+
import { SettingsEdits, changeFailure, replaceRecord, sameRecord, structuredValue } from "./settings-edits.js";
|
|
3
5
|
import { SETTINGS_APP_ID } from "./settings-route.js";
|
|
4
6
|
export { SETTINGS_APP_ID, SETTINGS_ROUTE } from "./settings-route.js";
|
|
5
7
|
const SCOPE = SETTINGS_APP_ID;
|
|
@@ -14,11 +16,6 @@ const DIALOG_SCOPE = `${SETTINGS_APP_ID}-parts`;
|
|
|
14
16
|
const MODEL_SCOPE = `${SETTINGS_APP_ID}-models`;
|
|
15
17
|
const MODEL_LEVEL_SCOPE = `${SETTINGS_APP_ID}-model-levels`;
|
|
16
18
|
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
19
|
/** What a structured value offers instead of printing itself. */
|
|
23
20
|
const CONFIGURE = "configure";
|
|
24
21
|
export const SETTINGS_SHORTCUTS = new ShortcutRegistry();
|
|
@@ -96,12 +93,7 @@ export class SettingsApp {
|
|
|
96
93
|
#menu = null;
|
|
97
94
|
#structured = null;
|
|
98
95
|
// Invariant: pending values remain visible until the source reflects them.
|
|
99
|
-
#
|
|
100
|
-
#undoHistory = [];
|
|
101
|
-
#editSequence = 0;
|
|
102
|
-
#undoRequests = 0;
|
|
103
|
-
#undoActive = false;
|
|
104
|
-
#closed = false;
|
|
96
|
+
#edits;
|
|
105
97
|
#bodyTopForFrame = SETTINGS_TOP_RULE_ROWS + SETTINGS_TITLE_ROWS;
|
|
106
98
|
#bodyHeightForFrame = 0;
|
|
107
99
|
#panelTopForFrame = 0;
|
|
@@ -110,16 +102,12 @@ export class SettingsApp {
|
|
|
110
102
|
#frameRows = [];
|
|
111
103
|
#menuFrame = null;
|
|
112
104
|
// Invariant: rail hover and drag live in the shared keyed state, as the transcript's do.
|
|
113
|
-
#
|
|
105
|
+
#rail = new SettingsScrollRail();
|
|
114
106
|
// 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
107
|
#options;
|
|
121
108
|
constructor(session, options) {
|
|
122
109
|
this.#session = session;
|
|
110
|
+
this.#edits = new SettingsEdits(session, () => this.#structured);
|
|
123
111
|
this.#options = {
|
|
124
112
|
dialogPanel: options.dialogPanel ?? null,
|
|
125
113
|
};
|
|
@@ -131,17 +119,14 @@ export class SettingsApp {
|
|
|
131
119
|
};
|
|
132
120
|
}
|
|
133
121
|
onActivate(host) {
|
|
134
|
-
this.#
|
|
122
|
+
this.#edits.open();
|
|
135
123
|
void this.#session.load().then(() => host.requestRender());
|
|
136
124
|
}
|
|
137
125
|
onClose(_host) {
|
|
138
|
-
this.#
|
|
139
|
-
this.#undoHistory.length = 0;
|
|
140
|
-
this.#undoRequests = 0;
|
|
126
|
+
this.#edits.close();
|
|
141
127
|
this.#search.input?.setFocused(false);
|
|
142
128
|
this.#search.input = null;
|
|
143
|
-
this.#
|
|
144
|
-
this.#rails.clear();
|
|
129
|
+
this.#rail.dispose();
|
|
145
130
|
}
|
|
146
131
|
render(rect, host) {
|
|
147
132
|
const theme = host.theme ?? PLAIN_THEME;
|
|
@@ -174,11 +159,7 @@ export class SettingsApp {
|
|
|
174
159
|
this.#panelTopForFrame = topRows + contentHeight + dividerRows;
|
|
175
160
|
this.#scroll = layout.scroll;
|
|
176
161
|
const now = Date.now();
|
|
177
|
-
|
|
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;
|
|
162
|
+
this.#rail.noteRenderedScroll(layout.scroll, now, () => host.requestRender());
|
|
182
163
|
// Invariant: auto and always keep the rail columns while the list fits, so a revealed
|
|
183
164
|
// rail never reflows the rows; hidden gives the columns back to the rows.
|
|
184
165
|
const appearance = this.#scrollbarAppearance();
|
|
@@ -191,22 +172,10 @@ export class SettingsApp {
|
|
|
191
172
|
scroll: layout.scroll,
|
|
192
173
|
trackHeight: Math.max(0, titleRows + bodyHeight - SCROLLBAR_TOP_INSET),
|
|
193
174
|
});
|
|
194
|
-
const presentation =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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;
|
|
175
|
+
const presentation = this.#rail.presentation({ geometry, appearance, style: this.#scrollbarStyle(), now });
|
|
176
|
+
this.#rail.setFrame(reservesRail && geometry !== null
|
|
177
|
+
? { rail: { column: rect.width, rowStart: topRows + SCROLLBAR_TOP_INSET, trackHeight: geometry.trackHeight }, geometry, page: layout.visible }
|
|
178
|
+
: null);
|
|
210
179
|
const body = [];
|
|
211
180
|
this.#frameRows = [];
|
|
212
181
|
if (rows.length === 0) {
|
|
@@ -275,7 +244,7 @@ export class SettingsApp {
|
|
|
275
244
|
const row = rows[selected];
|
|
276
245
|
if (row?.kind === "element" && (row.value.structured || isDefaultModelEntry(row.value)))
|
|
277
246
|
this.#openStructured(row.value);
|
|
278
|
-
else if (row?.kind === "element" && typeof this.#
|
|
247
|
+
else if (row?.kind === "element" && typeof this.#edits.shown(row.value) === "number")
|
|
279
248
|
this.#cycle(rows, selected, 1);
|
|
280
249
|
else
|
|
281
250
|
this.#openMenu(rows, selected, true);
|
|
@@ -313,7 +282,7 @@ export class SettingsApp {
|
|
|
313
282
|
this.#cycle(rows, selected, action === "next-value" ? 1 : -1);
|
|
314
283
|
return { consumed: true };
|
|
315
284
|
case "undo":
|
|
316
|
-
this.#undo();
|
|
285
|
+
this.#edits.undo();
|
|
317
286
|
return { consumed: true };
|
|
318
287
|
default:
|
|
319
288
|
return { consumed: false };
|
|
@@ -355,7 +324,7 @@ export class SettingsApp {
|
|
|
355
324
|
const value = menu.choices[overRow];
|
|
356
325
|
this.#closeMenu(menu);
|
|
357
326
|
if (value !== undefined)
|
|
358
|
-
this.#apply(menu.entry, value);
|
|
327
|
+
this.#edits.apply(menu.entry, value);
|
|
359
328
|
return { consumed: true };
|
|
360
329
|
}
|
|
361
330
|
if (event.kind === "wheel-up" || event.kind === "wheel-down") {
|
|
@@ -380,7 +349,14 @@ export class SettingsApp {
|
|
|
380
349
|
// cannot change its selection or act on the Settings list behind it.
|
|
381
350
|
return { consumed: true, render: false };
|
|
382
351
|
}
|
|
383
|
-
const rail = this.#
|
|
352
|
+
const rail = this.#rail.pointer(event, this.#scroll);
|
|
353
|
+
if (rail.scroll !== undefined)
|
|
354
|
+
this.#scroll = rail.scroll;
|
|
355
|
+
// Invariant: the rail is not a row: pointing at it lights nothing in the list.
|
|
356
|
+
if (rail.overRail) {
|
|
357
|
+
this.#hoverKey = null;
|
|
358
|
+
this.#hoverRegion = "label";
|
|
359
|
+
}
|
|
384
360
|
if (rail.owned)
|
|
385
361
|
return { consumed: true };
|
|
386
362
|
const row = this.#frameRows.find(candidate => candidate.screenRow === event.row - 1);
|
|
@@ -410,52 +386,6 @@ export class SettingsApp {
|
|
|
410
386
|
const changed = previousKey !== this.#hoverKey || previousRegion !== this.#hoverRegion || rail.changed;
|
|
411
387
|
return { consumed: event.kind !== "motion", render: changed };
|
|
412
388
|
}
|
|
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
389
|
#openMenu(rows, selected, selectCurrent = false) {
|
|
460
390
|
const row = rows[selected];
|
|
461
391
|
if (row === undefined || row.kind !== "element")
|
|
@@ -465,7 +395,7 @@ export class SettingsApp {
|
|
|
465
395
|
this.#openStructured(entry);
|
|
466
396
|
return;
|
|
467
397
|
}
|
|
468
|
-
const shown = this.#
|
|
398
|
+
const shown = this.#edits.shown(entry);
|
|
469
399
|
// Rationale: a number is stepped, not picked from a list: it has its own two controls,
|
|
470
400
|
// and pointing at it is not a request for anything else.
|
|
471
401
|
if (typeof shown === "number")
|
|
@@ -522,7 +452,7 @@ export class SettingsApp {
|
|
|
522
452
|
// thing under the pointer, so it stops looking like it.
|
|
523
453
|
this.#hoverKey = null;
|
|
524
454
|
this.#hoverRegion = "label";
|
|
525
|
-
this.#
|
|
455
|
+
this.#rail.clear();
|
|
526
456
|
const currentDefault = typeof entry.rawValue === "string" ? entry.rawValue : null;
|
|
527
457
|
this.#structured = {
|
|
528
458
|
entry,
|
|
@@ -557,7 +487,7 @@ export class SettingsApp {
|
|
|
557
487
|
this.#toggleFlag(open.index);
|
|
558
488
|
return { consumed: true };
|
|
559
489
|
case "undo":
|
|
560
|
-
this.#undo();
|
|
490
|
+
this.#edits.undo();
|
|
561
491
|
return { consumed: true };
|
|
562
492
|
default:
|
|
563
493
|
return { consumed: true, render: false };
|
|
@@ -689,14 +619,8 @@ export class SettingsApp {
|
|
|
689
619
|
this.#saveStructured(open, previous, forward);
|
|
690
620
|
}
|
|
691
621
|
#saveStructured(open, previous, forward) {
|
|
692
|
-
|
|
693
|
-
|
|
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.
|
|
622
|
+
// Concurrency: only roll back the dialog if no later press has moved the record on.
|
|
623
|
+
this.#edits.saveStructured(open.entry, previous, forward, () => {
|
|
700
624
|
if (this.#structured === open && sameRecord(open.record, forward))
|
|
701
625
|
replaceRecord(open.record, previous);
|
|
702
626
|
});
|
|
@@ -708,7 +632,7 @@ export class SettingsApp {
|
|
|
708
632
|
const key = KEYS[data] ?? data;
|
|
709
633
|
if (SETTINGS_SHORTCUTS.resolve(key, SCOPE) === "undo") {
|
|
710
634
|
this.#closeMenu(menu);
|
|
711
|
-
this.#undo();
|
|
635
|
+
this.#edits.undo();
|
|
712
636
|
return { consumed: true };
|
|
713
637
|
}
|
|
714
638
|
if (key === "escape") {
|
|
@@ -725,7 +649,7 @@ export class SettingsApp {
|
|
|
725
649
|
const value = menu.choices[menu.index];
|
|
726
650
|
this.#closeMenu(menu);
|
|
727
651
|
if (value !== undefined)
|
|
728
|
-
this.#apply(menu.entry, value);
|
|
652
|
+
this.#edits.apply(menu.entry, value);
|
|
729
653
|
}
|
|
730
654
|
return { consumed: true };
|
|
731
655
|
}
|
|
@@ -785,13 +709,13 @@ export class SettingsApp {
|
|
|
785
709
|
const entry = row.value;
|
|
786
710
|
if (!entry.editable)
|
|
787
711
|
return;
|
|
788
|
-
const shown = this.#
|
|
712
|
+
const shown = this.#edits.shown(entry);
|
|
789
713
|
if (typeof shown === "number") {
|
|
790
714
|
// Rationale: at the end of the range there is nothing to say: the arrow already reads
|
|
791
715
|
// as unavailable, so a message would only repeat it.
|
|
792
716
|
const next = steppedValue(rangeOf(entry), shown, delta);
|
|
793
717
|
if (next !== null)
|
|
794
|
-
this.#apply(entry, next);
|
|
718
|
+
this.#edits.apply(entry, next);
|
|
795
719
|
return;
|
|
796
720
|
}
|
|
797
721
|
const choices = entry.choices;
|
|
@@ -802,82 +726,7 @@ export class SettingsApp {
|
|
|
802
726
|
? (delta > 0 ? 0 : choices.length - 1)
|
|
803
727
|
: (current + delta + choices.length) % choices.length];
|
|
804
728
|
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();
|
|
729
|
+
this.#edits.apply(entry, next);
|
|
881
730
|
}
|
|
882
731
|
#scrollbarSpeed() {
|
|
883
732
|
const value = this.#scrollSetting("scrollbarSpeed");
|
|
@@ -895,7 +744,7 @@ export class SettingsApp {
|
|
|
895
744
|
const entry = this.#session.sections()
|
|
896
745
|
.flatMap(section => section.entries)
|
|
897
746
|
.find(candidate => candidate.backend === "a1" && candidate.id === id);
|
|
898
|
-
return entry === undefined ? this.#session.value(id) : this.#
|
|
747
|
+
return entry === undefined ? this.#session.value(id) : this.#edits.shown(entry);
|
|
899
748
|
}
|
|
900
749
|
#jump(rows, target) {
|
|
901
750
|
this.#select(rows, target);
|
|
@@ -967,7 +816,7 @@ export class SettingsApp {
|
|
|
967
816
|
}
|
|
968
817
|
#viewRow(entry) {
|
|
969
818
|
const key = `${entry.backend}:${entry.id}`;
|
|
970
|
-
const shown = this.#
|
|
819
|
+
const shown = this.#edits.shown(entry);
|
|
971
820
|
const value = entry.structured || isDefaultModelEntry(entry)
|
|
972
821
|
? CONFIGURE
|
|
973
822
|
: shown === null
|
|
@@ -993,7 +842,7 @@ export class SettingsApp {
|
|
|
993
842
|
...(menu.entry.id === "accentColor" ? {
|
|
994
843
|
previews: menu.choices.map(choice => theme.accentPreview?.(String(choice), "■") ?? ""),
|
|
995
844
|
} : {}),
|
|
996
|
-
current: this.#
|
|
845
|
+
current: this.#edits.shown(menu.entry) === null ? null : displaySettingValue(menu.entry, this.#edits.shown(menu.entry)),
|
|
997
846
|
index: menu.index,
|
|
998
847
|
};
|
|
999
848
|
const frame = valueMenuFrame(state, {
|
|
@@ -1155,36 +1004,6 @@ function thinkingLevelDescription(level) {
|
|
|
1155
1004
|
default: return "";
|
|
1156
1005
|
}
|
|
1157
1006
|
}
|
|
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
1007
|
function isDefaultModelEntry(entry) {
|
|
1189
1008
|
return entry.backend === "agent" && entry.id === "defaultModel";
|
|
1190
1009
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { OwnedUiSettingValue, OwnedUiSettingsChangeOutcome, OwnedUiSettingsEntry, OwnedSettingsManager } from "../../ui/settings/index.js";
|
|
2
|
+
type SettingsRecord = Record<string, boolean | string>;
|
|
3
|
+
/** The structured dialog an undo may need to roll back in place; null when none is open. */
|
|
4
|
+
export interface OpenStructuredRecord {
|
|
5
|
+
readonly entry: OwnedUiSettingsEntry;
|
|
6
|
+
readonly record: SettingsRecord;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The settings screen's edits: values shown ahead of the source, and an undo history ordered by edit
|
|
10
|
+
* sequence. Undo requests drain one at a time so each restores against the result of the one before.
|
|
11
|
+
*/
|
|
12
|
+
export declare class SettingsEdits {
|
|
13
|
+
#private;
|
|
14
|
+
constructor(session: Pick<OwnedSettingsManager, "change" | "changeStructured">, openStructured: () => OpenStructuredRecord | null);
|
|
15
|
+
/** The value a row shows: a pending edit, or the source's value. */
|
|
16
|
+
shown(entry: OwnedUiSettingsEntry): OwnedUiSettingValue | null;
|
|
17
|
+
apply(entry: OwnedUiSettingsEntry, value: OwnedUiSettingValue): void;
|
|
18
|
+
/** Saves a structured dialog's record; `rollback` runs when the change fails. */
|
|
19
|
+
saveStructured(entry: OwnedUiSettingsEntry, previous: Readonly<SettingsRecord>, forward: Readonly<SettingsRecord>, rollback: () => void): void;
|
|
20
|
+
undo(): void;
|
|
21
|
+
open(): void;
|
|
22
|
+
/** Drops the history and queued requests; late results no longer record undo steps. */
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
/** The record the engine stores: a choice part at its fallback is unset, so the engine's default applies. */
|
|
26
|
+
export declare function structuredValue(entry: OwnedUiSettingsEntry, record: Readonly<SettingsRecord>): SettingsRecord;
|
|
27
|
+
export declare function sameRecord(left: Readonly<SettingsRecord>, right: Readonly<SettingsRecord>): boolean;
|
|
28
|
+
export declare function replaceRecord(target: SettingsRecord, source: Readonly<SettingsRecord>): void;
|
|
29
|
+
/** Why a change did not take effect, or null when it did. */
|
|
30
|
+
export declare function changeFailure(outcome: OwnedUiSettingsChangeOutcome): string | null;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings screen's edits: values shown ahead of the source, and an undo history ordered by edit
|
|
3
|
+
* sequence. Undo requests drain one at a time so each restores against the result of the one before.
|
|
4
|
+
*/
|
|
5
|
+
export class SettingsEdits {
|
|
6
|
+
#session;
|
|
7
|
+
#openStructured;
|
|
8
|
+
#pending = new Map();
|
|
9
|
+
#history = [];
|
|
10
|
+
#sequence = 0;
|
|
11
|
+
#requests = 0;
|
|
12
|
+
#active = false;
|
|
13
|
+
#closed = false;
|
|
14
|
+
constructor(session, openStructured) {
|
|
15
|
+
this.#session = session;
|
|
16
|
+
this.#openStructured = openStructured;
|
|
17
|
+
}
|
|
18
|
+
/** The value a row shows: a pending edit, or the source's value. */
|
|
19
|
+
shown(entry) {
|
|
20
|
+
return this.#pending.get(pendingKey(entry)) ?? entry.value;
|
|
21
|
+
}
|
|
22
|
+
apply(entry, value) {
|
|
23
|
+
const key = pendingKey(entry);
|
|
24
|
+
const previous = this.shown(entry);
|
|
25
|
+
const sequence = ++this.#sequence;
|
|
26
|
+
// Invariant: shown immediately so the row never lags a keypress, and so the next press
|
|
27
|
+
// steps from here rather than from a value the source has not caught up to.
|
|
28
|
+
this.#pending.set(key, value);
|
|
29
|
+
void this.#session.change(entry.backend, entry.id, value).then(outcome => {
|
|
30
|
+
const failed = changeFailure(outcome) !== null;
|
|
31
|
+
if (!failed && previous !== null && previous !== value)
|
|
32
|
+
this.#record({ kind: "scalar", sequence, entry, previous });
|
|
33
|
+
// Concurrency: a later press may have moved on; only the last request clears itself.
|
|
34
|
+
if (this.#pending.get(key) === value)
|
|
35
|
+
this.#pending.delete(key);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/** Saves a structured dialog's record; `rollback` runs when the change fails. */
|
|
39
|
+
saveStructured(entry, previous, forward, rollback) {
|
|
40
|
+
const sequence = ++this.#sequence;
|
|
41
|
+
void this.#session.changeStructured(entry.backend, entry.id, structuredValue(entry, forward)).then(outcome => {
|
|
42
|
+
if (changeFailure(outcome) === null)
|
|
43
|
+
this.#record({ kind: "structured", sequence, entry, previous, forward });
|
|
44
|
+
else
|
|
45
|
+
rollback();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
undo() {
|
|
49
|
+
this.#requests++;
|
|
50
|
+
this.#drain();
|
|
51
|
+
}
|
|
52
|
+
open() { this.#closed = false; }
|
|
53
|
+
/** Drops the history and queued requests; late results no longer record undo steps. */
|
|
54
|
+
close() {
|
|
55
|
+
this.#closed = true;
|
|
56
|
+
this.#history.length = 0;
|
|
57
|
+
this.#requests = 0;
|
|
58
|
+
}
|
|
59
|
+
#record(record) {
|
|
60
|
+
if (this.#closed)
|
|
61
|
+
return;
|
|
62
|
+
const after = this.#history.findIndex(candidate => candidate.sequence > record.sequence);
|
|
63
|
+
if (after < 0)
|
|
64
|
+
this.#history.push(record);
|
|
65
|
+
else
|
|
66
|
+
this.#history.splice(after, 0, record);
|
|
67
|
+
}
|
|
68
|
+
#drain() {
|
|
69
|
+
if (this.#active || this.#requests === 0)
|
|
70
|
+
return;
|
|
71
|
+
const record = this.#history.pop();
|
|
72
|
+
if (record === undefined) {
|
|
73
|
+
this.#requests = 0;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.#requests--;
|
|
77
|
+
this.#active = true;
|
|
78
|
+
if (record.kind === "scalar") {
|
|
79
|
+
const key = pendingKey(record.entry);
|
|
80
|
+
this.#pending.set(key, record.previous);
|
|
81
|
+
void this.#session.change(record.entry.backend, record.entry.id, record.previous).then(outcome => {
|
|
82
|
+
if (this.#pending.get(key) === record.previous)
|
|
83
|
+
this.#pending.delete(key);
|
|
84
|
+
this.#finish(record, outcome);
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const open = this.#openStructured();
|
|
89
|
+
const target = open !== null && sameEntry(open.entry, record.entry) ? open : null;
|
|
90
|
+
if (target !== null)
|
|
91
|
+
replaceRecord(target.record, record.previous);
|
|
92
|
+
void this.#session.changeStructured(record.entry.backend, record.entry.id, structuredValue(record.entry, record.previous)).then(outcome => {
|
|
93
|
+
if (changeFailure(outcome) !== null && target !== null && this.#openStructured() === target && sameRecord(target.record, record.previous)) {
|
|
94
|
+
replaceRecord(target.record, record.forward);
|
|
95
|
+
}
|
|
96
|
+
this.#finish(record, outcome);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
#finish(record, outcome) {
|
|
100
|
+
if (changeFailure(outcome) !== null)
|
|
101
|
+
this.#record(record);
|
|
102
|
+
this.#active = false;
|
|
103
|
+
this.#drain();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function pendingKey(entry) {
|
|
107
|
+
return `${entry.backend}:${entry.id}`;
|
|
108
|
+
}
|
|
109
|
+
/** The record the engine stores: a choice part at its fallback is unset, so the engine's default applies. */
|
|
110
|
+
export function structuredValue(entry, record) {
|
|
111
|
+
return Object.fromEntries(Object.entries(record).filter(([key, value]) => {
|
|
112
|
+
const part = entry.flags.find(candidate => candidate.key === key);
|
|
113
|
+
return part?.choices === undefined || value !== part.fallback;
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
export function sameRecord(left, right) {
|
|
117
|
+
const leftEntries = Object.entries(left);
|
|
118
|
+
const rightEntries = Object.entries(right);
|
|
119
|
+
return leftEntries.length === rightEntries.length
|
|
120
|
+
&& leftEntries.every(([key, value]) => right[key] === value);
|
|
121
|
+
}
|
|
122
|
+
export function replaceRecord(target, source) {
|
|
123
|
+
for (const key of Object.keys(target))
|
|
124
|
+
delete target[key];
|
|
125
|
+
Object.assign(target, source);
|
|
126
|
+
}
|
|
127
|
+
/** Why a change did not take effect, or null when it did. */
|
|
128
|
+
export function changeFailure(outcome) {
|
|
129
|
+
if (outcome.failure !== null)
|
|
130
|
+
return outcome.failure;
|
|
131
|
+
if (outcome.status === "failed")
|
|
132
|
+
return "the effect failed";
|
|
133
|
+
if (outcome.status === "unavailable")
|
|
134
|
+
return outcome.limitationReason ?? "the setting is unavailable";
|
|
135
|
+
return outcome.limitationReason;
|
|
136
|
+
}
|
|
137
|
+
function sameEntry(left, right) {
|
|
138
|
+
return left.backend === right.backend && left.id === right.id;
|
|
139
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type PaneMouseEvent, type RailPosition, type ScrollbarGeometry, type ScrollbarPresentation, type ScrollbarPresentationInput } from "../../ui/components/index.js";
|
|
2
|
+
/** Where the rail was painted in the last frame, with the list page it scrolls by. */
|
|
3
|
+
export interface SettingsRailFrame {
|
|
4
|
+
readonly rail: RailPosition;
|
|
5
|
+
readonly geometry: ScrollbarGeometry;
|
|
6
|
+
readonly page: number;
|
|
7
|
+
}
|
|
8
|
+
/** How a pointer report landed on the rail: owned keeps it from the list, and `scroll` is the list's new offset. */
|
|
9
|
+
export interface SettingsRailPointer {
|
|
10
|
+
readonly owned: boolean;
|
|
11
|
+
readonly changed: boolean;
|
|
12
|
+
readonly overRail: boolean;
|
|
13
|
+
readonly scroll?: number;
|
|
14
|
+
}
|
|
15
|
+
/** The settings list's scrollbar rail: hover, thumb drag, track paging, and the lingering highlight after a scroll. */
|
|
16
|
+
export declare class SettingsScrollRail {
|
|
17
|
+
#private;
|
|
18
|
+
/** Notes the frame's scroll; a moved list keeps the rail lit and repaints once the highlight lapses. */
|
|
19
|
+
noteRenderedScroll(scroll: number, now: number, requestRender: () => void): void;
|
|
20
|
+
presentation(input: Omit<ScrollbarPresentationInput, "hovered" | "dragging" | "activeUntil">): ScrollbarPresentation;
|
|
21
|
+
/** Records where the rail was painted, or null when this frame has no rail. */
|
|
22
|
+
setFrame(frame: Omit<SettingsRailFrame, "rail"> & {
|
|
23
|
+
readonly rail: Omit<RailPosition, "key">;
|
|
24
|
+
} | null): void;
|
|
25
|
+
pointer(event: PaneMouseEvent, scroll: number): SettingsRailPointer;
|
|
26
|
+
/** Drops hover and any drag, as when a dialog takes the screen. */
|
|
27
|
+
clear(): void;
|
|
28
|
+
dispose(): void;
|
|
29
|
+
}
|