From c343481cb47c024a83b2982d07776aa3905e947e Mon Sep 17 00:00:00 2001 From: Rezo Date: Tue, 10 Feb 2026 19:00:10 +0400 Subject: [PATCH 1/2] Fix lint formatting for create-rezi --- .../release-demo/src/scenario1-screenshot.ts | 253 ++++++++++++----- examples/release-demo/src/scenario2-gif.ts | 267 +++++++++--------- .../src/__tests__/scaffold.test.ts | 4 +- packages/create-rezi/src/index.ts | 35 ++- packages/create-rezi/src/scaffold.ts | 6 +- .../templates/dashboard/src/main.ts | 181 ++++++------ .../templates/file-browser/src/main.ts | 150 ++++++---- .../templates/form-app/src/main.ts | 196 +++++++------ .../templates/streaming-viewer/src/main.ts | 114 ++++---- 9 files changed, 696 insertions(+), 510 deletions(-) diff --git a/examples/release-demo/src/scenario1-screenshot.ts b/examples/release-demo/src/scenario1-screenshot.ts index e925ef6e..07ba5a60 100644 --- a/examples/release-demo/src/scenario1-screenshot.ts +++ b/examples/release-demo/src/scenario1-screenshot.ts @@ -40,34 +40,135 @@ const cmt = (t: string) => s(t, { fg: tComment, italic: true }); const typescriptCode: readonly (readonly RichTextSpan[])[] = [ [cmt("// Typed core API")], - [kw("import"), plain(" { "), fn("createApp"), plain(", "), fn("ui"), plain(" } "), kw("from"), plain(" "), str('"@rezi-ui/core"')], - [kw("import"), plain(" { "), fn("createNodeBackend"), plain(" } "), kw("from"), plain(" "), str('"@rezi-ui/node"')], + [ + kw("import"), + plain(" { "), + fn("createApp"), + plain(", "), + fn("ui"), + plain(" } "), + kw("from"), + plain(" "), + str('"@rezi-ui/core"'), + ], + [ + kw("import"), + plain(" { "), + fn("createNodeBackend"), + plain(" } "), + kw("from"), + plain(" "), + str('"@rezi-ui/node"'), + ], [plain("")], [kw("const"), plain(" app = "), fn("createApp"), sym("({")], [plain(" backend"), sym(":"), plain(" "), fn("createNodeBackend"), sym("(),")], - [plain(" initialState"), sym(":"), plain(" { count"), sym(":"), plain(" "), num("0"), plain(" },")], + [ + plain(" initialState"), + sym(":"), + plain(" { count"), + sym(":"), + plain(" "), + num("0"), + plain(" },"), + ], [sym("});")], [plain("")], [plain("app."), fn("view"), sym("("), plain("state "), sym("=>")], [plain(" ui."), fn("column"), sym("({ p: "), num("1"), sym(", gap: "), num("1"), sym(" }, [")], - [plain(" ui."), fn("text"), sym("("), str('`Count: ${state.count}`'), sym("),")], - [plain(" ui."), fn("button"), sym("("), str('"inc"'), sym(", "), str('"+1"'), sym(", { onPress: () => app.update(s => ({ ...s, count: s.count + "), num("1"), sym(" })) })")], + [plain(" ui."), fn("text"), sym("("), str("`Count: ${state.count}`"), sym("),")], + [ + plain(" ui."), + fn("button"), + sym("("), + str('"inc"'), + sym(", "), + str('"+1"'), + sym(", { onPress: () => app.update(s => ({ ...s, count: s.count + "), + num("1"), + sym(" })) })"), + ], [plain(" ])"), sym(",")], [sym(");")], ]; const jsxCode: readonly (readonly RichTextSpan[])[] = [ [cmt("// JSX runtime")], - [kw("import"), plain(" { "), ty("Column"), plain(", "), ty("Text"), plain(", "), ty("Button"), plain(" } "), kw("from"), plain(" "), str('"@rezi-ui/jsx"')], - [kw("import"), plain(" { "), fn("useState"), plain(" } "), kw("from"), plain(" "), str('"react"')], + [ + kw("import"), + plain(" { "), + ty("Column"), + plain(", "), + ty("Text"), + plain(", "), + ty("Button"), + plain(" } "), + kw("from"), + plain(" "), + str('"@rezi-ui/jsx"'), + ], + [ + kw("import"), + plain(" { "), + fn("useState"), + plain(" } "), + kw("from"), + plain(" "), + str('"react"'), + ], [plain("")], [kw("function"), plain(" "), fn("Counter"), sym("()"), plain(" {")], - [plain(" "), kw("const"), plain(" [count, setCount] = "), fn("useState"), sym("("), num("0"), sym(");")], + [ + plain(" "), + kw("const"), + plain(" [count, setCount] = "), + fn("useState"), + sym("("), + num("0"), + sym(");"), + ], [plain(" "), kw("return"), plain(" (")], - [plain(" "), sym("<"), ty("Column"), plain(" p"), sym("={"), num("1"), sym("}"), plain(" gap"), sym("={"), num("1"), sym("}>")], - [plain(" "), sym("<"), ty("Text"), sym(">"), plain("Count: {count}"), sym("")], - [plain(" "), sym("<"), ty("Button"), plain(" id="), str('"inc"'), plain(" label="), str('"+1"')], - [plain(" onPress"), sym("={() => "), fn("setCount"), sym("(c => c + "), num("1"), sym(")}"), plain(" />")], + [ + plain(" "), + sym("<"), + ty("Column"), + plain(" p"), + sym("={"), + num("1"), + sym("}"), + plain(" gap"), + sym("={"), + num("1"), + sym("}>"), + ], + [ + plain(" "), + sym("<"), + ty("Text"), + sym(">"), + plain("Count: {count}"), + sym(""), + ], + [ + plain(" "), + sym("<"), + ty("Button"), + plain(" id="), + str('"inc"'), + plain(" label="), + str('"+1"'), + ], + [ + plain(" onPress"), + sym("={() => "), + fn("setCount"), + sym("(c => c + "), + num("1"), + sym(")}"), + plain(" />"), + ], [plain(" "), sym("")], [plain(" "), sym(")")], [sym("}")], @@ -140,75 +241,77 @@ app.view(({ revision, wipe }) => { return ui.box({ flex: 1, style: { bg } }, [ ui.row({ flex: 1, justify: "center" }, [ ui.column({ key: `shot-${revision}`, width: captureWidth, gap: 0, items: "stretch" }, [ - ui.box( - { - border: "rounded", - mt: 1, - px: 1, - py: 0, - style: { bg: panel, fg: borderBright }, - }, - [ - ui.column({ gap: 0, items: "stretch" }, [ - ...logoLines.map((line) => ui.row({ justify: "center", width: "100%" }, [ui.richText(line)])), - ui.row({ justify: "center", width: "100%" }, [ - ui.richText([ - s("Two API styles: Custom TypeScript and JSX (React)", { - fg: fg, - }), + ui.box( + { + border: "rounded", + mt: 1, + px: 1, + py: 0, + style: { bg: panel, fg: borderBright }, + }, + [ + ui.column({ gap: 0, items: "stretch" }, [ + ...logoLines.map((line) => + ui.row({ justify: "center", width: "100%" }, [ui.richText(line)]), + ), + ui.row({ justify: "center", width: "100%" }, [ + ui.richText([ + s("Two API styles: Custom TypeScript and JSX (React)", { + fg: fg, + }), + ]), ]), - ]), - ui.row({ justify: "center", width: "100%" }, [ - ui.row({ gap: 1, items: "center" }, [ - chip("Custom TypeScript", rgb(76, 204, 255)), - chip("JSX (React)", rgb(116, 176, 255)), + ui.row({ justify: "center", width: "100%" }, [ + ui.row({ gap: 1, items: "center" }, [ + chip("Custom TypeScript", rgb(76, 204, 255)), + chip("JSX (React)", rgb(116, 176, 255)), + ]), ]), ]), - ]), - ], - ), - - ui.row({ gap: 1, mt: 1, mb: 1, items: "stretch" }, [ - codePanel( - "Custom TypeScript", - "Typed API with direct state updates", - ["Custom API", "Type-safe"], - typescriptCode, - borderBright, - true, - ), - codePanel( - "JSX (React)", - "React-style JSX with hooks state", - ["JSX", "React hooks"], - jsxCode, - border, - false, + ], ), - ]), - ui.box( - { - border: "rounded", - mb: 1, - px: 1, - py: 0, - style: { bg: panel, fg: border }, - }, - [ - ui.row({ justify: "between", items: "center", gap: 2 }, [ - ui.richText([ - s("$ npm i ", { fg: fgDim }), - s("@rezi-ui/core @rezi-ui/node @rezi-ui/jsx", { fg: accent, bold: true }), - ]), - ui.richText([ - s("Custom TypeScript", { fg: success }), - s(" • ", { fg: fgDim }), - s("JSX (React)", { fg: accentWarm }), + ui.row({ gap: 1, mt: 1, mb: 1, items: "stretch" }, [ + codePanel( + "Custom TypeScript", + "Typed API with direct state updates", + ["Custom API", "Type-safe"], + typescriptCode, + borderBright, + true, + ), + codePanel( + "JSX (React)", + "React-style JSX with hooks state", + ["JSX", "React hooks"], + jsxCode, + border, + false, + ), + ]), + + ui.box( + { + border: "rounded", + mb: 1, + px: 1, + py: 0, + style: { bg: panel, fg: border }, + }, + [ + ui.row({ justify: "between", items: "center", gap: 2 }, [ + ui.richText([ + s("$ npm i ", { fg: fgDim }), + s("@rezi-ui/core @rezi-ui/node @rezi-ui/jsx", { fg: accent, bold: true }), + ]), + ui.richText([ + s("Custom TypeScript", { fg: success }), + s(" • ", { fg: fgDim }), + s("JSX (React)", { fg: accentWarm }), + ]), ]), - ]), - ], - ), + ], + ), ]), ]), ]); diff --git a/examples/release-demo/src/scenario2-gif.ts b/examples/release-demo/src/scenario2-gif.ts index 12953432..c2f5393c 100644 --- a/examples/release-demo/src/scenario2-gif.ts +++ b/examples/release-demo/src/scenario2-gif.ts @@ -72,15 +72,15 @@ function spark(tick: number, base: number, amp: number, freq: number, points = 2 } function metricTile(label: string, value: string, suffix: string, color: typeof accent) { - return ui.box({ flex: 1, border: "rounded", px: 1, py: 0, style: { bg: panelRaised, fg: border } }, [ - ui.column({ gap: 0 }, [ - ui.text(label, { style: { fg: fgDim } }), - ui.richText([ - s(value, { fg: color, bold: true }), - s(` ${suffix}`, { fg: fgMuted }), + return ui.box( + { flex: 1, border: "rounded", px: 1, py: 0, style: { bg: panelRaised, fg: border } }, + [ + ui.column({ gap: 0 }, [ + ui.text(label, { style: { fg: fgDim } }), + ui.richText([s(value, { fg: color, bold: true }), s(` ${suffix}`, { fg: fgMuted })]), ]), - ]), - ]); + ], + ); } type State = { tick: number }; @@ -96,7 +96,7 @@ app.view(({ tick }) => { const allDone = progress.every((v) => v >= 1); const activeIndex = allDone ? -1 : progress.findIndex((v) => v < 1); const completedCount = progress.filter((v) => v >= 1).length; - const activeStage = activeIndex >= 0 ? stages[activeIndex]?.label ?? "" : "Observe"; + const activeStage = activeIndex >= 0 ? (stages[activeIndex]?.label ?? "") : "Observe"; const stageText = activeStage.padEnd(10, " "); const statusText = (allDone ? "COMPLETED" : "IN PROGRESS").padEnd(11, " "); @@ -156,139 +156,141 @@ app.view(({ tick }) => { ), ui.row({ flex: 1, gap: 1, px: 1, py: 1, items: "stretch" }, [ - ui.box({ flex: 3, border: "rounded", px: 1, style: { bg: panelAlt, fg: borderBright } }, [ - ui.column( - { gap: 0 }, - stages.flatMap((stage, i) => { - const p = progress[i] ?? 0; - const done = p >= 1; - const active = i === activeIndex; - const activeFrames = ["-", "\\", "|", "/"] as const; - const activeFrame = activeFrames[tick % activeFrames.length] ?? "-"; - const icon = done ? "[x]" : active ? `[${activeFrame}]` : "[ ]"; - const iconColor = done ? success : active ? accent : fgDim; - const nameColor = done ? success : active ? fg : fgMuted; - const statusLabel = done ? "done" : active ? `${Math.round(p * 100)}%` : "pending"; - const statusColor = done ? success : active ? accentWarm : fgDim; + ui.box({ flex: 3, border: "rounded", px: 1, style: { bg: panelAlt, fg: borderBright } }, [ + ui.column( + { gap: 0 }, + stages.flatMap((stage, i) => { + const p = progress[i] ?? 0; + const done = p >= 1; + const active = i === activeIndex; + const activeFrames = ["-", "\\", "|", "/"] as const; + const activeFrame = activeFrames[tick % activeFrames.length] ?? "-"; + const icon = done ? "[x]" : active ? `[${activeFrame}]` : "[ ]"; + const iconColor = done ? success : active ? accent : fgDim; + const nameColor = done ? success : active ? fg : fgMuted; + const statusLabel = done ? "done" : active ? `${Math.round(p * 100)}%` : "pending"; + const statusColor = done ? success : active ? accentWarm : fgDim; - return [ - ui.row({ gap: 1 }, [ - ui.text(icon, { style: { fg: iconColor, bold: done || active } }), - // Leading spacer absorbs occasional first-cell clipping on some terminal/font combos. - ui.text(` ${stage.label.padEnd(12, " ")}`, { style: { fg: nameColor, bold: active } }), - ui.text(statusLabel.padStart(8, " "), { - style: { fg: statusColor, bold: done || active }, - }), - ]), - active - ? ui.progress(p, { - variant: "bar", - trackStyle: { fg: fgDim }, - style: { fg: accent }, - }) - : done - ? ui.progress(1, { variant: "minimal", style: { fg: success } }) - : ui.progress(0, { variant: "minimal", style: { fg: fgDim } }), - ]; - }), - ), - ]), - - ui.column({ flex: 2, gap: 1 }, [ - ui.row({ gap: 1 }, [ - metricTile("FPS", String(fps), "avg", success), - metricTile("Memory", String(mem), "MB", accentWarm), - metricTile("P95", String(p95), "ms", warning), + return [ + ui.row({ gap: 1 }, [ + ui.text(icon, { style: { fg: iconColor, bold: done || active } }), + // Leading spacer absorbs occasional first-cell clipping on some terminal/font combos. + ui.text(` ${stage.label.padEnd(12, " ")}`, { + style: { fg: nameColor, bold: active }, + }), + ui.text(statusLabel.padStart(8, " "), { + style: { fg: statusColor, bold: done || active }, + }), + ]), + active + ? ui.progress(p, { + variant: "bar", + trackStyle: { fg: fgDim }, + style: { fg: accent }, + }) + : done + ? ui.progress(1, { variant: "minimal", style: { fg: success } }) + : ui.progress(0, { variant: "minimal", style: { fg: fgDim } }), + ]; + }), + ), ]), - ui.box({ border: "rounded", px: 1, style: { bg: panelAlt, fg: border } }, [ - ui.column({ gap: 0 }, [ - ui.row({ gap: 1, items: "center" }, [ - ui.text("Runtime Budget", { style: { fg: fgMuted, bold: true } }), - ui.richText([s("•", { fg: fgDim })]), + ui.column({ flex: 2, gap: 1 }, [ + ui.row({ gap: 1 }, [ + metricTile("FPS", String(fps), "avg", success), + metricTile("Memory", String(mem), "MB", accentWarm), + metricTile("P95", String(p95), "ms", warning), + ]), + + ui.box({ border: "rounded", px: 1, style: { bg: panelAlt, fg: border } }, [ + ui.column({ gap: 0 }, [ + ui.row({ gap: 1, items: "center" }, [ + ui.text("Runtime Budget", { style: { fg: fgMuted, bold: true } }), + ui.richText([s("•", { fg: fgDim })]), + ui.richText([ + s(allDone ? "stable" : "active", { + fg: allDone ? success : accentWarm, + bold: true, + }), + ]), + ]), + ui.divider({ char: "─" }), + ui.miniChart( + [ + { label: "FPS", value: fps, max: 75 }, + { label: "MEM", value: mem, max: 120 }, + { label: "P95", value: p95, max: 20 }, + { label: "WRK", value: workers, max: 8 }, + ], + { variant: "pills" }, + ), + ui.sparkline(spark(tick, 58, 4.8, 0.22), { + min: 46, + max: 70, + style: { fg: success }, + }), + ]), + ]), + + ui.box({ border: "rounded", px: 1, flex: 1, style: { bg: panelAlt, fg: border } }, [ + ui.column({ gap: 0, flex: 1 }, [ + ui.row({ gap: 2 }, [ + ui.status(stageState(progress[0] ?? 0), { label: "Bootstrap", showLabel: true }), + ui.status(stageState(progress[1] ?? 0), { label: "Compile", showLabel: true }), + ui.status(stageState(progress[2] ?? 0), { label: "Tests", showLabel: true }), + ]), + ui.row({ gap: 2 }, [ + ui.status(stageState(progress[3] ?? 0), { label: "Lint", showLabel: true }), + ui.status(stageState(progress[4] ?? 0), { label: "Bundle", showLabel: true }), + ui.status(stageState(progress[5] ?? 0), { label: "Publish", showLabel: true }), + ]), ui.richText([ - s(allDone ? "stable" : "active", { + s("Release Status: ", { fg: fgDim }), + s(allDone ? "all checks passing" : `${activeStage} running`, { fg: allDone ? success : accentWarm, bold: true, }), ]), - ]), - ui.divider({ char: "─" }), - ui.miniChart( - [ - { label: "FPS", value: fps, max: 75 }, - { label: "MEM", value: mem, max: 120 }, - { label: "P95", value: p95, max: 20 }, - { label: "WRK", value: workers, max: 8 }, - ], - { variant: "pills" }, - ), - ui.sparkline(spark(tick, 58, 4.8, 0.22), { - min: 46, - max: 70, - style: { fg: success }, - }), - ]), - ]), - - ui.box({ border: "rounded", px: 1, flex: 1, style: { bg: panelAlt, fg: border } }, [ - ui.column({ gap: 0, flex: 1 }, [ - ui.row({ gap: 2 }, [ - ui.status(stageState(progress[0] ?? 0), { label: "Bootstrap", showLabel: true }), - ui.status(stageState(progress[1] ?? 0), { label: "Compile", showLabel: true }), - ui.status(stageState(progress[2] ?? 0), { label: "Tests", showLabel: true }), - ]), - ui.row({ gap: 2 }, [ - ui.status(stageState(progress[3] ?? 0), { label: "Lint", showLabel: true }), - ui.status(stageState(progress[4] ?? 0), { label: "Bundle", showLabel: true }), - ui.status(stageState(progress[5] ?? 0), { label: "Publish", showLabel: true }), - ]), - ui.richText([ - s("Release Status: ", { fg: fgDim }), - s(allDone ? "all checks passing" : `${activeStage} running`, { - fg: allDone ? success : accentWarm, - bold: true, - }), - ]), - ui.divider({ char: "─" }), - ui.row({ justify: "between", items: "center" }, [ - ui.text("Component Load", { style: { fg: fgMuted, bold: true } }), + ui.divider({ char: "─" }), + ui.row({ justify: "between", items: "center" }, [ + ui.text("Component Load", { style: { fg: fgMuted, bold: true } }), + ui.richText([ + s(`${loadTotal}%`, { fg: accentWarm, bold: true }), + s(" total", { fg: fgDim }), + ]), + ]), + ui.barChart( + [ + { label: "core", value: coreLoad, variant: "info" }, + { label: "node", value: nodeLoad, variant: "success" }, + { label: "jsx", value: jsxLoad, variant: "warning" }, + ], + { + showValues: true, + maxBarLength: 20, + style: { fg: fgMuted }, + }, + ), ui.richText([ - s(`${loadTotal}%`, { fg: accentWarm, bold: true }), - s(" total", { fg: fgDim }), + s("core ", { fg: success }), + s(`${coreLoad}%`, { fg: fg, bold: true }), + s(" • ", { fg: fgDim }), + s("node ", { fg: accent }), + s(`${nodeLoad}%`, { fg: fg, bold: true }), + s(" • ", { fg: fgDim }), + s("jsx ", { fg: warning }), + s(`${jsxLoad}%`, { fg: fg, bold: true }), ]), + ui.sparkline(spark(tick, 49, 5.5, 0.2), { + min: 38, + max: 62, + style: { fg: accent }, + }), ]), - ui.barChart( - [ - { label: "core", value: coreLoad, variant: "info" }, - { label: "node", value: nodeLoad, variant: "success" }, - { label: "jsx", value: jsxLoad, variant: "warning" }, - ], - { - showValues: true, - maxBarLength: 20, - style: { fg: fgMuted }, - }, - ), - ui.richText([ - s("core ", { fg: success }), - s(`${coreLoad}%`, { fg: fg, bold: true }), - s(" • ", { fg: fgDim }), - s("node ", { fg: accent }), - s(`${nodeLoad}%`, { fg: fg, bold: true }), - s(" • ", { fg: fgDim }), - s("jsx ", { fg: warning }), - s(`${jsxLoad}%`, { fg: fg, bold: true }), - ]), - ui.sparkline(spark(tick, 49, 5.5, 0.2), { - min: 38, - max: 62, - style: { fg: accent }, - }), ]), ]), ]), - ]), ui.box( { @@ -302,9 +304,12 @@ app.view(({ tick }) => { [ ui.row({ justify: "between", items: "center", gap: 2 }, [ ui.row({ gap: 1, items: "center" }, [ - ui.badge(allDone ? "Pipeline complete" : `Stage ${completedCount + 1}/${stages.length}`, { - variant: allDone ? "success" : "info", - }), + ui.badge( + allDone ? "Pipeline complete" : `Stage ${completedCount + 1}/${stages.length}`, + { + variant: allDone ? "success" : "info", + }, + ), ui.badge("Stable frame output", { variant: "default" }), ui.badge("Low-latency renderer", { variant: "default" }), ]), diff --git a/packages/create-rezi/src/__tests__/scaffold.test.ts b/packages/create-rezi/src/__tests__/scaffold.test.ts index 1acbb335..0b026ac5 100644 --- a/packages/create-rezi/src/__tests__/scaffold.test.ts +++ b/packages/create-rezi/src/__tests__/scaffold.test.ts @@ -1,7 +1,7 @@ -import { assert, test } from "@rezi-ui/testkit"; import { mkdtemp, readFile } from "node:fs/promises"; -import { join } from "node:path"; import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { assert, test } from "@rezi-ui/testkit"; import { createProject, normalizeTemplateName, toValidPackageName } from "../scaffold.js"; test("normalizeTemplateName accepts friendly aliases", () => { diff --git a/packages/create-rezi/src/index.ts b/packages/create-rezi/src/index.ts index 2fcf6e6d..e430ab14 100644 --- a/packages/create-rezi/src/index.ts +++ b/packages/create-rezi/src/index.ts @@ -1,8 +1,9 @@ #!/usr/bin/env node import { spawnSync } from "node:child_process"; -import { createInterface } from "node:readline/promises"; -import { cwd, exit, stdin, stdout } from "node:process"; import { relative, resolve } from "node:path"; +import { cwd, exit, stdin, stdout } from "node:process"; +import { createInterface } from "node:readline/promises"; +import { fileURLToPath } from "node:url"; import { TEMPLATE_DEFINITIONS, createProject, @@ -12,7 +13,6 @@ import { toDisplayName, toValidPackageName, } from "./scaffold.js"; -import { fileURLToPath } from "node:url"; type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; @@ -82,15 +82,15 @@ function parseArgs(argv: string[]): CliOptions { } function printHelp(): void { - stdout.write(`create-rezi\n\n`); - stdout.write(`Usage:\n`); - stdout.write(` npm create rezi my-app\n\n`); - stdout.write(`Options:\n`); - stdout.write(` --template, -t Choose a template\n`); - stdout.write(` --no-install Skip dependency install\n`); - stdout.write(` --pm Choose a package manager\n`); - stdout.write(` --list-templates Show available templates\n`); - stdout.write(` --help, -h Show this help\n`); + stdout.write("create-rezi\n\n"); + stdout.write("Usage:\n"); + stdout.write(" npm create rezi my-app\n\n"); + stdout.write("Options:\n"); + stdout.write(" --template, -t Choose a template\n"); + stdout.write(" --no-install Skip dependency install\n"); + stdout.write(" --pm Choose a package manager\n"); + stdout.write(" --list-templates Show available templates\n"); + stdout.write(" --help, -h Show this help\n"); } function printTemplates(): void { @@ -103,6 +103,7 @@ function printTemplates(): void { } function detectPackageManager(): PackageManager { + // biome-ignore lint/complexity/useLiteralKeys: process.env uses an index signature in TS. const ua = process.env["npm_config_user_agent"] ?? ""; if (ua.startsWith("pnpm/")) return "pnpm"; if (ua.startsWith("yarn/")) return "yarn"; @@ -113,7 +114,12 @@ function detectPackageManager(): PackageManager { function resolvePackageManager(value?: string): PackageManager { if (!value) return detectPackageManager(); const normalized = value.trim().toLowerCase(); - if (normalized === "npm" || normalized === "pnpm" || normalized === "yarn" || normalized === "bun") { + if ( + normalized === "npm" || + normalized === "pnpm" || + normalized === "yarn" || + normalized === "bun" + ) { return normalized as PackageManager; } throw new Error(`Unsupported package manager: ${value}`); @@ -189,8 +195,7 @@ async function main(): Promise { try { const targetDir = options.targetDir || (await promptText(rl, "Project name", "rezi-app")); - const templateInput = - options.template || (await promptTemplate(rl)); + const templateInput = options.template || (await promptTemplate(rl)); const templateKey = normalizeTemplateName(templateInput); if (!templateKey) { stdout.write(`\nUnknown template: ${templateInput}\n`); diff --git a/packages/create-rezi/src/scaffold.ts b/packages/create-rezi/src/scaffold.ts index 4f569ef9..0d9d9b89 100644 --- a/packages/create-rezi/src/scaffold.ts +++ b/packages/create-rezi/src/scaffold.ts @@ -1,4 +1,4 @@ -import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises"; +import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises"; import { basename, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -38,9 +38,7 @@ export const TEMPLATE_DEFINITIONS: readonly TemplateDefinition[] = [ }, ] as const; -const TEMPLATE_BY_KEY = new Map( - TEMPLATE_DEFINITIONS.map((template) => [template.key, template]), -); +const TEMPLATE_BY_KEY = new Map(TEMPLATE_DEFINITIONS.map((template) => [template.key, template])); const PACKAGE_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/; diff --git a/packages/create-rezi/templates/dashboard/src/main.ts b/packages/create-rezi/templates/dashboard/src/main.ts index 1c126d81..f2fcc3b8 100644 --- a/packages/create-rezi/templates/dashboard/src/main.ts +++ b/packages/create-rezi/templates/dashboard/src/main.ts @@ -79,14 +79,17 @@ function clamp(value: number, min: number, max: number) { } function panel(title: string, children: ReturnType[], flex = 1) { - return ui.box({ - title, - flex, - border: "rounded", - px: 1, - py: 0, - style: { bg: colors.panel, fg: colors.muted }, - }, children); + return ui.box( + { + title, + flex, + border: "rounded", + px: 1, + py: 0, + style: { bg: colors.panel, fg: colors.muted }, + }, + children, + ); } app.view((state) => { @@ -106,59 +109,71 @@ app.view((state) => { ]), ui.row({ flex: 1, gap: 1, items: "stretch" }, [ - panel("Services", [ - ui.column( - { gap: 0 }, - visible.map((svc, index) => { - const active = index === state.selected; - return ui.row( - { - key: svc.name, - gap: 1, - style: { - bg: active ? colors.inkSoft : undefined, - fg: active ? colors.accent : colors.muted, + panel( + "Services", + [ + ui.column( + { gap: 0 }, + visible.map((svc, index) => { + const active = index === state.selected; + return ui.row( + { + key: svc.name, + gap: 1, + style: { + bg: active ? colors.inkSoft : undefined, + fg: active ? colors.accent : colors.muted, + }, }, - }, - [ - ui.text(active ? ">" : " "), - ui.text(svc.name, { bold: active }), - ui.text(`(${svc.region})`, { fg: colors.muted }), - ui.text(svc.status.toUpperCase(), { - fg: statusColor(svc.status), - bold: true, - }), - ], - ); - }), - ), - ], 1), - - panel("Service Health", [ - ui.column({ gap: 1 }, [ - ui.text(selected ? selected.name : "-", { fg: colors.accent, bold: true }), - ui.row({ gap: 2 }, [ - ui.text(`Latency: ${selected ? selected.latencyMs : "-"} ms`), - ui.text(`Errors: ${selected ? selected.errorRate : "-"}%`, { - fg: selected && selected.errorRate > 2 ? colors.warn : colors.muted, + [ + ui.text(active ? ">" : " "), + ui.text(svc.name, { bold: active }), + ui.text(`(${svc.region})`, { fg: colors.muted }), + ui.text(svc.status.toUpperCase(), { + fg: statusColor(svc.status), + bold: true, + }), + ], + ); }), + ), + ], + 1, + ), + + panel( + "Service Health", + [ + ui.column({ gap: 1 }, [ + ui.text(selected ? selected.name : "-", { fg: colors.accent, bold: true }), + ui.row({ gap: 2 }, [ + ui.text(`Latency: ${selected ? selected.latencyMs : "-"} ms`), + ui.text(`Errors: ${selected ? selected.errorRate : "-"}%`, { + fg: selected && selected.errorRate > 2 ? colors.warn : colors.muted, + }), + ]), + ui.divider({ char: "-" }), + ui.text("Active incidents", { fg: colors.muted }), + ...incidents.map((line) => ui.text(`- ${line}`)), ]), - ui.divider({ char: "-" }), - ui.text("Active incidents", { fg: colors.muted }), - ...incidents.map((line) => ui.text(`- ${line}`)), - ]), - ], 2), - - panel("Activity", [ - ui.column({ gap: 1 }, [ - ui.text("Recent deploys", { fg: colors.muted }), - ...activity.map((line) => ui.text(`- ${line}`)), - ui.divider({ char: "-" }), - ui.text("Escalations", { fg: colors.muted }), - ui.text("- On-call rotation starts in 2h"), - ui.text("- 3 alerts awaiting acknowledgement"), - ]), - ], 1), + ], + 2, + ), + + panel( + "Activity", + [ + ui.column({ gap: 1 }, [ + ui.text("Recent deploys", { fg: colors.muted }), + ...activity.map((line) => ui.text(`- ${line}`)), + ui.divider({ char: "-" }), + ui.text("Escalations", { fg: colors.muted }), + ui.text("- On-call rotation starts in 2h"), + ui.text("- 3 alerts awaiting acknowledgement"), + ]), + ], + 1, + ), ]), state.showHelp @@ -184,54 +199,58 @@ app.view((state) => { ]), ], ) - : ui.box( - { px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, - [ - ui.row({ justify: "between", items: "center" }, [ - ui.text("Status: nominal"), - ui.row({ gap: 1 }, [ - ui.kbd("up/down"), - ui.text("Move"), - ui.kbd("f"), - ui.text("Filter"), - ui.kbd("?"), - ui.text("Help"), - ]), + : ui.box({ px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, [ + ui.row({ justify: "between", items: "center" }, [ + ui.text("Status: nominal"), + ui.row({ gap: 1 }, [ + ui.kbd("up/down"), + ui.text("Move"), + ui.kbd("f"), + ui.text("Filter"), + ui.kbd("?"), + ui.text("Help"), ]), - ], - ), + ]), + ]), ]); }); app.keys({ - "q": () => app.stop(), + q: () => app.stop(), "ctrl+c": () => app.stop(), - "up": () => + up: () => app.update((s) => { const list = filterServices(s.filter); return { ...s, selected: clamp(s.selected - 1, 0, Math.max(0, list.length - 1)) }; }), - "down": () => + down: () => app.update((s) => { const list = filterServices(s.filter); return { ...s, selected: clamp(s.selected + 1, 0, Math.max(0, list.length - 1)) }; }), - "k": () => + k: () => app.update((s) => { const list = filterServices(s.filter); return { ...s, selected: clamp(s.selected - 1, 0, Math.max(0, list.length - 1)) }; }), - "j": () => + j: () => app.update((s) => { const list = filterServices(s.filter); return { ...s, selected: clamp(s.selected + 1, 0, Math.max(0, list.length - 1)) }; }), - "f": () => + f: () => app.update((s) => { - const next: Filter = s.filter === "all" ? "warning" : s.filter === "warning" ? "healthy" : s.filter === "healthy" ? "down" : "all"; + const next: Filter = + s.filter === "all" + ? "warning" + : s.filter === "warning" + ? "healthy" + : s.filter === "healthy" + ? "down" + : "all"; return { ...s, filter: next, selected: 0 }; }), - "enter": () => + enter: () => app.update((s) => { const list = filterServices(s.filter); const svc = list[s.selected]; diff --git a/packages/create-rezi/templates/file-browser/src/main.ts b/packages/create-rezi/templates/file-browser/src/main.ts index fdc3690d..141fa2f9 100644 --- a/packages/create-rezi/templates/file-browser/src/main.ts +++ b/packages/create-rezi/templates/file-browser/src/main.ts @@ -17,24 +17,60 @@ const tree: Tree = { "/": [ { name: "src", type: "dir", size: "-", modified: "2026-02-10", preview: "" }, { name: "docs", type: "dir", size: "-", modified: "2026-02-08", preview: "" }, - { name: "package.json", type: "file", size: "2 KB", modified: "2026-02-07", preview: "{\n \"name\": \"__APP_NAME__\"\n}" }, + { + name: "package.json", + type: "file", + size: "2 KB", + modified: "2026-02-07", + preview: '{\n "name": "__APP_NAME__"\n}', + }, { name: ".env", type: "file", size: "1 KB", modified: "2026-02-05", preview: "API_KEY=***" }, ], "/src": [ - { name: "main.ts", type: "file", size: "4 KB", modified: "2026-02-10", preview: "import { createApp } from '@rezi-ui/core'" }, + { + name: "main.ts", + type: "file", + size: "4 KB", + modified: "2026-02-10", + preview: "import { createApp } from '@rezi-ui/core'", + }, { name: "components", type: "dir", size: "-", modified: "2026-02-09", preview: "" }, { name: "styles", type: "dir", size: "-", modified: "2026-02-09", preview: "" }, ], "/src/components": [ - { name: "Panel.ts", type: "file", size: "1 KB", modified: "2026-02-09", preview: "export function Panel() {}" }, - { name: "StatusBar.ts", type: "file", size: "1 KB", modified: "2026-02-09", preview: "export function StatusBar() {}" }, + { + name: "Panel.ts", + type: "file", + size: "1 KB", + modified: "2026-02-09", + preview: "export function Panel() {}", + }, + { + name: "StatusBar.ts", + type: "file", + size: "1 KB", + modified: "2026-02-09", + preview: "export function StatusBar() {}", + }, ], "/docs": [ - { name: "overview.md", type: "file", size: "3 KB", modified: "2026-02-08", preview: "# Overview" }, + { + name: "overview.md", + type: "file", + size: "3 KB", + modified: "2026-02-08", + preview: "# Overview", + }, { name: "usage.md", type: "file", size: "2 KB", modified: "2026-02-08", preview: "# Usage" }, ], "/src/styles": [ - { name: "tokens.ts", type: "file", size: "2 KB", modified: "2026-02-08", preview: "export const colors = {}" }, + { + name: "tokens.ts", + type: "file", + size: "2 KB", + modified: "2026-02-08", + preview: "export const colors = {}", + }, ], }; @@ -104,43 +140,55 @@ app.view((state) => { ]), ui.row({ flex: 1, gap: 1, items: "stretch" }, [ - panel("Files", [ - ui.column( - { gap: 0 }, - entries.map((entry, index) => { - const active = index === state.selected; - const prefix = active ? ">" : " "; - const marker = entry.type === "dir" ? "[D]" : "[F]"; - return ui.text(`${prefix} ${marker} ${entry.name}`, { - key: entry.name, - fg: active ? colors.accent : colors.muted, - bold: active, - }); - }), - ), - ], 1), - - panel("Details", [ - ui.column({ gap: 1 }, [ - ui.text(selected ? selected.name : "-", { fg: colors.accent, bold: true }), - ui.text(`Type: ${selected?.type ?? "-"}`), - ui.text(`Size: ${selected?.size ?? "-"}`), - ui.text(`Modified: ${selected?.modified ?? "-"}`), - ui.divider({ char: "-" }), - ui.text("Preview", { fg: colors.muted }), - ui.text(preview), - ]), - ], 2), - - panel("Pinned", [ - ui.column({ gap: 1 }, [ - ui.text("Last opened", { fg: colors.muted }), - ui.text(state.opened ?? "-"), - ui.text(""), - ui.text("Hidden files"), - ui.text(state.showHidden ? "Visible" : "Hidden"), - ]), - ], 1), + panel( + "Files", + [ + ui.column( + { gap: 0 }, + entries.map((entry, index) => { + const active = index === state.selected; + const prefix = active ? ">" : " "; + const marker = entry.type === "dir" ? "[D]" : "[F]"; + return ui.text(`${prefix} ${marker} ${entry.name}`, { + key: entry.name, + fg: active ? colors.accent : colors.muted, + bold: active, + }); + }), + ), + ], + 1, + ), + + panel( + "Details", + [ + ui.column({ gap: 1 }, [ + ui.text(selected ? selected.name : "-", { fg: colors.accent, bold: true }), + ui.text(`Type: ${selected?.type ?? "-"}`), + ui.text(`Size: ${selected?.size ?? "-"}`), + ui.text(`Modified: ${selected?.modified ?? "-"}`), + ui.divider({ char: "-" }), + ui.text("Preview", { fg: colors.muted }), + ui.text(preview), + ]), + ], + 2, + ), + + panel( + "Pinned", + [ + ui.column({ gap: 1 }, [ + ui.text("Last opened", { fg: colors.muted }), + ui.text(state.opened ?? "-"), + ui.text(""), + ui.text("Hidden files"), + ui.text(state.showHidden ? "Visible" : "Hidden"), + ]), + ], + 1, + ), ]), ui.box({ px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, [ @@ -164,36 +212,36 @@ app.view((state) => { }); app.keys({ - "q": () => app.stop(), + q: () => app.stop(), "ctrl+c": () => app.stop(), - "up": () => + up: () => app.update((s) => { const entries = listEntries(s); return { ...s, selected: clamp(s.selected - 1, 0, Math.max(0, entries.length - 1)) }; }), - "down": () => + down: () => app.update((s) => { const entries = listEntries(s); return { ...s, selected: clamp(s.selected + 1, 0, Math.max(0, entries.length - 1)) }; }), - "k": () => + k: () => app.update((s) => { const entries = listEntries(s); return { ...s, selected: clamp(s.selected - 1, 0, Math.max(0, entries.length - 1)) }; }), - "j": () => + j: () => app.update((s) => { const entries = listEntries(s); return { ...s, selected: clamp(s.selected + 1, 0, Math.max(0, entries.length - 1)) }; }), - "h": () => app.update((s) => ({ ...s, showHidden: !s.showHidden, selected: 0 })), - "backspace": () => + h: () => app.update((s) => ({ ...s, showHidden: !s.showHidden, selected: 0 })), + backspace: () => app.update((s) => ({ ...s, path: parentPath(s.path), selected: 0, })), - "enter": () => + enter: () => app.update((s) => { const entries = listEntries(s); const entry = entries[s.selected]; diff --git a/packages/create-rezi/templates/form-app/src/main.ts b/packages/create-rezi/templates/form-app/src/main.ts index 9d88e2f5..d389d219 100644 --- a/packages/create-rezi/templates/form-app/src/main.ts +++ b/packages/create-rezi/templates/form-app/src/main.ts @@ -69,104 +69,116 @@ app.view((state) => { ]), ui.row({ flex: 1, gap: 1, items: "stretch" }, [ - panel("Sections", [ - ui.column( - { gap: 0 }, - sections.map((label, index) => { - const active = index === state.section; - return ui.text(`${active ? ">" : " "} ${label}`, { - key: label, - fg: active ? colors.accent : colors.muted, - bold: active, - }); - }), - ), - ], 1), + panel( + "Sections", + [ + ui.column( + { gap: 0 }, + sections.map((label, index) => { + const active = index === state.section; + return ui.text(`${active ? ">" : " "} ${label}`, { + key: label, + fg: active ? colors.accent : colors.muted, + bold: active, + }); + }), + ), + ], + 1, + ), - panel("Customer Details", [ - ui.column({ gap: 1 }, [ - ui.field({ - label: "Name", - required: true, - children: ui.input({ - id: "name", - value: state.name, - placeholder: "Ada Lovelace", - onInput: (value) => app.update((s) => ({ ...s, name: value })), + panel( + "Customer Details", + [ + ui.column({ gap: 1 }, [ + ui.field({ + label: "Name", + required: true, + children: ui.input({ + id: "name", + value: state.name, + placeholder: "Ada Lovelace", + onInput: (value) => app.update((s) => ({ ...s, name: value })), + }), }), - }), - ui.field({ - label: "Email", - required: true, - children: ui.input({ - id: "email", - value: state.email, - placeholder: "ada@lovelace.io", - onInput: (value) => app.update((s) => ({ ...s, email: value })), + ui.field({ + label: "Email", + required: true, + children: ui.input({ + id: "email", + value: state.email, + placeholder: "ada@lovelace.io", + onInput: (value) => app.update((s) => ({ ...s, email: value })), + }), }), - }), - ui.field({ - label: "Company", - children: ui.input({ - id: "company", - value: state.company, - placeholder: "Analytical Engines Ltd", - onInput: (value) => app.update((s) => ({ ...s, company: value })), + ui.field({ + label: "Company", + children: ui.input({ + id: "company", + value: state.company, + placeholder: "Analytical Engines Ltd", + onInput: (value) => app.update((s) => ({ ...s, company: value })), + }), }), - }), - ui.field({ - label: "Plan", - children: ui.select({ - id: "plan", - value: state.plan, - options: [ - { value: "starter", label: "Starter" }, - { value: "growth", label: "Growth" }, - { value: "enterprise", label: "Enterprise" }, - ], - onChange: (value) => app.update((s) => ({ ...s, plan: value as Plan })), + ui.field({ + label: "Plan", + children: ui.select({ + id: "plan", + value: state.plan, + options: [ + { value: "starter", label: "Starter" }, + { value: "growth", label: "Growth" }, + { value: "enterprise", label: "Enterprise" }, + ], + onChange: (value) => app.update((s) => ({ ...s, plan: value as Plan })), + }), }), - }), - ui.field({ - label: "Seats", - children: ui.input({ - id: "seats", - value: state.seats, - onInput: (value) => app.update((s) => ({ ...s, seats: value })), + ui.field({ + label: "Seats", + children: ui.input({ + id: "seats", + value: state.seats, + onInput: (value) => app.update((s) => ({ ...s, seats: value })), + }), }), - }), - ui.checkbox({ - id: "newsletter", - label: "Subscribe to release notes", - checked: state.newsletter, - onChange: (checked) => app.update((s) => ({ ...s, newsletter: checked })), - }), - ]), - ], 2), + ui.checkbox({ + id: "newsletter", + label: "Subscribe to release notes", + checked: state.newsletter, + onChange: (checked) => app.update((s) => ({ ...s, newsletter: checked })), + }), + ]), + ], + 2, + ), - panel("Preview", [ - ui.column({ gap: 1 }, [ - ui.text("Summary", { fg: colors.accent, bold: true }), - ui.text(`Name: ${state.name || "-"}`), - ui.text(`Email: ${state.email || "-"}`), - ui.text(`Company: ${state.company || "-"}`), - ui.text(`Plan: ${state.plan}`), - ui.text(`Seats: ${state.seats || "-"}`), - ui.text(`Newsletter: ${state.newsletter ? "Yes" : "No"}`), - ui.divider({ char: "-" }), - ui.text("Notes"), - ui.text(state.notes || "Add internal notes in Review."), - ui.button({ - id: "save", - label: "Save draft", - onPress: () => - app.update((s) => ({ - ...s, - status: `Saved at ${new Date().toLocaleTimeString()}`, - })), - }), - ]), - ], 1), + panel( + "Preview", + [ + ui.column({ gap: 1 }, [ + ui.text("Summary", { fg: colors.accent, bold: true }), + ui.text(`Name: ${state.name || "-"}`), + ui.text(`Email: ${state.email || "-"}`), + ui.text(`Company: ${state.company || "-"}`), + ui.text(`Plan: ${state.plan}`), + ui.text(`Seats: ${state.seats || "-"}`), + ui.text(`Newsletter: ${state.newsletter ? "Yes" : "No"}`), + ui.divider({ char: "-" }), + ui.text("Notes"), + ui.text(state.notes || "Add internal notes in Review."), + ui.button({ + id: "save", + label: "Save draft", + onPress: () => + app.update((s) => ({ + ...s, + status: `Saved at ${new Date().toLocaleTimeString()}`, + })), + }), + ]), + ], + 1, + ), ]), ui.box({ px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, [ @@ -188,7 +200,7 @@ app.view((state) => { }); app.keys({ - "q": () => app.stop(), + q: () => app.stop(), "ctrl+c": () => app.stop(), "ctrl+s": () => app.update((s) => ({ diff --git a/packages/create-rezi/templates/streaming-viewer/src/main.ts b/packages/create-rezi/templates/streaming-viewer/src/main.ts index 38866700..125fdab0 100644 --- a/packages/create-rezi/templates/streaming-viewer/src/main.ts +++ b/packages/create-rezi/templates/streaming-viewer/src/main.ts @@ -17,26 +17,10 @@ const streams: readonly Stream[] = [ ]; const chatByStream: Record = { - "city-cam-01": [ - "switch to night mode?", - "pan left a bit", - "traffic spike @ 18:00", - ], - "forest-node": [ - "birdsong level: high", - "new fox spotted", - "wind noise up", - ], - "harbor-live": [ - "dock 3 unloading", - "signal jitter", - "fog incoming", - ], - "lab-monitor": [ - "temperature stable", - "note: calibrate sensor", - "airflow normal", - ], + "city-cam-01": ["switch to night mode?", "pan left a bit", "traffic spike @ 18:00"], + "forest-node": ["birdsong level: high", "new fox spotted", "wind noise up"], + "harbor-live": ["dock 3 unloading", "signal jitter", "fog incoming"], + "lab-monitor": ["temperature stable", "note: calibrate sensor", "airflow normal"], }; type State = { @@ -93,40 +77,52 @@ app.view((state) => { ]), ui.row({ flex: 1, gap: 1, items: "stretch" }, [ - panel("Streams", [ - ui.column( - { gap: 0 }, - streams.map((stream, index) => { - const active = index === state.selected; - const prefix = active ? ">" : " "; - return ui.text(`${prefix} ${stream.name}`, { - key: stream.name, - fg: active ? colors.accent : colors.muted, - bold: active, - }); - }), - ), - ], 1), + panel( + "Streams", + [ + ui.column( + { gap: 0 }, + streams.map((stream, index) => { + const active = index === state.selected; + const prefix = active ? ">" : " "; + return ui.text(`${prefix} ${stream.name}`, { + key: stream.name, + fg: active ? colors.accent : colors.muted, + bold: active, + }); + }), + ), + ], + 1, + ), ui.column({ flex: 2, gap: 1, items: "stretch" }, [ - panel("Viewer", [ - ui.column({ gap: 1 }, [ - ui.text(current ? current.name : "-", { fg: colors.accent, bold: true }), - ui.text(`Category: ${current?.category ?? "-"}`), - ui.text(`Viewers: ${current?.viewers ?? 0}`), - ui.text(`Health: ${current?.health ?? "-"}`, { - fg: current?.health === "warning" ? colors.warn : colors.ok, - }), - ui.text(`Bitrate: ${current?.bitrate ?? 0} Mbps`), - ui.progress(bufferValue, { label: "Buffer", showPercent: true }), - ]), - ], 1), - panel("Chat", [ - ui.column({ gap: 1 }, [ - ui.text("Live chat", { fg: colors.muted }), - ...chat.map((line) => ui.text(`- ${line}`)), - ]), - ], 1), + panel( + "Viewer", + [ + ui.column({ gap: 1 }, [ + ui.text(current ? current.name : "-", { fg: colors.accent, bold: true }), + ui.text(`Category: ${current?.category ?? "-"}`), + ui.text(`Viewers: ${current?.viewers ?? 0}`), + ui.text(`Health: ${current?.health ?? "-"}`, { + fg: current?.health === "warning" ? colors.warn : colors.ok, + }), + ui.text(`Bitrate: ${current?.bitrate ?? 0} Mbps`), + ui.progress(bufferValue, { label: "Buffer", showPercent: true }), + ]), + ], + 1, + ), + panel( + "Chat", + [ + ui.column({ gap: 1 }, [ + ui.text("Live chat", { fg: colors.muted }), + ...chat.map((line) => ui.text(`- ${line}`)), + ]), + ], + 1, + ), ]), ]), @@ -149,30 +145,30 @@ app.view((state) => { }); app.keys({ - "q": () => app.stop(), + q: () => app.stop(), "ctrl+c": () => app.stop(), - "up": () => + up: () => app.update((s) => ({ ...s, selected: clamp(s.selected - 1, 0, streams.length - 1), })), - "down": () => + down: () => app.update((s) => ({ ...s, selected: clamp(s.selected + 1, 0, streams.length - 1), })), - "k": () => + k: () => app.update((s) => ({ ...s, selected: clamp(s.selected - 1, 0, streams.length - 1), })), - "j": () => + j: () => app.update((s) => ({ ...s, selected: clamp(s.selected + 1, 0, streams.length - 1), })), - "space": () => app.update((s) => ({ ...s, paused: !s.paused })), - "f": () => app.update((s) => ({ ...s, follow: !s.follow })), + space: () => app.update((s) => ({ ...s, paused: !s.paused })), + f: () => app.update((s) => ({ ...s, follow: !s.follow })), }); await app.start(); From e1d41cd0f36e656c5c4969d9de182081e757bf86 Mon Sep 17 00:00:00 2001 From: Rezo Date: Tue, 10 Feb 2026 19:06:15 +0400 Subject: [PATCH 2/2] Add testkit reference to create-rezi build --- packages/create-rezi/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-rezi/tsconfig.json b/packages/create-rezi/tsconfig.json index f1c330c2..d18fac19 100644 --- a/packages/create-rezi/tsconfig.json +++ b/packages/create-rezi/tsconfig.json @@ -6,5 +6,6 @@ "outDir": "./dist", "lib": ["ES2022"] }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts"], + "references": [{ "path": "../testkit" }] }