@jkwd/inbase 0.1.19 → 0.1.20

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 CHANGED
@@ -55,7 +55,7 @@ A normal chat request does not open a session. Use `/inbase` after Setup LLM ses
55
55
 
56
56
  If several sessions are open, `/inbase` attaches the oldest session that is still waiting. Sessions that already have an LLM are skipped, and the map window does not need to be focused.
57
57
 
58
- Turn on **Make LLM look where I look** if the agent should prefer the island you are standing on and the blocks you are facing. With **Step by step** on, click **Create proposal** to start a step, then **Accept proposal** when the patch is ready. With it off, the LLM implements the full plan; you can still walk Previous/Next over the diffs, then **Accept proposal**. Send an alternative instruction from the HUD to revise the remaining plan, or **Stop** to end the session.
58
+ Turn on **Make LLM look where I look** if the agent should prefer the island you are standing on and the blocks you are facing. With **Step by step** on, click **Create proposal** to start a step, then **Accept proposal** when the patch is ready. With it off, the LLM implements the full plan; you can still walk Previous/Next over the diffs, then **Accept proposal**. Send an alternative instruction from the HUD to update the current proposal, or **Stop** to end the session.
59
59
 
60
60
  When no LLM is currently making changes, turn on **Show branch changes** (or press **G**) to highlight the current git branch against its base — committed, unstaged, and untracked files — instead of LLM patch files. The control is disabled while an LLM session is writing or reviewing a patch.
61
61
 
@@ -124,6 +124,7 @@ export const emptyIntent: {
124
124
  blueprintVariables: unknown[]
125
125
  blueprintImports: unknown[]
126
126
  blueprintNotes: unknown[]
127
+ blueprintPointers: unknown[]
127
128
  }
128
129
 
129
130
  export function isLastStep(intent: {
@@ -551,6 +551,7 @@ export const emptyIntent = {
551
551
  blueprintVariables: [],
552
552
  blueprintImports: [],
553
553
  blueprintNotes: [],
554
+ blueprintPointers: [],
554
555
  }
555
556
 
556
557
  export function isLastStep(intent) {
@@ -185,6 +185,7 @@ export type SessionBlueprint = {
185
185
  addedVariables: unknown[]
186
186
  addedImports: unknown[]
187
187
  notes: unknown[]
188
+ pointers: unknown[]
188
189
  }
189
190
  export function emptyBlueprint(): SessionBlueprint
190
191
  export function readBlueprint(dataDir: string, sessionId?: string): SessionBlueprint
@@ -224,6 +225,7 @@ export function updateBlueprint(
224
225
  addedVariables?: unknown[]
225
226
  addedImports?: unknown[]
226
227
  notes?: unknown[]
228
+ pointers?: unknown[]
227
229
  },
228
230
  ): SessionBlueprint
229
231
  export function sendBlueprint(
@@ -236,6 +238,7 @@ export function sendBlueprint(
236
238
  addedVariables?: unknown[]
237
239
  addedImports?: unknown[]
238
240
  notes?: unknown[]
241
+ pointers?: unknown[]
239
242
  },
240
243
  ): DiffManifest
241
244
  export function maybeStartVisualizerHandshake(
@@ -424,7 +424,8 @@ function blueprintHasContent(blueprint) {
424
424
  (blueprint.addedFunctions?.length ?? 0) > 0 ||
425
425
  (blueprint.addedVariables?.length ?? 0) > 0 ||
426
426
  (blueprint.addedImports?.length ?? 0) > 0 ||
427
- (blueprint.notes?.length ?? 0) > 0
427
+ (blueprint.notes?.length ?? 0) > 0 ||
428
+ (blueprint.pointers?.length ?? 0) > 0
428
429
  )
429
430
  }
430
431
 
@@ -801,6 +802,7 @@ export function sessionIntent(
801
802
  blueprintVariables: blueprint.addedVariables,
802
803
  blueprintImports: blueprint.addedImports,
803
804
  blueprintNotes: blueprint.notes,
805
+ blueprintPointers: blueprint.pointers,
804
806
  }
805
807
  }
806
808
 
@@ -1020,11 +1022,21 @@ export function autoAdvance(dataDir, sessionId, targetRoot = null) {
1020
1022
  const active = manifest.diffs.at(-1)
1021
1023
  if (!active || active.status !== 'pending') return manifest
1022
1024
  if (active.step >= manifest.steps.length) return manifest
1025
+ if (isRevisedProposal(manifest, active)) return manifest
1023
1026
  return invokeStep(dataDir, sessionId, active.step + 1, targetRoot)
1024
1027
  }
1025
1028
  return manifest
1026
1029
  }
