@jkwd/inbase 0.1.18 → 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.
@@ -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(() => {
@@ -475,10 +494,11 @@ function Explorer({
475
494
 
476
495
  const flyAlongRelation = useCallback(
477
496
  (fromId: string, toId: string) => {
497
+ if (mode !== 'walk') return
478
498
  const [x, z] = walkPos.current
479
499
  travelToFile(relationTravelTarget(fromId, toId, x, z, layout.files), true)
480
500
  },
481
- [layout.files, travelToFile],
501
+ [layout.files, mode, travelToFile],
482
502
  )
483
503
 
484
504
  const runWorkflowAction = useCallback(
@@ -510,6 +530,7 @@ function Explorer({
510
530
  addedVariables: blueprintVariables,
511
531
  addedImports: blueprintImports,
512
532
  notes: blueprintNotes,
533
+ pointers: blueprintPointers,
513
534
  }
514
535
  : {}),
515
536
  },
@@ -533,6 +554,7 @@ function Explorer({
533
554
  blueprintFunctions,
534
555
  blueprintImports,
535
556
  blueprintNotes,
557
+ blueprintPointers,
536
558
  blueprintVariables,
537
559
  intents,
538
560
  onRefreshGraph,
@@ -617,6 +639,7 @@ function Explorer({
617
639
  variables: PatchSymbolAddition[] = blueprintVariablesRef.current,
618
640
  imports: PatchImportAddition[] = blueprintImportsRef.current,
619
641
  notes: BlueprintNote[] = blueprintNotesRef.current,
642
+ pointers: BlueprintPointer[] = blueprintPointersRef.current,
620
643
  ) => {
621
644
  if (notePersistTimer.current != null) {
622
645
  window.clearTimeout(notePersistTimer.current)
@@ -631,6 +654,7 @@ function Explorer({
631
654
  addedVariables: variables,
632
655
  addedImports: imports,
633
656
  notes,
657
+ pointers,
634
658
  }).finally(() => {
635
659
  if (gen !== blueprintPersistGen.current) return
636
660
  if (notePersistTimer.current != null) return
@@ -663,6 +687,7 @@ function Explorer({
663
687
  addedVariables: blueprintVariablesRef.current,
664
688
  addedImports: blueprintImportsRef.current,
665
689
  notes: blueprintNotesRef.current,
690
+ pointers: blueprintPointersRef.current,
666
691
  })
667
692
  notesDirty.current = false
668
693
  }
@@ -683,6 +708,28 @@ function Explorer({
683
708
  [persistBlueprintSoon],
684
709
  )
685
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
+
686
733
  const placeBlock = useCallback(
687
734
  (spot: { x: number; z: number; folder: string }) => {
688
735
  if (!canPlace) return
@@ -831,10 +878,23 @@ function Explorer({
831
878
  if (!selected || selected.naming) return false
832
879
  const next = userBlocks.filter((block) => block.id !== selectedId)
833
880
  const notes = dropBlueprintFileNotes(blueprintNotesRef.current, [selectedId])
881
+ const pointers = dropBlueprintFilePointers(blueprintPointersRef.current, [
882
+ selectedId,
883
+ ])
834
884
  blueprintNotesRef.current = notes
885
+ blueprintPointersRef.current = pointers
835
886
  setUserBlocks(next)
836
887
  setBlueprintNotes(notes)
837
- 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
+ )
838
898
  setSelectedId(null)
839
899
  return true
840
900
  }, [canPlace, persistBlueprint, selectedId, userBlocks, userIslands])
@@ -999,9 +1059,17 @@ function Explorer({
999
1059
  'function',
1000
1060
  name,
1001
1061
  )
1062
+ const pointers = dropBlueprintSymbolPointer(
1063
+ blueprintPointersRef.current,
1064
+ fileId,
1065
+ 'function',
1066
+ name,
1067
+ )
1002
1068
  blueprintNotesRef.current = notes
1069
+ blueprintPointersRef.current = pointers
1003
1070
  setBlueprintFunctions(next)
1004
1071
  setBlueprintNotes(notes)
1072
+ setBlueprintPointers(pointers)
1005
1073
  persistBlueprint(
1006
1074
  userBlocks,
1007
1075
  userIslands,
@@ -1009,6 +1077,7 @@ function Explorer({
1009
1077
  blueprintVariables,
1010
1078
  blueprintImports,
1011
1079
  notes,
1080
+ pointers,
1012
1081
  )
1013
1082
  },
1014
1083
  [
@@ -1034,9 +1103,17 @@ function Explorer({
1034
1103
  'variable',
1035
1104
  name,
1036
1105
  )
1106
+ const pointers = dropBlueprintSymbolPointer(
1107
+ blueprintPointersRef.current,
1108
+ fileId,
1109
+ 'variable',
1110
+ name,
1111
+ )
1037
1112
  blueprintNotesRef.current = notes
1113
+ blueprintPointersRef.current = pointers
1038
1114
  setBlueprintVariables(next)
1039
1115
  setBlueprintNotes(notes)
1116
+ setBlueprintPointers(pointers)
1040
1117
  persistBlueprint(
1041
1118
  userBlocks,
1042
1119
  userIslands,
@@ -1044,6 +1121,7 @@ function Explorer({
1044
1121
  next,
1045
1122
  blueprintImports,
1046
1123
  notes,
1124
+ pointers,
1047
1125
  )
1048
1126
  },
1049
1127
  [
@@ -1221,6 +1299,11 @@ function Explorer({
1221
1299
  setBlueprintFunctions(bundle.blueprint.addedFunctions)
1222
1300
  setBlueprintVariables(bundle.blueprint.addedVariables)
1223
1301
  setBlueprintImports(bundle.blueprint.addedImports)
1302
+ if (!notesDirty.current) {
1303
+ const nextPointers = parseBlueprintPointers(bundle.blueprint.pointers)
1304
+ blueprintPointersRef.current = nextPointers
1305
+ setBlueprintPointers(nextPointers)
1306
+ }
1224
1307
  if (!notesDirty.current && !isKeyboardIsolated()) {
1225
1308
  const nextNotes = parseBlueprintNotes(bundle.blueprint.notes)
1226
1309
  blueprintNotesRef.current = nextNotes
@@ -1304,7 +1387,8 @@ function Explorer({
1304
1387
  blueprintFunctions.length > 0 ||
1305
1388
  blueprintVariables.length > 0 ||
1306
1389
  blueprintImports.length > 0 ||
1307
- blueprintNotes.length > 0
1390
+ blueprintNotes.length > 0 ||
1391
+ blueprintPointers.length > 0
1308
1392
  const blueprintCanCleanup =
1309
1393
  userBlocks.some((block) => !block.naming && knownFileIds.has(block.id)) ||
1310
1394
  userIslands.some(
@@ -1324,7 +1408,9 @@ function Explorer({
1324
1408
  setBlueprintVariables([])
1325
1409
  setBlueprintImports([])
1326
1410
  blueprintNotesRef.current = []
1411
+ blueprintPointersRef.current = []
1327
1412
  setBlueprintNotes([])
1413
+ setBlueprintPointers([])
1328
1414
  void persistBlueprintClear()
1329
1415
  }, [])
1330
1416
 
@@ -1411,6 +1497,20 @@ function Explorer({
1411
1497
  userCreatedBlocks={visibleBlocks}
1412
1498
  userCreatedIslands={visibleIslands}
1413
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
+ }
1414
1514
  mapGraph={
1415
1515
  changePathsOnly && hasChangeSet ? changePathGraph : null
1416
1516
  }
@@ -1469,6 +1569,7 @@ function Explorer({
1469
1569
  blueprintVariables={blueprintVariables}
1470
1570
  blueprintImports={blueprintImports}
1471
1571
  blueprintNotes={blueprintNotes}
1572
+ blueprintPointers={blueprintPointers}
1472
1573
  onAddBlueprintFunction={addBlueprintFunction}
1473
1574
  onAddBlueprintVariable={addBlueprintVariable}
1474
1575
  onAddBlueprintImport={addBlueprintImport}
@@ -1476,6 +1577,7 @@ function Explorer({
1476
1577
  onRemoveBlueprintVariable={removeBlueprintVariable}
1477
1578
  onRemoveBlueprintImport={removeBlueprintImport}
1478
1579
  onSetBlueprintNote={applyBlueprintNote}
1580
+ onToggleBlueprintPointer={applyBlueprintPointer}
1479
1581
  onMapAddFile={placeBlockOnFolder}
1480
1582
  onMapAddFolder={placeIslandOnFolder}
1481
1583
  onInspectFile={inspectFile}
@@ -1492,8 +1594,16 @@ function Explorer({
1492
1594
  />
1493
1595
  <MapContextMenu
1494
1596
  menu={mapMenu}
1597
+ pointed={
1598
+ mapMenu
1599
+ ? findBlueprintPointer(blueprintPointers, 'folder', mapMenu.folder)
1600
+ : false
1601
+ }
1495
1602
  onAddFile={placeBlockOnFolder}
1496
1603
  onAddFolder={placeIslandOnFolder}
1604
+ onPointToFolder={(folder) =>
1605
+ applyBlueprintPointer({ kind: 'folder', path: folder })
1606
+ }
1497
1607
  onClose={() => setMapMenu(null)}
1498
1608
  />
1499
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'
@@ -65,6 +67,26 @@ function normalizeAck(
65
67
  }
66
68
  }
67
69
 
70
+ function normalizeContextFiles(value: unknown): NonNullable<AgentIntent['contextFiles']> {
71
+ if (!Array.isArray(value)) return []
72
+ return value.flatMap((item) => {
73
+ if (!item || typeof item !== 'object') return []
74
+ const id = (item as { id?: unknown }).id
75
+ const name = (item as { name?: unknown }).name
76
+ const mimeType = (item as { mimeType?: unknown }).mimeType
77
+ const size = (item as { size?: unknown }).size
78
+ if (
79
+ typeof id !== 'string' ||
80
+ typeof name !== 'string' ||
81
+ typeof mimeType !== 'string' ||
82
+ typeof size !== 'number'
83
+ ) {
84
+ return []
85
+ }
86
+ return [{ id, name, mimeType, size }]
87
+ })
88
+ }
89
+
68
90
  export const emptyIntent: AgentIntent = {
69
91
  updatedAt: null,
70
92
  showMap: false,
@@ -101,6 +123,7 @@ export const emptyIntent: AgentIntent = {
101
123
  listening: false,
102
124
  lastAck: null,
103
125
  initialInstruction: null,
126
+ contextFiles: [],
104
127
  creationMode: false,
105
128
  canEnterBlueprint: false,
106
129
  blueprintHidden: false,
@@ -112,6 +135,7 @@ export const emptyIntent: AgentIntent = {
112
135
  blueprintVariables: [],
113
136
  blueprintImports: [],
114
137
  blueprintNotes: [],
138
+ blueprintPointers: [],
115
139
  }
116
140
 
117
141
  function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
@@ -155,6 +179,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
155
179
  lastAck: normalizeAck(data?.lastAck),
156
180
  initialInstruction:
157
181
  typeof data?.initialInstruction === 'string' ? data.initialInstruction : null,
182
+ contextFiles: normalizeContextFiles(data?.contextFiles),
158
183
  creationMode: Boolean(data?.creationMode),
159
184
  canEnterBlueprint: Boolean(data?.canEnterBlueprint),
160
185
  blueprintHidden: Boolean(data?.blueprintHidden),
@@ -170,6 +195,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
170
195
  blueprintVariables: normalizeSymbolAdditions(data?.blueprintVariables),
171
196
  blueprintImports: normalizeImportAdditions(data?.blueprintImports),
172
197
  blueprintNotes: parseBlueprintNotes(data?.blueprintNotes),
198
+ blueprintPointers: parseBlueprintPointers(data?.blueprintPointers),
173
199
  }
174
200
  }
175
201
 
@@ -184,6 +210,7 @@ function normalizeBlueprint(data: Partial<AgentIntentBundle['blueprint']> | null
184
210
  addedVariables: normalizeSymbolAdditions(data?.addedVariables),
185
211
  addedImports: normalizeImportAdditions(data?.addedImports),
186
212
  notes: parseBlueprintNotes(data?.notes),
213
+ pointers: parseBlueprintPointers(data?.pointers),
187
214
  }
188
215
  }
189
216
 
@@ -234,13 +261,15 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
234
261
  intent.blueprintFunctions.length > 0 ||
235
262
  intent.blueprintVariables.length > 0 ||
236
263
  intent.blueprintImports.length > 0 ||
237
- intent.blueprintNotes.length > 0,
264
+ intent.blueprintNotes.length > 0 ||
265
+ intent.blueprintPointers.length > 0,
238
266
  userCreatedBlocks: intent.userCreatedBlocks,
239
267
  userCreatedIslands: intent.userCreatedIslands,
240
268
  addedFunctions: intent.blueprintFunctions,
241
269
  addedVariables: intent.blueprintVariables,
242
270
  addedImports: intent.blueprintImports,
243
271
  notes: intent.blueprintNotes,
272
+ pointers: intent.blueprintPointers,
244
273
  }),
245
274
  }
246
275
  }
@@ -273,6 +302,7 @@ export async function performAgentAction(
273
302
  addedVariables?: PatchSymbolAddition[]
274
303
  addedImports?: PatchImportAddition[]
275
304
  notes?: BlueprintNote[]
305
+ pointers?: BlueprintPointer[]
276
306
  } = {},
277
307
  ) {
278
308
  const response = await fetch('/api/agent-intent', {
@@ -296,6 +326,7 @@ export function persistSessionBlueprint(
296
326
  addedVariables?: PatchSymbolAddition[]
297
327
  addedImports?: PatchImportAddition[]
298
328
  notes?: BlueprintNote[]
329
+ pointers?: BlueprintPointer[]
299
330
  },
300
331
  ) {
301
332
  return fetch('/api/agent-intent', {
@@ -352,6 +383,50 @@ export function persistInitialInstruction(sessionId: string, instruction: string
352
383
  })
353
384
  }
354
385
 
386
+ export async function persistAddContextFiles(
387
+ sessionId: string,
388
+ files: Array<{ name: string; mimeType: string; contentBase64: string }>,
389
+ ) {
390
+ const response = await fetch('/api/agent-intent', {
391
+ method: 'POST',
392
+ headers: { 'Content-Type': 'application/json' },
393
+ body: JSON.stringify({
394
+ action: 'add_context_files',
395
+ sessionId,
396
+ files,
397
+ }),
398
+ })
399
+ if (!response.ok) {
400
+ const detail = await response.text()
401
+ let message = detail || 'Could not attach files'
402
+ try {
403
+ const parsed = JSON.parse(detail) as { error?: string }
404
+ if (parsed?.error) message = parsed.error
405
+ } catch {
406
+ // Use the raw body when it is not JSON.
407
+ }
408
+ throw new Error(message)
409
+ }
410
+ return normalize((await response.json()) as AgentIntent)
411
+ }
412
+
413
+ export async function persistRemoveContextFile(sessionId: string, fileId: string) {
414
+ const response = await fetch('/api/agent-intent', {
415
+ method: 'POST',
416
+ headers: { 'Content-Type': 'application/json' },
417
+ body: JSON.stringify({
418
+ action: 'remove_context_file',
419
+ sessionId,
420
+ fileId,
421
+ }),
422
+ })
423
+ if (!response.ok) {
424
+ const detail = await response.text()
425
+ throw new Error(detail || 'Could not remove file')
426
+ }
427
+ return normalize((await response.json()) as AgentIntent)
428
+ }
429
+
355
430
  export function persistSessionFocus(sessionId: string) {
356
431
  fetch('/api/agent-intent', {
357
432
  method: 'POST',
@@ -1133,6 +1133,71 @@ button {
1133
1133
  margin-top: 0;
1134
1134
  }
1135
1135
 
1136
+ .hud-context {
1137
+ display: grid;
1138
+ gap: 8px;
1139
+ margin-top: 8px;
1140
+ }
1141
+
1142
+ .hud-context-drop {
1143
+ width: 100%;
1144
+ min-height: 52px;
1145
+ padding: 10px 8px;
1146
+ color: #8b95a5;
1147
+ background: var(--vc-surface);
1148
+ border: 1px dashed #3a4250;
1149
+ cursor: pointer;
1150
+ font: 12px/1.4 Inter, ui-sans-serif, system-ui, sans-serif;
1151
+ text-align: center;
1152
+ }
1153
+
1154
+ .hud-context-drop:hover,
1155
+ .hud-context-drop:focus-visible,
1156
+ .hud-context-drop[data-over='true'] {
1157
+ color: #e7ebf2;
1158
+ border-color: #d6b56a;
1159
+ outline: none;
1160
+ }
1161
+
1162
+ .hud-context-drop[data-busy='true'],
1163
+ .hud-context-drop:disabled {
1164
+ cursor: wait;
1165
+ opacity: 0.7;
1166
+ }
1167
+
1168
+ .hud-context-list {
1169
+ display: grid;
1170
+ gap: 4px;
1171
+ margin: 0;
1172
+ padding: 0;
1173
+ list-style: none;
1174
+ }
1175
+
1176
+ .hud-context-list li {
1177
+ display: grid;
1178
+ grid-template-columns: minmax(0, 1fr) auto auto;
1179
+ align-items: center;
1180
+ gap: 6px;
1181
+ color: #e7ebf2;
1182
+ font-size: 11px;
1183
+ }
1184
+
1185
+ .hud-context-name {
1186
+ overflow: hidden;
1187
+ text-overflow: ellipsis;
1188
+ white-space: nowrap;
1189
+ }
1190
+
1191
+ .hud-context-size {
1192
+ color: #8b95a5;
1193
+ }
1194
+
1195
+ .hud-context-error {
1196
+ margin: 0;
1197
+ color: #ff8a9b;
1198
+ font-size: 11px;
1199
+ }
1200
+
1136
1201
  .hud-instruction textarea {
1137
1202
  width: 100%;
1138
1203
  resize: vertical;
@@ -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]}
@@ -22,7 +22,6 @@ type MapViewProps = {
22
22
  onLand: (x: number, z: number) => void
23
23
  onSelect: (fileId: string | null) => void
24
24
  onSelectFolder: (folderPath: string | null) => void
25
- onTravelTo: (fromId: string, toId: string) => void
26
25
  onBlueprintMenu?: (menu: MapBlueprintMenu) => void
27
26
  }
28
27
 
@@ -36,7 +35,6 @@ export function MapView({
36
35
  onLand,
37
36
  onSelect,
38
37
  onSelectFolder,
39
- onTravelTo,
40
38
  onBlueprintMenu,
41
39
  }: MapViewProps) {
42
40
  const size = useThree((state) => state.size)
@@ -157,18 +155,11 @@ export function MapView({
157
155
 
158
156
  const pick = pickAt(event.clientX, event.clientY)
159
157
  if (!pick) return
160
- const { relationHit, fileHit } = pick
158
+ const { fileHit } = pick
161
159
  if (fileHit) {
162
160
  onSelect(fileHit.object.userData.fileId as string)
163
161
  return
164
162
  }
165
- if (relationHit) {
166
- onTravelTo(
167
- relationHit.object.userData.relationFrom as string,
168
- relationHit.object.userData.relationTo as string,
169
- )
170
- return
171
- }
172
163
 
173
164
  const hit = new THREE.Vector3()
174
165
  const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
@@ -286,7 +277,6 @@ export function MapView({
286
277
  onBlueprintMenu,
287
278
  onSelect,
288
279
  onSelectFolder,
289
- onTravelTo,
290
280
  scene,
291
281
  selectedFolder,
292
282
  ])