Skip to content

feat(create-rezi): add starship command console template#191

Merged
RtlZeroMemory merged 2 commits intomainfrom
feat/starship-template
Feb 24, 2026
Merged

feat(create-rezi): add starship command console template#191
RtlZeroMemory merged 2 commits intomainfrom
feat/starship-template

Conversation

@RtlZeroMemory
Copy link
Copy Markdown
Owner

@RtlZeroMemory RtlZeroMemory commented Feb 24, 2026

Summary

  • add a new 'starship' create-rezi template with six routed screens and full integration showcase
  • register the template in scaffold definitions, aliases, and CLI metadata/tests
  • include template tests and README
  • harden Bridge layout by stacking the two heavy telemetry panels vertically to avoid a runtime backend fatal at certain PTY sizes

Validation

  • node scripts/run-tests.mjs --filter starship
  • manual PTY resize/stress runs in generated scaffolded app (80x24, 120x40, 160x50, 214x68, 260x72)

Summary by CodeRabbit

  • New Features
    • Added a new "Starship" starter template: interactive UI with Bridge, Engineering, Crew, Communications, Cargo, and Settings screens; theming, keyboard navigation, simulation data, and persistent state flows.
  • Tests
    • Added extensive tests covering template keybindings, state/reducer behavior, and screen rendering.
  • Documentation
    • Updated CLI and docs to list and describe the new "starship" template and its aliases.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

Adds a new "starship" starter template to create-rezi: a full demo app including types, state management, seed data, keybindings, theme utilities, six UI screens, a shell, an application bootstrap, tests, and template configuration/docs.

Changes

Cohort / File(s) Summary
Template registration & CLI
packages/create-rezi/src/__tests__/scaffold.test.ts, packages/create-rezi/src/index.ts, packages/create-rezi/src/scaffold.ts
Register new starship template (aliases: ship, bridge, command) and update CLI help/tests to include it.
Template package & docs
packages/create-rezi/templates/starship/package.json, .../tsconfig.json, .../.gitignore, .../README.md
Add template package metadata, scripts, TypeScript config, ignore rules, and comprehensive README documentation.
Types & theme
packages/create-rezi/templates/starship/src/types.ts, .../theme.ts
Introduce domain types, StarshipState/StarshipAction, toCoreToast mapper, theme definitions, theme metadata and utilities.
State, simulation & formatters
packages/create-rezi/templates/starship/src/helpers/state.ts, .../simulation.ts, .../formatters.ts
Add deterministic telemetry, full reducer with actions/selectors, seed data generators, toast generator, and UI formatting/aggregation helpers.
Keybindings
packages/create-rezi/templates/starship/src/helpers/keybindings.ts, packages/create-rezi/src/__tests__/scaffold.test.ts
Add StarshipCommand type, global and route-specific key mappings, and resolver function; tests updated/added for keybinding resolution.
Screens & shell
packages/create-rezi/templates/starship/src/screens/... (bridge.ts, engineering.ts, crew.ts, comms.ts, cargo.ts, settings.ts, shell.ts, index.ts)
Add six rendered screens and a shared shell with command palette, navigation, toasts, and UI composition utilities.
App bootstrap
packages/create-rezi/templates/starship/src/main.ts
Add application entrypoint wiring state, routing, keyboard handling, command processing, HSR support, lifecycle, tick/toast scheduling, and signal handling.
Tests & render checks
packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts, .../reducer.test.ts, .../render.test.ts
Add unit/integration tests covering keybindings, reducer behavior, seed data, and text-based render assertions for screens.
Repository docs updates
README.md, docs/**, packages/create-rezi/README.md
Update docs and examples to list starship among available templates and show CLI examples.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Input as Input Handler
    participant Resolver as Command Resolver
    participant Dispatcher as Dispatch
    participant Reducer as State Reducer
    participant Router as Router
    participant Screen as Screen Renderer
    participant Shell as Shell UI
    participant Display as Terminal

    User->>Input: press key
    Input->>Resolver: normalize key & resolveStarshipCommand(key, route)
    Resolver-->>Input: StarshipCommand | undefined
    Input->>Dispatcher: dispatch(action / apply-command)
    Dispatcher->>Reducer: reduceStarshipState(state, action)
    Reducer-->>Dispatcher: new state
    Dispatcher->>Router: navigate/update current route
    Router->>Screen: render(context, deps) with new state
    Screen->>Shell: renderShell(body)
    Shell->>Display: update terminal UI
    Display-->>User: refreshed UI
Loading
sequenceDiagram
    participant Bootstrap as App Bootstrap
    participant State as State Container
    participant Ticker as Tick Scheduler
    participant Generator as Simulation
    participant Dispatcher as Dispatch
    participant Renderer as Screen Renderer
    participant Display as Terminal

    Bootstrap->>State: createInitialState(now)
    Bootstrap->>Ticker: start interval (TICK_MS)
    loop every tick
        Ticker->>Generator: generate telemetry/comms/toast(tick)
        Generator-->>Dispatcher: dispatch tick actions
        Dispatcher->>State: reduceStarshipState(...)
        State-->>Renderer: new state
        Renderer->>Display: render updated UI
    end
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I stitched a bridge of code and starry light,
Decks hum with telemetry through the night,
Keybinds skip, toasts sparkle, themes rotate—
A tiny rabbit cheers this stellar crate,
All hands, prepare: the demo springs to flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and accurately summarizes the main change: adding a new 'starship' command console template to create-rezi.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/starship-template

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a2a5627285

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +157 to +160
const channel = channels[(tick / 2) % channels.length] ?? "fleet";
const priority = priorities[tick % priorities.length] ?? "routine";
const sender = senders[tick % senders.length] ?? "Fleet Command";
const content = `${contents[(tick * 3) % contents.length] ?? "Status update"} · t${tick}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Base comms message variants on emission index

This generator only runs when tick % 4 === 0, but the channel/priority/sender/content selectors are all indexed directly by tick; as a result, generated traffic is heavily degenerate (priority is always routine, content is always the first template, and channels only alternate between fleet/internal). In normal runtime this silently removes most channel and severity variation from the comms simulation, so deck behaviors that rely on diverse traffic never get exercised.

Useful? React with 👍 / 👎.

title: "Command Deck",
style: styles.panelStyle,
},
[BridgeCommandDeck({ key: `bridge-command-${state.tick}`, state, dispatch: deps.dispatch })],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep bridge command deck key stable across ticks

Using state.tick in this component key forces BridgeCommandDeck to unmount/remount every simulation tick (800ms), which resets its hook state and restarts its internal 1s useInterval before it can fire. In practice the local uptime/scan sweep loop never progresses and the deck does extra mount churn each frame; this key should be stable so widget-local animations and timers can persist.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

🧹 Nitpick comments (17)
packages/create-rezi/templates/starship/tsconfig.json (1)

2-12: Add outDir to prevent emitted JavaScript files in source directory.

The build script runs tsc --pretty false, which emits JavaScript without an outDir directive, causing .js files to be written alongside .ts sources. The template should specify "outDir": "dist" to keep sources clean and align with the project's .gitignore pattern for dist/.

🔧 Proposed addition
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
+   "outDir": "dist",
    "strict": true,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/tsconfig.json` around lines 2 - 12,
The tsconfig.json compilerOptions is missing an outDir which causes tsc to emit
.js files next to .ts sources; update the "compilerOptions" block in
packages/create-rezi/templates/starship/tsconfig.json to include "outDir":
"dist" (so the build (tsc --pretty false) writes outputs to dist/), ensuring
this aligns with the project's .gitignore and prevents emitted JS from
cluttering source files.
packages/create-rezi/src/scaffold.ts (1)

121-124: "starship" → "starship" identity alias is redundant.

TEMPLATE_ALIASES is already seeded with every key → key identity mapping at lines 103–105 (TEMPLATE_DEFINITIONS.map((t) => [t.key, t.key])). No other template re-sets its own identity alias in the manual block — removing line 121 is the consistent approach.

♻️ Proposed fix
-TEMPLATE_ALIASES.set("starship", "starship");
 TEMPLATE_ALIASES.set("ship", "starship");
 TEMPLATE_ALIASES.set("bridge", "starship");
 TEMPLATE_ALIASES.set("command", "starship");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/src/scaffold.ts` around lines 121 - 124, Remove the
redundant identity alias by deleting the explicit
TEMPLATE_ALIASES.set("starship", "starship") entry; TEMPLATE_ALIASES is already
seeded with identity mappings from TEMPLATE_DEFINITIONS (via
TEMPLATE_DEFINITIONS.map((t) => [t.key, t.key])), so keep the other aliases
(TEMPLATE_ALIASES.set("ship", "starship"), TEMPLATE_ALIASES.set("bridge",
"starship"), TEMPLATE_ALIASES.set("command", "starship")) but remove the
duplicate TEMPLATE_ALIASES.set("starship", "starship") line.
packages/create-rezi/templates/starship/src/helpers/state.ts (2)

19-28: Consider exporting SubsystemTreeNode and SystemHealth types.

These types are used as return types of the exported subsystemTree() and systemHealth() functions. Without exporting them, consumers cannot reference these types by name (they can only infer them). If any screen or test needs to annotate a variable with these types, they'd be unable to.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/helpers/state.ts` around lines 19
- 28, Export the two helper types so callers can reference them: make
SubsystemTreeNode and SystemHealth exported types (they are the return shapes
used by subsystemTree() and systemHealth()), e.g. change their declarations to
exported type aliases or exported interfaces; update any local references to use
the exported names so consumers (screens/tests) can import and annotate
variables with SubsystemTreeNode and SystemHealth.

48-50: freezeArray appears unused.

freezeArray is defined but not called anywhere in this file.

🧹 Remove dead code
-function freezeArray<T>(input: readonly T[]): readonly T[] {
-  return Object.freeze([...input]);
-}
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/helpers/state.ts` around lines 48
- 50, The function freezeArray is defined but never used; remove the dead code
by deleting the freezeArray<T>(input: readonly T[]): readonly T[] { return
Object.freeze([...input]); } declaration (and any related unused imports or
exports) and run type-check/build to ensure no remaining references; if the
original intent was to reuse it elsewhere, alternatively replace local usages to
call freezeArray (rather than an inline freeze) and export it—choose one fix so
freezeArray is either removed or actually referenced.
packages/create-rezi/templates/starship/src/helpers/simulation.ts (1)

91-103: clampInt, fract, and noise are duplicated in state.ts.

These three utility functions are identically defined in both simulation.ts and state.ts. Consider extracting them into a shared internal utility (e.g., helpers/math.ts) to avoid the duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/helpers/simulation.ts` around
lines 91 - 103, The functions clampInt, fract, and noise are duplicated in both
simulation.ts and state.ts; extract them into a shared helper module (e.g.,
create a new helpers/math.ts exporting clampInt, fract, and noise), remove the
duplicate definitions from simulation.ts and state.ts, and import the shared
implementations in both files (use the exported function names clampInt, fract,
noise) so both modules reuse the same utilities.
packages/create-rezi/templates/starship/src/screens/engineering.ts (1)

24-33: Consider memoizing derived structures rebuilt every render.

buildSubsystemChildren(...) and heatmapData are recomputed each render (and this screen likely re-renders on ticks). Memoizing them (based on props.state.subsystems) would reduce per-tick allocations.

As per coding guidelines, “Use ctx.useMemo() for expensive computations in widget render functions”.

Also applies to: 90-104

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/engineering.ts` around
lines 24 - 33, The buildSubsystemChildren map and heatmapData are rebuilt every
render; wrap their computation with ctx.useMemo() keyed on
props.state.subsystems (and any other inputs like props.state.heatmapSources) so
they are recomputed only when inputs change; locate the buildSubsystemChildren
function and the code that computes heatmapData in the engineering screen and
replace direct calls/inline computation with ctx.useMemo(() => /* compute */ ,
[props.state.subsystems, ...]) ensuring the memo returns the Map and heatmap
array respectively.
packages/create-rezi/templates/starship/src/screens/settings.ts (1)

6-13: Consider aligning slider bounds with the validation range (optional).

