@jkwd/inbase 0.1.21 → 0.1.23

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 (58) hide show
  1. package/README.md +13 -7
  2. package/apps/explorer/package.json +1 -0
  3. package/apps/explorer/scripts/explain-store.d.ts +135 -0
  4. package/apps/explorer/scripts/explain-store.mjs +666 -0
  5. package/apps/explorer/scripts/patch-lib.mjs +4 -0
  6. package/apps/explorer/scripts/scan-target.mjs +22 -5
  7. package/apps/explorer/scripts/session-store.d.ts +86 -11
  8. package/apps/explorer/scripts/session-store.mjs +371 -58
  9. package/apps/explorer/scripts/target-config.d.ts +38 -3
  10. package/apps/explorer/scripts/target-config.mjs +147 -3
  11. package/apps/explorer/src/App.tsx +1073 -158
  12. package/apps/explorer/src/agentIntent.ts +61 -7
  13. package/apps/explorer/src/codebase.ts +1 -1
  14. package/apps/explorer/src/devTargets.ts +66 -0
  15. package/apps/explorer/src/explain.ts +312 -0
  16. package/apps/explorer/src/index.css +874 -222
  17. package/apps/explorer/src/layout.ts +55 -0
  18. package/apps/explorer/src/scene/DistantFileBlocks.tsx +4 -2
  19. package/apps/explorer/src/scene/FileBlock.tsx +95 -72
  20. package/apps/explorer/src/scene/FolderArea.tsx +55 -32
  21. package/apps/explorer/src/scene/MapView.tsx +506 -33
  22. package/apps/explorer/src/scene/RelationLines.tsx +7 -0
  23. package/apps/explorer/src/scene/World.tsx +146 -32
  24. package/apps/explorer/src/speech.ts +228 -0
  25. package/apps/explorer/src/theme.ts +29 -0
  26. package/apps/explorer/src/types.ts +98 -8
  27. package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +1 -1
  28. package/apps/explorer/src/ui/ExplainAskCard.tsx +142 -0
  29. package/apps/explorer/src/ui/ExplainHud.tsx +524 -0
  30. package/apps/explorer/src/ui/ExplainInfoPanel.tsx +135 -0
  31. package/apps/explorer/src/ui/ExplainPointer.tsx +73 -0
  32. package/apps/explorer/src/ui/EyeIcon.tsx +38 -1
  33. package/apps/explorer/src/ui/HUD.tsx +1067 -795
  34. package/apps/explorer/src/ui/NameInput.tsx +114 -5
  35. package/apps/explorer/src/userContext.ts +0 -11
  36. package/apps/explorer/src/userCreated.ts +54 -1
  37. package/apps/explorer/vite.config.ts +173 -26
  38. package/bin/inbase.mjs +11 -2
  39. package/bin/project.mjs +6 -4
  40. package/bin/session.mjs +287 -38
  41. package/package.json +4 -1
  42. package/skill/commands/amber.md +23 -0
  43. package/skill/commands/blue.md +13 -0
  44. package/skill/commands/coral.md +23 -0
  45. package/skill/commands/explain.md +77 -0
  46. package/skill/commands/green.md +23 -0
  47. package/skill/commands/inbase.md +7 -5
  48. package/skill/commands/lime.md +23 -0
  49. package/skill/commands/orange.md +23 -0
  50. package/skill/commands/purple.md +23 -0
  51. package/skill/commands/red.md +23 -0
  52. package/skill/commands/skipinbase.md +1 -1
  53. package/skill/commands/violet.md +23 -0
  54. package/skill/commands/yellow.md +23 -0
  55. package/skill/inbase/SKILL.md +124 -76
  56. package/apps/explorer/src/scene/BlockPlacer.tsx +0 -78
  57. package/apps/explorer/src/scene/IslandPlacer.tsx +0 -31
  58. package/apps/explorer/src/scene/SelectionThumbnail.tsx +0 -1069
@@ -1,9 +1,15 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { Canvas } from '@react-three/fiber'
3
3
  import { shouldIgnoreShortcut, isKeyboardIsolated } from './keyboard'
4
- import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistBlueprintCleanup, persistBlueprintClear, persistBlueprintHidden, persistSessionBlueprint, persistSessionFocus, setupVisualizerSession } from './agentIntent'
4
+ import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistBlueprintCleanup, persistBlueprintClear, persistBlueprintHidden, persistSessionBlueprint, persistSessionFocus } from './agentIntent'
5
5
  import { emptyBranchChanges, fetchBranchChanges } from './branchChanges'
6
6
  import { fetchCodebase, updateCodebase } from './codebase'
