@valyrianjs/terminal 0.2.0 → 0.2.2

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.
Files changed (102) hide show
  1. package/dist/ansi.d.ts +2 -0
  2. package/dist/ansi.d.ts.map +1 -1
  3. package/dist/ansi.js +23 -13
  4. package/dist/ansi.js.map +1 -1
  5. package/dist/events.d.ts.map +1 -1
  6. package/dist/events.js +10 -2
  7. package/dist/events.js.map +1 -1
  8. package/dist/frame-style.d.ts +7 -0
  9. package/dist/frame-style.d.ts.map +1 -0
  10. package/dist/frame-style.js +27 -0
  11. package/dist/frame-style.js.map +1 -0
  12. package/dist/keymap.d.ts.map +1 -1
  13. package/dist/keymap.js +4 -2
  14. package/dist/keymap.js.map +1 -1
  15. package/dist/layout.d.ts +5 -1
  16. package/dist/layout.d.ts.map +1 -1
  17. package/dist/layout.js +55 -24
  18. package/dist/layout.js.map +1 -1
  19. package/dist/mouse.d.ts +6 -0
  20. package/dist/mouse.d.ts.map +1 -1
  21. package/dist/mouse.js +38 -17
  22. package/dist/mouse.js.map +1 -1
  23. package/dist/primitives.d.ts.map +1 -1
  24. package/dist/primitives.js +8 -1
  25. package/dist/primitives.js.map +1 -1
  26. package/dist/render.d.ts.map +1 -1
  27. package/dist/render.js +266 -70
  28. package/dist/render.js.map +1 -1
  29. package/dist/runtime.d.ts.map +1 -1
  30. package/dist/runtime.js +13 -5
  31. package/dist/runtime.js.map +1 -1
  32. package/dist/session.d.ts.map +1 -1
  33. package/dist/session.js +325 -83
  34. package/dist/session.js.map +1 -1
  35. package/dist/text.d.ts +7 -0
  36. package/dist/text.d.ts.map +1 -1
  37. package/dist/text.js +114 -0
  38. package/dist/text.js.map +1 -1
  39. package/dist/theme.d.ts.map +1 -1
  40. package/dist/theme.js +3 -0
  41. package/dist/theme.js.map +1 -1
  42. package/dist/tree.d.ts.map +1 -1
  43. package/dist/tree.js +18 -4
  44. package/dist/tree.js.map +1 -1
  45. package/dist/types.d.ts +41 -4
  46. package/dist/types.d.ts.map +1 -1
  47. package/docs/api-reference.md +18 -8
  48. package/docs/cookbook.md +1 -1
  49. package/docs/interaction-model.md +10 -8
  50. package/docs/primitive-gallery.md +9 -5
  51. package/examples/basic.tsx +22 -0
  52. package/examples/cli.tsx +55 -0
  53. package/examples/demo.tsx +98 -0
  54. package/examples/docs/background-fill.tsx +107 -0
  55. package/examples/docs/component-composition.tsx +140 -0
  56. package/examples/docs/cursor.tsx +121 -0
  57. package/examples/docs/employees-list.tsx +138 -0
  58. package/examples/docs/hello.tsx +98 -0
  59. package/examples/docs/interactive-note.tsx +111 -0
  60. package/examples/docs/module-api-dashboard.tsx +307 -0
  61. package/examples/docs/module-flux-store.tsx +181 -0
  62. package/examples/docs/module-form-workflow.tsx +339 -0
  63. package/examples/docs/module-forms.tsx +218 -0
  64. package/examples/docs/module-money.tsx +175 -0
  65. package/examples/docs/module-native-store.tsx +188 -0
  66. package/examples/docs/module-pulses.tsx +142 -0
  67. package/examples/docs/module-query.tsx +209 -0
  68. package/examples/docs/module-request.tsx +194 -0
  69. package/examples/docs/module-state-workbench.tsx +283 -0
  70. package/examples/docs/module-tasks.tsx +223 -0
  71. package/examples/docs/module-translate.tsx +194 -0
  72. package/examples/docs/module-utils.tsx +168 -0
  73. package/examples/docs/module-valyrian-core.tsx +159 -0
  74. package/examples/docs/pizza-builder.tsx +463 -0
  75. package/examples/docs/primitive-activity-console.tsx +113 -0
  76. package/examples/docs/primitive-command-panel.tsx +186 -0
  77. package/examples/docs/primitive-data-explorer.tsx +155 -0
  78. package/examples/docs/primitive-input-workbench.tsx +128 -0
  79. package/examples/docs/primitive-layout-shell.tsx +115 -0
  80. package/examples/docs/responsive-split.tsx +186 -0
  81. package/examples/docs/style-system.tsx +209 -0
  82. package/examples/docs/theme-colors.tsx +225 -0
  83. package/examples/docs/virtualized-list-workbench.tsx +232 -0
  84. package/examples/opencode-dogfood-app.tsx +215 -0
  85. package/examples/opencode-dogfood-lifecycle.tsx +194 -0
  86. package/examples/opencode-dogfood.tsx +11 -0
  87. package/llms-full.txt +38 -22
  88. package/package.json +3 -2
  89. package/src/ansi.ts +23 -13
  90. package/src/events.ts +6 -2
  91. package/src/frame-style.ts +36 -0
  92. package/src/keymap.ts +4 -2
  93. package/src/layout.ts +59 -25
  94. package/src/mouse.ts +41 -16
  95. package/src/primitives.ts +8 -1
  96. package/src/render.ts +286 -71
  97. package/src/runtime.ts +13 -5
  98. package/src/session.ts +343 -79
  99. package/src/text.ts +148 -0
  100. package/src/theme.ts +3 -0
  101. package/src/tree.ts +19 -4
  102. package/src/types.ts +48 -3
