castle-web-cli 0.4.75 → 0.4.77

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 (74) hide show
  1. package/dist/agent-prompts.d.ts +13 -0
  2. package/dist/agent-prompts.js +43 -13
  3. package/dist/agent.d.ts +29 -0
  4. package/dist/agent.js +1004 -257
  5. package/dist/castle-host/host.js +59 -5
  6. package/dist/commonInstructions.d.ts +1 -1
  7. package/dist/commonInstructions.js +11 -1
  8. package/dist/filesChanged.d.ts +25 -0
  9. package/dist/filesChanged.js +140 -0
  10. package/dist/ide.d.ts +1 -0
  11. package/dist/ide.js +250 -1
  12. package/dist/init.js +46 -4
  13. package/dist/save-deck.js +8 -1
  14. package/dist/serve.js +45 -3
  15. package/dist/shell/assets/index-CvHiGhAV.js +141 -0
  16. package/dist/shell/assets/{index-WNbOHPBj.css → index-QteLRDnK.css} +1 -1
  17. package/dist/shell/index.html +2 -2
  18. package/kits/basic-2d/CLAUDE.md +13 -5
  19. package/kits/basic-2d/castle.json +15 -0
  20. package/kits/basic-2d/drawings/pig.pxart +22 -55
  21. package/kits/basic-2d/editors/PlayOnly.jsx +8 -3
  22. package/kits/basic-2d/editors/PxArtEditor.jsx +237 -4
  23. package/kits/basic-2d/editors/SingleEditor.jsx +30 -59
  24. package/kits/basic-2d/editors/pixelEditorChrome.jsx +17 -11
  25. package/kits/basic-2d/editors/pixelGeometry.js +95 -0
  26. package/kits/basic-2d/editors/pixelInspector.jsx +229 -51
  27. package/kits/basic-2d/editors/pxArtTools.js +109 -1
  28. package/kits/basic-2d/engine/ScenePlayer.jsx +11 -160
  29. package/kits/basic-2d/engine/liveReload.js +88 -0
  30. package/kits/basic-2d/engine/ui.jsx +15 -0
  31. package/kits/basic-2d/engine/ui.module.css +44 -117
  32. package/kits/basic-2d/main.jsx +6 -10
  33. package/package.json +10 -1
  34. package/dist/shell/assets/index-Dfn29Bkt.js +0 -108
  35. package/kits/basic-2d/editors/App.jsx +0 -226
  36. package/kits/basic-2d/editors/CodeEditor.jsx +0 -79
  37. package/kits/basic-2d/editors/FileBrowser.jsx +0 -349
  38. package/kits/basic-2d/editors/codeTheme.js +0 -135
  39. package/kits/basic-2d/engine/playConsole.js +0 -66
  40. package/kits/basic-2d/pnpm-workspace.yaml +0 -3
  41. package/kits/basic-3d/.prettierrc +0 -8
  42. package/kits/basic-3d/CLAUDE.md +0 -162
  43. package/kits/basic-3d/behaviors/Camera.jsx +0 -56
  44. package/kits/basic-3d/behaviors/Collider.jsx +0 -78
  45. package/kits/basic-3d/behaviors/Mesh.jsx +0 -82
  46. package/kits/basic-3d/behaviors/Model.jsx +0 -61
  47. package/kits/basic-3d/behaviors/Transform.jsx +0 -35
  48. package/kits/basic-3d/editors/App.jsx +0 -147
  49. package/kits/basic-3d/editors/CodeEditor.jsx +0 -112
  50. package/kits/basic-3d/editors/FileBrowser.jsx +0 -143
  51. package/kits/basic-3d/editors/ModelEditor.jsx +0 -400
  52. package/kits/basic-3d/editors/PlayOnly.jsx +0 -22
  53. package/kits/basic-3d/editors/SceneEditor.jsx +0 -1081
  54. package/kits/basic-3d/editors/behaviorRegistry.js +0 -24
  55. package/kits/basic-3d/editors/editorHistory.js +0 -52
  56. package/kits/basic-3d/editors/viewportRig.js +0 -90
  57. package/kits/basic-3d/engine/ScenePlayer.jsx +0 -58
  58. package/kits/basic-3d/engine/SceneUI.jsx +0 -67
  59. package/kits/basic-3d/engine/SceneViewport.jsx +0 -102
  60. package/kits/basic-3d/engine/autoInspector.jsx +0 -51
  61. package/kits/basic-3d/engine/files.js +0 -73
  62. package/kits/basic-3d/engine/scene.js +0 -502
  63. package/kits/basic-3d/engine/threeUtil.js +0 -260
  64. package/kits/basic-3d/engine/ui.jsx +0 -352
  65. package/kits/basic-3d/engine/ui.module.css +0 -944
  66. package/kits/basic-3d/eslint.config.js +0 -51
  67. package/kits/basic-3d/index.html +0 -11
  68. package/kits/basic-3d/main.jsx +0 -10
  69. package/kits/basic-3d/models/block.model +0 -14
  70. package/kits/basic-3d/package-lock.json +0 -2713
  71. package/kits/basic-3d/package.json +0 -41
  72. package/kits/basic-3d/pnpm-lock.yaml +0 -1769
  73. package/kits/basic-3d/scenes/main.scene +0 -76
  74. package/kits/basic-3d/vite.config.js +0 -1
