From e03ee3b158bb9195d20a61f6d3b8b717db97d087 Mon Sep 17 00:00:00 2001 From: JorchCortez Date: Fri, 2 Oct 2026 18:25:02 -0700 Subject: [PATCH 1/2] Ask which project to use before CLI enrollment --- README.md | 25 ++++++++++---- src/cli-commands/agent.ts | 6 ++-- src/cli-commands/guidance.ts | 2 +- src/cli.test.ts | 65 +++++++++++++++++++++++++----------- src/client.ts | 4 +-- src/commands.ts | 45 ++++++++++++------------- src/native-program.test.ts | 2 +- 7 files changed, 92 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 09a7cba2..8ee0b4bd 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,16 @@ rules validate invocations before configuration or network access. ## Projects and enrollment +Before writing with an existing credential, run `screenrig project show` and +confirm the project name and ID. The contact email alone does not identify the +intended project: one person may belong to several projects. + +On a fresh or disconnected installation, ask whether the user wants to connect +to an existing project or create a new one. `screenrig agent connect` requests +dashboard approval for an existing project; after approval, verify the selected +project with `project show`. Only use `agent enroll` after the user chooses a +new project. The CLI does not discover memberships from an email address. + An explicit enrollment creates a project, attaches this agent, and emails a member invitation to the contact address. The same person can belong to more than one project. @@ -136,10 +146,12 @@ screenrig project rename NAME `--project-name` names the project; `--name` names this agent. Enrollment reports the project ID and name and confirms that a member invitation was requested, without exposing a credential or invitation URL. The invitation is the human's -dashboard login; nothing waits on it, and the agent can pair screens and publish -right away. Other authenticated commands never enroll automatically. If the -server rejects the stored credential, `agent disconnect --yes` clears it locally -before a new enrollment. +project membership; a matching email or existing dashboard login does not accept +it. The person must accept the invitation before the new project appears in +their dashboard. Nothing waits on that step, and the agent can pair screens and +publish right away. Other authenticated commands never enroll automatically. +If the server rejects the stored credential, resolve the intended project +before disconnecting or enrolling again. ## Invitations @@ -183,9 +195,8 @@ The response is neutral whether or not the address is known: if this address can receive sign-in instructions, check its inbox. Delivery is not confirmed. Rerunning an ambiguous request reuses its saved Idempotency-Key. -First setup is `agent enroll`. Only to attach a project that already exists to -this installation, run `screenrig agent connect` and approve the connection -request in that project's dashboard. +To attach a project that already exists to this installation, run `screenrig +agent connect` and approve the connection request in that project's dashboard. ## Screen host and recovery diff --git a/src/cli-commands/agent.ts b/src/cli-commands/agent.ts index aaeba0df..d6cad0ae 100644 --- a/src/cli-commands/agent.ts +++ b/src/cli-commands/agent.ts @@ -8,15 +8,15 @@ import { usageError } from "../problems.js"; export function registerAgentCommands(root: Command, bind: CommandActionBinder): void { const agent = root.command("agent").description("Enroll, connect, and manage this agent"); - agent.command("enroll").description("Create a new project and its first agent") + agent.command("enroll").description("Create a new project and its first agent after choosing new-project setup") .option("--email
", "Set the project contact email") .option("--project-name ", "Set the project display name") .addOption(new Option("--intent ", "Choose the project's purpose: signage (default) or advertising").choices(["signage", "advertising"])) .option("--name ", "Set this agent installation name") - .option("--force", "Discard pending enrollment or connection state before enrolling") + .option("--force", "Discard pending enrollment or connection state after choosing a new project") .action(bind(handleAgentEnroll)); - addCommandNotes(agent.command("connect").description("Reconnect this installation to an existing project") + addCommandNotes(agent.command("connect").description("Connect this installation to an existing project") .option("--name ", "Set this agent installation name") .addOption(new Option("--capability ", "Request a capability (repeatable; default: all six)") .choices([...AGENT_CAPABILITIES]) diff --git a/src/cli-commands/guidance.ts b/src/cli-commands/guidance.ts index b6a1902b..053df436 100644 --- a/src/cli-commands/guidance.ts +++ b/src/cli-commands/guidance.ts @@ -127,7 +127,7 @@ export function registerGuidance(root: Command): void { "dashboard reset-sign-in": "Unauthenticated: never enrolls, sends a stored token, or changes stored credentials. The acknowledgment is identical for known and unknown addresses. Instructions expire after one hour. An unresolved request retains only its retry key and fingerprint in the local write ledger.", "agent connect": "Repeat --capability to request only the named areas; omitted requests all six. Dashboard approval can grant a non-empty subset. Capabilities cannot change on an existing request or credential: resume without changing flags and inspect the grant with agent status. To change permissions, connect a new agent in a separate private --config, approve and verify it, then disconnect the old agent.", "agent status": "Shows this credential's granted capabilities, distinct from project feature entitlements. A forbidden capability refusal requires a newly connected and approved agent with that capability; retrying the same credential cannot expand its permissions.", - "agent enroll": "Creates a new project and first agent, even when the contact email belongs to another project. Ask for the contact email and supply --email. --project-name sets its display name; --name names this installation. The server emails the contact address a member invitation for their dashboard login; nothing waits on it, so tell the user and go straight on to pairing and content without creating an invitation link. An already enrolled installation reuses its project. --force discards pending enrollment or connection state; intentional existing-project reconnects use agent connect. --intent advertising enables advertiser=true/screens=false without changing billing-plan assignment. A pending enrollment keeps its original email, name, and purpose.", + "agent enroll": "On an unconnected installation, ask whether the user wants an existing project or a new one. Use agent connect for an existing project. Only after the user chooses new, ask for the contact email and supply --email. Enrollment creates a separate project even when that email belongs to another project. --project-name sets its display name; --name names this installation. The server emails the contact address a member invitation for their dashboard login; nothing waits on it, so tell the user and go straight on to pairing and content without creating an invitation link. The person must accept the invitation before the project appears in their dashboard. An already enrolled installation reuses its project; verify it with project show before writes. --force discards pending enrollment or connection state and must not skip an intentional existing-project connection. --intent advertising enables advertiser=true/screens=false without changing billing-plan assignment. A pending enrollment keeps its original email, name, and purpose.", "project capabilities": "Read this project's plan id, independent advertiser/screens feature flags, feature revision, and the server's effective capability set. Never infer permission from a quota, plan name, or command inventory. A route denial still wins over previously read capabilities.", "dashboard": "Opens the dashboard origin without minting credentials. If the browser opener fails, the origin is printed. Sign in with a retained person credential.", "dashboard open": "Opens the dashboard origin without minting credentials. The origin is printed only if the browser opener fails.", diff --git a/src/cli.test.ts b/src/cli.test.ts index 7bbafef2..5d336edd 100644 --- a/src/cli.test.ts +++ b/src/cli.test.ts @@ -138,7 +138,7 @@ test("pairing requires explicit enrollment and then preserves the original pairi assert.equal(missing.code, ExitCode.Auth); const blocked = JSON.parse(missing.stdout) as { error: { code: string; next: { command: string } } }; assert.equal(blocked.error.code, "not_enrolled"); - assert.equal(blocked.error.next.command, "screenrig agent enroll --email ADDRESS"); + assert.equal(blocked.error.next.command, "screenrig agent --help"); assert.equal(transport.calls.length, 0); const enrolled = await withRuntime( @@ -440,13 +440,13 @@ test("every authenticated command reports not_enrolled instead of enrolling", as const envelope = JSON.parse(result.stdout) as { ok: boolean; error: { code: string; next: { command: string } } }; assert.equal(envelope.ok, false, argv.join(" ")); assert.equal(envelope.error.code, "not_enrolled", argv.join(" ")); - assert.equal(envelope.error.next.command, "screenrig agent enroll --email ADDRESS", argv.join(" ")); + assert.equal(envelope.error.next.command, "screenrig agent --help", argv.join(" ")); assert.equal(transport.calls.length, 0, argv.join(" ")); await rm(result.configDir, { recursive: true, force: true }); } }); -test("revoked agent history without a pending reconnect directs authenticated commands to enroll", async () => { +test("revoked agent history without a pending reconnect asks which project to use", async () => { const configDir = await testTemp("revoked-prefers-enroll-"); const fsLike = { mkdir, open, rename, rm, chmod, stat, homedir: () => configDir, env: { XDG_CONFIG_HOME: configDir } }; await writeConfigAtomic(path.join(configDir, "screenrig", "config.json"), { @@ -466,8 +466,8 @@ test("revoked agent history without a pending reconnect directs authenticated co error: { code: string; next: { command: string; reason: string } }; }; assert.equal(envelope.error.code, "not_enrolled"); - assert.equal(envelope.error.next.command, "screenrig agent enroll --email ADDRESS"); - assert.match(envelope.error.next.reason, /contact email/i); + assert.equal(envelope.error.next.command, "screenrig agent --help"); + assert.match(envelope.error.next.reason, /existing project or create a new one/i); await rm(configDir, { recursive: true, force: true }); }); @@ -476,11 +476,36 @@ test("agent status never enrolls a missing installation", async () => { const status = await withRuntime(["--json", "agent", "status"], transport); assert.equal(status.code, 0, status.stdout); assert.equal(JSON.parse(status.stdout).data.status, "not_enrolled"); - assert.equal(JSON.parse(status.stdout).data.path, "first_run_enroll"); + assert.equal(JSON.parse(status.stdout).data.path, "choose_project"); assert.equal(transport.calls.length, 0); await rm(status.configDir, { recursive: true, force: true }); }); +test("a pending new-project enrollment still resumes instead of asking again", async () => { + const configDir = await testTemp("pending-enrollment-choice-"); + const fsLike = { mkdir, open, rename, rm, chmod, stat, homedir: () => configDir, env: { XDG_CONFIG_HOME: configDir } }; + await writeConfigAtomic(path.join(configDir, "screenrig", "config.json"), { + api_url: "https://api.screenrig.ai", + enrollment: { client_id: `cli_${"a".repeat(43)}`, idempotency_key: "pending-project-choice", email: "owner@example.com" }, + }, fsLike); + const transport = new FakeTransport(); + const status = await withRuntime(["--json", "agent", "status"], transport, { fs: fsLike }); + assert.equal(JSON.parse(status.stdout).data.path, "first_run_enroll"); + const blocked = await withRuntime(["--json", "project", "show"], transport, { fs: fsLike }); + assert.equal(JSON.parse(blocked.stdout).error.next.command, "screenrig agent enroll"); + await writeConfigAtomic(path.join(configDir, "screenrig", "config.json"), { + api_url: "https://api.screenrig.ai", + enrollment: { client_id: `cli_${"a".repeat(43)}`, idempotency_key: "pending-project-choice", email: "owner@example.com" }, + last_agent: { id: TEST_AGENT.id, name: TEST_AGENT.name, agent_type: TEST_AGENT.agent_type, capabilities: [...AGENT_CAPABILITIES], state: "revoked" }, + }, fsLike); + const disconnected = await withRuntime(["--json", "project", "show"], transport, { fs: fsLike }); + assert.equal(JSON.parse(disconnected.stdout).error.next.command, "screenrig agent enroll"); + assert.equal(transport.calls.length, 0); + const doctor = await withRuntime(["--json", "doctor"], transport, { fs: fsLike, runProcess: fullToolchainProbe() }); + assert.equal(JSON.parse(doctor.stdout).data.checks.find((check: { name: string }) => check.name === "token")?.path, "first_run_enroll"); + await rm(configDir, { recursive: true, force: true }); +}); + test("unsupported auth aliases do not make requests or enroll", async () => { const transport = new FakeTransport(); for (const command of [[], ["status"], ["revoke", "--yes"]]) { @@ -865,7 +890,7 @@ test("agent disconnect locally cleans an unauthorized credential without retryin const status = await withRuntime(["--json", "agent", "status"], new FakeTransport(), { fs: fsLike }); assert.deepEqual(JSON.parse(status.stdout).data, { status: "not_enrolled", - path: "first_run_enroll", + path: "choose_project", }); await rm(configDir, { recursive: true, force: true }); }); @@ -942,7 +967,7 @@ test("agent disconnect revokes only this installation and preserves safe disconn assert.equal(local?.last_agent?.id, active.id); const status = await withRuntime(["--json", "agent", "status"], new FakeTransport(), { fs: fsLike }); assert.equal(JSON.parse(status.stdout).data.status, "disconnected"); - assert.equal(JSON.parse(status.stdout).data.path, "first_run_enroll"); + assert.equal(JSON.parse(status.stdout).data.path, "choose_project"); await rm(configDir, { recursive: true, force: true }); }); @@ -2922,7 +2947,7 @@ test("doctor reports a configured credential as presence only", async () => { } }); -test("doctor warns, exits 0, and names agent enroll when a fresh install has no credential", async () => { +test("doctor warns, exits 0, and asks which project to use when a fresh install has no credential", async () => { resetFfmpegToolchainCache(); const { code, stdout, configDir } = await withRuntime(["--json", "doctor"], memoryBackend(), { runProcess: fullToolchainProbe() }); assert.equal(code, ExitCode.Success, stdout); @@ -2937,11 +2962,11 @@ test("doctor warns, exits 0, and names agent enroll when a fresh install has no const token = envelope.data.checks.find((check) => check.name === "token"); assert.ok(token, stdout); assert.equal(token.status, "warn"); - assert.equal(token.path, "first_run_enroll"); + assert.equal(token.path, "choose_project"); assert.match(token.detail, /^\(none\); this installation is not enrolled/); - assert.equal(token.next?.command, "screenrig agent enroll --email ADDRESS"); - assert.equal(envelope.data.next?.command, "screenrig agent enroll --email ADDRESS"); - assert.match(envelope.data.next?.reason ?? "", /not_enrolled/); + assert.equal(token.next?.command, "screenrig agent --help"); + assert.equal(envelope.data.next?.command, "screenrig agent --help"); + assert.match(envelope.data.next?.reason ?? "", /existing project or create a new one/i); assert.equal(envelope.data.checks.some((check) => check.status === "fail" && check.name === "token"), false); assert.doesNotMatch(stdout, /sr_live_/); await rm(configDir, { recursive: true, force: true }); @@ -2978,7 +3003,7 @@ test("doctor points a pending agent connection at agent connect instead of enrol await rm(configDir, { recursive: true, force: true }); }); -test("doctor treats revoked agent history without a pending reconnect as first-run enrollment", async () => { +test("doctor asks which project to use after revocation without a pending reconnect", async () => { resetFfmpegToolchainCache(); const configDir = await testTemp("doctor-revoked-enroll-"); const fsLike = { mkdir, open, rename, rm, chmod, stat, homedir: () => configDir, env: { XDG_CONFIG_HOME: configDir } }; @@ -2998,10 +3023,10 @@ test("doctor treats revoked agent history without a pending reconnect as first-r data: { next?: { command: string }; checks: Array<{ name: string; path?: string; next?: { command: string; reason: string } }> }; }; const token = envelope.data.checks.find((check) => check.name === "token"); - assert.equal(token?.path, "first_run_enroll"); - assert.equal(token?.next?.command, "screenrig agent enroll --email ADDRESS"); - assert.match(token?.next?.reason ?? "", /contact email/i); - assert.equal(envelope.data.next?.command, "screenrig agent enroll --email ADDRESS"); + assert.equal(token?.path, "choose_project"); + assert.equal(token?.next?.command, "screenrig agent --help"); + assert.match(token?.next?.reason ?? "", /existing project or create a new one/i); + assert.equal(envelope.data.next?.command, "screenrig agent --help"); await rm(configDir, { recursive: true, force: true }); }); @@ -7270,12 +7295,12 @@ test("every error envelope carries a hint; usage errors point at the invoked com const unknown = await withRuntime(["--json", "screen", "no-such-command"], transport); const unknownError = JSON.parse(unknown.stdout).error as { hint?: string; next?: { command: string } }; assert.ok(unknownError.hint); - // Not enrolled: a hint plus the enrollment next. + // Not enrolled: a hint plus the neutral project-choice next. const notEnrolled = await withRuntime(["--json", "screen", "list"], transport); const notEnrolledError = JSON.parse(notEnrolled.stdout).error as { code: string; hint?: string; next?: { command: string } }; assert.equal(notEnrolledError.code, "not_enrolled"); assert.match(String(notEnrolledError.hint), /Run the next command/); - assert.match(String(notEnrolledError.next?.command), /agent enroll/); + assert.equal(notEnrolledError.next?.command, "screenrig agent --help"); // Human output shows the same hint. const human = await withRuntime(["--human", "screen", "list"], transport); assert.match(human.stderr, /^hint: .*Run the next command/m); diff --git a/src/client.ts b/src/client.ts index ea1174f6..7d5d1f97 100644 --- a/src/client.ts +++ b/src/client.ts @@ -444,8 +444,8 @@ export function requireToken(token: string | undefined): string { throw new CliError( makeProblem("unauthenticated", "Credential unavailable", 401, "This installation has no durable agent credential.", { next: { - command: "screenrig agent enroll --email ADDRESS", - reason: "Enrollment is explicit. Create the first agent, then retry the original command.", + command: "screenrig agent --help", + reason: "Ask whether to connect to an existing project or create a new one before retrying the original command.", }, }), ExitCode.Auth, diff --git a/src/commands.ts b/src/commands.ts index d199bd1a..fe306f93 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -468,6 +468,11 @@ function humanLines(title: string, fields: Array<[string, string | undefined]>): export type CommandHandler = (args: ParsedArgs, runtime: CliRuntime) => Promise; type ResolvedCommandConfig = Awaited>; +const chooseProjectNext = { + command: "screenrig agent --help", + reason: "Ask whether to connect to an existing project or create a new one. Use agent connect for existing projects; enroll only after choosing a new project.", +}; + /** Prepare configuration, logging and credential guidance once for a bound leaf. */ function commandHandler( handler: (args: ParsedArgs, runtime: CliRuntime, resolved: ResolvedCommandConfig) => Promise, @@ -486,17 +491,15 @@ function commandHandler( } if (resolved.lastAgent) { throw notEnrolledError("This installation is disconnected and cannot run project commands.", { - command: "screenrig agent enroll --email ADDRESS", - reason: "Ask the user for their contact email, then create a new project agent. Use agent connect only for an intentional existing-project reconnect.", + ...(resolved.enrollment?.email + ? { command: "screenrig agent enroll", reason: "Resume the exact pending enrollment before running another project command." } + : chooseProjectNext), }); } throw notEnrolledError("This installation is not enrolled. Enrollment is an explicit step and is never a side effect of another command.", { - command: resolved.enrollment?.email - ? "screenrig agent enroll" - : "screenrig agent enroll --email ADDRESS", - reason: resolved.enrollment?.email - ? "Resume the exact pending enrollment before running pairing or another project command." - : "Create the first agent with unverified contact metadata, then retry the original command.", + ...(resolved.enrollment?.email + ? { command: "screenrig agent enroll", reason: "Resume the exact pending enrollment before running pairing or another project command." } + : chooseProjectNext), }); } // Specialized enrollment, generation, upload/bundle, and handoff flows own @@ -764,7 +767,7 @@ async function agentStatus( const local = resolved.lastAgent; const status = local ? "disconnected" : "not_enrolled"; return { - envelope: successEnvelope({ status, path: "first_run_enroll", ...(local ? { agent: local } : {}) }), + envelope: successEnvelope({ status, path: resolved.enrollment?.email ? "first_run_enroll" : "choose_project", ...(local ? { agent: local } : {}) }), exitCode: ExitCode.Success, human: humanLines("Agent", [ ["status", status], @@ -1500,7 +1503,7 @@ async function agentDisconnect( human: humanLines("Agent disconnected", [ ["local_credential", "removed"], ["project_screens_and_other_agents", "preserved"], - ["next", "screenrig agent enroll --email ADDRESS creates a new project; screenrig agent connect joins an existing one only when the user asks"], + ["next", "Ask whether to connect to an existing project or create a new one; use agent connect for existing, or agent enroll only after choosing new"], ]), }; } @@ -5954,7 +5957,7 @@ interface DoctorCheck { name: string; status: DoctorStatus; detail: string; - path?: "first_run_enroll" | "reconnect_existing"; + path?: "choose_project" | "first_run_enroll" | "reconnect_existing"; /** The command that clears a `warn` or `fail` row, when one exists. */ next?: ProblemNext; } @@ -5988,24 +5991,20 @@ function credentialCheck(resolved: Awaited>): D name: "token", status: "warn", detail: `${detail}; this installation was disconnected`, - path: "first_run_enroll", - next: { - command: "screenrig agent enroll --email ADDRESS", - reason: "Ask the user for their contact email, enroll a new project agent, then rerun doctor. Use agent connect only for an intentional existing-project reconnect.", - }, + path: resolved.enrollment?.email ? "first_run_enroll" : "choose_project", + next: resolved.enrollment?.email + ? { command: "screenrig agent enroll", reason: "Resume the exact pending enrollment, then rerun doctor." } + : chooseProjectNext, }; } return { name: "token", status: "warn", detail: `${detail}; this installation is not enrolled`, - path: "first_run_enroll", - next: { - command: resolved.enrollment?.email ? "screenrig agent enroll" : "screenrig agent enroll --email ADDRESS", - reason: resolved.enrollment?.email - ? "Resume the exact pending enrollment, then rerun doctor." - : "Create the first agent with unverified contact metadata, then rerun doctor. Every authenticated command fails with not_enrolled until then.", - }, + path: resolved.enrollment?.email ? "first_run_enroll" : "choose_project", + next: resolved.enrollment?.email + ? { command: "screenrig agent enroll", reason: "Resume the exact pending enrollment, then rerun doctor." } + : chooseProjectNext, }; } diff --git a/src/native-program.test.ts b/src/native-program.test.ts index 7f945c09..6b4586b6 100644 --- a/src/native-program.test.ts +++ b/src/native-program.test.ts @@ -95,7 +95,7 @@ test("unauthenticated native mutation actions reject before local preparation or assert.equal(await run(fixture.runtime), ExitCode.Auth, fixture.output()); const envelope = JSON.parse(fixture.output()); assert.equal(envelope.error.code, "not_enrolled"); - assert.equal(envelope.error.next.command, "screenrig agent enroll --email ADDRESS"); + assert.equal(envelope.error.next.command, "screenrig agent --help"); assert.equal(transport.calls.length, 0); assert.deepEqual(await readdir(fixture.configDir), [], "authentication failure must not create retry or enrollment state"); } finally { From 7cead10e031635e33001f7583e5624d40e0eb424 Mon Sep 17 00:00:00 2001 From: JorchCortez Date: Fri, 2 Oct 2026 18:41:05 -0700 Subject: [PATCH 2/2] Use installed fonts in compose examples --- src/compose/catalog.ts | 10 ---------- src/compose/compose.test.ts | 2 -- tools/compositor/README.md | 3 +-- tools/compositor/examples/board.json | 1 - tools/compositor/examples/exec-intro.json | 1 - tools/compositor/examples/menu.json | 1 - tools/compositor/examples/showcase.json | 5 ----- tools/compositor/examples/slide.json | 1 - tools/compositor/examples/supper.json | 1 - 9 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/compose/catalog.ts b/src/compose/catalog.ts index c2b4d492..d1321720 100644 --- a/src/compose/catalog.ts +++ b/src/compose/catalog.ts @@ -100,7 +100,6 @@ export async function composeCatalog(): Promise { slide: { width: 1920, height: 1080, - font: "Liberation Serif", background: "#1C1410", brand: "#C9A227", text: "#F3E6D0", @@ -119,7 +118,6 @@ export async function composeCatalog(): Promise { menu: { width: 1920, height: 1080, - font: "Liberation Serif", background: "#14110C", brand: "#C9A227", text: "#F3E6D0", @@ -138,7 +136,6 @@ export async function composeCatalog(): Promise { table: { width: 1920, height: 1080, - font: "Liberation Sans", background: "#0E1A2B", brand: "#FFB800", text: "#F4F7FA", @@ -157,7 +154,6 @@ export async function composeCatalog(): Promise { overlay: { width: 1920, height: 1080, - font: "Liberation Serif", background: "#00000000", brand: "#C9A227", text: "#FFFFFF", @@ -176,7 +172,6 @@ export async function composeCatalog(): Promise { "overlay-left": { width: 1920, height: 1080, - font: "Liberation Sans", background: "#2A3547", brand: "#F8B334", text: "#F4F7FA", @@ -195,7 +190,6 @@ export async function composeCatalog(): Promise { "overlay-right": { width: 1920, height: 1080, - font: "Liberation Sans", background: "#2A3547", brand: "#F8B334", text: "#F4F7FA", @@ -214,7 +208,6 @@ export async function composeCatalog(): Promise { "overlay-bottom": { width: 1920, height: 1080, - font: "Liberation Sans", background: "#2A3547", brand: "#F8B334", text: "#F4F7FA", @@ -233,7 +226,6 @@ export async function composeCatalog(): Promise { "overlay-title": { width: 1920, height: 1080, - font: "Liberation Sans", background: "#2A3547", brand: "#F8B334", text: "#F4F7FA", @@ -250,7 +242,6 @@ export async function composeCatalog(): Promise { "overlay-still": { width: 1920, height: 1080, - font: "Liberation Sans", background: "#2A3547", brand: "#F8B334", text: "#F4F7FA", @@ -264,7 +255,6 @@ export async function composeCatalog(): Promise { deck: { width: 1920, height: 1080, - font: "Liberation Serif", background: "#0D0D0D", brand: "#D4AF37", text: "#F2EDE4", diff --git a/src/compose/compose.test.ts b/src/compose/compose.test.ts index c872d406..35f01c51 100644 --- a/src/compose/compose.test.ts +++ b/src/compose/compose.test.ts @@ -742,7 +742,6 @@ test("an ink-fit card hugs every measured line and reports no overflow", async ( const result = await composeDocument({ width: 1920, height: 1080, - font: "Liberation Sans", background: "#00000000", brand: "#FFD166", text: "#FFFFFF", @@ -797,7 +796,6 @@ test("regions in one row share a type scale and title baseline regardless of bod const spec = { width: 1920, height: 1080, - font: "Liberation Sans", background: "#1B1B1F", brand: "#E9C46A", text: "#F1FAEE", diff --git a/tools/compositor/README.md b/tools/compositor/README.md index 9bbdb117..4a02a64e 100644 --- a/tools/compositor/README.md +++ b/tools/compositor/README.md @@ -41,13 +41,12 @@ The preview plays layered output: region PNGs stacked by `z`/`rect`, `