@@ -55,6 +55,18 @@ type AdvanceCreateFlowOptions = {
5555 */
5656export type CreateSettingsNavigationDirection = "up" | "down"
5757
58+ /**
59+ * Horizontal choice direction over finite Create settings with discrete values.
60+ *
61+ * @pure true
62+ * @effect none
63+ * @invariant value ∈ {"left", "right"}
64+ * @precondition n/a
65+ * @postcondition direction maps only to an input-buffer token, never to applied Create values
66+ * @complexity O(1)
67+ */
68+ export type CreateSettingsChoiceDirection = "left" | "right"
69+
5870/**
5971 * User-facing key guide shown only after Create leaves the repo URL step.
6072 *
@@ -117,6 +129,81 @@ export const renderCreateStepLabel = (step: CreateStep, defaults: CreateInputs):
117129 Match . exhaustive
118130 )
119131
132+ const renderExplicitBooleanChoice = ( value : boolean ) : string => value ? "Y" : "N"
133+
134+ const parseExplicitBooleanChoice = ( input : string ) : boolean | null => {
135+ const normalized = input . trim ( ) . toLowerCase ( )
136+ if ( normalized === "y" || normalized === "yes" ) {
137+ return true
138+ }
139+ if ( normalized === "n" || normalized === "no" ) {
140+ return false
141+ }
142+ return null
143+ }
144+
145+ const parseExplicitGpuChoice = (
146+ input : string
147+ ) : GpuMode | null => {
148+ const normalized = input . trim ( ) . toLowerCase ( )
149+ if ( normalized === "y" || normalized === "yes" ) {
150+ return "all"
151+ }
152+ if ( normalized === "n" || normalized === "no" ) {
153+ return "none"
154+ }
155+ if ( isGpuMode ( normalized ) ) {
156+ return normalized
157+ }
158+ return null
159+ }
160+
161+ /**
162+ * Renders the active Create settings label with an unapplied input-buffer preview.
163+ *
164+ * @pure true
165+ * @effect none
166+ * @invariant invalid or empty preview buffers preserve the committed/default label
167+ * @precondition defaults are resolved Create inputs
168+ * @postcondition Create values are not mutated or applied by rendering
169+ * @complexity O(1)
170+ */
171+ export const renderCreateStepLabelWithBufferPreview = (
172+ step : CreateStep ,
173+ defaults : CreateInputs ,
174+ buffer : string
175+ ) : string =>
176+ Match . value ( step ) . pipe (
177+ Match . when ( "repoUrl" , ( ) => renderCreateStepLabel ( step , defaults ) ) ,
178+ Match . when ( "repoRef" , ( ) => renderCreateStepLabel ( step , defaults ) ) ,
179+ Match . when ( "outDir" , ( ) => renderCreateStepLabel ( step , defaults ) ) ,
180+ Match . when ( "cpuLimit" , ( ) => renderCreateStepLabel ( step , defaults ) ) ,
181+ Match . when ( "ramLimit" , ( ) => renderCreateStepLabel ( step , defaults ) ) ,
182+ Match . when ( "gpu" , ( ) => {
183+ const gpu = parseExplicitGpuChoice ( buffer )
184+ return gpu === null ? renderCreateStepLabel ( step , defaults ) : `GPU access [${ gpu } ]`
185+ } ) ,
186+ Match . when ( "runUp" , ( ) => {
187+ const runUp = parseExplicitBooleanChoice ( buffer )
188+ return runUp === null
189+ ? renderCreateStepLabel ( step , defaults )
190+ : `Run docker compose up now? [${ renderExplicitBooleanChoice ( runUp ) } ]`
191+ } ) ,
192+ Match . when ( "mcpPlaywright" , ( ) => {
193+ const enableMcpPlaywright = parseExplicitBooleanChoice ( buffer )
194+ return enableMcpPlaywright === null
195+ ? renderCreateStepLabel ( step , defaults )
196+ : `Enable Playwright MCP (nested Chromium browser)? [${ renderExplicitBooleanChoice ( enableMcpPlaywright ) } ]`
197+ } ) ,
198+ Match . when ( "force" , ( ) => {
199+ const force = parseExplicitBooleanChoice ( buffer )
200+ return force === null
201+ ? renderCreateStepLabel ( step , defaults )
202+ : `Force recreate (overwrite files + wipe volumes)? [${ renderExplicitBooleanChoice ( force ) } ]`
203+ } ) ,
204+ Match . exhaustive
205+ )
206+
120207const normalizeCreateFlowContext = (
121208 context : string | CreateFlowContext
122209) : CreateFlowContext =>
@@ -405,6 +492,20 @@ export const resolveCreateFlowSteps = (
405492 . filter ( ( step ) => ! isCreateStepSatisfied ( step , values ) )
406493]
407494
495+ /**
496+ * Resolves the stable Create display rows used by browser Settings mode.
497+ *
498+ * @pure true
499+ * @effect none
500+ * @invariant result = createSteps and is independent of applied values
501+ * @precondition n/a
502+ * @postcondition applied settings rows remain present in the result
503+ * @complexity O(1)
504+ */
505+ export const resolveCreateDisplaySteps = (
506+ _values : Partial < CreateInputs > = { }
507+ ) : ReadonlyArray < CreateStep > => createSteps
508+
408509const applyCreateStep = ( input : {
409510 readonly step : CreateStep
410511 readonly buffer : string
@@ -504,6 +605,54 @@ const nextCreateSettingsStep = (
504605 Match . exhaustive
505606 )
506607
608+ const booleanChoiceBuffer = ( direction : CreateSettingsChoiceDirection ) : string =>
609+ Match . value ( direction ) . pipe (
610+ Match . when ( "left" , ( ) => "n" ) ,
611+ Match . when ( "right" , ( ) => "y" ) ,
612+ Match . exhaustive
613+ )
614+
615+ const gpuChoiceBuffer = ( direction : CreateSettingsChoiceDirection ) : string =>
616+ Match . value ( direction ) . pipe (
617+ Match . when ( "left" , ( ) => "none" ) ,
618+ Match . when ( "right" , ( ) => "all" ) ,
619+ Match . exhaustive
620+ )
621+
622+ /**
623+ * Resolves a horizontal settings choice to the Create input buffer without applying it.
624+ *
625+ * @pure true
626+ * @effect none
627+ * @invariant result = null for free-text Create rows
628+ * @invariant result != null -> view.values are unchanged by caller-visible semantics
629+ * @precondition view is a CreateFlowView snapshot
630+ * @postcondition result ∈ {"none", "all", "n", "y"} ∪ {null}
631+ * @complexity O(1)
632+ */
633+ export const resolveCreateSettingsChoiceBuffer = (
634+ view : CreateFlowView ,
635+ direction : CreateSettingsChoiceDirection
636+ ) : string | null => {
637+ const step = resolveCreateDisplaySteps ( ) [ view . step ]
638+ if ( step === undefined ) {
639+ return null
640+ }
641+
642+ return Match . value ( step ) . pipe (
643+ Match . when ( "repoUrl" , ( ) => null ) ,
644+ Match . when ( "repoRef" , ( ) => null ) ,
645+ Match . when ( "outDir" , ( ) => null ) ,
646+ Match . when ( "cpuLimit" , ( ) => null ) ,
647+ Match . when ( "ramLimit" , ( ) => null ) ,
648+ Match . when ( "gpu" , ( ) => gpuChoiceBuffer ( direction ) ) ,
649+ Match . when ( "runUp" , ( ) => booleanChoiceBuffer ( direction ) ) ,
650+ Match . when ( "mcpPlaywright" , ( ) => booleanChoiceBuffer ( direction ) ) ,
651+ Match . when ( "force" , ( ) => booleanChoiceBuffer ( direction ) ) ,
652+ Match . exhaustive
653+ )
654+ }
655+
507656/**
508657 * Moves the selected Create settings row without applying the current buffer.
509658 *
@@ -538,6 +687,122 @@ export const moveCreateSettingsStep = (
538687 }
539688}
540689
690+ /**
691+ * Moves the selected browser Create settings row over the full display list.
692+ *
693+ * @pure true
694+ * @effect none
695+ * @invariant applied rows do not affect navigation order
696+ * @invariant view.step = 0 -> result = null
697+ * @invariant result != null -> 1 <= result.step < |resolveCreateDisplaySteps()|
698+ * @precondition view is a CreateFlowView snapshot
699+ * @postcondition result values are identical to input values
700+ * @complexity O(1)
701+ */
702+ export const moveCreateDisplaySettingsStep = (
703+ view : CreateFlowView ,
704+ direction : CreateSettingsNavigationDirection
705+ ) : CreateFlowView | null => {
706+ const steps = resolveCreateDisplaySteps ( )
707+ const lastStep = steps . length - 1
708+ if ( view . step < firstCreateSettingsStepIndex || lastStep < firstCreateSettingsStepIndex ) {
709+ return null
710+ }
711+
712+ const currentStep = clampCreateSettingsStep ( view . step , lastStep )
713+ const step = nextCreateSettingsStep ( currentStep , lastStep , direction )
714+ if ( step === view . step ) {
715+ return view
716+ }
717+ return {
718+ ...view ,
719+ step,
720+ buffer : ""
721+ }
722+ }
723+
724+ /**
725+ * Applies one browser Create settings display row without advancing or submitting.
726+ *
727+ * @pure true
728+ * @effect none
729+ * @invariant result._tag = "Continue" -> result.view.step = view.step
730+ * @invariant result._tag = "Continue" -> result.view.buffer = ""
731+ * @precondition view.step points at a settings display row
732+ * @postcondition successful result stores the parsed setting in result.view.values
733+ * @complexity O(1)
734+ */
735+ export const applyCreateDisplaySettingsStep = (
736+ contextOrCwd : string | CreateFlowContext ,
737+ view : CreateFlowView
738+ ) : AdvanceCreateFlowResult | null => {
739+ const step = resolveCreateDisplaySteps ( ) [ view . step ]
740+ if ( view . step < firstCreateSettingsStepIndex || step === undefined ) {
741+ return null
742+ }
743+
744+ const context = normalizeCreateFlowContext ( contextOrCwd )
745+ const buffer = view . buffer . trim ( )
746+ const currentDefaults = resolveCreateInputs ( context , view . values )
747+ const nextValues : Partial < Mutable < CreateInputs > > = { ...view . values }
748+ const updated = applyCreateStep ( {
749+ step,
750+ buffer,
751+ currentDefaults,
752+ nextValues,
753+ context
754+ } )
755+ if ( Either . isLeft ( updated ) ) {
756+ return {
757+ _tag : "Error" ,
758+ error : updated . left
759+ }
760+ }
761+
762+ return continueCreateFlow ( view . step , nextValues )
763+ }
764+
765+ /**
766+ * Completes browser Create settings by applying a non-empty active buffer first.
767+ *
768+ * @pure true
769+ * @effect none
770+ * @invariant non-empty invalid buffer -> result._tag = "Error"
771+ * @invariant successful result._tag = "Complete"
772+ * @precondition view.step points at a settings display row
773+ * @postcondition submitted inputs include all committed values and defaults
774+ * @complexity O(1)
775+ */
776+ export const completeCreateDisplaySettingsFlow = (
777+ contextOrCwd : string | CreateFlowContext ,
778+ view : CreateFlowView
779+ ) : AdvanceCreateFlowResult | null => {
780+ const step = resolveCreateDisplaySteps ( ) [ view . step ]
781+ if ( view . step < firstCreateSettingsStepIndex || step === undefined ) {
782+ return null
783+ }
784+
785+ const context = normalizeCreateFlowContext ( contextOrCwd )
786+ if ( view . buffer . trim ( ) . length === 0 ) {
787+ return {
788+ _tag : "Complete" ,
789+ inputs : resolveCreateInputs ( context , view . values )
790+ }
791+ }
792+
793+ const applied = applyCreateDisplaySettingsStep ( context , view )
794+ if ( applied === null || applied . _tag === "Error" ) {
795+ return applied
796+ }
797+ if ( applied . _tag === "Continue" ) {
798+ return {
799+ _tag : "Complete" ,
800+ inputs : resolveCreateInputs ( context , applied . view . values )
801+ }
802+ }
803+ return applied
804+ }
805+
541806const resolveNextCreateFlowStep = (
542807 currentStep : CreateStep ,
543808 currentStepIndex : number ,
0 commit comments