@@ -48,6 +48,7 @@ import {
4848 type DecentralizeOutcome ,
4949 type DecentralizeSource ,
5050} from "../../utils/decentralize/run.js" ;
51+ import { ModdableErrorStage , ModdablePreflightStage } from "../deploy/ModdableStages.js" ;
5152import {
5253 pickNextStage ,
5354 validateDomainInput ,
@@ -82,6 +83,13 @@ export interface DecentralizeScreenProps {
8283 * publish prompt is shown.
8384 */
8485 initialPublishToPlayground : boolean | null ;
86+ /**
87+ * Pre-set when `--moddable` was passed on the CLI: skips the moddable
88+ * prompt and drives straight into the git-origin preflight (only if the
89+ * user ends up in the path + publish flow — URL mode ignores it). `null`
90+ * means the prompt is shown.
91+ */
92+ initialModdable : boolean | null ;
8593 onDone : ( result : DecentralizeResult ) => void ;
8694}
8795
@@ -92,6 +100,7 @@ export function DecentralizeScreen({
92100 explicitSigner,
93101 sessionSigner,
94102 initialPublishToPlayground,
103+ initialModdable,
95104 onDone,
96105} : DecentralizeScreenProps ) {
97106 // A caller-provided site URL pre-selects the URL flow (vestigial today —
@@ -114,6 +123,8 @@ export function DecentralizeScreen({
114123 const [ publishToPlayground , setPublishToPlayground ] = useState < boolean | null > (
115124 initialPublishToPlayground ,
116125 ) ;
126+ const [ moddable , setModdable ] = useState < boolean | null > ( initialModdable ) ;
127+ const [ repositoryUrl , setRepositoryUrl ] = useState < string | null > ( null ) ;
117128
118129 const [ stage , setStage ] = useState < Stage > ( ( ) =>
119130 pickNextStage ( {
@@ -124,6 +135,8 @@ export function DecentralizeScreen({
124135 domainLabel : null ,
125136 domainRaw : initialDot ,
126137 publishToPlayground : initialPublishToPlayground ,
138+ moddable : initialModdable ,
139+ repositoryUrl : null ,
127140 } ) ,
128141 ) ;
129142
@@ -136,6 +149,8 @@ export function DecentralizeScreen({
136149 domainLabel : string | null ;
137150 domainRaw : string | null ;
138151 publishToPlayground : boolean | null ;
152+ moddable : boolean | null ;
153+ repositoryUrl : string | null ;
139154 } > = { } ,
140155 ) => {
141156 setStage (
@@ -150,10 +165,21 @@ export function DecentralizeScreen({
150165 next . publishToPlayground !== undefined
151166 ? next . publishToPlayground
152167 : publishToPlayground ,
168+ moddable : next . moddable !== undefined ? next . moddable : moddable ,
169+ repositoryUrl :
170+ next . repositoryUrl !== undefined ? next . repositoryUrl : repositoryUrl ,
153171 } ) ,
154172 ) ;
155173 } ;
156174
175+ // Single "user declined moddable" transition, shared by the remix prompt's
176+ // "no" answer and the setup-error menu's "continue without moddable", so
177+ // the two paths can't drift apart. Mirrors deploy's helper of the same name.
178+ const declineModdable = ( ) => {
179+ setModdable ( false ) ;
180+ advance ( { moddable : false } ) ;
181+ } ;
182+
157183 // Compose the active signer for downstream stages. Memoised so the
158184 // ResolvedSigner identity stays stable across re-renders (the dev branch
159185 // would otherwise produce a fresh `createDevPublishSigner()` instance on
@@ -316,24 +342,82 @@ export function DecentralizeScreen({
316342 ) }
317343
318344 { stage . kind === "prompt-publish" && (
345+ < >
346+ { sourceKind === "path" && (
347+ < Callout tone = "accent" title = "Your app detail page" >
348+ < Text >
349+ If you publish, the README.md in your directory becomes your app's
350+ detail page on the playground. Make sure it's up to date.
351+ </ Text >
352+ </ Callout >
353+ ) }
354+ < Select < boolean >
355+ label = "publish to the playground registry?"
356+ options = { [
357+ {
358+ value : false ,
359+ label : "no" ,
360+ hint : "just register the .dot name (DotNS only)" ,
361+ } ,
362+ {
363+ value : true ,
364+ label : "yes" ,
365+ hint : "list the site in the playground apps tab" ,
366+ } ,
367+ ] }
368+ onSelect = { ( choice ) => {
369+ setPublishToPlayground ( choice ) ;
370+ advance ( { publishToPlayground : choice } ) ;
371+ } }
372+ />
373+ </ >
374+ ) }
375+
376+ { stage . kind === "prompt-moddable" && (
319377 < Select < boolean >
320- label = "publish to the playground registry ?"
378+ label = "let others remix (mod) this app ?"
321379 options = { [
322- {
323- value : false ,
324- label : "no" ,
325- hint : "just register the .dot name (DotNS only)" ,
326- } ,
327380 {
328381 value : true ,
329382 label : "yes" ,
330- hint : "list the mirrored site in the playground apps tab" ,
383+ hint : "record my public GitHub repo so others can `playground mod` it" ,
384+ } ,
385+ {
386+ value : false ,
387+ label : "no" ,
388+ hint : "keep my source private" ,
331389 } ,
332390 ] }
333- onSelect = { ( choice ) => {
334- setPublishToPlayground ( choice ) ;
335- advance ( { publishToPlayground : choice } ) ;
391+ initialIndex = { 0 }
392+ onSelect = { ( yes ) => {
393+ if ( yes ) {
394+ setModdable ( true ) ;
395+ setStage ( { kind : "moddable-preflight" } ) ;
396+ } else {
397+ declineModdable ( ) ;
398+ }
399+ } }
400+ />
401+ ) }
402+
403+ { stage . kind === "moddable-preflight" && (
404+ < ModdablePreflightStage
405+ // git resolves `origin` from any subdirectory of the repo,
406+ // so the typed path works even when it's a build output dir.
407+ projectDir = { localPath ! }
408+ onResolved = { ( url ) => {
409+ setRepositoryUrl ( url ) ;
410+ advance ( { moddable : true , repositoryUrl : url } ) ;
336411 } }
412+ onError = { ( msg ) => setStage ( { kind : "moddable-error" , message : msg } ) }
413+ />
414+ ) }
415+
416+ { stage . kind === "moddable-error" && (
417+ < ModdableErrorStage
418+ message = { stage . message }
419+ onContinueWithoutModdable = { declineModdable }
420+ onExit = { ( ) => onDone ( { kind : "cancel" } ) }
337421 />
338422 ) }
339423
@@ -347,6 +431,7 @@ export function DecentralizeScreen({
347431 signer = { activeSigner ! }
348432 signerMode = { signerMode ! }
349433 publishToPlayground = { publishToPlayground === true }
434+ repositoryUrl = { repositoryUrl }
350435 onConfirm = { ( ) => setStage ( { kind : "running" } ) }
351436 onCancel = { ( ) => onDone ( { kind : "cancel" } ) }
352437 />
@@ -360,6 +445,7 @@ export function DecentralizeScreen({
360445 mode = { signerMode ! }
361446 userSigner = { explicitSigner ?? sessionSigner }
362447 publishToPlayground = { publishToPlayground === true }
448+ repositoryUrl = { repositoryUrl }
363449 env = { env }
364450 onComplete = { ( outcome ) => setStage ( { kind : "done" , outcome } ) }
365451 onFailed = { ( message ) => setStage ( { kind : "error" , message } ) }
@@ -473,6 +559,7 @@ function ConfirmStage({
473559 signer,
474560 signerMode,
475561 publishToPlayground,
562+ repositoryUrl,
476563 onConfirm,
477564 onCancel,
478565} : {
@@ -485,6 +572,8 @@ function ConfirmStage({
485572 signer : ResolvedSigner ;
486573 signerMode : SignerMode ;
487574 publishToPlayground : boolean ;
575+ /** Resolved public GitHub URL when moddable was accepted; null otherwise. */
576+ repositoryUrl : string | null ;
488577 onConfirm : ( ) => void ;
489578 onCancel : ( ) => void ;
490579} ) {
@@ -524,6 +613,13 @@ function ConfirmStage({
524613 value = { publishToPlayground ? "publish to apps tab" : "skip" }
525614 tone = { publishToPlayground ? "accent" : "muted" }
526615 />
616+ { source . kind === "path" && publishToPlayground && (
617+ < Row
618+ label = "moddable"
619+ value = { repositoryUrl ? `yes · ${ repositoryUrl } ` : "no" }
620+ tone = { repositoryUrl ? "accent" : "muted" }
621+ />
622+ ) }
527623 { availabilityNote && < Row label = "note" value = { availabilityNote } tone = "warning" /> }
528624 { largeLocal && (
529625 < Row
@@ -572,6 +668,7 @@ function RunningStage({
572668 mode,
573669 userSigner,
574670 publishToPlayground,
671+ repositoryUrl,
575672 env,
576673 onComplete,
577674 onFailed,
@@ -582,6 +679,8 @@ function RunningStage({
582679 mode : SignerMode ;
583680 userSigner : ResolvedSigner | null ;
584681 publishToPlayground : boolean ;
682+ /** Preflighted public GitHub URL when moddable was accepted; null otherwise. */
683+ repositoryUrl : string | null ;
585684 env : Env ;
586685 onComplete : ( outcome : DecentralizeOutcome ) => void ;
587686 onFailed : ( message : string ) => void ;
@@ -627,6 +726,7 @@ function RunningStage({
627726 mode,
628727 userSigner,
629728 publishToPlayground,
729+ repositoryUrl,
630730 env,
631731 onEvent : ( event ) => {
632732 switch ( event . kind ) {
0 commit comments