@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.
- package/README.md +13 -7
- package/apps/explorer/package.json +1 -0
- package/apps/explorer/scripts/explain-store.d.ts +135 -0
- package/apps/explorer/scripts/explain-store.mjs +666 -0
- package/apps/explorer/scripts/patch-lib.mjs +4 -0
- package/apps/explorer/scripts/scan-target.mjs +22 -5
- package/apps/explorer/scripts/session-store.d.ts +86 -11
- package/apps/explorer/scripts/session-store.mjs +371 -58
- package/apps/explorer/scripts/target-config.d.ts +38 -3
- package/apps/explorer/scripts/target-config.mjs +147 -3
- package/apps/explorer/src/App.tsx +1073 -158
- package/apps/explorer/src/agentIntent.ts +61 -7
- package/apps/explorer/src/codebase.ts +1 -1
- package/apps/explorer/src/devTargets.ts +66 -0
- package/apps/explorer/src/explain.ts +312 -0
- package/apps/explorer/src/index.css +874 -222
- package/apps/explorer/src/layout.ts +55 -0
- package/apps/explorer/src/scene/DistantFileBlocks.tsx +4 -2
- package/apps/explorer/src/scene/FileBlock.tsx +95 -72
- package/apps/explorer/src/scene/FolderArea.tsx +55 -32
- package/apps/explorer/src/scene/MapView.tsx +506 -33
- package/apps/explorer/src/scene/RelationLines.tsx +7 -0
- package/apps/explorer/src/scene/World.tsx +146 -32
- package/apps/explorer/src/speech.ts +228 -0
- package/apps/explorer/src/theme.ts +29 -0
- package/apps/explorer/src/types.ts +98 -8
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +1 -1
- package/apps/explorer/src/ui/ExplainAskCard.tsx +142 -0
- package/apps/explorer/src/ui/ExplainHud.tsx +524 -0
- package/apps/explorer/src/ui/ExplainInfoPanel.tsx +135 -0
- package/apps/explorer/src/ui/ExplainPointer.tsx +73 -0
- package/apps/explorer/src/ui/EyeIcon.tsx +38 -1
- package/apps/explorer/src/ui/HUD.tsx +1067 -795
- package/apps/explorer/src/ui/NameInput.tsx +114 -5
- package/apps/explorer/src/userContext.ts +0 -11
- package/apps/explorer/src/userCreated.ts +54 -1
- package/apps/explorer/vite.config.ts +173 -26
- package/bin/inbase.mjs +11 -2
- package/bin/project.mjs +6 -4
- package/bin/session.mjs +287 -38
- package/package.json +4 -1
- package/skill/commands/amber.md +23 -0
- package/skill/commands/blue.md +13 -0
- package/skill/commands/coral.md +23 -0
- package/skill/commands/explain.md +77 -0
- package/skill/commands/green.md +23 -0
- package/skill/commands/inbase.md +7 -5
- package/skill/commands/lime.md +23 -0
- package/skill/commands/orange.md +23 -0
- package/skill/commands/purple.md +23 -0
- package/skill/commands/red.md +23 -0
- package/skill/commands/skipinbase.md +1 -1
- package/skill/commands/violet.md +23 -0
- package/skill/commands/yellow.md +23 -0
- package/skill/inbase/SKILL.md +124 -76
- package/apps/explorer/src/scene/BlockPlacer.tsx +0 -78
- package/apps/explorer/src/scene/IslandPlacer.tsx +0 -31
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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 =
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
782
|
+
mapBlueprint.functions,
|
|
783
|
+
mapBlueprint.variables,
|
|
784
|
+
mapBlueprint.imports,
|
|
278
785
|
),
|
|
279
786
|
[
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
|
307
|
-
for (const item of
|
|
308
|
-
for (const item of
|
|
309
|
-
for (const item of
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
836
|
+
for (const pointer of mapBlueprint.pointers) {
|
|
334
837
|
if (pointer.kind === 'folder') paths.add(pointer.path)
|
|
335
838
|
}
|
|
336
839
|
return [...paths]
|
|
337
|
-
}, [
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
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 =
|
|
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
|
-
|
|
1402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1426
|
-
|
|
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
|
-
|
|
1432
|
-
|
|
2294
|
+
const nextVariables = blueprintVariables.filter(
|
|
2295
|
+
(item) => !removed.has(item.file),
|
|
1433
2296
|
)
|
|
1434
|
-
|
|
1435
|
-
|
|
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
|
-
|
|
1444
|
-
|
|
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=
|
|
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={
|
|
1474
|
-
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={
|
|
2377
|
+
importedBy={mapImportedBy}
|
|
1495
2378
|
namingId={namingId}
|
|
1496
2379
|
namingIslandId={namingIslandId}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
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={
|
|
1505
|
-
userCreatedIslands={
|
|
2387
|
+
userCreatedBlocks={mapBlueprint.blocks}
|
|
2388
|
+
userCreatedIslands={mapBlueprint.islands}
|
|
1506
2389
|
overlayBlocks={overlayBlocks}
|
|
1507
|
-
pointedFileIds={
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
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
|
-
|
|
2395
|
+
explaining
|
|
2396
|
+
? null
|
|
2397
|
+
: changePathsOnly && hasChangeSet
|
|
2398
|
+
? changePathGraph
|
|
2399
|
+
: null
|
|
1523
2400
|
}
|
|
1524
2401
|
mapLayout={
|
|
1525
|
-
|
|
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
|
-
|
|
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
|
}
|