Skip to content

Commit 31ce61a

Browse files
Merge pull request #377 from paritytech/feat/signer-prompt-and-decentralize-sync
feat: sync decentralize prompts with deploy and clarify update banner
2 parents d7c257f + ebc64b5 commit 31ce61a

18 files changed

Lines changed: 486 additions & 115 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
Sync the `playground decentralize` prompts with `playground deploy`:
6+
7+
- The signer pickers in both commands list your phone signer first and start the cursor on it (when logged in). The "dev signer earns no XP" warning now appears below the options, only while the dev signer is highlighted, and disappears when you move back to the phone signer.
8+
- `playground decentralize` now shows the same magenta help boxes as deploy above its signer, domain, publish, and (new) tags prompts, and its intro box uses the same accent style.
9+
- The "publish to the playground registry?" prompt now lists "yes" first and selects it by default.
10+
- `playground decentralize` gained a category-tag step and a `--tag` flag (whitelisted to the playground tags, requires `--playground`), matching deploy; the chosen tag is written to the published app metadata and shown in the confirm summary.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
Clarify the "update available" banner: it now reads `Run pg update to update the CLI.` (previously "playground update to upgrade") so the suggested command matches the wording.

src/commands/decentralize/DecentralizeScreen.tsx

Lines changed: 131 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,24 @@ import {
2727
Hint,
2828
Input,
2929
type MarkKind,
30+
PromptInfo,
3031
Row,
3132
Section,
3233
Select,
33-
type SelectOption,
3434
} from "../../utils/ui/theme/index.js";
3535
import { PhoneApprovalCallout } from "../../utils/ui/theme/PhoneApprovalCallout.js";
3636
import { getNetworkLabel, type Env } from "../../config.js";
3737
import { VERSION_LABEL } from "../../utils/version.js";
3838
import type { ResolvedSigner } from "../../utils/signer.js";
3939
import { createDevPublishSigner, type SignerMode } from "../../utils/deploy/signerMode.js";
40+
import {
41+
DEV_SIGNER_NO_XP_TITLE,
42+
DEV_SIGNER_NO_XP_BODY,
43+
shouldShowDevNoXpWarning,
44+
} from "../deploy/signerNotice.js";
45+
import { SIGNER_HELP, DOMAIN_HELP, PUBLISH_HELP, TAGS_HELP } from "../deploy/promptHelp.js";
46+
import { PLAYGROUND_TAGS } from "../../utils/deploy/tags.js";
47+
import { decentralizeSignerOptions, decentralizeSignerInitialIndex } from "./signerPrompt.js";
4048
import type { SigningEvent } from "../../utils/deploy/signingProxy.js";
4149
import { resolveDomain } from "../../utils/decentralize/domain.js";
4250
import { FREE_DOMAIN_SUFFIX_NOTE } from "../../utils/decentralize/randomName.js";
@@ -72,6 +80,11 @@ export interface DecentralizeScreenProps {
7280
* publish prompt is shown.
7381
*/
7482
initialPublishToPlayground: boolean | null;
83+
/**
84+
* Pre-set to the `--tag` value when passed (skips the tag prompt).
85+
* `undefined` means the tag prompt is shown when publishing.
86+
*/
87+
initialTag?: string;
7588
onDone: (result: DecentralizeResult) => void;
7689
}
7790

