diff --git a/.changeset/mod-community-notice.md b/.changeset/mod-community-notice.md new file mode 100644 index 0000000..b405300 --- /dev/null +++ b/.changeset/mod-community-notice.md @@ -0,0 +1,5 @@ +--- +"playground-cli": patch +--- + +`playground mod` now shows a community-code notice before downloading an app: a callout above the interactive picker list, and the same notice on the setup screen for the direct `playground mod ` path. It tells users that apps are community-published open source, not reviewed, and that modding runs the app's setup script on their machine. Also, the moddable prompt in `playground deploy` now defaults its cursor to yes. diff --git a/src/commands/deploy/DeployScreen.tsx b/src/commands/deploy/DeployScreen.tsx index 307bc52..af81598 100644 --- a/src/commands/deploy/DeployScreen.tsx +++ b/src/commands/deploy/DeployScreen.tsx @@ -307,7 +307,7 @@ export function DeployScreen({ }, { value: true, label: "yes", hint: "publishes your source repo URL" }, ]} - initialIndex={0} + initialIndex={1} onSelect={(yes) => { setModdable(yes); if (yes) { diff --git a/src/commands/mod/AppBrowser.tsx b/src/commands/mod/AppBrowser.tsx index 45c59bd..710803e 100644 --- a/src/commands/mod/AppBrowser.tsx +++ b/src/commands/mod/AppBrowser.tsx @@ -15,9 +15,10 @@ import { useState, useEffect, useRef, useCallback } from "react"; import { Box, Text, useInput, useStdout } from "ink"; -import { Mark, Hint, COLOR } from "../../utils/ui/theme/index.js"; +import { Mark, Hint, Callout, COLOR } from "../../utils/ui/theme/index.js"; import { fetchBulletinJson, getBulletinGateway } from "../../utils/bulletinGateway.js"; +import { COMMUNITY_NOTICE_TITLE, COMMUNITY_NOTICE_BODY } from "./communityNotice.js"; import { filterModdable, type AppEntry } from "./browserFilter.js"; export type { AppEntry }; @@ -37,7 +38,9 @@ function pad(s: string, w: number): string { export function AppBrowser({ registry, onSelect, onCancel, moddableOnly }: Props) { const { stdout } = useStdout(); - const viewH = Math.max((stdout?.rows ?? 24) - 6, 5); + // The community-code callout above the list takes ~8 rows (margins, + // borders, title, wrapped body), on top of the 6 rows of list chrome. + const viewH = Math.max((stdout?.rows ?? 24) - 14, 5); const [apps, setApps] = useState([]); const [total, setTotal] = useState(0); @@ -168,54 +171,59 @@ export function AppBrowser({ registry, onSelect, onCancel, moddableOnly }: Props const descW = Math.max((stdout?.columns ?? 80) - COL.num - COL.domain - COL.name - 10, 10); return ( - - - - {`${pad(" #", COL.num)} ${pad("domain", COL.domain)} ${pad( - "name", - COL.name, - )} description`} - - - - {"─".repeat(COL.num + COL.domain + COL.name + descW + 6)} - + + + {COMMUNITY_NOTICE_BODY} + + + + + {`${pad(" #", COL.num)} ${pad("domain", COL.domain)} ${pad( + "name", + COL.name, + )} description`} + + + + {"─".repeat(COL.num + COL.domain + COL.name + descW + 6)} + - {visible.map((app, i) => { - const idx = scroll + i; - const sel = idx === cursor; - const num = sel - ? `›${String(idx + 1).padStart(COL.num - 1)}` - : ` ${String(idx + 1).padStart(COL.num - 1)}`; - return ( - - - {`${num} ${pad(app.domain, COL.domain)} ${pad( - app.name ?? (app.name === null ? "…" : "—"), - COL.name, - )} ${pad(app.description ?? "", descW)}`} - - - ); - })} + {visible.map((app, i) => { + const idx = scroll + i; + const sel = idx === cursor; + const num = sel + ? `›${String(idx + 1).padStart(COL.num - 1)}` + : ` ${String(idx + 1).padStart(COL.num - 1)}`; + return ( + + + {`${num} ${pad(app.domain, COL.domain)} ${pad( + app.name ?? (app.name === null ? "…" : "—"), + COL.name, + )} ${pad(app.description ?? "", descW)}`} + + + ); + })} - {fetching && ( - - - loading apps… - - )} - {!fetching && filtered.length === 0 && nextStart.current === null && ( - - No moddable apps in the registry yet. + {fetching && ( + + + loading apps… + + )} + {!fetching && filtered.length === 0 && nextStart.current === null && ( + + No moddable apps in the registry yet. + + )} + + {`↑↓ navigate · ⏎ select · q quit · ${ + moddableOnly + ? `(${filtered.length} moddable, ${apps.length}/${total} scanned)` + : `(${apps.length}/${total})` + }`} - )} - - {`↑↓ navigate · ⏎ select · q quit · ${ - moddableOnly - ? `(${filtered.length} moddable, ${apps.length}/${total} scanned)` - : `(${apps.length}/${total})` - }`} ); diff --git a/src/commands/mod/SetupScreen.tsx b/src/commands/mod/SetupScreen.tsx index 055681b..558e5fb 100644 --- a/src/commands/mod/SetupScreen.tsx +++ b/src/commands/mod/SetupScreen.tsx @@ -14,11 +14,12 @@ // limitations under the License. import { useRef, useState } from "react"; -import { Box } from "ink"; +import { Box, Text } from "ink"; import { existsSync, readFileSync, writeFileSync, appendFileSync } from "node:fs"; import { resolve } from "node:path"; import { StepRunner, type Step } from "../../utils/ui/components/StepRunner.js"; -import { Header, Hint, Row, Section } from "../../utils/ui/theme/index.js"; +import { Header, Hint, Row, Section, Callout } from "../../utils/ui/theme/index.js"; +import { COMMUNITY_NOTICE_TITLE, COMMUNITY_NOTICE_BODY } from "./communityNotice.js"; import { runCommand } from "../../utils/git.js"; import { createOptionalGitBaseline } from "../../utils/mod/git-baseline.js"; import { downloadGitHubTarball, parseGitHubRepoUrl } from "../../utils/mod/source.js"; @@ -140,6 +141,15 @@ export function SetupScreen({ domain, metadata: initial, registry, targetDir, on right={VERSION_LABEL} /> + {/* The interactive picker already showed this notice above the + app list; only the direct `playground mod ` path + (no pre-fetched metadata) needs it here. */} + {initial === null && ( + + {COMMUNITY_NOTICE_BODY} + + )} + ` path renders it on the setup screen. The wording + * follows the marketplace-standard pair (unreviewed community content + use + * at your own risk) without license-text legalese. + */ +export const COMMUNITY_NOTICE_TITLE = "Community Code"; + +export const COMMUNITY_NOTICE_BODY = + "Apps here are open source, published by the community, and not reviewed. " + + "Modding downloads the source and runs its setup script on your machine. " + + "Review anything you don't trust first and use at your own risk.";