@jkwd/inbase 0.1.3 → 0.1.5
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 +3 -1
- package/apps/explorer/scripts/open-editor.d.ts +6 -0
- package/apps/explorer/scripts/open-editor.mjs +219 -0
- package/apps/explorer/scripts/session-store.d.ts +51 -1
- package/apps/explorer/scripts/session-store.mjs +461 -48
- package/apps/explorer/src/App.tsx +178 -78
- package/apps/explorer/src/agentIntent.ts +54 -1
- package/apps/explorer/src/index.css +238 -2
- package/apps/explorer/src/layout.ts +46 -0
- package/apps/explorer/src/scene/FileBlock.tsx +110 -145
- package/apps/explorer/src/scene/FolderArea.tsx +17 -4
- package/apps/explorer/src/scene/MapSelectBorder.tsx +3 -1
- package/apps/explorer/src/scene/MapView.tsx +41 -18
- package/apps/explorer/src/scene/Player.tsx +27 -0
- package/apps/explorer/src/scene/RelationLines.tsx +36 -32
- package/apps/explorer/src/scene/SelectionController.tsx +21 -3
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +451 -0
- package/apps/explorer/src/scene/World.tsx +19 -50
- package/apps/explorer/src/theme.ts +10 -1
- package/apps/explorer/src/types.ts +12 -0
- package/apps/explorer/src/ui/HUD.tsx +699 -390
- package/apps/explorer/vite.config.ts +72 -22
- package/bin/session.mjs +17 -3
- package/package.json +3 -1
- package/skill/inbase/SKILL.md +22 -14
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import { Canvas, useFrame, useThree } from '@react-three/fiber'
|
|
3
|
+
import {
|
|
4
|
+
fileChangeKind,
|
|
5
|
+
filesImporting,
|
|
6
|
+
folderAt,
|
|
7
|
+
folderChangeHighlights,
|
|
8
|
+
folderOfFile,
|
|
9
|
+
} from '../layout'
|
|
10
|
+
import { CONFIG, WORLD_VOID, type ChangeKind } from '../theme'
|
|
11
|
+
import type { CodebaseGraph, PlacedFile, PlacedFolder, WorldLayout } from '../types'
|
|
12
|
+
import { FileBlock } from './FileBlock'
|
|
13
|
+
import { FolderArea } from './FolderArea'
|
|
14
|
+
import { RelationLines } from './RelationLines'
|
|
15
|
+
|
|
16
|
+
type SelectionThumbnailProps = {
|
|
17
|
+
graph: CodebaseGraph
|
|
18
|
+
layout: WorldLayout
|
|
19
|
+
selectedId: string | null
|
|
20
|
+
selectedFolder: string | null
|
|
21
|
+
landAt: [number, number]
|
|
22
|
+
importedBy?: boolean
|
|
23
|
+
minimized?: boolean
|
|
24
|
+
plannedIds?: string[]
|
|
25
|
+
createdIds?: string[]
|
|
26
|
+
deletedIds?: string[]
|
|
27
|
+
onMinimize?: () => void
|
|
28
|
+
onHide: () => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ThumbnailTarget = {
|
|
32
|
+
folder: PlacedFolder
|
|
33
|
+
files: Array<{ file: CodebaseGraph['files'][number]; placed: PlacedFile }>
|
|
34
|
+
relatedIds: Set<string>
|
|
35
|
+
selectedFileId: string | null
|
|
36
|
+
camera: {
|
|
37
|
+
position: [number, number, number]
|
|
38
|
+
lookAt: [number, number, number]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function elevatedCamera(
|
|
43
|
+
lookAt: [number, number, number],
|
|
44
|
+
spanX: number,
|
|
45
|
+
spanY: number,
|
|
46
|
+
spanZ: number,
|
|
47
|
+
towardX: 1 | -1,
|
|
48
|
+
): ThumbnailTarget['camera'] {
|
|
49
|
+
const span = Math.max(spanX, spanZ, 4)
|
|
50
|
+
const distance = Math.max(12, span * 0.9, spanY * 1.8)
|
|
51
|
+
const height = Math.max(CONFIG.eyeHeight * 6, spanY + 8, distance * 0.7)
|
|
52
|
+
return {
|
|
53
|
+
position: [
|
|
54
|
+
lookAt[0] + towardX * distance * 0.55,
|
|
55
|
+
lookAt[1] + height,
|
|
56
|
+
lookAt[2] + distance * 0.72,
|
|
57
|
+
],
|
|
58
|
+
lookAt,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function filesOnFolder(
|
|
63
|
+
graph: CodebaseGraph,
|
|
64
|
+
layout: WorldLayout,
|
|
65
|
+
folderPath: string,
|
|
66
|
+
) {
|
|
67
|
+
return graph.files.flatMap((file) => {
|
|
68
|
+
if (file.folder !== folderPath && folderOfFile(file.id) !== folderPath) {
|
|
69
|
+
return []
|
|
70
|
+
}
|
|
71
|
+
const placed = layout.files[file.id]
|
|
72
|
+
return placed ? [{ file, placed }] : []
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveTarget(
|
|
77
|
+
graph: CodebaseGraph,
|
|
78
|
+
layout: WorldLayout,
|
|
79
|
+
selectedId: string | null,
|
|
80
|
+
selectedFolder: string | null,
|
|
81
|
+
landAt: [number, number],
|
|
82
|
+
importedBy: boolean,
|
|
83
|
+
): ThumbnailTarget | null {
|
|
84
|
+
const selectedFile = selectedId
|
|
85
|
+
? graph.files.find((file) => file.id === selectedId)
|
|
86
|
+
: undefined
|
|
87
|
+
const selectedPlaced = selectedFile
|
|
88
|
+
? layout.files[selectedFile.id]
|
|
89
|
+
: undefined
|
|
90
|
+
|
|
91
|
+
const folder =
|
|
92
|
+
(selectedFile
|
|
93
|
+
? layout.folders[selectedFile.folder] ??
|
|
94
|
+
layout.folders[folderOfFile(selectedFile.id)]
|
|
95
|
+
: undefined) ??
|
|
96
|
+
(selectedFolder ? layout.folders[selectedFolder] : undefined) ??
|
|
97
|
+
folderAt(landAt[0], landAt[1], layout) ??
|
|
98
|
+
Object.values(layout.folders)[0]
|
|
99
|
+
if (!folder) return null
|
|
100
|
+
|
|
101
|
+
const islandFiles = filesOnFolder(graph, layout, folder.path)
|
|
102
|
+
const relatedIds = new Set(
|
|
103
|
+
selectedFile
|
|
104
|
+
? importedBy
|
|
105
|
+
? filesImporting(graph.files, selectedFile.id).map((file) => file.id)
|
|
106
|
+
: selectedFile.imports
|
|
107
|
+
: [],
|
|
108
|
+
)
|
|
109
|
+
const extraFiles = graph.files.flatMap((file) => {
|
|
110
|
+
if (!relatedIds.has(file.id)) return []
|
|
111
|
+
if (islandFiles.some((entry) => entry.file.id === file.id)) return []
|
|
112
|
+
const placed = layout.files[file.id]
|
|
113
|
+
return placed ? [{ file, placed }] : []
|
|
114
|
+
})
|
|
115
|
+
const files = [...islandFiles, ...extraFiles]
|
|
116
|
+
|
|
117
|
+
if (selectedPlaced) {
|
|
118
|
+
const [width, height, depth] = selectedPlaced.size
|
|
119
|
+
return {
|
|
120
|
+
folder,
|
|
121
|
+
files,
|
|
122
|
+
relatedIds,
|
|
123
|
+
selectedFileId: selectedFile?.id ?? null,
|
|
124
|
+
camera: elevatedCamera(
|
|
125
|
+
[
|
|
126
|
+
selectedPlaced.position[0],
|
|
127
|
+
selectedPlaced.position[1],
|
|
128
|
+
selectedPlaced.position[2],
|
|
129
|
+
],
|
|
130
|
+
width,
|
|
131
|
+
height,
|
|
132
|
+
depth,
|
|
133
|
+
selectedPlaced.aisleFace,
|
|
134
|
+
),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const tallest = files.reduce(
|
|
139
|
+
(height, entry) => Math.max(height, entry.placed.size[1]),
|
|
140
|
+
CONFIG.minHeight,
|
|
141
|
+
)
|
|
142
|
+
return {
|
|
143
|
+
folder,
|
|
144
|
+
files,
|
|
145
|
+
relatedIds,
|
|
146
|
+
selectedFileId: null,
|
|
147
|
+
camera: elevatedCamera(
|
|
148
|
+
[folder.x, 1.2, folder.z + folder.depth / 2],
|
|
149
|
+
folder.width,
|
|
150
|
+
tallest,
|
|
151
|
+
folder.depth,
|
|
152
|
+
1,
|
|
153
|
+
),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const MIN_ZOOM = 0.55
|
|
158
|
+
const MAX_ZOOM = 3.4
|
|
159
|
+
|
|
160
|
+
function clampZoom(value: number) {
|
|
161
|
+
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, value))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function touchDistance(touches: TouchList) {
|
|
165
|
+
if (touches.length < 2) return 0
|
|
166
|
+
return Math.hypot(
|
|
167
|
+
touches[0].clientX - touches[1].clientX,
|
|
168
|
+
touches[0].clientY - touches[1].clientY,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function CameraRig({
|
|
173
|
+
position,
|
|
174
|
+
lookAt,
|
|
175
|
+
zoom,
|
|
176
|
+
}: {
|
|
177
|
+
position: [number, number, number]
|
|
178
|
+
lookAt: [number, number, number]
|
|
179
|
+
zoom: number
|
|
180
|
+
}) {
|
|
181
|
+
const { camera } = useThree()
|
|
182
|
+
const aim = () => {
|
|
183
|
+
const scale = 1 / zoom
|
|
184
|
+
camera.up.set(0, 1, 0)
|
|
185
|
+
camera.position.set(
|
|
186
|
+
lookAt[0] + (position[0] - lookAt[0]) * scale,
|
|
187
|
+
lookAt[1] + (position[1] - lookAt[1]) * scale,
|
|
188
|
+
lookAt[2] + (position[2] - lookAt[2]) * scale,
|
|
189
|
+
)
|
|
190
|
+
camera.lookAt(lookAt[0], lookAt[1], lookAt[2])
|
|
191
|
+
camera.updateProjectionMatrix()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
useLayoutEffect(aim, [camera, lookAt, position, zoom])
|
|
195
|
+
useFrame(aim)
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function ThumbnailScene({
|
|
200
|
+
graph,
|
|
201
|
+
layout,
|
|
202
|
+
importedBy,
|
|
203
|
+
target,
|
|
204
|
+
highlightedFolders,
|
|
205
|
+
planned,
|
|
206
|
+
created,
|
|
207
|
+
deleted,
|
|
208
|
+
zoom,
|
|
209
|
+
}: {
|
|
210
|
+
graph: CodebaseGraph
|
|
211
|
+
layout: WorldLayout
|
|
212
|
+
importedBy: boolean
|
|
213
|
+
target: ThumbnailTarget
|
|
214
|
+
highlightedFolders: Partial<Record<string, ChangeKind>>
|
|
215
|
+
planned: Set<string>
|
|
216
|
+
created: Set<string>
|
|
217
|
+
deleted: Set<string>
|
|
218
|
+
zoom: number
|
|
219
|
+
}) {
|
|
220
|
+
return (
|
|
221
|
+
<>
|
|
222
|
+
<color attach="background" args={[WORLD_VOID]} />
|
|
223
|
+
<hemisphereLight args={['#d7e2ee', '#2a3038', 1.1]} />
|
|
224
|
+
<directionalLight position={[8, 60, 8]} intensity={1.35} />
|
|
225
|
+
<ambientLight intensity={0.7} />
|
|
226
|
+
<pointLight
|
|
227
|
+
position={[
|
|
228
|
+
target.camera.position[0],
|
|
229
|
+
target.camera.position[1] - 1.4,
|
|
230
|
+
target.camera.position[2],
|
|
231
|
+
]}
|
|
232
|
+
color="#f4f1e8"
|
|
233
|
+
intensity={7}
|
|
234
|
+
distance={40}
|
|
235
|
+
decay={1.2}
|
|
236
|
+
/>
|
|
237
|
+
<CameraRig
|
|
238
|
+
position={target.camera.position}
|
|
239
|
+
lookAt={target.camera.lookAt}
|
|
240
|
+
zoom={zoom}
|
|
241
|
+
/>
|
|
242
|
+
<FolderArea
|
|
243
|
+
folder={target.folder}
|
|
244
|
+
highlightKind={highlightedFolders[target.folder.path] ?? null}
|
|
245
|
+
previewLabels
|
|
246
|
+
/>
|
|
247
|
+
{target.files.map(({ file, placed }) => {
|
|
248
|
+
const selected = file.id === target.selectedFileId
|
|
249
|
+
const related = target.relatedIds.has(file.id)
|
|
250
|
+
const changeKind = fileChangeKind(file.id, planned, created, deleted)
|
|
251
|
+
return (
|
|
252
|
+
<FileBlock
|
|
253
|
+
key={file.id}
|
|
254
|
+
file={file}
|
|
255
|
+
placed={placed}
|
|
256
|
+
selected={selected}
|
|
257
|
+
related={related}
|
|
258
|
+
planned={Boolean(changeKind)}
|
|
259
|
+
changeKind={changeKind}
|
|
260
|
+
added={created.has(file.id) || file.userCreated}
|
|
261
|
+
highlightMapChange
|
|
262
|
+
previewLabels
|
|
263
|
+
dimmed={
|
|
264
|
+
Boolean(target.selectedFileId) &&
|
|
265
|
+
!selected &&
|
|
266
|
+
!related &&
|
|
267
|
+
!changeKind
|
|
268
|
+
}
|
|
269
|
+
/>
|
|
270
|
+
)
|
|
271
|
+
})}
|
|
272
|
+
{target.selectedFileId && (
|
|
273
|
+
<RelationLines
|
|
274
|
+
selectedId={target.selectedFileId}
|
|
275
|
+
aimedRelation={null}
|
|
276
|
+
files={graph.files}
|
|
277
|
+
layout={layout}
|
|
278
|
+
fromAbove
|
|
279
|
+
importedBy={importedBy}
|
|
280
|
+
/>
|
|
281
|
+
)}
|
|
282
|
+
</>
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export function SelectionThumbnail({
|
|
287
|
+
graph,
|
|
288
|
+
layout,
|
|
289
|
+
selectedId,
|
|
290
|
+
selectedFolder,
|
|
291
|
+
landAt,
|
|
292
|
+
importedBy = false,
|
|
293
|
+
minimized = false,
|
|
294
|
+
plannedIds = [],
|
|
295
|
+
createdIds = [],
|
|
296
|
+
deletedIds = [],
|
|
297
|
+
onMinimize,
|
|
298
|
+
onHide,
|
|
299
|
+
}: SelectionThumbnailProps) {
|
|
300
|
+
const stageRef = useRef<HTMLDivElement>(null)
|
|
301
|
+
const zoomRef = useRef(1)
|
|
302
|
+
const pinchRef = useRef({ start: 0, zoom: 1 })
|
|
303
|
+
const [zoom, setZoom] = useState(1)
|
|
304
|
+
const planned = useMemo(() => new Set(plannedIds), [plannedIds])
|
|
305
|
+
const created = useMemo(() => new Set(createdIds), [createdIds])
|
|
306
|
+
const deleted = useMemo(() => new Set(deletedIds), [deletedIds])
|
|
307
|
+
const highlightedFolders = useMemo(
|
|
308
|
+
() => folderChangeHighlights(planned, created, deleted, layout.folders),
|
|
309
|
+
[created, deleted, layout.folders, planned],
|
|
310
|
+
)
|
|
311
|
+
const target = useMemo(
|
|
312
|
+
() =>
|
|
313
|
+
resolveTarget(
|
|
314
|
+
graph,
|
|
315
|
+
layout,
|
|
316
|
+
selectedId,
|
|
317
|
+
selectedFolder,
|
|
318
|
+
landAt,
|
|
319
|
+
importedBy,
|
|
320
|
+
),
|
|
321
|
+
[graph, importedBy, landAt, layout, selectedFolder, selectedId],
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
setZoom(1)
|
|
326
|
+
zoomRef.current = 1
|
|
327
|
+
}, [selectedId, selectedFolder, landAt])
|
|
328
|
+
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
zoomRef.current = zoom
|
|
331
|
+
}, [zoom])
|
|
332
|
+
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
const stage = stageRef.current
|
|
335
|
+
if (!stage || minimized) return
|
|
336
|
+
|
|
337
|
+
const applyZoom = (next: number) => {
|
|
338
|
+
const clamped = clampZoom(next)
|
|
339
|
+
zoomRef.current = clamped
|
|
340
|
+
setZoom(clamped)
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const onWheel = (event: WheelEvent) => {
|
|
344
|
+
if (!event.ctrlKey && !event.metaKey) return
|
|
345
|
+
event.preventDefault()
|
|
346
|
+
event.stopPropagation()
|
|
347
|
+
applyZoom(zoomRef.current * Math.exp(-event.deltaY * 0.01))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const onGestureStart = (event: Event) => {
|
|
351
|
+
event.preventDefault()
|
|
352
|
+
pinchRef.current.zoom = zoomRef.current
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const onGestureChange = (event: Event) => {
|
|
356
|
+
event.preventDefault()
|
|
357
|
+
const scale = Number((event as Event & { scale?: number }).scale)
|
|
358
|
+
if (!Number.isFinite(scale) || scale <= 0) return
|
|
359
|
+
applyZoom(pinchRef.current.zoom * scale)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const onTouchStart = (event: TouchEvent) => {
|
|
363
|
+
if (event.touches.length !== 2) return
|
|
364
|
+
pinchRef.current = {
|
|
365
|
+
start: touchDistance(event.touches),
|
|
366
|
+
zoom: zoomRef.current,
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const onTouchMove = (event: TouchEvent) => {
|
|
371
|
+
if (event.touches.length !== 2 || pinchRef.current.start <= 0) return
|
|
372
|
+
event.preventDefault()
|
|
373
|
+
event.stopPropagation()
|
|
374
|
+
applyZoom(
|
|
375
|
+
pinchRef.current.zoom *
|
|
376
|
+
(touchDistance(event.touches) / pinchRef.current.start),
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
stage.addEventListener('wheel', onWheel, { passive: false, capture: true })
|
|
381
|
+
stage.addEventListener('gesturestart', onGestureStart, { capture: true })
|
|
382
|
+
stage.addEventListener('gesturechange', onGestureChange, { capture: true })
|
|
383
|
+
stage.addEventListener('touchstart', onTouchStart, { passive: true })
|
|
384
|
+
stage.addEventListener('touchmove', onTouchMove, { passive: false, capture: true })
|
|
385
|
+
return () => {
|
|
386
|
+
stage.removeEventListener('wheel', onWheel, { capture: true })
|
|
387
|
+
stage.removeEventListener('gesturestart', onGestureStart, { capture: true })
|
|
388
|
+
stage.removeEventListener('gesturechange', onGestureChange, { capture: true })
|
|
389
|
+
stage.removeEventListener('touchstart', onTouchStart)
|
|
390
|
+
stage.removeEventListener('touchmove', onTouchMove, { capture: true })
|
|
391
|
+
}
|
|
392
|
+
}, [minimized])
|
|
393
|
+
|
|
394
|
+
if (!target) return null
|
|
395
|
+
|
|
396
|
+
return (
|
|
397
|
+
<div className="hud-thumbnail" data-minimized={minimized}>
|
|
398
|
+
<div className="hud-thumbnail-bar">
|
|
399
|
+
<span>3D view</span>
|
|
400
|
+
<div className="hud-panel-controls">
|
|
401
|
+
{onMinimize && (
|
|
402
|
+
<button
|
|
403
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
404
|
+
type="button"
|
|
405
|
+
aria-label={minimized ? 'Restore 3D view' : 'Minimize 3D view'}
|
|
406
|
+
onClick={onMinimize}
|
|
407
|
+
>
|
|
408
|
+
{minimized ? '+' : '−'}
|
|
409
|
+
</button>
|
|
410
|
+
)}
|
|
411
|
+
<button
|
|
412
|
+
className="hud-button hud-icon-button hud-panel-control"
|
|
413
|
+
type="button"
|
|
414
|
+
aria-label="Close 3D view"
|
|
415
|
+
onClick={onHide}
|
|
416
|
+
>
|
|
417
|
+
×
|
|
418
|
+
</button>
|
|
419
|
+
</div>
|
|
420
|
+
</div>
|
|
421
|
+
{!minimized && (
|
|
422
|
+
<div className="hud-thumbnail-stage" ref={stageRef}>
|
|
423
|
+
<Canvas
|
|
424
|
+
shadows={false}
|
|
425
|
+
dpr={[1, 1.5]}
|
|
426
|
+
resize={{ offsetSize: true }}
|
|
427
|
+
gl={{ antialias: true, toneMappingExposure: 1.25 }}
|
|
428
|
+
camera={{
|
|
429
|
+
fov: 50,
|
|
430
|
+
near: 0.1,
|
|
431
|
+
far: 400,
|
|
432
|
+
position: target.camera.position,
|
|
433
|
+
}}
|
|
434
|
+
>
|
|
435
|
+
<ThumbnailScene
|
|
436
|
+
graph={graph}
|
|
437
|
+
layout={layout}
|
|
438
|
+
importedBy={importedBy}
|
|
439
|
+
target={target}
|
|
440
|
+
highlightedFolders={highlightedFolders}
|
|
441
|
+
planned={planned}
|
|
442
|
+
created={created}
|
|
443
|
+
deleted={deleted}
|
|
444
|
+
zoom={zoom}
|
|
445
|
+
/>
|
|
446
|
+
</Canvas>
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
)
|
|
451
|
+
}
|
|
@@ -8,9 +8,13 @@ import { SelectionController } from './SelectionController'
|
|
|
8
8
|
import { UserContextTracker } from './UserContextTracker'
|
|
9
9
|
import { BlockPlacer } from './BlockPlacer'
|
|
10
10
|
import { IslandPlacer } from './IslandPlacer'
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import {
|
|
12
|
+
fileChangeKind,
|
|
13
|
+
filesImporting,
|
|
14
|
+
folderChangeHighlights,
|
|
15
|
+
folderOfFile,
|
|
16
|
+
} from '../layout'
|
|
17
|
+
import { WORLD_VOID } from '../theme'
|
|
14
18
|
import type {
|
|
15
19
|
CodebaseGraph,
|
|
16
20
|
FileNode,
|
|
@@ -127,60 +131,23 @@ export function World({
|
|
|
127
131
|
Boolean(selectedFolder) ||
|
|
128
132
|
planned.size > 0 ||
|
|
129
133
|
deleted.size > 0
|
|
130
|
-
const highlightedFolders
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
const bounds = worldBounds(layout)
|
|
163
|
-
const groundPad = 120
|
|
164
|
-
const groundWidth = Math.max(bounds.width + groundPad * 2, 400)
|
|
165
|
-
const groundDepth = Math.max(bounds.depth + groundPad * 2, 400)
|
|
134
|
+
const highlightedFolders = folderChangeHighlights(
|
|
135
|
+
planned,
|
|
136
|
+
created,
|
|
137
|
+
deleted,
|
|
138
|
+
layout.folders,
|
|
139
|
+
)
|
|
166
140
|
|
|
167
141
|
return (
|
|
168
142
|
<>
|
|
169
|
-
<color attach="background" args={[
|
|
170
|
-
{!mapping && <fog attach="fog" args={[
|
|
143
|
+
<color attach="background" args={[WORLD_VOID]} />
|
|
144
|
+
{!mapping && <fog attach="fog" args={[WORLD_VOID, 38, 160]} />}
|
|
171
145
|
<hemisphereLight args={['#d7e2ee', '#2a3038', mapping ? 1.1 : 0.85]} />
|
|
172
146
|
<directionalLight
|
|
173
147
|
position={mapping ? [8, 60, 8] : [12, 22, 8]}
|
|
174
148
|
intensity={mapping ? 1.35 : 0.55}
|
|
175
149
|
/>
|
|
176
150
|
<ambientLight intensity={mapping ? 0.7 : 0.42} />
|
|
177
|
-
<mesh
|
|
178
|
-
rotation={[-Math.PI / 2, 0, 0]}
|
|
179
|
-
position={[bounds.cx, -0.06, bounds.cz]}
|
|
180
|
-
>
|
|
181
|
-
<planeGeometry args={[groundWidth, groundDepth]} />
|
|
182
|
-
<meshBasicMaterial color={EDITOR_GREY.chrome} />
|
|
183
|
-
</mesh>
|
|
184
151
|
<MapView
|
|
185
152
|
layout={layout}
|
|
186
153
|
enabled={mapping}
|
|
@@ -200,7 +167,9 @@ export function World({
|
|
|
200
167
|
naming={folder.path === namingIslandId}
|
|
201
168
|
selected={folder.path === selectedFolder}
|
|
202
169
|
mapMode={mapping}
|
|
203
|
-
highlightKind={
|
|
170
|
+
highlightKind={
|
|
171
|
+
mapping ? highlightedFolders[folder.path] ?? null : null
|
|
172
|
+
}
|
|
204
173
|
/>
|
|
205
174
|
))}
|
|
206
175
|
{layout.bridges.map((bridge) => (
|
|
@@ -212,7 +181,7 @@ export function World({
|
|
|
212
181
|
const selected = file.id === selectedId
|
|
213
182
|
const isRelated = related.has(file.id)
|
|
214
183
|
const isPlanned = planned.has(file.id) || deleted.has(file.id)
|
|
215
|
-
const changeKind =
|
|
184
|
+
const changeKind = fileChangeKind(file.id, planned, created, deleted)
|
|
216
185
|
const naming = file.id === namingId
|
|
217
186
|
return (
|
|
218
187
|
<FileBlock
|
|
@@ -25,6 +25,9 @@ export const EDITOR_GREY = {
|
|
|
25
25
|
surface: '#272c36',
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** Infinite map/walk backdrop. Keep distinct from chrome HUD greys. */
|
|
29
|
+
export const WORLD_VOID = '#000000'
|
|
30
|
+
|
|
28
31
|
export type ChangeKind = 'add' | 'edit' | 'remove'
|
|
29
32
|
|
|
30
33
|
export const CHANGE_HIGHLIGHT: Record<
|
|
@@ -51,10 +54,16 @@ export const CHANGE_HIGHLIGHT: Record<
|
|
|
51
54
|
},
|
|
52
55
|
}
|
|
53
56
|
|
|
57
|
+
export const FILE_SELECTION = {
|
|
58
|
+
color: '#c026ff',
|
|
59
|
+
emissive: '#6d00b8',
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
export const MAP_SELECTION = {
|
|
55
63
|
color: '#000000',
|
|
64
|
+
island: FILE_SELECTION.color,
|
|
56
65
|
islandPad: 0.38,
|
|
57
|
-
blockPad: 0.
|
|
66
|
+
blockPad: 0.1,
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
export function fileHeight(lines: number) {
|
|
@@ -152,6 +152,13 @@ export function isReviewingIntent(status: AgentIntentStatus) {
|
|
|
152
152
|
)
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
export function canStopSession(intent: {
|
|
156
|
+
sessionId: string | null
|
|
157
|
+
status: AgentIntentStatus
|
|
158
|
+
}) {
|
|
159
|
+
return Boolean(intent.sessionId) && intent.status !== 'idle'
|
|
160
|
+
}
|
|
161
|
+
|
|
155
162
|
export type PlanStep = {
|
|
156
163
|
index: number
|
|
157
164
|
title: string
|
|
@@ -208,6 +215,11 @@ export type WorkflowAction =
|
|
|
208
215
|
| 'blueprint_send'
|
|
209
216
|
| 'blueprint_update'
|
|
210
217
|
|
|
218
|
+
export type AgentIntentBundle = {
|
|
219
|
+
focusedSessionId: string | null
|
|
220
|
+
intents: AgentIntent[]
|
|
221
|
+
}
|
|
222
|
+
|
|
211
223
|
export type AgentIntent = {
|
|
212
224
|
updatedAt: string | null
|
|
213
225
|
showMap: boolean
|