@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.
- package/README.md +76 -0
- package/apps/explorer/index.html +12 -0
- package/apps/explorer/package.json +28 -0
- package/apps/explorer/scripts/js-source.mjs +188 -0
- package/apps/explorer/scripts/patch-lib.d.ts +115 -0
- package/apps/explorer/scripts/patch-lib.mjs +472 -0
- package/apps/explorer/scripts/scan-target.mjs +188 -0
- package/apps/explorer/scripts/session-store.d.ts +156 -0
- package/apps/explorer/scripts/session-store.mjs +809 -0
- package/apps/explorer/scripts/target-config.d.ts +8 -0
- package/apps/explorer/scripts/target-config.mjs +42 -0
- package/apps/explorer/src/App.tsx +941 -0
- package/apps/explorer/src/agentIntent.ts +182 -0
- package/apps/explorer/src/codebase.ts +15 -0
- package/apps/explorer/src/index.css +632 -0
- package/apps/explorer/src/layout.ts +508 -0
- package/apps/explorer/src/main.tsx +16 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +87 -0
- package/apps/explorer/src/scene/Bridge.tsx +290 -0
- package/apps/explorer/src/scene/FileBlock.tsx +256 -0
- package/apps/explorer/src/scene/FolderArea.tsx +96 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +40 -0
- package/apps/explorer/src/scene/MapSelectBorder.tsx +57 -0
- package/apps/explorer/src/scene/MapView.tsx +247 -0
- package/apps/explorer/src/scene/Player.tsx +245 -0
- package/apps/explorer/src/scene/RelationLines.tsx +223 -0
- package/apps/explorer/src/scene/SelectionController.tsx +89 -0
- package/apps/explorer/src/scene/UserContextTracker.tsx +152 -0
- package/apps/explorer/src/scene/World.tsx +323 -0
- package/apps/explorer/src/theme.ts +111 -0
- package/apps/explorer/src/types.ts +245 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +38 -0
- package/apps/explorer/src/ui/HUD.tsx +1090 -0
- package/apps/explorer/src/ui/NameInput.tsx +45 -0
- package/apps/explorer/src/userContext.ts +73 -0
- package/apps/explorer/src/userCreated.ts +354 -0
- package/apps/explorer/src/vite-env.d.ts +1 -0
- package/apps/explorer/tsconfig.json +21 -0
- package/apps/explorer/vite.config.ts +295 -0
- package/bin/inbase.mjs +170 -0
- package/bin/project.mjs +94 -0
- package/bin/session.mjs +241 -0
- package/package.json +63 -0
- 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
|
+
}
|