1027
1030
 
1031
+ function isRevisedProposal(manifest, active) {
1032
+ return manifest.diffs.some(
1033
+ (entry) =>
1034
+ entry.id !== active.id &&
1035
+ entry.step === active.step &&
1036
+ (entry.status === 'extended' || entry.status === 'extend'),
1037
+ )
1038
+ }
1039
+
1028
1040
  export function setStepByStep(dataDir, sessionId, enabled, targetRoot = null) {
1029
1041
  const manifest = requireManifest(dataDir, sessionId)
1030
1042
  manifest.stepByStep = Boolean(enabled)
@@ -1277,6 +1289,7 @@ export function updateBlueprint(dataDir, _sessionId, input = {}) {
1277
1289
  addedVariables: input.addedVariables ?? current.addedVariables,
1278
1290
  addedImports: input.addedImports ?? current.addedImports,
1279
1291
  notes: input.notes ?? current.notes,
1292
+ pointers: input.pointers ?? current.pointers,
1280
1293
  }
1281
1294
  return writeBlueprint(dataDir, next)
1282
1295
  }
@@ -1293,7 +1306,8 @@ export function sendBlueprint(dataDir, sessionId, input = {}) {
1293
1306
  input.addedFunctions ||
1294
1307
  input.addedVariables ||
1295
1308
  input.addedImports ||
1296
- input.notes
1309
+ input.notes ||
1310
+ input.pointers
1297
1311
  ) {
1298
1312
  updateBlueprint(dataDir, safeId, input)
1299
1313
  }
@@ -1755,6 +1769,7 @@ export function emptyBlueprint() {
1755
1769
  addedVariables: [],
1756
1770
  addedImports: [],
1757
1771
  notes: [],
1772
+ pointers: [],
1758
1773
  }
1759
1774
  }
1760
1775
 
@@ -1849,6 +1864,36 @@ function namedBlueprintNotes(value) {
1849
1864
  return notes
1850
1865
  }
1851
1866
 
