@pellux/goodvibes-tui 1.16.0 → 1.17.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 +95 -9
- package/README.md +1 -1
- package/docs/foundation-artifacts/operator-contract.json +1091 -271
- 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/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/conversation-line-cache.ts +3 -3
- package/src/core/conversation-rendering.ts +4 -4
- package/src/core/conversation.ts +2 -2
- package/src/core/long-task-notifier.ts +7 -7
- package/src/core/stream-event-wiring.ts +8 -6
- package/src/core/system-message-noise.ts +1 -1
- package/src/core/system-message-router.ts +6 -6
- package/src/core/turn-cancellation.ts +1 -1
- package/src/core/turn-event-wiring.ts +8 -8
- package/src/daemon/cli.ts +20 -5
- 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 +34 -9
- 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/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/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/provider-accounts-runtime.ts +71 -6
- package/src/input/commands/provider.ts +7 -5
- 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-runtime.ts +2 -2
- package/src/input/commands.ts +0 -2
- package/src/input/config-modal-types.ts +3 -3
- package/src/input/config-modal.ts +10 -11
- 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 +6 -6
- package/src/input/handler.ts +2 -2
- package/src/input/keybindings.ts +2 -2
- 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 +22 -21
- package/src/input/settings-modal-mutations.ts +12 -68
- package/src/input/settings-modal-types.ts +23 -16
- package/src/input/settings-modal.ts +74 -55
- package/src/main.ts +48 -48
- 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 +29 -10
- package/src/panels/builtin/session.ts +5 -5
- package/src/panels/builtin/shared.ts +4 -4
- package/src/panels/builtin-modals.ts +10 -10
- package/src/panels/builtin-panels.ts +1 -1
- package/src/panels/cost-tracker-panel.ts +124 -7
- package/src/panels/diff-panel.ts +5 -5
- package/src/panels/eval-registry.ts +2 -2
- package/src/panels/fleet-deep-link.ts +1 -1
- package/src/panels/fleet-panel-format.ts +44 -8
- package/src/panels/fleet-panel.ts +16 -16
- package/src/panels/fleet-read-model.ts +52 -13
- package/src/panels/fleet-steer.ts +2 -2
- package/src/panels/fleet-stop.ts +3 -3
- package/src/panels/fleet-tabs.ts +13 -14
- 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/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 +4 -4
- 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 +2 -2
- 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 +1 -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/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/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/onboarding/onboarding-wizard.ts +22 -8
- 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 +102 -24
- package/src/renderer/session-picker-modal.ts +2 -2
- package/src/renderer/settings-modal-helpers.ts +14 -5
- package/src/renderer/settings-modal.ts +127 -39
- package/src/renderer/shell-surface.ts +3 -0
- package/src/renderer/terminal-bg-probe.ts +1 -1
- package/src/renderer/theme.ts +3 -3
- package/src/renderer/tool-call.ts +1 -1
- package/src/renderer/tool-result-summary.ts +1 -1
- package/src/renderer/turn-injection.ts +7 -6
- package/src/renderer/ui-factory.ts +6 -1
- package/src/runtime/bootstrap-command-context.ts +4 -1
- package/src/runtime/bootstrap-command-parts.ts +6 -4
- package/src/runtime/bootstrap-core.ts +35 -35
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +6 -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 +55 -0
- package/src/runtime/embedded-service-factories.ts +40 -0
- package/src/runtime/feature-settings.ts +147 -0
- package/src/runtime/index.ts +4 -21
- 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/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 +81 -82
- 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 +3 -1
- package/src/runtime/update-check.ts +15 -69
- package/src/runtime/workstream-services.ts +2 -2
- package/src/shell/blocking-input.ts +102 -6
- 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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// workstream-runtime.ts — /workstream
|
|
3
3
|
//
|
|
4
4
|
// UX over the OrchestrationEngine (@pellux/goodvibes-sdk/platform/orchestration,
|
|
5
|
-
//
|
|
5
|
+
//) via its command-facing facade wired onto RuntimeServices as
|
|
6
6
|
// `workstreamCommands` (src/runtime/workstream-services.ts) and threaded
|
|
7
7
|
// through CommandContext.session.workstreamEngine exactly like wrfcController
|
|
8
8
|
// already is (bootstrap-command-context.ts).
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// pre-creation "draft" concept, so approval happens on a TUI-held draft
|
|
22
22
|
// (workstream-services.ts's WorkstreamDraft) before anything is created in
|
|
23
23
|
// the engine at all — see that module's header doc for the full reality-wins
|
|
24
|
-
// divergence from the
|
|
24
|
+
// divergence from the design brief.
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
26
26
|
|
|
27
27
|
import { AdaptivePlanner } from '@pellux/goodvibes-sdk/platform/core';
|
package/src/input/commands.ts
CHANGED
|
@@ -36,7 +36,6 @@ import { registerRemoteRuntimeCommands } from './commands/remote-runtime.ts';
|
|
|
36
36
|
import { registerTeleportRuntimeCommands } from './commands/teleport-runtime.ts';
|
|
37
37
|
import { registerSubscriptionRuntimeCommands } from './commands/subscription-runtime.ts';
|
|
38
38
|
import { registerHooksRuntimeCommands } from './commands/hooks-runtime.ts';
|
|
39
|
-
import { registerFlagsRuntimeCommands } from './commands/flags-runtime.ts';
|
|
40
39
|
import { registerEditorRuntimeCommands } from './commands/editor-runtime.ts';
|
|
41
40
|
import { registerControlRoomRuntimeCommands } from './commands/control-room-runtime.ts';
|
|
42
41
|
import { registerMcpRuntimeCommands } from './commands/mcp-runtime.ts';
|
|
@@ -121,7 +120,6 @@ export const BUILTIN_COMMAND_GROUPS: readonly BuiltinCommandGroup[] = [
|
|
|
121
120
|
{ category: 'Remote', register: registerTeleportRuntimeCommands },
|
|
122
121
|
{ category: 'Subscriptions', register: registerSubscriptionRuntimeCommands },
|
|
123
122
|
{ category: 'Hooks', register: registerHooksRuntimeCommands },
|
|
124
|
-
{ category: 'Feature Flags', register: registerFlagsRuntimeCommands },
|
|
125
123
|
{ category: 'Editor', register: registerEditorRuntimeCommands },
|
|
126
124
|
{ category: 'Control Room', register: registerControlRoomRuntimeCommands },
|
|
127
125
|
{ category: 'MCP', register: registerMcpRuntimeCommands },
|
|
@@ -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
|
|
@@ -114,7 +114,7 @@ export interface ConfigModalAction {
|
|
|
114
114
|
* Trigger key: a single printable char ('r', 'd') or a named key ('enter').
|
|
115
115
|
* Must not collide with the host-reserved nav keys (up/down/left/right/tab/
|
|
116
116
|
* j/k/escape/'/') — those are consumed by the host before actions are
|
|
117
|
-
* consulted. '/' arms the host's type-to-filter (
|
|
117
|
+
* consulted. '/' arms the host's type-to-filter (item 1); while
|
|
118
118
|
* filtering, EVERY printable key (not just j/k) is captured by the query
|
|
119
119
|
* instead of firing an action — see handleConfigModalToken.
|
|
120
120
|
*/
|
|
@@ -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();
|
|
@@ -395,8 +395,7 @@ export class ConfigModal {
|
|
|
395
395
|
*
|
|
396
396
|
* `labelWrapWidth` is the wrap column ModalFactory's list section will use
|
|
397
397
|
* 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
|
|
398
|
+
* 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
399
|
* mid-tick — a structural change (an extra visible line) without an
|
|
401
400
|
* interaction — by measuring wrapped line counts here, before the label
|
|
402
401
|
* ever reaches the renderer.
|
|
@@ -445,7 +444,7 @@ export class ConfigModal {
|
|
|
445
444
|
const visible = this.visibleRows;
|
|
446
445
|
const windowed = allRows.slice(this.scrollOffset, this.scrollOffset + visible);
|
|
447
446
|
|
|
448
|
-
//
|
|
447
|
+
// item 1: while filtering, the surface's own action/tab hints are
|
|
449
448
|
// unreliable (their printable-letter keys are captured by the filter
|
|
450
449
|
// instead of firing — see handleConfigModalToken), so the footer swaps
|
|
451
450
|
// to just the filter status (query + a truthful match count) and the
|
|
@@ -483,7 +482,7 @@ export class ConfigModal {
|
|
|
483
482
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
484
483
|
|
|
485
484
|
/**
|
|
486
|
-
*
|
|
485
|
+
* item 2 (wrap-clamp overlay): keep a live label within the FROZEN
|
|
487
486
|
* row's line footprint until the next interaction boundary re-freezes it.
|
|
488
487
|
* `frozenLabel` is what the row wrapped to when the structure was captured
|
|
489
488
|
* (open/nav/filter keystroke); `liveLabel` is this tick's value. If the
|
|
@@ -526,7 +525,7 @@ export class ConfigModal {
|
|
|
526
525
|
* empty-state copy, warning banners) always pass through unfiltered —
|
|
527
526
|
* they're context, not data. When the query excludes every selectable row
|
|
528
527
|
* but the tab genuinely had some, append the honest "no rows match" line
|
|
529
|
-
* (
|
|
528
|
+
* (item 1's empty-result case) instead of silently showing nothing
|
|
530
529
|
* or misleadingly falling back to the surface's own emptyText (which
|
|
531
530
|
* describes "no data at all", not "no data matches your filter").
|
|
532
531
|
*/
|
|
@@ -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;
|
|
@@ -95,7 +95,7 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
|
|
|
95
95
|
dispatchSelectionAction(action, selected);
|
|
96
96
|
}
|
|
97
97
|
} else if (state.selectionModal.allowSearch) {
|
|
98
|
-
//
|
|
98
|
+
// instant filter: an unclaimed keystroke arms search AND starts
|
|
99
99
|
// the query immediately, instead of silently doing nothing until the
|
|
100
100
|
// user discovers '/' first (the "help search needs '/' arming while
|
|
101
101
|
// the palette filters instantly" evaluator finding — /help has no
|
|
@@ -108,7 +108,7 @@ export function handleSelectionModalToken(state: SelectionRouteState, token: Inp
|
|
|
108
108
|
}
|
|
109
109
|
} else if (token.type === 'key') {
|
|
110
110
|
if (token.logicalName === 'escape') {
|
|
111
|
-
//
|
|
111
|
+
// ONE Escape always closes the modal, regardless of search state.
|
|
112
112
|
// Clearing an in-progress query is Backspace's job, not Esc's — the old
|
|
113
113
|
// two-stage contract (1st Esc clears query, 2nd blurs search, 3rd
|
|
114
114
|
// finally closes) was the "help modal took 3 Escapes after searching"
|
|
@@ -234,6 +234,8 @@ type SettingsRouteState = {
|
|
|
234
234
|
searchQuery: string;
|
|
235
235
|
commitEdit: () => void;
|
|
236
236
|
toggleSelectedFlag: () => void;
|
|
237
|
+
/** Scroll the documentation pane (PgUp/PgDn); the renderer clamps and shows honest markers. */
|
|
238
|
+
scrollContext?: (delta: number) => void;
|
|
237
239
|
activateSelected: () => void;
|
|
238
240
|
handleSubscriptionLogoutKey?: (key: string) => 'confirmed' | 'cancelled' | 'absorbed' | 'inactive';
|
|
239
241
|
adjustSelected: (direction: 'left' | 'right', step?: number) => void;
|
|
@@ -415,6 +417,12 @@ export function handleSettingsModalToken(state: SettingsRouteState, token: Input
|
|
|
415
417
|
const reset = state.settingsModal.resetSelected?.();
|
|
416
418
|
if (reset) syncRuntimeAfterSettingReset(state.commandContext, reset.key, reset.value);
|
|
417
419
|
}
|
|
420
|
+
else if (token.logicalName === 'pageup' && !state.settingsModal.editingMode) {
|
|
421
|
+
state.settingsModal.scrollContext?.(-3);
|
|
422
|
+
}
|
|
423
|
+
else if (token.logicalName === 'pagedown' && !state.settingsModal.editingMode) {
|
|
424
|
+
state.settingsModal.scrollContext?.(3);
|
|
425
|
+
}
|
|
418
426
|
else if (token.logicalName === 'tab' && !state.settingsModal.searchFocused) {
|
|
419
427
|
if (state.settingsModal.toggleFocusPane) state.settingsModal.toggleFocusPane();
|
|
420
428
|
else if (focusPane === 'categories') state.settingsModal.focusSettings?.();
|
|
@@ -556,9 +564,9 @@ type ConfigModalRouteState = {
|
|
|
556
564
|
};
|
|
557
565
|
|
|
558
566
|
/**
|
|
559
|
-
* Route a key to the generic config-modal host (
|
|
567
|
+
* Route a key to the generic config-modal host (MIGRATE-TO-MODAL
|
|
560
568
|
* surfaces). The host owns the reserved navigation keys (Esc, up/down/j/k tab
|
|
561
|
-
* left/right, and now '/' —
|
|
569
|
+
* left/right, and now '/' — item 1's type-to-filter, armed the same
|
|
562
570
|
* way as scrollable-list-panel.ts's opt-in filter and SettingsModal's own
|
|
563
571
|
* '/'-armed search); every other key is offered to the active surface's
|
|
564
572
|
* declarative action table (fireAction handles the two-press confirm for
|
|
@@ -227,7 +227,7 @@ export function handleModalTokenRoutes(state: ModalTokenRouteState, token: Input
|
|
|
227
227
|
return withState(state, true);
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
//
|
|
230
|
+
// retirement: the live-tail, agent-detail, and process-modal token
|
|
231
231
|
// routes were removed with those modals (F2 now opens Fleet, which subsumes
|
|
232
232
|
// the live process tree; the three modals were structurally unreachable after
|
|
233
233
|
// the F2 repoint and were deleted).
|
|
@@ -138,10 +138,10 @@ export async function handleMigrateLegacyDaemonServiceForHandler(handler: InputH
|
|
|
138
138
|
legacy,
|
|
139
139
|
);
|
|
140
140
|
handler.commandContext?.print?.(
|
|
141
|
-
['Migrate
|
|
141
|
+
['Migrate install-script daemon service:', ...result.lines.map((line: string) => ` ${line}`)].join('\n'),
|
|
142
142
|
);
|
|
143
143
|
} catch (error) {
|
|
144
|
-
handler.commandContext?.print?.(`Migrate
|
|
144
|
+
handler.commandContext?.print?.(`Migrate install-script daemon service failed: ${summarizeError(error)}`);
|
|
145
145
|
} finally {
|
|
146
146
|
handler.onboardingApplyPending = false;
|
|
147
147
|
handler.requestRender();
|