Validation errors if threshold is outside 20–95 (Line 10-12), but the slider allows 0–100 (Line 81-83). If you want to prevent invalid states rather than demonstrate validation, tighten min/max to 20/95.

Also applies to: 79-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/settings.ts` around lines
6 - 13, The validation in function validationError checks state.alertThreshold
must be between 20 and 95 but the UI slider allows 0–100, so update the slider's
bounds to match the validation: change its min to 20 and max to 95 (and
optionally adjust any default/initial value to fall in that range) so
state.alertThreshold cannot be set to an invalid value; ensure references to
state.alertThreshold and the slider component are consistent with the 20–95
range.
packages/create-rezi/templates/starship/src/screens/shell.ts (1)

21-37: Command palette route shortcuts: consider adding 3–6 for parity with global nav keys.

routeCommandItems() only assigns shortcuts for Bridge/Engineering (Line 28-32), but the template supports 1–6 route switching. Adding shortcuts for the remaining routes makes the command palette match the rest of the UX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/shell.ts` around lines 21
- 37, routeCommandItems currently only assigns shortcuts for route.id ===
"bridge" and "engineering", so update the function to assign numeric shortcuts
for all supported route ids (1–6) to match global nav keys; inside
routeCommandItems (and the object spread building each CommandItem) replace the
two-branch shortcut logic with a mapping from route.id to shortcut (e.g., map
route ids to "1","2","3","4","5","6") or a switch that returns { shortcut: "<n>"
} for each known route id, leaving routes without a mapping unchanged.
packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts (1)

5-30: Add normalization edge-cases to match trim().toLowerCase() behavior.

Since the resolver normalizes input, consider adding a couple assertions like:

  • " Q ""quit"
  • "CTRL+P""toggle-command-palette"
  • "" / " "undefined

That will lock in the input-normalization contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts`
around lines 5 - 30, Update the keybinding tests to cover input-normalization
edge cases for resolveStarshipCommand: add assertions that inputs with
surrounding whitespace and mixed case are normalized (e.g.,
resolveStarshipCommand(" Q ") === "quit" and resolveStarshipCommand("CTRL+P")
=== "toggle-command-palette"), and that empty or all-whitespace strings return
undefined (e.g., resolveStarshipCommand("") and resolveStarshipCommand("   ")
=== undefined); place these assertions alongside the existing tests so
resolveStarshipCommand's trim().toLowerCase() contract is explicitly validated.
packages/create-rezi/templates/starship/src/screens/crew.ts (1)

57-61: Loading state currently renders the skeleton and the full table.

Right now asyncCrew.loading (Line 252) only adds a “Loading Crew Manifest” panel; ui.masterDetail(...) is still always rendered. If the intention is to “replace” the table during loading, consider gating the master/detail block behind the same condition and dropping the redundant ui.text("") fallback.

Also applies to: 251-261

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/crew.ts` around lines 57
- 61, The loading state currently renders both the skeleton and the full table;
update the render logic so when asyncCrew.loading is true the master/detail
table is not rendered. Specifically, in the component that uses asyncCrew (the
useAsync call and the asyncCrew variable), guard the ui.masterDetail(...) block
behind a check like if (!asyncCrew.loading) { ui.masterDetail(...) } (or render
the master/detail only when asyncCrew.loading is false) and remove the redundant
ui.text("") fallback so the loading skeleton replaces—not duplicates—the table.
Ensure you reference asyncCrew.loading and the ui.masterDetail(...) call when
making the change.
packages/create-rezi/templates/starship/src/screens/cargo.ts (1)

44-47: Scatter y-values can exceed the declared axis max (100).

With y: item.priority * 20 + (index % 7) (Line 45), priority=5 yields up to 106, but y-axis max is 100 (Line 132). Consider clamping or adjusting the axis max to avoid clipped points.

Also applies to: 131-133

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts` around lines 44
- 47, The scatter y calculation (y: item.priority * 20 + (index % 7)) can
produce values >100 while the chart y-axis max is fixed at 100; update the data
mapping that builds y (the expression using item.priority and index) to clamp
values to the axis max (e.g., replace the y expression with a Math.min(..., 100)
clamp) or compute a dynamic axis max from the dataset and set the chart’s y-axis
max to that computed value so points never get clipped; adjust the code that
sets the chart y-domain/max to use the new dynamic value if you choose that
route.
packages/create-rezi/templates/starship/src/main.ts (2)

83-105: stopApp disposes even if app.stop() throws — verify this is safe.

Line 103 calls app.dispose() unconditionally after catching any error from app.stop(). If stop() partially completed, dispose() on a half-stopped app may throw or leak. Consider guarding:

try { await app.stop(); } catch { /* ignore */ }
try { app.dispose(); } catch { /* ignore */ }

That said, since the process exits immediately after on Line 104, this is low-risk in practice.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/main.ts` around lines 83 - 105,
The stopApp function currently calls app.dispose() unconditionally after
awaiting app.stop(), which can throw if stop() partially completed; update
stopApp so that app.dispose() is invoked inside its own try/catch (separate from
the app.stop() try/catch) to swallow or log any dispose errors and avoid
crashing before exit; keep existing clearing of tickTimer/toastTimer and setting
stopping, and ensure exit(code) still runs after the guarded dispose so the
process terminates reliably (references: stopApp, app.stop, app.dispose,
tickTimer, toastTimer, stopping, exit).

107-316: Consider a lookup table for simple command → action mappings.

applyCommand is ~210 lines of sequential if/return blocks. Many branches are trivial one-liner dispatches (e.g., cycle-themedispatch({ type: "cycle-theme" })). Extracting those into a Record<string, StarshipAction> map and falling through to a dispatch(map[command]) would cut the function roughly in half while keeping the complex branches (navigation, comms-acknowledge, channel cycling) as explicit if blocks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/main.ts` around lines 107 - 316,
applyCommand is lengthy with many trivial if-branches; extract simple
command→action mappings into a lookup (e.g., const simpleActions: Record<string,
StarshipAction>) and replace repeated one-line dispatch calls with a single
lookup + dispatch path inside applyCommand; keep complex branches (navigation
commands using navigate, deck offsets using navigateDeckOffset,
comms-acknowledge using app.update + filteredMessages + reduceStarshipState, and
channel cycling logic) as explicit if-blocks so they remain unchanged, and
ensure keys like "cycle-theme", "toggle-help", "toggle-command-palette",
"toggle-pause", "toggle-autopilot", "toggle-red-alert",
"set-alert-green"/"yellow"/"red", "engineering-boost",
"engineering-diagnostics", "crew-new-assignment"/"crew-edit-selected",
"cargo-sort-*" and "settings-reset" map to the correct action objects used by
dispatch.
packages/create-rezi/templates/starship/src/screens/bridge.ts (3)

101-116: Hardcoded chart colors in lineSeries.

"#7ec8ff" and "#7cf0c1" are hardcoded. Same concern as in drawShipSchematic — these should ideally derive from theme tokens so chart colors update with theme changes.

As per coding guidelines: "NEVER hardcode RGB values — let the theme handle colors via tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts` around lines
101 - 116, The chart colors are hardcoded in the lineSeries constant (colors
"#7ec8ff" and "#7cf0c1"); replace these hex literals with theme token lookups so
colors react to theme changes—for example obtain reactor/shield color tokens
from the theme/context used by this module (the same approach used in
drawShipSchematic) and use those token values in the objects inside lineSeries;
ensure you still provide a sensible fallback if a token is missing and keep the
dependency array on ctx.useMemo in sync with any theme token values you read.

91-91: Misleading variable name scanId.

scanId (from ctx.id("scan")) is used as the id for the red alert button (Line 275), not for the scan button (which uses ctx.id("scan-button") at Line 257). Consider renaming to alertId or redAlertId to avoid confusion during maintenance.

Also applies to: 274-279

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts` at line 91,
The variable scanId (assigned via ctx.id("scan")) is misleading because it is
used as the id for the red alert button; rename scanId to redAlertId (or
alertId), change the ctx.id invocation to ctx.id("red-alert") (or similar
distinct string) and update every usage where the red alert button consumes that
id so that the scan button still uses ctx.id("scan-button") unchanged; ensure
all references to scanId in this file (including the red alert button creation
and any handlers) are updated to the new symbol name.

32-87: Hardcoded RGB hex values in canvas drawing.

The drawShipSchematic function contains 11+ hardcoded hex color strings (e.g., "#0e1626", "#35608a", "#89d0ff", "#6ff0cf", etc.). The coding guidelines state: NEVER hardcode RGB values — let the theme handle colors via tokens.

Even for a canvas context where theme integration may be less straightforward, consider passing theme-derived colors into drawShipSchematic via the options parameter so the canvas respects theme switches (day/night/etc.). At minimum, extract these into named constants at the top of the file so they're easy to swap.

As per coding guidelines: "NEVER hardcode RGB values — let the theme handle colors via tokens".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts` around lines
32 - 87, drawShipSchematic currently uses many hardcoded hex color strings which
violates the "NEVER hardcode RGB values" guideline; fix by making the canvas
colors theme-aware: add a colors bag to the drawShipSchematic signature (e.g.,
extend options with a colors: Readonly<{ background, border, hull, reactorFill,
reactorStroke, shield, sweep, warp, starA, starB, text }>) and replace each
literal like "#0e1626", "#35608a", "#89d0ff", "#6ff0cf", "#afffe8", "#7eb9ff",
"#4fd3f7", "#80f5ff", "#d5f3ff", "#8ec7ff", "#b6d9ff" with the corresponding
token from that colors object; alternatively, if changing the signature is not
feasible, extract those literals into named top-level constants
(backgroundColor, borderColor, hullColor, reactorFillColor, reactorStrokeColor,
shieldColor, sweepColor, warpColor, starColorA, starColorB, textColor) and use
those symbols in drawShipSchematic so theme tokens can be mapped in one place.
packages/create-rezi/templates/starship/src/screens/comms.ts (1)

52-60: when() fallback to empty ui.text("") — minor but could use show() instead.