@@ -82,11 +95,18 @@ export function DecentralizeScreen({
8295
explicitSigner,
8396
sessionSigner,
8497
initialPublishToPlayground,
98+
initialTag,
8599
onDone,
86100
}: DecentralizeScreenProps) {
87101
const [siteUrl, setSiteUrl] = useState<string | null>(initialSiteUrl);
88102
// If --suri was passed, the user has effectively pre-chosen dev.
89103
const [signerMode, setSignerMode] = useState<SignerMode | null>(explicitSigner ? "dev" : null);
104+
// Which signer option the cursor is on in the prompt-signer Select. Drives
105+
// the "dev signer earns no XP" warning (shown only while dev is highlighted
106+
// and a session exists). Initialised to match the default cursor position.
107+
const [highlightedSigner, setHighlightedSigner] = useState<SignerMode>(
108+
sessionSigner ? "phone" : "dev",
109+
);
90110
const [domainRaw, setDomainRaw] = useState<string | null>(initialDot);
91111
const [domainLabel, setDomainLabel] = useState<string | null>(null);
92112
const [fullDomain, setFullDomain] = useState<string | null>(null);
@@ -97,6 +117,9 @@ export function DecentralizeScreen({
97117
const [publishToPlayground, setPublishToPlayground] = useState<boolean | null>(
98118
initialPublishToPlayground,
99119
);
120+
// Category tag (tri-state, mirroring deploy): `undefined` = not asked yet,
121+
// `null` = explicitly skipped, a string = chosen. Pre-filled from `--tag`.
122+
const [tag, setTag] = useState<string | null | undefined>(initialTag);
100123

101124
const [stage, setStage] = useState<Stage>(() =>
102125
pickNextStage({
@@ -105,6 +128,7 @@ export function DecentralizeScreen({
105128
domainLabel: null,
106129
domainRaw: initialDot,
107130
publishToPlayground: initialPublishToPlayground,
131+
tag: initialTag,
108132
}),
109133
);
110134

@@ -115,6 +139,7 @@ export function DecentralizeScreen({
115139
domainLabel: string | null;
116140
domainRaw: string | null;
117141
publishToPlayground: boolean | null;
142+
tag: string | null;
118143
}> = {},
119144
) => {
120145
setStage(
@@ -127,6 +152,9 @@ export function DecentralizeScreen({
127152
next.publishToPlayground !== undefined
128153
? next.publishToPlayground
129154
: publishToPlayground,
155+
// `undefined` is a meaningful tag value ("not asked"), so detect
156+
// presence with `in` rather than the `!== undefined` sentinel.
157+
tag: "tag" in next ? next.tag : tag,
130158
}),
131159
);
132160
};
@@ -154,7 +182,7 @@ export function DecentralizeScreen({
154182

155183
{stage.kind === "prompt-url" && (
156184
<>
157-
<Callout tone="warning" title="About This Command">
185+
<Callout tone="accent" title="About This Command">
158186
<Text>
159187
Mirrors a live static site (https URL) and republishes it as a .dot
160188
site. Large sites can take several minutes to download — press Ctrl+C
@@ -174,37 +202,54 @@ export function DecentralizeScreen({
174202
)}
175203

176204
{stage.kind === "prompt-signer" && (
177-
<Select<SignerMode>
178-
label="signer"
179-
options={signerOptions(sessionSigner)}
180-
onSelect={(mode) => {
181-
if (mode === "phone" && !sessionSigner) {
182-
setStage({
183-
kind: "error",
184-
message:
185-
'No session found — run "playground login" to log in, then re-run, or pick the dev signer.',
186-
});
187-
return;
188-
}
189-
setSignerMode(mode);
190-
advance({ signerMode: mode });
191-
}}
192-
/>
205+
<Box flexDirection="column">
206+
<PromptInfo box={SIGNER_HELP} />
207+
<Select<SignerMode>
208+
label="signer"
209+
options={decentralizeSignerOptions(sessionSigner != null)}
210+
initialIndex={decentralizeSignerInitialIndex(sessionSigner != null)}
211+
onHighlight={setHighlightedSigner}
212+
onSelect={(mode) => {
213+
if (mode === "phone" && !sessionSigner) {
214+
setStage({
215+
kind: "error",
216+
message:
217+
'No session found — run "playground login" to log in, then re-run, or pick the dev signer.',
218+
});
219+
return;
220+
}
221+
setSignerMode(mode);
222+
advance({ signerMode: mode });
223+
}}
224+
/>
225+
{/* Below the options (mirroring the phone-approval notices)
226+
and only while the dev option is highlighted with a
227+
session present, so the "no XP" trade-off shows exactly
228+
when the user is about to pick the dev signer. */}
229+
{shouldShowDevNoXpWarning(sessionSigner != null, highlightedSigner) && (
230+
<Callout tone="warning" title={DEV_SIGNER_NO_XP_TITLE}>
231+
<Text>{DEV_SIGNER_NO_XP_BODY}</Text>
232+
</Callout>
233+
)}
234+
</Box>
193235
)}
194236

195237
{stage.kind === "prompt-domain" && (
196-
<Input
197-
label="domain"
198-
placeholder="leave blank to auto-generate from the URL"
199-
prefill={domainRaw ?? ""}
200-
externalError={domainError}
201-
validate={validateDomainInput}
202-
onSubmit={(value) => {
203-
setDomainError(null);
204-
setDomainRaw(value);
205-
advance({ domainRaw: value });
206-
}}
207-
/>
238+
<Box flexDirection="column">
239+
<PromptInfo box={DOMAIN_HELP} />
240+
<Input
241+
label="domain"
242+
placeholder="leave blank to auto-generate from the URL"
243+
prefill={domainRaw ?? ""}
244+
externalError={domainError}
245+
validate={validateDomainInput}
246+
onSubmit={(value) => {
247+
setDomainError(null);
248+
setDomainRaw(value);
249+
advance({ domainRaw: value });
250+
}}
251+
/>
252+
</Box>
208253
)}
209254

210255
{stage.kind === "validate-domain" && (
@@ -235,25 +280,49 @@ export function DecentralizeScreen({
235280
)}
236281

237282
{stage.kind === "prompt-publish" && (
238-
<Select<boolean>
239-
label="publish to the playground registry?"
240-
options={[
241-
{
242-
value: false,
243-
label: "no",
244-
hint: "just register the .dot name (DotNS only)",
245-
},
246-
{
247-
value: true,
248-
label: "yes",
249-
hint: "list the mirrored site in the playground apps tab",
250-
},
251-
]}
252-
onSelect={(choice) => {
253-
setPublishToPlayground(choice);
254-
advance({ publishToPlayground: choice });
255-
}}
256-
/>
283+
<Box flexDirection="column">
284+
<PromptInfo box={PUBLISH_HELP} />
285+
<Select<boolean>
286+
label="publish to the playground registry?"
287+
options={[
288+
{
289+
value: true,
290+
label: "yes",
291+
hint: "list the mirrored site in the playground apps tab",
292+
},
293+
{
294+
value: false,
295+
label: "no",
296+
hint: "just register the .dot name (DotNS only)",
297+
},
298+
]}
299+
initialIndex={0}
300+
onSelect={(choice) => {
301+
setPublishToPlayground(choice);
302+
advance({ publishToPlayground: choice });
303+
}}
304+
/>
305+
</Box>
306+
)}
307+
308+
{stage.kind === "prompt-tags" && (
309+
<Box flexDirection="column">
310+
<PromptInfo box={TAGS_HELP} />
311+
<Select<string | null>
312+
label="tag this app?"
313+
options={[
314+
...PLAYGROUND_TAGS.map((t) => ({
315+
value: t as string | null,
316+
label: t,
317+
})),
318+
{ value: null, label: "skip", hint: "publish without a tag" },
319+
]}
320+
onSelect={(t) => {
321+
setTag(t);
322+
advance({ tag: t });
323+
}}
324+
/>
325+
</Box>
257326
)}
258327

259328
{stage.kind === "confirm" && (
@@ -265,6 +334,7 @@ export function DecentralizeScreen({
265334
signer={activeSigner!}
266335
signerMode={signerMode!}
267336
publishToPlayground={publishToPlayground === true}
337+
tag={publishToPlayground === true ? (tag ?? null) : null}
268338
onConfirm={() => setStage({ kind: "running" })}
269339
onCancel={() => onDone({ kind: "cancel" })}
270340
/>
@@ -278,6 +348,7 @@ export function DecentralizeScreen({
278348
mode={signerMode!}
279349
userSigner={explicitSigner ?? sessionSigner}
280350
publishToPlayground={publishToPlayground === true}
351+
tag={publishToPlayground === true ? (tag ?? null) : null}
281352
env={env}
282353
onComplete={(outcome) => setStage({ kind: "done", outcome })}
283354
onFailed={(message) => setStage({ kind: "error", message })}
@@ -301,23 +372,6 @@ export function DecentralizeScreen({
301372
);
302373
}
303374

304-
function signerOptions(sessionSigner: ResolvedSigner | null): SelectOption<SignerMode>[] {
305-
return [
306-
{
307-
value: "dev",
308-
label: "dev signer",
309-
hint: "fast, signs locally with the polkadot-app-deploy default account",
310-
},
311-
{
312-
value: "phone",
313-
label: "your phone signer",
314-
hint: sessionSigner
315-
? "signed with your logged-in account"
316-
: "requires `playground login` first",
317-
},
318-
];
319-
}
320-
321375
// ── Validate-domain stage ────────────────────────────────────────────────────
322376

323377
function ValidateDomainStage({
@@ -390,6 +444,7 @@ function ConfirmStage({
390444
signer,
391445
signerMode,
392446
publishToPlayground,
447+
tag,
393448
onConfirm,
394449
onCancel,
395450
}: {
@@ -400,6 +455,7 @@ function ConfirmStage({
400455
signer: ResolvedSigner;
401456
signerMode: SignerMode;
402457
publishToPlayground: boolean;
458+
tag: string | null;
403459
onConfirm: () => void;
404460
onCancel: () => void;
405461
}) {
@@ -418,6 +474,12 @@ function ConfirmStage({
418474
value={publishToPlayground ? "publish to apps tab" : "skip"}
419475
tone={publishToPlayground ? "accent" : "muted"}
420476
/>
477+
{/* Surface the chosen tag before the irreversible publish, like
478+
deploy's confirm summary. Only shown when publishing — the
479+
tag is otherwise irrelevant. */}
480+
{publishToPlayground && (
481+
<Row label="tag" value={tag ?? "none"} tone={tag ? "accent" : "muted"} />
482+
)}
421483
{availabilityNote && <Row label="note" value={availabilityNote} tone="warning" />}
422484
</Section>
423485
{autoGenerated && <Hint indent={2}>{FREE_DOMAIN_SUFFIX_NOTE}</Hint>}
@@ -461,6 +523,7 @@ function RunningStage({
461523
mode,
462524
userSigner,
463525
publishToPlayground,
526+
tag,
464527
env,
465528
onComplete,
466529
onFailed,
@@ -471,6 +534,7 @@ function RunningStage({
471534
mode: SignerMode;
472535
userSigner: ResolvedSigner | null;
473536
publishToPlayground: boolean;
537+
tag: string | null;
474538
env: Env;
475539
onComplete: (outcome: DecentralizeOutcome) => void;
476540
onFailed: (message: string) => void;
@@ -516,6 +580,7 @@ function RunningStage({
516580
mode,
517581
userSigner,
518582
publishToPlayground,
583+
tag,
519584
env,
520585
onEvent: (event) => {
521586
switch (event.kind) {

0 commit comments

Comments
 (0)