Skip to content

Commit a2a5627

Browse files
committed
feat(create-rezi): add starship template and register scaffold
1 parent cee18ba commit a2a5627

File tree

25 files changed

+4348
-3
lines changed

25 files changed

+4348
-3
lines changed

packages/create-rezi/src/__tests__/scaffold.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,21 @@ test("normalizeTemplateName accepts friendly aliases", () => {
2929
assert.equal(normalizeTemplateName("mini"), "minimal");
3030
assert.equal(normalizeTemplateName("basic"), "minimal");
3131
assert.equal(normalizeTemplateName("utility"), "minimal");
32+
assert.equal(normalizeTemplateName("starship"), "starship");
33+
assert.equal(normalizeTemplateName("ship"), "starship");
34+
assert.equal(normalizeTemplateName("bridge"), "starship");
35+
assert.equal(normalizeTemplateName("command"), "starship");
3236
});
3337

3438
test("template keys match template directories and include highlights", async () => {
35-
const expectedKeys = ["dashboard", "stress-test", "cli-tool", "animation-lab", "minimal"];
39+
const expectedKeys = [
40+
"dashboard",
41+
"stress-test",
42+
"cli-tool",
43+
"animation-lab",
44+
"minimal",
45+
"starship",
46+
];
3647
const keys = TEMPLATE_DEFINITIONS.map((template) => template.key);
3748
assert.equal(keys.join(","), expectedKeys.join(","));
3849

packages/create-rezi/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ function printHelp(): void {
8888
stdout.write(" bun create rezi my-app\n\n");
8989
stdout.write("Options:\n");
9090
stdout.write(
91-
" --template, -t <name> dashboard | stress-test | cli-tool | animation-lab | minimal\n",
91+
" --template, -t <name> dashboard | stress-test | cli-tool | animation-lab | minimal | starship\n",
9292
);
9393
stdout.write(" --no-install Skip dependency install\n");
9494
stdout.write(" --pm <npm|pnpm|yarn|bun> Choose a package manager\n");

packages/create-rezi/src/scaffold.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
22
import { basename, join, resolve } from "node:path";
33
import { fileURLToPath } from "node:url";
44

5-
export type TemplateKey = "dashboard" | "stress-test" | "cli-tool" | "animation-lab" | "minimal";
5+
export type TemplateKey =
6+
| "dashboard"
7+
| "stress-test"
8+
| "cli-tool"
9+
| "animation-lab"
10+
| "minimal"
11+
| "starship";
612

713
export type TemplateDefinition = {
814
key: TemplateKey;
@@ -77,6 +83,20 @@ export const TEMPLATE_DEFINITIONS: readonly TemplateDefinition[] = [
7783
],
7884
dir: "minimal",
7985
},
86+
{
87+
key: "starship",
88+
label: "Starship Command Console",
89+
description:
90+
"Multi-deck command console showcasing routing, animation, charts, forms, and the full widget catalog",
91+
safetyTag: "safe-default",
92+
safetyNote:
93+
"Feature-rich showcase template with moderate CPU usage from animation hooks and live telemetry.",
94+
highlights: [
95+
"six-screen bridge with routing, animated gauges, live telemetry charts, and crew management",
96+
"command palette, modal dialogs, toast notifications, forms, split panes, canvas, and theme cycling with keybinding modes",
97+
],
98+
dir: "starship",
99+
},
80100
] as const;
81101

82102
const TEMPLATE_BY_KEY = new Map(TEMPLATE_DEFINITIONS.map((template) => [template.key, template]));
@@ -98,6 +118,10 @@ TEMPLATE_ALIASES.set("motion", "animation-lab");
98118
TEMPLATE_ALIASES.set("mini", "minimal");
99119
TEMPLATE_ALIASES.set("basic", "minimal");
100120
TEMPLATE_ALIASES.set("utility", "minimal");
121+
TEMPLATE_ALIASES.set("starship", "starship");
122+
TEMPLATE_ALIASES.set("ship", "starship");
123+
TEMPLATE_ALIASES.set("bridge", "starship");
124+
TEMPLATE_ALIASES.set("command", "starship");
101125

102126
const PACKAGE_NAME_RE = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
103127

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# __APP_NAME__
2+
3+
Scaffolded with `create-rezi` using the **__TEMPLATE_LABEL__** template.
4+
5+
## Concept
6+
7+
`__APP_NAME__` is a multi-deck starship operations console. It demonstrates routed application architecture, deterministic telemetry simulation, rich widgets, layered overlays, and declarative animation hooks in a cohesive, production-like TUI.
8+
9+
## Decks
10+
11+
- `bridge`: Command overview with animated schematic, gauges, sparkline/line chart telemetry, and system health lanes.
12+
- `engineering`: Split-pane reactor/diagnostics deck with tree navigation, accordion diagnostics, heatmap, and animated subsystem bring-up.
13+
- `crew`: Manifest deck with searchable/sortable table, master-detail, assignment editor form, and pagination.
14+
- `comms`: Channel tabs, logs console, rich text inspection, emergency callouts, and modal hail composer.
15+
- `cargo`: High-volume cargo analytics with bar/scatter charts, virtualized manifest, sorting/filter controls, and priority editor.
16+
- `settings`: Full settings form, validation callouts, theme preview, keybinding reference, and reset confirmation dialog.
17+
18+
## Keybindings
19+
20+
### Global
21+
22+
| Key | Command |
23+
|---|---|
24+
| `q`, `ctrl+c` | Quit |
25+
| `1-6` | Navigate decks (`bridge``settings`) |
26+
| `tab`, `shift+tab` | Next/previous deck |
27+
| `t` | Cycle theme |
28+
| `ctrl+p` | Toggle command palette |
29+
| `?` | Toggle help modal |
30+
| `space` | Pause/resume simulation |
31+
| `g`, `y`, `r` | Set alert level (green/yellow/red) |
32+
33+
### Bridge
34+
35+
| Key | Command |
36+
|---|---|
37+
| `a` | Toggle autopilot |
38+
| `r` | Toggle red alert |
39+
| `s` | Scan |
40+
41+
### Engineering
42+
43+
| Key | Command |
44+
|---|---|
45+
| `b` | Toggle boost |
46+
| `d` | Toggle diagnostics |
47+
48+
### Crew
49+
50+
| Key | Command |
51+
|---|---|
52+
| `n` | New assignment |
53+
| `e` | Edit selected crew member |
54+
| `/` | Focus/search crew workflow |
55+
56+
### Comms
57+
58+
| Key | Command |
59+
|---|---|
60+
| `h` | Open hail dialog |
61+
| `enter` | Acknowledge next unacked message |
62+
| `n`, `p` | Next/previous channel |
63+
| `/` | Search messages |
64+
65+
### Cargo / Settings
66+
67+
| Key | Command |
68+
|---|---|
69+
| `n`, `c`, `q`, `p` (Cargo) | Sort by name/category/quantity/priority |
70+
| `ctrl+r` (Settings) | Open reset confirmation |
71+
| `ctrl+s` (Settings) | Save snapshot |
72+
73+
## Feature Showcase
74+
75+
- **Routing + shell:** `createNodeApp({ routes })`, `routerBreadcrumb()`, `routerTabs()`, route-aware key command dispatch.
76+
- **Layouts:** `ui.appShell`, `ui.page`, `ui.panel`, `ui.card`, `ui.grid`, `ui.masterDetail`, `ui.splitPane`, `ui.center`.
77+
- **Data + forms:** `ui.table`, `ui.tree`, `ui.virtualList`, `ui.form`, `ui.field`, `ui.actions`, `ui.select`, `ui.radioGroup`, `ui.slider`.
78+
- **Visualization:** `ui.canvas` (braille), `ui.gauge`, `ui.progress`, `ui.sparkline`, `ui.lineChart`, `ui.barChart`, `ui.scatter`, `ui.heatmap`.
79+
- **Overlays:** `ui.layers`, `ui.layer`, `ui.modal`, `ui.dialog`, `ui.toastContainer`, `ui.commandPalette`.
80+
- **Composition hooks:** `defineWidget`, `useTransition`, `useSpring`, `useSequence`, `useStagger`, `useInterval`, `useAsync`.
81+
- **Render helpers:** `show`, `when`, `match`, `maybe`, `each`, `eachInline`.
82+
83+
## File Layout
84+
85+
- `src/types.ts`: readonly state, domain entities, actions.
86+
- `src/theme.ts`: starship theme catalog and display styles.
87+
- `src/helpers/simulation.ts`: deterministic seed generators + toast events.
88+
- `src/helpers/formatters.ts`: UI format and badge helpers.
89+
- `src/helpers/state.ts`: reducer, selectors, simulation evolution.
90+
- `src/helpers/keybindings.ts`: route-aware key resolution.
91+
- `src/screens/`: shared shell + six route screens.
92+
- `src/main.ts`: app bootstrap, routing, keybindings, timers, lifecycle.
93+
- `src/__tests__/`: reducer, keybinding, and render coverage.
94+
95+
## Quickstart
96+
97+
```bash
98+
npm install
99+
npm run start
100+
```
101+
102+
## Dev (HSR)
103+
104+
```bash
105+
npm run dev
106+
```
107+
108+
## Tests
109+
110+
```bash
111+
npm test
112+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "__PACKAGE_NAME__",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"start": "tsx src/main.ts",
8+
"dev": "tsx src/main.ts --hsr",
9+
"build": "tsc --pretty false",
10+
"typecheck": "tsc --noEmit",
11+
"test": "tsx --test src/__tests__"
12+
},
13+
"dependencies": {
14+
"@rezi-ui/core": "^0.1.0-alpha.17",
15+
"@rezi-ui/node": "^0.1.0-alpha.17"
16+
},
17+
"devDependencies": {
18+
"@types/node": "^22.13.1",
19+
"tsx": "^4.20.0",
20+
"typescript": "^5.6.3"
21+
},
22+
"engines": {
23+
"node": ">=18"
24+
}
25+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import { resolveStarshipCommand } from "../helpers/keybindings.js";
4+
5+
test("global keys resolve correctly", () => {
6+
assert.equal(resolveStarshipCommand("q"), "quit");
7+
assert.equal(resolveStarshipCommand("ctrl+c"), "quit");
8+
assert.equal(resolveStarshipCommand("1"), "go-bridge");
9+
assert.equal(resolveStarshipCommand("2"), "go-engineering");
10+
assert.equal(resolveStarshipCommand("3"), "go-crew");
11+
assert.equal(resolveStarshipCommand("4"), "go-comms");
12+
assert.equal(resolveStarshipCommand("5"), "go-cargo");
13+
assert.equal(resolveStarshipCommand("6"), "go-settings");
14+
assert.equal(resolveStarshipCommand("t"), "cycle-theme");
15+
assert.equal(resolveStarshipCommand("?"), "toggle-help");
16+
assert.equal(resolveStarshipCommand("ctrl+p"), "toggle-command-palette");
17+
});
18+
19+
test("route-specific keys resolve on matching routes", () => {
20+
assert.equal(resolveStarshipCommand("a", "bridge"), "toggle-autopilot");
21+
assert.equal(resolveStarshipCommand("b", "engineering"), "engineering-boost");
22+
assert.equal(resolveStarshipCommand("n", "crew"), "crew-new-assignment");
23+
assert.equal(resolveStarshipCommand("h", "comms"), "comms-hail");
24+
assert.equal(resolveStarshipCommand("enter", "comms"), "comms-acknowledge");
25+
});
26+
27+
test("unknown keys return undefined", () => {
28+
assert.equal(resolveStarshipCommand("z"), undefined);
29+
assert.equal(resolveStarshipCommand("ctrl+alt+9"), undefined);
30+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import {
4+
createInitialState,
5+
reduceStarshipState,
6+
sortedCargo,
7+
visibleCrew,
8+
} from "../helpers/state.js";
9+
10+
test("tick advances telemetry deterministically", () => {
11+
const initial = createInitialState(1_000);
12+
const nextA = reduceStarshipState(initial, { type: "tick", nowMs: 1_800 });
13+
const nextB = reduceStarshipState(initial, { type: "tick", nowMs: 1_800 });
14+
15+
assert.deepEqual(nextA.telemetry, nextB.telemetry);
16+
assert.equal(nextA.tick, 1);
17+
assert.equal(nextA.telemetryHistory.length, 60);
18+
});
19+
20+
test("toggle-pause toggles state", () => {
21+
const initial = createInitialState(0);
22+
const paused = reduceStarshipState(initial, { type: "toggle-pause" });
23+
const resumed = reduceStarshipState(paused, { type: "toggle-pause" });
24+
assert.equal(paused.paused, true);
25+
assert.equal(resumed.paused, false);
26+
});
27+
28+
test("toggle-red-alert cycles alert level", () => {
29+
const initial = createInitialState(0);
30+
const red = reduceStarshipState(initial, { type: "toggle-red-alert" });
31+
const yellow = reduceStarshipState(red, { type: "toggle-red-alert" });
32+
assert.equal(red.alertLevel, "red");
33+
assert.equal(yellow.alertLevel, "yellow");
34+
});
35+
36+
test("crew selection and search filtering", () => {
37+
const initial = createInitialState(0);
38+
const member = initial.crew[5];
39+
assert.ok(member);
40+
41+
const selected = reduceStarshipState(initial, { type: "select-crew", crewId: member.id });
42+
assert.equal(selected.selectedCrewId, member.id);
43+
44+
const searched = reduceStarshipState(selected, {
45+
type: "set-crew-search",
46+
query: member.name.split(" ")[0] ?? "",
47+
});
48+
const visible = visibleCrew(searched);
49+
assert.ok(visible.some((entry) => entry.id === member.id));
50+
});
51+
52+
test("subsystem tree expansion toggles", () => {
53+
const initial = createInitialState(0);
54+
const target = "warp-core";
55+
const opened = reduceStarshipState(initial, { type: "toggle-subsystem", subsystemId: target });
56+
assert.equal(opened.expandedSubsystemIds.includes(target), true);
57+
58+
const closed = reduceStarshipState(opened, { type: "toggle-subsystem", subsystemId: target });
59+
assert.equal(closed.expandedSubsystemIds.includes(target), false);
60+
});
61+
62+
test("comms channel switching and message acknowledgment", () => {
63+
const initial = createInitialState(0);
64+
const switched = reduceStarshipState(initial, { type: "switch-channel", channel: "emergency" });
65+
assert.equal(switched.activeChannel, "emergency");
66+
67+
const first = switched.messages[0];
68+
assert.ok(first);
69+
const acknowledged = reduceStarshipState(switched, {
70+
type: "acknowledge-message",
71+
messageId: first.id,
72+
});
73+
assert.equal(acknowledged.messages.find((item) => item.id === first.id)?.acknowledged, true);
74+
});
75+
76+
test("cargo sorting updates ordering", () => {
77+
const initial = createInitialState(0);
78+
const sortedByName = reduceStarshipState(initial, { type: "set-cargo-sort", sortBy: "name" });
79+
const sorted = sortedCargo(sortedByName);
80+
assert.ok(sorted.length > 5);
81+
assert.equal(sorted[0]!.name.localeCompare(sorted[1]!.name) <= 0, true);
82+
});
83+
84+
test("settings name can be validated by reducer result", () => {
85+
const initial = createInitialState(0);
86+
const invalid = reduceStarshipState(initial, { type: "set-ship-name", name: "" });
87+
assert.equal(invalid.shipName.trim().length, 0);
88+
89+
const valid = reduceStarshipState(invalid, { type: "set-ship-name", name: "USS Atlas" });
90+
assert.equal(valid.shipName, "USS Atlas");
91+
});
92+
93+
test("toast add and dismiss", () => {
94+
const initial = createInitialState(0);
95+
const withToast = reduceStarshipState(initial, {
96+
type: "add-toast",
97+
toast: {
98+
id: "toast-1",
99+
message: "Hello",
100+
level: "info",
101+
timestamp: 0,
102+
durationMs: 1000,
103+
},
104+
});
105+
assert.equal(withToast.toasts.length, 1);
106+
107+
const dismissed = reduceStarshipState(withToast, { type: "dismiss-toast", toastId: "toast-1" });
108+
assert.equal(dismissed.toasts.length, 0);
109+
});

0 commit comments

Comments
 (0)