@pellux/goodvibes-tui 0.19.24 → 0.19.26
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 +13 -0
- package/README.md +5 -5
- package/bin/goodvibes +10 -0
- package/bin/goodvibes-daemon +10 -0
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +3 -2
- package/src/cli/bundle-command.ts +225 -0
- package/src/cli/completion.ts +90 -0
- package/src/cli/config-overrides.ts +159 -0
- package/src/cli/endpoints.ts +63 -0
- package/src/cli/entrypoint.ts +169 -0
- package/src/cli/help.ts +301 -0
- package/src/cli/index.ts +11 -0
- package/src/cli/management-commands.ts +426 -0
- package/src/cli/management.ts +719 -0
- package/src/cli/network-posture.ts +46 -0
- package/src/cli/package-verification.ts +119 -0
- package/src/cli/parser.ts +369 -0
- package/src/cli/provider-classification.ts +107 -0
- package/src/cli/redaction.ts +105 -0
- package/src/cli/service-command.ts +45 -0
- package/src/cli/service-posture.ts +247 -0
- package/src/cli/status.ts +382 -0
- package/src/cli/surface-command.ts +248 -0
- package/src/cli/tui-startup.ts +32 -0
- package/src/cli/types.ts +69 -0
- package/src/cli-flags.ts +18 -55
- package/src/config/index.ts +1 -1
- package/src/config/secrets.ts +44 -0
- package/src/daemon/cli.ts +62 -11
- package/src/input/command-registry.ts +3 -0
- package/src/input/commands/guidance-runtime.ts +9 -4
- package/src/input/commands/local-runtime.ts +21 -7
- package/src/input/commands/local-setup.ts +31 -38
- package/src/input/commands/onboarding-runtime.ts +14 -0
- package/src/input/commands/runtime-services.ts +9 -0
- package/src/input/commands.ts +2 -0
- package/src/input/feed-context-factory.ts +8 -1
- package/src/input/handler-feed.ts +13 -8
- package/src/input/handler-interactions.ts +266 -0
- package/src/input/handler-modal-stack.ts +23 -3
- package/src/input/handler-modal-token-routes.ts +23 -1
- package/src/input/handler-onboarding.ts +696 -0
- package/src/input/handler-picker-routes.ts +15 -7
- package/src/input/handler-ui-state.ts +58 -0
- package/src/input/handler.ts +120 -246
- package/src/input/onboarding/handler-onboarding-routes.ts +105 -0
- package/src/input/onboarding/onboarding-wizard-apply.ts +211 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +148 -0
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +712 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +218 -0
- package/src/input/onboarding/onboarding-wizard-rules.ts +224 -0
- package/src/input/onboarding/onboarding-wizard-state.ts +354 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +642 -0
- package/src/input/onboarding/onboarding-wizard-types.ts +170 -0
- package/src/input/onboarding/onboarding-wizard.ts +594 -0
- package/src/main.ts +32 -39
- package/src/panels/builtin/operations.ts +0 -10
- package/src/panels/index.ts +0 -1
- package/src/renderer/conversation-overlays.ts +6 -0
- package/src/renderer/help-overlay.ts +1 -1
- package/src/renderer/onboarding/onboarding-wizard.ts +533 -0
- package/src/runtime/bootstrap-core.ts +1 -0
- package/src/runtime/bootstrap.ts +123 -0
- package/src/runtime/onboarding/apply.ts +685 -0
- package/src/runtime/onboarding/derivation.ts +495 -0
- package/src/runtime/onboarding/index.ts +7 -0
- package/src/runtime/onboarding/markers.ts +161 -0
- package/src/runtime/onboarding/snapshot.ts +400 -0
- package/src/runtime/onboarding/state.ts +140 -0
- package/src/runtime/onboarding/types.ts +402 -0
- package/src/runtime/onboarding/verify.ts +233 -0
- package/src/runtime/ui-services.ts +16 -0
- package/src/shell/ui-openers.ts +12 -2
- package/src/version.ts +1 -1
- package/src/panels/welcome-panel.ts +0 -64
|
@@ -0,0 +1,594 @@
|
|
|
1
|
+
import type { ModelPickerTarget } from '../model-picker.ts';
|
|
2
|
+
import type { OnboardingApplyRequest, OnboardingSnapshotState, OnboardingStep1CapabilityId, OnboardingStep1CapabilityItem, OnboardingStepDerivationState } from '../../runtime/onboarding/index.ts';
|
|
3
|
+
import { STEP_ORDER } from './onboarding-wizard-constants.ts';
|
|
4
|
+
import { buildOnboardingApplyRequest } from './onboarding-wizard-apply.ts';
|
|
5
|
+
import { buildOnboardingWizardSteps } from './onboarding-wizard-steps.ts';
|
|
6
|
+
import { buildDefaultDerivedState, clamp, cloneSelection, getRuntimeDerivedState, maskValue, modelSelectionLabel, normalizeText } from './onboarding-wizard-helpers.ts';
|
|
7
|
+
import { defaultReviewUserMarker as defaultReviewUserMarkerForController, getBooleanFieldValue as getBooleanFieldValueForController, getDefaultAdminUsername as getDefaultAdminUsernameForController, getNumberFieldValue as getNumberFieldValueForController, getPortFieldValue as getPortFieldValueForController, getSelectedSecretMedium as getSelectedSecretMediumForController, getSharedIpDefault as getSharedIpDefaultForController, getSharedIpHostDefault as getSharedIpHostDefaultForController, getStringFieldValue as getStringFieldValueForController, hasAdminAuthUser as hasAdminAuthUserForController, hasBootstrapCredentialPresent as hasBootstrapCredentialPresentForController, hasSelectedInboundExternalSurface as hasSelectedInboundExternalSurfaceForController, hasServerCapabilitiesSelected as hasServerCapabilitiesSelectedForController, isCapabilitySelected as isCapabilitySelectedForController, isRequiredExternalSetupField as isRequiredExternalSetupFieldForController, parseIntegerFieldValue as parseIntegerFieldValueForController, requiresAuthBootstrap as requiresAuthBootstrapForController, selectAllServerCapabilities as selectAllServerCapabilitiesForController, selectLocalTuiOnly as selectLocalTuiOnlyForController, setCapabilityValue as setCapabilityValueForController, shouldEnableBrowserSurface as shouldEnableBrowserSurfaceForController, shouldEnableHttpListener as shouldEnableHttpListenerForController, shouldExposeControlPlaneNetwork as shouldExposeControlPlaneNetworkForController, shouldExposeHttpListenerNetworkFields as shouldExposeHttpListenerNetworkFieldsForController, toggleCapability as toggleCapabilityForController } from './onboarding-wizard-rules.ts';
|
|
8
|
+
import { ensureSelectionVisible as ensureSelectionVisibleForController, getBlockingFieldLabels as getBlockingFieldLabelsForController, getCapabilitySelectionState as getCapabilitySelectionStateForController, getCompletedFieldCount as getCompletedFieldCountForController, getCompletedToggleCount as getCompletedToggleCountForController, getCurrentCapabilities as getCurrentCapabilitiesForController, getFieldById as getFieldByIdForController, getFieldValidationError as getFieldValidationErrorForController, getStepFieldCount as getStepFieldCountForController, getToggleFieldCount as getToggleFieldCountForController, hasExistingAccessState as hasExistingAccessStateForController, isFieldDirty as isFieldDirtyForController, isFieldDirtyByDefinition as isFieldDirtyByDefinitionForController, isFieldSatisfied as isFieldSatisfiedForController, isStepDirty as isStepDirtyForController, recalculateDirtyState as recalculateDirtyStateForController, reconcileStateWithCurrentDefinitions as reconcileStateWithCurrentDefinitionsForController, reconcileStepCursor as reconcileStepCursorForController, resetValuesFromCurrentDefinitions as resetValuesFromCurrentDefinitionsForController } from './onboarding-wizard-state.ts';
|
|
9
|
+
import type { MutableModelSelectionMap, OnboardingWizardAction, OnboardingWizardFieldDefinition, OnboardingWizardFieldWindow, OnboardingWizardMode, OnboardingWizardModelSelection, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStepDefinition, OnboardingWizardStepId } from './onboarding-wizard-types.ts';
|
|
10
|
+
|
|
11
|
+
export type { OnboardingWizardAcknowledgementFieldDefinition, OnboardingWizardAction, OnboardingWizardActionFieldDefinition, OnboardingWizardChecklistFieldDefinition, OnboardingWizardFieldDefinition, OnboardingWizardFieldKind, OnboardingWizardFieldWindow, OnboardingWizardMaskedFieldDefinition, OnboardingWizardMode, OnboardingWizardModelPickerFieldDefinition, OnboardingWizardModelSelection, OnboardingWizardRadioFieldDefinition, OnboardingWizardRadioOption, OnboardingWizardRuntimeHydration, OnboardingWizardSnapshot, OnboardingWizardStatusFieldDefinition, OnboardingWizardStepDefinition, OnboardingWizardStepId, OnboardingWizardTextFieldDefinition } from './onboarding-wizard-types.ts';
|
|
12
|
+
export { getOnboardingWizardBodyRows, getOnboardingWizardVisibleFieldCount } from './onboarding-wizard-helpers.ts';
|
|
13
|
+
|
|
14
|
+
export class OnboardingWizardController {
|
|
15
|
+
public active = false;
|
|
16
|
+
public mode: OnboardingWizardMode = 'new';
|
|
17
|
+
public stepIndex = 0;
|
|
18
|
+
public hydrationPending = false;
|
|
19
|
+
public readonly scrollOffsets = Array.from({ length: STEP_ORDER.length }, () => 0);
|
|
20
|
+
public readonly selectedFieldIndices = Array.from({ length: STEP_ORDER.length }, () => 0);
|
|
21
|
+
public readonly dirtyStepIds = new Set<OnboardingWizardStepId>();
|
|
22
|
+
public pendingModelPickerTarget: ModelPickerTarget | null = null;
|
|
23
|
+
public pendingAction: OnboardingWizardAction | null = null;
|
|
24
|
+
|
|
25
|
+
public readonly toggleState = new Map<string, boolean>();
|
|
26
|
+
public readonly touchedActionFields = new Set<string>();
|
|
27
|
+
public readonly radioState = new Map<string, string>();
|
|
28
|
+
public readonly textState = new Map<string, string>();
|
|
29
|
+
public readonly modelSelectionState: MutableModelSelectionMap = new Map();
|
|
30
|
+
public editingFieldId: string | null = null;
|
|
31
|
+
public editBuffer = '';
|
|
32
|
+
public hydrationError: string | null = null;
|
|
33
|
+
|
|
34
|
+
public readonly baselineToggleState = new Map<string, boolean>();
|
|
35
|
+
public readonly baselineRadioState = new Map<string, string>();
|
|
36
|
+
public readonly baselineTextState = new Map<string, string>();
|
|
37
|
+
public readonly baselineModelSelectionState: MutableModelSelectionMap = new Map();
|
|
38
|
+
|
|
39
|
+
public runtimeSnapshot: OnboardingSnapshotState | null = null;
|
|
40
|
+
public runtimeDerived: OnboardingStepDerivationState = buildDefaultDerivedState();
|
|
41
|
+
|
|
42
|
+
public get steps(): readonly OnboardingWizardStepDefinition[] {
|
|
43
|
+
return buildOnboardingWizardSteps(this);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public get currentStep(): OnboardingWizardStepDefinition {
|
|
47
|
+
return this.steps[this.stepIndex] ?? this.steps[0]!;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get dirty(): boolean {
|
|
51
|
+
return this.dirtyStepIds.size > 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public get dirtyStepCount(): number {
|
|
55
|
+
return this.dirtyStepIds.size;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor() {
|
|
59
|
+
this.resetValuesFromCurrentDefinitions();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public open(mode: OnboardingWizardMode = 'new'): void {
|
|
63
|
+
this.mode = mode;
|
|
64
|
+
this.active = true;
|
|
65
|
+
this.hydrationPending = false;
|
|
66
|
+
this.hydrationError = null;
|
|
67
|
+
this.stepIndex = 0;
|
|
68
|
+
this.pendingModelPickerTarget = null;
|
|
69
|
+
this.pendingAction = null;
|
|
70
|
+
this.editingFieldId = null;
|
|
71
|
+
this.editBuffer = '';
|
|
72
|
+
this.scrollOffsets.fill(0);
|
|
73
|
+
this.selectedFieldIndices.fill(0);
|
|
74
|
+
this.resetValuesFromCurrentDefinitions();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public close(): void {
|
|
78
|
+
this.active = false;
|
|
79
|
+
this.hydrationPending = false;
|
|
80
|
+
this.hydrationError = null;
|
|
81
|
+
this.pendingModelPickerTarget = null;
|
|
82
|
+
this.pendingAction = null;
|
|
83
|
+
this.cancelEdit();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public reopen(): void {
|
|
87
|
+
this.active = true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public setMode(mode: OnboardingWizardMode): void {
|
|
91
|
+
if (this.mode === mode) return;
|
|
92
|
+
this.mode = mode;
|
|
93
|
+
this.cancelEdit();
|
|
94
|
+
this.pendingModelPickerTarget = null;
|
|
95
|
+
this.pendingAction = null;
|
|
96
|
+
this.resetValuesFromCurrentDefinitions();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public hydrateRuntimeState(
|
|
100
|
+
hydration: OnboardingWizardRuntimeHydration,
|
|
101
|
+
options: { resetValues?: boolean } = {},
|
|
102
|
+
): void {
|
|
103
|
+
this.runtimeSnapshot = hydration.snapshot ?? this.runtimeSnapshot;
|
|
104
|
+
this.runtimeDerived = getRuntimeDerivedState(hydration);
|
|
105
|
+
this.hydrationPending = false;
|
|
106
|
+
this.hydrationError = null;
|
|
107
|
+
|
|
108
|
+
if (options.resetValues ?? true) {
|
|
109
|
+
this.pendingModelPickerTarget = null;
|
|
110
|
+
this.cancelEdit();
|
|
111
|
+
this.stepIndex = 0;
|
|
112
|
+
this.resetValuesFromCurrentDefinitions();
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.reconcileStateWithCurrentDefinitions();
|
|
117
|
+
this.recalculateDirtyState();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public captureHydratedState(): OnboardingWizardRuntimeHydration {
|
|
121
|
+
return {
|
|
122
|
+
snapshot: this.runtimeSnapshot,
|
|
123
|
+
derived: this.runtimeDerived,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public captureSnapshot(): OnboardingWizardSnapshot {
|
|
128
|
+
return {
|
|
129
|
+
active: this.active,
|
|
130
|
+
mode: this.mode,
|
|
131
|
+
stepIndex: this.stepIndex,
|
|
132
|
+
scrollOffsets: [...this.scrollOffsets],
|
|
133
|
+
selectedFieldIndices: [...this.selectedFieldIndices],
|
|
134
|
+
dirtyStepIds: [...this.dirtyStepIds],
|
|
135
|
+
pendingModelPickerTarget: this.pendingModelPickerTarget,
|
|
136
|
+
pendingAction: this.pendingAction,
|
|
137
|
+
toggleState: [...this.toggleState.entries()],
|
|
138
|
+
radioState: [...this.radioState.entries()],
|
|
139
|
+
textState: [...this.textState.entries()],
|
|
140
|
+
modelSelectionState: [...this.modelSelectionState.entries()].map(([target, selection]) => [target, cloneSelection(selection)] as const),
|
|
141
|
+
touchedActionFields: [...this.touchedActionFields],
|
|
142
|
+
editingFieldId: this.editingFieldId,
|
|
143
|
+
editBuffer: this.editBuffer,
|
|
144
|
+
hydrationPending: this.hydrationPending,
|
|
145
|
+
hydrationError: this.hydrationError,
|
|
146
|
+
hydration: this.captureHydratedState(),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
public restoreSnapshot(snapshot: OnboardingWizardSnapshot, options: { active?: boolean } = {}): void {
|
|
151
|
+
this.runtimeSnapshot = snapshot.hydration.snapshot ?? null;
|
|
152
|
+
this.runtimeDerived = getRuntimeDerivedState(snapshot.hydration);
|
|
153
|
+
this.hydrationPending = snapshot.hydrationPending;
|
|
154
|
+
this.hydrationError = snapshot.hydrationError;
|
|
155
|
+
this.mode = snapshot.mode;
|
|
156
|
+
this.stepIndex = clamp(snapshot.stepIndex, 0, Math.max(0, this.steps.length - 1));
|
|
157
|
+
this.pendingModelPickerTarget = snapshot.pendingModelPickerTarget;
|
|
158
|
+
this.pendingAction = snapshot.pendingAction;
|
|
159
|
+
this.dirtyStepIds.clear();
|
|
160
|
+
for (const stepId of snapshot.dirtyStepIds) this.dirtyStepIds.add(stepId);
|
|
161
|
+
|
|
162
|
+
for (let index = 0; index < this.scrollOffsets.length; index += 1) {
|
|
163
|
+
this.scrollOffsets[index] = snapshot.scrollOffsets[index] ?? 0;
|
|
164
|
+
this.selectedFieldIndices[index] = snapshot.selectedFieldIndices[index] ?? 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
this.toggleState.clear();
|
|
168
|
+
for (const [fieldId, value] of snapshot.toggleState) this.toggleState.set(fieldId, value);
|
|
169
|
+
this.radioState.clear();
|
|
170
|
+
for (const [fieldId, value] of snapshot.radioState) this.radioState.set(fieldId, value);
|
|
171
|
+
this.textState.clear();
|
|
172
|
+
for (const [fieldId, value] of snapshot.textState) this.textState.set(fieldId, value);
|
|
173
|
+
this.modelSelectionState.clear();
|
|
174
|
+
for (const [target, selection] of snapshot.modelSelectionState) this.modelSelectionState.set(target, cloneSelection(selection));
|
|
175
|
+
this.touchedActionFields.clear();
|
|
176
|
+
for (const fieldId of snapshot.touchedActionFields) this.touchedActionFields.add(fieldId);
|
|
177
|
+
|
|
178
|
+
this.editingFieldId = snapshot.editingFieldId;
|
|
179
|
+
this.editBuffer = snapshot.editBuffer;
|
|
180
|
+
this.active = options.active ?? snapshot.active;
|
|
181
|
+
this.reconcileStateWithCurrentDefinitions();
|
|
182
|
+
this.recalculateDirtyState();
|
|
183
|
+
this.reconcileStepCursor(this.stepIndex);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public beginRuntimeHydration(): void {
|
|
187
|
+
this.hydrationPending = true;
|
|
188
|
+
this.hydrationError = null;
|
|
189
|
+
this.stepIndex = 0;
|
|
190
|
+
this.pendingModelPickerTarget = null;
|
|
191
|
+
this.pendingAction = null;
|
|
192
|
+
this.cancelEdit();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public finishRuntimeHydration(): void {
|
|
196
|
+
this.hydrationPending = false;
|
|
197
|
+
this.hydrationError = null;
|
|
198
|
+
this.stepIndex = clamp(this.stepIndex, 0, Math.max(0, this.steps.length - 1));
|
|
199
|
+
this.reconcileStepCursor(this.stepIndex);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
public failRuntimeHydration(message: string): void {
|
|
203
|
+
this.hydrationPending = false;
|
|
204
|
+
this.hydrationError = message;
|
|
205
|
+
this.stepIndex = 0;
|
|
206
|
+
this.pendingModelPickerTarget = null;
|
|
207
|
+
this.pendingAction = null;
|
|
208
|
+
this.cancelEdit();
|
|
209
|
+
this.scrollOffsets.fill(0);
|
|
210
|
+
this.selectedFieldIndices.fill(0);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
public setStep(stepIndex: number): void {
|
|
214
|
+
const clamped = clamp(stepIndex, 0, this.steps.length - 1);
|
|
215
|
+
if (clamped === this.stepIndex) return;
|
|
216
|
+
this.cancelEdit();
|
|
217
|
+
this.stepIndex = clamped;
|
|
218
|
+
this.pendingModelPickerTarget = null;
|
|
219
|
+
this.pendingAction = null;
|
|
220
|
+
this.reconcileStepCursor(clamped);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public nextStep(): void {
|
|
224
|
+
this.setStep(this.stepIndex + 1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public prevStep(): void {
|
|
228
|
+
this.setStep(this.stepIndex - 1);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public moveSelection(delta: number, visibleFields: number): void {
|
|
232
|
+
const total = this.currentStep.fields.length;
|
|
233
|
+
if (total === 0) return;
|
|
234
|
+
this.cancelEdit();
|
|
235
|
+
const nextIndex = clamp(this.getSelectedFieldIndex() + delta, 0, total - 1);
|
|
236
|
+
this.selectedFieldIndices[this.stepIndex] = nextIndex;
|
|
237
|
+
this.ensureSelectionVisible(visibleFields);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
public pageSelection(delta: number, visibleFields: number): void {
|
|
241
|
+
const distance = Math.max(1, visibleFields) * delta;
|
|
242
|
+
this.moveSelection(distance, visibleFields);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public selectFirst(visibleFields: number): void {
|
|
246
|
+
this.cancelEdit();
|
|
247
|
+
this.selectedFieldIndices[this.stepIndex] = 0;
|
|
248
|
+
this.ensureSelectionVisible(visibleFields);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public selectLast(visibleFields: number): void {
|
|
252
|
+
const total = this.currentStep.fields.length;
|
|
253
|
+
if (total === 0) return;
|
|
254
|
+
this.cancelEdit();
|
|
255
|
+
this.selectedFieldIndices[this.stepIndex] = total - 1;
|
|
256
|
+
this.ensureSelectionVisible(visibleFields);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
public getSelectedFieldIndex(): number {
|
|
260
|
+
this.reconcileStepCursor(this.stepIndex);
|
|
261
|
+
return this.selectedFieldIndices[this.stepIndex] ?? 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
public getSelectedField(): OnboardingWizardFieldDefinition | null {
|
|
265
|
+
return this.currentStep.fields[this.getSelectedFieldIndex()] ?? null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
public isEditingTextField(): boolean {
|
|
269
|
+
return this.editingFieldId !== null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public getFieldWindow(visibleFields: number): OnboardingWizardFieldWindow {
|
|
273
|
+
const fields = this.currentStep.fields;
|
|
274
|
+
const total = fields.length;
|
|
275
|
+
if (total === 0) {
|
|
276
|
+
return {
|
|
277
|
+
start: 0,
|
|
278
|
+
end: 0,
|
|
279
|
+
total: 0,
|
|
280
|
+
fields: [],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
this.ensureSelectionVisible(visibleFields);
|
|
285
|
+
const start = clamp(this.scrollOffsets[this.stepIndex] ?? 0, 0, Math.max(0, total - visibleFields));
|
|
286
|
+
const end = Math.min(total, start + Math.max(1, visibleFields));
|
|
287
|
+
|
|
288
|
+
return {
|
|
289
|
+
start,
|
|
290
|
+
end,
|
|
291
|
+
total,
|
|
292
|
+
fields: fields.slice(start, end),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public clearPendingModelPickerTarget(): void {
|
|
297
|
+
this.pendingModelPickerTarget = null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
public consumePendingModelPickerTarget(): ModelPickerTarget | null {
|
|
301
|
+
const target = this.pendingModelPickerTarget;
|
|
302
|
+
this.pendingModelPickerTarget = null;
|
|
303
|
+
return target;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public clearPendingAction(): void {
|
|
307
|
+
this.pendingAction = null;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
public consumePendingAction(): OnboardingWizardAction | null {
|
|
311
|
+
const action = this.pendingAction;
|
|
312
|
+
this.pendingAction = null;
|
|
313
|
+
return action;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
public activateSelected(): void {
|
|
317
|
+
if (this.hydrationPending || this.hydrationError !== null) return;
|
|
318
|
+
const field = this.getSelectedField();
|
|
319
|
+
if (!field) return;
|
|
320
|
+
if (field.kind === 'status') return;
|
|
321
|
+
|
|
322
|
+
if (field.kind === 'checklist' && field.capabilityId) {
|
|
323
|
+
this.toggleCapability(field.capabilityId);
|
|
324
|
+
this.pendingModelPickerTarget = null;
|
|
325
|
+
this.pendingAction = null;
|
|
326
|
+
this.recalculateDirtyState();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
if (field.kind === 'checklist' || field.kind === 'acknowledgement') {
|
|
331
|
+
const current = this.toggleState.get(field.id) ?? field.defaultValue;
|
|
332
|
+
this.toggleState.set(field.id, !current);
|
|
333
|
+
this.pendingModelPickerTarget = null;
|
|
334
|
+
this.pendingAction = null;
|
|
335
|
+
this.recalculateDirtyState();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (field.kind === 'radio') {
|
|
340
|
+
const options = field.options;
|
|
341
|
+
const current = this.radioState.get(field.id) ?? field.defaultValue;
|
|
342
|
+
const currentIndex = Math.max(0, options.findIndex((option) => option.id === current));
|
|
343
|
+
const next = options[(currentIndex + 1) % options.length];
|
|
344
|
+
if (!next) return;
|
|
345
|
+
this.radioState.set(field.id, next.id);
|
|
346
|
+
this.pendingModelPickerTarget = null;
|
|
347
|
+
this.pendingAction = null;
|
|
348
|
+
this.recalculateDirtyState();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (field.kind === 'text' || field.kind === 'masked') {
|
|
353
|
+
if (this.editingFieldId === field.id) this.commitEdit();
|
|
354
|
+
else this.beginEdit(field.id);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (field.kind === 'action') {
|
|
359
|
+
if (field.action === 'select-all-capabilities') {
|
|
360
|
+
this.selectAllServerCapabilities();
|
|
361
|
+
this.pendingAction = null;
|
|
362
|
+
this.pendingModelPickerTarget = null;
|
|
363
|
+
this.recalculateDirtyState();
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (field.action === 'clear-capabilities') {
|
|
368
|
+
this.selectLocalTuiOnly();
|
|
369
|
+
this.pendingAction = null;
|
|
370
|
+
this.pendingModelPickerTarget = null;
|
|
371
|
+
this.recalculateDirtyState();
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.pendingAction = field.action;
|
|
376
|
+
this.pendingModelPickerTarget = null;
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
this.pendingModelPickerTarget = field.target;
|
|
381
|
+
this.pendingAction = null;
|
|
382
|
+
this.touchedActionFields.add(field.id);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
public beginEdit(fieldId: string): void {
|
|
386
|
+
const field = this.getFieldById(fieldId);
|
|
387
|
+
if (!field || (field.kind !== 'text' && field.kind !== 'masked')) return;
|
|
388
|
+
this.pendingModelPickerTarget = null;
|
|
389
|
+
this.pendingAction = null;
|
|
390
|
+
this.editingFieldId = fieldId;
|
|
391
|
+
this.editBuffer = this.textState.get(field.id) ?? field.defaultValue;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
public commitEdit(): void {
|
|
395
|
+
const fieldId = this.editingFieldId;
|
|
396
|
+
if (fieldId === null) return;
|
|
397
|
+
const field = this.getFieldById(fieldId);
|
|
398
|
+
if (field && (field.kind === 'text' || field.kind === 'masked')) {
|
|
399
|
+
this.textState.set(fieldId, this.editBuffer);
|
|
400
|
+
this.recalculateDirtyState();
|
|
401
|
+
}
|
|
402
|
+
this.editingFieldId = null;
|
|
403
|
+
this.editBuffer = '';
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
public cancelEdit(): void {
|
|
407
|
+
this.editingFieldId = null;
|
|
408
|
+
this.editBuffer = '';
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
public editChar(char: string): void {
|
|
412
|
+
if (this.editingFieldId === null || char.length === 0) return;
|
|
413
|
+
this.editBuffer += char;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
public editBackspace(): void {
|
|
417
|
+
if (this.editingFieldId === null || this.editBuffer.length === 0) return;
|
|
418
|
+
this.editBuffer = this.editBuffer.slice(0, -1);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public setFieldValue(fieldId: string, value: boolean | string): void {
|
|
422
|
+
const field = this.getFieldById(fieldId);
|
|
423
|
+
if (!field) return;
|
|
424
|
+
|
|
425
|
+
if (field.kind === 'checklist' || field.kind === 'acknowledgement') {
|
|
426
|
+
if (typeof value === 'boolean') {
|
|
427
|
+
if (field.kind === 'checklist' && field.capabilityId) this.setCapabilityValue(field.capabilityId, value);
|
|
428
|
+
else this.toggleState.set(fieldId, value);
|
|
429
|
+
this.recalculateDirtyState();
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (field.kind === 'radio') {
|
|
435
|
+
if (typeof value === 'string' && field.options.some((option) => option.id === value)) {
|
|
436
|
+
this.radioState.set(fieldId, value);
|
|
437
|
+
this.recalculateDirtyState();
|
|
438
|
+
}
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (field.kind === 'text' || field.kind === 'masked') {
|
|
443
|
+
if (typeof value === 'string') {
|
|
444
|
+
this.textState.set(fieldId, value);
|
|
445
|
+
if (this.editingFieldId === fieldId) this.editBuffer = value;
|
|
446
|
+
this.recalculateDirtyState();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public getFieldValue(
|
|
452
|
+
field: OnboardingWizardFieldDefinition,
|
|
453
|
+
): boolean | string | OnboardingWizardModelSelection {
|
|
454
|
+
if (field.kind === 'status') return field.defaultValue;
|
|
455
|
+
|
|
456
|
+
if (field.kind === 'checklist' || field.kind === 'acknowledgement') {
|
|
457
|
+
return this.toggleState.get(field.id) ?? field.defaultValue;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (field.kind === 'radio') {
|
|
461
|
+
return this.radioState.get(field.id) ?? field.defaultValue;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (field.kind === 'text' || field.kind === 'masked') {
|
|
465
|
+
if (this.editingFieldId === field.id) return this.editBuffer;
|
|
466
|
+
return this.textState.get(field.id) ?? field.defaultValue;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (field.kind === 'action') return field.defaultValue;
|
|
470
|
+
|
|
471
|
+
return this.modelSelectionState.get(field.target) ?? field.defaultSelection;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
public getTextFieldValue(fieldId: string, fallback = ''): string {
|
|
475
|
+
return this.getStringFieldValue(fieldId, fallback);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
public getFieldValueLabel(field: OnboardingWizardFieldDefinition): string {
|
|
479
|
+
if (field.kind === 'status') {
|
|
480
|
+
const value = normalizeText(field.defaultValue);
|
|
481
|
+
return value.length > 0 ? value : 'Status';
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (field.kind === 'checklist') {
|
|
485
|
+
return (this.getFieldValue(field) as boolean) ? 'Included' : 'Off';
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (field.kind === 'acknowledgement') {
|
|
489
|
+
const accepted = this.getFieldValue(field) as boolean;
|
|
490
|
+
if (accepted) return 'Accepted';
|
|
491
|
+
return field.required ? 'Pending' : 'Not needed';
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (field.kind === 'radio') {
|
|
495
|
+
const value = this.getFieldValue(field) as string;
|
|
496
|
+
return field.options.find((option) => option.id === value)?.label ?? value;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (field.kind === 'text') {
|
|
500
|
+
const value = normalizeText(this.getFieldValue(field) as string);
|
|
501
|
+
return value.length > 0 ? value : field.placeholder;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (field.kind === 'masked') {
|
|
505
|
+
const value = normalizeText(this.getFieldValue(field) as string);
|
|
506
|
+
return value.length > 0 ? maskValue(value) : field.placeholder;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (field.kind === 'action') return field.defaultValue;
|
|
510
|
+
|
|
511
|
+
return modelSelectionLabel(this.getFieldValue(field) as OnboardingWizardModelSelection);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
public applyModelSelection(
|
|
515
|
+
target: ModelPickerTarget,
|
|
516
|
+
selection: Pick<OnboardingWizardModelSelection, 'providerId' | 'modelId'> & { enabled?: boolean },
|
|
517
|
+
): void {
|
|
518
|
+
this.modelSelectionState.set(target, {
|
|
519
|
+
providerId: selection.providerId,
|
|
520
|
+
modelId: selection.modelId,
|
|
521
|
+
enabled: selection.enabled ?? true,
|
|
522
|
+
});
|
|
523
|
+
this.pendingModelPickerTarget = null;
|
|
524
|
+
this.recalculateDirtyState();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
public buildApplyRequest(): OnboardingApplyRequest {
|
|
528
|
+
return buildOnboardingApplyRequest(this);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
public markApplied(): void {
|
|
532
|
+
this.baselineToggleState.clear();
|
|
533
|
+
for (const [key, value] of this.toggleState) this.baselineToggleState.set(key, value);
|
|
534
|
+
this.baselineRadioState.clear();
|
|
535
|
+
for (const [key, value] of this.radioState) this.baselineRadioState.set(key, value);
|
|
536
|
+
this.baselineTextState.clear();
|
|
537
|
+
for (const [key, value] of this.textState) this.baselineTextState.set(key, value);
|
|
538
|
+
this.baselineModelSelectionState.clear();
|
|
539
|
+
for (const [key, value] of this.modelSelectionState) this.baselineModelSelectionState.set(key, cloneSelection(value));
|
|
540
|
+
this.dirtyStepIds.clear();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
public getSharedIpDefault(enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean }): boolean {
|
|
544
|
+
return getSharedIpDefaultForController(this, enabled);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
public getSharedIpHostDefault(enabled: { readonly controlPlane: boolean; readonly httpListener: boolean; readonly web: boolean }): string {
|
|
548
|
+
return getSharedIpHostDefaultForController(this, enabled);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
public defaultReviewUserMarker(): boolean { return defaultReviewUserMarkerForController(this); }
|
|
552
|
+
public toggleCapability(capabilityId: OnboardingStep1CapabilityId): void { toggleCapabilityForController(this, capabilityId); }
|
|
553
|
+
public selectAllServerCapabilities(): void { selectAllServerCapabilitiesForController(this); }
|
|
554
|
+
public selectLocalTuiOnly(): void { selectLocalTuiOnlyForController(this); }
|
|
555
|
+
public setCapabilityValue(capabilityId: OnboardingStep1CapabilityId, selected: boolean): void { setCapabilityValueForController(this, capabilityId, selected); }
|
|
556
|
+
public isCapabilitySelected(capabilityId: OnboardingStep1CapabilityId): boolean { return isCapabilitySelectedForController(this, capabilityId); }
|
|
557
|
+
public hasServerCapabilitiesSelected(): boolean { return hasServerCapabilitiesSelectedForController(this); }
|
|
558
|
+
public shouldEnableBrowserSurface(): boolean { return shouldEnableBrowserSurfaceForController(this); }
|
|
559
|
+
public hasSelectedInboundExternalSurface(): boolean { return hasSelectedInboundExternalSurfaceForController(this); }
|
|
560
|
+
public isRequiredExternalSetupField(fieldId: string): boolean { return isRequiredExternalSetupFieldForController(this, fieldId); }
|
|
561
|
+
public getSelectedSecretMedium(): 'secure' | 'plaintext' { return getSelectedSecretMediumForController(this); }
|
|
562
|
+
public shouldEnableHttpListener(): boolean { return shouldEnableHttpListenerForController(this); }
|
|
563
|
+
public shouldExposeHttpListenerNetworkFields(): boolean { return shouldExposeHttpListenerNetworkFieldsForController(this); }
|
|
564
|
+
public shouldExposeControlPlaneNetwork(): boolean { return shouldExposeControlPlaneNetworkForController(this); }
|
|
565
|
+
public requiresAuthBootstrap(): boolean { return requiresAuthBootstrapForController(this); }
|
|
566
|
+
public hasAdminAuthUser(): boolean { return hasAdminAuthUserForController(this); }
|
|
567
|
+
public hasBootstrapCredentialPresent(): boolean { return hasBootstrapCredentialPresentForController(this); }
|
|
568
|
+
public getDefaultAdminUsername(): string { return getDefaultAdminUsernameForController(this); }
|
|
569
|
+
public getBooleanFieldValue(fieldId: string, fallback: boolean): boolean { return getBooleanFieldValueForController(this, fieldId, fallback); }
|
|
570
|
+
public getStringFieldValue(fieldId: string, fallback: string): string { return getStringFieldValueForController(this, fieldId, fallback); }
|
|
571
|
+
public parseIntegerFieldValue(fieldId: string, fallback: number): number | null { return parseIntegerFieldValueForController(this, fieldId, fallback); }
|
|
572
|
+
public getPortFieldValue(fieldId: string, fallback: number): number { return getPortFieldValueForController(this, fieldId, fallback); }
|
|
573
|
+
public getNumberFieldValue(fieldId: string, fallback: number, min?: number, max?: number): number { return getNumberFieldValueForController(this, fieldId, fallback, min, max); }
|
|
574
|
+
|
|
575
|
+
public getToggleFieldCount(stepIndex: number): number { return getToggleFieldCountForController(this, stepIndex); }
|
|
576
|
+
public getCompletedToggleCount(stepIndex: number): number { return getCompletedToggleCountForController(this, stepIndex); }
|
|
577
|
+
public getStepFieldCount(stepIndex: number): number { return getStepFieldCountForController(this, stepIndex); }
|
|
578
|
+
public getCompletedFieldCount(stepIndex: number): number { return getCompletedFieldCountForController(this, stepIndex); }
|
|
579
|
+
public isStepDirty(stepIndex: number): boolean { return isStepDirtyForController(this, stepIndex); }
|
|
580
|
+
public isFieldDirty(fieldId: string): boolean { return isFieldDirtyForController(this, fieldId); }
|
|
581
|
+
public getBlockingFieldLabels(): readonly string[] { return getBlockingFieldLabelsForController(this); }
|
|
582
|
+
public getFieldValidationError(step: OnboardingWizardStepDefinition, field: OnboardingWizardFieldDefinition): string | null { return getFieldValidationErrorForController(this, step, field); }
|
|
583
|
+
public getFieldById(fieldId: string): OnboardingWizardFieldDefinition | null { return getFieldByIdForController(this, fieldId); }
|
|
584
|
+
public ensureSelectionVisible(visibleFields: number): void { ensureSelectionVisibleForController(this, visibleFields); }
|
|
585
|
+
public reconcileStepCursor(stepIndex: number): void { reconcileStepCursorForController(this, stepIndex); }
|
|
586
|
+
public resetValuesFromCurrentDefinitions(): void { resetValuesFromCurrentDefinitionsForController(this); }
|
|
587
|
+
public reconcileStateWithCurrentDefinitions(): void { reconcileStateWithCurrentDefinitionsForController(this); }
|
|
588
|
+
public recalculateDirtyState(): void { recalculateDirtyStateForController(this); }
|
|
589
|
+
public isFieldDirtyByDefinition(field: OnboardingWizardFieldDefinition): boolean { return isFieldDirtyByDefinitionForController(this, field); }
|
|
590
|
+
public isFieldSatisfied(field: OnboardingWizardFieldDefinition): boolean { return isFieldSatisfiedForController(this, field); }
|
|
591
|
+
public getCurrentCapabilities(): readonly OnboardingStep1CapabilityItem[] { return getCurrentCapabilitiesForController(this); }
|
|
592
|
+
public getCapabilitySelectionState(): readonly OnboardingStep1CapabilityItem[] { return getCapabilitySelectionStateForController(this); }
|
|
593
|
+
public hasExistingAccessState(): boolean { return hasExistingAccessStateForController(this); }
|
|
594
|
+
}
|