7
+ import {
8
+ emptyDevTargets,
9
+ fetchDevTargets,
10
+ selectDevTarget,
11
+ type DevTargetsState,
12
+ } from './devTargets'
7
13
  import {
8
14
  layoutWorld,
9
15
  markCreatedFolders,
@@ -12,17 +18,36 @@ import {
12
18
  withPreviewGraph,
13
19
  filterGraphToChangePaths,
14
20
  mapPointOntoFolder,
21
+ regionBounds,
15
22
  } from './layout'
16
23
  import { World } from './scene/World'
17
24
  import { HUD } from './ui/HUD'
25
+ import { ExplainAskCard } from './ui/ExplainAskCard'
26
+ import { ExplainHud } from './ui/ExplainHud'
27
+ import { ExplainInfoPanel } from './ui/ExplainInfoPanel'
28
+ import { ExplainPointer } from './ui/ExplainPointer'
18
29
  import { CanvasErrorBoundary } from './ui/CanvasErrorBoundary'
19
30
  import {
20
31
  MapContextMenu,
21
32
  type MapContextMenuState,
22
33
  } from './ui/MapContextMenu'
34
+ import {
35
+ currentExplainStep,
36
+ emptyExplain,
37
+ explainFocus,
38
+ explainInfoFile,
39
+ explainIsCard,
40
+ explainTargetQuestion,
41
+ fetchExplain,
42
+ persistExplainAsk,
43
+ persistExplainStart,
44
+ persistExplainStep,
45
+ persistExplainStop,
46
+ stripExplainSubSteps,
47
+ topLevelExplainStepId,
48
+ } from './explain'
23
49
  import {
24
50
  fetchUserContext,
25
- persistFollowLook,
26
51
  persistShowBranchChanges,
27
52
  persistUserContext,
28
53
  } from './userContext'
@@ -36,11 +61,13 @@ import {
36
61
  isBlueprintSymbolName,
37
62
  namedCreatedBlocks,
38
63
  namedCreatedIslands,
64
+ blueprintImportRawFromFile,
39
65
  parseBlueprintImport,
40
66
  parseBlueprintNotes,
41
67
  parseBlueprintPointers,
42
68
  parseUserCreatedBlocks,
43
69
  parseUserCreatedIslands,
70
+ remapBlueprintFileId,
44
71
  resolveCreatedFile,
45
72
  resolveCreatedIsland,
46
73
  setBlueprintNote,
@@ -51,23 +78,399 @@ import {
51
78
  } from './userCreated'
52
79
  import {
53
80
  isPatchPreview,
81
+ isReviewingIntent,
54
82
  llmIsMakingChanges,
55
83
  type AgentIntent,
56
84
  type AimedRelation,
57
85
  type BlueprintNote,
58
86
  type BlueprintNoteKind,
87
+ type BlueprintOption,
59
88
  type BlueprintPointer,
60
89
  type BlueprintPointerKind,
61
90
  type CodebaseGraph,
91
+ type ExplainSession,
92
+ type ExplainTargetKind,
62
93
  type FlyTo,
94
+ GLOBAL_BLUEPRINT_COLOR,
95
+ type LocalBlueprint,
63
96
  type PatchImportAddition,
64
97
  type PatchSymbolAddition,
98
+ type SharedBlueprint,
65
99
  type UserCreatedBlock,
66
100
  type UserCreatedIsland,
67
101
  type ViewMode,
68
102
  type WorkflowAction,
69
103
  } from './types'
70
104
 
105
+ function emptySharedBlueprint(): SharedBlueprint {
106
+ return {
107
+ hidden: false,
108
+ revision: 0,
109
+ enabled: false,
110
+ userCreatedBlocks: [],
111
+ userCreatedIslands: [],
112
+ addedFunctions: [],
113
+ addedVariables: [],
114
+ addedImports: [],
115
+ notes: [],
116
+ pointers: [],
117
+ }
118
+ }
119
+
120
+ function blueprintForColor(
121
+ color: string,
122
+ global: SharedBlueprint,
123
+ locals: LocalBlueprint[],
124
+ ) {
125
+ if (color === GLOBAL_BLUEPRINT_COLOR.id) return global
126
+ return locals.find((item) => item.color === color) ?? emptySharedBlueprint()
127
+ }
128
+
129
+ function blueprintOptionsFrom(
130
+ intents: AgentIntent[],
131
+ locals: LocalBlueprint[],
132
+ ): BlueprintOption[] {
133
+ const byColor = new Map<string, BlueprintOption>()
134
+ for (const intent of intents) {
135
+ if (!intent.color || byColor.has(intent.color)) continue
136
+ byColor.set(intent.color, {
137
+ id: intent.color,
138
+ name: intent.colorName || intent.color,
139
+ hex: intent.colorHex || '#6b7280',
140
+ kind: 'local',
141
+ sessionId: intent.sessionId,
142
+ })
143
+ }
144
+ for (const local of locals) {
145
+ const existing = byColor.get(local.color)
146
+ byColor.set(local.color, {
147
+ id: local.color,
148
+ name: local.colorName || existing?.name || local.color,
149
+ hex: local.colorHex || existing?.hex || '#6b7280',
150
+ kind: 'local',
151
+ sessionId: local.sessionId,
152
+ })
153
+ }
154
+ return [
155
+ {
156
+ id: GLOBAL_BLUEPRINT_COLOR.id,
157
+ name: GLOBAL_BLUEPRINT_COLOR.name,
158
+ hex: GLOBAL_BLUEPRINT_COLOR.hex,
159
+ kind: 'global',
160
+ sessionId: null,
161
+ },
162
+ ...byColor.values(),
163
+ ]
164
+ }
165
+
166
+ function blueprintLiveEnabled(
167
+ blocks: UserCreatedBlock[],
168
+ islands: UserCreatedIsland[],
169
+ functions: PatchSymbolAddition[],
170
+ variables: PatchSymbolAddition[],
171
+ imports: PatchImportAddition[],
172
+ notes: BlueprintNote[],
173
+ pointers: BlueprintPointer[],
174
+ ) {
175
+ return (
176
+ namedCreatedBlocks(blocks).length > 0 ||
177
+ namedCreatedIslands(islands).length > 0 ||
178
+ functions.length > 0 ||
179
+ variables.length > 0 ||
180
+ imports.length > 0 ||
181
+ notes.length > 0 ||
182
+ pointers.length > 0
183
+ )
184
+ }
185
+
186
+ function keepBlueprintDrafts(
187
+ stored: SharedBlueprint,
188
+ previous?: SharedBlueprint | null,
189
+ ): SharedBlueprint {
190
+ if (!previous) return stored
191
+ const draftBlocks = previous.userCreatedBlocks.filter((block) => block.naming)
192
+ const draftIslands = previous.userCreatedIslands.filter((island) => island.naming)
193
+ if (draftBlocks.length === 0 && draftIslands.length === 0) return stored
194
+ const draftBlockIds = new Set(draftBlocks.map((block) => block.id))
195
+ const draftIslandIds = new Set(draftIslands.map((island) => island.id))
196
+ return {
197
+ ...stored,
198
+ userCreatedBlocks: [
199
+ ...stored.userCreatedBlocks.filter((block) => !draftBlockIds.has(block.id)),
200
+ ...draftBlocks,
201
+ ],
202
+ userCreatedIslands: [
203
+ ...stored.userCreatedIslands.filter(
204
+ (island) => !draftIslandIds.has(island.id),
205
+ ),
206
+ ...draftIslands,
207
+ ],
208
+ }
209
+ }
210
+
211
+ function capturedBlueprintContents(
212
+ current: SharedBlueprint,
213
+ capture: {
214
+ hidden: boolean
215
+ blocks: UserCreatedBlock[]
216
+ islands: UserCreatedIsland[]
217
+ functions: PatchSymbolAddition[]
218
+ variables: PatchSymbolAddition[]
219
+ imports: PatchImportAddition[]
220
+ notes: BlueprintNote[]
221
+ pointers: BlueprintPointer[]
222
+ },
223
+ ): SharedBlueprint {
224
+ return {
225
+ ...current,
226
+ hidden: capture.hidden,
227
+ enabled: blueprintLiveEnabled(
228
+ capture.blocks,
229
+ capture.islands,
230
+ capture.functions,
231
+ capture.variables,
232
+ capture.imports,
233
+ capture.notes,
234
+ capture.pointers,
235
+ ),
236
+ userCreatedBlocks: capture.blocks,
237
+ userCreatedIslands: capture.islands,
238
+ addedFunctions: capture.functions,
239
+ addedVariables: capture.variables,
240
+ addedImports: capture.imports,
241
+ notes: capture.notes,
242
+ pointers: capture.pointers,
243
+ }
244
+ }
245
+
246
+ function withCapturedBlueprint(
247
+ current: { global: SharedBlueprint; locals: LocalBlueprint[] },
248
+ capture: {
249
+ color: string
250
+ hidden: boolean
251
+ blocks: UserCreatedBlock[]
252
+ islands: UserCreatedIsland[]
253
+ functions: PatchSymbolAddition[]
254
+ variables: PatchSymbolAddition[]
255
+ imports: PatchImportAddition[]
256
+ notes: BlueprintNote[]
257
+ pointers: BlueprintPointer[]
258
+ options: BlueprintOption[]
259
+ },
260
+ ): { global: SharedBlueprint; locals: LocalBlueprint[] } {
261
+ const fields = {
262
+ hidden: capture.hidden,
263
+ blocks: capture.blocks,
264
+ islands: capture.islands,
265
+ functions: capture.functions,
266
+ variables: capture.variables,
267
+ imports: capture.imports,
268
+ notes: capture.notes,
269
+ pointers: capture.pointers,
270
+ }
271
+ if (capture.color === GLOBAL_BLUEPRINT_COLOR.id) {
272
+ return {
273
+ global: capturedBlueprintContents(current.global, fields),
274
+ locals: current.locals,
275
+ }
276
+ }
277
+ const existing = current.locals.find((item) => item.color === capture.color)
278
+ const option = capture.options.find((item) => item.id === capture.color)
279
+ const nextLocal: LocalBlueprint = {
280
+ color: capture.color,
281
+ colorName: existing?.colorName || option?.name || capture.color,
282
+ colorHex: existing?.colorHex || option?.hex || '#6b7280',
283
+ sessionId: existing?.sessionId || option?.sessionId || '',
284
+ ...capturedBlueprintContents(existing ?? emptySharedBlueprint(), fields),
285
+ }
286
+ const locals = existing
287
+ ? current.locals.map((item) =>
288
+ item.color === capture.color ? nextLocal : item,
289
+ )
290
+ : [...current.locals, nextLocal]
291
+ return { global: current.global, locals }
292
+ }
293
+
294
+ function withPolledBlueprints(
295
+ previous: { global: SharedBlueprint; locals: LocalBlueprint[] },
296
+ polled: { global: SharedBlueprint; locals: LocalBlueprint[] },
297
+ ): { global: SharedBlueprint; locals: LocalBlueprint[] } {
298
+ return {
299
+ global: keepBlueprintDrafts(polled.global, previous.global),
300
+ locals: polled.locals.map((local) => {
301
+ const prior = previous.locals.find((item) => item.color === local.color)
302
+ const merged = keepBlueprintDrafts(local, prior)
303
+ return {
304
+ ...local,
305
+ ...merged,
306
+ color: local.color,
307
+ colorName: local.colorName,
308
+ colorHex: local.colorHex,
309
+ sessionId: local.sessionId,
310
+ }
311
+ }),
312
+ }
313
+ }
314
+
315
+ function visibleItemsForBlueprint(
316
+ hidden: boolean,
317
+ blocks: UserCreatedBlock[],
318
+ islands: UserCreatedIsland[],
319
+ ) {
320
+ if (!hidden) return { blocks, islands }
321
+ return {
322
+ blocks: blocks.filter((block) => block.naming),
323
+ islands: islands.filter((island) => island.naming),
324
+ }
325
+ }
326
+
327
+ function collectMapBlueprints(input: {
328
+ selectedColor: string
329
+ selectedHidden: boolean
330
+ selectedBlocks: UserCreatedBlock[]
331
+ selectedIslands: UserCreatedIsland[]
332
+ selectedFunctions: PatchSymbolAddition[]
333
+ selectedVariables: PatchSymbolAddition[]
334
+ selectedImports: PatchImportAddition[]
335
+ selectedPointers: BlueprintPointer[]
336
+ global: SharedBlueprint
337
+ locals: LocalBlueprint[]
338
+ }) {
339
+ const blocks = new Map<string, UserCreatedBlock>()
340
+ const islands = new Map<string, UserCreatedIsland>()
341
+ const functions: PatchSymbolAddition[] = []
342
+ const variables: PatchSymbolAddition[] = []
343
+ const imports: PatchImportAddition[] = []
344
+ const pointers: Array<BlueprintPointer & { colorHex: string }> = []
345
+
346
+ const sources: Array<{
347
+ id: string
348
+ hex: string
349
+ hidden: boolean
350
+ live: boolean
351
+ blocks: UserCreatedBlock[]
352
+ islands: UserCreatedIsland[]
353
+ functions: PatchSymbolAddition[]
354
+ variables: PatchSymbolAddition[]
355
+ imports: PatchImportAddition[]
356
+ pointers: BlueprintPointer[]
357
+ }> = [
358
+ {
359
+ id: GLOBAL_BLUEPRINT_COLOR.id,
360
+ hex: GLOBAL_BLUEPRINT_COLOR.hex,
361
+ hidden:
362
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
363
+ ? input.selectedHidden
364
+ : input.global.hidden,
365
+ live: input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id,
366
+ blocks:
367
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
368
+ ? input.selectedBlocks
369
+ : input.global.userCreatedBlocks,
370
+ islands:
371
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
372
+ ? input.selectedIslands
373
+ : input.global.userCreatedIslands,
374
+ functions:
375
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
376
+ ? input.selectedFunctions
377
+ : input.global.addedFunctions,
378
+ variables:
379
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
380
+ ? input.selectedVariables
381
+ : input.global.addedVariables,
382
+ imports:
383
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
384
+ ? input.selectedImports
385
+ : input.global.addedImports,
386
+ pointers:
387
+ input.selectedColor === GLOBAL_BLUEPRINT_COLOR.id
388
+ ? input.selectedPointers
389
+ : input.global.pointers,
390
+ },
391
+ ...input.locals.map((local) => ({
392
+ id: local.color,
393
+ hex: local.colorHex || GLOBAL_BLUEPRINT_COLOR.hex,
394
+ hidden:
395
+ input.selectedColor === local.color
396
+ ? input.selectedHidden
397
+ : local.hidden,
398
+ live: input.selectedColor === local.color,
399
+ blocks:
400
+ input.selectedColor === local.color
401
+ ? input.selectedBlocks
402
+ : local.userCreatedBlocks,
403
+ islands:
404
+ input.selectedColor === local.color
405
+ ? input.selectedIslands
406
+ : local.userCreatedIslands,
407
+ functions:
408
+ input.selectedColor === local.color
409
+ ? input.selectedFunctions
410
+ : local.addedFunctions,
411
+ variables:
412
+ input.selectedColor === local.color
413
+ ? input.selectedVariables
414
+ : local.addedVariables,
415
+ imports:
416
+ input.selectedColor === local.color
417
+ ? input.selectedImports
418
+ : local.addedImports,
419
+ pointers:
420
+ input.selectedColor === local.color
421
+ ? input.selectedPointers
422
+ : local.pointers,
423
+ })),
424
+ ]
425
+
426
+ sources.sort((left, right) => Number(left.live) - Number(right.live))
427
+ for (const source of sources) {
428
+ const visible = visibleItemsForBlueprint(
429
+ source.hidden,
430
+ source.blocks,
431
+ source.islands,
432
+ )
433
+ for (const block of visible.blocks) {
434
+ blocks.set(block.id, { ...block, colorHex: source.hex })
435
+ }
436
+ for (const island of visible.islands) {
437
+ islands.set(island.id, { ...island, colorHex: source.hex })
438
+ }
439
+ if (!source.hidden) {
440
+ functions.push(...source.functions)
441
+ variables.push(...source.variables)
442
+ imports.push(...source.imports)
443
+ for (const pointer of source.pointers) {
444
+ pointers.push({ ...pointer, colorHex: source.hex })
445
+ }
446
+ }
447
+ }
448
+
449
+ return {
450
+ blocks: [...blocks.values()],
451
+ islands: [...islands.values()],
452
+ functions,
453
+ variables,
454
+ imports,
455
+ pointers,
456
+ }
457
+ }
458
+
459
+ function pointerColorMaps(
460
+ pointers: Array<BlueprintPointer & { colorHex?: string }>,
461
+ ) {
462
+ const files: Record<string, string[]> = {}
463
+ const folders: Record<string, string[]> = {}
464
+ for (const item of pointers) {
465
+ const hex = item.colorHex || GLOBAL_BLUEPRINT_COLOR.hex
466
+ const target = item.kind === 'folder' ? folders : files
467
+ const list = target[item.path] ?? []
468
+ if (!list.includes(hex)) list.push(hex)
469
+ target[item.path] = list
470
+ }
471
+ return { files, folders }
472
+ }
473
+
71
474
  function intentSignature(intent: AgentIntent) {
72
475
  return JSON.stringify({
73
476
  updatedAt: intent.updatedAt,
@@ -102,6 +505,8 @@ export default function App() {
102
505
  const [loadError, setLoadError] = useState<string | null>(null)
103
506
  const [updatingModel, setUpdatingModel] = useState(false)
104
507
  const updatingModelRef = useRef(false)
508
+ const [devTargets, setDevTargets] = useState<DevTargetsState>(emptyDevTargets)
509
+ const [targetEpoch, setTargetEpoch] = useState(0)
105
510
 
106
511
  const graphSig = useRef<string | null>(null)
107
512
  const applyGraph = useCallback((next: CodebaseGraph | null, failed: string) => {
@@ -121,7 +526,7 @@ export default function App() {
121
526
  }, [])
122
527
 
123
528
  const refreshGraph = useCallback(async () => {
124
- applyGraph(await fetchCodebase(), 'Could not load the project map.')
529
+ applyGraph(await fetchCodebase(), 'Could not load files and folders.')
125
530
  }, [applyGraph])
126
531
 
127
532
  const updateModel = useCallback(async () => {
@@ -129,31 +534,60 @@ export default function App() {
129
534
  updatingModelRef.current = true
130
535
  setUpdatingModel(true)
131
536
  try {
132
- applyGraph(await updateCodebase(), 'Could not update the project map.')
537
+ applyGraph(await updateCodebase(), 'Could not update files and folders.')
133
538
  } finally {
134
539
  updatingModelRef.current = false
135
540
  setUpdatingModel(false)
136
541
  }
137
542
  }, [applyGraph])
138
543
 
544
+ const switchDevTarget = useCallback(
545
+ async (id: string) => {
546
+ if (updatingModelRef.current) return
547
+ updatingModelRef.current = true
548
+ setUpdatingModel(true)
549
+ try {
550
+ const result = await selectDevTarget(id)
551
+ if (!result?.graph) {
552
+ setLoadError((current) => current ?? 'Could not switch project.')
553
+ return
554
+ }
555
+ setDevTargets(result.state)
556
+ applyGraph(result.graph, 'Could not switch project.')
557
+ setTargetEpoch((value) => value + 1)
558
+ } finally {
559
+ updatingModelRef.current = false
560
+ setUpdatingModel(false)
561
+ }
562
+ },
563
+ [applyGraph],
564
+ )
565
+
139
566
  useEffect(() => {
140
567
  void refreshGraph()
141
568
  }, [refreshGraph])
142
569
 
570
+ useEffect(() => {
571
+ void fetchDevTargets().then(setDevTargets)
572
+ }, [])
573
+
143
574
  if (!graph) {
144
575
  return (
145
576
  <div className="boot">
146
- <p>{loadError ?? 'Loading map…'}</p>
577
+ <p>{loadError ?? 'Loading files and folders…'}</p>
147
578
  </div>
148
579
  )
149
580
  }
150
581
 
151
582
  return (
152
583
  <Explorer
584
+ key={targetEpoch}
153
585
  graph={graph}
154
586
  onRefreshGraph={refreshGraph}
155
587
  onUpdateModel={updateModel}
156
588
  updatingModel={updatingModel}
589
+ devTargets={devTargets}
590
+ onSelectDevTarget={switchDevTarget}
157
591
  />
158
592
  )
159
593
  }
@@ -163,11 +597,15 @@ function Explorer({
163
597
  onRefreshGraph,
164
598
  onUpdateModel,
165
599
  updatingModel,
600
+ devTargets,
601
+ onSelectDevTarget,
166
602
  }: {
167
603
  graph: CodebaseGraph
168
604
  onRefreshGraph: () => Promise<void>
169
605
  onUpdateModel: () => Promise<void>
170
606
  updatingModel: boolean
607
+ devTargets: DevTargetsState
608
+ onSelectDevTarget: (id: string) => void
171
609
  }) {
172
610
  const [intents, setIntents] = useState<AgentIntent[]>([])
173
611
  const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
@@ -211,6 +649,23 @@ function Explorer({
211
649
  [],
212
650
  )
213
651
  const [blueprintHidden, setBlueprintHidden] = useState(false)
652
+ const [blueprintColor, setBlueprintColor] = useState<string>(
653
+ GLOBAL_BLUEPRINT_COLOR.id,
654
+ )
655
+ const [globalBlueprint, setGlobalBlueprint] = useState<SharedBlueprint>(
656
+ emptySharedBlueprint,
657
+ )
658
+ const [localBlueprints, setLocalBlueprints] = useState<LocalBlueprint[]>([])
659
+ const blueprintColorRef = useRef(blueprintColor)
660
+ blueprintColorRef.current = blueprintColor
661
+ const blueprintHiddenRef = useRef(blueprintHidden)
662
+ blueprintHiddenRef.current = blueprintHidden
663
+ const latestBlueprintsRef = useRef({
664
+ global: emptySharedBlueprint(),
665
+ locals: [] as LocalBlueprint[],
666
+ })
667
+ const blueprintOptionsRef = useRef<BlueprintOption[]>([])
668
+ blueprintOptionsRef.current = blueprintOptionsFrom(intents, localBlueprints)
214
669
  const userBlocksRef = useRef(userBlocks)
215
670
  const userIslandsRef = useRef(userIslands)
216
671
  const blueprintFunctionsRef = useRef(blueprintFunctions)
@@ -218,6 +673,8 @@ function Explorer({
218
673
  const blueprintImportsRef = useRef(blueprintImports)
219
674
  const blueprintNotesRef = useRef(blueprintNotes)
220
675
  const blueprintPointersRef = useRef(blueprintPointers)
676
+ const persistBlueprintRef = useRef<() => void>(() => {})
677
+ const selectBlueprintColorRef = useRef<(color: string) => void>(() => {})
221
678
  const notesDirty = useRef(false)
222
679
  const blueprintPersistGen = useRef(0)
223
680
  const notePersistTimer = useRef<number | null>(null)
@@ -256,30 +713,80 @@ function Explorer({
256
713
  () => new Set(previewGraph.folders.map((folder) => folder.path)),
257
714
  [previewGraph],
258
715
  )
259
- const visibleBlocks = blueprintHidden
260
- ? userBlocks.filter((block) => block.naming)
261
- : userBlocks
262
- const visibleIslands = blueprintHidden
263
- ? userIslands.filter((island) => island.naming)
264
- : userIslands
265
- const pendingBlocks = visibleBlocks.filter(
716
+ const mapBlueprint = useMemo(
717
+ () =>
718
+ collectMapBlueprints({
719
+ selectedColor: blueprintColor,
720
+ selectedHidden: blueprintHidden,
721
+ selectedBlocks: userBlocks,
722
+ selectedIslands: userIslands,
723
+ selectedFunctions: blueprintFunctions,
724
+ selectedVariables: blueprintVariables,
725
+ selectedImports: blueprintImports,
726
+ selectedPointers: blueprintPointers,
727
+ global: globalBlueprint,
728
+ locals: localBlueprints,
729
+ }),
730
+ [
731
+ blueprintColor,
732
+ blueprintFunctions,
733
+ blueprintHidden,
734
+ blueprintImports,
735
+ blueprintPointers,
736
+ blueprintVariables,
737
+ globalBlueprint,
738
+ localBlueprints,
739
+ userBlocks,
740
+ userIslands,
741
+ ],
742
+ )
743
+ const blueprintOptions = useMemo(
744
+ () => blueprintOptionsFrom(intents, localBlueprints),
745
+ [intents, localBlueprints],
746
+ )
747
+ const blueprintColorPointers = useMemo(
748
+ () =>
749
+ blueprintOptions.map((option) => ({
750
+ ...option,
751
+ pointers:
752
+ option.id === blueprintColor
753
+ ? blueprintPointers
754
+ : option.id === GLOBAL_BLUEPRINT_COLOR.id
755
+ ? globalBlueprint.pointers
756
+ : (localBlueprints.find((item) => item.color === option.id)
757
+ ?.pointers ?? []),
758
+ })),
759
+ [
760
+ blueprintColor,
761
+ blueprintOptions,
762
+ blueprintPointers,
763
+ globalBlueprint.pointers,
764
+ localBlueprints,
765
+ ],
766
+ )
767
+ const pointedColors = useMemo(
768
+ () => pointerColorMaps(mapBlueprint.pointers),
769
+ [mapBlueprint.pointers],
770
+ )
771
+ const pendingBlocks = mapBlueprint.blocks.filter(
266
772
  (block) => block.naming || !knownFileIds.has(block.id),
267
773
  )
268
- const overlayBlocks = visibleBlocks.filter(
774
+ const overlayBlocks = mapBlueprint.blocks.filter(
269
775
  (block) => !block.naming && knownFileIds.has(block.id),
270
776
  )
777
+ const visibleIslands = mapBlueprint.islands
271
778
  const displayGraph = useMemo(
272
779
  () =>
273
780
  withBlueprintIntent(
274
781
  withUserCreatedGraph(previewGraph, pendingBlocks, visibleIslands),
275
- blueprintFunctions,
276
- blueprintVariables,
277
- blueprintImports,
782
+ mapBlueprint.functions,
783
+ mapBlueprint.variables,
784
+ mapBlueprint.imports,
278
785
  ),
279
786
  [
280
- blueprintFunctions,
281
- blueprintImports,
282
- blueprintVariables,
787
+ mapBlueprint.functions,
788
+ mapBlueprint.imports,
789
+ mapBlueprint.variables,
283
790
  pendingBlocks,
284
791
  previewGraph,
285
792
  visibleIslands,
@@ -303,38 +810,34 @@ function Explorer({
303
810
  for (const id of changeSet.files) ids.add(id)
304
811
  for (const id of changeSet.creates) ids.add(id)
305
812
  for (const id of changeSet.deletes) ids.add(id)
306
- for (const block of userBlocks) ids.add(block.id)
307
- for (const item of blueprintFunctions) ids.add(item.file)
308
- for (const item of blueprintVariables) ids.add(item.file)
309
- for (const item of blueprintImports) ids.add(item.file)
813
+ for (const block of mapBlueprint.blocks) ids.add(block.id)
814
+ for (const item of mapBlueprint.functions) ids.add(item.file)
815
+ for (const item of mapBlueprint.variables) ids.add(item.file)
816
+ for (const item of mapBlueprint.imports) ids.add(item.file)
310
817
  for (const note of blueprintNotes) ids.add(note.file)
311
- for (const pointer of blueprintPointers) {
818
+ for (const pointer of mapBlueprint.pointers) {
312
819
  if (pointer.kind !== 'folder') ids.add(pointer.path)
313
820
  }
314
821
  return [...ids]
315
822
  }, [
316
- blueprintFunctions,
317
- blueprintImports,
318
823
  blueprintNotes,
319
- blueprintPointers,
320
- blueprintVariables,
321
824
  changeSet.creates,
322
825
  changeSet.deletes,
323
826
  changeSet.files,
324
- userBlocks,
827
+ mapBlueprint,
325
828
  ])
326
829
  const changeFolderPaths = useMemo(() => {
327
830
  const paths = new Set<string>()
328
831
  for (const path of changeSet.createFolders ?? []) paths.add(path)
329
- for (const island of userIslands) {
832
+ for (const island of mapBlueprint.islands) {
330
833
  if (island.path) paths.add(island.path)
331
834
  else if (island.id) paths.add(island.id)
332
835
  }
333
- for (const pointer of blueprintPointers) {
836
+ for (const pointer of mapBlueprint.pointers) {
334
837
  if (pointer.kind === 'folder') paths.add(pointer.path)
335
838
  }
336
839
  return [...paths]
337
- }, [blueprintPointers, changeSet.createFolders, userIslands])
840
+ }, [changeSet.createFolders, mapBlueprint])
338
841
  const hasChangeSet =
339
842
  changeFileIds.length > 0 || changeFolderPaths.length > 0
340
843
  const changePathGraph = useMemo(() => {
@@ -350,7 +853,7 @@ function Explorer({
350
853
  const world = layoutWorld(changePathGraph)
351
854
  markCreatedFolders(world, [
352
855
  ...(changeSet.createFolders ?? []),
353
- ...userIslands.map((island) => island.path || island.id),
856
+ ...mapBlueprint.islands.map((island) => island.path || island.id),
354
857
  ])
355
858
  return world
356
859
  }, [
@@ -358,9 +861,20 @@ function Explorer({
358
861
  changeSet.createFolders,
359
862
  hasChangeSet,
360
863
  layout,
361
- userIslands,
864
+ mapBlueprint.islands,
362
865
  ])
363
866
  const [mode, setMode] = useState<ViewMode>('map')
867
+ const [explain, setExplain] = useState<ExplainSession>(emptyExplain)
868
+ const [dismissedCardQuestion, setDismissedCardQuestion] = useState<
869
+ string | null
870
+ >(null)
871
+ const dismissedCardQuestionRef = useRef<string | null>(null)
872
+ dismissedCardQuestionRef.current = dismissedCardQuestion
873
+ const cardExplain = explainIsCard(explain)
874
+ const explaining =
875
+ explain.active &&
876
+ !cardExplain &&
877
+ explain.question !== dismissedCardQuestion
364
878
  const [landAt, setLandAt] = useState<[number, number]>([
365
879
  layout.spawn[0],
366
880
  layout.spawn[2],
@@ -369,13 +883,16 @@ function Explorer({
369
883
  const [selectedId, setSelectedId] = useState<string | null>(null)
370
884
  const [selectedTick, setSelectedTick] = useState(0)
371
885
  const [selectedFolder, setSelectedFolder] = useState<string | null>(null)
886
+ const [importPickFrom, setImportPickFrom] = useState<string | null>(null)
887
+ const importPickFromRef = useRef<string | null>(null)
888
+ importPickFromRef.current = importPickFrom
889
+ const pickImportTargetRef = useRef<(fileId: string) => void>(() => {})
372
890
  const [mapMenu, setMapMenu] = useState<MapContextMenuState | null>(null)
373
891
  const [aimedRelation, setAimedRelation] = useState<AimedRelation | null>(null)
374
892
  const [aimedFileId, setAimedFileId] = useState<string | null>(null)
375
893
  const [inspectTick, setInspectTick] = useState(0)
376
894
  const [flyTo, setFlyTo] = useState<FlyTo | null>(null)
377
895
  const [locked, setLocked] = useState(false)
378
- const [followLook, setFollowLook] = useState(false)
379
896
  const [importedBy, setImportedBy] = useState(false)
380
897
  const [changePathsOnly, setChangePathsOnly] = useState(false)
381
898
  const lastIntentSig = useRef<string | null>(null)
@@ -411,14 +928,6 @@ function Explorer({
411
928
  persistSessionFocus(sessionId)
412
929
  }, [])
413
930
 
414
- const setupLlmSession = useCallback(async () => {
415
- const next = await setupVisualizerSession()
416
- lastIntentSig.current = null
417
- applyIntent(next, next.sessionId ?? undefined)
418
- if (next.sessionId) setFocusedSessionId(next.sessionId)
419
- return next
420
- }, [applyIntent])
421
-
422
931
  const rememberWalk = useCallback((x: number, z: number) => {
423
932
  walkPos.current = [x, z]
424
933
  }, [])
@@ -431,10 +940,12 @@ function Explorer({
431
940
  }, [])
432
941
 
433
942
  const openWalk = useCallback(() => {
943
+ if (explaining) return
434
944
  setMode('walk')
435
- }, [])
945
+ }, [explaining])
436
946
 
437
947
  const toggleMap = useCallback(() => {
948
+ if (explaining) return
438
949
  if (mode === 'walk') {
439
950
  setLandAt(walkPos.current)
440
951
  setFlyTo(null)
@@ -444,14 +955,15 @@ function Explorer({
444
955
  return
445
956
  }
446
957
  setMode('walk')
447
- }, [mode])
958
+ }, [explaining, mode])
448
959
 
449
960
  const land = useCallback((x: number, z: number) => {
961
+ if (explaining) return
450
962
  walkPos.current = [x, z]
451
963
  setFlyTo(null)
452
964
  setLandAt([x, z])
453
965
  setMode('walk')
454
- }, [])
966
+ }, [explaining])
455
967
 
456
968
  const landFromMap = useCallback(
457
969
  (x: number, z: number) => {
@@ -516,23 +1028,13 @@ function Explorer({
516
1028
  return
517
1029
  }
518
1030
  try {
1031
+ if (action === 'blueprint_send') persistBlueprintRef.current()
519
1032
  const next = await performAgentAction(
520
1033
  action,
521
1034
  current.sessionId,
522
1035
  {
523
1036
  ...options,
524
1037
  diffId: current.diffId ?? undefined,
525
- ...(action === 'blueprint_send'
526
- ? {
527
- userCreatedBlocks: namedCreatedBlocks(userBlocks),
528
- userCreatedIslands: namedCreatedIslands(userIslands),
529
- addedFunctions: blueprintFunctions,
530
- addedVariables: blueprintVariables,
531
- addedImports: blueprintImports,
532
- notes: blueprintNotes,
533
- pointers: blueprintPointers,
534
- }
535
- : {}),
536
1038
  },
537
1039
  )
538
1040
  browsingHistory.current[sessionId] = false
@@ -547,19 +1049,13 @@ function Explorer({
547
1049
  }
548
1050
  } catch {
549
1051
  // Keep the pending patch visible if apply failed.
1052
+ return false
550
1053
  }
551
1054
  },
552
1055
  [
553
1056
  applyIntent,
554
- blueprintFunctions,
555
- blueprintImports,
556
- blueprintNotes,
557
- blueprintPointers,
558
- blueprintVariables,
559
1057
  intents,
560
1058
  onRefreshGraph,
561
- userBlocks,
562
- userIslands,
563
1059
  ],
564
1060
  )
565
1061
 
@@ -619,9 +1115,6 @@ function Explorer({
619
1115
  let cancelled = false
620
1116
  void fetchUserContext().then((context) => {
621
1117
  if (cancelled) return
622
- if (typeof context?.followLook === 'boolean') {
623
- setFollowLook(context.followLook)
624
- }
625
1118
  if (typeof context?.showBranchChanges === 'boolean') {
626
1119
  setWantBranchChanges(context.showBranchChanges)
627
1120
  }
@@ -631,6 +1124,171 @@ function Explorer({
631
1124
  }
632
1125
  }, [])
633
1126
 
1127
+ useEffect(() => {
1128
+ let cancelled = false
1129
+ const poll = async () => {
1130
+ const next = await fetchExplain()
1131
+ if (cancelled) return
1132
+ setExplain((current) => {
1133
+ if (
1134
+ current.presentation === 'card' &&
1135
+ current.active &&
1136
+ current.pendingStart &&
1137
+ !next.active
1138
+ ) {
1139
+ return current
1140
+ }
1141
+ const dismissed = dismissedCardQuestionRef.current
1142
+ if (
1143
+ next.presentation === 'card' &&
1144
+ next.active &&
1145
+ dismissed &&
1146
+ next.question === dismissed
1147
+ ) {
1148
+ return current.active ? current : emptyExplain()
1149
+ }
1150
+ return next
1151
+ })
1152
+ }
1153
+ void poll()
1154
+ const timer = window.setInterval(() => {
1155
+ void poll()
1156
+ }, 250)
1157
+ return () => {
1158
+ cancelled = true
1159
+ window.clearInterval(timer)
1160
+ }
1161
+ }, [])
1162
+
1163
+ useEffect(() => {
1164
+ if (explain.active && mode !== 'map') openMap()
1165
+ }, [explain.active, mode, openMap])
1166
+
1167
+ useEffect(() => {
1168
+ if (
1169
+ explain.active &&
1170
+ explain.presentation !== 'card' &&
1171
+ dismissedCardQuestion &&
1172
+ explain.question !== dismissedCardQuestion
1173
+ ) {
1174
+ setDismissedCardQuestion(null)
1175
+ }
1176
+ }, [
1177
+ dismissedCardQuestion,
1178
+ explain.active,
1179
+ explain.presentation,
1180
+ explain.question,
1181
+ ])
1182
+
1183
+ const goExplainStep = useCallback((step: string) => {
1184
+ setExplain((current) =>
1185
+ current.active ? { ...current, currentStep: step } : current,
1186
+ )
1187
+ void persistExplainStep(step)
1188
+ }, [])
1189
+
1190
+ const askExplainStep = useCallback((step: string, question: string) => {
1191
+ const parent = topLevelExplainStepId(step)
1192
+ setExplain((current) => {
1193
+ if (!current.active) return current
1194
+ const clicked = current.steps.find((item) => item.index === step)
1195
+ return {
1196
+ ...current,
1197
+ steps: stripExplainSubSteps(current.steps),
1198
+ currentStep: parent,
1199
+ pendingQuestion: {
1200
+ parent,
1201
+ question,
1202
+ from: step,
1203
+ fromTitle: clicked?.title ?? '',
1204
+ },
1205
+ answering: false,
1206
+ }
1207
+ })
1208
+ void persistExplainAsk(step, question)
1209
+ }, [])
1210
+
1211
+ const exitExplain = useCallback(() => {
1212
+ setExplain(emptyExplain())
1213
+ void persistExplainStop()
1214
+ }, [])
1215
+
1216
+ const closeAskCard = useCallback(() => {
1217
+ setDismissedCardQuestion(explain.question)
1218
+ exitExplain()
1219
+ }, [exitExplain, explain.question])
1220
+
1221
+ const startExplainTarget = useCallback(
1222
+ (input: { kind: ExplainTargetKind; path: string; name?: string }) => {
1223
+ const target = input.path.trim()
1224
+ const name = input.name?.trim()
1225
+ if (!target) return
1226
+ if (
1227
+ (input.kind === 'function' ||
1228
+ input.kind === 'variable' ||
1229
+ input.kind === 'class') &&
1230
+ !name
1231
+ ) {
1232
+ return
1233
+ }
1234
+ const question = explainTargetQuestion(input.kind, target, name)
1235
+ setDismissedCardQuestion(null)
1236
+ setExplain({
1237
+ active: true,
1238
+ question,
1239
+ steps: [],
1240
+ currentStep: '1',
1241
+ pendingQuestion: null,
1242
+ pendingStart: name
1243
+ ? { kind: input.kind, path: target, name, question }
1244
+ : { kind: input.kind, path: target, question },
1245
+ answering: false,
1246
+ presentation: 'card',
1247
+ updatedAt: new Date().toISOString(),
1248
+ })
1249
+ void persistExplainStart({
1250
+ kind: input.kind,
1251
+ path: target,
1252
+ name,
1253
+ sessionId: focusedSessionId ?? intent.sessionId,
1254
+ })
1255
+ },
1256
+ [focusedSessionId, intent.sessionId],
1257
+ )
1258
+
1259
+ const rememberLiveBlueprint = useCallback(
1260
+ (
1261
+ color: string = blueprintColorRef.current,
1262
+ snapshot: {
1263
+ hidden?: boolean
1264
+ blocks?: UserCreatedBlock[]
1265
+ islands?: UserCreatedIsland[]
1266
+ functions?: PatchSymbolAddition[]
1267
+ variables?: PatchSymbolAddition[]
1268
+ imports?: PatchImportAddition[]
1269
+ notes?: BlueprintNote[]
1270
+ pointers?: BlueprintPointer[]
1271
+ } = {},
1272
+ ) => {
1273
+ const next = withCapturedBlueprint(latestBlueprintsRef.current, {
1274
+ color,
1275
+ hidden: snapshot.hidden ?? blueprintHiddenRef.current,
1276
+ blocks: snapshot.blocks ?? userBlocksRef.current,
1277
+ islands: snapshot.islands ?? userIslandsRef.current,
1278
+ functions: snapshot.functions ?? blueprintFunctionsRef.current,
1279
+ variables: snapshot.variables ?? blueprintVariablesRef.current,
1280
+ imports: snapshot.imports ?? blueprintImportsRef.current,
1281
+ notes: snapshot.notes ?? blueprintNotesRef.current,
1282
+ pointers: snapshot.pointers ?? blueprintPointersRef.current,
1283
+ options: blueprintOptionsRef.current,
1284
+ })
1285
+ latestBlueprintsRef.current = next
1286
+ setGlobalBlueprint(next.global)
1287
+ setLocalBlueprints(next.locals)
1288
+ },
1289
+ [],
1290
+ )
1291
+
634
1292
  const persistBlueprint = useCallback(
635
1293
  (
636
1294
  blocks: UserCreatedBlock[] = userBlocksRef.current,
@@ -640,7 +1298,17 @@ function Explorer({
640
1298
  imports: PatchImportAddition[] = blueprintImportsRef.current,
641
1299
  notes: BlueprintNote[] = blueprintNotesRef.current,
642
1300
  pointers: BlueprintPointer[] = blueprintPointersRef.current,
1301
+ color: string = blueprintColorRef.current,
643
1302
  ) => {
1303
+ rememberLiveBlueprint(color, {
1304
+ blocks,
1305
+ islands,
1306
+ functions,
1307
+ variables,
1308
+ imports,
1309
+ notes,
1310
+ pointers,
1311
+ })
644
1312
  if (notePersistTimer.current != null) {
645
1313
  window.clearTimeout(notePersistTimer.current)
646
1314
  notePersistTimer.current = null
@@ -648,6 +1316,7 @@ function Explorer({
648
1316
  const gen = ++blueprintPersistGen.current
649
1317
  notesDirty.current = true
650
1318
  void persistSessionBlueprint(intent.sessionId, {
1319
+ color,
651
1320
  userCreatedBlocks: namedCreatedBlocks(blocks),
652
1321
  userCreatedIslands: namedCreatedIslands(islands),
653
1322
  addedFunctions: functions,
@@ -661,8 +1330,9 @@ function Explorer({
661
1330
  notesDirty.current = false
662
1331
  })
663
1332
  },
664
- [intent.sessionId],
1333
+ [intent.sessionId, rememberLiveBlueprint],
665
1334
  )
1335
+ persistBlueprintRef.current = () => persistBlueprint()
666
1336
 
667
1337
  const persistBlueprintSoon = useCallback(() => {
668
1338
  notesDirty.current = true
@@ -681,6 +1351,7 @@ function Explorer({
681
1351
  notePersistTimer.current = null
682
1352
  if (!notesDirty.current) return
683
1353
  persistSessionBlueprint(intent.sessionId, {
1354
+ color: blueprintColorRef.current,
684
1355
  userCreatedBlocks: namedCreatedBlocks(userBlocksRef.current),
685
1356
  userCreatedIslands: namedCreatedIslands(userIslandsRef.current),
686
1357
  addedFunctions: blueprintFunctionsRef.current,
@@ -713,8 +1384,14 @@ function Explorer({
713
1384
  kind: BlueprintPointerKind
714
1385
  path: string
715
1386
  name?: string
1387
+ color?: string
716
1388
  }) => {
717
- const pointers = toggleBlueprintPointer(blueprintPointersRef.current, next)
1389
+ const { color, ...pointer } = next
1390
+ if (color) selectBlueprintColorRef.current(color)
1391
+ const pointers = toggleBlueprintPointer(
1392
+ blueprintPointersRef.current,
1393
+ pointer,
1394
+ )
718
1395
  blueprintPointersRef.current = pointers
719
1396
  setBlueprintPointers(pointers)
720
1397
  persistBlueprint(
@@ -798,6 +1475,57 @@ function Explorer({
798
1475
  setUserBlocks((current) => current.filter((block) => block.id !== id))
799
1476
  }, [])
800
1477
 
1478
+ const renameCreatedBlock = useCallback(
1479
+ (id: string, rawName: string) => {
1480
+ const current = userBlocks.find((block) => block.id === id)
1481
+ if (!current || current.naming) return null
1482
+ const resolved = resolveCreatedFile(rawName, current.folder)
1483
+ if (!resolved) return null
1484
+ if (resolved.id === id) return id
1485
+ const taken =
1486
+ graph.files.some((file) => file.id === resolved.id) ||
1487
+ userBlocks.some((block) => block.id === resolved.id && block.id !== id)
1488
+ if (taken) return null
1489
+ const nextBlocks = userBlocks.map((block) =>
1490
+ block.id === id
1491
+ ? {
1492
+ ...resolved,
1493
+ x: current.x,
1494
+ z: current.z,
1495
+ colorHex: current.colorHex,
1496
+ }
1497
+ : block,
1498
+ )
1499
+ const remapped = remapBlueprintFileId(id, resolved.id, {
1500
+ functions: blueprintFunctionsRef.current,
1501
+ variables: blueprintVariablesRef.current,
1502
+ imports: blueprintImportsRef.current,
1503
+ notes: blueprintNotesRef.current,
1504
+ pointers: blueprintPointersRef.current,
1505
+ })
1506
+ blueprintNotesRef.current = remapped.notes
1507
+ blueprintPointersRef.current = remapped.pointers
1508
+ setUserBlocks(nextBlocks)
1509
+ setBlueprintFunctions(remapped.functions)
1510
+ setBlueprintVariables(remapped.variables)
1511
+ setBlueprintImports(remapped.imports)
1512
+ setBlueprintNotes(remapped.notes)
1513
+ setBlueprintPointers(remapped.pointers)
1514
+ persistBlueprint(
1515
+ nextBlocks,
1516
+ userIslands,
1517
+ remapped.functions,
1518
+ remapped.variables,
1519
+ remapped.imports,
1520
+ remapped.notes,
1521
+ remapped.pointers,
1522
+ )
1523
+ if (selectedId === id) setSelectedId(resolved.id)
1524
+ return resolved.id
1525
+ },
1526
+ [graph.files, persistBlueprint, selectedId, userBlocks, userIslands],
1527
+ )
1528
+
801
1529
  const placeIsland = useCallback(
802
1530
  (parent: string) => {
803
1531
  if (!canPlace) return
@@ -901,6 +1629,11 @@ function Explorer({
901
1629
 
902
1630
  const selectFile = useCallback(
903
1631
  (fileId: string | null) => {
1632
+ const pickFrom = importPickFromRef.current
1633
+ if (pickFrom) {
1634
+ if (fileId && fileId !== pickFrom) pickImportTargetRef.current(fileId)
1635
+ return
1636
+ }
904
1637
  setSelectedId(fileId)
905
1638
  if (fileId) {
906
1639
  setSelectedFolder(null)
@@ -920,6 +1653,7 @@ function Explorer({
920
1653
  )
921
1654
 
922
1655
  const selectFolder = useCallback((folderPath: string | null) => {
1656
+ if (importPickFromRef.current) return
923
1657
  setSelectedFolder(folderPath)
924
1658
  if (folderPath) setSelectedId(null)
925
1659
  }, [])
@@ -1047,6 +1781,23 @@ function Explorer({
1047
1781
  ],
1048
1782
  )
1049
1783
 
1784
+ pickImportTargetRef.current = (fileId: string) => {
1785
+ const from = importPickFromRef.current
1786
+ if (!from || fileId.startsWith('draft:')) return
1787
+ const target = displayGraph.files.find((file) => file.id === fileId)
1788
+ if (!target) return
1789
+ addBlueprintImport(from, blueprintImportRawFromFile(target))
1790
+ setImportPickFrom(null)
1791
+ }
1792
+
1793
+ const cancelImportPick = useCallback(() => {
1794
+ setImportPickFrom(null)
1795
+ }, [])
1796
+
1797
+ const toggleImportPick = useCallback(() => {
1798
+ setImportPickFrom((current) => (current ? null : selectedId))
1799
+ }, [selectedId])
1800
+
1050
1801
  const removeBlueprintFunction = useCallback(
1051
1802
  (fileId: string, name: string) => {
1052
1803
  if (!canPlace) return
@@ -1175,6 +1926,25 @@ function Explorer({
1175
1926
  return () => window.removeEventListener('keydown', onKey)
1176
1927
  }, [cancelBlockName, cancelIslandName, namingId, namingIslandId])
1177
1928
 
1929
+ useEffect(() => {
1930
+ if (mode !== 'map' || importedBy || !selectedId || namingId) {
1931
+ setImportPickFrom(null)
1932
+ }
1933
+ }, [importedBy, mode, namingId, selectedId])
1934
+
1935
+ useEffect(() => {
1936
+ if (!importPickFrom) return
1937
+ const onKey = (event: KeyboardEvent) => {
1938
+ if (event.code !== 'Escape') return
1939
+ if (isKeyboardIsolated()) return
1940
+ event.preventDefault()
1941
+ event.stopPropagation()
1942
+ setImportPickFrom(null)
1943
+ }
1944
+ window.addEventListener('keydown', onKey, true)
1945
+ return () => window.removeEventListener('keydown', onKey, true)
1946
+ }, [importPickFrom])
1947
+
1178
1948
  useEffect(() => {
1179
1949
  if (mode !== 'map' || naming) setMapMenu(null)
1180
1950
  }, [mode, naming])
@@ -1190,14 +1960,6 @@ function Explorer({
1190
1960
  return () => window.removeEventListener('keydown', onKey)
1191
1961
  }, [deleteSelectedCreatedBlock])
1192
1962
 
1193
- const toggleFollowLook = useCallback(() => {
1194
- setFollowLook((current) => {
1195
- const next = !current
1196
- persistFollowLook(next)
1197
- return next
1198
- })
1199
- }, [])
1200
-
1201
1963
  const canToggleBranchChanges = !llmBusy && (wantBranchChanges || branchChanges.available)
1202
1964
 
1203
1965
  const toggleShowBranchChanges = useCallback(() => {
@@ -1275,40 +2037,74 @@ function Explorer({
1275
2037
  return () => window.removeEventListener('keydown', onKey)
1276
2038
  }, [hasChangeSet, mode, toggleChangePathsOnly])
1277
2039
 
2040
+ const applyBlueprintContents = useCallback(
2041
+ (blueprint: SharedBlueprint, keepDrafts = true) => {
2042
+ blueprintHiddenRef.current = Boolean(blueprint.hidden)
2043
+ setBlueprintHidden(Boolean(blueprint.hidden))
2044
+ const nextBlocks = parseUserCreatedBlocks(blueprint.userCreatedBlocks)
2045
+ const nextIslands = parseUserCreatedIslands(blueprint.userCreatedIslands)
2046
+ setUserBlocks((current) => {
2047
+ if (!keepDrafts) return nextBlocks
2048
+ const drafts = current.filter((block) => block.naming)
2049
+ if (drafts.length === 0) return nextBlocks
2050
+ const namedIds = new Set(drafts.map((block) => block.id))
2051
+ return [...nextBlocks.filter((block) => !namedIds.has(block.id)), ...drafts]
2052
+ })
2053
+ setUserIslands((current) => {
2054
+ if (!keepDrafts) return nextIslands
2055
+ const drafts = current.filter((island) => island.naming)
2056
+ if (drafts.length === 0) return nextIslands
2057
+ const namedIds = new Set(drafts.map((island) => island.id))
2058
+ return [...nextIslands.filter((island) => !namedIds.has(island.id)), ...drafts]
2059
+ })
2060
+ setBlueprintFunctions(blueprint.addedFunctions)
2061
+ setBlueprintVariables(blueprint.addedVariables)
2062
+ setBlueprintImports(blueprint.addedImports)
2063
+ if (!keepDrafts || !notesDirty.current) {
2064
+ const nextPointers = parseBlueprintPointers(blueprint.pointers)
2065
+ blueprintPointersRef.current = nextPointers
2066
+ setBlueprintPointers(nextPointers)
2067
+ }
2068
+ if (!keepDrafts || (!notesDirty.current && !isKeyboardIsolated())) {
2069
+ const nextNotes = parseBlueprintNotes(blueprint.notes)
2070
+ blueprintNotesRef.current = nextNotes
2071
+ setBlueprintNotes(nextNotes)
2072
+ }
2073
+ },
2074
+ [],
2075
+ )
2076
+
1278
2077
  useEffect(() => {
1279
2078
  let cancelled = false
1280
2079
  const poll = async () => {
1281
2080
  try {
1282
2081
  const bundle = await fetchAgentIntents()
1283
2082
  if (cancelled) return
1284
- setBlueprintHidden(Boolean(bundle.blueprint.hidden))
1285
- const nextBlocks = parseUserCreatedBlocks(bundle.blueprint.userCreatedBlocks)
1286
- const nextIslands = parseUserCreatedIslands(bundle.blueprint.userCreatedIslands)
1287
- setUserBlocks((current) => {
1288
- const drafts = current.filter((block) => block.naming)
1289
- if (drafts.length === 0) return nextBlocks
1290
- const namedIds = new Set(drafts.map((block) => block.id))
1291
- return [...nextBlocks.filter((block) => !namedIds.has(block.id)), ...drafts]
1292
- })
1293
- setUserIslands((current) => {
1294
- const drafts = current.filter((island) => island.naming)
1295
- if (drafts.length === 0) return nextIslands
1296
- const namedIds = new Set(drafts.map((island) => island.id))
1297
- return [...nextIslands.filter((island) => !namedIds.has(island.id)), ...drafts]
2083
+ const nextBlueprints = withPolledBlueprints(latestBlueprintsRef.current, {
2084
+ global: bundle.blueprint,
2085
+ locals: bundle.localBlueprints,
1298
2086
  })
1299
- setBlueprintFunctions(bundle.blueprint.addedFunctions)
1300
- setBlueprintVariables(bundle.blueprint.addedVariables)
1301
- setBlueprintImports(bundle.blueprint.addedImports)
1302
- if (!notesDirty.current) {
1303
- const nextPointers = parseBlueprintPointers(bundle.blueprint.pointers)
1304
- blueprintPointersRef.current = nextPointers
1305
- setBlueprintPointers(nextPointers)
1306
- }
1307
- if (!notesDirty.current && !isKeyboardIsolated()) {
1308
- const nextNotes = parseBlueprintNotes(bundle.blueprint.notes)
1309
- blueprintNotesRef.current = nextNotes
1310
- setBlueprintNotes(nextNotes)
2087
+ latestBlueprintsRef.current = nextBlueprints
2088
+ setGlobalBlueprint(nextBlueprints.global)
2089
+ setLocalBlueprints(nextBlueprints.locals)
2090
+ const colorIds = new Set([
2091
+ GLOBAL_BLUEPRINT_COLOR.id,
2092
+ ...bundle.localBlueprints.map((item) => item.color),
2093
+ ...bundle.intents
2094
+ .map((item) => item.color)
2095
+ .filter((id): id is string => Boolean(id)),
2096
+ ])
2097
+ if (!colorIds.has(blueprintColorRef.current)) {
2098
+ blueprintColorRef.current = GLOBAL_BLUEPRINT_COLOR.id
2099
+ setBlueprintColor(GLOBAL_BLUEPRINT_COLOR.id)
1311
2100
  }
2101
+ applyBlueprintContents(
2102
+ blueprintForColor(
2103
+ blueprintColorRef.current,
2104
+ nextBlueprints.global,
2105
+ nextBlueprints.locals,
2106
+ ),
2107
+ )
1312
2108
  setNextAttachSessionId(bundle.nextAttachSessionId)
1313
2109
  const merged: AgentIntent[] = []
1314
2110
  for (const next of bundle.intents) {
@@ -1369,10 +2165,10 @@ function Explorer({
1369
2165
  cancelled = true
1370
2166
  window.clearInterval(timer)
1371
2167
  }
1372
- }, [])
2168
+ }, [applyBlueprintContents])
1373
2169
 
1374
2170
  const plannedIds = previewing ? [...changeSet.files, ...changeSet.creates] : []
1375
- const blueprintImportEdges = blueprintImports.flatMap((item) => {
2171
+ const blueprintImportEdges = mapBlueprint.imports.flatMap((item) => {
1376
2172
  if (!displayGraph.files.some((file) => file.id === item.from)) return []
1377
2173
  return [{ from: item.file, to: item.from }]
1378
2174
  })
@@ -1394,12 +2190,67 @@ function Explorer({
1394
2190
  userIslands.some(
1395
2191
  (island) => !island.naming && knownFolderPaths.has(island.path),
1396
2192
  )
2193
+ const canAskLlm =
2194
+ Boolean(intent.sessionId) &&
2195
+ isReviewingIntent(intent.status) &&
2196
+ intent.awaitingAttach === false &&
2197
+ !intent.llmIdle
2198
+
2199
+ const explainStep = currentExplainStep(explain)
2200
+ const explainView = useMemo(
2201
+ () => (explaining ? explainFocus(explainStep, displayGraph) : null),
2202
+ [displayGraph, explainStep, explaining],
2203
+ )
2204
+ const mapLayoutForView =
2205
+ !explaining && changePathsOnly && hasChangeSet ? changePathLayout : layout
2206
+ const explainBounds = useMemo(() => {
2207
+ if (!explaining || !explainView) return null
2208
+ if (
2209
+ explainView.zoomFiles.length === 0 &&
2210
+ explainView.zoomFolders.length === 0
2211
+ ) {
2212
+ return null
2213
+ }
2214
+ return regionBounds(
2215
+ mapLayoutForView,
2216
+ explainView.zoomFiles,
2217
+ explainView.zoomFolders,
2218
+ )
2219
+ }, [explainView, explaining, mapLayoutForView])
2220
+ const mapSelectedId = explaining ? (explainView?.select ?? null) : selectedId
2221
+ const mapImportedBy = explaining ? Boolean(explainView?.importedBy) : importedBy
2222
+ const mapSelectedFolder = explaining ? null : selectedFolder
2223
+ const explainFile = explaining ? explainInfoFile(explainStep, displayGraph) : null
1397
2224
 
1398
2225
  const toggleBlueprintHidden = useCallback(() => {
1399
2226
  const next = !blueprintHidden
2227
+ blueprintHiddenRef.current = next
1400
2228
  setBlueprintHidden(next)
1401
- void persistBlueprintHidden(next).catch(() => setBlueprintHidden(!next))
1402
- }, [blueprintHidden])
2229
+ rememberLiveBlueprint(blueprintColorRef.current, { hidden: next })
2230
+ void persistBlueprintHidden(next, blueprintColorRef.current).catch(() => {
2231
+ blueprintHiddenRef.current = !next
2232
+ setBlueprintHidden(!next)
2233
+ })
2234
+ }, [blueprintHidden, rememberLiveBlueprint])
2235
+
2236
+ const selectBlueprintColor = useCallback(
2237
+ (color: string) => {
2238
+ if (color === blueprintColorRef.current) return
2239
+ persistBlueprint()
2240
+ blueprintColorRef.current = color
2241
+ setBlueprintColor(color)
2242
+ applyBlueprintContents(
2243
+ blueprintForColor(
2244
+ color,
2245
+ latestBlueprintsRef.current.global,
2246
+ latestBlueprintsRef.current.locals,
2247
+ ),
2248
+ false,
2249
+ )
2250
+ },
2251
+ [applyBlueprintContents, persistBlueprint],
2252
+ )
2253
+ selectBlueprintColorRef.current = selectBlueprintColor
1403
2254
 
1404
2255
  const clearSharedBlueprint = useCallback(() => {
1405
2256
  setUserBlocks([])
@@ -1411,41 +2262,72 @@ function Explorer({
1411
2262
  blueprintPointersRef.current = []
1412
2263
  setBlueprintNotes([])
1413
2264
  setBlueprintPointers([])
1414
- void persistBlueprintClear()
1415
- }, [])
2265
+ rememberLiveBlueprint(blueprintColorRef.current, {
2266
+ blocks: [],
2267
+ islands: [],
2268
+ functions: [],
2269
+ variables: [],
2270
+ imports: [],
2271
+ notes: [],
2272
+ pointers: [],
2273
+ })
2274
+ void persistBlueprintClear(blueprintColorRef.current)
2275
+ }, [rememberLiveBlueprint])
1416
2276
 
1417
2277
  const cleanupSharedBlueprint = useCallback(() => {
1418
2278
  const files = knownFileIds
1419
2279
  const folders = knownFolderPaths
2280
+ const nextBlocks = userBlocks.filter(
2281
+ (block) => block.naming || !files.has(block.id),
2282
+ )
2283
+ const nextIslands = userIslands.filter(
2284
+ (island) => island.naming || !folders.has(island.path),
2285
+ )
1420
2286
  const removed = new Set(
1421
2287
  userBlocks
1422
2288
  .filter((block) => !block.naming && files.has(block.id))
1423
2289
  .map((block) => block.id),
1424
2290
  )
1425
- setUserBlocks((current) =>
1426
- current.filter((block) => block.naming || !files.has(block.id)),
1427
- )
1428
- setUserIslands((current) =>
1429
- current.filter((island) => island.naming || !folders.has(island.path)),
2291
+ const nextFunctions = blueprintFunctions.filter(
2292
+ (item) => !removed.has(item.file),
1430
2293
  )
1431
- setBlueprintFunctions((current) =>
1432
- current.filter((item) => !removed.has(item.file)),
2294
+ const nextVariables = blueprintVariables.filter(
2295
+ (item) => !removed.has(item.file),
1433
2296
  )
1434
- setBlueprintVariables((current) =>
1435
- current.filter((item) => !removed.has(item.file)),
1436
- )
1437
- setBlueprintImports((current) =>
1438
- current.filter((item) => !removed.has(item.file)),
2297
+ const nextImports = blueprintImports.filter(
2298
+ (item) => !removed.has(item.file),
1439
2299
  )
2300
+ setUserBlocks(nextBlocks)
2301
+ setUserIslands(nextIslands)
2302
+ setBlueprintFunctions(nextFunctions)
2303
+ setBlueprintVariables(nextVariables)
2304
+ setBlueprintImports(nextImports)
1440
2305
  const notes = dropBlueprintFileNotes(blueprintNotesRef.current, removed)
1441
2306
  blueprintNotesRef.current = notes
1442
2307
  setBlueprintNotes(notes)
1443
- void persistBlueprintCleanup()
1444
- }, [knownFileIds, knownFolderPaths, userBlocks])
2308
+ rememberLiveBlueprint(blueprintColorRef.current, {
2309
+ blocks: nextBlocks,
2310
+ islands: nextIslands,
2311
+ functions: nextFunctions,
2312
+ variables: nextVariables,
2313
+ imports: nextImports,
2314
+ notes,
2315
+ })
2316
+ void persistBlueprintCleanup(blueprintColorRef.current)
2317
+ }, [
2318
+ blueprintFunctions,
2319
+ blueprintImports,
2320
+ blueprintVariables,
2321
+ knownFileIds,
2322
+ knownFolderPaths,
2323
+ rememberLiveBlueprint,
2324
+ userBlocks,
2325
+ userIslands,
2326
+ ])
1445
2327
 
1446
2328
  return (
1447
2329
  <>
1448
- <div className="stage">
2330
+ <div className={explaining ? 'stage stage-explain' : 'stage'}>
1449
2331
  <CanvasErrorBoundary>
1450
2332
  <Canvas
1451
2333
  shadows={false}
@@ -1470,11 +2352,12 @@ function Explorer({
1470
2352
  layout={layout}
1471
2353
  mode={mode}
1472
2354
  landAt={landAt}
1473
- selectedId={selectedId}
1474
- selectedFolder={selectedFolder}
2355
+ selectedId={mapSelectedId}
2356
+ selectedFolder={mapSelectedFolder}
1475
2357
  locked={locked}
1476
- onSelect={selectFile}
1477
- onSelectFolder={selectFolder}
2358
+ onSelect={explaining ? () => {} : selectFile}
2359
+ onSelectFolder={explaining ? () => {} : selectFolder}
2360
+ pickingImport={Boolean(importPickFrom)}
1478
2361
  onLockedChange={setLocked}
1479
2362
  onLand={landFromMap}
1480
2363
  onWalkPosition={rememberWalk}
@@ -1491,53 +2374,77 @@ function Explorer({
1491
2374
  onAimFile={setAimedFileId}
1492
2375
  onInspect={inspectBlock}
1493
2376
  onTravelTo={flyAlongRelation}
1494
- importedBy={importedBy}
2377
+ importedBy={mapImportedBy}
1495
2378
  namingId={namingId}
1496
2379
  namingIslandId={namingIslandId}
1497
- onPlaceBlock={canPlace ? placeBlock : undefined}
1498
- onPlaceIsland={canPlace ? placeIsland : undefined}
1499
- onBlueprintMenu={canPlace ? setMapMenu : undefined}
2380
+ onBlueprintMenu={
2381
+ canPlace && !explaining ? setMapMenu : undefined
2382
+ }
1500
2383
  onCommitName={commitBlockName}
1501
2384
  onCancelName={cancelBlockName}
1502
2385
  onCommitIslandName={commitIslandName}
1503
2386
  onCancelIslandName={cancelIslandName}
1504
- userCreatedBlocks={visibleBlocks}
1505
- userCreatedIslands={visibleIslands}
2387
+ userCreatedBlocks={mapBlueprint.blocks}
2388
+ userCreatedIslands={mapBlueprint.islands}
1506
2389
  overlayBlocks={overlayBlocks}
1507
- pointedFileIds={
1508
- blueprintHidden
1509
- ? undefined
1510
- : blueprintPointers.flatMap((item) =>
1511
- item.kind === 'folder' ? [] : [item.path],
1512
- )
1513
- }
1514
- pointedFolderPaths={
1515
- blueprintHidden
1516
- ? undefined
1517
- : blueprintPointers.flatMap((item) =>
1518
- item.kind === 'folder' ? [item.path] : [],
1519
- )
1520
- }
2390
+ pointedFileIds={Object.keys(pointedColors.files)}
2391
+ pointedFileColors={pointedColors.files}
2392
+ pointedFolderPaths={Object.keys(pointedColors.folders)}
2393
+ pointedFolderColors={pointedColors.folders}
1521
2394
  mapGraph={
1522
- changePathsOnly && hasChangeSet ? changePathGraph : null
2395
+ explaining
2396
+ ? null
2397
+ : changePathsOnly && hasChangeSet
2398
+ ? changePathGraph
2399
+ : null
1523
2400
  }
1524
2401
  mapLayout={
1525
- changePathsOnly && hasChangeSet ? changePathLayout : null
2402
+ explaining
2403
+ ? null
2404
+ : changePathsOnly && hasChangeSet
2405
+ ? changePathLayout
2406
+ : null
1526
2407
  }
2408
+ explainActive={explaining}
2409
+ explainFocus={explainView}
2410
+ focusBounds={explainBounds}
2411
+ focusFlightKey={explaining ? explain.currentStep : 0}
2412
+ landEnabled={!explaining}
1527
2413
  />
1528
2414
  </Canvas>
1529
2415
  </CanvasErrorBoundary>
1530
2416
  </div>
2417
+ {cardExplain ? (
2418
+ <ExplainAskCard explain={explain} onClose={closeAskCard} />
2419
+ ) : null}
2420
+ {explaining ? (
2421
+ <>
2422
+ <ExplainHud
2423
+ explain={explain}
2424
+ onStep={goExplainStep}
2425
+ onAsk={askExplainStep}
2426
+ onExit={exitExplain}
2427
+ />
2428
+ {explainFile && explainStep ? (
2429
+ <ExplainInfoPanel
2430
+ file={explainFile}
2431
+ highlights={explainStep.highlights}
2432
+ point={explainStep.point}
2433
+ />
2434
+ ) : null}
2435
+ {explainStep?.point ? (
2436
+ <ExplainPointer stepKey={explain.currentStep} />
2437
+ ) : null}
2438
+ </>
2439
+ ) : (
1531
2440
  <HUD
1532
2441
  graph={displayGraph}
1533
- layout={layout}
1534
2442
  mode={mode}
1535
2443
  locked={locked}
1536
2444
  selectedId={selectedId}
1537
2445
  selectedTick={selectedTick}
1538
2446
  inspectTick={inspectTick}
1539
2447
  selectedFolder={selectedFolder}
1540
- landAt={landAt}
1541
2448
  aimedRelation={aimedRelation}
1542
2449
  aimedFileId={aimedFileId}
1543
2450
  intent={intent}
@@ -1545,13 +2452,10 @@ function Explorer({
1545
2452
  focusedSessionId={focusedSessionId}
1546
2453
  nextAttachSessionId={nextAttachSessionId}
1547
2454
  onFocusSession={focusSessionPanel}
1548
- onSetupSession={setupLlmSession}
1549
2455
  onWorkflowAction={runWorkflowAction}
1550
2456
  onNavigateDiff={navigateDiff}
1551
2457
  onOpenMap={openMap}
1552
2458
  onWalk={openWalk}
1553
- followLook={followLook}
1554
- onToggleFollowLook={toggleFollowLook}
1555
2459
  showBranchChanges={showingBranchChanges}
1556
2460
  branchChanges={branchChanges}
1557
2461
  canShowBranchChanges={canToggleBranchChanges}
@@ -1580,6 +2484,9 @@ function Explorer({
1580
2484
  onAddBlueprintFunction={addBlueprintFunction}
1581
2485
  onAddBlueprintVariable={addBlueprintVariable}
1582
2486
  onAddBlueprintImport={addBlueprintImport}
2487
+ importPickActive={Boolean(importPickFrom)}
2488
+ onToggleImportPick={toggleImportPick}
2489
+ onCancelImportPick={cancelImportPick}
1583
2490
  onRemoveBlueprintFunction={removeBlueprintFunction}
1584
2491
  onRemoveBlueprintVariable={removeBlueprintVariable}
1585
2492
  onRemoveBlueprintImport={removeBlueprintImport}
@@ -1587,18 +2494,25 @@ function Explorer({
1587
2494
  onToggleBlueprintPointer={applyBlueprintPointer}
1588
2495
  onMapAddFile={placeBlockOnFolder}
1589
2496
  onMapAddFolder={placeIslandOnFolder}
2497
+ onRenameCreatedFile={renameCreatedBlock}
1590
2498
  onInspectFile={inspectFile}
1591
2499
  onInspectBlock={inspectBlock}
1592
- plannedIds={plannedIds}
1593
- createdIds={plannedCreates}
1594
- deletedIds={deletedIds}
2500
+ onExplainTarget={canAskLlm ? startExplainTarget : undefined}
1595
2501
  blueprintHidden={blueprintHidden}
1596
2502
  blueprintHasContent={blueprintHasContent}
1597
2503
  blueprintCanCleanup={blueprintCanCleanup}
2504
+ blueprintColor={blueprintColor}
2505
+ blueprintOptions={blueprintOptions}
2506
+ blueprintColorPointers={blueprintColorPointers}
2507
+ onSelectBlueprintColor={selectBlueprintColor}
1598
2508
  onToggleBlueprintHidden={toggleBlueprintHidden}
1599
2509
  onClearBlueprint={clearSharedBlueprint}
1600
2510
  onCleanupBlueprint={cleanupSharedBlueprint}
2511
+ devTargets={devTargets}
2512
+ onSelectDevTarget={onSelectDevTarget}
1601
2513
  />
2514
+ )}
2515
+ {!explaining && (
1602
2516
  <MapContextMenu
1603
2517
  menu={mapMenu}
1604
2518
  pointed={
@@ -1613,6 +2527,7 @@ function Explorer({
1613
2527
  }
1614
2528
  onClose={() => setMapMenu(null)}
1615
2529
  />
2530
+ )}
1616
2531
  </>
1617
2532
  )
1618
2533
  }