The when(...) ?? ui.text("") pattern at Line 53 renders an invisible empty text node when the condition is false. If the framework's show() helper handles the falsy case by returning nothing (no node), it would be slightly cleaner. Not a bug — just a readability note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/comms.ts` around lines 52
- 60, Replace the fallback empty node pattern "when(...) ?? ui.text(\"\")" with
the framework's show() helper so no invisible text node is created; locate the
ui.column call and the when(...) that checks state.activeChannel === "emergency"
and wrap the conditional callout in show(...) (or use show(() =>
state.activeChannel === "emergency" ? ui.callout(...) : undefined)) so the
callout is rendered when true and nothing is rendered when false.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/create-rezi/templates/starship/package.json`:
- Around line 18-23: The package.json lists "@types/node": "^22.13.1" which
targets Node 22 while "engines.node" is ">=18"; update the dependency to the
conservative minimum major (e.g., change "@types/node" to "^18") so TypeScript
typings match the declared engine floor and review other template package.json
entries for the same mismatch; locate the "@types/node" entry in the
dependencies/devDependencies section to modify it and run install/typecheck to
verify.

In `@packages/create-rezi/templates/starship/src/__tests__/reducer.test.ts`:
- Around line 76-82: Replace the forbidden non-null assertions in the test that
uses createInitialState, reduceStarshipState and sortedCargo: instead of using
sorted[0]! and sorted[1]!, use safe access (optional chaining or explicit
guards) to get names after the existing assert.ok(sorted.length > 5) check —
e.g. obtain names via sorted[0]?.name and sorted[1]?.name with a sensible
fallback or assert the two entries exist before comparing; update the
localeCompare call to handle possibly undefined names so the linter no longer
requires non-null assertions.

In `@packages/create-rezi/templates/starship/src/__tests__/render.test.ts`:
- Around line 1-13: The import declarations are unsorted causing the CI failure;
reorder and group them to match the project's organizeImports rules (external
packages first, then local modules, and alphabetically within groups) and/or run
the repo's autofix (e.g., eslint --fix or your editor's "organize imports") to
apply the change; specifically adjust the block importing createTestRenderer,
createInitialState, STARSHIP_ROUTES, and the render* imports
(renderBridgeScreen, renderCargoScreen, renderCommsScreen, renderCrewScreen,
renderEngineeringScreen, renderSettingsScreen) and ensure type imports
(RouteRenderContext, RouterApi, RouteDeps, RouteId, StarshipState) are grouped
correctly.

In `@packages/create-rezi/templates/starship/src/helpers/keybindings.ts`:
- Around line 118-131: In resolveStarshipCommand, when a routeId is provided the
fallback chain can pull keys from other routes via COMMAND_BY_KEY; change the
logic so after checking ROUTE_KEY_OVERRIDES[routeId] you only fall back to
GLOBAL_COMMAND_BY_KEY[normalized] (remove the COMMAND_BY_KEY fallback) so active
routes only use their overrides plus globals; reference resolveStarshipCommand,
ROUTE_KEY_OVERRIDES, GLOBAL_COMMAND_BY_KEY and COMMAND_BY_KEY to locate and
update the conditional return.

In `@packages/create-rezi/templates/starship/src/helpers/state.ts`:
- Around line 300-308: Replace forbidden non-null assertions in clampSizes by
using safe access/defaults: in function clampSizes use (normalized[0] ?? 0) and
(normalized[1] ?? 0) instead of normalized[0]! and normalized[1]! (update
variable `total` and the numerator when computing `first` accordingly) so the
logic and guards remain the same without using `!` (references: clampSizes,
normalized, total, first, second).

In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 304-316: The "settings-save" branch inside applyCommand dispatches
the toast but is missing a trailing return and thus falls through; update the
branch where command === "settings-save" (the block that dispatches the toast
with id `save-${Date.now()}` and message "Settings snapshot saved") to append a
return; statement after the dispatch so it mirrors the other branches and
prevents fall-through/double-execution.
- Line 20: The linter flags the single-assignment `let app!` declaration
(variable app created with createNodeApp<StarshipState>) — suppress the rule by
adding an ESLint directive immediately above that line (e.g. `//
eslint-disable-next-line prefer-const`) so the `let app!` pattern is allowed
while retaining the existing closure-based initialization; ensure the comment
directly precedes the `let app!: ReturnType<typeof
createNodeApp<StarshipState>>;` declaration.
- Around line 3-7: Re-order the import statements using your editor's "Organize
Imports" (or manually) so they satisfy the linter: sort the module specifiers
alphabetically and keep the type-only import separate (import type { RouteDeps,
RouteId, StarshipAction, StarshipState } from "./types.js") as the final import;
ensure the value imports (resolveStarshipCommand, createInitialState,
filteredMessages, reduceStarshipState, createStarshipRoutes, STARSHIP_ROUTES,
themeSpec) are ordered by their module paths (e.g. "./helpers/keybindings.js",
"./helpers/state.js", "./screens/index.js", "./theme.js") to match the lint
rule.
- Around line 367-375: The escape handler currently mutates state via
bindingMap.escape using app.update(...) with object spreads which bypasses the
reducer and freezeState; change it to read the current state with app.getState()
and then call dispatch(...) with the appropriate action(s) (e.g., the existing
toggle-help, toggle-command-palette, toggle-hail-dialog, toggle-reset-dialog
action types handled by reduceStarshipState) instead of calling app.update
directly so the reducer and freezeState run and any reducer side-effects are
applied.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts`:
- Line 306: The key for BridgeCommandDeck is unstable because it's tied to
state.tick, causing the component to be destroyed and recreated every 800ms and
losing local state (scanSweep, scanBoost, uptime, lastReactorRef) and restarting
animation hooks (useSpring/useTransition/useSequence/useStagger); change the key
to a stable value (e.g., a constant string like "bridge-command" or a stable
identifier) instead of using `state.tick` so the BridgeCommandDeck instance
persists across ticks and retains its local state and animation progress.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts`:
- Around line 43-47: The scatterPoints mapping currently hardcodes hex colors;
update it to use theme tokens by deriving colors from
themeSpec(state.themeName).theme.colors (or a shared palette helper) and map
categories to those tokens (e.g., ordnance, medical, default) so scatterPoints
(the x/y/color mapping for cargo.slice(0,160)) uses theme-based colors instead
of "#ff6b77", "#7ff0b2", "#7cc6ff"; ensure you import/resolve themeSpec or the
palette helper and reference the specific color tokens when building the color
property.

In `@packages/create-rezi/templates/starship/src/screens/comms.ts`:
- Around line 177-195: The modal actions put the primary "Transmit" button
before the "Cancel" button; reorder the buttons in the actions array so the
cancel button (id "comms-hail-cancel", onPress -> deps.dispatch({ type:
"toggle-hail-dialog" })) comes first and the primary submit/send button (id
"comms-hail-send", onPress -> deps.dispatch({ type: "send-hail", target:
state.hailTarget..., message: state.hailMessage... })) comes last to match the
guideline ui.actions([cancelBtn, submitBtn]).

In `@packages/create-rezi/templates/starship/src/screens/crew.ts`:
- Around line 77-80: The page calculation can become 0 causing a negative start
and surprising slice results; change the clamp for page to ensure it's at least
1 and at most totalPages by computing page using both Math.max and Math.min
(i.e., clamp props.state.crewPage between 1 and totalPages) before deriving
start and pageData so start = (page - 1) * props.state.crewPageSize never goes
negative; update the variables page, start, and pageData usage around
totalPages, props.state.crewPage, props.state.crewPageSize, and sorted
accordingly.

In `@packages/create-rezi/templates/starship/src/screens/engineering.ts`:
- Around line 34-70: drawReactor currently hardcodes hex colors throughout
(ctx.clear, ctx.strokeRect, ctx.arc, ctx.fillCircle, ctx.circle, ctx.line,
ctx.text); change drawReactor to use theme colors instead by either accepting a
colors object param or pulling themeSpec(...).theme.colors inside the function
(e.g., replace "#131728", "#476088", "#8acaff", "#ffb681", "#66efd0", "#bffef0",
"#79d8ff", "#8ef5c2", "#b7ccff" with theme tokens). Update the call sites to
pass the palette (or ensure theme access is available) and map the token names
to the appropriate uses (background, border, arcPrimary/arcSecondary, coreFill,
coreStroke, linePrimary/lineAlternate, text) so no hardcoded RGB values remain.
- Around line 121-142: The subsystem tree (ui.tree) is incorrectly wired to
props.state.selectedCrewId and has a no-op onSelect; remove the conditional
selected prop that spreads ...(props.state.selectedCrewId === null ? {} : {
selected: props.state.selectedCrewId }) and delete the onSelect: () => {}
handler from the ui.tree props in the engineering screen so the tree no longer
advertises a subsystem selection tied to a crew ID (there is no subsystem
selection state/action like "toggle-subsystem" to handle it).

In `@packages/create-rezi/templates/starship/src/screens/settings.ts`:
- Around line 150-162: The toast id can collide because onPress currently uses
`settings-saved-${state.tick}`; change the id generation in the onPress handler
to use a monotonic timestamp (e.g., `state.nowMs`) or combine it with tick
(e.g., `${state.nowMs}-${state.tick}`) so each dispatched toast in deps.dispatch
(type "add-toast") has a unique id; update the toast object creation inside the
onPress function to use the new id scheme.
- Around line 179-203: The three theme buttons (ids "theme-day", "theme-night",
"theme-alert") currently all call deps.dispatch({ type: "cycle-theme" }) so they
only cycle instead of selecting the named theme; update each ui.button.onPress
to dispatch a new explicit action like deps.dispatch({ type: "set-theme", theme:
"day" }) / "night" / "alert", and add handling for "set-theme" in the reducer
that sets state.themeName to the provided theme (or, if you prefer, implement a
payload convention consistent with existing actions); ensure the intent checks
still use state.themeName to highlight the active button.

In `@packages/create-rezi/templates/starship/src/screens/shell.ts`:
- Around line 110-156: The help modal currently hard-codes returnFocusTo to
"deck-sidebar-bridge", causing focus to always jump to the bridge item; update
the help modal instantiation (the returnFocusTo property on the help modal /
dialog) to use the dynamic currentRoute variable instead, i.e. set returnFocusTo
to `deck-sidebar-${currentRoute}` so focus is restored to the currently selected
sidebar item (use the existing currentRoute in scope where the help modal is
created).

In `@packages/create-rezi/templates/starship/src/theme.ts`:
- Around line 1-10: The imports in theme.ts are incorrectly ordered causing CI
failures; move the external package import from "@rezi-ui/core" above the local
relative import "./types.js" so that darkTheme, draculaTheme, extendTheme,
nordTheme and the type imports (BadgeVariant, TextStyle, ThemeDefinition) are
imported before AlertLevel and ThemeName; update the import block to list the
`@rezi-ui/core` import first, then the ./types.js import to satisfy the import
sorting rule.

---

Nitpick comments:
In `@packages/create-rezi/src/scaffold.ts`:
- Around line 121-124: Remove the redundant identity alias by deleting the
explicit TEMPLATE_ALIASES.set("starship", "starship") entry; TEMPLATE_ALIASES is
already seeded with identity mappings from TEMPLATE_DEFINITIONS (via
TEMPLATE_DEFINITIONS.map((t) => [t.key, t.key])), so keep the other aliases
(TEMPLATE_ALIASES.set("ship", "starship"), TEMPLATE_ALIASES.set("bridge",
"starship"), TEMPLATE_ALIASES.set("command", "starship")) but remove the
duplicate TEMPLATE_ALIASES.set("starship", "starship") line.

In `@packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts`:
- Around line 5-30: Update the keybinding tests to cover input-normalization
edge cases for resolveStarshipCommand: add assertions that inputs with
surrounding whitespace and mixed case are normalized (e.g.,
resolveStarshipCommand(" Q ") === "quit" and resolveStarshipCommand("CTRL+P")
=== "toggle-command-palette"), and that empty or all-whitespace strings return
undefined (e.g., resolveStarshipCommand("") and resolveStarshipCommand("   ")
=== undefined); place these assertions alongside the existing tests so
resolveStarshipCommand's trim().toLowerCase() contract is explicitly validated.

In `@packages/create-rezi/templates/starship/src/helpers/simulation.ts`:
- Around line 91-103: The functions clampInt, fract, and noise are duplicated in
both simulation.ts and state.ts; extract them into a shared helper module (e.g.,
create a new helpers/math.ts exporting clampInt, fract, and noise), remove the
duplicate definitions from simulation.ts and state.ts, and import the shared
implementations in both files (use the exported function names clampInt, fract,
noise) so both modules reuse the same utilities.

In `@packages/create-rezi/templates/starship/src/helpers/state.ts`:
- Around line 19-28: Export the two helper types so callers can reference them:
make SubsystemTreeNode and SystemHealth exported types (they are the return
shapes used by subsystemTree() and systemHealth()), e.g. change their
declarations to exported type aliases or exported interfaces; update any local
references to use the exported names so consumers (screens/tests) can import and
annotate variables with SubsystemTreeNode and SystemHealth.
- Around line 48-50: The function freezeArray is defined but never used; remove
the dead code by deleting the freezeArray<T>(input: readonly T[]): readonly T[]
{ return Object.freeze([...input]); } declaration (and any related unused
imports or exports) and run type-check/build to ensure no remaining references;
if the original intent was to reuse it elsewhere, alternatively replace local
usages to call freezeArray (rather than an inline freeze) and export it—choose
one fix so freezeArray is either removed or actually referenced.

In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 83-105: The stopApp function currently calls app.dispose()
unconditionally after awaiting app.stop(), which can throw if stop() partially
completed; update stopApp so that app.dispose() is invoked inside its own
try/catch (separate from the app.stop() try/catch) to swallow or log any dispose
errors and avoid crashing before exit; keep existing clearing of
tickTimer/toastTimer and setting stopping, and ensure exit(code) still runs
after the guarded dispose so the process terminates reliably (references:
stopApp, app.stop, app.dispose, tickTimer, toastTimer, stopping, exit).
- Around line 107-316: applyCommand is lengthy with many trivial if-branches;
extract simple command→action mappings into a lookup (e.g., const simpleActions:
Record<string, StarshipAction>) and replace repeated one-line dispatch calls
with a single lookup + dispatch path inside applyCommand; keep complex branches
(navigation commands using navigate, deck offsets using navigateDeckOffset,
comms-acknowledge using app.update + filteredMessages + reduceStarshipState, and
channel cycling logic) as explicit if-blocks so they remain unchanged, and
ensure keys like "cycle-theme", "toggle-help", "toggle-command-palette",
"toggle-pause", "toggle-autopilot", "toggle-red-alert",
"set-alert-green"/"yellow"/"red", "engineering-boost",
"engineering-diagnostics", "crew-new-assignment"/"crew-edit-selected",
"cargo-sort-*" and "settings-reset" map to the correct action objects used by
dispatch.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts`:
- Around line 101-116: The chart colors are hardcoded in the lineSeries constant
(colors "#7ec8ff" and "#7cf0c1"); replace these hex literals with theme token
lookups so colors react to theme changes—for example obtain reactor/shield color
tokens from the theme/context used by this module (the same approach used in
drawShipSchematic) and use those token values in the objects inside lineSeries;
ensure you still provide a sensible fallback if a token is missing and keep the
dependency array on ctx.useMemo in sync with any theme token values you read.
- Line 91: The variable scanId (assigned via ctx.id("scan")) is misleading
because it is used as the id for the red alert button; rename scanId to
redAlertId (or alertId), change the ctx.id invocation to ctx.id("red-alert") (or
similar distinct string) and update every usage where the red alert button
consumes that id so that the scan button still uses ctx.id("scan-button")
unchanged; ensure all references to scanId in this file (including the red alert
button creation and any handlers) are updated to the new symbol name.
- Around line 32-87: drawShipSchematic currently uses many hardcoded hex color
strings which violates the "NEVER hardcode RGB values" guideline; fix by making
the canvas colors theme-aware: add a colors bag to the drawShipSchematic
signature (e.g., extend options with a colors: Readonly<{ background, border,
hull, reactorFill, reactorStroke, shield, sweep, warp, starA, starB, text }>)
and replace each literal like "#0e1626", "#35608a", "#89d0ff", "#6ff0cf",
"#afffe8", "#7eb9ff", "#4fd3f7", "#80f5ff", "#d5f3ff", "#8ec7ff", "#b6d9ff" with
the corresponding token from that colors object; alternatively, if changing the
signature is not feasible, extract those literals into named top-level constants
(backgroundColor, borderColor, hullColor, reactorFillColor, reactorStrokeColor,
shieldColor, sweepColor, warpColor, starColorA, starColorB, textColor) and use
those symbols in drawShipSchematic so theme tokens can be mapped in one place.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts`:
- Around line 44-47: The scatter y calculation (y: item.priority * 20 + (index %
7)) can produce values >100 while the chart y-axis max is fixed at 100; update
the data mapping that builds y (the expression using item.priority and index) to
clamp values to the axis max (e.g., replace the y expression with a
Math.min(..., 100) clamp) or compute a dynamic axis max from the dataset and set
the chart’s y-axis max to that computed value so points never get clipped;
adjust the code that sets the chart y-domain/max to use the new dynamic value if
you choose that route.

In `@packages/create-rezi/templates/starship/src/screens/comms.ts`:
- Around line 52-60: Replace the fallback empty node pattern "when(...) ??
ui.text(\"\")" with the framework's show() helper so no invisible text node is
created; locate the ui.column call and the when(...) that checks
state.activeChannel === "emergency" and wrap the conditional callout in
show(...) (or use show(() => state.activeChannel === "emergency" ?
ui.callout(...) : undefined)) so the callout is rendered when true and nothing
is rendered when false.

In `@packages/create-rezi/templates/starship/src/screens/crew.ts`:
- Around line 57-61: The loading state currently renders both the skeleton and
the full table; update the render logic so when asyncCrew.loading is true the
master/detail table is not rendered. Specifically, in the component that uses
asyncCrew (the useAsync call and the asyncCrew variable), guard the
ui.masterDetail(...) block behind a check like if (!asyncCrew.loading) {
ui.masterDetail(...) } (or render the master/detail only when asyncCrew.loading
is false) and remove the redundant ui.text("") fallback so the loading skeleton
replaces—not duplicates—the table. Ensure you reference asyncCrew.loading and
the ui.masterDetail(...) call when making the change.

In `@packages/create-rezi/templates/starship/src/screens/engineering.ts`:
- Around line 24-33: The buildSubsystemChildren map and heatmapData are rebuilt
every render; wrap their computation with ctx.useMemo() keyed on
props.state.subsystems (and any other inputs like props.state.heatmapSources) so
they are recomputed only when inputs change; locate the buildSubsystemChildren
function and the code that computes heatmapData in the engineering screen and
replace direct calls/inline computation with ctx.useMemo(() => /* compute */ ,
[props.state.subsystems, ...]) ensuring the memo returns the Map and heatmap
array respectively.

In `@packages/create-rezi/templates/starship/src/screens/settings.ts`:
- Around line 6-13: The validation in function validationError checks
state.alertThreshold must be between 20 and 95 but the UI slider allows 0–100,
so update the slider's bounds to match the validation: change its min to 20 and
max to 95 (and optionally adjust any default/initial value to fall in that
range) so state.alertThreshold cannot be set to an invalid value; ensure
references to state.alertThreshold and the slider component are consistent with
the 20–95 range.

In `@packages/create-rezi/templates/starship/src/screens/shell.ts`:
- Around line 21-37: routeCommandItems currently only assigns shortcuts for
route.id === "bridge" and "engineering", so update the function to assign
numeric shortcuts for all supported route ids (1–6) to match global nav keys;
inside routeCommandItems (and the object spread building each CommandItem)
replace the two-branch shortcut logic with a mapping from route.id to shortcut
(e.g., map route ids to "1","2","3","4","5","6") or a switch that returns {
shortcut: "<n>" } for each known route id, leaving routes without a mapping
unchanged.

In `@packages/create-rezi/templates/starship/tsconfig.json`:
- Around line 2-12: The tsconfig.json compilerOptions is missing an outDir which
causes tsc to emit .js files next to .ts sources; update the "compilerOptions"
block in packages/create-rezi/templates/starship/tsconfig.json to include
"outDir": "dist" (so the build (tsc --pretty false) writes outputs to dist/),
ensuring this aligns with the project's .gitignore and prevents emitted JS from
cluttering source files.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cee18ba and a2a5627.

📒 Files selected for processing (25)
  • packages/create-rezi/src/__tests__/scaffold.test.ts
  • packages/create-rezi/src/index.ts
  • packages/create-rezi/src/scaffold.ts
  • packages/create-rezi/templates/starship/.gitignore
  • packages/create-rezi/templates/starship/README.md
  • packages/create-rezi/templates/starship/package.json
  • packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts
  • packages/create-rezi/templates/starship/src/__tests__/reducer.test.ts
  • packages/create-rezi/templates/starship/src/__tests__/render.test.ts
  • packages/create-rezi/templates/starship/src/helpers/formatters.ts
  • packages/create-rezi/templates/starship/src/helpers/keybindings.ts
  • packages/create-rezi/templates/starship/src/helpers/simulation.ts
  • packages/create-rezi/templates/starship/src/helpers/state.ts
  • packages/create-rezi/templates/starship/src/main.ts
  • packages/create-rezi/templates/starship/src/screens/bridge.ts
  • packages/create-rezi/templates/starship/src/screens/cargo.ts
  • packages/create-rezi/templates/starship/src/screens/comms.ts
  • packages/create-rezi/templates/starship/src/screens/crew.ts
  • packages/create-rezi/templates/starship/src/screens/engineering.ts
  • packages/create-rezi/templates/starship/src/screens/index.ts
  • packages/create-rezi/templates/starship/src/screens/settings.ts
  • packages/create-rezi/templates/starship/src/screens/shell.ts
  • packages/create-rezi/templates/starship/src/theme.ts
  • packages/create-rezi/templates/starship/src/types.ts
  • packages/create-rezi/templates/starship/tsconfig.json

Comment on lines +1 to +13
import assert from "node:assert/strict";
import test from "node:test";
import type { RouteRenderContext, RouterApi } from "@rezi-ui/core";
import { createTestRenderer } from "@rezi-ui/core/testing";
import { createInitialState } from "../helpers/state.js";
import { STARSHIP_ROUTES } from "../screens/index.js";
import { renderBridgeScreen } from "../screens/bridge.js";
import { renderCargoScreen } from "../screens/cargo.js";
import { renderCommsScreen } from "../screens/comms.js";
import { renderCrewScreen } from "../screens/crew.js";
import { renderEngineeringScreen } from "../screens/engineering.js";
import { renderSettingsScreen } from "../screens/settings.js";
import type { RouteDeps, RouteId, StarshipState } from "../types.js";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

CI failure: organize imports in this file.

Pipeline reports Import statements could be sorted by organizeImports (Lines 4-11). Please run the repo’s “organize imports” autofix / lint-fix so CI passes.

🧰 Tools
🪛 GitHub Actions: ci

[error] 4-11: Import statements could be sorted by organizeImports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/__tests__/render.test.ts` around
lines 1 - 13, The import declarations are unsorted causing the CI failure;
reorder and group them to match the project's organizeImports rules (external
packages first, then local modules, and alphabetically within groups) and/or run
the repo's autofix (e.g., eslint --fix or your editor's "organize imports") to
apply the change; specifically adjust the block importing createTestRenderer,
createInitialState, STARSHIP_ROUTES, and the render* imports
(renderBridgeScreen, renderCargoScreen, renderCommsScreen, renderCrewScreen,
renderEngineeringScreen, renderSettingsScreen) and ensure type imports
(RouteRenderContext, RouterApi, RouteDeps, RouteId, StarshipState) are grouped
correctly.

