@jkwd/inbase 0.1.0

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 (44) hide show
  1. package/README.md +76 -0
  2. package/apps/explorer/index.html +12 -0
  3. package/apps/explorer/package.json +28 -0
  4. package/apps/explorer/scripts/js-source.mjs +188 -0
  5. package/apps/explorer/scripts/patch-lib.d.ts +115 -0
  6. package/apps/explorer/scripts/patch-lib.mjs +472 -0
  7. package/apps/explorer/scripts/scan-target.mjs +188 -0
  8. package/apps/explorer/scripts/session-store.d.ts +156 -0
  9. package/apps/explorer/scripts/session-store.mjs +809 -0
  10. package/apps/explorer/scripts/target-config.d.ts +8 -0
  11. package/apps/explorer/scripts/target-config.mjs +42 -0
  12. package/apps/explorer/src/App.tsx +941 -0
  13. package/apps/explorer/src/agentIntent.ts +182 -0
  14. package/apps/explorer/src/codebase.ts +15 -0
  15. package/apps/explorer/src/index.css +632 -0
  16. package/apps/explorer/src/layout.ts +508 -0
  17. package/apps/explorer/src/main.tsx +16 -0
  18. package/apps/explorer/src/scene/BlockPlacer.tsx +87 -0
  19. package/apps/explorer/src/scene/Bridge.tsx +290 -0
  20. package/apps/explorer/src/scene/FileBlock.tsx +256 -0
  21. package/apps/explorer/src/scene/FolderArea.tsx +96 -0
  22. package/apps/explorer/src/scene/IslandPlacer.tsx +40 -0
  23. package/apps/explorer/src/scene/MapSelectBorder.tsx +57 -0
  24. package/apps/explorer/src/scene/MapView.tsx +247 -0
  25. package/apps/explorer/src/scene/Player.tsx +245 -0
  26. package/apps/explorer/src/scene/RelationLines.tsx +223 -0
  27. package/apps/explorer/src/scene/SelectionController.tsx +89 -0
  28. package/apps/explorer/src/scene/UserContextTracker.tsx +152 -0
  29. package/apps/explorer/src/scene/World.tsx +323 -0
  30. package/apps/explorer/src/theme.ts +111 -0
  31. package/apps/explorer/src/types.ts +245 -0
  32. package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +38 -0
  33. package/apps/explorer/src/ui/HUD.tsx +1090 -0
  34. package/apps/explorer/src/ui/NameInput.tsx +45 -0
  35. package/apps/explorer/src/userContext.ts +73 -0
  36. package/apps/explorer/src/userCreated.ts +354 -0
  37. package/apps/explorer/src/vite-env.d.ts +1 -0
  38. package/apps/explorer/tsconfig.json +21 -0
  39. package/apps/explorer/vite.config.ts +295 -0
  40. package/bin/inbase.mjs +170 -0
  41. package/bin/project.mjs +94 -0
  42. package/bin/session.mjs +241 -0
  43. package/package.json +63 -0
  44. package/skill/inbase/SKILL.md +167 -0
@@ -0,0 +1,323 @@
1
+ import { FolderArea } from './FolderArea'
2
+ import { FileBlock } from './FileBlock'
3
+ import { Bridge } from './Bridge'
4
+ import { RelationLines } from './RelationLines'
5
+ import { Player } from './Player'
6
+ import { MapView } from './MapView'
7
+ import { SelectionController } from './SelectionController'
8
+ import { UserContextTracker } from './UserContextTracker'
9
+ import { BlockPlacer } from './BlockPlacer'
10
+ import { IslandPlacer } from './IslandPlacer'
11
+ import { folderOfFile, filesImporting } from '../layout'
12
+ import { EDITOR_GREY } from '../theme'
13
+ import type { ChangeKind } from '../theme'
14
+ import type {
15
+ CodebaseGraph,
16
+ FileNode,
17
+ FlyTo,
18
+ AimedRelation,
19
+ PatchImport,
20
+ PlacedFile,
21
+ UserContext,
22
+ UserCreatedBlock,
23
+ UserCreatedIsland,
24
+ ViewMode,
25
+ WorldLayout,
26
+ } from '../types'
27
+
28
+ type WorldProps = {
29
+ graph: CodebaseGraph
30
+ layout: WorldLayout
31
+ mode: ViewMode
32
+ landAt: [number, number]
33
+ selectedId: string | null
34
+ selectedFolder?: string | null
35
+ locked: boolean
36
+ onSelect: (fileId: string | null) => void
37
+ onSelectFolder: (folderPath: string | null) => void
38
+ onLockedChange: (locked: boolean) => void
39
+ onFolderChange: (label: string) => void
40
+ onLand: (x: number, z: number) => void
41
+ onWalkPosition: (x: number, z: number) => void
42
+ onContext: (context: UserContext) => void
43
+ plannedIds: string[]
44
+ previewFiles: Record<string, PlacedFile>
45
+ plannedImports: PatchImport[]
46
+ createdIds: string[]
47
+ deletedIds?: string[]
48
+ createLines: Record<string, number>
49
+ flyTo: FlyTo | null
50
+ aimedRelation: AimedRelation | null
51
+ onAimRelation: (aim: AimedRelation | null) => void
52
+ onTravelTo: (fromId: string, toId: string) => void
53
+ importedBy?: boolean
54
+ namingId?: string | null
55
+ onPlaceBlock?: (spot: { x: number; z: number; folder: string }) => void
56
+ onPlaceIsland?: (parent: string) => void
57
+ onCommitName?: (id: string, name: string) => boolean
58
+ onCancelName?: (id: string) => void
59
+ userCreatedBlocks?: UserCreatedBlock[]
60
+ userCreatedIslands?: UserCreatedIsland[]
61
+ namingIslandId?: string | null
62
+ }
63
+
64
+ export function World({
65
+ graph,
66
+ layout,
67
+ mode,
68
+ landAt,
69
+ selectedId,
70
+ selectedFolder = null,
71
+ locked,
72
+ onSelect,
73
+ onSelectFolder,
74
+ onLockedChange,
75
+ onFolderChange,
76
+ onLand,
77
+ onWalkPosition,
78
+ onContext,
79
+ plannedIds,
80
+ previewFiles,
81
+ plannedImports,
82
+ createdIds,
83
+ deletedIds = [],
84
+ createLines,
85
+ flyTo,
86
+ aimedRelation,
87
+ onAimRelation,
88
+ onTravelTo,
89
+ importedBy = false,
90
+ namingId = null,
91
+ namingIslandId = null,
92
+ onPlaceBlock,
93
+ onPlaceIsland,
94
+ onCommitName,
95
+ onCancelName,
96
+ userCreatedBlocks = [],
97
+ userCreatedIslands = [],
98
+ }: WorldProps) {
99
+ const created = new Set(createdIds)
100
+ const deleted = new Set(deletedIds)
101
+ const mapping = mode === 'map'
102
+ const placing = Boolean(namingId || namingIslandId)
103
+ const planned = new Set(plannedIds)
104
+ const ghosts = previewFiles
105
+ const related = new Set(
106
+ selectedId
107
+ ? importedBy
108
+ ? filesImporting(graph.files, selectedId).map((file) => file.id)
109
+ : (graph.files.find((file) => file.id === selectedId)?.imports ?? [])
110
+ : [],
111
+ )
112
+ const patchLinked = new Set<string>()
113
+ for (const edge of plannedImports) {
114
+ patchLinked.add(edge.from)
115
+ patchLinked.add(edge.to)
116
+ if (!planned.has(edge.to) && !deleted.has(edge.to)) related.add(edge.to)
117
+ if (!planned.has(edge.from) && !deleted.has(edge.from)) related.add(edge.from)
118
+ }
119
+ const folderFileIds = new Set(
120
+ selectedFolder
121
+ ? (graph.folders.find((folder) => folder.path === selectedFolder)?.files ??
122
+ [])
123
+ : [],
124
+ )
125
+ const hasFocus =
126
+ Boolean(selectedId) ||
127
+ Boolean(selectedFolder) ||
128
+ planned.size > 0 ||
129
+ deleted.size > 0
130
+ const highlightedFolders: Partial<Record<string, ChangeKind>> = {}
131
+ const folderKinds = new Map<string, Set<ChangeKind>>()
132
+ const addFolderKind = (id: string, kind: ChangeKind) => {
133
+ const folder = folderOfFile(id)
134
+ const kinds = folderKinds.get(folder) ?? new Set<ChangeKind>()
135
+ kinds.add(kind)
136
+ folderKinds.set(folder, kinds)
137
+ }
138
+ for (const id of planned) {
139
+ addFolderKind(id, created.has(id) ? 'add' : 'edit')
140
+ }
141
+ for (const id of deleted) addFolderKind(id, 'remove')
142
+ for (const [folder, kinds] of folderKinds) {
143
+ if (kinds.size === 1) highlightedFolders[folder] = [...kinds][0]
144
+ }
145
+ if (planned.size > 0 || deleted.size > 0) {
146
+ for (const folder of Object.values(layout.folders)) {
147
+ if (!folder.added) continue
148
+ const kinds = folderKinds.get(folder.path)
149
+ if (!kinds || kinds.size === 1) {
150
+ highlightedFolders[folder.path] ??= 'add'
151
+ }
152
+ }
153
+ }
154
+
155
+ const changeKindOf = (id: string): ChangeKind | null => {
156
+ if (deleted.has(id)) return 'remove'
157
+ if (created.has(id)) return 'add'
158
+ if (planned.has(id)) return 'edit'
159
+ return null
160
+ }
161
+
162
+ return (
163
+ <>
164
+ <color attach="background" args={[EDITOR_GREY.editor]} />
165
+ {!mapping && <fog attach="fog" args={[EDITOR_GREY.editor, 38, 160]} />}
166
+ <hemisphereLight args={['#d7e2ee', '#2a3038', mapping ? 1.1 : 0.85]} />
167
+ <directionalLight
168
+ position={mapping ? [8, 60, 8] : [12, 22, 8]}
169
+ intensity={mapping ? 1.35 : 0.55}
170
+ />
171
+ <ambientLight intensity={mapping ? 0.7 : 0.42} />
172
+ <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.06, 40]}>
173
+ <planeGeometry args={[400, 400]} />
174
+ <meshBasicMaterial color={EDITOR_GREY.chrome} />
175
+ </mesh>
176
+ <MapView
177
+ layout={layout}
178
+ enabled={mapping}
179
+ marker={landAt}
180
+ highlightedFolders={highlightedFolders}
181
+ selectedFolder={selectedFolder}
182
+ onLand={onLand}
183
+ onSelect={onSelect}
184
+ onSelectFolder={onSelectFolder}
185
+ onTravelTo={onTravelTo}
186
+ />
187
+
188
+ {Object.values(layout.folders).map((folder) => (
189
+ <FolderArea
190
+ key={folder.path}
191
+ folder={folder}
192
+ naming={folder.path === namingIslandId}
193
+ selected={folder.path === selectedFolder}
194
+ highlightKind={highlightedFolders[folder.path] ?? null}
195
+ />
196
+ ))}
197
+ {layout.bridges.map((bridge) => (
198
+ <Bridge key={bridge.id} bridge={bridge} />
199
+ ))}
200
+ {graph.files.map((file) => {
201
+ const placed = layout.files[file.id]
202
+ if (!placed) return null
203
+ const selected = file.id === selectedId
204
+ const isRelated = related.has(file.id)
205
+ const isPlanned = planned.has(file.id) || deleted.has(file.id)
206
+ const changeKind = changeKindOf(file.id)
207
+ const naming = file.id === namingId
208
+ return (
209
+ <FileBlock
210
+ key={file.id}
211
+ file={file}
212
+ placed={placed}
213
+ selected={selected}
214
+ related={isRelated}
215
+ planned={isPlanned}
216
+ changeKind={changeKind}
217
+ added={created.has(file.id) || file.userCreated}
218
+ aimed={file.id === aimedRelation?.flyTo}
219
+ dimmed={
220
+ hasFocus &&
221
+ !selected &&
222
+ !isRelated &&
223
+ !changeKind &&
224
+ !patchLinked.has(file.id) &&
225
+ !folderFileIds.has(file.id)
226
+ }
227
+ naming={naming}
228
+ mapMode={mapping}
229
+ onCommitName={
230
+ naming && onCommitName
231
+ ? (name) => {
232
+ onCommitName(file.id, name)
233
+ }
234
+ : undefined
235
+ }
236
+ onCancelName={
237
+ naming && onCancelName ? () => onCancelName(file.id) : undefined
238
+ }
239
+ />
240
+ )
241
+ })}
242
+ {Object.values(ghosts).map((placed) => {
243
+ const file: FileNode = {
244
+ id: placed.id,
245
+ name: placed.id.split('/').pop() ?? placed.id,
246
+ path: placed.id,
247
+ folder: folderOfFile(placed.id),
248
+ lines: createLines[placed.id] ?? 12,
249
+ language: placed.id.split('.').pop()?.toLowerCase() ?? 'txt',
250
+ symbols: [],
251
+ imports: plannedImports
252
+ .filter((edge) => edge.from === placed.id)
253
+ .map((edge) => edge.to),
254
+ }
255
+ return (
256
+ <FileBlock
257
+ key={`add:${file.id}`}
258
+ file={file}
259
+ placed={placed}
260
+ selected={file.id === selectedId}
261
+ related={false}
262
+ planned
263
+ changeKind="add"
264
+ added
265
+ dimmed={false}
266
+ mapMode={mapping}
267
+ />
268
+ )
269
+ })}
270
+ <RelationLines
271
+ selectedId={selectedId}
272
+ aimedRelation={aimedRelation}
273
+ files={graph.files}
274
+ layout={layout}
275
+ extras={ghosts}
276
+ plannedEdges={plannedImports}
277
+ fromAbove={mapping}
278
+ importedBy={importedBy}
279
+ />
280
+ <Player
281
+ layout={layout}
282
+ mode={mode}
283
+ landAt={landAt}
284
+ locked={locked}
285
+ lockEnabled={!placing}
286
+ onLockedChange={onLockedChange}
287
+ onFolderChange={onFolderChange}
288
+ onWalkPosition={onWalkPosition}
289
+ flyTo={flyTo}
290
+ />
291
+ {onPlaceBlock && (
292
+ <BlockPlacer
293
+ enabled={!mapping && !placing}
294
+ layout={layout}
295
+ onPlace={onPlaceBlock}
296
+ />
297
+ )}
298
+ {onPlaceIsland && (
299
+ <IslandPlacer
300
+ enabled={!mapping && !placing}
301
+ layout={layout}
302
+ onPlace={onPlaceIsland}
303
+ />
304
+ )}
305
+ <SelectionController
306
+ locked={locked && !mapping}
307
+ onSelect={onSelect}
308
+ onAimRelation={onAimRelation}
309
+ onTravelTo={onTravelTo}
310
+ files={{ ...layout.files, ...ghosts }}
311
+ />
312
+ <UserContextTracker
313
+ graph={graph}
314
+ layout={layout}
315
+ mode={mode}
316
+ selectedId={selectedId}
317
+ userCreatedBlocks={userCreatedBlocks}
318
+ userCreatedIslands={userCreatedIslands}
319
+ onContext={onContext}
320
+ />
321
+ </>
322
+ )
323
+ }
@@ -0,0 +1,111 @@
1
+ export const CONFIG = {
2
+ fileWidth: 2.4,
3
+ fileDepth: 2.4,
4
+ heightPerLine: 0.09,
5
+ minHeight: 0.9,
6
+ maxHeight: 12,
7
+ aisleWidth: 8,
8
+ fileSpacing: 5,
9
+ areaPadding: 6,
10
+ bridgeLength: 22,
11
+ bridgeWidth: 3.4,
12
+ bridgeOutset: 5.6,
13
+ bridgeOverlap: 1.15,
14
+ siblingGap: 3.2,
15
+ bridgeDockGap: 1.6,
16
+ eyeHeight: 1.7,
17
+ walkSpeed: 9,
18
+ sprintSpeed: 16,
19
+ }
20
+
21
+ /** Greys mirror the Cursor Dark Midnight editor theme. Keep in sync with index.css. */
22
+ export const EDITOR_GREY = {
23
+ chrome: '#191c22',
24
+ editor: '#1e2127',
25
+ surface: '#272c36',
26
+ }
27
+
28
+ export type ChangeKind = 'add' | 'edit' | 'remove'
29
+
30
+ export const CHANGE_HIGHLIGHT: Record<
31
+ ChangeKind,
32
+ { color: string; emissive: string; floor: string; aisle: string }
33
+ > = {
34
+ add: {
35
+ color: '#22ff66',
36
+ emissive: '#00e84a',
37
+ floor: '#08351c',
38
+ aisle: '#0f5a2e',
39
+ },
40
+ edit: {
41
+ color: '#2f8cff',
42
+ emissive: '#0066ff',
43
+ floor: '#062448',
44
+ aisle: '#0d3d78',
45
+ },
46
+ remove: {
47
+ color: '#ff2d4a',
48
+ emissive: '#ff1038',
49
+ floor: '#3a0810',
50
+ aisle: '#5c101c',
51
+ },
52
+ }
53
+
54
+ export const MAP_SELECTION = {
55
+ color: '#000000',
56
+ islandPad: 0.38,
57
+ blockPad: 0.2,
58
+ }
59
+
60
+ export function fileHeight(lines: number) {
61
+ return Math.min(
62
+ CONFIG.maxHeight,
63
+ Math.max(CONFIG.minHeight, lines * CONFIG.heightPerLine),
64
+ )
65
+ }
66
+
67
+ export function fileColor(language: string) {
68
+ switch (language) {
69
+ case 'tsx':
70
+ return '#3f6f9a'
71
+ case 'ts':
72
+ return '#2f6d68'
73
+ case 'jsx':
74
+ return '#7d6aa3'
75
+ case 'js':
76
+ case 'mjs':
77
+ case 'cjs':
78
+ return '#6a5f8f'
79
+ case 'css':
80
+ case 'scss':
81
+ return '#8a5b33'
82
+ case 'json':
83
+ return '#7a6a38'
84
+ case 'html':
85
+ return '#6d4e38'
86
+ default:
87
+ return '#4a5160'
88
+ }
89
+ }
90
+
91
+ export function dimColor(hex: string, amount = 0.32) {
92
+ const value = hex.replace('#', '')
93
+ if (value.length !== 6) return hex
94
+ const mix = Math.max(0, Math.min(1, amount))
95
+ const channel = (start: number) => {
96
+ const n = parseInt(value.slice(start, start + 2), 16)
97
+ return Math.round(n * (1 - mix))
98
+ .toString(16)
99
+ .padStart(2, '0')
100
+ }
101
+ return `#${channel(0)}${channel(2)}${channel(4)}`
102
+ }
103
+
104
+ export function folderFloorColor(path: string) {
105
+ let hash = 0
106
+ for (let i = 0; i < path.length; i += 1) {
107
+ hash = path.charCodeAt(i) + ((hash << 5) - hash)
108
+ }
109
+ const hue = Math.abs(hash) % 360
110
+ return `hsl(${hue}, 16%, 18%)`
111
+ }
@@ -0,0 +1,245 @@
1
+ export type SymbolKind = 'function' | 'variable' | 'class'
2
+
3
+ export type CodeSymbol = {
4
+ name: string
5
+ kind: SymbolKind
6
+ intended?: boolean
7
+ }
8
+
9
+ export type FileNode = {
10
+ id: string
11
+ name: string
12
+ path: string
13
+ folder: string
14
+ lines: number
15
+ language: string
16
+ symbols: CodeSymbol[]
17
+ imports: string[]
18
+ userCreated?: boolean
19
+ }
20
+
21
+ export type FolderNode = {
22
+ path: string
23
+ name: string
24
+ parent: string | null
25
+ files: string[]
26
+ children: string[]
27
+ userCreated?: boolean
28
+ }
29
+
30
+ export type CodebaseGraph = {
31
+ root: string
32
+ targetName: string
33
+ files: FileNode[]
34
+ folders: FolderNode[]
35
+ }
36
+
37
+ export type PlacedFile = {
38
+ id: string
39
+ position: [number, number, number]
40
+ size: [number, number, number]
41
+ aisleFace: 1 | -1
42
+ }
43
+
44
+ export type PlacedFolder = {
45
+ path: string
46
+ name: string
47
+ x: number
48
+ z: number
49
+ width: number
50
+ depth: number
51
+ added?: boolean
52
+ }
53
+
54
+ export type PlacedBridge = {
55
+ id: string
56
+ label: string
57
+ fromLabel: string
58
+ points: [number, number][]
59
+ }
60
+
61
+ export type WorldLayout = {
62
+ files: Record<string, PlacedFile>
63
+ folders: Record<string, PlacedFolder>
64
+ bridges: PlacedBridge[]
65
+ spawn: [number, number, number]
66
+ }
67
+
68
+ export type ViewMode = 'map' | 'walk'
69
+
70
+ export type FlyTo = {
71
+ nonce: number
72
+ lookAt: [number, number, number]
73
+ }
74
+
75
+ export type UserFileRef = {
76
+ id: string
77
+ name: string
78
+ path: string
79
+ folder: string
80
+ }
81
+
82
+ export type UserCreatedBlock = {
83
+ id: string
84
+ name: string
85
+ path: string
86
+ folder: string
87
+ x: number
88
+ z: number
89
+ naming?: boolean
90
+ }
91
+
92
+ export type UserCreatedIsland = {
93
+ id: string
94
+ name: string
95
+ path: string
96
+ parent: string
97
+ naming?: boolean
98
+ }
99
+
100
+ export type UserContext = {
101
+ updatedAt: string | null
102
+ mode: ViewMode
103
+ island: {
104
+ path: string | null
105
+ name: string
106
+ }
107
+ lookingAt: UserFileRef | null
108
+ lookingAtFiles: UserFileRef[]
109
+ selected: UserFileRef | null
110
+ filesOnIsland: UserFileRef[]
111
+ position: {
112
+ x: number
113
+ z: number
114
+ }
115
+ followLook?: boolean
116
+ userCreatedBlocks?: UserCreatedBlock[]
117
+ userCreatedIslands?: UserCreatedIsland[]
118
+ }
119
+
120
+ export type AgentIntentStatus =
121
+ | 'idle'
122
+ | 'blueprint_ask'
123
+ | 'blueprint'
124
+ | 'preparing'
125
+ | 'planned'
126
+ | 'working'
127
+ | 'replanning'
128
+ | 'pending'
129
+ | 'approved'
130
+ | 'extend'
131
+ | 'extended'
132
+ | 'finished'
133
+ | 'rejected'
134
+
135
+ export function isPatchPreview(status: AgentIntentStatus) {
136
+ return status === 'pending' || status === 'extend' || status === 'extended'
137
+ }
138
+
139
+ export function isReviewingIntent(status: AgentIntentStatus) {
140
+ return (
141
+ status === 'blueprint_ask' ||
142
+ status === 'blueprint' ||
143
+ status === 'preparing' ||
144
+ status === 'planned' ||
145
+ status === 'working' ||
146
+ status === 'replanning' ||
147
+ status === 'pending' ||
148
+ status === 'approved' ||
149
+ status === 'extend' ||
150
+ status === 'extended' ||
151
+ status === 'finished'
152
+ )
153
+ }
154
+
155
+ export type PlanStep = {
156
+ index: number
157
+ title: string
158
+ }
159
+
160
+ export type PatchImport = {
161
+ from: string
162
+ to: string
163
+ }
164
+
165
+ export type PatchSymbolAddition = {
166
+ name: string
167
+ file: string
168
+ }
169
+
170
+ export type PatchImportAddition = {
171
+ name: string
172
+ from: string
173
+ file: string
174
+ }
175
+
176
+ export type AimedRelation = {
177
+ from: string
178
+ to: string
179
+ flyTo: string
180
+ }
181
+
182
+ export type DiffChainEntry = {
183
+ id: string
184
+ index: number
185
+ step: number
186
+ title: string
187
+ status: 'pending' | 'extend' | 'extended' | 'applied' | 'rejected'
188
+ }
189
+
190
+ export type WorkflowPhase =
191
+ | 'blueprint_ask'
192
+ | 'blueprint'
193
+ | 'preparing'
194
+ | 'plan_ready'
195
+ | 'working'
196
+ | 'review'
197
+ | 'replanning'
198
+ | 'finished'
199
+ | 'stopped'
200
+
201
+ export type WorkflowAction =
202
+ | 'invoke'
203
+ | 'continue'
204
+ | 'instruct'
205
+ | 'stop'
206
+ | 'blueprint_yes'
207
+ | 'blueprint_no'
208
+ | 'blueprint_send'
209
+ | 'blueprint_update'
210
+
211
+ export type AgentIntent = {
212
+ updatedAt: string | null
213
+ showMap: boolean
214
+ status: AgentIntentStatus
215
+ feature: string | null
216
+ steps: PlanStep[]
217
+ step: number | null
218
+ files: string[]
219
+ creates: string[]
220
+ deletes: string[]
221
+ createFolders: string[]
222
+ createLines: Record<string, number>
223
+ imports: PatchImport[]
224
+ addedFunctions: PatchSymbolAddition[]
225
+ addedVariables: PatchSymbolAddition[]
226
+ addedImports: PatchImportAddition[]
227
+ reason: string | null
228
+ sessionId: string | null
229
+ diffId: string | null
230
+ parentDiffId: string | null
231
+ chainIndex: number | null
232
+ chain: DiffChainEntry[]
233
+ isActiveDiff: boolean
234
+ preview: boolean
235
+ phase: WorkflowPhase | null
236
+ working: boolean
237
+ creationMode: boolean
238
+ canEnterBlueprint: boolean
239
+ blueprintSessionId: string | null
240
+ userCreatedBlocks: UserCreatedBlock[]
241
+ userCreatedIslands: UserCreatedIsland[]
242
+ blueprintFunctions: PatchSymbolAddition[]
243
+ blueprintVariables: PatchSymbolAddition[]
244
+ blueprintImports: PatchImportAddition[]
245
+ }
@@ -0,0 +1,38 @@
1
+ import { Component, type ReactNode } from 'react'
2
+
3
+ type Props = {
4
+ children: ReactNode
5
+ }
6
+
7
+ type State = {
8
+ failed: boolean
9
+ }
10
+
11
+ export class CanvasErrorBoundary extends Component<Props, State> {
12
+ state: State = { failed: false }
13
+
14
+ static getDerivedStateFromError() {
15
+ return { failed: true }
16
+ }
17
+
18
+ render() {
19
+ if (this.state.failed) {
20
+ return (
21
+ <div className="canvas-error">
22
+ <div className="hud-gate-card">
23
+ <h1>Visualizer stopped</h1>
24
+ <p>The 3D view hit an error. Reload the page to restore it.</p>
25
+ <button
26
+ className="hud-button"
27
+ type="button"
28
+ onClick={() => window.location.reload()}
29
+ >
30
+ Reload
31
+ </button>
32
+ </div>
33
+ </div>
34
+ )
35
+ }
36
+ return this.props.children
37
+ }
38
+ }