@pellux/goodvibes-tui 1.16.1 → 1.18.0
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/CHANGELOG.md +138 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +5490 -1806
- package/package.json +7 -7
- package/src/cli/completions/generate.ts +5 -4
- package/src/cli/config-overrides.ts +51 -11
- package/src/cli/doctor.ts +4 -5
- package/src/cli/entrypoint.ts +13 -8
- package/src/cli/help.ts +7 -6
- package/src/cli/launch-auto-update.ts +214 -0
- package/src/cli/management-commands.ts +82 -15
- package/src/cli/management.ts +0 -2
- package/src/cli/parser.ts +5 -0
- package/src/cli/status.ts +13 -0
- package/src/cli/surface-command.ts +5 -5
- package/src/cli/tui-startup.ts +60 -97
- package/src/cli/types.ts +2 -0
- package/src/config/index.ts +1 -1
- package/src/config/tui-extension-settings.ts +33 -0
- package/src/core/composer-state.ts +1 -1
- package/src/core/consolidation-receipt.ts +41 -0
- package/src/core/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +18 -9
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/memory-provenance.ts +67 -0
- package/src/core/pairing-devices.ts +49 -0
- package/src/core/pairing-handoff.ts +101 -0
- package/src/core/pairing-offers.ts +83 -0
- package/src/core/pairing-origin.ts +78 -0
- package/src/core/pairing-tailscale-gateway.ts +56 -0
- package/src/core/power-chip-source.ts +91 -0
- package/src/core/power-status.ts +95 -0
- package/src/core/stable-host.ts +171 -0
- package/src/core/stream-event-wiring.ts +29 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-budget-outcome.ts +60 -0
- package/src/core/turn-cancellation.ts +40 -1
- package/src/core/turn-event-wiring.ts +14 -8
- package/src/daemon/cli.ts +64 -58
- package/src/daemon/lifecycle.ts +41 -0
- package/src/daemon/service-commands.ts +5 -5
- package/src/export/cost-utils.ts +128 -6
- package/src/input/autocomplete.ts +2 -2
- package/src/input/command-registry.ts +55 -9
- package/src/input/commands/acp-runtime.ts +72 -0
- package/src/input/commands/ci-runtime.ts +5 -1
- package/src/input/commands/codebase-runtime.ts +6 -6
- package/src/input/commands/cost-runtime.ts +1 -1
- package/src/input/commands/devices-runtime.ts +109 -0
- package/src/input/commands/git-runtime.ts +28 -4
- package/src/input/commands/graph-runtime.ts +45 -0
- package/src/input/commands/health-runtime.ts +2 -2
- package/src/input/commands/intelligence-runtime.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +1 -1
- package/src/input/commands/local-provider-runtime.ts +49 -10
- package/src/input/commands/local-runtime.ts +53 -5
- package/src/input/commands/memory.ts +2 -2
- package/src/input/commands/operator-panel-runtime.ts +5 -5
- package/src/input/commands/operator-rpc.ts +32 -11
- package/src/input/commands/permissions-provenance.ts +1 -1
- package/src/input/commands/permissions-runtime.ts +99 -8
- package/src/input/commands/planning-runtime.ts +3 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -1
- package/src/input/commands/power-runtime.ts +56 -0
- package/src/input/commands/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- package/src/input/commands/qrcode-runtime.ts +14 -19
- package/src/input/commands/queue-runtime.ts +70 -0
- package/src/input/commands/recall-files-git.ts +13 -51
- package/src/input/commands/recall-files.ts +8 -11
- package/src/input/commands/recall-review.ts +2 -2
- package/src/input/commands/relay-runtime.ts +1 -1
- package/src/input/commands/remote-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +4 -4
- package/src/input/commands/schedule-runtime.ts +14 -7
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-workflow.ts +2 -2
- package/src/input/commands/settings-sync-runtime.ts +2 -2
- package/src/input/commands/share-runtime.ts +1 -1
- package/src/input/commands/shell-core.ts +46 -68
- package/src/input/commands/subscription-runtime.ts +1 -1
- package/src/input/commands/update-runtime.ts +153 -96
- package/src/input/commands/work-plan-runtime.ts +1 -1
- package/src/input/commands/workstream-attempts.ts +6 -1
- package/src/input/commands/workstream-runtime.ts +46 -27
- package/src/input/commands/worktree-runtime.ts +35 -8
- package/src/input/commands.ts +10 -2
- package/src/input/config-modal-types.ts +14 -3
- package/src/input/config-modal.ts +75 -13
- package/src/input/feature-unit-layout.ts +93 -82
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/handler-command-route.ts +2 -2
- package/src/input/handler-content-actions.ts +1 -1
- package/src/input/handler-feed-routes.ts +7 -7
- package/src/input/handler-feed.ts +4 -4
- package/src/input/handler-interactions.ts +3 -2
- package/src/input/handler-modal-routes.ts +12 -4
- package/src/input/handler-modal-token-routes.ts +1 -1
- package/src/input/handler-onboarding-daemon-adopt.ts +2 -2
- package/src/input/handler-onboarding.ts +3 -2
- package/src/input/handler-picker-routes.ts +26 -13
- package/src/input/handler-shortcuts.ts +29 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +21 -3
- package/src/input/onboarding/onboarding-feature-units.ts +34 -27
- package/src/input/onboarding/onboarding-provider-key-fields.ts +112 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +22 -21
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +17 -0
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +2 -2
- package/src/input/onboarding/onboarding-wizard-steps.ts +30 -42
- package/src/input/panel-integration-actions.ts +2 -2
- package/src/input/panel-mouse-geometry.ts +1 -1
- package/src/input/panel-paste-flood-guard.ts +1 -1
- package/src/input/provider-key-intake.ts +167 -0
- package/src/input/sandbox-exec-config.ts +2 -2
- package/src/input/selection-modal.ts +1 -1
- package/src/input/settings-modal-activation.ts +5 -0
- package/src/input/settings-modal-adjustment.ts +4 -31
- package/src/input/settings-modal-data.ts +52 -57
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +27 -17
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +90 -90
- package/src/panels/agent-inspector-shared.ts +4 -4
- package/src/panels/base-panel.ts +2 -2
- package/src/panels/builtin/agent.ts +4 -4
- package/src/panels/builtin/development.ts +4 -4
- package/src/panels/builtin/knowledge.ts +1 -1
- package/src/panels/builtin/operations.ts +83 -11
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +16 -4
- package/src/panels/builtin-modals.ts +59 -42
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +39 -6
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-acts.ts +446 -0
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-gateway.ts +117 -0
- package/src/panels/fleet-observed-render.ts +128 -0
- package/src/panels/fleet-panel-format.ts +163 -11
- package/src/panels/fleet-panel-worktree-detail.ts +14 -0
- package/src/panels/fleet-panel.ts +73 -57
- package/src/panels/fleet-read-model.ts +84 -14
- package/src/panels/fleet-spawn.ts +266 -0
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +41 -5
- package/src/panels/fleet-tabs.ts +15 -16
- package/src/panels/fleet-transcript.ts +5 -5
- package/src/panels/git-panel.ts +2 -2
- package/src/panels/index.ts +4 -1
- package/src/panels/local-auth-panel.ts +1 -1
- package/src/panels/memory-consolidation-gateway.ts +94 -0
- package/src/panels/modals/devices-modal.ts +161 -0
- package/src/panels/modals/hooks-modal.ts +1 -1
- package/src/panels/modals/keybindings-modal.ts +2 -2
- package/src/panels/modals/knowledge-modal.ts +1 -1
- package/src/panels/modals/marketplace-modal.ts +4 -4
- package/src/panels/modals/memory-modal.ts +153 -9
- package/src/panels/modals/modal-surface-helpers.ts +1 -1
- package/src/panels/modals/modal-theme.ts +1 -1
- package/src/panels/modals/pairing-modal.ts +198 -68
- package/src/panels/modals/planning-modal.ts +2 -2
- package/src/panels/modals/plugins-modal.ts +1 -1
- package/src/panels/modals/policy-modal.ts +1 -1
- package/src/panels/modals/security-modal.ts +7 -1
- package/src/panels/modals/skills-modal.ts +1 -1
- package/src/panels/modals/work-plan-modal.ts +3 -3
- package/src/panels/notifications-feed.ts +127 -0
- package/src/panels/notifications-panel.ts +160 -0
- package/src/panels/panel-manager.ts +9 -9
- package/src/panels/plugins-panel.ts +4 -4
- package/src/panels/project-planning-answer-actions.ts +1 -1
- package/src/panels/provider-health-routes.ts +2 -2
- package/src/panels/skills-panel.ts +4 -5
- package/src/panels/token-budget-panel.ts +11 -3
- package/src/panels/types.ts +5 -5
- package/src/panels/workstream-graph-render.ts +123 -0
- package/src/permissions/broker-approval-card.ts +201 -0
- package/src/permissions/hunk-selection.ts +28 -4
- package/src/permissions/prompt.ts +244 -29
- package/src/renderer/autocomplete-overlay.ts +107 -54
- package/src/renderer/code-block.ts +2 -2
- package/src/renderer/compaction-history-modal.ts +1 -1
- package/src/renderer/compaction-preview.ts +1 -1
- package/src/renderer/compaction-quality.ts +1 -1
- package/src/renderer/composer-fragments.ts +69 -0
- package/src/renderer/compositor.ts +2 -2
- package/src/renderer/config-modal.ts +1 -1
- package/src/renderer/conversation-overlays.ts +1 -1
- package/src/renderer/footer-tips.ts +3 -3
- package/src/renderer/hint-grammar.ts +1 -1
- package/src/renderer/modal-factory.ts +1 -1
- package/src/renderer/model-picker-overlay.ts +1 -1
- package/src/renderer/model-workspace.ts +7 -2
- package/src/renderer/panel-composite.ts +2 -2
- package/src/renderer/panel-workspace-bar.ts +1 -1
- package/src/renderer/process-indicator.ts +1 -1
- package/src/renderer/selection-modal-overlay.ts +1 -1
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +23 -5
- package/src/renderer/settings-modal.ts +129 -39
- package/src/renderer/shell-surface.ts +6 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +13 -7
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +38 -1
- package/src/runtime/bootstrap-command-context.ts +7 -2
- package/src/runtime/bootstrap-command-parts.ts +9 -5
- package/src/runtime/bootstrap-core.ts +41 -41
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +13 -5
- package/src/runtime/bootstrap.ts +34 -32
- package/src/runtime/channel-composition.ts +46 -0
- package/src/runtime/code-index-services.ts +3 -4
- package/src/runtime/daemon-attach-notices.ts +120 -0
- package/src/runtime/durability-services.ts +90 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/fleet-services.ts +41 -0
- package/src/runtime/idle-power-services.ts +85 -0
- package/src/runtime/index.ts +6 -21
- package/src/runtime/interaction-seams.ts +109 -0
- package/src/runtime/legacy-daemon-migration.ts +29 -19
- package/src/runtime/memory-fold.ts +1 -1
- package/src/runtime/notification-dispatch.ts +110 -0
- package/src/runtime/onboarding/apply.ts +3 -29
- package/src/runtime/onboarding/fast-path.ts +39 -6
- package/src/runtime/onboarding/provider-key-capture.ts +126 -0
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +9 -3
- package/src/runtime/power-keepawake-remote.ts +75 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/provider-fallback.ts +55 -0
- package/src/runtime/provider-optimizer-wiring.ts +55 -0
- package/src/runtime/release-artifacts.ts +19 -103
- package/src/runtime/resume-notice.ts +1 -1
- package/src/runtime/services.ts +113 -113
- package/src/runtime/session-continuity-hints.ts +38 -0
- package/src/runtime/surface-feature-flags.ts +40 -9
- package/src/runtime/terminal-output-guard.ts +2 -2
- package/src/runtime/trust/workspace-registration.ts +10 -2
- package/src/runtime/trust/workspace-trust.ts +58 -31
- package/src/runtime/ui-services.ts +6 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +108 -132
- package/src/shell/recovery-input-helpers.ts +53 -166
- package/src/shell/ui-openers.ts +38 -6
- package/src/utils/format-duration.ts +1 -1
- package/src/verification/verification-ledger.ts +21 -4
- package/src/version.ts +1 -1
- package/src/input/commands/flags-runtime.ts +0 -328
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { ModalSectionStyle } from '../renderer/modal-factory.ts';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Config-modal host — the shared seam every
|
|
4
|
+
* Config-modal host — the shared seam every MIGRATE-TO-MODAL surface
|
|
5
5
|
* (provider-health, services, subscription, remote, local-auth, settings-sync,
|
|
6
|
-
* sandbox, and the
|
|
6
|
+
* sandbox, and the ecosystem/governance set) is expressed through.
|
|
7
7
|
*
|
|
8
8
|
* A surface is PURE DATA + ACTIONS: it hands the host a `buildView()` that maps
|
|
9
9
|
* live read-models to a tabbed, list-structured view, plus a declarative action
|
|
@@ -106,6 +106,17 @@ export interface ConfigModalActionContext {
|
|
|
106
106
|
readonly setStatus: (message: string) => void;
|
|
107
107
|
/** Close the modal (focus returns to whatever opened it). */
|
|
108
108
|
readonly close: () => void;
|
|
109
|
+
/**
|
|
110
|
+
* Switch the active tab and select a row within it, by id — the in-surface
|
|
111
|
+
* "jump" affordance (e.g. the Memory modal's Proposals tab jumping to an
|
|
112
|
+
* affected record in the Review Queue tab, rather than opening a second
|
|
113
|
+
* modal). Host-owned, populated by ConfigModal itself (like `close`/
|
|
114
|
+
* `setStatus`) — a surface's `onAction` calls it, never constructs it. A
|
|
115
|
+
* no-op if either id is not present in the surface's current structure; the
|
|
116
|
+
* surface should check reachability itself and print an honest status line
|
|
117
|
+
* when it isn't, rather than relying on a silent no-op.
|
|
118
|
+
*/
|
|
119
|
+
readonly jumpToRow?: (tabId: string, rowId: string) => void;
|
|
109
120
|
}
|
|
110
121
|
|
|
111
122
|
/** A declarative action bound to a key. */
|
|
@@ -114,7 +125,7 @@ export interface ConfigModalAction {
|
|
|
114
125
|
* Trigger key: a single printable char ('r', 'd') or a named key ('enter').
|
|
115
126
|
* Must not collide with the host-reserved nav keys (up/down/left/right/tab/
|
|
116
127
|
* j/k/escape/'/') — those are consumed by the host before actions are
|
|
117
|
-
* consulted. '/' arms the host's type-to-filter (
|
|
128
|
+
* consulted. '/' arms the host's type-to-filter (item 1); while
|
|
118
129
|
* filtering, EVERY printable key (not just j/k) is captured by the query
|
|
119
130
|
* instead of firing an action — see handleConfigModalToken.
|
|
120
131
|
*/
|
|
@@ -66,7 +66,7 @@ const DEFAULT_VISIBLE_ROWS = 10;
|
|
|
66
66
|
const DEFAULT_LABEL_WRAP_WIDTH = 70;
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
* Stable id for the synthesized "no matches" row
|
|
69
|
+
* Stable id for the synthesized "no matches" row item 1 injects when a
|
|
70
70
|
* filter query excludes every selectable row in a tab. Non-selectable (info
|
|
71
71
|
* rows are never filtered — see ConfigModal's filter doc) so this id can
|
|
72
72
|
* never collide with a real surface row id.
|
|
@@ -97,7 +97,7 @@ export class ConfigModal {
|
|
|
97
97
|
private pendingConfirmKey: string | null = null;
|
|
98
98
|
|
|
99
99
|
/**
|
|
100
|
-
*
|
|
100
|
+
* item 1 — the host's own type-to-filter, armed with '/' (matching
|
|
101
101
|
* the pre-existing "'/' to filter" convention: scrollable-list-panel.ts's
|
|
102
102
|
* opt-in filter, and SettingsModal's own '/'-armed search). `filterActive`
|
|
103
103
|
* gates whether printable keys go to the query instead of nav/actions;
|
|
@@ -111,11 +111,11 @@ export class ConfigModal {
|
|
|
111
111
|
* - '/'-ARMED here (config-modal host surfaces): these surfaces bind PLAIN
|
|
112
112
|
* single keys to ACTION HOTKEYS (e.g. 'r' refresh, 'd' delete). A key
|
|
113
113
|
* can't be both an action and a filter character, so filtering must be
|
|
114
|
-
* explicitly armed with '/' first. This is
|
|
114
|
+
* explicitly armed with '/' first. This is a deliberate design choice.
|
|
115
115
|
* - INSTANT filtering (pure pickers — help overlay, command palette,
|
|
116
116
|
* selection-modal): these have no single-key actions, so every printable
|
|
117
117
|
* key is unambiguously a filter character and narrows the list on the
|
|
118
|
-
* first keystroke. This is
|
|
118
|
+
* first keystroke. This is a deliberate design choice (selection-modal-overlay.ts).
|
|
119
119
|
* - Both keep single-Esc-close semantics. The only extra step is here: a
|
|
120
120
|
* two-stage Esc (first Esc clears a NON-EMPTY query, second Esc closes)
|
|
121
121
|
* — see the Esc branch in handleConfigModalToken. With an empty query,
|
|
@@ -221,7 +221,7 @@ export class ConfigModal {
|
|
|
221
221
|
this._clampScroll();
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
// ── Filter (
|
|
224
|
+
// ── Filter (item 1 — each mutation is an interaction boundary) ──────
|
|
225
225
|
|
|
226
226
|
isFilterActive(): boolean {
|
|
227
227
|
return this.filterActive;
|
|
@@ -306,7 +306,7 @@ export class ConfigModal {
|
|
|
306
306
|
private _switchTab(dir: 1 | -1): void {
|
|
307
307
|
this._clearConfirm();
|
|
308
308
|
// A filter query is scoped to the tab it was typed against — switching
|
|
309
|
-
// tabs resets it (
|
|
309
|
+
// tabs resets it (item 1), same as statusMessage below.
|
|
310
310
|
this.filterActive = false;
|
|
311
311
|
this.filterQuery = '';
|
|
312
312
|
this.syncStructure();
|
|
@@ -339,7 +339,7 @@ export class ConfigModal {
|
|
|
339
339
|
* action (consumed). Handles the two-press confirm for destructive actions.
|
|
340
340
|
* This is an interaction boundary (syncs structure first).
|
|
341
341
|
*/
|
|
342
|
-
fireAction(key: string, ctx: Omit<ConfigModalActionContext, 'row' | 'tabId' | 'setStatus' | 'close' | 'requestRender'>): boolean {
|
|
342
|
+
fireAction(key: string, ctx: Omit<ConfigModalActionContext, 'row' | 'tabId' | 'setStatus' | 'close' | 'requestRender' | 'jumpToRow'>): boolean {
|
|
343
343
|
this.syncStructure();
|
|
344
344
|
const action = this.resolveAction(key);
|
|
345
345
|
if (!action) return false;
|
|
@@ -359,11 +359,36 @@ export class ConfigModal {
|
|
|
359
359
|
requestRender: this.requestRender,
|
|
360
360
|
setStatus: (m: string) => { this.statusMessage = m; },
|
|
361
361
|
close: () => this.close(),
|
|
362
|
+
jumpToRow: (tabId: string, rowId: string) => this.jumpToRow(tabId, rowId),
|
|
362
363
|
};
|
|
363
364
|
this.surface?.onAction?.(action.id, fullCtx);
|
|
364
365
|
return true;
|
|
365
366
|
}
|
|
366
367
|
|
|
368
|
+
/**
|
|
369
|
+
* In-surface "jump": switch the active tab (by id) and select a specific
|
|
370
|
+
* row (by id) within it — the mechanism a surface's own action handler
|
|
371
|
+
* uses to move the user from one tab to a specific row in another (e.g.
|
|
372
|
+
* the Memory modal's Proposals tab jumping to an affected record in the
|
|
373
|
+
* Review Queue tab). An interaction boundary (re-syncs structure first,
|
|
374
|
+
* same as `_switchTab`). A no-op if either id is absent from the
|
|
375
|
+
* freshly-synced structure.
|
|
376
|
+
*/
|
|
377
|
+
jumpToRow(tabId: string, rowId: string): void {
|
|
378
|
+
this._clearConfirm();
|
|
379
|
+
this.filterActive = false;
|
|
380
|
+
this.filterQuery = '';
|
|
381
|
+
this.syncStructure();
|
|
382
|
+
const tab = this.frozenView?.tabs.find((t) => t.id === tabId);
|
|
383
|
+
if (!tab) return;
|
|
384
|
+
if (!tab.rows.some((r) => r.id === rowId && r.selectable !== false)) return;
|
|
385
|
+
this.activeTabId = tabId;
|
|
386
|
+
this.selectedRowId = rowId;
|
|
387
|
+
this.scrollOffset = 0;
|
|
388
|
+
this.statusMessage = '';
|
|
389
|
+
this._clampScroll();
|
|
390
|
+
}
|
|
391
|
+
|
|
367
392
|
/** Clear a pending confirm when the user navigates or presses an unrelated key. */
|
|
368
393
|
clearConfirmOnMiss(): void {
|
|
369
394
|
this._clearConfirm();
|
|
@@ -395,8 +420,7 @@ export class ConfigModal {
|
|
|
395
420
|
*
|
|
396
421
|
* `labelWrapWidth` is the wrap column ModalFactory's list section will use
|
|
397
422
|
* for row labels (renderConfigModal computes and passes the real one for
|
|
398
|
-
* the current terminal size). It exists so the wrap-clamp below (
|
|
399
|
-
* item 2) can pre-empt a live label growing past ModalFactory's wrap width
|
|
423
|
+
* the current terminal size). It exists so the wrap-clamp below (* item 2) can pre-empt a live label growing past ModalFactory's wrap width
|
|
400
424
|
* mid-tick — a structural change (an extra visible line) without an
|
|
401
425
|
* interaction — by measuring wrapped line counts here, before the label
|
|
402
426
|
* ever reaches the renderer.
|
|
@@ -443,9 +467,9 @@ export class ConfigModal {
|
|
|
443
467
|
});
|
|
444
468
|
|
|
445
469
|
const visible = this.visibleRows;
|
|
446
|
-
const windowed =
|
|
470
|
+
const windowed = this._windowRowsByLineBudget(allRows, this.scrollOffset, visible, labelWrapWidth);
|
|
447
471
|
|
|
448
|
-
//
|
|
472
|
+
// item 1: while filtering, the surface's own action/tab hints are
|
|
449
473
|
// unreliable (their printable-letter keys are captured by the filter
|
|
450
474
|
// instead of firing — see handleConfigModalToken), so the footer swaps
|
|
451
475
|
// to just the filter status (query + a truthful match count) and the
|
|
@@ -483,7 +507,7 @@ export class ConfigModal {
|
|
|
483
507
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
484
508
|
|
|
485
509
|
/**
|
|
486
|
-
*
|
|
510
|
+
* item 2 (wrap-clamp overlay): keep a live label within the FROZEN
|
|
487
511
|
* row's line footprint until the next interaction boundary re-freezes it.
|
|
488
512
|
* `frozenLabel` is what the row wrapped to when the structure was captured
|
|
489
513
|
* (open/nav/filter keystroke); `liveLabel` is this tick's value. If the
|
|
@@ -506,6 +530,44 @@ export class ConfigModal {
|
|
|
506
530
|
return clamped.join('\n');
|
|
507
531
|
}
|
|
508
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Modal sizing rule (owner, zero tolerance: a modal/list must never clip
|
|
535
|
+
* its full descriptive text — size to content or scroll, never clip).
|
|
536
|
+
* `scrollOffset`/`visible` are ROW-count based, which is exactly right when
|
|
537
|
+
* every row wraps to one line — but ModalFactory renders each row's WRAPPED
|
|
538
|
+
* lines and then bounds the whole section to a fixed content-row budget
|
|
539
|
+
* computed from `visible` (see renderConfigModal's targetContentRows). A
|
|
540
|
+
* row whose label wraps to MULTIPLE lines can therefore push the total past
|
|
541
|
+
* that budget, and the LAST row handed to the renderer gets cut off
|
|
542
|
+
* mid-line instead of being deferred to the next scroll page — the
|
|
543
|
+
* clipping hazard this closes.
|
|
544
|
+
*
|
|
545
|
+
* Windows `rows` starting at `scrollOffset`, greedily including whole rows
|
|
546
|
+
* while their CUMULATIVE wrapped-line count (at `wrapWidth`) stays within
|
|
547
|
+
* the `visible` line budget. Always includes at least the first candidate
|
|
548
|
+
* row so the view is never empty — a single row that alone exceeds the
|
|
549
|
+
* budget is shown in full rather than replaced with nothing; only a
|
|
550
|
+
* SUBSEQUENT row that would overflow is deferred to scrolling instead of
|
|
551
|
+
* being rendered partially.
|
|
552
|
+
*/
|
|
553
|
+
private _windowRowsByLineBudget(
|
|
554
|
+
rows: readonly ConfigModalRenderRow[],
|
|
555
|
+
scrollOffset: number,
|
|
556
|
+
visible: number,
|
|
557
|
+
wrapWidth: number,
|
|
558
|
+
): ConfigModalRenderRow[] {
|
|
559
|
+
const candidates = rows.slice(scrollOffset);
|
|
560
|
+
const out: ConfigModalRenderRow[] = [];
|
|
561
|
+
let usedLines = 0;
|
|
562
|
+
for (const row of candidates) {
|
|
563
|
+
const lineCount = Math.max(1, wrapText(row.label, wrapWidth).length);
|
|
564
|
+
if (out.length > 0 && usedLines + lineCount > visible) break;
|
|
565
|
+
out.push(row);
|
|
566
|
+
usedLines += lineCount;
|
|
567
|
+
}
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
|
|
509
571
|
/** Footer text for the active filter: the query + a truthful match count. */
|
|
510
572
|
private _filterStatusHint(matched: number, total: number): string {
|
|
511
573
|
const q = this.filterQuery;
|
|
@@ -526,7 +588,7 @@ export class ConfigModal {
|
|
|
526
588
|
* empty-state copy, warning banners) always pass through unfiltered —
|
|
527
589
|
* they're context, not data. When the query excludes every selectable row
|
|
528
590
|
* but the tab genuinely had some, append the honest "no rows match" line
|
|
529
|
-
* (
|
|
591
|
+
* (item 1's empty-result case) instead of silently showing nothing
|
|
530
592
|
* or misleadingly falling back to the surface's own emptyText (which
|
|
531
593
|
* describes "no data at all", not "no data matches your filter").
|
|
532
594
|
*/
|
|
@@ -1,83 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* feature-unit-layout —
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* (sandbox with Runtime & Data, surfaces with Surfaces & Cloud, and so on).
|
|
2
|
+
* feature-unit-layout — every platform capability renders as a feature UNIT in
|
|
3
|
+
* its settings DOMAIN: the feature's real enablement row (its domain settings
|
|
4
|
+
* key, with the feature's name and full description attached) immediately
|
|
5
|
+
* followed by the settings keys that tune it.
|
|
7
6
|
*
|
|
8
|
-
* Grouping metadata is sourced entirely from the SDK's
|
|
9
|
-
* (the single source of truth, completeness test-guarded SDK-side)
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* Grouping metadata is sourced entirely from the SDK's FEATURE_SETTINGS
|
|
8
|
+
* surface (the single source of truth, completeness test-guarded SDK-side):
|
|
9
|
+
* `domain` is the top-level settings namespace the feature lives in, the
|
|
10
|
+
* enablement binding carries the real option shape (boolean switch, enum mode
|
|
11
|
+
* choices, or a constant capability), and `settings` lists every key that
|
|
12
|
+
* configures the feature.
|
|
12
13
|
*
|
|
13
|
-
* A
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* A feature-unit header is the REAL config row for the feature's enablement
|
|
15
|
+
* key — boolean headers toggle, enum headers cycle their mode choices through
|
|
16
|
+
* the ordinary settings interactions. Two features can share one enablement
|
|
17
|
+
* key (e.g. behavior.compactionStrategy drives both compaction features);
|
|
18
|
+
* each still gets its own header row so every capability keeps its own name,
|
|
19
|
+
* description, and state, all reading the same underlying key.
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
|
-
import {
|
|
19
|
-
import type {
|
|
20
|
-
import type {
|
|
22
|
+
import { FEATURE_SETTINGS } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
23
|
+
import type { FeatureSetting } from '@pellux/goodvibes-sdk/platform/runtime/state';
|
|
24
|
+
import type { ConfigSetting } from '@pellux/goodvibes-sdk/platform/config';
|
|
25
|
+
import { getConfigSchemaSetting } from '../runtime/feature-settings.ts';
|
|
21
26
|
import type { FlagEntry, SettingEntry, SettingsCategory } from './settings-modal-types.ts';
|
|
22
27
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
*/
|
|
28
|
-
export const ADVANCED_FEATURES_CATEGORY: SettingsCategory = 'flags';
|
|
28
|
+
/** Every enablement key — these rows render as feature headers, never as plain sub-rows. */
|
|
29
|
+
const ENABLEMENT_KEYS: ReadonlySet<string> = new Set(
|
|
30
|
+
FEATURE_SETTINGS.map((feature) => feature.enablement.key),
|
|
31
|
+
);
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
|
-
* Host category for a
|
|
32
|
-
*
|
|
33
|
-
* Features bucket when the flag has no config keys.
|
|
34
|
+
* Host category for a feature unit: its settings domain (the top-level
|
|
35
|
+
* namespace of its enablement key), which is always a real settings category.
|
|
34
36
|
*/
|
|
35
|
-
export function getFeatureUnitHostCategory(
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
return (namespace ?? ADVANCED_FEATURES_CATEGORY) as SettingsCategory;
|
|
37
|
+
export function getFeatureUnitHostCategory(featureId: string): SettingsCategory | null {
|
|
38
|
+
const feature = FEATURE_SETTINGS.find((candidate) => candidate.id === featureId);
|
|
39
|
+
return feature ? (feature.domain as SettingsCategory) : null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
* Map every
|
|
43
|
-
*
|
|
44
|
-
* it. A key claimed by two
|
|
45
|
-
*
|
|
46
|
-
* owner, so no config key double-lists across feature units.
|
|
43
|
+
* Map every non-enablement settings key claimed by any feature to the feature
|
|
44
|
+
* that OWNS it — the first feature (in FEATURE_SETTINGS declaration order)
|
|
45
|
+
* whose settings list contains it. A key claimed by two features is listed
|
|
46
|
+
* once, under its owner, so no settings key double-lists across feature units.
|
|
47
47
|
*/
|
|
48
48
|
export function buildConfigKeyOwnership(): Map<string, string> {
|
|
49
49
|
const owner = new Map<string, string>();
|
|
50
|
-
for (const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (!owner.has(key)) owner.set(key, flag.id);
|
|
50
|
+
for (const feature of FEATURE_SETTINGS) {
|
|
51
|
+
for (const key of feature.settings) {
|
|
52
|
+
if (ENABLEMENT_KEYS.has(key)) continue;
|
|
53
|
+
if (!owner.has(key)) owner.set(key, feature.id);
|
|
55
54
|
}
|
|
56
55
|
}
|
|
57
56
|
return owner;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
|
-
* Build the
|
|
62
|
-
* key
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* `flag` marks the row as a feature-unit header for rendering and interaction.
|
|
60
|
+
* Build a feature unit's header row: the REAL config row for the feature's
|
|
61
|
+
* enablement key (so booleans toggle and enums cycle their mode choices
|
|
62
|
+
* through the ordinary settings paths), cloned with the feature attached.
|
|
63
|
+
* When the category has no row for the key (it should always have one — every
|
|
64
|
+
* binding key is CONFIG_SCHEMA-registered), the schema descriptor is used
|
|
65
|
+
* directly so the header still renders honestly.
|
|
68
66
|
*/
|
|
69
|
-
export function
|
|
70
|
-
const
|
|
71
|
-
const setting: ConfigSetting =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
67
|
+
export function buildFeatureHeaderEntry(entry: FlagEntry, base: SettingEntry | null): SettingEntry {
|
|
68
|
+
const feature = entry.feature;
|
|
69
|
+
const setting: ConfigSetting = base?.setting
|
|
70
|
+
?? getConfigSchemaSetting(feature.enablement.key)
|
|
71
|
+
?? {
|
|
72
|
+
key: feature.enablement.key,
|
|
73
|
+
type: 'boolean',
|
|
74
|
+
default: feature.defaultEnabled,
|
|
75
|
+
description: feature.description,
|
|
76
|
+
};
|
|
77
77
|
return {
|
|
78
|
+
...(base ?? {}),
|
|
78
79
|
setting,
|
|
79
|
-
currentValue:
|
|
80
|
-
isDefault:
|
|
80
|
+
currentValue: base ? base.currentValue : setting.default,
|
|
81
|
+
isDefault: base ? base.isDefault : true,
|
|
81
82
|
flag: entry,
|
|
82
83
|
};
|
|
83
84
|
}
|
|
@@ -88,13 +89,14 @@ function cloneOwnedEntry(entry: SettingEntry, ownerFlagId: string): SettingEntry
|
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/**
|
|
91
|
-
* Rewrite `groups` in place so each
|
|
92
|
-
* (
|
|
93
|
-
* followed by the category's remaining un-owned
|
|
94
|
-
*
|
|
92
|
+
* Rewrite `groups` in place so each settings domain leads with its feature
|
|
93
|
+
* units (feature header + its owned settings sub-rows, in FEATURE_SETTINGS
|
|
94
|
+
* declaration order), followed by the category's remaining un-owned rows.
|
|
95
|
+
* The plain rows for enablement keys are consumed into the headers; cross
|
|
96
|
+
* listed copies in the combined network view keep their plain form.
|
|
95
97
|
*
|
|
96
|
-
* No-op when there are no
|
|
97
|
-
* plain namespace-bucketed layout intact.
|
|
98
|
+
* No-op when there are no feature entries (the featureless test path),
|
|
99
|
+
* leaving the plain namespace-bucketed layout intact.
|
|
98
100
|
*/
|
|
99
101
|
export function applyFeatureUnitLayout(
|
|
100
102
|
groups: Map<SettingsCategory, SettingEntry[]>,
|
|
@@ -102,39 +104,48 @@ export function applyFeatureUnitLayout(
|
|
|
102
104
|
): void {
|
|
103
105
|
if (flagEntries.length === 0) return;
|
|
104
106
|
const owner = buildConfigKeyOwnership();
|
|
105
|
-
const
|
|
107
|
+
const entryById = new Map<string, FlagEntry>(flagEntries.map((entry) => [entry.feature.id, entry]));
|
|
106
108
|
|
|
107
|
-
// Group the
|
|
108
|
-
// preserving
|
|
109
|
-
const
|
|
110
|
-
for (const
|
|
111
|
-
if (!
|
|
112
|
-
const category =
|
|
113
|
-
if (!
|
|
114
|
-
|
|
109
|
+
// Group the features this runtime actually registered by their domain,
|
|
110
|
+
// preserving FEATURE_SETTINGS declaration order within each category.
|
|
111
|
+
const featuresByCategory = new Map<SettingsCategory, FeatureSetting[]>();
|
|
112
|
+
for (const feature of FEATURE_SETTINGS) {
|
|
113
|
+
if (!entryById.has(feature.id)) continue; // not registered in this runtime
|
|
114
|
+
const category = feature.domain as SettingsCategory;
|
|
115
|
+
if (!featuresByCategory.has(category)) featuresByCategory.set(category, []);
|
|
116
|
+
featuresByCategory.get(category)!.push(feature);
|
|
115
117
|
}
|
|
116
118
|
|
|
117
|
-
for (const [category,
|
|
119
|
+
for (const [category, features] of featuresByCategory) {
|
|
118
120
|
const existing = groups.get(category) ?? [];
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
const rowByKey = new Map<string, SettingEntry>();
|
|
122
|
+
for (const entry of existing) {
|
|
123
|
+
if (!rowByKey.has(entry.setting.key)) rowByKey.set(entry.setting.key, entry);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Split existing config rows: enablement rows are consumed into headers,
|
|
127
|
+
// rows owned by a feature hosted here become indented sub-rows, and the
|
|
128
|
+
// rest stay as orphan rows after the feature units.
|
|
129
|
+
const hostedEnablementKeys = new Set(features.map((feature) => feature.enablement.key));
|
|
130
|
+
const ownedByFeature = new Map<string, SettingEntry[]>();
|
|
123
131
|
const orphans: SettingEntry[] = [];
|
|
124
132
|
for (const entry of existing) {
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
133
|
+
const key = entry.setting.key;
|
|
134
|
+
if (hostedEnablementKeys.has(key)) continue; // consumed into header(s)
|
|
135
|
+
const ownerId = owner.get(key);
|
|
136
|
+
if (ownerId !== undefined && entryById.has(ownerId)) {
|
|
137
|
+
if (!ownedByFeature.has(ownerId)) ownedByFeature.set(ownerId, []);
|
|
138
|
+
ownedByFeature.get(ownerId)!.push(cloneOwnedEntry(entry, ownerId));
|
|
129
139
|
} else {
|
|
130
140
|
orphans.push(entry);
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
|
|
134
144
|
const rebuilt: SettingEntry[] = [];
|
|
135
|
-
for (const
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
for (const feature of features) {
|
|
146
|
+
const base = rowByKey.get(feature.enablement.key) ?? null;
|
|
147
|
+
rebuilt.push(buildFeatureHeaderEntry(entryById.get(feature.id)!, base));
|
|
148
|
+
for (const sub of ownedByFeature.get(feature.id) ?? []) rebuilt.push(sub);
|
|
138
149
|
}
|
|
139
150
|
rebuilt.push(...orphans);
|
|
140
151
|
groups.set(category, rebuilt);
|
|
@@ -90,7 +90,7 @@ export interface FeedContextMutableInit {
|
|
|
90
90
|
* - `commandRegistry`, `commandContext`, `autocomplete`, `inputHistory`,
|
|
91
91
|
* `conversationManager` — wired after construction; synced at feed() entry only
|
|
92
92
|
* (not per-action) since no in-feed action changes them
|
|
93
|
-
* - `focusTracker`
|
|
93
|
+
* - `focusTracker` — shared instance from uiServices.platform, mutated
|
|
94
94
|
* directly by feedInputTokens() on 'focus' tokens; never reallocated
|
|
95
95
|
*
|
|
96
96
|
* **Rationale:** per-feed mutation on a single object avoids per-keystroke GC pressure
|
|
@@ -126,7 +126,7 @@ export interface FeedContextStableRefs {
|
|
|
126
126
|
keybindingsManager: KeybindingsManager;
|
|
127
127
|
killRing: KillRing;
|
|
128
128
|
focusTracker: FocusTracker;
|
|
129
|
-
/**
|
|
129
|
+
/** item 5 — paste-flood guard state, mutated in place (never reallocated). */
|
|
130
130
|
panelBurstGuard: PanelBurstGuardState;
|
|
131
131
|
getHistory: () => InfiniteBuffer;
|
|
132
132
|
getViewportHeight: () => number;
|
|
@@ -145,13 +145,13 @@ function withPanelFocusSync(context: CommandContext, state: CommandModeRouteStat
|
|
|
145
145
|
showPanel: context.showPanel
|
|
146
146
|
? (panelId, pane, target, opts) => {
|
|
147
147
|
context.showPanel?.(panelId, pane, target, opts);
|
|
148
|
-
//
|
|
148
|
+
// showPanel no longer transfers focus by default — the command
|
|
149
149
|
// path leaves the composer focused (ui-openers.ts's showPanel only
|
|
150
150
|
// calls focusPanels() when opts.focus is set). Only flip the local
|
|
151
151
|
// panelFocused mirror when the caller explicitly asked for focus;
|
|
152
152
|
// otherwise leave it exactly as it was (the composer, since you can
|
|
153
153
|
// only type a command from there in the first place). `target` is the
|
|
154
|
-
//
|
|
154
|
+
// deep-link jump target, forwarded verbatim.
|
|
155
155
|
if (opts?.focus) state.panelFocused = true;
|
|
156
156
|
}
|
|
157
157
|
: undefined,
|
|
@@ -440,7 +440,7 @@ export function handleCtrlC(
|
|
|
440
440
|
// double-press window from advancing. A throw from the async TTS-stop path
|
|
441
441
|
// (e.g. killing the audio subprocess) would otherwise swallow this press and
|
|
442
442
|
// strand the user unable to quit while TTS is speaking. Decouple the two: the
|
|
443
|
-
// press below is recorded no matter how speech-stop fares. (
|
|
443
|
+
// press below is recorded no matter how speech-stop fares. (item 6b.)
|
|
444
444
|
let stoppedSpeech = false;
|
|
445
445
|
try {
|
|
446
446
|
stoppedSpeech = cancelGeneration?.() === true;
|
|
@@ -45,7 +45,7 @@ export type PanelFocusRouteState = {
|
|
|
45
45
|
* no text capture (Invariant A: focus must not silently flip to the composer).
|
|
46
46
|
*/
|
|
47
47
|
onPasteDropped?: (panelName: string) => void;
|
|
48
|
-
/** Wall-clock time (ms) for this token; `burstGuard` is
|
|
48
|
+
/** Wall-clock time (ms) for this token; `burstGuard` is item 5's flood-guard state, mutated in place across tokens (see panel-paste-flood-guard.ts). */
|
|
49
49
|
now: number;
|
|
50
50
|
burstGuard: PanelBurstGuardState;
|
|
51
51
|
/**
|
|
@@ -92,7 +92,7 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
92
92
|
// (e.g. dismiss a confirm dialog or clear search). Only unfocus if the
|
|
93
93
|
// panel returns false (unconsumed) or there is no active panel.
|
|
94
94
|
if (token.logicalName === 'escape') {
|
|
95
|
-
//
|
|
95
|
+
// While streaming, a focused panel must not swallow the
|
|
96
96
|
// only way to cancel — Escape's first job is always cancel-turn; the
|
|
97
97
|
// panel's own two-stage consume-or-unfocus contract only runs once no
|
|
98
98
|
// turn is active, so a *second* Escape falls through to it normally
|
|
@@ -148,12 +148,12 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
148
148
|
|
|
149
149
|
if (token.type === 'text' && token.value) {
|
|
150
150
|
const activePanel = state.panelManager.getActive();
|
|
151
|
-
//
|
|
151
|
+
// '/' is an explicit transfer verb back to the composer (a capturing panel, isCapturingTextBurst, keeps '/' for itself).
|
|
152
152
|
if (token.value === '/' && !activePanel?.isCapturingTextBurst?.()) {
|
|
153
153
|
panelFocused = false;
|
|
154
154
|
return { handled: false, panelFocused };
|
|
155
155
|
}
|
|
156
|
-
// Invariant A/B
|
|
156
|
+
// Invariant A/B: a paste (isPasteToken, one multi-char token) into
|
|
157
157
|
// a capturing panel forwards verbatim below; elsewhere it is DROPPED with
|
|
158
158
|
// a one-shot hint (never exploded into hotkeys, never a silent focus
|
|
159
159
|
// flip — focus moves only on an explicit verb). Discrete keystrokes fall
|
|
@@ -164,7 +164,7 @@ export function handlePanelFocusToken(state: PanelFocusRouteState, token: InputT
|
|
|
164
164
|
return { handled: true, panelFocused };
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// item 5 — paste flood guard (rate-based; see panel-paste-flood-guard.ts).
|
|
168
168
|
if (!state.isPasteToken && !activePanel?.isCapturingTextBurst?.()) {
|
|
169
169
|
const guard = trackPanelPasteFloodGuard(state.burstGuard, state.now);
|
|
170
170
|
if (!guard.dispatch) {
|
|
@@ -211,7 +211,7 @@ export function handleIndicatorFocusToken(state: IndicatorFocusRouteState, token
|
|
|
211
211
|
}
|
|
212
212
|
if (token.logicalName === 'enter') {
|
|
213
213
|
indicatorFocused = false;
|
|
214
|
-
//
|
|
214
|
+
// opens the Fleet panel (see openFleetPanel's own doc, line ~352).
|
|
215
215
|
state.openFleetPanel();
|
|
216
216
|
state.requestRender();
|
|
217
217
|
return { handled: true, indicatorFocused };
|
|
@@ -626,7 +626,7 @@ export function handlePromptKeyToken(state: KeyRouteState, token: InputToken): {
|
|
|
626
626
|
return { handled: true, prompt, cursorPos, inputScrollTop, commandMode, indicatorFocused };
|
|
627
627
|
}
|
|
628
628
|
|
|
629
|
-
//
|
|
629
|
+
// F2 is now handled globally with toggle semantics (handler-shortcuts.ts)
|
|
630
630
|
// BEFORE it ever reaches this route; this branch is dead in the real
|
|
631
631
|
// pipeline but stays for panel-entry-points-reachable.test.ts's direct test.
|
|
632
632
|
if (token.logicalName === 'f2') {
|
|
@@ -129,9 +129,9 @@ export interface InputFeedContext {
|
|
|
129
129
|
inputHistory: InputHistory | null;
|
|
130
130
|
conversationManager: ConversationManager | null;
|
|
131
131
|
readonly killRing: KillRing;
|
|
132
|
-
/** Terminal focus tracker
|
|
132
|
+
/** Terminal focus tracker — updated here from 'focus' tokens, read by the unfocused-alert notifiers in core/. */
|
|
133
133
|
readonly focusTracker: FocusTracker;
|
|
134
|
-
/**
|
|
134
|
+
/** item 5 — the paste-flood guard's persistent state, mutated in place across tokens by handlePanelFocusToken (see panel-paste-flood-guard.ts). Never reallocated. */
|
|
135
135
|
readonly panelBurstGuard: PanelBurstGuardState;
|
|
136
136
|
readonly getHistory: () => InfiniteBuffer;
|
|
137
137
|
readonly getViewportHeight: () => number;
|
|
@@ -204,13 +204,13 @@ export function feedInputTokens(context: InputFeedContext, tokens: readonly Inpu
|
|
|
204
204
|
// several batched into one feed() by render-tick latency — arrive as
|
|
205
205
|
// separate 1-char 'text' tokens. The old per-feed sum misread two quick nav
|
|
206
206
|
// keystrokes (e.g. j then k in one drain) as a "burst" and yanked focus.
|
|
207
|
-
//
|
|
207
|
+
// item 5's flood guard reuses that same per-token model but adds
|
|
208
208
|
// real timing: one `now` per feed() call (not per token) is intentional —
|
|
209
209
|
// a genuine flood delivers many tokens in one drain, and they should all
|
|
210
210
|
// measure as arriving "at once", not spread across meaningless sub-ms noise.
|
|
211
211
|
const now = Date.now();
|
|
212
212
|
for (const token of tokens) {
|
|
213
|
-
// Focus-reporting tokens (CSI ?1004h
|
|
213
|
+
// Focus-reporting tokens (CSI ?1004h) never reach the composer or any
|
|
214
214
|
// modal route — consumed here, first, unconditionally. No render needed.
|
|
215
215
|
if (token.type === 'focus') {
|
|
216
216
|
context.focusTracker.setFocused(token.action === 'in');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildProviderAccountSnapshot } from '@/runtime/index.ts';
|
|
2
|
+
import { enrichProviderAccountsSnapshot } from '../runtime/onboarding/provider-key-capture.ts';
|
|
2
3
|
import type { OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
3
4
|
import { collectOnboardingSnapshot } from '../runtime/onboarding/index.ts';
|
|
4
5
|
import { cleanupMarkerRegistry, expandPrompt, findMarkerAtPos, handleBlockCopy, handleBlockRerun, handleBlockSave, handleBlockToggle, handleBookmark, handleClipboardPaste, handleCopy, handleCtrlC, handleDiffApply, registerPaste } from './handler-content-actions.ts';
|
|
@@ -37,12 +38,12 @@ export async function hydrateOnboardingWizardFromRuntimeForHandler(handler: Inpu
|
|
|
37
38
|
list: () => handler.uiServices.platform.surfaceRegistry.syncConfiguredSurfaces(),
|
|
38
39
|
},
|
|
39
40
|
providerAccounts: {
|
|
40
|
-
loadSnapshot: () => buildProviderAccountSnapshot({
|
|
41
|
+
loadSnapshot: async () => enrichProviderAccountsSnapshot(await buildProviderAccountSnapshot({
|
|
41
42
|
providerRegistry: handler.uiServices.providers.providerRegistry,
|
|
42
43
|
serviceRegistry: handler.uiServices.platform.serviceRegistry,
|
|
43
44
|
subscriptionManager: handler.uiServices.platform.subscriptionManager,
|
|
44
45
|
secretsManager: handler.uiServices.platform.secretsManager,
|
|
45
|
-
}),
|
|
46
|
+
}), handler.uiServices.providers.providerRegistry),
|
|
46
47
|
},
|
|
47
48
|
});
|
|
48
49
|
if (!handler.onboardingWizard.active || hydrationSerial !== handler.onboardingHydrationSerial) return;
|