@@ -0,0 +1,113 @@
1
+ import {
2
+ Button,
3
+ LogView,
4
+ Pane,
5
+ Screen,
6
+ ScrollView,
7
+ Text,
8
+ mountTerminal
9
+ } from "@valyrianjs/terminal";
10
+ import type { TerminalLogViewEntry, TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
11
+
12
+ interface PrimitiveActivityConsoleState {
13
+ entries: TerminalLogViewEntry[];
14
+ count: number;
15
+ running: boolean;
16
+ }
17
+
18
+ export interface PrimitiveActivityConsoleDemo {
19
+ session: TerminalSession;
20
+ dispatchKey(key: string): string;
21
+ output(): string;
22
+ ansiOutput(): string;
23
+ isRunning(): boolean;
24
+ destroy(): void;
25
+ }
26
+
27
+ function shouldRunSnapshot() {
28
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
29
+ }
30
+
31
+ function addEvent(state: PrimitiveActivityConsoleState) {
32
+ state.count += 1;
33
+ state.entries = [...state.entries, { id: `event-${state.count}`, content: `event ${state.count}: operations update received` }];
34
+ }
35
+
36
+ export function App({ state }: { state: PrimitiveActivityConsoleState }) {
37
+ return (
38
+ <Screen title="Activity Console">
39
+ <Text>Activity Console</Text>
40
+ <Pane style={{ background: "#111827", padding: { x: 1, y: 0 } }}>
41
+ <Text>Activity timeline</Text>
42
+ <ScrollView id="activity-scroll" height={5}>
43
+ <Text>09:00 Intake queue opened</Text>
44
+ <Text>09:12 Billing sync completed</Text>
45
+ <Text>09:24 Support ticket assigned</Text>
46
+ <Text>09:31 Export job finished</Text>
47
+ <Text>09:45 Deployment window scheduled</Text>
48
+ <Text>10:00 Operations console ready</Text>
49
+ </ScrollView>
50
+ <LogView entries={state.entries} followTail emptyText="No events yet" />
51
+ <Button id="add-event" onpress={() => addEvent(state)}>Add activity event</Button>
52
+ </Pane>
53
+ <Text>Enter: add event Up/Down: scroll when viewport has focus Ctrl+C: quit</Text>
54
+ </Screen>
55
+ );
56
+ }
57
+
58
+ export function createPrimitiveActivityConsoleDemo(options: TerminalMountOptions = {}): PrimitiveActivityConsoleDemo {
59
+ const state: PrimitiveActivityConsoleState = { entries: [{ id: "event-0", content: "event 0: operations console ready" }], count: 0, running: true };
60
+ let session: TerminalSession;
61
+
62
+ function quit() {
63
+ state.running = false;
64
+ session.destroy();
65
+ }
66
+
67
+ session = mountTerminal(<App state={state} />, {
68
+ ...options,
69
+ keymap: {
70
+ ...options.keymap,
71
+ bindings: [...(options.keymap?.bindings || []), { key: "CTRL_C", command: { id: "quit" }, scope: "global" }],
72
+ onCommand(command, context) {
73
+ if (command.id === "quit") {
74
+ quit();
75
+ return true;
76
+ }
77
+ return options.keymap?.onCommand?.(command, context);
78
+ }
79
+ }
80
+ });
81
+ session.focus("add-event");
82
+
83
+ return {
84
+ session,
85
+ dispatchKey(key: string) {
86
+ return session.dispatchKey(key);
87
+ },
88
+ output() {
89
+ return session.output();
90
+ },
91
+ ansiOutput() {
92
+ return session.ansiOutput();
93
+ },
94
+ isRunning() {
95
+ return state.running;
96
+ },
97
+ destroy() {
98
+ state.running = false;
99
+ session.destroy();
100
+ }
101
+ };
102
+ }
103
+
104
+ if (import.meta.main) {
105
+ if (shouldRunSnapshot()) {
106
+ const demo = createPrimitiveActivityConsoleDemo({ runtime: "headless", cols: 72, rows: 16 });
107
+ process.stdout.write(demo.output());
108
+ process.stdout.write("\n");
109
+ demo.destroy();
110
+ } else {
111
+ createPrimitiveActivityConsoleDemo();
112
+ }
113
+ }
@@ -0,0 +1,186 @@
1
+ import {
2
+ Button,
3
+ FocusScope,
4
+ Input,
5
+ List,
6
+ Overlay,
7
+ Screen,
8
+ Text,
9
+ mountTerminal
10
+ } from "@valyrianjs/terminal";
11
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
12
+
13
+ interface PrimitiveCommandPanelState {
14
+ query: string;
15
+ selectedIndex: number;
16
+ picked: string;
17
+ running: boolean;
18
+ }
19
+
20
+ const COMMANDS = ["Open activity console", "Create ticket", "Show data explorer"] as const;
21
+
22
+ export interface PrimitiveCommandPanelDemo {
23
+ session: TerminalSession;
24
+ dispatchKey(key: string): string;
25
+ output(): string;
26
+ ansiOutput(): string;
27
+ isRunning(): boolean;
28
+ destroy(): void;
29
+ }
30
+
31
+ function shouldRunSnapshot() {
32
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
33
+ }
34
+
35
+ function filteredCommandsForQuery(query: string) {
36
+ const normalizedQuery = query.trim().toLowerCase();
37
+ if (!normalizedQuery) {
38
+ return [...COMMANDS];
39
+ }
40
+ return COMMANDS.filter((command) => command.toLowerCase().includes(normalizedQuery));
41
+ }
42
+
43
+ function clampSelectedIndex(state: PrimitiveCommandPanelState, commands: readonly string[]) {
44
+ if (commands.length === 0) {
45
+ state.selectedIndex = 0;
46
+ return;
47
+ }
48
+ state.selectedIndex = Math.max(0, Math.min(commands.length - 1, state.selectedIndex));
49
+ }
50
+
51
+ function pickCommand(state: PrimitiveCommandPanelState, commands = filteredCommandsForQuery(state.query)) {
52
+ clampSelectedIndex(state, commands);
53
+ state.picked = commands[state.selectedIndex] || "";
54
+ }
55
+
56
+ export function App({ state }: { state: PrimitiveCommandPanelState }) {
57
+ const filteredCommands = filteredCommandsForQuery(state.query);
58
+ clampSelectedIndex(state, filteredCommands);
59
+ const selectedCommand = filteredCommands[state.selectedIndex] || "none";
60
+
61
+ return (
62
+ <Screen title="Command Panel">
63
+ <Text>Command Panel</Text>
64
+ <Text>{state.picked ? `Picked command: ${state.picked}` : "Choose a command"}</Text>
65
+ <Overlay id="command-overlay" margin={{ x: "10%", y: "20%" }} trapFocus style={{ background: "#111827", padding: { x: 1, y: 0 } }}>
66
+ <FocusScope>
67
+ <Text>Command Panel</Text>
68
+ <Input
69
+ id="command-query"
70
+ value={state.query}
71
+ placeholder="Filter commands"
72
+ onchange={(event) => {
73
+ state.query = event.value;
74
+ clampSelectedIndex(state, filteredCommandsForQuery(state.query));
75
+ }}
76
+ />
77
+ <List
78
+ id="command-list"
79
+ items={filteredCommands}
80
+ renderItem={(item, index) => `${index === state.selectedIndex ? ">" : " "} ${item}`}
81
+ onchange={(event) => {
82
+ state.selectedIndex = event.index;
83
+ }}
84
+ onpress={() => pickCommand(state, filteredCommands)}
85
+ />
86
+ <Button id="run-command" onpress={() => pickCommand(state, filteredCommands)}>Run command</Button>
87
+ </FocusScope>
88
+ </Overlay>
89
+ <Text>Selected: {selectedCommand}</Text>
90
+ <Text>Type to filter ↑/↓: choose Enter: pick Ctrl+C: quit</Text>
91
+ </Screen>
92
+ );
93
+ }
94
+
95
+ export function createPrimitiveCommandPanelDemo(options: TerminalMountOptions = {}): PrimitiveCommandPanelDemo {
96
+ const state: PrimitiveCommandPanelState = { query: "", selectedIndex: 0, picked: "", running: true };
97
+ let session: TerminalSession;
98
+
99
+ function quit() {
100
+ state.running = false;
101
+ session.destroy();
102
+ }
103
+
104
+ function move(delta: number) {
105
+ const filteredCommands = filteredCommandsForQuery(state.query);
106
+ clampSelectedIndex(state, filteredCommands);
107
+ if (filteredCommands.length === 0) {
108
+ return;
109
+ }
110
+ const nextIndex = state.selectedIndex + delta;
111
+ state.selectedIndex = Math.max(0, Math.min(filteredCommands.length - 1, nextIndex));
112
+ }
113
+
114
+ session = mountTerminal(<App state={state} />, {
115
+ ...options,
116
+ keymap: {
117
+ ...options.keymap,
118
+ bindings: [
119
+ ...(options.keymap?.bindings || []),
120
+ { key: "j", command: { id: "input.insertText", text: "j" }, scope: "input", when: { focusedId: "command-query", focusedTag: "terminal-input" } },
121
+ { key: "J", command: { id: "input.insertText", text: "J" }, scope: "input", when: { focusedId: "command-query", focusedTag: "terminal-input" } },
122
+ { key: "k", command: { id: "input.insertText", text: "k" }, scope: "input", when: { focusedId: "command-query", focusedTag: "terminal-input" } },
123
+ { key: "K", command: { id: "input.insertText", text: "K" }, scope: "input", when: { focusedId: "command-query", focusedTag: "terminal-input" } },
124
+ { key: "j", command: { id: "command.next" }, scope: "global" },
125
+ { key: "J", command: { id: "command.next" }, scope: "global" },
126
+ { key: "DOWN", command: { id: "command.next" }, scope: "global" },
127
+ { key: "k", command: { id: "command.prev" }, scope: "global" },
128
+ { key: "K", command: { id: "command.prev" }, scope: "global" },
129
+ { key: "UP", command: { id: "command.prev" }, scope: "global" },
130
+ { key: "ENTER", command: { id: "command.pick" }, scope: "global" },
131
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
132
+ ],
133
+ onCommand(command, context) {
134
+ if (command.id === "command.next") {
135
+ move(1);
136
+ return true;
137
+ }
138
+ if (command.id === "command.prev") {
139
+ move(-1);
140
+ return true;
141
+ }
142
+ if (command.id === "command.pick") {
143
+ pickCommand(state);
144
+ return true;
145
+ }
146
+ if (command.id === "quit") {
147
+ quit();
148
+ return true;
149
+ }
150
+ return options.keymap?.onCommand?.(command, context);
151
+ }
152
+ }
153
+ });
154
+ session.focus("command-query");
155
+
156
+ return {
157
+ session,
158
+ dispatchKey(key: string) {
159
+ return session.dispatchKey(key);
160
+ },
161
+ output() {
162
+ return session.output();
163
+ },
164
+ ansiOutput() {
165
+ return session.ansiOutput();
166
+ },
167
+ isRunning() {
168
+ return state.running;
169
+ },
170
+ destroy() {
171
+ state.running = false;
172
+ session.destroy();
173
+ }
174
+ };
175
+ }
176
+
177
+ if (import.meta.main) {
178
+ if (shouldRunSnapshot()) {
179
+ const demo = createPrimitiveCommandPanelDemo({ runtime: "headless", cols: 64, rows: 14 });
180
+ process.stdout.write(demo.output());
181
+ process.stdout.write("\n");
182
+ demo.destroy();
183
+ } else {
184
+ createPrimitiveCommandPanelDemo();
185
+ }
186
+ }
@@ -0,0 +1,155 @@
1
+ import {
2
+ List,
3
+ Pane,
4
+ Row,
5
+ Screen,
6
+ Split,
7
+ Table,
8
+ Td,
9
+ Text,
10
+ mountTerminal
11
+ } from "@valyrianjs/terminal";
12
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
13
+
14
+ type Item = { name: string; status: string; queue: string };
15
+
16
+ interface PrimitiveDataExplorerState {
17
+ items: Item[];
18
+ selectedIndex: number;
19
+ running: boolean;
20
+ }
21
+
22
+ export interface PrimitiveDataExplorerDemo {
23
+ session: TerminalSession;
24
+ dispatchKey(key: string): string;
25
+ output(): string;
26
+ ansiOutput(): string;
27
+ isRunning(): boolean;
28
+ destroy(): void;
29
+ }
30
+
31
+ function shouldRunSnapshot() {
32
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
33
+ }
34
+
35
+ function createInitialState(): PrimitiveDataExplorerState {
36
+ return {
37
+ selectedIndex: 0,
38
+ running: true,
39
+ items: [
40
+ { name: "API service", status: "healthy", queue: "8 requests" },
41
+ { name: "Billing queue", status: "attention", queue: "3 invoices" },
42
+ { name: "Reports", status: "ready", queue: "12 exports" }
43
+ ]
44
+ };
45
+ }
46
+
47
+ export function App({ state }: { state: PrimitiveDataExplorerState }) {
48
+ const selected = state.items[state.selectedIndex] || state.items[0];
49
+ return (
50
+ <Screen title="Service Explorer">
51
+ <Text>Service Explorer</Text>
52
+ <Split gap={1} sizes={["35%", "1fr"]}>
53
+ <Pane style={{ background: "#111827", padding: { x: 1, y: 0 } }}>
54
+ <Text>Services</Text>
55
+ <List
56
+ id="service-list"
57
+ items={state.items}
58
+ renderItem={(item, index) => `${index === state.selectedIndex ? ">" : " "} ${item.name}`}
59
+ onchange={(event) => {
60
+ state.selectedIndex = event.index;
61
+ }}
62
+ />
63
+ </Pane>
64
+ <Pane style={{ background: "#1f2937", padding: { x: 1, y: 0 } }}>
65
+ <Text>Details</Text>
66
+ <Table>
67
+ <Row><Td><Text>Name</Text></Td><Td><Text>{selected.name}</Text></Td></Row>
68
+ <Row><Td><Text>Status</Text></Td><Td><Text>{selected.status}</Text></Td></Row>
69
+ <Row><Td><Text>Queue</Text></Td><Td><Text>{selected.queue}</Text></Td></Row>
70
+ </Table>
71
+ </Pane>
72
+ </Split>
73
+ <Text>J/K or arrows: choose service Ctrl+C: quit</Text>
74
+ </Screen>
75
+ );
76
+ }
77
+
78
+ export function createPrimitiveDataExplorerDemo(options: TerminalMountOptions = {}): PrimitiveDataExplorerDemo {
79
+ const state = createInitialState();
80
+ let session: TerminalSession;
81
+
82
+ function quit() {
83
+ state.running = false;
84
+ session.destroy();
85
+ }
86
+
87
+ function move(delta: number) {
88
+ const nextIndex = state.selectedIndex + delta;
89
+ state.selectedIndex = Math.max(0, Math.min(state.items.length - 1, nextIndex));
90
+ }
91
+
92
+ session = mountTerminal(<App state={state} />, {
93
+ ...options,
94
+ keymap: {
95
+ ...options.keymap,
96
+ bindings: [
97
+ ...(options.keymap?.bindings || []),
98
+ { key: "j", command: { id: "service.next" }, scope: "global" },
99
+ { key: "J", command: { id: "service.next" }, scope: "global" },
100
+ { key: "DOWN", command: { id: "service.next" }, scope: "global" },
101
+ { key: "k", command: { id: "service.prev" }, scope: "global" },
102
+ { key: "K", command: { id: "service.prev" }, scope: "global" },
103
+ { key: "UP", command: { id: "service.prev" }, scope: "global" },
104
+ { key: "CTRL_C", command: { id: "quit" }, scope: "global" }
105
+ ],
106
+ onCommand(command, context) {
107
+ if (command.id === "service.next") {
108
+ move(1);
109
+ return true;
110
+ }
111
+ if (command.id === "service.prev") {
112
+ move(-1);
113
+ return true;
114
+ }
115
+ if (command.id === "quit") {
116
+ quit();
117
+ return true;
118
+ }
119
+ return options.keymap?.onCommand?.(command, context);
120
+ }
121
+ }
122
+ });
123
+ session.focus("service-list");
124
+
125
+ return {
126
+ session,
127
+ dispatchKey(key: string) {
128
+ return session.dispatchKey(key);
129
+ },
130
+ output() {
131
+ return session.output();
132
+ },
133
+ ansiOutput() {
134
+ return session.ansiOutput();
135
+ },
136
+ isRunning() {
137
+ return state.running;
138
+ },
139
+ destroy() {
140
+ state.running = false;
141
+ session.destroy();
142
+ }
143
+ };
144
+ }
145
+
146
+ if (import.meta.main) {
147
+ if (shouldRunSnapshot()) {
148
+ const demo = createPrimitiveDataExplorerDemo({ runtime: "headless", cols: 76, rows: 14 });
149
+ process.stdout.write(demo.output());
150
+ process.stdout.write("\n");
151
+ demo.destroy();
152
+ } else {
153
+ createPrimitiveDataExplorerDemo();
154
+ }
155
+ }
@@ -0,0 +1,128 @@
1
+ import {
2
+ Box,
3
+ Button,
4
+ Editor,
5
+ FocusScope,
6
+ Input,
7
+ Screen,
8
+ Text,
9
+ mountTerminal
10
+ } from "@valyrianjs/terminal";
11
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
12
+
13
+ interface PrimitiveInputWorkbenchState {
14
+ summary: string;
15
+ details: string;
16
+ saved: string;
17
+ running: boolean;
18
+ }
19
+
20
+ export interface PrimitiveInputWorkbenchDemo {
21
+ session: TerminalSession;
22
+ dispatchKey(key: string): string;
23
+ output(): string;
24
+ ansiOutput(): string;
25
+ isRunning(): boolean;
26
+ destroy(): void;
27
+ }
28
+
29
+ function shouldRunSnapshot() {
30
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
31
+ }
32
+
33
+ function saveTicket(state: PrimitiveInputWorkbenchState) {
34
+ const summary = state.summary.trim();
35
+ const details = state.details.trim();
36
+ state.saved = summary || details || "draft";
37
+ }
38
+
39
+ export function App({ state }: { state: PrimitiveInputWorkbenchState }) {
40
+ return (
41
+ <Screen title="Ticket Intake">
42
+ <Text>Ticket Intake</Text>
43
+ <FocusScope>
44
+ <Box direction="column" gap={1} style={{ background: "#111827", padding: { x: 1, y: 0 } }}>
45
+ <Text>Ticket summary</Text>
46
+ <Input
47
+ id="summary"
48
+ value={state.summary}
49
+ placeholder="Short title"
50
+ onchange={(event) => {
51
+ state.summary = event.value;
52
+ }}
53
+ />
54
+ <Text>Description</Text>
55
+ <Editor
56
+ id="details"
57
+ height={3}
58
+ value={state.details}
59
+ placeholder="Add context"
60
+ onchange={(event) => {
61
+ state.details = event.value;
62
+ }}
63
+ />
64
+ <Button id="save-ticket" onpress={() => saveTicket(state)}>Save ticket</Button>
65
+ </Box>
66
+ </FocusScope>
67
+ <Text>{state.saved ? `Saved ticket: ${state.saved}` : "Saved ticket: none"}</Text>
68
+ <Text>Tab: move focus Enter: save from button Ctrl+C: quit</Text>
69
+ </Screen>
70
+ );
71
+ }
72
+
73
+ export function createPrimitiveInputWorkbenchDemo(options: TerminalMountOptions = {}): PrimitiveInputWorkbenchDemo {
74
+ const state: PrimitiveInputWorkbenchState = { summary: "", details: "", saved: "", running: true };
75
+ let session: TerminalSession;
76
+
77
+ function quit() {
78
+ state.running = false;
79
+ session.destroy();
80
+ }
81
+
82
+ session = mountTerminal(<App state={state} />, {
83
+ ...options,
84
+ keymap: {
85
+ ...options.keymap,
86
+ bindings: [...(options.keymap?.bindings || []), { key: "CTRL_C", command: { id: "quit" }, scope: "global" }],
87
+ onCommand(command, context) {
88
+ if (command.id === "quit") {
89
+ quit();
90
+ return true;
91
+ }
92
+ return options.keymap?.onCommand?.(command, context);
93
+ }
94
+ }
95
+ });
96
+ session.focus("summary");
97
+
98
+ return {
99
+ session,
100
+ dispatchKey(key: string) {
101
+ return session.dispatchKey(key);
102
+ },
103
+ output() {
104
+ return session.output();
105
+ },
106
+ ansiOutput() {
107
+ return session.ansiOutput();
108
+ },
109
+ isRunning() {
110
+ return state.running;
111
+ },
112
+ destroy() {
113
+ state.running = false;
114
+ session.destroy();
115
+ }
116
+ };
117
+ }
118
+
119
+ if (import.meta.main) {
120
+ if (shouldRunSnapshot()) {
121
+ const demo = createPrimitiveInputWorkbenchDemo({ runtime: "headless", cols: 64, rows: 14 });
122
+ process.stdout.write(demo.output());
123
+ process.stdout.write("\n");
124
+ demo.destroy();
125
+ } else {
126
+ createPrimitiveInputWorkbenchDemo();
127
+ }
128
+ }
@@ -0,0 +1,115 @@
1
+ import {
2
+ Box,
3
+ Fixed,
4
+ Pane,
5
+ Screen,
6
+ Split,
7
+ Text,
8
+ View,
9
+ mountTerminal
10
+ } from "@valyrianjs/terminal";
11
+ import type { TerminalMountOptions, TerminalSession } from "@valyrianjs/terminal";
12
+
13
+ interface PrimitiveLayoutShellState {
14
+ running: boolean;
15
+ }
16
+
17
+ export interface PrimitiveLayoutShellDemo {
18
+ session: TerminalSession;
19
+ dispatchKey(key: string): string;
20
+ output(): string;
21
+ ansiOutput(): string;
22
+ isRunning(): boolean;
23
+ destroy(): void;
24
+ }
25
+
26
+ function shouldRunSnapshot() {
27
+ return process.argv.includes("--snapshot") || process.env.VALYRIAN_TERMINAL_EXAMPLE_SNAPSHOT === "1" || !process.stdin.isTTY;
28
+ }
29
+
30
+ export function App({ state }: { state: PrimitiveLayoutShellState }) {
31
+ return (
32
+ <Screen title="Operations Workspace">
33
+ <Fixed position="top" size={2}>
34
+ <Text>Operations Workspace</Text>
35
+ <Text>Workspace status and navigation</Text>
36
+ </Fixed>
37
+ <Split gap={1} sizes={["28%", "1fr"]} breakpoints={[{ maxCols: 64, direction: "column", sizes: ["1fr", "2fr"], gap: 1 }]}>
38
+ <Pane style={{ background: "#111827", padding: { x: 1, y: 0 } }}>
39
+ <Text>Sidebar</Text>
40
+ <Text>Inbox</Text>
41
+ <Text>Reports</Text>
42
+ <Text>Settings</Text>
43
+ </Pane>
44
+ <Pane style={{ background: "#1f2937", padding: { x: 1, y: 0 } }}>
45
+ <View role="main" direction="column" gap={1}>
46
+ <Text>Main area</Text>
47
+ <Box style={{ background: "#0f172a", padding: { x: 1, y: 0 } }}>
48
+ <Text>Open ticket: customer export</Text>
49
+ <Text>Status: ready for triage</Text>
50
+ </Box>
51
+ </View>
52
+ </Pane>
53
+ </Split>
54
+ <Fixed position="bottom" size={1}>
55
+ <Text>Ctrl+C: quit</Text>
56
+ </Fixed>
57
+ </Screen>
58
+ );
59
+ }
60
+
61
+ export function createPrimitiveLayoutShellDemo(options: TerminalMountOptions = {}): PrimitiveLayoutShellDemo {
62
+ const state: PrimitiveLayoutShellState = { running: true };
63
+ let session: TerminalSession;
64
+
65
+ function quit() {
66
+ state.running = false;
67
+ session.destroy();
68
+ }
69
+
70
+ session = mountTerminal(<App state={state} />, {
71
+ ...options,
72
+ keymap: {
73
+ ...options.keymap,
74
+ bindings: [...(options.keymap?.bindings || []), { key: "CTRL_C", command: { id: "quit" }, scope: "global" }],
75
+ onCommand(command, context) {
76
+ if (command.id === "quit") {
77
+ quit();
78
+ return true;
79
+ }
80
+ return options.keymap?.onCommand?.(command, context);
81
+ }
82
+ }
83
+ });
84
+
85
+ return {
86
+ session,
87
+ dispatchKey(key: string) {
88
+ return session.dispatchKey(key);
89
+ },
90
+ output() {
91
+ return session.output();
92
+ },
93
+ ansiOutput() {
94
+ return session.ansiOutput();
95
+ },
96
+ isRunning() {
97
+ return state.running;
98
+ },
99
+ destroy() {
100
+ state.running = false;
101
+ session.destroy();
102
+ }
103
+ };
104
+ }
105
+
106
+ if (import.meta.main) {
107
+ if (shouldRunSnapshot()) {
108
+ const demo = createPrimitiveLayoutShellDemo({ runtime: "headless", cols: 72, rows: 16 });
109
+ process.stdout.write(demo.output());
110
+ process.stdout.write("\n");
111
+ demo.destroy();
112
+ } else {
113
+ createPrimitiveLayoutShellDemo();
114
+ }
115
+ }