@jkwd/inbase 0.1.15 → 0.1.16

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.
@@ -1,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { Canvas } from '@react-three/fiber'
3
- import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint, persistSessionFocus, setupVisualizerSession } from './agentIntent'
3
+ import { shouldIgnoreShortcut, isKeyboardIsolated } from './keyboard'
4
+ import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistBlueprintCleanup, persistBlueprintClear, persistBlueprintHidden, persistSessionBlueprint, persistSessionFocus, setupVisualizerSession } from './agentIntent'
4
5
  import { emptyBranchChanges, fetchBranchChanges } from './branchChanges'
5
6
  import { fetchCodebase, updateCodebase } from './codebase'
6
7
  import {
@@ -27,14 +28,18 @@ import {
27
28
  } from './userContext'
28
29
  import {
29
30
  defaultBlockSpot,
31
+ dropBlueprintFileNotes,
32
+ dropBlueprintSymbolNote,
30
33
  isBlueprintSymbolName,
31
34
  namedCreatedBlocks,
32
35
  namedCreatedIslands,
33
36
  parseBlueprintImport,
37
+ parseBlueprintNotes,
34
38
  parseUserCreatedBlocks,
35
39
  parseUserCreatedIslands,
36
40
  resolveCreatedFile,
37
41
  resolveCreatedIsland,
42
+ setBlueprintNote,
38
43
  withBlueprintIntent,
39
44
  withUserCreatedGraph,
40
45
  withUserCreatedLayout,
@@ -44,6 +49,8 @@ import {
44
49
  llmIsMakingChanges,
45
50
  type AgentIntent,
46
51
  type AimedRelation,
52
+ type BlueprintNote,
53
+ type BlueprintNoteKind,
47
54
  type CodebaseGraph,
48
55
  type FlyTo,
49
56
  type PatchImportAddition,
@@ -166,7 +173,7 @@ function Explorer({
166
173
  intents.find((item) => item.sessionId === focusedSessionId) ??
167
174
  intents[0] ??
168
175
  emptyIntent
169
- const canPlace = Boolean(intent.sessionId && intent.creationMode)
176
+ const canPlace = true
170
177
  const llmBusy = intents.some(llmIsMakingChanges)
171
178
  const llmPreviewing = intent.preview || isPatchPreview(intent.status)
172
179
  const showingBranchChanges =
@@ -192,6 +199,23 @@ function Explorer({
192
199
  const [blueprintImports, setBlueprintImports] = useState<
193
200
  PatchImportAddition[]
194
201
  >([])
202
+ const [blueprintNotes, setBlueprintNotes] = useState<BlueprintNote[]>([])
203
+ const [blueprintHidden, setBlueprintHidden] = useState(false)
204
+ const userBlocksRef = useRef(userBlocks)
205
+ const userIslandsRef = useRef(userIslands)
206
+ const blueprintFunctionsRef = useRef(blueprintFunctions)
207
+ const blueprintVariablesRef = useRef(blueprintVariables)
208
+ const blueprintImportsRef = useRef(blueprintImports)
209
+ const blueprintNotesRef = useRef(blueprintNotes)
210
+ const notesDirty = useRef(false)
211
+ const blueprintPersistGen = useRef(0)
212
+ const notePersistTimer = useRef<number | null>(null)
213
+ userBlocksRef.current = userBlocks
214
+ userIslandsRef.current = userIslands
215
+ blueprintFunctionsRef.current = blueprintFunctions
216
+ blueprintVariablesRef.current = blueprintVariables
217
+ blueprintImportsRef.current = blueprintImports
218
+ blueprintNotesRef.current = blueprintNotes
195
219
  const namingId = userBlocks.find((block) => block.naming)?.id ?? null
196
220
  const namingIslandId = userIslands.find((island) => island.naming)?.id ?? null
197
221
  const naming = Boolean(namingId || namingIslandId)
@@ -212,10 +236,30 @@ function Explorer({
212
236
  plannedCreates,
213
237
  previewing,
214
238
  ])
239
+ const knownFileIds = useMemo(
240
+ () => new Set(previewGraph.files.map((file) => file.id)),
241
+ [previewGraph],
242
+ )
243
+ const knownFolderPaths = useMemo(
244
+ () => new Set(previewGraph.folders.map((folder) => folder.path)),
245
+ [previewGraph],
246
+ )
247
+ const visibleBlocks = blueprintHidden
248
+ ? userBlocks.filter((block) => block.naming)
249
+ : userBlocks
250
+ const visibleIslands = blueprintHidden
251
+ ? userIslands.filter((island) => island.naming)
252
+ : userIslands
253
+ const pendingBlocks = visibleBlocks.filter(
254
+ (block) => block.naming || !knownFileIds.has(block.id),
255
+ )
256
+ const overlayBlocks = visibleBlocks.filter(
257
+ (block) => !block.naming && knownFileIds.has(block.id),
258
+ )
215
259
  const displayGraph = useMemo(
216
260
  () =>
217
261
  withBlueprintIntent(
218
- withUserCreatedGraph(previewGraph, userBlocks, userIslands),
262
+ withUserCreatedGraph(previewGraph, pendingBlocks, visibleIslands),
219
263
  blueprintFunctions,
220
264
  blueprintVariables,
221
265
  blueprintImports,
@@ -224,23 +268,23 @@ function Explorer({
224
268
  blueprintFunctions,
225
269
  blueprintImports,
226
270
  blueprintVariables,
271
+ pendingBlocks,
227
272
  previewGraph,
228
- userBlocks,
229
- userIslands,
273
+ visibleIslands,
230
274
  ],
231
275
  )
232
276
  const layout = useMemo(() => {
233
277
  const world = layoutWorld(
234
- withUserCreatedGraph(previewGraph, [], userIslands),
278
+ withUserCreatedGraph(previewGraph, [], visibleIslands),
235
279
  )
236
280
  if (previewing) markCreatedFolders(world, changeSet.createFolders ?? [])
237
- return withUserCreatedLayout(world, userBlocks, userIslands)
281
+ return withUserCreatedLayout(world, pendingBlocks, visibleIslands)
238
282
  }, [
239
283
  changeSet.createFolders,
284
+ pendingBlocks,
240
285
  previewGraph,
241
286
  previewing,
242
- userBlocks,
243
- userIslands,
287
+ visibleIslands,
244
288
  ])
245
289
  const changeFileIds = useMemo(() => {
246
290
  const ids = new Set<string>()
@@ -251,10 +295,12 @@ function Explorer({
251
295
  for (const item of blueprintFunctions) ids.add(item.file)
252
296
  for (const item of blueprintVariables) ids.add(item.file)
253
297
  for (const item of blueprintImports) ids.add(item.file)
298
+ for (const note of blueprintNotes) ids.add(note.file)
254
299
  return [...ids]
255
300
  }, [
256
301
  blueprintFunctions,
257
302
  blueprintImports,
303
+ blueprintNotes,
258
304
  blueprintVariables,
259
305
  changeSet.creates,
260
306
  changeSet.deletes,
@@ -316,7 +362,6 @@ function Explorer({
316
362
  const lastIntentSig = useRef<string | null>(null)
317
363
  const viewedDiffId = useRef<Record<string, string | null>>({})
318
364
  const browsingHistory = useRef<Record<string, boolean>>({})
319
- const loadedBlueprintSession = useRef<string | null>(null)
320
365
  const seenSessionIds = useRef<Set<string>>(new Set())
321
366
 
322
367
  const applyIntent = useCallback((next: AgentIntent, sessionId?: string) => {
@@ -464,6 +509,7 @@ function Explorer({
464
509
  addedFunctions: blueprintFunctions,
465
510
  addedVariables: blueprintVariables,
466
511
  addedImports: blueprintImports,
512
+ notes: blueprintNotes,
467
513
  }
468
514
  : {}),
469
515
  },
@@ -486,6 +532,7 @@ function Explorer({
486
532
  applyIntent,
487
533
  blueprintFunctions,
488
534
  blueprintImports,
535
+ blueprintNotes,
489
536
  blueprintVariables,
490
537
  intents,
491
538
  onRefreshGraph,
@@ -538,16 +585,7 @@ function Explorer({
538
585
  useEffect(() => {
539
586
  const onKey = (event: KeyboardEvent) => {
540
587
  if (event.repeat || event.code !== 'KeyM') return
541
- const target = event.target
542
- if (
543
- target instanceof HTMLElement &&
544
- (target.tagName === 'TEXTAREA' ||
545
- target.tagName === 'INPUT' ||
546
- target.tagName === 'SELECT' ||
547
- target.isContentEditable)
548
- ) {
549
- return
550
- }
588
+ if (shouldIgnoreShortcut(event)) return
551
589
  event.preventDefault()
552
590
  toggleMap()
553
591
  }
@@ -571,98 +609,78 @@ function Explorer({
571
609
  }
572
610
  }, [])
573
611
 
574
- useEffect(() => {
575
- if (!intent.sessionId) {
576
- loadedBlueprintSession.current = null
577
- setUserBlocks([])
578
- setUserIslands([])
579
- setBlueprintFunctions([])
580
- setBlueprintVariables([])
581
- setBlueprintImports([])
582
- return
583
- }
584
- const knownFiles = new Set(graph.files.map((file) => file.id))
585
- const knownFolders = new Set(graph.folders.map((folder) => folder.path))
586
- const nextBlocks = parseUserCreatedBlocks(intent.userCreatedBlocks).filter(
587
- (block) => !knownFiles.has(block.id),
588
- )
589
- const nextIslands = parseUserCreatedIslands(intent.userCreatedIslands).filter(
590
- (island) => !knownFolders.has(island.path),
591
- )
592
- const switched = loadedBlueprintSession.current !== intent.sessionId
593
- if (switched) {
594
- loadedBlueprintSession.current = intent.sessionId
595
- setUserBlocks(nextBlocks)
596
- setUserIslands(nextIslands)
597
- setBlueprintFunctions(intent.blueprintFunctions)
598
- setBlueprintVariables(intent.blueprintVariables)
599
- setBlueprintImports(intent.blueprintImports)
600
- return
601
- }
602
- if (intent.creationMode) {
603
- setUserBlocks((current) =>
604
- current.some((block) => block.naming) || current.length > 0
605
- ? current
606
- : nextBlocks,
607
- )
608
- setUserIslands((current) =>
609
- current.some((island) => island.naming) || current.length > 0
610
- ? current
611
- : nextIslands,
612
- )
613
- setBlueprintFunctions((current) =>
614
- current.length > 0 ? current : intent.blueprintFunctions,
615
- )
616
- setBlueprintVariables((current) =>
617
- current.length > 0 ? current : intent.blueprintVariables,
618
- )
619
- setBlueprintImports((current) =>
620
- current.length > 0 ? current : intent.blueprintImports,
621
- )
622
- return
623
- }
624
- setUserBlocks(nextBlocks)
625
- setUserIslands(nextIslands)
626
- setBlueprintFunctions(intent.blueprintFunctions)
627
- setBlueprintVariables(intent.blueprintVariables)
628
- setBlueprintImports(intent.blueprintImports)
629
- }, [
630
- intent.blueprintFunctions,
631
- intent.blueprintImports,
632
- intent.blueprintVariables,
633
- intent.creationMode,
634
- intent.sessionId,
635
- intent.userCreatedBlocks,
636
- intent.userCreatedIslands,
637
- graph,
638
- ])
639
-
640
612
  const persistBlueprint = useCallback(
641
613
  (
642
- blocks: UserCreatedBlock[] = userBlocks,
643
- islands: UserCreatedIsland[] = userIslands,
644
- functions: PatchSymbolAddition[] = blueprintFunctions,
645
- variables: PatchSymbolAddition[] = blueprintVariables,
646
- imports: PatchImportAddition[] = blueprintImports,
614
+ blocks: UserCreatedBlock[] = userBlocksRef.current,
615
+ islands: UserCreatedIsland[] = userIslandsRef.current,
616
+ functions: PatchSymbolAddition[] = blueprintFunctionsRef.current,
617
+ variables: PatchSymbolAddition[] = blueprintVariablesRef.current,
618
+ imports: PatchImportAddition[] = blueprintImportsRef.current,
619
+ notes: BlueprintNote[] = blueprintNotesRef.current,
647
620
  ) => {
648
- if (!intent.sessionId || !intent.creationMode) return
649
- persistSessionBlueprint(intent.sessionId, {
621
+ if (notePersistTimer.current != null) {
622
+ window.clearTimeout(notePersistTimer.current)
623
+ notePersistTimer.current = null
624
+ }
625
+ const gen = ++blueprintPersistGen.current
626
+ notesDirty.current = true
627
+ void persistSessionBlueprint(intent.sessionId, {
650
628
  userCreatedBlocks: namedCreatedBlocks(blocks),
651
629
  userCreatedIslands: namedCreatedIslands(islands),
652
630
  addedFunctions: functions,
653
631
  addedVariables: variables,
654
632
  addedImports: imports,
633
+ notes,
634
+ }).finally(() => {
635
+ if (gen !== blueprintPersistGen.current) return
636
+ if (notePersistTimer.current != null) return
637
+ notesDirty.current = false
655
638
  })
656
639
  },
657
- [
658
- blueprintFunctions,
659
- blueprintImports,
660
- blueprintVariables,
661
- intent.creationMode,
662
- intent.sessionId,
663
- userBlocks,
664
- userIslands,
665
- ],
640
+ [intent.sessionId],
641
+ )
642
+
643
+ const persistBlueprintSoon = useCallback(() => {
644
+ notesDirty.current = true
645
+ if (notePersistTimer.current != null) {
646
+ window.clearTimeout(notePersistTimer.current)
647
+ }
648
+ notePersistTimer.current = window.setTimeout(() => {
649
+ persistBlueprint()
650
+ }, 400)
651
+ }, [persistBlueprint])
652
+
653
+ useEffect(() => {
654
+ return () => {
655
+ if (notePersistTimer.current == null) return
656
+ window.clearTimeout(notePersistTimer.current)
657
+ notePersistTimer.current = null
658
+ if (!notesDirty.current) return
659
+ persistSessionBlueprint(intent.sessionId, {
660
+ userCreatedBlocks: namedCreatedBlocks(userBlocksRef.current),
661
+ userCreatedIslands: namedCreatedIslands(userIslandsRef.current),
662
+ addedFunctions: blueprintFunctionsRef.current,
663
+ addedVariables: blueprintVariablesRef.current,
664
+ addedImports: blueprintImportsRef.current,
665
+ notes: blueprintNotesRef.current,
666
+ })
667
+ notesDirty.current = false
668
+ }
669
+ }, [intent.sessionId])
670
+
671
+ const applyBlueprintNote = useCallback(
672
+ (next: {
673
+ file: string
674
+ kind: BlueprintNoteKind
675
+ name?: string
676
+ note: string
677
+ }) => {
678
+ const notes = setBlueprintNote(blueprintNotesRef.current, next)
679
+ blueprintNotesRef.current = notes
680
+ setBlueprintNotes(notes)
681
+ persistBlueprintSoon()
682
+ },
683
+ [persistBlueprintSoon],
666
684
  )
667
685
 
668
686
  const placeBlock = useCallback(
@@ -812,8 +830,11 @@ function Explorer({
812
830
  const selected = userBlocks.find((block) => block.id === selectedId)
813
831
  if (!selected || selected.naming) return false
814
832
  const next = userBlocks.filter((block) => block.id !== selectedId)
833
+ const notes = dropBlueprintFileNotes(blueprintNotesRef.current, [selectedId])
834
+ blueprintNotesRef.current = notes
815
835
  setUserBlocks(next)
816
- persistBlueprint(next, userIslands)
836
+ setBlueprintNotes(notes)
837
+ persistBlueprint(next, userIslands, undefined, undefined, undefined, notes)
817
838
  setSelectedId(null)
818
839
  return true
819
840
  }, [canPlace, persistBlueprint, selectedId, userBlocks, userIslands])
@@ -972,8 +993,23 @@ function Explorer({
972
993
  const next = blueprintFunctions.filter(
973
994
  (item) => !(item.file === fileId && item.name === name),
974
995
  )
996
+ const notes = dropBlueprintSymbolNote(
997
+ blueprintNotesRef.current,
998
+ fileId,
999
+ 'function',
1000
+ name,
1001
+ )
1002
+ blueprintNotesRef.current = notes
975
1003
  setBlueprintFunctions(next)
976
- persistBlueprint(userBlocks, userIslands, next, blueprintVariables, blueprintImports)
1004
+ setBlueprintNotes(notes)
1005
+ persistBlueprint(
1006
+ userBlocks,
1007
+ userIslands,
1008
+ next,
1009
+ blueprintVariables,
1010
+ blueprintImports,
1011
+ notes,
1012
+ )
977
1013
  },
978
1014
  [
979
1015
  blueprintFunctions,
@@ -992,8 +1028,23 @@ function Explorer({
992
1028
  const next = blueprintVariables.filter(
993
1029
  (item) => !(item.file === fileId && item.name === name),
994
1030
  )
1031
+ const notes = dropBlueprintSymbolNote(
1032
+ blueprintNotesRef.current,
1033
+ fileId,
1034
+ 'variable',
1035
+ name,
1036
+ )
1037
+ blueprintNotesRef.current = notes
995
1038
  setBlueprintVariables(next)
996
- persistBlueprint(userBlocks, userIslands, blueprintFunctions, next, blueprintImports)
1039
+ setBlueprintNotes(notes)
1040
+ persistBlueprint(
1041
+ userBlocks,
1042
+ userIslands,
1043
+ blueprintFunctions,
1044
+ next,
1045
+ blueprintImports,
1046
+ notes,
1047
+ )
997
1048
  },
998
1049
  [
999
1050
  blueprintFunctions,
@@ -1037,6 +1088,7 @@ function Explorer({
1037
1088
  if (!namingId && !namingIslandId) return
1038
1089
  const onKey = (event: KeyboardEvent) => {
1039
1090
  if (event.code !== 'Escape') return
1091
+ if (shouldIgnoreShortcut(event)) return
1040
1092
  event.preventDefault()
1041
1093
  if (namingId) cancelBlockName(namingId)
1042
1094
  if (namingIslandId) cancelIslandName(namingIslandId)
@@ -1052,16 +1104,7 @@ function Explorer({
1052
1104
  useEffect(() => {
1053
1105
  const onKey = (event: KeyboardEvent) => {
1054
1106
  if (event.repeat || event.code !== 'Backspace') return
1055
- const target = event.target
1056
- if (
1057
- target instanceof HTMLElement &&
1058
- (target.tagName === 'TEXTAREA' ||
1059
- target.tagName === 'INPUT' ||
1060
- target.tagName === 'SELECT' ||
1061
- target.isContentEditable)
1062
- ) {
1063
- return
1064
- }
1107
+ if (shouldIgnoreShortcut(event)) return
1065
1108
  if (!deleteSelectedCreatedBlock()) return
1066
1109
  event.preventDefault()
1067
1110
  }
@@ -1101,16 +1144,7 @@ function Explorer({
1101
1144
  useEffect(() => {
1102
1145
  const onKey = (event: KeyboardEvent) => {
1103
1146
  if (event.repeat || event.code !== 'KeyK') return
1104
- const target = event.target
1105
- if (
1106
- target instanceof HTMLElement &&
1107
- (target.tagName === 'TEXTAREA' ||
1108
- target.tagName === 'INPUT' ||
1109
- target.tagName === 'SELECT' ||
1110
- target.isContentEditable)
1111
- ) {
1112
- return
1113
- }
1147
+ if (shouldIgnoreShortcut(event)) return
1114
1148
  event.preventDefault()
1115
1149
  toggleImportedBy()
1116
1150
  }
@@ -1121,16 +1155,7 @@ function Explorer({
1121
1155
  useEffect(() => {
1122
1156
  const onKey = (event: KeyboardEvent) => {
1123
1157
  if (event.repeat || event.code !== 'KeyG') return
1124
- const target = event.target
1125
- if (
1126
- target instanceof HTMLElement &&
1127
- (target.tagName === 'TEXTAREA' ||
1128
- target.tagName === 'INPUT' ||
1129
- target.tagName === 'SELECT' ||
1130
- target.isContentEditable)
1131
- ) {
1132
- return
1133
- }
1158
+ if (shouldIgnoreShortcut(event)) return
1134
1159
  if (!canToggleBranchChanges) return
1135
1160
  event.preventDefault()
1136
1161
  toggleShowBranchChanges()
@@ -1164,16 +1189,7 @@ function Explorer({
1164
1189
  if (mode !== 'map' || !hasChangeSet) return
1165
1190
  const onKey = (event: KeyboardEvent) => {
1166
1191
  if (event.repeat || event.code !== 'KeyC') return
1167
- const target = event.target
1168
- if (
1169
- target instanceof HTMLElement &&
1170
- (target.tagName === 'TEXTAREA' ||
1171
- target.tagName === 'INPUT' ||
1172
- target.tagName === 'SELECT' ||
1173
- target.isContentEditable)
1174
- ) {
1175
- return
1176
- }
1192
+ if (shouldIgnoreShortcut(event)) return
1177
1193
  event.preventDefault()
1178
1194
  toggleChangePathsOnly()
1179
1195
  }
@@ -1187,6 +1203,29 @@ function Explorer({
1187
1203
  try {
1188
1204
  const bundle = await fetchAgentIntents()
1189
1205
  if (cancelled) return
1206
+ setBlueprintHidden(Boolean(bundle.blueprint.hidden))
1207
+ const nextBlocks = parseUserCreatedBlocks(bundle.blueprint.userCreatedBlocks)
1208
+ const nextIslands = parseUserCreatedIslands(bundle.blueprint.userCreatedIslands)
1209
+ setUserBlocks((current) => {
1210
+ const drafts = current.filter((block) => block.naming)
1211
+ if (drafts.length === 0) return nextBlocks
1212
+ const namedIds = new Set(drafts.map((block) => block.id))
1213
+ return [...nextBlocks.filter((block) => !namedIds.has(block.id)), ...drafts]
1214
+ })
1215
+ setUserIslands((current) => {
1216
+ const drafts = current.filter((island) => island.naming)
1217
+ if (drafts.length === 0) return nextIslands
1218
+ const namedIds = new Set(drafts.map((island) => island.id))
1219
+ return [...nextIslands.filter((island) => !namedIds.has(island.id)), ...drafts]
1220
+ })
1221
+ setBlueprintFunctions(bundle.blueprint.addedFunctions)
1222
+ setBlueprintVariables(bundle.blueprint.addedVariables)
1223
+ setBlueprintImports(bundle.blueprint.addedImports)
1224
+ if (!notesDirty.current && !isKeyboardIsolated()) {
1225
+ const nextNotes = parseBlueprintNotes(bundle.blueprint.notes)
1226
+ blueprintNotesRef.current = nextNotes
1227
+ setBlueprintNotes(nextNotes)
1228
+ }
1190
1229
  setNextAttachSessionId(bundle.nextAttachSessionId)
1191
1230
  const merged: AgentIntent[] = []
1192
1231
  for (const next of bundle.intents) {
@@ -1259,6 +1298,64 @@ function Explorer({
1259
1298
  ...blueprintImportEdges,
1260
1299
  ]
1261
1300
  const deletedIds = previewing ? changeSet.deletes : []
1301
+ const blueprintHasContent =
1302
+ namedCreatedBlocks(userBlocks).length > 0 ||
1303
+ namedCreatedIslands(userIslands).length > 0 ||
1304
+ blueprintFunctions.length > 0 ||
1305
+ blueprintVariables.length > 0 ||
1306
+ blueprintImports.length > 0 ||
1307
+ blueprintNotes.length > 0
1308
+ const blueprintCanCleanup =
1309
+ userBlocks.some((block) => !block.naming && knownFileIds.has(block.id)) ||
1310
+ userIslands.some(
1311
+ (island) => !island.naming && knownFolderPaths.has(island.path),
1312
+ )
1313
+
1314
+ const toggleBlueprintHidden = useCallback(() => {
1315
+ const next = !blueprintHidden
1316
+ setBlueprintHidden(next)
1317
+ void persistBlueprintHidden(next).catch(() => setBlueprintHidden(!next))
1318
+ }, [blueprintHidden])
1319
+
1320
+ const clearSharedBlueprint = useCallback(() => {
1321
+ setUserBlocks([])
1322
+ setUserIslands([])
1323
+ setBlueprintFunctions([])
1324
+ setBlueprintVariables([])
1325
+ setBlueprintImports([])
1326
+ blueprintNotesRef.current = []
1327
+ setBlueprintNotes([])
1328
+ void persistBlueprintClear()
1329
+ }, [])
1330
+
1331
+ const cleanupSharedBlueprint = useCallback(() => {
1332
+ const files = knownFileIds
1333
+ const folders = knownFolderPaths
1334
+ const removed = new Set(
1335
+ userBlocks
1336
+ .filter((block) => !block.naming && files.has(block.id))
1337
+ .map((block) => block.id),
1338
+ )
1339
+ setUserBlocks((current) =>
1340
+ current.filter((block) => block.naming || !files.has(block.id)),
1341
+ )
1342
+ setUserIslands((current) =>
1343
+ current.filter((island) => island.naming || !folders.has(island.path)),
1344
+ )
1345
+ setBlueprintFunctions((current) =>
1346
+ current.filter((item) => !removed.has(item.file)),
1347
+ )
1348
+ setBlueprintVariables((current) =>
1349
+ current.filter((item) => !removed.has(item.file)),
1350
+ )
1351
+ setBlueprintImports((current) =>
1352
+ current.filter((item) => !removed.has(item.file)),
1353
+ )
1354
+ const notes = dropBlueprintFileNotes(blueprintNotesRef.current, removed)
1355
+ blueprintNotesRef.current = notes
1356
+ setBlueprintNotes(notes)
1357
+ void persistBlueprintCleanup()
1358
+ }, [knownFileIds, knownFolderPaths, userBlocks])
1262
1359
 
1263
1360
  return (
1264
1361
  <>
@@ -1309,8 +1406,11 @@ function Explorer({
1309
1406
  onBlueprintMenu={canPlace ? setMapMenu : undefined}
1310
1407
  onCommitName={commitBlockName}
1311
1408
  onCancelName={cancelBlockName}
1312
- userCreatedBlocks={userBlocks}
1313
- userCreatedIslands={userIslands}
1409
+ onCommitIslandName={commitIslandName}
1410
+ onCancelIslandName={cancelIslandName}
1411
+ userCreatedBlocks={visibleBlocks}
1412
+ userCreatedIslands={visibleIslands}
1413
+ overlayBlocks={overlayBlocks}
1314
1414
  mapGraph={
1315
1415
  changePathsOnly && hasChangeSet ? changePathGraph : null
1316
1416
  }
@@ -1368,12 +1468,14 @@ function Explorer({
1368
1468
  blueprintFunctions={blueprintFunctions}
1369
1469
  blueprintVariables={blueprintVariables}
1370
1470
  blueprintImports={blueprintImports}
1471
+ blueprintNotes={blueprintNotes}
1371
1472
  onAddBlueprintFunction={addBlueprintFunction}
1372
1473
  onAddBlueprintVariable={addBlueprintVariable}
1373
1474
  onAddBlueprintImport={addBlueprintImport}
1374
1475
  onRemoveBlueprintFunction={removeBlueprintFunction}
1375
1476
  onRemoveBlueprintVariable={removeBlueprintVariable}
1376
1477
  onRemoveBlueprintImport={removeBlueprintImport}
1478
+ onSetBlueprintNote={applyBlueprintNote}
1377
1479
  onMapAddFile={placeBlockOnFolder}
1378
1480
  onMapAddFolder={placeIslandOnFolder}
1379
1481
  onInspectFile={inspectFile}
@@ -1381,6 +1483,12 @@ function Explorer({
1381
1483
  plannedIds={plannedIds}
1382
1484
  createdIds={plannedCreates}
1383
1485
  deletedIds={deletedIds}
1486
+ blueprintHidden={blueprintHidden}
1487
+ blueprintHasContent={blueprintHasContent}
1488
+ blueprintCanCleanup={blueprintCanCleanup}
1489
+ onToggleBlueprintHidden={toggleBlueprintHidden}
1490
+ onClearBlueprint={clearSharedBlueprint}
1491
+ onCleanupBlueprint={cleanupSharedBlueprint}
1384
1492
  />
1385
1493
  <MapContextMenu
1386
1494
  menu={mapMenu}