1867
+ function namedBlueprintPointers(value) {
1868
+ if (!Array.isArray(value)) return []
1869
+ const seen = new Set()
1870
+ const pointers = []
1871
+ for (const item of value) {
1872
+ if (!item || typeof item !== 'object') continue
1873
+ const path = typeof item.path === 'string' ? item.path.trim() : ''
1874
+ if (!path) continue
1875
+ let stored = null
1876
+ if (item.kind === 'file' || item.kind === 'folder') {
1877
+ stored = { kind: item.kind, path }
1878
+ } else if (
1879
+ (item.kind === 'function' || item.kind === 'variable') &&
1880
+ typeof item.name === 'string' &&
1881
+ item.name.trim() !== ''
1882
+ ) {
1883
+ stored = { kind: item.kind, path, name: item.name.trim() }
1884
+ }
1885
+ if (!stored) continue
1886
+ const key =
1887
+ stored.kind === 'file' || stored.kind === 'folder'
1888
+ ? `${stored.kind}:${stored.path}`
1889
+ : `${stored.kind}:${stored.path}:${stored.name}`
1890
+ if (seen.has(key)) continue
1891
+ seen.add(key)
1892
+ pointers.push(stored)
1893
+ }
1894
+ return pointers
1895
+ }
1896
+
1852
1897
  function blueprintContentEqual(left, right) {
1853
1898
  return (
1854
1899
  JSON.stringify({
@@ -1858,6 +1903,7 @@ function blueprintContentEqual(left, right) {
1858
1903
  addedVariables: left.addedVariables,
1859
1904
  addedImports: left.addedImports,
1860
1905
  notes: left.notes,
1906
+ pointers: left.pointers,
1861
1907
  }) ===
1862
1908
  JSON.stringify({
1863
1909
  userCreatedBlocks: right.userCreatedBlocks,
@@ -1866,6 +1912,7 @@ function blueprintContentEqual(left, right) {
1866
1912
  addedVariables: right.addedVariables,
1867
1913
  addedImports: right.addedImports,
1868
1914
  notes: right.notes,
1915
+ pointers: right.pointers,
1869
1916
  })
1870
1917
  )
1871
1918
  }
@@ -1877,6 +1924,7 @@ function normalizeBlueprint(value) {
1877
1924
  const addedVariables = namedBlueprintSymbols(value?.addedVariables)
1878
1925
  const addedImports = namedBlueprintImportAdditions(value?.addedImports)
1879
1926
  const notes = namedBlueprintNotes(value?.notes)
1927
+ const pointers = namedBlueprintPointers(value?.pointers)
1880
1928
  const revision =
1881
1929
  Number.isInteger(value?.revision) && value.revision >= 0 ? value.revision : 0
1882
1930
  return {
@@ -1889,6 +1937,7 @@ function normalizeBlueprint(value) {
1889
1937
  addedVariables,
1890
1938
  addedImports,
1891
1939
  notes,
1940
+ pointers,
1892
1941
  }),
1893
1942
  sent: true,
1894
1943
  userCreatedBlocks,
@@ -1897,6 +1946,7 @@ function normalizeBlueprint(value) {
1897
1946
  addedVariables,
1898
1947
  addedImports,
1899
1948
  notes,
1949
+ pointers,
1900
1950
  }
1901
1951
  }
1902
1952
 
@@ -1935,6 +1985,7 @@ export function writeBlueprint(dataDir, sessionIdOrBlueprint, maybeBlueprint) {
1935
1985
  addedVariables: namedBlueprintSymbols(next.addedVariables),
1936
1986
  addedImports: namedBlueprintImportAdditions(next.addedImports),
1937
1987
  notes: namedBlueprintNotes(next.notes),
1988
+ pointers: namedBlueprintPointers(next.pointers),
1938
1989
  },
1939
1990
  null,
1940
1991
  2,
@@ -1973,6 +2024,7 @@ export function cleanupBlueprint(dataDir, knownFileIds = [], knownFolderPaths =
1973
2024
  addedVariables: current.addedVariables.filter((item) => !removedFiles.has(item.file)),
1974
2025
  addedImports: current.addedImports.filter((item) => !removedFiles.has(item.file)),
1975
2026
  notes: current.notes.filter((item) => !removedFiles.has(item.file)),
2027
+ pointers: current.pointers,
1976
2028
  }
1977
2029
  return writeBlueprint(dataDir, next)
1978
2030
  }
@@ -29,17 +29,22 @@ import {
29
29
  import {
30
30
  defaultBlockSpot,
31
31
  dropBlueprintFileNotes,
32
+ dropBlueprintFilePointers,
32
33
  dropBlueprintSymbolNote,
34
+ dropBlueprintSymbolPointer,
35
+ findBlueprintPointer,
33
36
  isBlueprintSymbolName,
34
37
  namedCreatedBlocks,
35
38
  namedCreatedIslands,
36
39
  parseBlueprintImport,
37
40
  parseBlueprintNotes,
41
+ parseBlueprintPointers,
38
42
  parseUserCreatedBlocks,
39
43
  parseUserCreatedIslands,
40
44
  resolveCreatedFile,
41
45
  resolveCreatedIsland,
42
46
  setBlueprintNote,
47
+ toggleBlueprintPointer,
43
48
  withBlueprintIntent,
44
49
  withUserCreatedGraph,
45
50
  withUserCreatedLayout,
@@ -51,6 +56,8 @@ import {
51
56
  type AimedRelation,
52
57
  type BlueprintNote,
53
58
  type BlueprintNoteKind,
59
+ type BlueprintPointer,
60
+ type BlueprintPointerKind,
54
61
  type CodebaseGraph,
55
62
  type FlyTo,
56
63
  type PatchImportAddition,
@@ -200,6 +207,9 @@ function Explorer({
200
207
  PatchImportAddition[]
201
208
  >([])
202
209
  const [blueprintNotes, setBlueprintNotes] = useState<BlueprintNote[]>([])
210
+ const [blueprintPointers, setBlueprintPointers] = useState<BlueprintPointer[]>(
211
+ [],
212
+ )
203
213
  const [blueprintHidden, setBlueprintHidden] = useState(false)
204
214
  const userBlocksRef = useRef(userBlocks)
205
215
  const userIslandsRef = useRef(userIslands)
@@ -207,6 +217,7 @@ function Explorer({
207
217
  const blueprintVariablesRef = useRef(blueprintVariables)
208
218
  const blueprintImportsRef = useRef(blueprintImports)
209
219
  const blueprintNotesRef = useRef(blueprintNotes)
220
+ const blueprintPointersRef = useRef(blueprintPointers)
210
221
  const notesDirty = useRef(false)
211
222
  const blueprintPersistGen = useRef(0)
212
223
  const notePersistTimer = useRef<number | null>(null)
@@ -216,6 +227,7 @@ function Explorer({
216
227
  blueprintVariablesRef.current = blueprintVariables
217
228
  blueprintImportsRef.current = blueprintImports
218
229
  blueprintNotesRef.current = blueprintNotes
230
+ blueprintPointersRef.current = blueprintPointers
219
231
  const namingId = userBlocks.find((block) => block.naming)?.id ?? null
220
232
  const namingIslandId = userIslands.find((island) => island.naming)?.id ?? null
221
233
  const naming = Boolean(namingId || namingIslandId)
@@ -296,11 +308,15 @@ function Explorer({
296
308
  for (const item of blueprintVariables) ids.add(item.file)
297
309
  for (const item of blueprintImports) ids.add(item.file)
298
310
  for (const note of blueprintNotes) ids.add(note.file)
311
+ for (const pointer of blueprintPointers) {
312
+ if (pointer.kind !== 'folder') ids.add(pointer.path)
313
+ }
299
314
  return [...ids]
300
315
  }, [
301
316
  blueprintFunctions,
302
317
  blueprintImports,
303
318
  blueprintNotes,
319
+ blueprintPointers,
304
320
  blueprintVariables,
305
321
  changeSet.creates,
306
322
  changeSet.deletes,
@@ -314,8 +330,11 @@ function Explorer({
314
330
  if (island.path) paths.add(island.path)
315
331
  else if (island.id) paths.add(island.id)
316
332
  }
333
+ for (const pointer of blueprintPointers) {
334
+ if (pointer.kind === 'folder') paths.add(pointer.path)
335
+ }
317
336
  return [...paths]
318
- }, [changeSet.createFolders, userIslands])
337
+ }, [blueprintPointers, changeSet.createFolders, userIslands])
319
338
  const hasChangeSet =
320
339
  changeFileIds.length > 0 || changeFolderPaths.length > 0
321
340
  const changePathGraph = useMemo(() => {
@@ -511,6 +530,7 @@ function Explorer({
511
530
  addedVariables: blueprintVariables,
512
531
  addedImports: blueprintImports,
513
532
  notes: blueprintNotes,
533
+ pointers: blueprintPointers,
514
534
  }
515
535
  : {}),
516
536
  },
@@ -534,6 +554,7 @@ function Explorer({
534
554
  blueprintFunctions,
535
555
  blueprintImports,
536
556
  blueprintNotes,
557
+ blueprintPointers,
537
558
  blueprintVariables,
538
559
  intents,
539
560
  onRefreshGraph,
@@ -618,6 +639,7 @@ function Explorer({
618
639
  variables: PatchSymbolAddition[] = blueprintVariablesRef.current,
619
640
  imports: PatchImportAddition[] = blueprintImportsRef.current,
620
641
  notes: BlueprintNote[] = blueprintNotesRef.current,
642
+ pointers: BlueprintPointer[] = blueprintPointersRef.current,
621
643
  ) => {
622
644
  if (notePersistTimer.current != null) {
623
645
  window.clearTimeout(notePersistTimer.current)
@@ -632,6 +654,7 @@ function Explorer({
632
654
  addedVariables: variables,
633
655
  addedImports: imports,
634
656
  notes,
657
+ pointers,
635
658
  }).finally(() => {
636
659
  if (gen !== blueprintPersistGen.current) return
637
660
  if (notePersistTimer.current != null) return
@@ -664,6 +687,7 @@ function Explorer({
664
687
  addedVariables: blueprintVariablesRef.current,
665
688
  addedImports: blueprintImportsRef.current,
666
689
  notes: blueprintNotesRef.current,
690
+ pointers: blueprintPointersRef.current,
667
691
  })
668
692
  notesDirty.current = false
669
693
  }
@@ -684,6 +708,28 @@ function Explorer({
684
708
  [persistBlueprintSoon],
685
709
  )
686
710
 
711
+ const applyBlueprintPointer = useCallback(
712
+ (next: {
713
+ kind: BlueprintPointerKind
714
+ path: string
715
+ name?: string
716
+ }) => {
717
+ const pointers = toggleBlueprintPointer(blueprintPointersRef.current, next)
718
+ blueprintPointersRef.current = pointers
719
+ setBlueprintPointers(pointers)
720
+ persistBlueprint(
721
+ userBlocksRef.current,
722
+ userIslandsRef.current,
723
+ blueprintFunctionsRef.current,
724
+ blueprintVariablesRef.current,
725
+ blueprintImportsRef.current,
726
+ blueprintNotesRef.current,
727
+ pointers,
728
+ )
729
+ },
730
+ [persistBlueprint],
731
+ )
732
+
687
733
  const placeBlock = useCallback(
688
734
  (spot: { x: number; z: number; folder: string }) => {
689
735
  if (!canPlace) return
@@ -832,10 +878,23 @@ function Explorer({
832
878
  if (!selected || selected.naming) return false
833
879
  const next = userBlocks.filter((block) => block.id !== selectedId)
834
880
  const notes = dropBlueprintFileNotes(blueprintNotesRef.current, [selectedId])
881
+ const pointers = dropBlueprintFilePointers(blueprintPointersRef.current, [
882
+ selectedId,
883
+ ])
835
884
  blueprintNotesRef.current = notes
885
+ blueprintPointersRef.current = pointers
836
886
  setUserBlocks(next)
837
887
  setBlueprintNotes(notes)
838
- persistBlueprint(next, userIslands, undefined, undefined, undefined, notes)
888
+ setBlueprintPointers(pointers)
889
+ persistBlueprint(
890
+ next,
891
+ userIslands,
892
+ undefined,
893
+ undefined,
894
+ undefined,
895
+ notes,
896
+ pointers,
897
+ )
839
898
  setSelectedId(null)
840
899
  return true
841
900
  }, [canPlace, persistBlueprint, selectedId, userBlocks, userIslands])
@@ -1000,9 +1059,17 @@ function Explorer({
1000
1059
  'function',
1001
1060
  name,
1002
1061
  )
1062
+ const pointers = dropBlueprintSymbolPointer(
1063
+ blueprintPointersRef.current,
1064
+ fileId,
1065
+ 'function',
1066
+ name,
1067
+ )
1003
1068
  blueprintNotesRef.current = notes
1069
+ blueprintPointersRef.current = pointers
1004
1070
  setBlueprintFunctions(next)
1005
1071
  setBlueprintNotes(notes)
1072
+ setBlueprintPointers(pointers)
1006
1073
  persistBlueprint(
1007
1074
  userBlocks,
1008
1075
  userIslands,
@@ -1010,6 +1077,7 @@ function Explorer({
1010
1077
  blueprintVariables,
1011
1078
  blueprintImports,
1012
1079
  notes,
1080
+ pointers,
1013
1081
  )
1014
1082
  },
1015
1083
  [
@@ -1035,9 +1103,17 @@ function Explorer({
1035
1103
  'variable',
1036
1104
  name,
1037
1105
  )
1106
+ const pointers = dropBlueprintSymbolPointer(
1107
+ blueprintPointersRef.current,
1108
+ fileId,
1109
+ 'variable',
1110
+ name,
1111
+ )
1038
1112
  blueprintNotesRef.current = notes
1113
+ blueprintPointersRef.current = pointers
1039
1114
  setBlueprintVariables(next)
1040
1115
  setBlueprintNotes(notes)
1116
+ setBlueprintPointers(pointers)
1041
1117
  persistBlueprint(
1042
1118
  userBlocks,
1043
1119
  userIslands,
@@ -1045,6 +1121,7 @@ function Explorer({
1045
1121
  next,
1046
1122
  blueprintImports,
1047
1123
  notes,
1124
+ pointers,
1048
1125
  )
1049
1126
  },
1050
1127
  [
@@ -1222,6 +1299,11 @@ function Explorer({
1222
1299
  setBlueprintFunctions(bundle.blueprint.addedFunctions)
1223
1300
  setBlueprintVariables(bundle.blueprint.addedVariables)
1224
1301
  setBlueprintImports(bundle.blueprint.addedImports)
1302
+ if (!notesDirty.current) {
1303
+ const nextPointers = parseBlueprintPointers(bundle.blueprint.pointers)
1304
+ blueprintPointersRef.current = nextPointers
1305
+ setBlueprintPointers(nextPointers)
1306
+ }
1225
1307
  if (!notesDirty.current && !isKeyboardIsolated()) {
1226
1308
  const nextNotes = parseBlueprintNotes(bundle.blueprint.notes)
1227
1309
  blueprintNotesRef.current = nextNotes
@@ -1305,7 +1387,8 @@ function Explorer({
1305
1387
  blueprintFunctions.length > 0 ||
1306
1388
  blueprintVariables.length > 0 ||
1307
1389
  blueprintImports.length > 0 ||
1308
- blueprintNotes.length > 0
1390
+ blueprintNotes.length > 0 ||
1391
+ blueprintPointers.length > 0
1309
1392
  const blueprintCanCleanup =
1310
1393
  userBlocks.some((block) => !block.naming && knownFileIds.has(block.id)) ||
1311
1394
  userIslands.some(
@@ -1325,7 +1408,9 @@ function Explorer({
1325
1408
  setBlueprintVariables([])
1326
1409
  setBlueprintImports([])
1327
1410
  blueprintNotesRef.current = []
1411
+ blueprintPointersRef.current = []
1328
1412
  setBlueprintNotes([])
1413
+ setBlueprintPointers([])
1329
1414
  void persistBlueprintClear()
1330
1415
  }, [])
1331
1416
 
@@ -1412,6 +1497,20 @@ function Explorer({
1412
1497
  userCreatedBlocks={visibleBlocks}
1413
1498
  userCreatedIslands={visibleIslands}
1414
1499
  overlayBlocks={overlayBlocks}
1500
+ pointedFileIds={
1501
+ blueprintHidden
1502
+ ? undefined
1503
+ : blueprintPointers.flatMap((item) =>
1504
+ item.kind === 'folder' ? [] : [item.path],
1505
+ )
1506
+ }
1507
+ pointedFolderPaths={
1508
+ blueprintHidden
1509
+ ? undefined
1510
+ : blueprintPointers.flatMap((item) =>
1511
+ item.kind === 'folder' ? [item.path] : [],
1512
+ )
1513
+ }
1415
1514
  mapGraph={
1416
1515
  changePathsOnly && hasChangeSet ? changePathGraph : null
1417
1516
  }
@@ -1470,6 +1569,7 @@ function Explorer({
1470
1569
  blueprintVariables={blueprintVariables}
1471
1570
  blueprintImports={blueprintImports}
1472
1571
  blueprintNotes={blueprintNotes}
1572
+ blueprintPointers={blueprintPointers}
1473
1573
  onAddBlueprintFunction={addBlueprintFunction}
1474
1574
  onAddBlueprintVariable={addBlueprintVariable}
1475
1575
  onAddBlueprintImport={addBlueprintImport}
@@ -1477,6 +1577,7 @@ function Explorer({
1477
1577
  onRemoveBlueprintVariable={removeBlueprintVariable}
1478
1578
  onRemoveBlueprintImport={removeBlueprintImport}
1479
1579
  onSetBlueprintNote={applyBlueprintNote}
1580
+ onToggleBlueprintPointer={applyBlueprintPointer}
1480
1581
  onMapAddFile={placeBlockOnFolder}
1481
1582
  onMapAddFolder={placeIslandOnFolder}
1482
1583
  onInspectFile={inspectFile}
@@ -1493,8 +1594,16 @@ function Explorer({
1493
1594
  />
1494
1595
  <MapContextMenu
1495
1596
  menu={mapMenu}
1597
+ pointed={
1598
+ mapMenu
1599
+ ? findBlueprintPointer(blueprintPointers, 'folder', mapMenu.folder)
1600
+ : false
1601
+ }
1496
1602
  onAddFile={placeBlockOnFolder}
1497
1603
  onAddFolder={placeIslandOnFolder}
1604
+ onPointToFolder={(folder) =>
1605
+ applyBlueprintPointer({ kind: 'folder', path: folder })
1606
+ }
1498
1607
  onClose={() => setMapMenu(null)}
1499
1608
  />
1500
1609
  </>
@@ -2,6 +2,7 @@ import type {
2
2
  AgentIntent,
3
3
  AgentIntentBundle,
4
4
  BlueprintNote,
5
+ BlueprintPointer,
5
6
  PatchImport,
6
7
  PatchImportAddition,
7
8
  PatchSymbolAddition,
@@ -11,6 +12,7 @@ import type {
11
12
  } from './types'
12
13
  import {
13
14
  parseBlueprintNotes,
15
+ parseBlueprintPointers,
14
16
  parseUserCreatedBlocks,
15
17
  parseUserCreatedIslands,
16
18
  } from './userCreated'
@@ -133,6 +135,7 @@ export const emptyIntent: AgentIntent = {
133
135
  blueprintVariables: [],
134
136
  blueprintImports: [],
135
137
  blueprintNotes: [],
138
+ blueprintPointers: [],
136
139
  }
137
140
 
138
141
  function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
@@ -192,6 +195,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
192
195
  blueprintVariables: normalizeSymbolAdditions(data?.blueprintVariables),
193
196
  blueprintImports: normalizeImportAdditions(data?.blueprintImports),
194
197
  blueprintNotes: parseBlueprintNotes(data?.blueprintNotes),
198
+ blueprintPointers: parseBlueprintPointers(data?.blueprintPointers),
195
199
  }
196
200
  }
197
201
 
@@ -206,6 +210,7 @@ function normalizeBlueprint(data: Partial<AgentIntentBundle['blueprint']> | null
206
210
  addedVariables: normalizeSymbolAdditions(data?.addedVariables),
207
211
  addedImports: normalizeImportAdditions(data?.addedImports),
208
212
  notes: parseBlueprintNotes(data?.notes),
213
+ pointers: parseBlueprintPointers(data?.pointers),
209
214
  }
210
215
  }
211
216
 
@@ -256,13 +261,15 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
256
261
  intent.blueprintFunctions.length > 0 ||
257
262
  intent.blueprintVariables.length > 0 ||
258
263
  intent.blueprintImports.length > 0 ||
259
- intent.blueprintNotes.length > 0,
264
+ intent.blueprintNotes.length > 0 ||
265
+ intent.blueprintPointers.length > 0,
260
266
  userCreatedBlocks: intent.userCreatedBlocks,
261
267
  userCreatedIslands: intent.userCreatedIslands,
262
268
  addedFunctions: intent.blueprintFunctions,
263
269
  addedVariables: intent.blueprintVariables,
264
270
  addedImports: intent.blueprintImports,
265
271
  notes: intent.blueprintNotes,
272
+ pointers: intent.blueprintPointers,
266
273
  }),
267
274
  }
268
275
  }
@@ -295,6 +302,7 @@ export async function performAgentAction(
295
302
  addedVariables?: PatchSymbolAddition[]
296
303
  addedImports?: PatchImportAddition[]
297
304
  notes?: BlueprintNote[]
305
+ pointers?: BlueprintPointer[]
298
306
  } = {},
299
307
  ) {
300
308
  const response = await fetch('/api/agent-intent', {
@@ -318,6 +326,7 @@ export function persistSessionBlueprint(
318
326
  addedVariables?: PatchSymbolAddition[]
319
327
  addedImports?: PatchImportAddition[]
320
328
  notes?: BlueprintNote[]
329
+ pointers?: BlueprintPointer[]
321
330
  },
322
331
  ) {
323
332
  return fetch('/api/agent-intent', {
@@ -2,6 +2,7 @@ import { memo, Suspense } from 'react'
2
2
  import { Billboard, Edges, Html, Text } from '@react-three/drei'
3
3
  import { CHANGE_HIGHLIGHT, CONFIG, dimColor, fileColor, FILE_SELECTION, MAP_SELECTION, type ChangeKind } from '../theme'
4
4
  import { NameInput } from '../ui/NameInput'
5
+ import { EyeIcon } from '../ui/EyeIcon'
5
6
  import { MapSelectBorder } from './MapSelectBorder'
6
7
  import type { FileNode } from '../types'
7
8
  import type { PlacedFile } from '../types'
@@ -30,6 +31,7 @@ type FileBlockProps = {
30
31
  highlightMapChange?: boolean
31
32
  previewLabels?: boolean
32
33
  labelVisible?: boolean
34
+ pointed?: boolean
33
35
  onCommitName?: (name: string) => void
34
36
  onCancelName?: () => void
35
37
  }
@@ -93,6 +95,7 @@ export const FileBlock = memo(function FileBlock({
93
95
  highlightMapChange = mapMode,
94
96
  previewLabels = false,
95
97
  labelVisible = true,
98
+ pointed = false,
96
99
  onCommitName,
97
100
  onCancelName,
98
101
  }: FileBlockProps) {
@@ -191,6 +194,30 @@ export const FileBlock = memo(function FileBlock({
191
194
  {mapMode && !naming && mark && (
192
195
  <MapChangeMark mark={mark} width={width} depth={depth} height={height} />
193
196
  )}
197
+ {pointed && !naming && (
198
+ <Html
199
+ position={
200
+ mapMode
201
+ ? mark
202
+ ? [width * 0.28, height / 2 + 0.16, -depth * 0.28]
203
+ : [0, height / 2 + 0.12, 0]
204
+ : [0, height / 2 + (planned || highlight ? 0.92 : 0.74), 0]
205
+ }
206
+ center
207
+ occlude={false}
208
+ style={{ pointerEvents: 'none' }}
209
+ zIndexRange={[40, 0]}
210
+ >
211
+ <div
212
+ className="blueprint-eye"
213
+ data-map={mapMode ? 'true' : 'false'}
214
+ role="img"
215
+ aria-label="Keep in mind"
216
+ >
217
+ <EyeIcon size={mapMode ? 16 : 18} title="Keep in mind" />
218
+ </div>
219
+ </Html>
220
+ )}
194
221
  {naming && onCommitName && onCancelName && (
195
222
  <Html
196
223
  position={
@@ -11,6 +11,7 @@ import {
11
11
  import type { PlacedFolder } from '../types'
12
12
  import { MapSelectBorder } from './MapSelectBorder'
13
13
  import { NameInput } from '../ui/NameInput'
14
+ import { EyeIcon } from '../ui/EyeIcon'
14
15
 
15
16
  type FolderAreaProps = {
16
17
  folder: PlacedFolder
@@ -20,6 +21,7 @@ type FolderAreaProps = {
20
21
  highlightKind?: ChangeKind | null
21
22
  previewLabels?: boolean
22
23
  labelVisible?: boolean
24
+ pointed?: boolean
23
25
  onCommitName?: (name: string) => void
24
26
  onCancelName?: () => void
25
27
  }
@@ -32,6 +34,7 @@ export function FolderArea({
32
34
  highlightKind = null,
33
35
  previewLabels = false,
34
36
  labelVisible = true,
37
+ pointed = false,
35
38
  onCommitName,
36
39
  onCancelName,
37
40
  }: FolderAreaProps) {
@@ -93,6 +96,24 @@ export function FolderArea({
93
96
  <meshBasicMaterial color={aisle} />
94
97
  </mesh>
95
98
  )}
99
+ {pointed && !naming && (
100
+ <Html
101
+ position={[0, mapMode ? 1.15 : 1.45, -folder.depth / 2 + 2.2]}
102
+ center
103
+ occlude={false}
104
+ style={{ pointerEvents: 'none' }}
105
+ zIndexRange={[40, 0]}
106
+ >
107
+ <div
108
+ className="blueprint-eye"
109
+ data-map={mapMode ? 'true' : 'false'}
110
+ role="img"
111
+ aria-label="Keep in mind"
112
+ >
113
+ <EyeIcon size={mapMode ? 18 : 22} title="Keep in mind" />
114
+ </div>
115
+ </Html>
116
+ )}
96
117
  {naming && mapMode && onCommitName && onCancelName && (
97
118
  <Html
98
119
  position={[0, 1.35, -folder.depth / 2 + 1.35]}