create-rezi 0.1.0-alpha.49 → 0.1.0-alpha.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/templates/animation-lab/README.md +1 -1
- package/templates/animation-lab/src/__tests__/reducer.test.ts +2 -2
- package/templates/animation-lab/src/__tests__/render.test.ts +15 -1
- package/templates/animation-lab/src/helpers/state.ts +5 -22
- package/templates/animation-lab/src/screens/reactor-lab.ts +44 -31
- package/templates/animation-lab/src/types.ts +0 -2
- package/templates/cli-tool/src/screens/logs.ts +2 -2
- package/templates/dashboard/src/screens/overview.ts +24 -6
- package/templates/starship/src/__tests__/render.test.ts +17 -1
- package/templates/starship/src/helpers/layout.ts +10 -11
- package/templates/starship/src/main.ts +1 -1
- package/templates/starship/src/screens/bridge.ts +9 -9
- package/templates/starship/src/screens/cargo.ts +12 -5
- package/templates/starship/src/screens/comms.ts +4 -4
- package/templates/starship/src/screens/crew.ts +39 -19
- package/templates/starship/src/screens/engineering.ts +35 -24
- package/templates/starship/src/screens/primitives.ts +1 -1
- package/templates/starship/src/screens/settings.ts +4 -4
- package/templates/starship/src/screens/shell.ts +53 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-rezi",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.50",
|
|
4
4
|
"description": "Scaffold a Rezi terminal UI app.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://rezitui.dev",
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"bun": ">=1.3.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@rezi-ui/testkit": "0.1.0-alpha.
|
|
31
|
+
"@rezi-ui/testkit": "0.1.0-alpha.50"
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -15,7 +15,7 @@ Scaffolded with `create-rezi` using the **__TEMPLATE_LABEL__** template.
|
|
|
15
15
|
|
|
16
16
|
- `src/types.ts`: animation state and action contracts.
|
|
17
17
|
- `src/theme.ts`: template identity constants.
|
|
18
|
-
- `src/helpers/state.ts`:
|
|
18
|
+
- `src/helpers/state.ts`: viewport normalization + reducer transitions.
|
|
19
19
|
- `src/helpers/keybindings.ts`: key to command resolver.
|
|
20
20
|
- `src/screens/reactor-lab.ts`: reactor field screen renderer.
|
|
21
21
|
- `src/main.ts`: app bootstrap, resize handling, animation loop.
|
|
@@ -23,8 +23,8 @@ test("animation lab reducer applies viewport", () => {
|
|
|
23
23
|
|
|
24
24
|
assert.equal(resized.viewportCols, 70);
|
|
25
25
|
assert.equal(resized.viewportRows, 20);
|
|
26
|
-
assert.
|
|
27
|
-
assert.
|
|
26
|
+
assert.equal("panelWidth" in resized, false);
|
|
27
|
+
assert.equal("panelHeight" in resized, false);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
test("animation lab reducer supports nudge and phase cycle", () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createTestRenderer } from "@rezi-ui/core/testing";
|
|
4
|
-
import { createInitialState } from "../helpers/state.js";
|
|
4
|
+
import { createInitialState, reduceAnimationLabState } from "../helpers/state.js";
|
|
5
5
|
import { renderReactorLab } from "../screens/reactor-lab.js";
|
|
6
6
|
|
|
7
7
|
test("animation lab screen renders core sections", () => {
|
|
@@ -13,3 +13,17 @@ test("animation lab screen renders core sections", () => {
|
|
|
13
13
|
assert.match(output, /Animation Lab/);
|
|
14
14
|
assert.match(output, /controls: space\/p autoplay/);
|
|
15
15
|
});
|
|
16
|
+
|
|
17
|
+
test("animation lab screen renders after compact viewport resize", () => {
|
|
18
|
+
const initial = createInitialState({ cols: 140, rows: 48 });
|
|
19
|
+
const compact = reduceAnimationLabState(initial, {
|
|
20
|
+
type: "apply-viewport",
|
|
21
|
+
cols: 68,
|
|
22
|
+
rows: 20,
|
|
23
|
+
});
|
|
24
|
+
const renderer = createTestRenderer({ viewport: { cols: 68, rows: 20 } });
|
|
25
|
+
const output = renderer.render(renderReactorLab(compact)).toText();
|
|
26
|
+
|
|
27
|
+
assert.match(output, /Animation Lab/);
|
|
28
|
+
assert.match(output, /controls: space\/p autoplay/);
|
|
29
|
+
});
|
|
@@ -31,36 +31,25 @@ function toPositiveInt(value: number | undefined, fallback: number): number {
|
|
|
31
31
|
return value;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function
|
|
34
|
+
function resolveViewport(
|
|
35
35
|
cols: number,
|
|
36
36
|
rows: number,
|
|
37
37
|
): Readonly<{
|
|
38
38
|
viewportCols: number;
|
|
39
39
|
viewportRows: number;
|
|
40
|
-
panelWidth: number;
|
|
41
|
-
panelHeight: number;
|
|
42
40
|
}> {
|
|
43
41
|
const viewportCols = clamp(toPositiveInt(cols, 96), 20, 500);
|
|
44
42
|
const viewportRows = clamp(toPositiveInt(rows, 32), 10, 200);
|
|
45
|
-
|
|
46
|
-
const maxPanelWidth = Math.max(20, viewportCols - 4);
|
|
47
|
-
const panelWidth = maxPanelWidth < 44 ? maxPanelWidth : clamp(maxPanelWidth, 44, 140);
|
|
48
|
-
|
|
49
|
-
const maxPanelHeight = Math.max(8, viewportRows - 4);
|
|
50
|
-
const panelHeight = maxPanelHeight < 18 ? maxPanelHeight : clamp(maxPanelHeight, 18, 40);
|
|
51
|
-
|
|
52
|
-
return Object.freeze({ viewportCols, viewportRows, panelWidth, panelHeight });
|
|
43
|
+
return Object.freeze({ viewportCols, viewportRows });
|
|
53
44
|
}
|
|
54
45
|
|
|
55
46
|
export function createInitialState(viewport?: Viewport): AnimationLabState {
|
|
56
|
-
const layout =
|
|
47
|
+
const layout = resolveViewport(viewport?.cols ?? 96, viewport?.rows ?? 32);
|
|
57
48
|
return Object.freeze({
|
|
58
49
|
tick: 0,
|
|
59
50
|
phase: 0,
|
|
60
51
|
viewportCols: layout.viewportCols,
|
|
61
52
|
viewportRows: layout.viewportRows,
|
|
62
|
-
panelWidth: layout.panelWidth,
|
|
63
|
-
panelHeight: layout.panelHeight,
|
|
64
53
|
panelOpacity: 0.9,
|
|
65
54
|
driftTarget: 0.15,
|
|
66
55
|
fluxTarget: 0.58,
|
|
@@ -97,8 +86,6 @@ function advanceState(previous: AnimationLabState): AnimationLabState {
|
|
|
97
86
|
phase: previous.phase,
|
|
98
87
|
viewportCols: previous.viewportCols,
|
|
99
88
|
viewportRows: previous.viewportRows,
|
|
100
|
-
panelWidth: previous.panelWidth,
|
|
101
|
-
panelHeight: previous.panelHeight,
|
|
102
89
|
panelOpacity: clamp(panelOpacityTarget, 0.3, 1),
|
|
103
90
|
driftTarget,
|
|
104
91
|
fluxTarget,
|
|
@@ -109,12 +96,10 @@ function advanceState(previous: AnimationLabState): AnimationLabState {
|
|
|
109
96
|
}
|
|
110
97
|
|
|
111
98
|
function applyViewport(previous: AnimationLabState, cols: number, rows: number): AnimationLabState {
|
|
112
|
-
const layout =
|
|
99
|
+
const layout = resolveViewport(cols, rows);
|
|
113
100
|
if (
|
|
114
101
|
previous.viewportCols === layout.viewportCols &&
|
|
115
|
-
previous.viewportRows === layout.viewportRows
|
|
116
|
-
previous.panelWidth === layout.panelWidth &&
|
|
117
|
-
previous.panelHeight === layout.panelHeight
|
|
102
|
+
previous.viewportRows === layout.viewportRows
|
|
118
103
|
) {
|
|
119
104
|
return previous;
|
|
120
105
|
}
|
|
@@ -123,8 +108,6 @@ function applyViewport(previous: AnimationLabState, cols: number, rows: number):
|
|
|
123
108
|
...previous,
|
|
124
109
|
viewportCols: layout.viewportCols,
|
|
125
110
|
viewportRows: layout.viewportRows,
|
|
126
|
-
panelWidth: layout.panelWidth,
|
|
127
|
-
panelHeight: layout.panelHeight,
|
|
128
111
|
});
|
|
129
112
|
}
|
|
130
113
|
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defineWidget,
|
|
3
|
+
heightConstraints,
|
|
4
|
+
rgb,
|
|
3
5
|
ui,
|
|
4
6
|
useSequence,
|
|
5
7
|
useSpring,
|
|
6
8
|
useStagger,
|
|
7
9
|
useTransition,
|
|
10
|
+
visibilityConstraints,
|
|
11
|
+
widthConstraints,
|
|
8
12
|
type CanvasContext,
|
|
9
13
|
type VNode,
|
|
10
14
|
} from "@rezi-ui/core";
|
|
@@ -12,46 +16,46 @@ import { APP_NAME, PRODUCT_TAGLINE, TEMPLATE_LABEL } from "../theme.js";
|
|
|
12
16
|
import type { AnimationLabState } from "../types.js";
|
|
13
17
|
|
|
14
18
|
type Palette = Readonly<{
|
|
15
|
-
title:
|
|
19
|
+
title: number;
|
|
16
20
|
accent: string;
|
|
17
21
|
core: string;
|
|
18
22
|
hot: string;
|
|
19
23
|
wave: string;
|
|
20
|
-
module:
|
|
24
|
+
module: number;
|
|
21
25
|
}>;
|
|
22
26
|
|
|
23
27
|
const PALETTES: readonly Palette[] = Object.freeze([
|
|
24
28
|
Object.freeze({
|
|
25
|
-
title:
|
|
29
|
+
title: rgb(120, 225, 255),
|
|
26
30
|
accent: "#80dfff",
|
|
27
31
|
core: "#62ffd2",
|
|
28
32
|
hot: "#ffd28a",
|
|
29
33
|
wave: "#98ffc2",
|
|
30
|
-
module:
|
|
34
|
+
module: rgb(204, 232, 244),
|
|
31
35
|
}),
|
|
32
36
|
Object.freeze({
|
|
33
|
-
title:
|
|
37
|
+
title: rgb(173, 198, 255),
|
|
34
38
|
accent: "#9fb4ff",
|
|
35
39
|
core: "#7ee6ff",
|
|
36
40
|
hot: "#ffb26b",
|
|
37
41
|
wave: "#c4ff8f",
|
|
38
|
-
module:
|
|
42
|
+
module: rgb(214, 222, 245),
|
|
39
43
|
}),
|
|
40
44
|
Object.freeze({
|
|
41
|
-
title:
|
|
45
|
+
title: rgb(160, 255, 205),
|
|
42
46
|
accent: "#84ffd4",
|
|
43
47
|
core: "#65f5b2",
|
|
44
48
|
hot: "#ffc66f",
|
|
45
49
|
wave: "#9cf4ff",
|
|
46
|
-
module:
|
|
50
|
+
module: rgb(209, 239, 224),
|
|
47
51
|
}),
|
|
48
52
|
Object.freeze({
|
|
49
|
-
title:
|
|
53
|
+
title: rgb(255, 212, 150),
|
|
50
54
|
accent: "#ffd48e",
|
|
51
55
|
core: "#ffc871",
|
|
52
56
|
hot: "#ff8c6a",
|
|
53
57
|
wave: "#a7ffe3",
|
|
54
|
-
module:
|
|
58
|
+
module: rgb(240, 224, 208),
|
|
55
59
|
}),
|
|
56
60
|
]);
|
|
57
61
|
|
|
@@ -390,8 +394,8 @@ type CommandDeckProps = Readonly<{
|
|
|
390
394
|
key?: string;
|
|
391
395
|
tick: number;
|
|
392
396
|
phase: number;
|
|
393
|
-
|
|
394
|
-
|
|
397
|
+
viewportCols: number;
|
|
398
|
+
viewportRows: number;
|
|
395
399
|
driftTarget: number;
|
|
396
400
|
fluxTarget: number;
|
|
397
401
|
orbitTarget: number;
|
|
@@ -432,11 +436,13 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
432
436
|
});
|
|
433
437
|
|
|
434
438
|
const palette = paletteForPhase(props.phase);
|
|
435
|
-
const
|
|
436
|
-
const
|
|
437
|
-
const
|
|
439
|
+
const shellWidth = clamp(props.viewportCols - 4, 20, 140);
|
|
440
|
+
const shellHeight = clamp(props.viewportRows - 4, 8, 40);
|
|
441
|
+
const compact = shellWidth < 72 || shellHeight < 26;
|
|
442
|
+
const sidePanelWidth = compact ? clamp(Math.floor(shellWidth * 0.31), 14, 20) : 22;
|
|
443
|
+
const leftPanelWidth = clamp(shellWidth - sidePanelWidth - 2, 20, 64);
|
|
438
444
|
const coreCanvasWidth = clamp(leftPanelWidth - 4, 14, 48);
|
|
439
|
-
const coreCanvasHeight = clamp(Math.floor(
|
|
445
|
+
const coreCanvasHeight = clamp(Math.floor(shellHeight * 0.24), 7, 13);
|
|
440
446
|
const sideCanvasWidth = clamp(sidePanelWidth - 4, 8, 18);
|
|
441
447
|
const spectrumHeight = compact ? 8 : 10;
|
|
442
448
|
const streamChartHeight = compact ? 4 : 6;
|
|
@@ -496,8 +502,8 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
496
502
|
width: moduleProgressWidth,
|
|
497
503
|
variant: "blocks",
|
|
498
504
|
showPercent: false,
|
|
499
|
-
style: { fg:
|
|
500
|
-
trackStyle: { fg:
|
|
505
|
+
style: { fg: rgb(122, 255, 203) },
|
|
506
|
+
trackStyle: { fg: rgb(58, 86, 82) },
|
|
501
507
|
}),
|
|
502
508
|
]);
|
|
503
509
|
});
|
|
@@ -526,7 +532,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
526
532
|
]),
|
|
527
533
|
ui.text(PRODUCT_TAGLINE, {
|
|
528
534
|
key: "core-tagline",
|
|
529
|
-
style: { fg:
|
|
535
|
+
style: { fg: rgb(152, 176, 200) },
|
|
530
536
|
}),
|
|
531
537
|
ui.row({ key: "beacon-lane", gap: 0 }, [
|
|
532
538
|
ui.spacer({ key: "beacon-spacer", size: beaconLane }),
|
|
@@ -546,7 +552,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
546
552
|
[
|
|
547
553
|
ui.text("◆", {
|
|
548
554
|
key: "beacon-dot",
|
|
549
|
-
style: { fg:
|
|
555
|
+
style: { fg: rgb(255, 228, 158) },
|
|
550
556
|
}),
|
|
551
557
|
],
|
|
552
558
|
),
|
|
@@ -585,7 +591,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
585
591
|
width: sparklineWidth,
|
|
586
592
|
highRes: true,
|
|
587
593
|
blitter: "braille",
|
|
588
|
-
style: { fg:
|
|
594
|
+
style: { fg: rgb(132, 246, 198) },
|
|
589
595
|
}),
|
|
590
596
|
]),
|
|
591
597
|
],
|
|
@@ -596,6 +602,8 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
596
602
|
key: "spectrum-radar-panel",
|
|
597
603
|
border: "single",
|
|
598
604
|
width: sidePanelWidth,
|
|
605
|
+
// Helper-first visibility over `expr("if(viewport.w < 70, 0, 1)")`.
|
|
606
|
+
display: visibilityConstraints.viewportWidthAtLeast(70),
|
|
599
607
|
p: 1,
|
|
600
608
|
transition: {
|
|
601
609
|
duration: 300,
|
|
@@ -606,7 +614,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
606
614
|
[
|
|
607
615
|
ui.text("Spectrum and Radar", {
|
|
608
616
|
key: "spectrum-radar-title",
|
|
609
|
-
style: { fg:
|
|
617
|
+
style: { fg: rgb(174, 232, 255) },
|
|
610
618
|
}),
|
|
611
619
|
ui.canvas({
|
|
612
620
|
key: "spectrum-radar-canvas",
|
|
@@ -632,6 +640,8 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
632
640
|
key: "stream-panel",
|
|
633
641
|
border: "single",
|
|
634
642
|
width: sidePanelWidth,
|
|
643
|
+
// Helper-first visibility over `expr("if(viewport.h < 24, 0, 1)")`.
|
|
644
|
+
display: visibilityConstraints.viewportHeightAtLeast(24),
|
|
635
645
|
p: 1,
|
|
636
646
|
transition: {
|
|
637
647
|
duration: 320,
|
|
@@ -642,7 +652,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
642
652
|
[
|
|
643
653
|
ui.text("Telemetry Streams", {
|
|
644
654
|
key: "stream-title",
|
|
645
|
-
style: { fg:
|
|
655
|
+
style: { fg: rgb(154, 198, 255) },
|
|
646
656
|
}),
|
|
647
657
|
ui.lineChart({
|
|
648
658
|
key: "stream-chart",
|
|
@@ -681,6 +691,8 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
681
691
|
{
|
|
682
692
|
key: "modules-panel",
|
|
683
693
|
border: "single",
|
|
694
|
+
// Helper-first visibility over `expr("if(viewport.h < 22, 0, 1)")`.
|
|
695
|
+
display: visibilityConstraints.viewportHeightAtLeast(22),
|
|
684
696
|
p: 1,
|
|
685
697
|
transition: {
|
|
686
698
|
duration: 260,
|
|
@@ -691,7 +703,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
691
703
|
[
|
|
692
704
|
ui.text("Module Sync Rails", {
|
|
693
705
|
key: "modules-title",
|
|
694
|
-
style: { fg:
|
|
706
|
+
style: { fg: rgb(153, 255, 213) },
|
|
695
707
|
}),
|
|
696
708
|
ui.column({ key: "modules-list", gap: 0 }, moduleRows),
|
|
697
709
|
],
|
|
@@ -702,7 +714,7 @@ const CommandDeck = defineWidget<CommandDeckProps>((props, ctx): VNode => {
|
|
|
702
714
|
).padStart(3, "0")}% drift=${drift.toFixed(2)} burst=${String(Math.round(burst * 100)).padStart(3, "0")}%`,
|
|
703
715
|
{
|
|
704
716
|
key: "metrics-readout",
|
|
705
|
-
style: { fg:
|
|
717
|
+
style: { fg: rgb(138, 166, 193) },
|
|
706
718
|
},
|
|
707
719
|
),
|
|
708
720
|
]);
|
|
@@ -718,15 +730,16 @@ export function renderReactorLab(state: AnimationLabState): VNode {
|
|
|
718
730
|
ui.text("•", { key: "brand-dot" }),
|
|
719
731
|
ui.text("Animation Lab", {
|
|
720
732
|
key: "brand-name",
|
|
721
|
-
style: { fg:
|
|
733
|
+
style: { fg: rgb(150, 225, 255) },
|
|
722
734
|
}),
|
|
723
735
|
]),
|
|
724
736
|
ui.box(
|
|
725
737
|
{
|
|
726
738
|
key: "stage-shell",
|
|
727
739
|
border: "double",
|
|
728
|
-
|
|
729
|
-
|
|
740
|
+
// Helper-first viewport clamping over fragile raw `clamp(...)` expression strings.
|
|
741
|
+
width: widthConstraints.clampedViewportMinus({ minus: 4, min: 20, max: 140 }),
|
|
742
|
+
height: heightConstraints.clampedViewportMinus({ minus: 4, min: 8, max: 40 }),
|
|
730
743
|
opacity: state.panelOpacity,
|
|
731
744
|
p: 1,
|
|
732
745
|
transition: {
|
|
@@ -740,8 +753,8 @@ export function renderReactorLab(state: AnimationLabState): VNode {
|
|
|
740
753
|
key: "command-deck",
|
|
741
754
|
tick: state.tick,
|
|
742
755
|
phase: state.phase,
|
|
743
|
-
|
|
744
|
-
|
|
756
|
+
viewportCols: state.viewportCols,
|
|
757
|
+
viewportRows: state.viewportRows,
|
|
745
758
|
driftTarget: state.driftTarget,
|
|
746
759
|
fluxTarget: state.fluxTarget,
|
|
747
760
|
orbitTarget: state.orbitTarget,
|
|
@@ -756,7 +769,7 @@ export function renderReactorLab(state: AnimationLabState): VNode {
|
|
|
756
769
|
)} controls: space/p autoplay, enter step, arrows tune vectors, b burst, m palette, r random, q quit`,
|
|
757
770
|
{
|
|
758
771
|
key: "footer",
|
|
759
|
-
style: { fg:
|
|
772
|
+
style: { fg: rgb(130, 150, 180) },
|
|
760
773
|
},
|
|
761
774
|
),
|
|
762
775
|
]);
|
|
@@ -57,9 +57,9 @@ export function renderLogsScreen(
|
|
|
57
57
|
? {}
|
|
58
58
|
: { levelFilter: Object.freeze(["info", "warn", "error"] as const) }),
|
|
59
59
|
onScroll: (scrollTop) => deps.dispatch({ type: "set-scroll-top", scrollTop }),
|
|
60
|
-
|
|
60
|
+
onChange: (entryId, expanded) =>
|
|
61
61
|
deps.dispatch({ type: "set-entry-expanded", entryId, expanded }),
|
|
62
|
-
|
|
62
|
+
onPress: () => deps.dispatch({ type: "clear-logs" }),
|
|
63
63
|
}),
|
|
64
64
|
]),
|
|
65
65
|
ui.panel({ title: "Recent entries", style: styles.panelStyle }, [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { VNode } from "@rezi-ui/core";
|
|
2
|
-
import { ui, when } from "@rezi-ui/core";
|
|
2
|
+
import { groupConstraints, heightConstraints, ui, when, widthConstraints } from "@rezi-ui/core";
|
|
3
3
|
import {
|
|
4
4
|
filterLabel,
|
|
5
5
|
fleetCounts,
|
|
@@ -34,6 +34,22 @@ export function renderOverviewScreen(state: DashboardState, handlers: DashboardS
|
|
|
34
34
|
const health = statusBadge(overallStatus(state.services));
|
|
35
35
|
const theme = themeSpec(state.themeName);
|
|
36
36
|
|
|
37
|
+
// Helper-first constraints: sibling label equalization via max_sibling(#id.min_w) and intrinsic-aware modals.
|
|
38
|
+
function inspectorRow(label: string, value: string): VNode {
|
|
39
|
+
return ui.row({ key: label, gap: 2, wrap: true, items: "center" }, [
|
|
40
|
+
ui.box(
|
|
41
|
+
{
|
|
42
|
+
id: "inspector-key",
|
|
43
|
+
width: groupConstraints.maxSiblingMinWidth("inspector-key"),
|
|
44
|
+
border: "none",
|
|
45
|
+
p: 0,
|
|
46
|
+
},
|
|
47
|
+
[ui.text(label, { style: styles.mutedStyle })],
|
|
48
|
+
),
|
|
49
|
+
ui.text(value),
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
const serviceRows: readonly VNode[] =
|
|
38
54
|
visible.length === 0
|
|
39
55
|
? [ui.text("No services match the current filter.", { style: styles.mutedStyle })]
|
|
@@ -70,10 +86,10 @@ export function renderOverviewScreen(state: DashboardState, handlers: DashboardS
|
|
|
70
86
|
ui.tag(service.owner, { variant: "default" }),
|
|
71
87
|
ui.tag(service.region, { variant: "info" }),
|
|
72
88
|
]),
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
inspectorRow("Latency", formatLatency(service.latencyMs)),
|
|
90
|
+
inspectorRow("Error Rate", formatErrorRate(service.errorRate)),
|
|
91
|
+
inspectorRow("Traffic", formatTraffic(service.trafficRpm)),
|
|
92
|
+
inspectorRow("Update rate", `${updateRate} Hz`),
|
|
77
93
|
ui.sparkline(service.history, { width: 18, min: 0, max: 220 }),
|
|
78
94
|
]);
|
|
79
95
|
},
|
|
@@ -182,7 +198,9 @@ export function renderOverviewScreen(state: DashboardState, handlers: DashboardS
|
|
|
182
198
|
ui.modal({
|
|
183
199
|
id: "dashboard-help",
|
|
184
200
|
title: `${PRODUCT_NAME} Commands`,
|
|
185
|
-
|
|
201
|
+
// Helper-first intrinsic sizing: this modal adapts to its content but stays within viewport bounds.
|
|
202
|
+
width: widthConstraints.clampedIntrinsicPlus({ pad: 8, min: 44, max: "parent" }),
|
|
203
|
+
height: heightConstraints.clampedIntrinsicPlus({ pad: 4, min: 10, max: "parent" }),
|
|
186
204
|
backdrop: "none",
|
|
187
205
|
returnFocusTo: "help",
|
|
188
206
|
content: ui.column({ gap: 1 }, [
|
|
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import type { RouteRenderContext, RouterApi } from "@rezi-ui/core";
|
|
4
4
|
import { createTestRenderer } from "@rezi-ui/core/testing";
|
|
5
|
-
import { createInitialState } from "../helpers/state.js";
|
|
5
|
+
import { createInitialState, reduceStarshipState } from "../helpers/state.js";
|
|
6
6
|
import { renderBridgeScreen } from "../screens/bridge.js";
|
|
7
7
|
import { renderCargoScreen } from "../screens/cargo.js";
|
|
8
8
|
import { renderCommsScreen } from "../screens/comms.js";
|
|
@@ -70,6 +70,22 @@ test("bridge screen renders core markers", () => {
|
|
|
70
70
|
assert.match(output, /Route Health/);
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
test("bridge screen remains deterministic in compact viewport", () => {
|
|
74
|
+
const state = reduceStarshipState(createInitialState(0), {
|
|
75
|
+
type: "set-viewport",
|
|
76
|
+
cols: 76,
|
|
77
|
+
rows: 30,
|
|
78
|
+
});
|
|
79
|
+
const renderer = createTestRenderer({ viewport: { cols: 76, rows: 30 } });
|
|
80
|
+
const output = renderer
|
|
81
|
+
.render(renderBridgeScreen(createContext(state, "bridge"), createDeps()))
|
|
82
|
+
.toText();
|
|
83
|
+
|
|
84
|
+
assert.match(output, /Bridge Overview/);
|
|
85
|
+
assert.doesNotMatch(output, /Navigation/);
|
|
86
|
+
assert.doesNotMatch(output, /Route Health/);
|
|
87
|
+
});
|
|
88
|
+
|
|
73
89
|
test("engineering screen renders core markers", () => {
|
|
74
90
|
const state = createInitialState(0);
|
|
75
91
|
const renderer = createTestRenderer({ viewport: { cols: 140, rows: 48 } });
|
|
@@ -7,10 +7,6 @@ export type ResponsiveLayout = Readonly<{
|
|
|
7
7
|
stackRightRail: boolean;
|
|
8
8
|
compactSidebar: boolean;
|
|
9
9
|
hideNonCritical: boolean;
|
|
10
|
-
sidebarWidth: number;
|
|
11
|
-
crewMasterWidth: number;
|
|
12
|
-
chartWidth: number;
|
|
13
|
-
canvasWidth: number;
|
|
14
10
|
}>;
|
|
15
11
|
|
|
16
12
|
export type ViewportSnapshot = Readonly<{
|
|
@@ -24,14 +20,21 @@ function clamp(value: number, min: number, max: number): number {
|
|
|
24
20
|
return value;
|
|
25
21
|
}
|
|
26
22
|
|
|
23
|
+
function toPositiveInt(value: number, fallback: number): number {
|
|
24
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
if (value >= 0) return Math.floor(value);
|
|
28
|
+
return Math.ceil(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
27
31
|
export function resolveLayout(viewport: ViewportSnapshot): ResponsiveLayout {
|
|
28
|
-
const width =
|
|
29
|
-
const height =
|
|
32
|
+
const width = clamp(toPositiveInt(viewport.width, 96), 40, 500);
|
|
33
|
+
const height = clamp(toPositiveInt(viewport.height, 32), 18, 200);
|
|
30
34
|
const wide = width >= 120;
|
|
31
35
|
const stackRightRail = width < 120;
|
|
32
36
|
const compactSidebar = width < 90;
|
|
33
37
|
const hideNonCritical = width < 80 || height < 26;
|
|
34
|
-
const sidebarWidth = compactSidebar ? 18 : 34;
|
|
35
38
|
|
|
36
39
|
return Object.freeze({
|
|
37
40
|
width,
|
|
@@ -40,10 +43,6 @@ export function resolveLayout(viewport: ViewportSnapshot): ResponsiveLayout {
|
|
|
40
43
|
stackRightRail,
|
|
41
44
|
compactSidebar,
|
|
42
45
|
hideNonCritical,
|
|
43
|
-
sidebarWidth,
|
|
44
|
-
crewMasterWidth: wide ? 60 : 100,
|
|
45
|
-
chartWidth: clamp(Math.floor(width * (wide ? 0.5 : 0.9)), 28, 132),
|
|
46
|
-
canvasWidth: clamp(Math.floor(width * (wide ? 0.48 : 0.9)), 26, 116),
|
|
47
46
|
});
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -195,7 +195,7 @@ async function stopApp(code = 0): Promise<void> {
|
|
|
195
195
|
} catch {
|
|
196
196
|
// Ignore stop races.
|
|
197
197
|
}
|
|
198
|
-
frameAuditGlobal.__reziFrameAuditContext =
|
|
198
|
+
frameAuditGlobal.__reziFrameAuditContext = () => Object.freeze({});
|
|
199
199
|
stopResolve?.();
|
|
200
200
|
stopResolve = null;
|
|
201
201
|
}
|
|
@@ -131,8 +131,8 @@ const BridgeCommandDeck = defineWidget<BridgeCommandDeckProps>((props, ctx): VNo
|
|
|
131
131
|
|
|
132
132
|
const selected = selectedCrew(props.state);
|
|
133
133
|
const subsystemNames = props.state.subsystems.map((item) => item.name);
|
|
134
|
-
const chartWidth = layout.
|
|
135
|
-
const schematicWidth = layout.
|
|
134
|
+
const chartWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.5 : 0.9)), 28, 132);
|
|
135
|
+
const schematicWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.48 : 0.9)), 26, 116);
|
|
136
136
|
const schematicHeight = clamp(Math.floor(contentRows * 0.38), 8, 14);
|
|
137
137
|
const showGaugeRow = contentRows >= 28;
|
|
138
138
|
const showSparkline = contentRows >= 24;
|
|
@@ -326,7 +326,7 @@ const BridgeCommandDeck = defineWidget<BridgeCommandDeckProps>((props, ctx): VNo
|
|
|
326
326
|
showSystemsPanel: contentRows >= 60,
|
|
327
327
|
});
|
|
328
328
|
const commandRegion = showCommandSummary
|
|
329
|
-
? ui.row({ gap: SPACE.sm, items: "stretch", width: "
|
|
329
|
+
? ui.row({ gap: SPACE.sm, items: "stretch", width: "full" }, [
|
|
330
330
|
ui.box({ border: "none", p: 0, flex: 2 }, [commandDeck]),
|
|
331
331
|
ui.box({ border: "none", p: 0, flex: 1 }, [commandSummary]),
|
|
332
332
|
])
|
|
@@ -498,18 +498,18 @@ const BridgeCommandDeck = defineWidget<BridgeCommandDeckProps>((props, ctx): VNo
|
|
|
498
498
|
|
|
499
499
|
const showSchematicRail = layout.wide && !layout.hideNonCritical && contentRows >= 28;
|
|
500
500
|
const telemetryRegion = showSchematicRail
|
|
501
|
-
? ui.row({ gap: SPACE.sm, items: "stretch", wrap: false, width: "
|
|
501
|
+
? ui.row({ gap: SPACE.sm, items: "stretch", wrap: false, width: "full" }, [
|
|
502
502
|
ui.box({ border: "none", p: 0, flex: 2 }, [telemetryPanel]),
|
|
503
503
|
ui.box({ border: "none", p: 0, flex: 1 }, [schematicPanel]),
|
|
504
504
|
])
|
|
505
505
|
: telemetryPanel;
|
|
506
506
|
|
|
507
507
|
if (veryCompactHeight) {
|
|
508
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
508
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [commandDeck]);
|
|
509
509
|
}
|
|
510
510
|
|
|
511
511
|
if (compactHeight) {
|
|
512
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
512
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
513
513
|
commandDeck,
|
|
514
514
|
surfacePanel(
|
|
515
515
|
tokens,
|
|
@@ -532,10 +532,10 @@ const BridgeCommandDeck = defineWidget<BridgeCommandDeckProps>((props, ctx): VNo
|
|
|
532
532
|
}
|
|
533
533
|
|
|
534
534
|
if (constrainedHeight) {
|
|
535
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
535
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [commandDeck, telemetryPanel]);
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
538
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
539
539
|
commandRegion,
|
|
540
540
|
telemetryRegion,
|
|
541
541
|
...(contentRows >= 60 ? [systemsPanel] : []),
|
|
@@ -554,7 +554,7 @@ export function renderBridgeScreen(
|
|
|
554
554
|
title: "Bridge Overview",
|
|
555
555
|
context,
|
|
556
556
|
deps,
|
|
557
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
557
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
558
558
|
BridgeCommandDeck({ key: "bridge-command-deck", state, dispatch: deps.dispatch }),
|
|
559
559
|
]),
|
|
560
560
|
});
|
|
@@ -22,6 +22,12 @@ function categoryLabel(category: CargoItem["category"]): string {
|
|
|
22
22
|
return "Ordnance";
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
function clamp(value: number, min: number, max: number): number {
|
|
26
|
+
if (value < min) return min;
|
|
27
|
+
if (value > max) return max;
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
25
31
|
export function renderCargoScreen(
|
|
26
32
|
context: RouteRenderContext<StarshipState>,
|
|
27
33
|
deps: RouteDeps,
|
|
@@ -30,7 +36,7 @@ export function renderCargoScreen(
|
|
|
30
36
|
title: "Cargo Hold",
|
|
31
37
|
context,
|
|
32
38
|
deps,
|
|
33
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
39
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
34
40
|
CargoDeck({
|
|
35
41
|
key: "cargo-deck",
|
|
36
42
|
state: context.state,
|
|
@@ -53,6 +59,7 @@ const CargoDeck = defineWidget<CargoDeckProps>((props, ctx): VNode => {
|
|
|
53
59
|
width: state.viewportCols,
|
|
54
60
|
height: state.viewportRows,
|
|
55
61
|
});
|
|
62
|
+
const chartWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.5 : 0.9)), 28, 132);
|
|
56
63
|
const cargo = sortedCargo(state);
|
|
57
64
|
const summary = cargoSummary(cargo);
|
|
58
65
|
const totalItems = cargo.length;
|
|
@@ -62,7 +69,7 @@ const CargoDeck = defineWidget<CargoDeckProps>((props, ctx): VNode => {
|
|
|
62
69
|
: cargo.reduce((sum, item) => sum + item.priority, 0) / Math.max(1, cargo.length);
|
|
63
70
|
const selected =
|
|
64
71
|
(state.selectedCargoId && cargo.find((item) => item.id === state.selectedCargoId)) || cargo[0] || null;
|
|
65
|
-
const nameWidth = Math.max(12, Math.min(24,
|
|
72
|
+
const nameWidth = Math.max(12, Math.min(24, chartWidth - 14));
|
|
66
73
|
const showMetricsPanel = !layout.hideNonCritical && layout.height >= 40;
|
|
67
74
|
const showSelectedPanel = !layout.hideNonCritical && layout.height >= 48;
|
|
68
75
|
|
|
@@ -99,7 +106,7 @@ const CargoDeck = defineWidget<CargoDeckProps>((props, ctx): VNode => {
|
|
|
99
106
|
});
|
|
100
107
|
|
|
101
108
|
if (layout.height <= 28) {
|
|
102
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
109
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
103
110
|
surfacePanel(tokens, "Cargo Snapshot", [
|
|
104
111
|
sectionHeader(tokens, "Compact Cargo View", "Expand terminal height for full manifest + charts"),
|
|
105
112
|
ui.row({ gap: SPACE.xs, wrap: true }, [
|
|
@@ -188,7 +195,7 @@ const CargoDeck = defineWidget<CargoDeckProps>((props, ctx): VNode => {
|
|
|
188
195
|
ui.barChart(chartItems, { orientation: "horizontal", showValues: true }),
|
|
189
196
|
ui.scatter({
|
|
190
197
|
id: "cargo-scatter",
|
|
191
|
-
width: Math.max(32,
|
|
198
|
+
width: Math.max(32, chartWidth + 6),
|
|
192
199
|
height: 10,
|
|
193
200
|
points: scatterPoints,
|
|
194
201
|
blitter: "braille",
|
|
@@ -308,7 +315,7 @@ const CargoDeck = defineWidget<CargoDeckProps>((props, ctx): VNode => {
|
|
|
308
315
|
{ tone: "base" },
|
|
309
316
|
);
|
|
310
317
|
|
|
311
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
318
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
312
319
|
controlsPanel,
|
|
313
320
|
showMetricsPanel
|
|
314
321
|
? ui.row({ gap: SPACE.sm, wrap: true, items: "stretch" }, [
|
|
@@ -73,7 +73,7 @@ const CommsDeck = defineWidget<CommsDeckProps>((props, ctx): VNode => {
|
|
|
73
73
|
};
|
|
74
74
|
|
|
75
75
|
if (compactHeight) {
|
|
76
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
76
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
77
77
|
surfacePanel(tokens, "Channel Controls", [
|
|
78
78
|
sectionHeader(tokens, "Compact Comms View", "Expand terminal height for full traffic console"),
|
|
79
79
|
ui.tabs({
|
|
@@ -165,7 +165,7 @@ const CommsDeck = defineWidget<CommsDeckProps>((props, ctx): VNode => {
|
|
|
165
165
|
focusedStyle: { fg: tokens.text.primary, bg: tokens.bg.panel.elevated, bold: true },
|
|
166
166
|
onScroll: (scrollTop) => props.dispatch({ type: "set-comms-scroll", scrollTop }),
|
|
167
167
|
expandedEntries: state.expandedMessageIds,
|
|
168
|
-
|
|
168
|
+
onChange: (entryId, expanded) =>
|
|
169
169
|
props.dispatch({
|
|
170
170
|
type: "toggle-message-expanded",
|
|
171
171
|
messageId: entryId,
|
|
@@ -259,7 +259,7 @@ const CommsDeck = defineWidget<CommsDeckProps>((props, ctx): VNode => {
|
|
|
259
259
|
}) ?? ui.text(""),
|
|
260
260
|
]);
|
|
261
261
|
|
|
262
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
262
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
263
263
|
show(
|
|
264
264
|
state.activeChannel === "emergency",
|
|
265
265
|
ui.callout("Emergency channel monitored with elevated priority.", {
|
|
@@ -462,7 +462,7 @@ export function renderCommsScreen(
|
|
|
462
462
|
title: "Communications",
|
|
463
463
|
context,
|
|
464
464
|
deps,
|
|
465
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
465
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
466
466
|
CommsDeck({
|
|
467
467
|
key: "comms-deck",
|
|
468
468
|
state: context.state,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
conditionalConstraints,
|
|
2
3
|
defineWidget,
|
|
4
|
+
heightConstraints,
|
|
3
5
|
maybe,
|
|
4
6
|
show,
|
|
5
7
|
ui,
|
|
6
8
|
useAsync,
|
|
9
|
+
visibilityConstraints,
|
|
7
10
|
type RouteRenderContext,
|
|
8
11
|
type VNode,
|
|
9
12
|
} from "@rezi-ui/core";
|
|
@@ -36,6 +39,12 @@ function validateCriticalDepartments(crew: readonly CrewMember[]): string | null
|
|
|
36
39
|
return null;
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
function clamp(value: number, min: number, max: number): number {
|
|
43
|
+
if (value < min) return min;
|
|
44
|
+
if (value > max) return max;
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
type CrewDeckProps = Readonly<{
|
|
40
49
|
key?: string;
|
|
41
50
|
state: StarshipState;
|
|
@@ -48,6 +57,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
48
57
|
width: props.state.viewportCols,
|
|
49
58
|
height: props.state.viewportRows,
|
|
50
59
|
});
|
|
60
|
+
const chartWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.5 : 0.9)), 28, 132);
|
|
51
61
|
const compactHeight = layout.height < 34;
|
|
52
62
|
const showDetailPane = layout.height >= 38;
|
|
53
63
|
const visible = visibleCrew(props.state);
|
|
@@ -194,7 +204,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
194
204
|
...tableSkin(tokens),
|
|
195
205
|
});
|
|
196
206
|
|
|
197
|
-
const detailPanel = ui.column({ gap: SPACE.sm, width: "
|
|
207
|
+
const detailPanel = ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
198
208
|
maybe(selected, (member) =>
|
|
199
209
|
surfacePanel(
|
|
200
210
|
tokens,
|
|
@@ -208,7 +218,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
208
218
|
]),
|
|
209
219
|
progressRow(tokens, "Efficiency", member.efficiency / 100, {
|
|
210
220
|
labelWidth: 12,
|
|
211
|
-
width: Math.max(18,
|
|
221
|
+
width: Math.max(18, chartWidth - 10),
|
|
212
222
|
tone: member.efficiency < 45 ? "warning" : "success",
|
|
213
223
|
trend: member.efficiency >= 50 ? 1 : -1,
|
|
214
224
|
}),
|
|
@@ -301,12 +311,12 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
301
311
|
),
|
|
302
312
|
]);
|
|
303
313
|
|
|
304
|
-
const manifestBlock = ui.column({ gap: SPACE.sm, width: "
|
|
314
|
+
const manifestBlock = ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
305
315
|
ui.box(
|
|
306
316
|
{
|
|
307
317
|
border: "none",
|
|
308
318
|
p: 0,
|
|
309
|
-
width: "
|
|
319
|
+
width: "full",
|
|
310
320
|
flex: 1,
|
|
311
321
|
minHeight: 10,
|
|
312
322
|
overflow: "hidden",
|
|
@@ -328,8 +338,8 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
328
338
|
{
|
|
329
339
|
id: ctx.id("crew-master-detail"),
|
|
330
340
|
gap: SPACE.sm,
|
|
331
|
-
width: "
|
|
332
|
-
height: "
|
|
341
|
+
width: "full",
|
|
342
|
+
height: "full",
|
|
333
343
|
items: "stretch",
|
|
334
344
|
wrap: false,
|
|
335
345
|
},
|
|
@@ -338,8 +348,13 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
338
348
|
{
|
|
339
349
|
border: "none",
|
|
340
350
|
p: 0,
|
|
341
|
-
|
|
342
|
-
|
|
351
|
+
// Helper-first responsive sizing: wide terminals get a stable master width, narrow gets fallback width.
|
|
352
|
+
width: conditionalConstraints.ifThenElse(
|
|
353
|
+
visibilityConstraints.viewportWidthAtLeast(120),
|
|
354
|
+
60,
|
|
355
|
+
100,
|
|
356
|
+
),
|
|
357
|
+
height: "full",
|
|
343
358
|
overflow: "hidden",
|
|
344
359
|
},
|
|
345
360
|
[manifestBlock],
|
|
@@ -349,7 +364,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
349
364
|
border: "none",
|
|
350
365
|
p: 0,
|
|
351
366
|
flex: 1,
|
|
352
|
-
height: "
|
|
367
|
+
height: "full",
|
|
353
368
|
overflow: "hidden",
|
|
354
369
|
},
|
|
355
370
|
[detailPanel],
|
|
@@ -358,19 +373,24 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
358
373
|
)
|
|
359
374
|
: manifestBlock
|
|
360
375
|
: showDetailPane
|
|
361
|
-
? ui.column({ gap: SPACE.sm, width: "
|
|
376
|
+
? ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
362
377
|
ui.box(
|
|
363
|
-
{ border: "none", p: 0, width: "
|
|
378
|
+
{ border: "none", p: 0, width: "full", flex: 1, minHeight: 10, overflow: "hidden" },
|
|
364
379
|
[manifestBlock],
|
|
365
380
|
),
|
|
366
381
|
ui.box(
|
|
367
|
-
{ border: "none", p: 0, width: "
|
|
382
|
+
{ border: "none", p: 0, width: "full", flex: 1, minHeight: 10, overflow: "hidden" },
|
|
368
383
|
[detailPanel],
|
|
369
384
|
),
|
|
370
385
|
])
|
|
371
386
|
: manifestBlock;
|
|
372
387
|
|
|
373
|
-
|
|
388
|
+
// Helper-first viewport-derived sizing (clamp 12..22 at 34% of viewport height).
|
|
389
|
+
const operationsPanelHeightExpr = heightConstraints.clampedPercentOfViewport({
|
|
390
|
+
ratio: 0.34,
|
|
391
|
+
min: 12,
|
|
392
|
+
max: 22,
|
|
393
|
+
});
|
|
374
394
|
debugSnapshot("crew.render", {
|
|
375
395
|
viewportCols: props.state.viewportCols,
|
|
376
396
|
viewportRows: props.state.viewportRows,
|
|
@@ -380,15 +400,15 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
380
400
|
totalPages,
|
|
381
401
|
pageDataCount: pageData.length,
|
|
382
402
|
showDetailPane,
|
|
383
|
-
|
|
403
|
+
operationsPanelHeight: operationsPanelHeightExpr.source,
|
|
384
404
|
editingCrew: props.state.editingCrew,
|
|
385
405
|
});
|
|
386
406
|
const operationsPanel = ui.box(
|
|
387
407
|
{
|
|
388
408
|
border: "none",
|
|
389
409
|
p: 0,
|
|
390
|
-
width: "
|
|
391
|
-
height:
|
|
410
|
+
width: "full",
|
|
411
|
+
height: operationsPanelHeightExpr,
|
|
392
412
|
overflow: "scroll",
|
|
393
413
|
},
|
|
394
414
|
[
|
|
@@ -502,7 +522,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
502
522
|
],
|
|
503
523
|
);
|
|
504
524
|
|
|
505
|
-
return ui.column({ gap: SPACE.md, width: "
|
|
525
|
+
return ui.column({ gap: SPACE.md, width: "full", height: "full" }, [
|
|
506
526
|
operationsPanel,
|
|
507
527
|
show(
|
|
508
528
|
asyncCrew.loading,
|
|
@@ -519,7 +539,7 @@ const CrewDeck = defineWidget<CrewDeckProps>((props, ctx): VNode => {
|
|
|
519
539
|
{
|
|
520
540
|
border: "none",
|
|
521
541
|
p: 0,
|
|
522
|
-
width: "
|
|
542
|
+
width: "full",
|
|
523
543
|
flex: 1,
|
|
524
544
|
minHeight: 12,
|
|
525
545
|
overflow: "hidden",
|
|
@@ -535,7 +555,7 @@ export function renderCrewScreen(context: RouteRenderContext<StarshipState>, dep
|
|
|
535
555
|
title: "Crew Manifest",
|
|
536
556
|
context,
|
|
537
557
|
deps,
|
|
538
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
558
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
539
559
|
CrewDeck({
|
|
540
560
|
key: "crew-deck",
|
|
541
561
|
state: context.state,
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
useSequence,
|
|
6
6
|
useSpring,
|
|
7
7
|
useStagger,
|
|
8
|
+
widthConstraints,
|
|
8
9
|
type CanvasContext,
|
|
9
10
|
type NodeState,
|
|
10
11
|
type RouteRenderContext,
|
|
@@ -28,6 +29,12 @@ function buildSubsystemChildren(subsystems: readonly Subsystem[]): Map<string |
|
|
|
28
29
|
return map;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
function clamp(value: number, min: number, max: number): number {
|
|
33
|
+
if (value < min) return min;
|
|
34
|
+
if (value > max) return max;
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
31
38
|
type ReactorPalette = Readonly<{
|
|
32
39
|
background: string;
|
|
33
40
|
border: string;
|
|
@@ -105,6 +112,8 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
105
112
|
const leftPanePanelCount = 1 + (showSecondaryPanels ? 1 : 0);
|
|
106
113
|
const rightPanePanelCount = 1 + (showSecondaryPanels ? 2 : 0);
|
|
107
114
|
const reactorCanvasHeight = Math.max(8, Math.min(14, Math.floor(contentRows * 0.42)));
|
|
115
|
+
const chartWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.5 : 0.9)), 28, 132);
|
|
116
|
+
const canvasWidth = clamp(Math.floor(layout.width * (layout.wide ? 0.48 : 0.9)), 26, 116);
|
|
108
117
|
debugSnapshot("engineering.layout", {
|
|
109
118
|
viewportCols: props.state.viewportCols,
|
|
110
119
|
viewportRows: props.state.viewportRows,
|
|
@@ -214,7 +223,7 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
214
223
|
: surfacePanel(tokens, "Reactor Schematic", [
|
|
215
224
|
ui.canvas({
|
|
216
225
|
id: ctx.id("reactor-canvas"),
|
|
217
|
-
width: Math.max(32,
|
|
226
|
+
width: Math.max(32, canvasWidth),
|
|
218
227
|
height: reactorCanvasHeight,
|
|
219
228
|
blitter: "braille",
|
|
220
229
|
draw: (canvas) => {
|
|
@@ -272,7 +281,7 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
272
281
|
...(selectedSubsystemId ? { selected: selectedSubsystemId } : {}),
|
|
273
282
|
showLines: true,
|
|
274
283
|
indentSize: 2,
|
|
275
|
-
|
|
284
|
+
onChange: (node) => props.dispatch({ type: "toggle-subsystem", subsystemId: node.id }),
|
|
276
285
|
onSelect: (node) => setSelectedSubsystemId(node.id),
|
|
277
286
|
renderNode: (node, depth, state: NodeState) =>
|
|
278
287
|
ui.row({ gap: SPACE.xs, wrap: false }, [
|
|
@@ -310,7 +319,7 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
310
319
|
ui.column({ key: subsystem.id, gap: SPACE.xs }, [
|
|
311
320
|
progressRow(tokens, subsystem.name, subsystem.power / 100, {
|
|
312
321
|
labelWidth: 18,
|
|
313
|
-
width: Math.max(22,
|
|
322
|
+
width: Math.max(22, chartWidth - 8),
|
|
314
323
|
tone: subsystem.health < props.state.alertThreshold ? "warning" : "default",
|
|
315
324
|
trend: subsystem.health < props.state.alertThreshold ? -1 : 1,
|
|
316
325
|
}),
|
|
@@ -318,7 +327,7 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
318
327
|
? [
|
|
319
328
|
progressRow(tokens, "Boot", stagger[index] ?? 0, {
|
|
320
329
|
labelWidth: 18,
|
|
321
|
-
width: Math.max(22,
|
|
330
|
+
width: Math.max(22, chartWidth - 8),
|
|
322
331
|
tone: "success",
|
|
323
332
|
trend: 1,
|
|
324
333
|
}),
|
|
@@ -332,7 +341,7 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
332
341
|
const thermalPanel = surfacePanel(tokens, "Thermal Map", [
|
|
333
342
|
ui.heatmap({
|
|
334
343
|
id: ctx.id("engineering-heatmap"),
|
|
335
|
-
width: Math.max(30,
|
|
344
|
+
width: Math.max(30, chartWidth),
|
|
336
345
|
height: 10,
|
|
337
346
|
data: heatmapData,
|
|
338
347
|
colorScale: "inferno",
|
|
@@ -372,40 +381,40 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
372
381
|
]);
|
|
373
382
|
|
|
374
383
|
const leftPane = showSecondaryPanels
|
|
375
|
-
? ui.column({ gap: SPACE.sm, width: "
|
|
376
|
-
ui.box({ border: "none", p: 0, width: "
|
|
377
|
-
ui.box({ border: "none", p: 0, width: "
|
|
384
|
+
? ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
385
|
+
ui.box({ border: "none", p: 0, width: "full", flex: 3, minHeight: 12 }, [reactorPanel]),
|
|
386
|
+
ui.box({ border: "none", p: 0, width: "full", flex: 2, minHeight: 10, overflow: "hidden" }, [
|
|
378
387
|
treePanel,
|
|
379
388
|
]),
|
|
380
389
|
])
|
|
381
|
-
: ui.column({ gap: SPACE.sm, width: "
|
|
390
|
+
: ui.column({ gap: SPACE.sm, width: "full" }, [reactorPanel]);
|
|
382
391
|
const rightPane = showSecondaryPanels
|
|
383
|
-
? ui.column({ gap: SPACE.sm, width: "
|
|
384
|
-
ui.box({ border: "none", p: 0, width: "
|
|
392
|
+
? ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
393
|
+
ui.box({ border: "none", p: 0, width: "full", flex: 3, minHeight: 12, overflow: "hidden" }, [
|
|
385
394
|
powerPanel,
|
|
386
395
|
]),
|
|
387
|
-
ui.box({ border: "none", p: 0, width: "
|
|
388
|
-
ui.box({ border: "none", p: 0, width: "
|
|
396
|
+
ui.box({ border: "none", p: 0, width: "full", flex: 2, minHeight: 10 }, [thermalPanel]),
|
|
397
|
+
ui.box({ border: "none", p: 0, width: "full", flex: 2, minHeight: 10, overflow: "hidden" }, [
|
|
389
398
|
diagnosticsPanel,
|
|
390
399
|
]),
|
|
391
400
|
])
|
|
392
|
-
: ui.column({ gap: SPACE.sm, width: "
|
|
401
|
+
: ui.column({ gap: SPACE.sm, width: "full" }, [powerPanel]);
|
|
393
402
|
|
|
394
403
|
const responsiveDeckMinHeight = Math.max(
|
|
395
404
|
16,
|
|
396
405
|
contentRows - (showControlsSummary ? 12 : 10) - (showSecondaryPanels ? 0 : 2),
|
|
397
406
|
);
|
|
398
407
|
const responsiveDeckBody = useWideRow
|
|
399
|
-
? ui.row({ gap: SPACE.sm, items: "stretch", width: "
|
|
408
|
+
? ui.row({ gap: SPACE.sm, items: "stretch", width: "full" }, [
|
|
400
409
|
ui.box({ border: "none", p: 0, flex: 2 }, [leftPane]),
|
|
401
410
|
ui.box({ border: "none", p: 0, flex: 3 }, [rightPane]),
|
|
402
411
|
])
|
|
403
|
-
: ui.column({ gap: SPACE.sm, width: "
|
|
412
|
+
: ui.column({ gap: SPACE.sm, width: "full" }, [leftPane, rightPane]);
|
|
404
413
|
const responsiveDeck = ui.box(
|
|
405
414
|
{
|
|
406
415
|
border: "none",
|
|
407
416
|
p: 0,
|
|
408
|
-
width: "
|
|
417
|
+
width: "full",
|
|
409
418
|
flex: 1,
|
|
410
419
|
minHeight: responsiveDeckMinHeight,
|
|
411
420
|
overflow: "scroll",
|
|
@@ -475,12 +484,13 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
475
484
|
);
|
|
476
485
|
|
|
477
486
|
const controlsRegion = showControlsSummary
|
|
478
|
-
? ui.row({ gap: SPACE.sm, items: "start", width: "
|
|
487
|
+
? ui.row({ gap: SPACE.sm, items: "start", width: "full", wrap: false }, [
|
|
479
488
|
ui.box(
|
|
480
489
|
{
|
|
481
490
|
border: "none",
|
|
482
491
|
p: 0,
|
|
483
|
-
|
|
492
|
+
// Helper-first: replaces raw `expr("max(56, viewport.w * 0.62)")`.
|
|
493
|
+
width: widthConstraints.minViewportPercent({ ratio: 0.62, min: 56 }),
|
|
484
494
|
},
|
|
485
495
|
[controlsPanel],
|
|
486
496
|
),
|
|
@@ -488,7 +498,8 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
488
498
|
{
|
|
489
499
|
border: "none",
|
|
490
500
|
p: 0,
|
|
491
|
-
|
|
501
|
+
// Helper-first: replaces raw `expr("max(34, viewport.w * 0.34)")`.
|
|
502
|
+
width: widthConstraints.minViewportPercent({ ratio: 0.34, min: 34 }),
|
|
492
503
|
},
|
|
493
504
|
[controlsSummary],
|
|
494
505
|
),
|
|
@@ -507,14 +518,14 @@ const EngineeringDeck = defineWidget<EngineeringDeckProps>((props, ctx): VNode =
|
|
|
507
518
|
});
|
|
508
519
|
|
|
509
520
|
if (veryCompactHeight) {
|
|
510
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
521
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [controlsPanel]);
|
|
511
522
|
}
|
|
512
523
|
|
|
513
524
|
if (compactHeight) {
|
|
514
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
525
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [controlsPanel, reactorPanel]);
|
|
515
526
|
}
|
|
516
527
|
|
|
517
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
528
|
+
return ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
518
529
|
controlsRegion,
|
|
519
530
|
responsiveDeck,
|
|
520
531
|
]);
|
|
@@ -528,7 +539,7 @@ export function renderEngineeringScreen(
|
|
|
528
539
|
title: "Engineering Deck",
|
|
529
540
|
context,
|
|
530
541
|
deps,
|
|
531
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
542
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
532
543
|
EngineeringDeck({
|
|
533
544
|
key: "engineering-deck",
|
|
534
545
|
state: context.state,
|
|
@@ -66,7 +66,7 @@ export function surfacePanel(
|
|
|
66
66
|
border: "rounded",
|
|
67
67
|
p: options?.p ?? SPACE.sm,
|
|
68
68
|
gap: options?.gap ?? SPACE.sm,
|
|
69
|
-
...(fill ? { width: "
|
|
69
|
+
...(fill ? { width: "full" } : {}),
|
|
70
70
|
style: { bg: colors.background, fg: tokens.text.primary },
|
|
71
71
|
borderStyle: { fg: colors.border, bg: colors.background },
|
|
72
72
|
inheritStyle: { fg: tokens.text.primary },
|
|
@@ -159,7 +159,7 @@ const SettingsDeck = defineWidget<SettingsDeckProps>((props, ctx): VNode => {
|
|
|
159
159
|
], { tone: "base" });
|
|
160
160
|
|
|
161
161
|
return ui.layers([
|
|
162
|
-
ui.column({ gap: SPACE.md, width: "
|
|
162
|
+
ui.column({ gap: SPACE.md, width: "full" }, [
|
|
163
163
|
settingsForm,
|
|
164
164
|
layout.hideNonCritical
|
|
165
165
|
? surfacePanel(tokens, "Theme Snapshot", [
|
|
@@ -209,7 +209,7 @@ function settingsRightRail(state: StarshipState, deps: RouteDeps): VNode {
|
|
|
209
209
|
subtitle: activeTheme.label,
|
|
210
210
|
actions: [ui.badge("Preview", { variant: "info" })],
|
|
211
211
|
}),
|
|
212
|
-
body: ui.column({ gap: SPACE.xs, width: "
|
|
212
|
+
body: ui.column({ gap: SPACE.xs, width: "full", height: "full" }, [
|
|
213
213
|
ui.breadcrumb({
|
|
214
214
|
items: [{ label: "Bridge" }, { label: "Settings" }, { label: "Theme Preview" }],
|
|
215
215
|
}),
|
|
@@ -228,7 +228,7 @@ function settingsRightRail(state: StarshipState, deps: RouteDeps): VNode {
|
|
|
228
228
|
}),
|
|
229
229
|
});
|
|
230
230
|
|
|
231
|
-
return ui.column({ gap: SPACE.sm, width: "
|
|
231
|
+
return ui.column({ gap: SPACE.sm, width: "full" }, [
|
|
232
232
|
surfacePanel(tokens, "Theme Preview", [
|
|
233
233
|
sectionHeader(tokens, "Theme Modes", "Changes apply instantly across the console"),
|
|
234
234
|
ui.grid(
|
|
@@ -277,7 +277,7 @@ export function renderSettingsScreen(
|
|
|
277
277
|
title: "Ship Settings",
|
|
278
278
|
context,
|
|
279
279
|
deps,
|
|
280
|
-
body: ui.column({ gap: SPACE.sm, width: "
|
|
280
|
+
body: ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [
|
|
281
281
|
SettingsDeck({
|
|
282
282
|
key: "settings-deck",
|
|
283
283
|
state: context.state,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ui,
|
|
3
|
+
visibilityConstraints,
|
|
3
4
|
type CommandItem,
|
|
4
5
|
type CommandSource,
|
|
5
6
|
type RegisteredBinding,
|
|
@@ -149,12 +150,15 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
149
150
|
});
|
|
150
151
|
const compactHeight = layout.height <= 34;
|
|
151
152
|
const minimalHeight = layout.height <= 30;
|
|
152
|
-
const showBreadcrumbStrip = !compactHeight;
|
|
153
|
-
const showTabsStrip = !minimalHeight;
|
|
154
153
|
const showSidebar = !minimalHeight && layout.width >= 78;
|
|
155
154
|
const showRouteHealth = !compactHeight && showSidebar;
|
|
156
155
|
const showToastOverlay = !compactHeight && state.toasts.length > 0;
|
|
157
|
-
const showRightRail = Boolean(
|
|
156
|
+
const showRightRail = Boolean(
|
|
157
|
+
options.rightRail && !layout.hideNonCritical && layout.height >= 40 && layout.width >= 80,
|
|
158
|
+
);
|
|
159
|
+
const sidebarWidth = layout.compactSidebar ? 18 : 34;
|
|
160
|
+
// Helper-first constraints (readable intent) over raw `expr("if(viewport.w < ...)")` strings.
|
|
161
|
+
const rightRailDisplay = visibilityConstraints.viewportAtLeast({ width: 80, height: 40 });
|
|
158
162
|
debugSnapshot("shell.layout", {
|
|
159
163
|
route: options.context.router.currentRoute().id,
|
|
160
164
|
viewportCols: state.viewportCols,
|
|
@@ -298,7 +302,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
298
302
|
{
|
|
299
303
|
border: "none",
|
|
300
304
|
p: SPACE.xs,
|
|
301
|
-
width: "
|
|
305
|
+
width: "full",
|
|
302
306
|
style: { bg: tokens.bg.panel.inset, fg: tokens.text.primary },
|
|
303
307
|
inheritStyle: { fg: tokens.text.primary },
|
|
304
308
|
},
|
|
@@ -337,7 +341,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
337
341
|
{
|
|
338
342
|
border: "none",
|
|
339
343
|
p: SPACE.xs,
|
|
340
|
-
width: "
|
|
344
|
+
width: "full",
|
|
341
345
|
style: { bg: tokens.bg.panel.inset, fg: tokens.text.primary },
|
|
342
346
|
inheritStyle: { fg: tokens.text.primary },
|
|
343
347
|
},
|
|
@@ -394,17 +398,33 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
394
398
|
const bodyMain = ui.column(
|
|
395
399
|
{
|
|
396
400
|
gap: SPACE.sm,
|
|
397
|
-
width: "
|
|
398
|
-
height: "
|
|
401
|
+
width: "full",
|
|
402
|
+
height: "full",
|
|
399
403
|
},
|
|
400
404
|
[
|
|
401
|
-
...(showBreadcrumbStrip ? [breadcrumbStrip] : []),
|
|
402
|
-
...(showTabsStrip ? [tabsStrip] : []),
|
|
403
405
|
ui.box(
|
|
404
406
|
{
|
|
405
407
|
border: "none",
|
|
406
408
|
p: 0,
|
|
407
|
-
width: "
|
|
409
|
+
width: "full",
|
|
410
|
+
display: visibilityConstraints.viewportHeightAtLeast(35),
|
|
411
|
+
},
|
|
412
|
+
[breadcrumbStrip],
|
|
413
|
+
),
|
|
414
|
+
ui.box(
|
|
415
|
+
{
|
|
416
|
+
border: "none",
|
|
417
|
+
p: 0,
|
|
418
|
+
width: "full",
|
|
419
|
+
display: visibilityConstraints.viewportHeightAtLeast(31),
|
|
420
|
+
},
|
|
421
|
+
[tabsStrip],
|
|
422
|
+
),
|
|
423
|
+
ui.box(
|
|
424
|
+
{
|
|
425
|
+
border: "none",
|
|
426
|
+
p: 0,
|
|
427
|
+
width: "full",
|
|
408
428
|
flex: 1,
|
|
409
429
|
style: { bg: tokens.bg.app, fg: tokens.text.primary },
|
|
410
430
|
inheritStyle: { fg: tokens.text.primary },
|
|
@@ -414,12 +434,22 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
414
434
|
],
|
|
415
435
|
);
|
|
416
436
|
|
|
417
|
-
const rightRailNode = showRightRail
|
|
437
|
+
const rightRailNode = showRightRail
|
|
438
|
+
? ui.box(
|
|
439
|
+
{
|
|
440
|
+
border: "none",
|
|
441
|
+
p: 0,
|
|
442
|
+
width: "full",
|
|
443
|
+
display: rightRailDisplay,
|
|
444
|
+
},
|
|
445
|
+
[options.rightRail],
|
|
446
|
+
)
|
|
447
|
+
: null;
|
|
418
448
|
const bodyWithRail =
|
|
419
449
|
rightRailNode
|
|
420
450
|
? layout.stackRightRail
|
|
421
|
-
? ui.column({ gap: SPACE.sm, width: "
|
|
422
|
-
: ui.row({ gap: SPACE.sm, items: "stretch", width: "
|
|
451
|
+
? ui.column({ gap: SPACE.sm, width: "full", height: "full" }, [bodyMain, rightRailNode])
|
|
452
|
+
: ui.row({ gap: SPACE.sm, items: "stretch", width: "full", height: "full" }, [
|
|
423
453
|
ui.box({ flex: 2, border: "none", p: 0 }, [bodyMain]),
|
|
424
454
|
ui.box({ flex: 1, border: "none", p: 0 }, [rightRailNode]),
|
|
425
455
|
])
|
|
@@ -456,7 +486,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
456
486
|
{
|
|
457
487
|
border: "none",
|
|
458
488
|
p: 0,
|
|
459
|
-
width: "
|
|
489
|
+
width: "full",
|
|
460
490
|
style: { bg: tokens.bg.panel.base, fg: tokens.text.primary },
|
|
461
491
|
inheritStyle: { fg: tokens.text.primary },
|
|
462
492
|
},
|
|
@@ -467,8 +497,8 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
467
497
|
{
|
|
468
498
|
border: "none",
|
|
469
499
|
p: 0,
|
|
470
|
-
width: "
|
|
471
|
-
height: "
|
|
500
|
+
width: "full",
|
|
501
|
+
height: "full",
|
|
472
502
|
style: { bg: tokens.bg.app, fg: tokens.text.primary },
|
|
473
503
|
inheritStyle: { fg: tokens.text.primary },
|
|
474
504
|
},
|
|
@@ -479,8 +509,8 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
479
509
|
{
|
|
480
510
|
border: "none",
|
|
481
511
|
p: 0,
|
|
482
|
-
width: "
|
|
483
|
-
height: "
|
|
512
|
+
width: "full",
|
|
513
|
+
height: "full",
|
|
484
514
|
style: { bg: tokens.bg.app, fg: tokens.text.primary },
|
|
485
515
|
inheritStyle: { fg: tokens.text.primary },
|
|
486
516
|
},
|
|
@@ -509,8 +539,8 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
509
539
|
{
|
|
510
540
|
border: "none",
|
|
511
541
|
p: 0,
|
|
512
|
-
width: "
|
|
513
|
-
height: "
|
|
542
|
+
width: "full",
|
|
543
|
+
height: "full",
|
|
514
544
|
style: { bg: tokens.bg.app, fg: tokens.text.primary },
|
|
515
545
|
inheritStyle: { fg: tokens.text.primary },
|
|
516
546
|
},
|
|
@@ -522,7 +552,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
522
552
|
...(showSidebar
|
|
523
553
|
? {
|
|
524
554
|
sidebar: {
|
|
525
|
-
width:
|
|
555
|
+
width: sidebarWidth,
|
|
526
556
|
content: sidebarContent,
|
|
527
557
|
},
|
|
528
558
|
}
|
|
@@ -588,7 +618,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
588
618
|
background: tokens.bg.panel.elevated,
|
|
589
619
|
foreground: tokens.text.primary,
|
|
590
620
|
},
|
|
591
|
-
|
|
621
|
+
onClose: (id) => options.deps.dispatch({ type: "dismiss-toast", toastId: id }),
|
|
592
622
|
}),
|
|
593
623
|
})
|
|
594
624
|
: null,
|
|
@@ -612,7 +642,7 @@ export function renderShell(options: ShellOptions): VNode {
|
|
|
612
642
|
sources: commandSources,
|
|
613
643
|
selectedIndex: state.commandIndex,
|
|
614
644
|
placeholder: "Type command or route",
|
|
615
|
-
|
|
645
|
+
onChange: (query) => options.deps.dispatch({ type: "set-command-query", query }),
|
|
616
646
|
onSelectionChange: (index) =>
|
|
617
647
|
options.deps.dispatch({ type: "set-command-index", index }),
|
|
618
648
|
onSelect: (item) => {
|