@@ -1,21 +1,15 @@
1
- import React, { useCallback, useEffect, useRef, useState } from 'react';
2
- import { createPortal } from 'react-dom';
1
+ import React, { useCallback, useEffect, useRef } from 'react';
3
2
  import { configureSceneCanvas, makeScene } from './scene';
4
3
  import { SceneUI } from './SceneUI';
5
- import { cx, Icon, styles } from './ui';
6
- import { usePlayLogs } from './playConsole';
7
4
  // Engine-level scene player: mount a `SceneRuntime` against a canvas, wire
8
5
  // keyboard / pointer input, run the update+draw loop, and render the
9
6
  // behavior-driven UI overlay. No game logic lives here -- behaviors and
10
- // scenes are the place for that.
7
+ // scenes are the place for that. The dev logs drawer is a builtin shell panel
8
+ // now (cli/src/shell), not rendered here.
11
9
  export function ScenePlayer({ sceneData, sprites, behaviorClasses, onFirstFrame }) {
12
10
  const canvasRef = useRef(null);
13
11
  const runtimeRef = useRef(null);
14
- // Bumping this re-runs the mount effect below, which tears down the running
15
- // loop/runtime and rebuilds a fresh scene from the same data -- a clean restart.
16
- const [runToken, setRunToken] = useState(0);
17
12
  const getRuntime = useCallback(() => runtimeRef.current, []);
18
- const restart = useCallback(() => setRunToken((token) => token + 1), []);
19
13
  useEffect(() => {
20
14
  const canvas = canvasRef.current;
21
15
  if (!canvas) return undefined;
@@ -61,9 +55,7 @@ export function ScenePlayer({ sceneData, sprites, behaviorClasses, onFirstFrame
61
55
  canvas.addEventListener('pointerup', onPointerUp);
62
56
  canvas.addEventListener('pointercancel', onPointerUp);
63
57
  canvas.focus();
64
- // Only signal first-frame readiness on the initial run; a restart shouldn't
65
- // re-fire the host launcher reveal.
66
- const stopLoop = startPlayerLoop(canvas, ctx, runtime, runToken === 0 ? onFirstFrame : undefined);
58
+ const stopLoop = startPlayerLoop(canvas, ctx, runtime, onFirstFrame);
67
59
  return () => {
68
60
  stopLoop();
69
61
  window.removeEventListener('keydown', onKeyDown);
@@ -75,156 +67,15 @@ export function ScenePlayer({ sceneData, sprites, behaviorClasses, onFirstFrame
75
67
  runtimeRef.current = null;
76
68
  };
77
69
  // eslint-disable-next-line react-hooks/exhaustive-deps
78
- }, [runToken]);
79
- return (
80
- <>
81
- <div style={{ position: 'fixed', inset: 0, background: '#000' }}>
82
- <canvas
83
- ref={canvasRef}
84
- tabIndex={0}
85
- style={{ width: '100%', height: '100%', display: 'block', outline: 'none' }}
86
- />
87
- <SceneUI getRuntime={getRuntime} />
88
- </div>
89
- {/* Portal to <body> so the console drawer escapes the SDK's card-sized,
90
- transformed `#root > *` wrapper and pins to the panel, outside the
91
- play preview. */}
92
- {createPortal(<PlayConsole onRestart={restart} />, document.body)}
93
- </>
94
- );
95
- }
96
- function PlayConsole({ onRestart }) {
97
- const { logs, clearLogs } = usePlayLogs();
98
- const [open, setOpen] = useState(false);
99
- const rootRef = useRef(null);
100
- const bodyRef = useRef(null);
101
- // Reserve the drawer's footprint by shrinking + lifting the SDK deck card so it
102
- // sits fully above the drawer (visible + interactive) instead of behind it.
103
- // Driven by the live drawer height; recomputed on toggle (height change via the
104
- // ResizeObserver) and on window/card resize.
105
- useEffect(() => {
106
- const el = rootRef.current;
107
- if (!el) return undefined;
108
- const docEl = document.documentElement;
109
- docEl.dataset.castleLogs = '';
110
- const TOP_MARGIN = 16;
111
- const MIN_CARD_SCALE = 0.2;
112
- const apply = () => {
113
- const drawerHeight = el.offsetHeight;
114
- const cs = getComputedStyle(docEl);
115
- const fullW = parseFloat(cs.getPropertyValue('--castle-card-w'));
116
- const fullH = parseFloat(cs.getPropertyValue('--castle-card-h'));
117
- docEl.style.setProperty('--castle-logs-shift', `${drawerHeight / 2}px`);
118
- // Only resize the card when we know its natural size; otherwise leave it to
119
- // the SDK fallback (the rule's var() defaults handle this).
120
- if (fullW > 0 && fullH > 0) {
121
- const available = window.innerHeight - drawerHeight - TOP_MARGIN;
122
- // Floor the scale so a very short panel (drawer taller than the area)
123
- // can't drive the card to 0/negative and make the deck disappear.
124
- const scale = Math.min(1, Math.max(MIN_CARD_SCALE, available / fullH));
125
- docEl.style.setProperty('--castle-logs-card-w', `${fullW * scale}px`);
126
- docEl.style.setProperty('--castle-logs-card-h', `${fullH * scale}px`);
127
- } else {
128
- docEl.style.removeProperty('--castle-logs-card-w');
129
- docEl.style.removeProperty('--castle-logs-card-h');
130
- }
131
- };
132
- apply();
133
- const observer = new ResizeObserver(apply);
134
- observer.observe(el);
135
- window.addEventListener('resize', apply);
136
- return () => {
137
- observer.disconnect();
138
- window.removeEventListener('resize', apply);
139
- delete docEl.dataset.castleLogs;
140
- docEl.style.removeProperty('--castle-logs-card-w');
141
- docEl.style.removeProperty('--castle-logs-card-h');
142
- docEl.style.removeProperty('--castle-logs-shift');
143
- };
144
70
  }, []);
145
- // Keep the newest line in view while expanded.
146
- useEffect(() => {
147
- if (open && bodyRef.current) bodyRef.current.scrollTop = bodyRef.current.scrollHeight;
148
- }, [logs, open]);
149
- const copy = useCallback(() => {
150
- const text = logs.map((line) => line.text).join('\n');
151
- navigator.clipboard?.writeText(text).catch(() => {});
152
- }, [logs]);
153
- const hasLogs = logs.length > 0;
154
71
  return (
155
- <div ref={rootRef} className={styles.playConsole}>
156
- {/* The whole header row toggles; the action buttons stopPropagation so
157
- they don't also collapse/expand. */}
158
- <div
159
- className={styles.playConsoleHeader}
160
- role="button"
161
- tabIndex={-1}
162
- aria-expanded={open}
163
- aria-label={open ? 'Collapse logs' : 'Expand logs'}
164
- onClick={() => setOpen((value) => !value)}>
165
- <span className={styles.playConsoleToggle}>
166
- <span className={styles.playConsoleCaret}>
167
- <Icon name={open ? 'chevron-down' : 'chevron-right'} />
168
- </span>
169
- <span className={styles.playConsoleTitle}>Logs</span>
170
- </span>
171
- <div
172
- className={styles.playConsoleActions}
173
- onClick={(event) => event.stopPropagation()}>
174
- {open ? (
175
- <>
176
- <button
177
- type="button"
178
- tabIndex={-1}
179
- className={styles.playConsoleBtn}
180
- aria-label="Copy logs"
181
- title="Copy logs"
182
- disabled={!hasLogs}
183
- onClick={copy}>
184
- <Icon name="clone" />
185
- </button>
186
- <button
187
- type="button"
188
- tabIndex={-1}
189
- className={styles.playConsoleBtn}
190
- aria-label="Clear logs"
191
- title="Clear logs"
192
- disabled={!hasLogs}
193
- onClick={clearLogs}>
194
- <Icon name="trash" />
195
- </button>
196
- </>
197
- ) : null}
198
- <button
199
- type="button"
200
- tabIndex={-1}
201
- className={styles.playConsoleBtn}
202
- aria-label="Restart"
203
- title="Restart"
204
- onClick={onRestart}>
205
- <Icon name="rotate" />
206
- </button>
207
- </div>
208
- </div>
209
- {open ? (
210
- <div ref={bodyRef} className={styles.playConsoleBody}>
211
- {hasLogs ? (
212
- logs.map((line) => (
213
- <div
214
- key={line.id}
215
- className={cx(
216
- styles.playConsoleLine,
217
- line.level === 'warn' && styles.playConsoleLineWarn,
218
- line.level === 'error' && styles.playConsoleLineError
219
- )}>
220
- {line.text}
221
- </div>
222
- ))
223
- ) : (
224
- <div className={styles.playConsoleEmpty}>no output</div>
225
- )}
226
- </div>
227
- ) : null}
72
+ <div style={{ position: 'fixed', inset: 0, background: '#000' }}>
73
+ <canvas
74
+ ref={canvasRef}
75
+ tabIndex={0}
76
+ style={{ width: '100%', height: '100%', display: 'block', outline: 'none' }}
77
+ />
78
+ <SceneUI getRuntime={getRuntime} />
228
79
  </div>
229
80
  );
230
81
  }
@@ -0,0 +1,88 @@
1
+ // Consumer side of the serve's `files_changed` broadcast. The engine decides
2
+ // what a change means for this bundle:
3
+ // - data files (.scene / .pxart / .drawing) are re-read over the serve's
4
+ // files API and folded into React state — live re-render, no reload.
5
+ // - code the bundle imports (per the event's module-graph closure), html,
6
+ // or added/deleted code files (import.meta.glob may pick them up) mean
7
+ // this context is stale — requestReload() (save-state hooks run first).
8
+ import { useEffect, useRef, useState } from 'react';
9
+ import { onFilesChanged, requestReload } from 'castle-web-sdk';
10
+ import { initialFiles } from './files';
11
+
12
+ const DATA_EXTS = ['.scene', '.pxart', '.drawing'];
13
+ const CODE_EXTS = ['.js', '.jsx', '.ts', '.tsx', '.css'];
14
+
15
+ export function isDataFile(path) {
16
+ return DATA_EXTS.some((ext) => path.endsWith(ext));
17
+ }
18
+
19
+ function needsContextReload(change) {
20
+ if (isDataFile(change.path)) return false;
21
+ if (change.path.endsWith('.html')) return true;
22
+ // In the module graph (directly or as someone's import) -> our code changed.
23
+ if (change.affected.length > 0) return true;
24
+ // A new/removed code file isn't in the graph yet but a glob may scan it.
25
+ if (change.event !== 'change') return CODE_EXTS.some((ext) => change.path.endsWith(ext));
26
+ return false;
27
+ }
28
+
29
+ async function readDeckFile(path) {
30
+ const res = await fetch(`/__castle/files/read?path=${encodeURIComponent(path)}`);
31
+ if (!res.ok) throw new Error(`read failed: ${res.status}`);
32
+ const data = await res.json();
33
+ return typeof data.contents === 'string' ? data.contents : '';
34
+ }
35
+
36
+ // Deck files as live state: starts from the build-time glob snapshot and stays
37
+ // current as files change on disk from any source (editors, agents, terminal).
38
+ // `shouldSkipPath` lets an editor keep its own in-flight edits from being
39
+ // clobbered by the fs echo of a stale write. `dataVersion` bumps on every
40
+ // applied data change — key a player on it to rebuild from fresh data.
41
+ export function useLiveDeckFiles({ shouldSkipPath } = {}) {
42
+ const [files, setFiles] = useState(initialFiles);
43
+ const [dataVersion, setDataVersion] = useState(0);
44
+ const skipRef = useRef(shouldSkipPath);
45
+ skipRef.current = shouldSkipPath;
46
+
47
+ useEffect(
48
+ () =>
49
+ onFilesChanged((event) => {
50
+ if (event.changes.some(needsContextReload)) {
51
+ requestReload();
52
+ return;
53
+ }
54
+ const dataChanges = event.changes.filter(
55
+ (change) => isDataFile(change.path) && !skipRef.current?.(change.path)
56
+ );
57
+ if (dataChanges.length === 0) return;
58
+ void applyDataChanges(dataChanges, setFiles, setDataVersion);
59
+ }),
60
+ []
61
+ );
62
+
63
+ return { files, setFiles, dataVersion };
64
+ }
65
+
66
+ async function applyDataChanges(changes, setFiles, setDataVersion) {
67
+ const updates = await Promise.all(
68
+ changes.map(async (change) => {
69
+ if (change.event === 'delete') return { path: change.path, text: null };
70
+ try {
71
+ return { path: change.path, text: await readDeckFile(change.path) };
72
+ } catch {
73
+ return null; // read raced a delete / serve hiccup — skip this path
74
+ }
75
+ })
76
+ );
77
+ const applied = updates.filter(Boolean);
78
+ if (applied.length === 0) return;
79
+ setFiles((current) => {
80
+ const next = { ...current };
81
+ for (const { path, text } of applied) {
82
+ if (text === null) delete next[path];
83
+ else next[path] = text;
84
+ }
85
+ return next;
86
+ });
87
+ setDataVersion((version) => version + 1);
88
+ }
@@ -2,10 +2,13 @@ import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import {
4
4
  faArrowsAlt,
5
+ faArrowsAltH,
6
+ faArrowsAltV,
5
7
  faBars,
6
8
  faChevronDown,
7
9
  faChevronRight,
8
10
  faChevronUp,
11
+ faCircle,
9
12
  faClone,
10
13
  faCode,
11
14
  faEraser,
@@ -22,6 +25,9 @@ import {
22
25
  faPlay,
23
26
  faPlus,
24
27
  faRedo,
28
+ faShapes,
29
+ faSlash,
30
+ faVectorSquare,
25
31
  faSquare,
26
32
  faStamp,
27
33
  faStop,
@@ -149,16 +155,20 @@ const icons = {
149
155
  'chevron-down': faChevronDown,
150
156
  'chevron-right': faChevronRight,
151
157
  'chevron-up': faChevronUp,
158
+ circle: faCircle,
152
159
  clone: faClone,
153
160
  code: faCode,
154
161
  eraser: faEraser,
155
162
  eyedropper: faEyeDropper,
156
163
  fill: faFillDrip,
164
+ 'flip-h': faArrowsAltH,
165
+ 'flip-v': faArrowsAltV,
157
166
  file: faFile,
158
167
  film: faFilm,
159
168
  globe: faGlobe,
160
169
  image: faImage,
161
170
  'layer-group': faLayerGroup,
171
+ marquee: faVectorSquare,
162
172
  move: faArrowsAlt,
163
173
  'object-group': faObjectGroup,
164
174
  palette: faPalette,
@@ -167,10 +177,15 @@ const icons = {
167
177
  plus: faPlus,
168
178
  redo: faRedo,
169
179
  rotate: faSyncAlt,
180
+ shapes: faShapes,
181
+ slash: faSlash,
170
182
  square: faSquare,
171
183
  stamp: faStamp,
172
184
  stop: faStop,
173
185
  times: faTimes,
186
+ // Hand-rolled equilateral-ish triangle glyph (FA free-solid has no plain
187
+ // triangle); shaped to match the filled-glyph look of the imported icons.
188
+ triangle: { icon: [512, 512, [], '', 'M256 64 L496 448 L16 448 Z'] },
174
189
  trash: faTrash,
175
190
  undo: faUndo,
176
191
  };
@@ -1158,12 +1158,21 @@
1158
1158
  flex-direction: column;
1159
1159
  width: 34px;
1160
1160
  flex: 0 0 auto;
1161
+ gap: 8px;
1162
+ }
1163
+
1164
+ /* Each control group (color, size, shape type, mode toggle) is its own boxed
1165
+ section, separated by the strip's gap. Controls stacked inside a section keep
1166
+ 1px dividers so a multi-button group still reads as one segmented unit. */
1167
+ .paintStripSection {
1168
+ display: flex;
1169
+ flex-direction: column;
1161
1170
  border: 1px solid var(--castle-inspector-border);
1162
1171
  border-radius: var(--castle-radius);
1163
1172
  overflow: hidden;
1164
1173
  }
1165
1174
 
1166
- .paintStrip > * + * {
1175
+ .paintStripSection > * + * {
1167
1176
  border-top: 1px solid var(--castle-inspector-border);
1168
1177
  }
1169
1178
 
@@ -1211,6 +1220,11 @@
1211
1220
  background: var(--castle-inspector-input-bg);
1212
1221
  }
1213
1222
 
1223
+ .paintStripModeButton:disabled {
1224
+ opacity: 0.35;
1225
+ cursor: default;
1226
+ }
1227
+
1214
1228
  .drawingSizeSliderVertical {
1215
1229
  width: 88px;
1216
1230
  height: 14px;
@@ -1292,6 +1306,21 @@
1292
1306
  justify-content: center;
1293
1307
  }
1294
1308
 
1309
+ /* Stacks the native artboard canvas and the display-resolution overlay so the
1310
+ marching-ants outline renders crisp instead of sub-pixel on the upscaled art. */
1311
+ .drawingArtboardFrame {
1312
+ position: relative;
1313
+ flex: 0 0 auto;
1314
+ }
1315
+
1316
+ .drawingOverlay {
1317
+ position: absolute;
1318
+ inset: 0;
1319
+ width: 100%;
1320
+ height: 100%;
1321
+ pointer-events: none;
1322
+ }
1323
+
1295
1324
  .drawingCanvas {
1296
1325
  image-rendering: pixelated;
1297
1326
  background:
@@ -1432,6 +1461,20 @@
1432
1461
  color: var(--castle-selected-ink);
1433
1462
  }
1434
1463
 
1464
+ /* Horizontal segmented row of shape sub-mode buttons in the wide-layout Tool
1465
+ Settings panel. Reuses .drawingToolButton glyph buttons with shared borders. */
1466
+ .drawingShapeModeRow {
1467
+ display: flex;
1468
+ width: max-content;
1469
+ border: 1px solid var(--castle-inspector-border);
1470
+ border-radius: var(--castle-radius);
1471
+ overflow: hidden;
1472
+ }
1473
+
1474
+ .drawingShapeModeRow > * + * {
1475
+ border-left: 1px solid var(--castle-inspector-border);
1476
+ }
1477
+
1435
1478
  .drawingToolHint {
1436
1479
  color: var(--castle-inspector-muted);
1437
1480
  font-size: 14px;
@@ -1902,119 +1945,3 @@
1902
1945
  }
1903
1946
  }
1904
1947
 
1905
- /* On-panel play console: a collapsible log drawer pinned to the bottom of the
1906
- play panel (portaled to <body> so it sits outside the SDK's card wrapper).
1907
- Houses the restart control plus copy/clear log affordances. */
1908
- .playConsole {
1909
- position: fixed;
1910
- left: 0;
1911
- right: 0;
1912
- bottom: 0;
1913
- z-index: 60;
1914
- display: flex;
1915
- flex-direction: column;
1916
- border-top: 1px solid rgba(255, 255, 255, 0.12);
1917
- background: rgba(13, 13, 13, 0.94);
1918
- color: #e6e6e6;
1919
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
1920
- 'Courier New', monospace;
1921
- padding-bottom: env(safe-area-inset-bottom, 0px);
1922
- overflow: hidden;
1923
- }
1924
- .playConsoleHeader {
1925
- display: flex;
1926
- align-items: center;
1927
- justify-content: space-between;
1928
- gap: 8px;
1929
- padding: 7px 8px 7px 10px;
1930
- cursor: pointer;
1931
- }
1932
- .playConsoleHeader:hover {
1933
- background: rgba(255, 255, 255, 0.04);
1934
- }
1935
- .playConsoleToggle {
1936
- display: flex;
1937
- align-items: center;
1938
- gap: 8px;
1939
- }
1940
- .playConsoleCaret {
1941
- display: inline-flex;
1942
- font-size: 11px;
1943
- color: #8a8a8a;
1944
- }
1945
- .playConsoleTitle {
1946
- font-size: 11px;
1947
- font-weight: 700;
1948
- letter-spacing: 1px;
1949
- text-transform: uppercase;
1950
- color: #b3b3b3;
1951
- }
1952
- .playConsoleActions {
1953
- display: flex;
1954
- align-items: center;
1955
- gap: 6px;
1956
- }
1957
- .playConsoleBtn {
1958
- display: inline-flex;
1959
- align-items: center;
1960
- justify-content: center;
1961
- width: 30px;
1962
- height: 28px;
1963
- padding: 0;
1964
- font-size: 13px;
1965
- color: #cfcfcf;
1966
- background: transparent;
1967
- border: 1px solid rgba(255, 255, 255, 0.14);
1968
- border-radius: 6px;
1969
- cursor: pointer;
1970
- }
1971
- .playConsoleBtn:hover {
1972
- background: rgba(255, 255, 255, 0.08);
1973
- color: #fff;
1974
- }
1975
- .playConsoleBtn:disabled {
1976
- opacity: 0.38;
1977
- cursor: default;
1978
- }
1979
- .playConsoleBtn:disabled:hover {
1980
- background: transparent;
1981
- color: #cfcfcf;
1982
- }
1983
- .playConsoleBody {
1984
- border-top: 1px solid rgba(255, 255, 255, 0.08);
1985
- height: 165px;
1986
- overflow-y: auto;
1987
- padding: 8px 10px;
1988
- font-size: 12px;
1989
- line-height: 1.5;
1990
- }
1991
- .playConsoleEmpty {
1992
- color: #6a6a6a;
1993
- }
1994
- .playConsoleLine {
1995
- white-space: pre-wrap;
1996
- word-break: break-word;
1997
- color: #c8c8c8;
1998
- }
1999
- .playConsoleLineWarn {
2000
- color: #e0af68;
2001
- }
2002
- .playConsoleLineError {
2003
- color: #ff7a93;
2004
- }
2005
-
2006
- /* While the logs drawer is mounted, scale the deck card to fit ABOVE the drawer
2007
- (rather than sitting behind it) so play stays fully visible and interactive.
2008
- The SDK's initPlayCard() pins `#root > *` as a viewport-centered card with
2009
- !important; this higher-specificity (attribute + id) rule overrides its size
2010
- and vertical offset. The three custom properties are driven from JS in
2011
- PlayConsole using the live drawer height + the SDK's natural card size. */
2012
- :global(html[data-castle-logs] #root > *) {
2013
- width: var(--castle-logs-card-w, var(--castle-card-w, 100vw)) !important;
2014
- height: var(--castle-logs-card-h, var(--castle-card-h, 100vh)) !important;
2015
- transform: translate(-50%, calc(-50% - var(--castle-logs-shift, 0px))) !important;
2016
- transition:
2017
- transform 0.18s ease,
2018
- width 0.18s ease,
2019
- height 0.18s ease;
2020
- }
@@ -1,27 +1,23 @@
1
1
  import React from 'react';
2
2
  import { createRoot } from 'react-dom/client';
3
3
  import { isEdit, setup } from 'castle-web-sdk';
4
- import { App } from './editors/App';
5
4
  import { PlayOnly } from './editors/PlayOnly';
6
5
  import { ErrorBoundary } from './editors/ErrorBoundary';
7
- import { FileList, SingleEditor } from './editors/SingleEditor';
6
+ import { SingleEditor } from './editors/SingleEditor';
8
7
  document.body.dataset.castleScreenshotTarget = 'viewport';
9
8
  setup();
10
9
  const root = document.getElementById('root');
11
10
  if (!root) throw new Error('Missing root element');
12
- // Route on URL params so the shell can mount each piece in its own panel iframe:
11
+ // Route on URL params so the shell can mount each piece in its own panel iframe.
12
+ // File browsing and the code/text editor are now builtin shell panels, so the
13
+ // kit only renders the deck (play) and its rich per-file editors (scene/pxart):
13
14
  // (no edit / ?edit=0) -> play the deck (playtest panel)
14
- // ?panel=files -> the file list (asks the shell to open files)
15
- // ?file=<path>[&editor=<id>] -> a single editor for that file
16
- // (edit, no params) -> the combined editor app (legacy fallback)
15
+ // ?file=<path>[&editor=<id>] -> a single rich editor for that file
17
16
  const params = new URLSearchParams(window.location.search);
18
17
  function pick() {
19
18
  if (!isEdit()) return <PlayOnly />;
20
- if (params.get('panel') === 'files') {
21
- return <FileList selectedPath={params.get('file') ?? ''} />;
22
- }
23
19
  const file = params.get('file');
24
20
  if (file) return <SingleEditor path={file} editor={params.get('editor') ?? undefined} />;
25
- return <App />;
21
+ return <PlayOnly />;
26
22
  }
27
23
  createRoot(root).render(<ErrorBoundary>{pick()}</ErrorBoundary>);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "castle-web-cli",
3
- "version": "0.4.75",
3
+ "version": "0.4.77",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "castle-web": "./dist/index.js"
@@ -23,15 +23,23 @@
23
23
  ]
24
24
  },
25
25
  "dependencies": {
26
+ "@codemirror/lang-javascript": "^6.2.5",
27
+ "@codemirror/language": "^6.12.3",
28
+ "@codemirror/state": "^6.6.0",
29
+ "@codemirror/view": "^6.41.1",
30
+ "@fortawesome/free-solid-svg-icons": "^5.15.4",
31
+ "@lezer/highlight": "^1.2.3",
26
32
  "@lydell/node-pty": "^1.2.0-beta.12",
27
33
  "@xterm/addon-fit": "^0.11.0",
28
34
  "@xterm/addon-serialize": "^0.14.0",
29
35
  "@xterm/headless": "^6.0.0",
30
36
  "@xterm/xterm": "^6.0.0",
37
+ "codemirror": "^6.0.2",
31
38
  "dockview": "^4.13.1",
32
39
  "marked": "^18.0.5",
33
40
  "nanoid": "^5.1.7",
34
41
  "open": "^10.0.0",
42
+ "picomatch": "^4.0.4",
35
43
  "react": "^18.3.1",
36
44
  "react-dom": "^18.3.1",
37
45
  "vite": "^8.0.3",
@@ -40,6 +48,7 @@
40
48
  },
41
49
  "devDependencies": {
42
50
  "@types/node": "^20.0.0",
51
+ "@types/picomatch": "^4.0.2",
43
52
  "@types/react": "^18.3.31",
44
53
  "@types/react-dom": "^18.3.7",
45
54
  "@types/ws": "^8.18.1",