Comment on lines +179 to +203
ui.panel("Theme Preview", [
ui.grid(
{
columns: 3,
gap: 1,
},
ui.button({
id: "theme-day",
label: "Day Shift",
intent: state.themeName === "day" ? "primary" : "secondary",
onPress: () => deps.dispatch({ type: "cycle-theme" }),
}),
ui.button({
id: "theme-night",
label: "Night Shift",
intent: state.themeName === "night" ? "primary" : "secondary",
onPress: () => deps.dispatch({ type: "cycle-theme" }),
}),
ui.button({
id: "theme-alert",
label: "Red Alert",
intent: state.themeName === "alert" ? "primary" : "secondary",
onPress: () => deps.dispatch({ type: "cycle-theme" }),
}),
),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for theme-related action types in the reducer
rg -n "cycle-theme|set-theme|set-theme-name" packages/create-rezi/templates/starship/src -t ts -B 2 -A 2

Repository: RtlZeroMemory/Rezi

Length of output: 5551


Theme Preview buttons don't select their named theme—all three just cycle.

Lines 189, 195, and 201 each dispatch { type: "cycle-theme" }, ignoring which button was clicked. The button labels and conditional intent styling (showing the active theme as "primary") imply direct selection, but the behavior contradicts this. Either add a set-theme action to the reducer with an explicit target, or rework the grid to clarify it's a cycle control, not theme selection buttons.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/settings.ts` around lines
179 - 203, The three theme buttons (ids "theme-day", "theme-night",
"theme-alert") currently all call deps.dispatch({ type: "cycle-theme" }) so they
only cycle instead of selecting the named theme; update each ui.button.onPress
to dispatch a new explicit action like deps.dispatch({ type: "set-theme", theme:
"day" }) / "night" / "alert", and add handling for "set-theme" in the reducer
that sets state.themeName to the provided theme (or, if you prefer, implement a
payload convention consistent with existing actions); ensure the intent checks
still use state.themeName to highlight the active button.

@RtlZeroMemory RtlZeroMemory merged commit 3baf62f into main Feb 24, 2026
30 of 31 checks passed
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/create-rezi/src/scaffold.ts (1)

5-123: ⚠️ Potential issue | 🟡 Minor

Update CHANGELOG.md with an entry for the new starship template. Other templates (dashboard, stress-test, animation-lab) are documented in the changelog; add a brief note about the starship template addition to keep releases traceable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/src/scaffold.ts` around lines 5 - 123, Add a brief
changelog entry documenting the new "starship" template: update CHANGELOG.md to
include a short release note referencing the TemplateDefinition with key
"starship" (from TEMPLATE_DEFINITIONS) — mention the addition of the Starship
Command Console template, its purpose/one-line summary, and link or note that
users can scaffold it via the "starship" template key (and aliases managed in
TEMPLATE_ALIASES) so releases remain traceable.
🧹 Nitpick comments (1)
packages/create-rezi/templates/starship/src/main.ts (1)

252-281: Prefer dispatch over app.update() for comms actions.

These branches bypass the reducer-first pattern. You can read state via app.getState() and then dispatch, keeping all updates consistent with the reducer flow.

♻️ Suggested refactor
   if (command === "comms-acknowledge") {
-    app.update((previous) => {
-      const candidate = filteredMessages(previous).find((message) => !message.acknowledged);
-      if (!candidate) return previous;
-      return reduceStarshipState(previous, {
-        type: "acknowledge-message",
-        messageId: candidate.id,
-      });
-    });
+    const state = app.getState();
+    const candidate = filteredMessages(state).find((message) => !message.acknowledged);
+    if (!candidate) return;
+    dispatch({ type: "acknowledge-message", messageId: candidate.id });
     return;
   }

   if (command === "comms-next-channel" || command === "comms-prev-channel") {
-    app.update((previous) => {
-      const channels: readonly StarshipState["activeChannel"][] = [
-        "fleet",
-        "local",
-        "emergency",
-        "internal",
-      ];
-      const index = channels.indexOf(previous.activeChannel);
-      const safe = index < 0 ? 0 : index;
-      const nextIndex =
-        command === "comms-next-channel"
-          ? (safe + 1) % channels.length
-          : (safe - 1 + channels.length) % channels.length;
-      const channel = channels[nextIndex] ?? "fleet";
-      return reduceStarshipState(previous, { type: "switch-channel", channel });
-    });
+    const state = app.getState();
+    const channels: readonly StarshipState["activeChannel"][] = [
+      "fleet",
+      "local",
+      "emergency",
+      "internal",
+    ];
+    const index = channels.indexOf(state.activeChannel);
+    const safe = index < 0 ? 0 : index;
+    const nextIndex =
+      command === "comms-next-channel"
+        ? (safe + 1) % channels.length
+        : (safe - 1 + channels.length) % channels.length;
+    const channel = channels[nextIndex] ?? "fleet";
+    dispatch({ type: "switch-channel", channel });
     return;
   }

As per coding guidelines: “Prefer useReducer pattern (reducer + dispatch) over raw app.update().”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/main.ts` around lines 252 - 281,
Replace the direct app.update(...) usage in the comms branches with a
reducer-style dispatch: call app.getState() to read current state, compute the
candidate/message or next channel using filteredMessages and the channels array,
and then call app.dispatch(...) with the corresponding action object instead of
calling reduceStarshipState inside app.update. Specifically, for the
"comms-acknowledge" branch use app.getState(), find candidate via
filteredMessages(previous).find(...), return early if none, otherwise
app.dispatch({ type: "acknowledge-message", messageId: candidate.id }); and for
"comms-next-channel"/"comms-prev-channel" compute nextIndex using
previous.activeChannel and the channels array then call app.dispatch({ type:
"switch-channel", channel }). Ensure you remove the app.update(...) blocks and
reference the same action types used by reduceStarshipState.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/create-rezi/templates/starship/src/helpers/state.ts`:
- Around line 554-583: Add a new reducer branch handling action.type ===
"set-cargo-bay" that updates the matching cargo item's bay and returns the
frozen updated state (following the pattern used in set-cargo-priority): map
state.cargo, replace the item with Object.freeze({ ...item, bay: action.bay })
when item.id === action.cargoId, wrap the resulting cargo array with
Object.freeze and return freezeState({ ...state, cargo }); also update the
StarshipAction union in types.ts to include the set-cargo-bay action shape
(cargoId and bay) so the action is typed across the codebase.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts`:
- Around line 47-87: The bridge screen is using hardcoded hex colors (e.g.,
"#0e1626", "#35608a", "#89d0ff", etc.) which breaks theming; update the
rendering to derive a color palette from the theme and use those tokens
everywhere: obtain theme.colors via themeSpec(...).theme.colors (or the existing
theme helper used in Cargo), create a small palette object (canvas, border,
accent, highlight, glow, sweep, pixelA/pixelB) and pass that palette into
drawShipSchematic (or wherever this snippet lives) instead of inlining hex
strings, and replace usages in ctx.clear, ctx.roundedRect, ctx.polyline,
ctx.fillCircle/ctx.circle, ctx.arc, ctx.line, ctx.setPixel, and the "USS REZI"
text; also update any line series code (lineSeries) to reference the theme-based
colors rather than raw hex literals (the same palette applies to lines 100-116).
Ensure function/variable names like drawShipSchematic, ctx.clear,
ctx.roundedRect, ctx.polyline, ctx.fillCircle, ctx.circle, ctx.arc, ctx.line,
ctx.setPixel, and lineSeries are updated to accept and use the palette.
- Around line 218-233: The "Systems Status" label uses the wrong text variant:
update the ui.text call that renders "Systems Status" (inside the ui.column
block where each(...) iterates subsystems) to use variant: "caption" instead of
variant: "label" so it matches the project's caption convention for section
labels; leave the surrounding ui.column, ui.row, and progress/ui.status logic
unchanged.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts`:
- Around line 150-233: Import the conditional helpers (maybe and/or show) from
`@rezi-ui/core` and replace the two ternary renderings: swap the cargo manifest
ternary that chooses between ui.empty(...) and ui.virtualList(...) to use a
conditional helper (e.g., show or maybe) so the empty state and the virtualList
are expressed via the helper instead of `cargo.length === 0 ? ... : ...` (refer
to the virtualList usage and ui.empty call). Likewise replace the selected-item
ternary that renders ui.column(...) vs ui.empty(...) with maybe(selected, (item)
=> ui.column(...)) ?? ui.empty(...) so the selected rendering uses the helper
(refer to the selected variable, ui.column, ui.slider, ui.field and the each
block). Ensure the top-level imports include maybe/show from `@rezi-ui/core` and
leave other symbols (virtualList, eachInline, each) intact.
- Around line 208-218: The Bay Assignment select in cargo.ts currently has a
no-op onChange; wire it to state by dispatching a new action: add a
"set-cargo-bay" variant to the StarshipAction union in types.ts, implement a
reducer branch for "set-cargo-bay" in helpers/state.ts that updates the cargo
bay (mirror the existing set-cargo-priority reducer logic), and change the
ui.select (id "cargo-bay-select", value derived from selected.bay) onChange to
dispatch the set-cargo-bay action with the newly selected bay value.

In `@packages/create-rezi/templates/starship/src/screens/crew.ts`:
- Around line 200-221: The actions array currently renders the Save button
before Cancel; update the ui.actions(...) call in the crew editor so the cancel
action (the button with id ctx.id("crew-cancel") / onPress dispatching { type:
"toggle-crew-editor" }) comes first and the primary Save action (the button with
id ctx.id("crew-save" / onPress dispatching { type: "assign-crew", crewId:
member.id, ... })) is last, preserving all props/handlers but swapping their
order to follow the cancel-first, submit-last guideline.

In `@packages/create-rezi/templates/starship/src/screens/shell.ts`:
- Around line 1-13: The Route Health list is being rendered with .map; replace
that with ui.eachInline (or eachInline) passing the items (systemHealth), a
renderer function, and an options object that contains the key function (e.g., {
key: h => h.id }) instead of embedding the key in ui.row props; update the
occurrences around the Route Health rendering (including the block referencing
systemHealth and the lines noted ~142-148) so the key is provided as the third
parameter to eachInline and the renderer only returns the row vnode.

---

Outside diff comments:
In `@packages/create-rezi/src/scaffold.ts`:
- Around line 5-123: Add a brief changelog entry documenting the new "starship"
template: update CHANGELOG.md to include a short release note referencing the
TemplateDefinition with key "starship" (from TEMPLATE_DEFINITIONS) — mention the
addition of the Starship Command Console template, its purpose/one-line summary,
and link or note that users can scaffold it via the "starship" template key (and
aliases managed in TEMPLATE_ALIASES) so releases remain traceable.

---

Nitpick comments:
In `@packages/create-rezi/templates/starship/src/main.ts`:
- Around line 252-281: Replace the direct app.update(...) usage in the comms
branches with a reducer-style dispatch: call app.getState() to read current
state, compute the candidate/message or next channel using filteredMessages and
the channels array, and then call app.dispatch(...) with the corresponding
action object instead of calling reduceStarshipState inside app.update.
Specifically, for the "comms-acknowledge" branch use app.getState(), find
candidate via filteredMessages(previous).find(...), return early if none,
otherwise app.dispatch({ type: "acknowledge-message", messageId: candidate.id
}); and for "comms-next-channel"/"comms-prev-channel" compute nextIndex using
previous.activeChannel and the channels array then call app.dispatch({ type:
"switch-channel", channel }). Ensure you remove the app.update(...) blocks and
reference the same action types used by reduceStarshipState.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2a5627 and a99275a.

📒 Files selected for processing (26)
  • README.md
  • docs/dev/repo-layout.md
  • docs/getting-started/create-rezi.md
  • docs/getting-started/examples.md
  • docs/getting-started/quickstart.md
  • docs/packages/create-rezi.md
  • docs/packages/index.md
  • packages/create-rezi/README.md
  • packages/create-rezi/src/scaffold.ts
  • packages/create-rezi/templates/starship/package.json
  • packages/create-rezi/templates/starship/src/__tests__/keybindings.test.ts
  • packages/create-rezi/templates/starship/src/__tests__/reducer.test.ts
  • packages/create-rezi/templates/starship/src/__tests__/render.test.ts
  • packages/create-rezi/templates/starship/src/helpers/keybindings.ts
  • packages/create-rezi/templates/starship/src/helpers/state.ts
  • packages/create-rezi/templates/starship/src/main.ts
  • packages/create-rezi/templates/starship/src/screens/bridge.ts
  • packages/create-rezi/templates/starship/src/screens/cargo.ts
  • packages/create-rezi/templates/starship/src/screens/comms.ts
  • packages/create-rezi/templates/starship/src/screens/crew.ts
  • packages/create-rezi/templates/starship/src/screens/engineering.ts
  • packages/create-rezi/templates/starship/src/screens/settings.ts
  • packages/create-rezi/templates/starship/src/screens/shell.ts
  • packages/create-rezi/templates/starship/src/theme.ts
  • packages/create-rezi/templates/starship/src/types.ts
  • packages/create-rezi/templates/starship/tsconfig.json
✅ Files skipped from review due to trivial changes (2)
  • docs/getting-started/create-rezi.md
  • README.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • packages/create-rezi/templates/starship/src/tests/render.test.ts
  • packages/create-rezi/templates/starship/tsconfig.json
  • packages/create-rezi/templates/starship/src/screens/settings.ts
  • packages/create-rezi/templates/starship/src/types.ts
  • packages/create-rezi/templates/starship/src/screens/engineering.ts

Comment on lines +554 to +583
if (action.type === "set-cargo-scroll") {
return freezeState({ ...state, cargoScrollTop: Math.max(0, Math.floor(action.scrollTop)) });
}

if (action.type === "set-cargo-sort") {
return freezeState({ ...state, cargoSortBy: action.sortBy });
}

if (action.type === "set-cargo-category-filter") {
return freezeState({ ...state, cargoCategoryFilter: action.category });
}

if (action.type === "select-cargo") {
return freezeState({ ...state, selectedCargoId: action.cargoId });
}

if (action.type === "set-cargo-bulk-checked") {
return freezeState({ ...state, cargoBulkChecked: action.checked });
}

if (action.type === "set-cargo-priority") {
const cargo = Object.freeze(
state.cargo.map((item) => {
if (item.id !== action.cargoId) return item;
return Object.freeze({ ...item, priority: clampInt(action.priority, 1, 5) });
}),
);
return freezeState({ ...state, cargo });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check the StarshipAction type definition in types.ts
echo "=== StarshipAction definition ==="
rg -n --type=ts -A 50 'type\s+StarshipAction' packages/create-rezi/templates/starship/src/types.ts | head -80

Repository: RtlZeroMemory/Rezi

Length of output: 3115


🏁 Script executed:

#!/bin/bash
# 2. Check for bay-related code in screens (searching for "bay" mentions)
echo "=== Bay-related code in screens ==="
find packages/create-rezi/templates/starship/src/screens -name "*.ts" -o -name "*.tsx" | xargs rg -i 'bay' -n

Repository: RtlZeroMemory/Rezi

Length of output: 661


🏁 Script executed:

#!/bin/bash
# 3. Check the full state.ts to see what's in the reducer
echo "=== Check if set-cargo-bay already exists in state.ts ==="
rg -n 'set-cargo-bay' packages/create-rezi/templates/starship/src/helpers/state.ts

Repository: RtlZeroMemory/Rezi

Length of output: 119


🏁 Script executed:

#!/bin/bash
# 4. Look at the Cargo interface/type definition to understand what properties it has
echo "=== Cargo type definition ==="
rg -n --type=ts -B 2 -A 10 'interface Cargo|type Cargo' packages/create-rezi/templates/starship/src/types.ts

Repository: RtlZeroMemory/Rezi

Length of output: 732


Add missing set-cargo-bay reducer case to persist bay changes.

The cargo screen has an interactive "Bay Assignment" select UI but no state action to persist the selection. Without the set-cargo-bay reducer case (and corresponding StarshipAction type update), bay changes are discarded on re-render.

Proposed reducer case (plus update StarshipAction union in types.ts)
   if (action.type === "set-cargo-bulk-checked") {
     return freezeState({ ...state, cargoBulkChecked: action.checked });
   }

+  if (action.type === "set-cargo-bay") {
+    const bay = clampInt(action.bay, 1, 12);
+    const cargo = Object.freeze(
+      state.cargo.map((item) => {
+        if (item.id !== action.cargoId) return item;
+        return Object.freeze({ ...item, bay });
+      }),
+    );
+    return freezeState({ ...state, cargo });
+  }
+
   if (action.type === "set-cargo-priority") {
     const cargo = Object.freeze(
       state.cargo.map((item) => {
         if (item.id !== action.cargoId) return item;
         return Object.freeze({ ...item, priority: clampInt(action.priority, 1, 5) });
       }),
     );
     return freezeState({ ...state, cargo });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/helpers/state.ts` around lines
554 - 583, Add a new reducer branch handling action.type === "set-cargo-bay"
that updates the matching cargo item's bay and returns the frozen updated state
(following the pattern used in set-cargo-priority): map state.cargo, replace the
item with Object.freeze({ ...item, bay: action.bay }) when item.id ===
action.cargoId, wrap the resulting cargo array with Object.freeze and return
freezeState({ ...state, cargo }); also update the StarshipAction union in
types.ts to include the set-cargo-bay action shape (cargoId and bay) so the
action is typed across the codebase.

Comment on lines +47 to +87
ctx.clear("#0e1626");
ctx.roundedRect(2, 2, Math.max(6, width - 4), Math.max(6, height - 4), 2, "#35608a");

const hullPoints = [
{ x: 4, y: centerY },
{ x: centerX - 3, y: centerY - 3 },
{ x: centerX + 8, y: centerY - 2 },
{ x: width - 6, y: centerY },
{ x: centerX + 8, y: centerY + 2 },
{ x: centerX - 3, y: centerY + 3 },
{ x: 4, y: centerY },
] as const;
ctx.polyline(hullPoints, "#89d0ff");

const reactorRadius = 1.2 + options.reactor * 1.8;
ctx.fillCircle(centerX - 4, centerY, reactorRadius, "#6ff0cf");
ctx.circle(centerX - 4, centerY, reactorRadius + 1.2, "#afffe8");

const shieldRadius = Math.max(2, Math.floor(5 + options.shield * 3 + options.pulse * 2));
ctx.arc(centerX + 3, centerY, shieldRadius, 0.2, Math.PI * 1.8, "#7eb9ff");

const sweepX = Math.floor(((options.sweep % 100) / 100) * (width - 8)) + 4;
ctx.line(sweepX, 3, sweepX, height - 4, "#4fd3f7");

const warpTrail: Array<Readonly<{ x: number; y: number }>> = [];
for (let i = 0; i < 8; i++) {
warpTrail.push({
x: centerX + 9 + i * 2,
y: centerY + Math.sin(i * 0.8 + options.warp * 2.1) * 1.2,
});
}
ctx.polyline(warpTrail, "#80f5ff");

for (let i = 0; i < 12; i++) {
const x = Math.floor(3 + ((i * 17 + Math.floor(options.sweep)) % Math.max(3, width - 6)));
const y = Math.floor(3 + ((i * 11 + Math.floor(options.sweep * 0.7)) % Math.max(3, height - 6)));
ctx.setPixel(x, y, i % 3 === 0 ? "#d5f3ff" : "#8ec7ff");
}

ctx.text(4, 1, "USS REZI // COMMAND SCOPE", "#b6d9ff");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace hardcoded hex colors with theme tokens (canvas + line chart).

Bridge currently hardcodes a full palette (#0e1626, #35608a, #89d0ff, etc.), so theme switching won’t apply and it violates the theming rules. Suggest deriving a palette from themeSpec(...).theme.colors (similar to Cargo) and passing it into drawShipSchematic, plus using theme-based colors for lineSeries.
As per coding guidelines, “NEVER hardcode RGB values — let the theme handle colors via tokens”.

One way to theme the palette using existing tokens + derived shades
 import { formatPower, formatWarpFactor } from "../helpers/formatters.js";
 import { selectedCrew } from "../helpers/state.js";
-import { stylesForTheme } from "../theme.js";
+import { stylesForTheme, themeSpec } from "../theme.js";
 import type { RouteDeps, StarshipState } from "../types.js";
 import { renderShell } from "./shell.js";

+type Rgb = Readonly<{ r: number; g: number; b: number }>;
+
+function toHex(color: Rgb): string {
+  const channel = (value: number) =>
+    Math.max(0, Math.min(255, Math.round(value)))
+      .toString(16)
+      .padStart(2, "0");
+  return `#${channel(color.r)}${channel(color.g)}${channel(color.b)}`;
+}
+
+function scaleRgb(color: Rgb, factor: number): Rgb {
+  return Object.freeze({
+    r: clamp(color.r * factor, 0, 255),
+    g: clamp(color.g * factor, 0, 255),
+    b: clamp(color.b * factor, 0, 255),
+  });
+}
+
+type BridgePalette = Readonly<{
+  bg: string;
+  frame: string;
+  hull: string;
+  reactor: string;
+  reactorGlow: string;
+  shield: string;
+  sweep: string;
+  warp: string;
+  starBright: string;
+  starDim: string;
+  text: string;
+}>;

 function drawShipSchematic(
   ctx: CanvasContext,
   options: Readonly<{
     warp: number;
     pulse: number;
     sweep: number;
     reactor: number;
     shield: number;
+    palette: BridgePalette;
   }>,
 ): void {
   const width = ctx.width;
   const height = ctx.height;
   const centerX = Math.floor(width * 0.35);
   const centerY = Math.floor(height * 0.52);

-  ctx.clear("#0e1626");
-  ctx.roundedRect(2, 2, Math.max(6, width - 4), Math.max(6, height - 4), 2, "#35608a");
+  ctx.clear(options.palette.bg);
+  ctx.roundedRect(
+    2,
+    2,
+    Math.max(6, width - 4),
+    Math.max(6, height - 4),
+    2,
+    options.palette.frame,
+  );

   const hullPoints = [
     { x: 4, y: centerY },
     { x: centerX - 3, y: centerY - 3 },
     { x: centerX + 8, y: centerY - 2 },
     { x: width - 6, y: centerY },
     { x: centerX + 8, y: centerY + 2 },
     { x: centerX - 3, y: centerY + 3 },
     { x: 4, y: centerY },
   ] as const;
-  ctx.polyline(hullPoints, "#89d0ff");
+  ctx.polyline(hullPoints, options.palette.hull);

   const reactorRadius = 1.2 + options.reactor * 1.8;
-  ctx.fillCircle(centerX - 4, centerY, reactorRadius, "#6ff0cf");
-  ctx.circle(centerX - 4, centerY, reactorRadius + 1.2, "#afffe8");
+  ctx.fillCircle(centerX - 4, centerY, reactorRadius, options.palette.reactor);
+  ctx.circle(centerX - 4, centerY, reactorRadius + 1.2, options.palette.reactorGlow);

   const shieldRadius = Math.max(2, Math.floor(5 + options.shield * 3 + options.pulse * 2));
-  ctx.arc(centerX + 3, centerY, shieldRadius, 0.2, Math.PI * 1.8, "#7eb9ff");
+  ctx.arc(centerX + 3, centerY, shieldRadius, 0.2, Math.PI * 1.8, options.palette.shield);

   const sweepX = Math.floor(((options.sweep % 100) / 100) * (width - 8)) + 4;
-  ctx.line(sweepX, 3, sweepX, height - 4, "#4fd3f7");
+  ctx.line(sweepX, 3, sweepX, height - 4, options.palette.sweep);

   const warpTrail: Array<Readonly<{ x: number; y: number }>> = [];
   for (let i = 0; i < 8; i++) {
     warpTrail.push({
       x: centerX + 9 + i * 2,
       y: centerY + Math.sin(i * 0.8 + options.warp * 2.1) * 1.2,
     });
   }
-  ctx.polyline(warpTrail, "#80f5ff");
+  ctx.polyline(warpTrail, options.palette.warp);

   for (let i = 0; i < 12; i++) {
     const x = Math.floor(3 + ((i * 17 + Math.floor(options.sweep)) % Math.max(3, width - 6)));
     const y = Math.floor(3 + ((i * 11 + Math.floor(options.sweep * 0.7)) % Math.max(3, height - 6)));
-    ctx.setPixel(x, y, i % 3 === 0 ? "#d5f3ff" : "#8ec7ff");
+    ctx.setPixel(x, y, i % 3 === 0 ? options.palette.starBright : options.palette.starDim);
   }

-  ctx.text(4, 1, "USS REZI // COMMAND SCOPE", "#b6d9ff");
+  ctx.text(4, 1, "USS REZI // COMMAND SCOPE", options.palette.text);
 }

 const BridgeCommandDeck = defineWidget<BridgeCommandDeckProps>((props, ctx): VNode => {
   const viewport = ctx.useViewport?.() ?? { width: 120, height: 40, breakpoint: "lg" as const };
   const redAlertId = ctx.id("red-alert");
   const [scanSweep, setScanSweep] = ctx.useState(0);
   const [scanBoost, setScanBoost] = ctx.useState(false);
   const [uptime, setUptime] = ctx.useState(0);
   const lastReactorRef = ctx.useRef(props.state.telemetry.reactorPower);

+  const palette = ctx.useMemo(() => {
+    const colors = themeSpec(props.state.themeName).theme.colors;
+    const base = colors.accent.primary;
+    const success = colors.success;
+    return Object.freeze({
+      bg: toHex(scaleRgb(base, 0.12)),
+      frame: toHex(scaleRgb(base, 0.45)),
+      hull: toHex(scaleRgb(base, 1.05)),
+      reactor: toHex(scaleRgb(success, 1.0)),
+      reactorGlow: toHex(scaleRgb(success, 1.35)),
+      shield: toHex(scaleRgb(base, 0.95)),
+      sweep: toHex(scaleRgb(base, 1.25)),
+      warp: toHex(scaleRgb(base, 1.4)),
+      starBright: toHex(scaleRgb(base, 1.8)),
+      starDim: toHex(scaleRgb(base, 1.2)),
+      text: toHex(scaleRgb(base, 1.6)),
+    });
+  }, [props.state.themeName]);
+
   const selected = selectedCrew(props.state);
   const subsystemNames = props.state.subsystems.map((item) => item.name);

   const chartWidth = clamp(Math.floor(viewport.width * 0.42), 28, 64);
   const lineSeries = ctx.useMemo(
     () =>
       Object.freeze([
         {
           data: props.state.telemetryHistory,
-          color: "#7ec8ff",
+          color: palette.hull,
           label: "Reactor",
         },
         {
           data: props.state.shieldHistory,
-          color: "#7cf0c1",
+          color: palette.reactor,
           label: "Shields",
         },
       ]),
-    [props.state.telemetryHistory, props.state.shieldHistory],
+    [props.state.telemetryHistory, props.state.shieldHistory, palette],
   );
@@
       draw: (canvasCtx) =>
         drawShipSchematic(canvasCtx, {
           warp,
           pulse: shieldPulse,
           sweep: scanSweep,
           reactor,
           shield: props.state.telemetry.shieldStrength / 100,
+          palette,
         }),
     }),

Also applies to: 100-116

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts` around lines
47 - 87, The bridge screen is using hardcoded hex colors (e.g., "#0e1626",
"#35608a", "#89d0ff", etc.) which breaks theming; update the rendering to derive
a color palette from the theme and use those tokens everywhere: obtain
theme.colors via themeSpec(...).theme.colors (or the existing theme helper used
in Cargo), create a small palette object (canvas, border, accent, highlight,
glow, sweep, pixelA/pixelB) and pass that palette into drawShipSchematic (or
wherever this snippet lives) instead of inlining hex strings, and replace usages
in ctx.clear, ctx.roundedRect, ctx.polyline, ctx.fillCircle/ctx.circle, ctx.arc,
ctx.line, ctx.setPixel, and the "USS REZI" text; also update any line series
code (lineSeries) to reference the theme-based colors rather than raw hex
literals (the same palette applies to lines 100-116). Ensure function/variable
names like drawShipSchematic, ctx.clear, ctx.roundedRect, ctx.polyline,
ctx.fillCircle, ctx.circle, ctx.arc, ctx.line, ctx.setPixel, and lineSeries are
updated to accept and use the palette.

Comment on lines +218 to +233
ui.column({ gap: 1 }, [
ui.text("Systems Status", { variant: "label" }),
each(
props.state.subsystems.slice(0, 8),
(subsystem, index) => {
const progress = bootProgress[index] ?? 0;
const online = subsystem.health >= props.state.alertThreshold;
return ui.row({ key: subsystem.id, gap: 1, items: "center", wrap: true }, [
ui.status(online ? "online" : "busy", { showLabel: false }),
ui.text(`${subsystem.name}: ${online ? "OK" : "CHECK"}`),
ui.progress(progress, {
label: `${String(Math.round(progress * 100)).padStart(3, " ")}%`,
dsTone: online ? "default" : "warning",
}),
]);
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check the exact content at the specified lines
head -233 "packages/create-rezi/templates/starship/src/screens/bridge.ts" | tail -16

Repository: RtlZeroMemory/Rezi

Length of output: 838


🏁 Script executed:

# Search for ui.text usage with "label" variant to understand current patterns
rg 'ui\.text.*variant.*label' packages/create-rezi/templates/starship/src/ -A 1 -B 1

Repository: RtlZeroMemory/Rezi

Length of output: 702


🏁 Script executed:

# Search for ui.text usage with "caption" variant to see the preferred pattern
rg 'ui\.text.*variant.*caption' packages/create-rezi/templates/starship/src/ -A 1 -B 1

Repository: RtlZeroMemory/Rezi

Length of output: 4032


Change variant: "label" to variant: "caption" for "Systems Status".

The coding guidelines specify that labels should use variant: "caption", not variant: "label". The codebase consistently applies variant: "caption" for similar section labels and descriptive text throughout the template.

- ui.text("Systems Status", { variant: "label" }),
+ ui.text("Systems Status", { variant: "caption" }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ui.column({ gap: 1 }, [
ui.text("Systems Status", { variant: "label" }),
each(
props.state.subsystems.slice(0, 8),
(subsystem, index) => {
const progress = bootProgress[index] ?? 0;
const online = subsystem.health >= props.state.alertThreshold;
return ui.row({ key: subsystem.id, gap: 1, items: "center", wrap: true }, [
ui.status(online ? "online" : "busy", { showLabel: false }),
ui.text(`${subsystem.name}: ${online ? "OK" : "CHECK"}`),
ui.progress(progress, {
label: `${String(Math.round(progress * 100)).padStart(3, " ")}%`,
dsTone: online ? "default" : "warning",
}),
]);
},
ui.column({ gap: 1 }, [
ui.text("Systems Status", { variant: "caption" }),
each(
props.state.subsystems.slice(0, 8),
(subsystem, index) => {
const progress = bootProgress[index] ?? 0;
const online = subsystem.health >= props.state.alertThreshold;
return ui.row({ key: subsystem.id, gap: 1, items: "center", wrap: true }, [
ui.status(online ? "online" : "busy", { showLabel: false }),
ui.text(`${subsystem.name}: ${online ? "OK" : "CHECK"}`),
ui.progress(progress, {
label: `${String(Math.round(progress * 100)).padStart(3, " ")}%`,
dsTone: online ? "default" : "warning",
}),
]);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/bridge.ts` around lines
218 - 233, The "Systems Status" label uses the wrong text variant: update the
ui.text call that renders "Systems Status" (inside the ui.column block where
each(...) iterates subsystems) to use variant: "caption" instead of variant:
"label" so it matches the project's caption convention for section labels; leave
the surrounding ui.column, ui.row, and progress/ui.status logic unchanged.

Comment on lines +150 to +233
ui.panel("Manifest", [
cargo.length === 0
? ui.empty("No cargo matches the active filter", {
description: "Adjust category or sort settings",
})
: ui.virtualList<CargoItem>({
id: "cargo-virtual-list",
items: cargo,
itemHeight: 1,
overscan: 5,
renderItem: (item, index, focused) =>
ui.row(
{
key: item.id,
gap: 1,
wrap: true,
...(focused ? { style: { inverse: true } } : {}),
},
[
ui.text(`${String(index + 1).padStart(4, "0")}`),
ui.text(item.name),
...eachInline(
[categoryLabel(item.category)],
(tag) => ui.tag(tag, { variant: categoryVariant(item.category) }),
{ key: (_, i) => `${item.id}-tag-${i}` },
),
ui.text(`Q${item.quantity}`, { variant: "code" }),
ui.text(`P${item.priority}`, { variant: "caption" }),
],
),
onScroll: (scrollTop) => deps.dispatch({ type: "set-cargo-scroll", scrollTop }),
onSelect: (item) => deps.dispatch({ type: "select-cargo", cargoId: item.id }),
}),
]),
]),
ui.panel("Selected Cargo", [
selected
? ui.column({ gap: 1 }, [
ui.row({ gap: 1, wrap: true }, [
ui.badge(selected.name, { variant: "info" }),
ui.tag(categoryLabel(selected.category), {
variant: categoryVariant(selected.category),
}),
]),
ui.slider({
id: "cargo-priority-slider",
value: selected.priority,
min: 1,
max: 5,
step: 1,
label: "Priority",
onChange: (priority) =>
deps.dispatch({
type: "set-cargo-priority",
cargoId: selected.id,
priority,
}),
}),
ui.field({
label: "Bay Assignment",
children: ui.select({
id: "cargo-bay-select",
value: String(selected.bay),
options: Array.from({ length: 12 }, (_, index) => ({
value: String(index + 1),
label: `Bay ${index + 1}`,
})),
onChange: () => {},
}),
}),
each(
[
`Quantity: ${selected.quantity}`,
`Priority: ${selected.priority}`,
`Bay: ${selected.bay}`,
],
(line, index) => ui.text(line, { key: `${selected.id}-detail-${index}`, variant: "caption" }),
{ key: (_, index) => `${selected?.id ?? "none"}-${index}` },
),
])
: ui.empty("No cargo item selected", {
description: "Select an item in the manifest",
}),
]),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for the conditional rendering helper functions
rg -n --type=ts '\b(show|when|maybe|match)\s*\(' packages/create-rezi/templates/starship/src --max-count=20

Repository: RtlZeroMemory/Rezi

Length of output: 2368


🏁 Script executed:

# Check if these functions are exported from the main package
rg -n --type=ts 'export.*\b(show|when|maybe|match)\b' packages/ --max-count=20

Repository: RtlZeroMemory/Rezi

Length of output: 600


🏁 Script executed:

# Look for where these might be imported from in the templates
rg -n --type=ts 'import.*\{.*\b(show|when|maybe|match)\b' packages/create-rezi/templates --max-count=20

Repository: RtlZeroMemory/Rezi

Length of output: 170


🏁 Script executed:

# Check the actual cargo.ts file to see current state and imports
head -n 30 packages/create-rezi/templates/starship/src/screens/cargo.ts

Repository: RtlZeroMemory/Rezi

Length of output: 1261


🏁 Script executed:

# Look at crew.ts to see how conditional helpers are used
sed -n '130,155p' packages/create-rezi/templates/starship/src/screens/crew.ts
sed -n '145,160p' packages/create-rezi/templates/starship/src/screens/crew.ts

Repository: RtlZeroMemory/Rezi

Length of output: 1768


🏁 Script executed:

# Check comms.ts for additional examples
sed -n '50,70p' packages/create-rezi/templates/starship/src/screens/comms.ts
sed -n '135,155p' packages/create-rezi/templates/starship/src/screens/comms.ts

Repository: RtlZeroMemory/Rezi

Length of output: 1886


🏁 Script executed:

# Verify that cargo.ts is missing the conditional helper imports
rg -A 5 "^import" packages/create-rezi/templates/starship/src/screens/cargo.ts | head -n 15

Repository: RtlZeroMemory/Rezi

Length of output: 674


🏁 Script executed:

# Confirm the exact ternary patterns in cargo.ts that should be converted
sed -n '150,155p' packages/create-rezi/templates/starship/src/screens/cargo.ts
sed -n '185,190p' packages/create-rezi/templates/starship/src/screens/cargo.ts

Repository: RtlZeroMemory/Rezi

Length of output: 612


🏁 Script executed:

# Double-check the exact pattern used: are there any ternaries in crew.ts for comparison?
rg -B 2 -A 5 'selected\s*\?' packages/create-rezi/templates/starship/src/screens/crew.ts | head -n 40

Repository: RtlZeroMemory/Rezi

Length of output: 600


🏁 Script executed:

# Check comms.ts for ternary patterns
rg -B 2 -A 5 'state\.\w+\s*\?' packages/create-rezi/templates/starship/src/screens/comms.ts | head -n 20

Repository: RtlZeroMemory/Rezi

Length of output: 44


Import and use conditional rendering helpers instead of ternaries.

The cargo.ts file uses ternary operators at lines 151 and 186, but the guideline requires using show() / when() / maybe() / match() for consistency with other templates like crew.ts and comms.ts.

Import maybe and/or show from @rezi-ui/core and refactor:

  • Line 151: Replace cargo.length === 0 ? ui.empty(...) : ui.virtualList(...) with maybe() or conditional logic
  • Line 186: Replace selected ? ui.column(...) : ui.empty(...) with maybe(selected, (item) => ui.column(...)) ?? ui.empty(...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts` around lines
150 - 233, Import the conditional helpers (maybe and/or show) from `@rezi-ui/core`
and replace the two ternary renderings: swap the cargo manifest ternary that
chooses between ui.empty(...) and ui.virtualList(...) to use a conditional
helper (e.g., show or maybe) so the empty state and the virtualList are
expressed via the helper instead of `cargo.length === 0 ? ... : ...` (refer to
the virtualList usage and ui.empty call). Likewise replace the selected-item
ternary that renders ui.column(...) vs ui.empty(...) with maybe(selected, (item)
=> ui.column(...)) ?? ui.empty(...) so the selected rendering uses the helper
(refer to the selected variable, ui.column, ui.slider, ui.field and the each
block). Ensure the top-level imports include maybe/show from `@rezi-ui/core` and
leave other symbols (virtualList, eachInline, each) intact.

Comment on lines +208 to +218
ui.field({
label: "Bay Assignment",
children: ui.select({
id: "cargo-bay-select",
value: String(selected.bay),
options: Array.from({ length: 12 }, (_, index) => ({
value: String(index + 1),
label: `Bay ${index + 1}`,
})),
onChange: () => {},
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -path "*/packages/create-rezi/templates/starship/src/screens/cargo.ts" -type f

Repository: RtlZeroMemory/Rezi

Length of output: 124


🏁 Script executed:

cat -n packages/create-rezi/templates/starship/src/screens/cargo.ts | head -250

Repository: RtlZeroMemory/Rezi

Length of output: 11314


🏁 Script executed:

# Search for reducer setup and action type definitions in the template
rg -A 5 "type.*Action|useReducer|dispatch" packages/create-rezi/templates/starship/src/ --max-count=20

Repository: RtlZeroMemory/Rezi

Length of output: 47798


🏁 Script executed:

# Check the StarshipAction type definition and look for bay-related actions
rg -A 50 "type StarshipAction" packages/create-rezi/templates/starship/src/types.ts

Repository: RtlZeroMemory/Rezi

Length of output: 2877


🏁 Script executed:

# Search for any existing "bay" references in action handlers or reducer
rg "bay" packages/create-rezi/templates/starship/src/ -i

Repository: RtlZeroMemory/Rezi

Length of output: 1027


🏁 Script executed:

# Check for reducer implementation
fd "*.ts" packages/create-rezi/templates/starship/src/helpers/ -type f

Repository: RtlZeroMemory/Rezi

Length of output: 232


🏁 Script executed:

# Find reducer and state helpers
find packages/create-rezi/templates/starship/src/helpers -name "*.ts" -type f

Repository: RtlZeroMemory/Rezi

Length of output: 321


🏁 Script executed:

# Check the reducer implementation for cargo-related actions
rg -A 3 "set-cargo-priority|select-cargo" packages/create-rezi/templates/starship/src/helpers/

Repository: RtlZeroMemory/Rezi

Length of output: 834


🏁 Script executed:

# View the reducer implementation for cargo actions
rg -B 2 -A 10 "set-cargo-priority|select-cargo" packages/create-rezi/templates/starship/src/helpers/state.ts

Repository: RtlZeroMemory/Rezi

Length of output: 669


🏁 Script executed:

# Check if there's any set-cargo-bay action handler in the reducer
rg "set-cargo-bay" packages/create-rezi/templates/starship/src/

Repository: RtlZeroMemory/Rezi

Length of output: 44


Wire "Bay Assignment" select to state — current onChange is a no-op.

onChange: () => {} makes the control feel broken in an otherwise "full integration" template. Add a set-cargo-bay action to the StarshipAction union type (in types.ts), implement its reducer case (in helpers/state.ts), and dispatch it from the select:

Changes required (cargo.ts, types.ts, state.ts)

cargo.ts — replace the onChange handler:

 onChange: () => {},
+onChange: (value) =>
+  deps.dispatch({
+    type: "set-cargo-bay",
+    cargoId: selected.id,
+    bay: Number(value),
+  }),

types.ts — add to StarshipAction union:

+ | Readonly<{ type: "set-cargo-bay"; cargoId: string; bay: number }>

state.ts — add reducer case (similar to set-cargo-priority):

+if (action.type === "set-cargo-bay") {
+  const cargo = Object.freeze(
+    state.cargo.map((item) => {
+      if (item.id !== action.cargoId) return item;
+      return Object.freeze({ ...item, bay: clampInt(action.bay, 1, 12) });
+    }),
+  );
+  return freezeState({ ...state, cargo });
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/cargo.ts` around lines
208 - 218, The Bay Assignment select in cargo.ts currently has a no-op onChange;
wire it to state by dispatching a new action: add a "set-cargo-bay" variant to
the StarshipAction union in types.ts, implement a reducer branch for
"set-cargo-bay" in helpers/state.ts that updates the cargo bay (mirror the
existing set-cargo-priority reducer logic), and change the ui.select (id
"cargo-bay-select", value derived from selected.bay) onChange to dispatch the
set-cargo-bay action with the newly selected bay value.

Comment on lines +200 to +221
ui.actions([
ui.button({
id: ctx.id("crew-save"),
label: "Save Assignment",
intent: "primary",
onPress: () => {
if (!staffingError) {
props.dispatch({
type: "assign-crew",
crewId: member.id,
department: props.state.crewDraft.department,
status: props.state.crewDraft.status,
});
}
},
}),
ui.button({
id: ctx.id("crew-cancel"),
label: "Cancel",
intent: "secondary",
onPress: () => props.dispatch({ type: "toggle-crew-editor" }),
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Swap Cancel/Save order in Assignment Editor actions.

Cancel should precede the primary Save button to match the form action order guideline.

🔧 Suggested change
             ui.actions([
-              ui.button({
-                id: ctx.id("crew-save"),
-                label: "Save Assignment",
-                intent: "primary",
-                onPress: () => {
-                  if (!staffingError) {
-                    props.dispatch({
-                      type: "assign-crew",
-                      crewId: member.id,
-                      department: props.state.crewDraft.department,
-                      status: props.state.crewDraft.status,
-                    });
-                  }
-                },
-              }),
               ui.button({
                 id: ctx.id("crew-cancel"),
                 label: "Cancel",
                 intent: "secondary",
                 onPress: () => props.dispatch({ type: "toggle-crew-editor" }),
               }),
+              ui.button({
+                id: ctx.id("crew-save"),
+                label: "Save Assignment",
+                intent: "primary",
+                onPress: () => {
+                  if (!staffingError) {
+                    props.dispatch({
+                      type: "assign-crew",
+                      crewId: member.id,
+                      department: props.state.crewDraft.department,
+                      status: props.state.crewDraft.status,
+                    });
+                  }
+                },
+              }),
             ]),

As per coding guidelines: “End forms with ui.actions([cancelBtn, submitBtn]) — cancel first, primary submit last.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ui.actions([
ui.button({
id: ctx.id("crew-save"),
label: "Save Assignment",
intent: "primary",
onPress: () => {
if (!staffingError) {
props.dispatch({
type: "assign-crew",
crewId: member.id,
department: props.state.crewDraft.department,
status: props.state.crewDraft.status,
});
}
},
}),
ui.button({
id: ctx.id("crew-cancel"),
label: "Cancel",
intent: "secondary",
onPress: () => props.dispatch({ type: "toggle-crew-editor" }),
}),
ui.actions([
ui.button({
id: ctx.id("crew-cancel"),
label: "Cancel",
intent: "secondary",
onPress: () => props.dispatch({ type: "toggle-crew-editor" }),
}),
ui.button({
id: ctx.id("crew-save"),
label: "Save Assignment",
intent: "primary",
onPress: () => {
if (!staffingError) {
props.dispatch({
type: "assign-crew",
crewId: member.id,
department: props.state.crewDraft.department,
status: props.state.crewDraft.status,
});
}
},
}),
]),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/crew.ts` around lines 200
- 221, The actions array currently renders the Save button before Cancel; update
the ui.actions(...) call in the crew editor so the cancel action (the button
with id ctx.id("crew-cancel") / onPress dispatching { type: "toggle-crew-editor"
}) comes first and the primary Save action (the button with id
ctx.id("crew-save" / onPress dispatching { type: "assign-crew", crewId:
member.id, ... })) is last, preserving all props/handlers but swapping their
order to follow the cancel-first, submit-last guideline.

Comment on lines +1 to +13
import type {
CommandItem,
CommandSource,
RegisteredBinding,
RouteRenderContext,
VNode,
} from "@rezi-ui/core";
import { routerBreadcrumb, routerTabs, ui } from "@rezi-ui/core";
import { alertLabel } from "../helpers/formatters.js";
import { systemHealth } from "../helpers/state.js";
import { alertBadgeVariant, PRODUCT_NAME, PRODUCT_TAGLINE, stylesForTheme, themeSpec } from "../theme.js";
import { toCoreToast, type RouteDeps, type RouteId, type StarshipState } from "../types.js";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "shell.ts" | grep -E "packages/create-rezi/templates/starship"

Repository: RtlZeroMemory/Rezi

Length of output: 124


🏁 Script executed:

if [ -f "packages/create-rezi/templates/starship/src/screens/shell.ts" ]; then
  wc -l "packages/create-rezi/templates/starship/src/screens/shell.ts"
fi

Repository: RtlZeroMemory/Rezi

Length of output: 126


🏁 Script executed:

# Read the file around lines 142-148 to see the Route Health panel code
if [ -f "packages/create-rezi/templates/starship/src/screens/shell.ts" ]; then
  sed -n '135,155p' "packages/create-rezi/templates/starship/src/screens/shell.ts"
fi

Repository: RtlZeroMemory/Rezi

Length of output: 815


🏁 Script executed:

# Search for all occurrences of .map used in list rendering in this file
rg -n "\.map\(" "packages/create-rezi/templates/starship/src/screens/shell.ts" -A 3

Repository: RtlZeroMemory/Rezi

Length of output: 1021


🏁 Script executed:

# Verify if eachInline is exported from `@rezi-ui/core`
rg -l "export.*eachInline" --type ts --type tsx

Repository: RtlZeroMemory/Rezi

Length of output: 89


🏁 Script executed:

# Search for eachInline definition or export in the codebase
rg "eachInline" --type ts -l

Repository: RtlZeroMemory/Rezi

Length of output: 272


🏁 Script executed:

# Check if eachInline is mentioned in the package exports
rg "eachInline" packages/core/src --type ts -A 2

Repository: RtlZeroMemory/Rezi

Length of output: 1540


🏁 Script executed:

# Check the main export file
if [ -f "packages/core/src/index.ts" ]; then
  cat -n "packages/core/src/index.ts" | head -100
fi

Repository: RtlZeroMemory/Rezi

Length of output: 3325


🏁 Script executed:

# Find where eachInline is exported in index.ts
rg "eachInline" "packages/core/src/index.ts" -B 2 -A 2

Repository: RtlZeroMemory/Rezi

Length of output: 138


🏁 Script executed:

# Check the collections.ts file to understand the eachInline signature and usage
cat -n "packages/core/src/widgets/collections.ts" | head -80

Repository: RtlZeroMemory/Rezi

Length of output: 2278


🏁 Script executed:

# Let's also check the current imports in the shell.ts file
sed -n '1,15p' "packages/create-rezi/templates/starship/src/screens/shell.ts"

Repository: RtlZeroMemory/Rezi

Length of output: 605


🏁 Script executed:

# Verify if eachInline has any function overloads
rg -A 10 "export function eachInline" "packages/core/src/widgets/collections.ts"

Repository: RtlZeroMemory/Rezi

Length of output: 392


Use eachInline() for Route Health list rendering with proper options parameter.

The list is rendered via map despite the list-rendering guideline. Switch to eachInline() for consistent list rendering. Note: eachInline requires three parameters—the key function must be passed in the options object, not embedded in the ui.row props.

♻️ Suggested change
-import { routerBreadcrumb, routerTabs, ui } from "@rezi-ui/core";
+import { eachInline, routerBreadcrumb, routerTabs, ui } from "@rezi-ui/core";
@@
         ui.panel("Route Health", [
-          ...options.deps.routes.map((route) =>
+          ...eachInline(options.deps.routes, (route) =>
             ui.row({ key: `route-health-${route.id}`, gap: 1, items: "center" }, [
               ui.status(route.id === currentRoute ? "online" : "away", { showLabel: false }),
               ui.text(route.title, { variant: "label" }),
             ]),
+          { key: (route) => `route-health-${route.id}` },
           ),
         ]),

As per coding guidelines: "Use each() / eachInline() for list rendering with keys."

Also applies to: 142-148

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/create-rezi/templates/starship/src/screens/shell.ts` around lines 1
- 13, The Route Health list is being rendered with .map; replace that with
ui.eachInline (or eachInline) passing the items (systemHealth), a renderer
function, and an options object that contains the key function (e.g., { key: h
=> h.id }) instead of embedding the key in ui.row props; update the occurrences
around the Route Health rendering (including the block referencing systemHealth
and the lines noted ~142-148) so the key is provided as the third parameter to
eachInline and the renderer only returns the row vnode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant