@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.
@@ -30,6 +30,8 @@ type SelectionThumbnailProps = {
30
30
  plannedIds?: string[]
31
31
  createdIds?: string[]
32
32
  deletedIds?: string[]
33
+ pointedFileIds?: string[]
34
+ pointedFolderPaths?: string[]
33
35
  onMinimize?: () => void
34
36
  onMaximize?: () => void
35
37
  onHide: () => void
@@ -464,6 +466,8 @@ function ThumbnailScene({
464
466
  planned,
465
467
  created,
466
468
  deleted,
469
+ pointedFiles,
470
+ pointedFolders,
467
471
  zoom,
468
472
  zoomRef,
469
473
  panRef,
@@ -481,6 +485,8 @@ function ThumbnailScene({
481
485
  planned: Set<string>
482
486
  created: Set<string>
483
487
  deleted: Set<string>
488
+ pointedFiles: Set<string>
489
+ pointedFolders: Set<string>
484
490
  zoom: number
485
491
  zoomRef: { current: number }
486
492
  panRef: { current: Vec3 }
@@ -496,15 +502,18 @@ function ThumbnailScene({
496
502
  const selected = file.id === target.selectedFileId
497
503
  const related = target.relatedIds.has(file.id)
498
504
  const changeKind = fileChangeKind(file.id, planned, created, deleted)
505
+ const pointed = pointedFiles.has(file.id)
499
506
  const dimmed =
500
507
  Boolean(target.selectedFileId) &&
501
508
  !selected &&
502
509
  !related &&
503
- !changeKind
510
+ !changeKind &&
511
+ !pointed
504
512
  let priority = placed.size[1]
505
513
  if (selected) priority += 1000
506
514
  else if (related) priority += 400
507
515
  else if (changeKind) priority += 300
516
+ else if (pointed) priority += 250
508
517
  else if (file.userCreated) priority += 80
509
518
  if (dimmed) priority -= 200
510
519
  return {
@@ -517,7 +526,7 @@ function ThumbnailScene({
517
526
  priority,
518
527
  }
519
528
  }),
520
- [created, deleted, planned, target],
529
+ [created, deleted, planned, pointedFiles, target],
521
530
  )
522
531
 
523
532
  return (
@@ -554,12 +563,14 @@ function ThumbnailScene({
554
563
  <FolderArea
555
564
  folder={target.folder}
556
565
  highlightKind={highlightedFolders[target.folder.path] ?? null}
566
+ pointed={pointedFolders.has(target.folder.path)}
557
567
  previewLabels={zoom < 1.45}
558
568
  />
559
569
  {target.files.map(({ file, placed }) => {
560
570
  const selected = file.id === target.selectedFileId
561
571
  const related = target.relatedIds.has(file.id)
562
572
  const changeKind = fileChangeKind(file.id, planned, created, deleted)
573
+ const pointed = pointedFiles.has(file.id)
563
574
  return (
564
575
  <FileBlock
565
576
  key={file.id}
@@ -570,6 +581,7 @@ function ThumbnailScene({
570
581
  planned={Boolean(changeKind)}
571
582
  changeKind={changeKind}
572
583
  added={created.has(file.id) || file.userCreated}
584
+ pointed={pointed}
573
585
  highlightMapChange
574
586
  previewLabels
575
587
  labelVisible={visibleLabelIds.has(file.id)}
@@ -577,7 +589,8 @@ function ThumbnailScene({
577
589
  Boolean(target.selectedFileId) &&
578
590
  !selected &&
579
591
  !related &&
580
- !changeKind
592
+ !changeKind &&
593
+ !pointed
581
594
  }
582
595
  />
583
596
  )
@@ -73,6 +73,8 @@ type WorldProps = {
73
73
  userCreatedBlocks?: UserCreatedBlock[]
74
74
  userCreatedIslands?: UserCreatedIsland[]
75
75
  overlayBlocks?: UserCreatedBlock[]
76
+ pointedFileIds?: string[]
77
+ pointedFolderPaths?: string[]
76
78
  namingIslandId?: string | null
77
79
  mapGraph?: CodebaseGraph | null
78
80
  mapLayout?: WorldLayout | null
@@ -117,11 +119,15 @@ export function World({
117
119
  userCreatedBlocks = [],
118
120
  userCreatedIslands = [],
119
121
  overlayBlocks = [],
122
+ pointedFileIds = [],
123
+ pointedFolderPaths = [],
120
124
  mapGraph = null,
121
125
  mapLayout = null,
122
126
  }: WorldProps) {
123
127
  const created = new Set(createdIds)
124
128
  const deleted = new Set(deletedIds)
129
+ const pointedFiles = new Set(pointedFileIds)
130
+ const pointedFolders = new Set(pointedFolderPaths)
125
131
  const mapping = mode === 'map'
126
132
  const viewGraph = mapping && mapGraph ? mapGraph : graph
127
133
  const viewLayout = mapping && mapLayout ? mapLayout : layout
@@ -169,17 +175,19 @@ export function World({
169
175
  if (selectedId) ids.add(selectedId)
170
176
  if (namingId) ids.add(namingId)
171
177
  if (aimedRelation?.flyTo) ids.add(aimedRelation.flyTo)
178
+ for (const id of pointedFileIds) ids.add(id)
172
179
  if (ghostKey) {
173
180
  for (const id of ghostKey.split('|')) ids.add(id)
174
181
  }
175
182
  return ids
176
- }, [aimedRelation?.flyTo, ghostKey, namingId, selectedId])
183
+ }, [aimedRelation?.flyTo, ghostKey, namingId, pointedFileIds, selectedId])
177
184
  const keepFolderPaths = useMemo(() => {
178
185
  const paths = new Set<string>()
179
186
  if (selectedFolder) paths.add(selectedFolder)
180
187
  if (namingIslandId) paths.add(namingIslandId)
188
+ for (const path of pointedFolderPaths) paths.add(path)
181
189
  return paths
182
- }, [namingIslandId, selectedFolder])
190
+ }, [namingIslandId, pointedFolderPaths, selectedFolder])
183
191
  const originLod = useMemo(() => {
184
192
  if (mapping) return null
185
193
  return computeWalkLod({
@@ -254,6 +262,7 @@ export function World({
254
262
  highlightKind={
255
263
  mapping ? highlightedFolders[folder.path] ?? null : null
256
264
  }
265
+ pointed={pointedFolders.has(folder.path)}
257
266
  labelVisible={!lod || lod.folderLabels.has(folder.path)}
258
267
  onCommitName={
259
268
  folder.path === namingIslandId && onCommitIslandName
@@ -285,14 +294,22 @@ export function World({
285
294
  const changeKind = fileChangeKind(file.id, planned, created, deleted)
286
295
  const naming = file.id === namingId
287
296
  const aimed = file.id === aimedRelation?.flyTo
297
+ const pointed = pointedFiles.has(file.id)
288
298
  const detailed =
289
- selected || isRelated || isPlanned || naming || aimed || Boolean(changeKind)
299
+ selected ||
300
+ isRelated ||
301
+ isPlanned ||
302
+ naming ||
303
+ aimed ||
304
+ pointed ||
305
+ Boolean(changeKind)
290
306
  if (lod && !lod.files.has(file.id)) return null
291
307
  const dimmed =
292
308
  hasFocus &&
293
309
  !selected &&
294
310
  !isRelated &&
295
311
  !changeKind &&
312
+ !pointed &&
296
313
  !patchLinked.has(file.id) &&
297
314
  !folderFileIds.has(file.id)
298
315
  if (lod && !detailed && !lod.labels.has(file.id)) {
@@ -310,6 +327,7 @@ export function World({
310
327
  changeKind={changeKind}
311
328
  added={created.has(file.id) || file.userCreated}
312
329
  aimed={aimed}
330
+ pointed={pointed}
313
331
  dimmed={dimmed}
314
332
  naming={naming}
315
333
  mapMode={mapping}
@@ -347,6 +365,7 @@ export function World({
347
365
  planned={false}
348
366
  changeKind="add"
349
367
  added
368
+ pointed={pointedFiles.has(file.id)}
350
369
  dimmed={false}
351
370
  mapMode={mapping}
352
371
  labelVisible
@@ -225,6 +225,14 @@ export type BlueprintNote = {
225
225
  note: string
226
226
  }
227
227
 
228
+ export type BlueprintPointerKind = 'file' | 'folder' | 'function' | 'variable'
229
+
230
+ export type BlueprintPointer = {
231
+ kind: BlueprintPointerKind
232
+ path: string
233
+ name?: string
234
+ }
235
+
228
236
  export type AimedRelation = {
229
237
  from: string
230
238
  to: string
@@ -275,6 +283,7 @@ export type SharedBlueprint = {
275
283
  addedVariables: PatchSymbolAddition[]
276
284
  addedImports: PatchImportAddition[]
277
285
  notes: BlueprintNote[]
286
+ pointers: BlueprintPointer[]
278
287
  }
279
288
 
280
289
  export type AgentIntentBundle = {
@@ -341,4 +350,5 @@ export type AgentIntent = {
341
350
  blueprintVariables: PatchSymbolAddition[]
342
351
  blueprintImports: PatchImportAddition[]
343
352
  blueprintNotes: BlueprintNote[]
353
+ blueprintPointers: BlueprintPointer[]
344
354
  }
@@ -0,0 +1,26 @@
1
+ export function EyeIcon({
2
+ size = 18,
3
+ title,
4
+ }: {
5
+ size?: number
6
+ title?: string
7
+ }) {
8
+ return (
9
+ <svg
10
+ viewBox="0 0 24 24"
11
+ width={size}
12
+ height={size}
13
+ fill="none"
14
+ stroke="currentColor"
15
+ strokeWidth="2"
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ aria-hidden={title ? undefined : true}
19
+ role={title ? 'img' : undefined}
20
+ >
21
+ {title ? <title>{title}</title> : null}
22
+ <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z" />
23
+ <circle cx="12" cy="12" r="3" />
24
+ </svg>
25
+ )
26
+ }
@@ -10,6 +10,8 @@ import {
10
10
  type AimedRelation,
11
11
  type BlueprintNote,
12
12
  type BlueprintNoteKind,
13
+ type BlueprintPointer,
14
+ type BlueprintPointerKind,
13
15
  type BranchChanges,
14
16
  type CodebaseGraph,
15
17
  type PatchImportAddition,
@@ -18,7 +20,8 @@ import {
18
20
  type WorldLayout,
19
21
  type WorkflowAction,
20
22
  } from '../types'
21
- import { findBlueprintNote } from '../userCreated'
23
+ import { findBlueprintNote, findBlueprintPointer } from '../userCreated'
24
+ import { EyeIcon } from './EyeIcon'
22
25
  import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
23
26
 
24
27
  function reviewTitle(status: AgentIntentStatus) {
@@ -311,25 +314,43 @@ function BlueprintSymbolRow({
311
314
  className,
312
315
  hasNote,
313
316
  noteOpen,
317
+ pointed,
314
318
  canEdit,
315
319
  canRemove,
316
320
  onRemove,
317
321
  onOpenNote,
322
+ onTogglePoint,
318
323
  }: {
319
324
  name: string
320
325
  className?: string
321
326
  hasNote: boolean
322
327
  noteOpen?: boolean
328
+ pointed?: boolean
323
329
  canEdit: boolean
324
330
  canRemove?: boolean
325
331
  onRemove?: () => void
326
332
  onOpenNote: () => void
333
+ onTogglePoint?: () => void
327
334
  }) {
328
335
  return (
329
336
  <li>
330
337
  <span className={className}>{name}</span>
331
338
  {canEdit && (
332
339
  <div className="hud-item-actions">
340
+ {onTogglePoint && (
341
+ <button
342
+ className="hud-item-point"
343
+ type="button"
344
+ data-pointed={pointed ? 'true' : 'false'}
345
+ aria-label={
346
+ pointed ? `Stop pointing to ${name}` : `Point to ${name}`
347
+ }
348
+ aria-pressed={Boolean(pointed)}
349
+ onClick={onTogglePoint}
350
+ >
351
+ <EyeIcon size={13} />
352
+ </button>
353
+ )}
333
354
  <button
334
355
  className="hud-item-note"
335
356
  type="button"
@@ -570,7 +591,8 @@ function blueprintIsDefined(intent: AgentIntent) {
570
591
  (intent.blueprintFunctions?.length ?? 0) > 0 ||
571
592
  (intent.blueprintVariables?.length ?? 0) > 0 ||
572
593
  (intent.blueprintImports?.length ?? 0) > 0 ||
573
- (intent.blueprintNotes?.length ?? 0) > 0
594
+ (intent.blueprintNotes?.length ?? 0) > 0 ||
595
+ (intent.blueprintPointers?.length ?? 0) > 0
574
596
  )
575
597
  }
576
598
 
@@ -680,6 +702,11 @@ function sessionLiveStatus(intent: AgentIntent) {
680
702
  if (intent.awaitingAttach) {
681
703
  return { text: 'Waiting for /inbase in Cursor', busy: false }
682
704
  }
705
+ if (intent.status === 'pending') {
706
+ return intent.listening
707
+ ? { text: 'LLM is listening — accept or send an instruction', busy: false }
708
+ : { text: 'Review this step', busy: false }
709
+ }
683
710
  if (kind === 'execute') {
684
711
  return { text: `LLM received ${detail}`, busy: true }
685
712
  }
@@ -708,11 +735,6 @@ function sessionLiveStatus(intent: AgentIntent) {
708
735
  ? { text: 'LLM is listening — run the next step', busy: false }
709
736
  : { text: 'Plan ready', busy: false }
710
737
  }
711
- if (intent.status === 'pending') {
712
- return intent.listening
713
- ? { text: 'LLM is listening — accept or send an instruction', busy: false }
714
- : { text: 'Review this step', busy: false }
715
- }
716
738
  if (
717
739
  kind === 'attached' ||
718
740
  intent.status === 'blueprint' ||
@@ -797,13 +819,16 @@ function SessionPanel({
797
819
  const [contextBusy, setContextBusy] = useState(false)
798
820
  const [contextError, setContextError] = useState<string | null>(null)
799
821
  const sessionId = intent.sessionId
800
- const pending = intent.status === 'pending' && intent.isActiveDiff
822
+ const latestEntry = intent.isActiveDiff ? intent.chain.at(-1) : null
823
+ const pending =
824
+ latestEntry?.status === 'pending' ||
825
+ (intent.status === 'pending' && intent.isActiveDiff)
801
826
  const askingBlueprint = intent.status === 'blueprint_ask'
802
827
  const sendingBlueprint = intent.status === 'blueprint'
803
828
  const canPlace = Boolean(intent.creationMode)
804
829
  const preparing = intent.status === 'preparing'
805
- const planReady = intent.status === 'planned'
806
830
  const working = intent.status === 'working' || intent.status === 'replanning'
831
+ const planReady = intent.status === 'planned' && !pending
807
832
  const previewing = intent.preview
808
833
  const chainIndex = intent.chainIndex ?? 0
809
834
  const previousDiff = intent.chain[chainIndex - 1]
@@ -812,10 +837,6 @@ function SessionPanel({
812
837
  intent.step && intent.steps?.length > 0
813
838
  ? `Step ${intent.step} of ${intent.steps.length}`
814
839
  : 'Patch'
815
- const lastStep =
816
- typeof intent.step === 'number' &&
817
- intent.steps.length > 0 &&
818
- intent.step >= intent.steps.length
819
840
  const stepByStep = intent.stepByStep !== false
820
841
  const addedFunctions = intent.addedFunctions ?? []
821
842
  const addedVariables = intent.addedVariables ?? []
@@ -826,25 +847,27 @@ function SessionPanel({
826
847
  intent.status === 'finished'
827
848
  ? intent.steps.map((step) => step.index)
828
849
  : intent.chain
829
- .filter(
830
- (entry) =>
831
- entry.status === 'applied' || entry.status === 'extended',
832
- )
850
+ .filter((entry) => entry.status === 'applied')
833
851
  .map((entry) => entry.step),
834
852
  )
835
853
  if (intent.status === 'approved' && typeof intent.step === 'number') {
836
854
  acceptedSteps.add(intent.step)
837
855
  }
838
- const proposalStep =
839
- pending && typeof intent.step === 'number' ? intent.step : null
856
+ const proposalStep = pending
857
+ ? (latestEntry?.status === 'pending' ? latestEntry.step : intent.step)
858
+ : null
840
859
  const processingStep =
841
860
  working && typeof intent.step === 'number' ? intent.step : null
842
861
  const invokeStep =
843
- planReady && !working
862
+ planReady && !working && proposalStep === null
844
863
  ? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
845
864
  : null
846
865
  const canRunNext = stepByStep && Boolean(invokeStep)
847
866
  const canAcceptProposal = proposalStep !== null
867
+ const lastStep =
868
+ typeof proposalStep === 'number' &&
869
+ intent.steps.length > 0 &&
870
+ proposalStep >= intent.steps.length
848
871
  const panelDone =
849
872
  intent.status === 'finished' ||
850
873
  intent.status === 'approved' ||
@@ -1071,7 +1094,7 @@ function SessionPanel({
1071
1094
  {intent.steps.map((step) => {
1072
1095
  const proposed = proposalStep === step.index
1073
1096
  const processing = processingStep === step.index
1074
- const accepted = acceptedSteps.has(step.index)
1097
+ const accepted = acceptedSteps.has(step.index) && !proposed
1075
1098
  const creating = processing && !proposed
1076
1099
  const showStepAction =
1077
1100
  creating ||
@@ -1270,7 +1293,7 @@ function SessionPanel({
1270
1293
  value={instruction}
1271
1294
  maxLength={4000}
1272
1295
  rows={3}
1273
- placeholder="Describe what should change in the next diff…"
1296
+ placeholder="Describe what should change in this proposal…"
1274
1297
  onChange={(event) => setInstruction(event.target.value)}
1275
1298
  onKeyDown={(event) => event.stopPropagation()}
1276
1299
  />
@@ -1546,6 +1569,11 @@ function explorerInstructions({
1546
1569
  ? [
1547
1570
  { id: 'space', keys: ['Space'], label: 'Place file' },
1548
1571
  { id: 'b-island', keys: ['B'], label: 'Place island' },
1572
+ {
1573
+ id: 'point-to',
1574
+ keys: ['Point to'],
1575
+ label: 'Keep a file, folder, or function in mind',
1576
+ },
1549
1577
  ]
1550
1578
  : []),
1551
1579
  ...backspace,
@@ -1629,7 +1657,7 @@ function explorerInstructions({
1629
1657
  {
1630
1658
  id: 'add-file-folder',
1631
1659
  keys: ['Right-click'],
1632
- label: 'Add file or folder',
1660
+ label: 'Add file or folder, or point to a folder',
1633
1661
  },
1634
1662
  ]
1635
1663
  : []),
@@ -1739,6 +1767,7 @@ type HUDProps = {
1739
1767
  blueprintVariables?: PatchSymbolAddition[]
1740
1768
  blueprintImports?: PatchImportAddition[]
1741
1769
  blueprintNotes?: BlueprintNote[]
1770
+ blueprintPointers?: BlueprintPointer[]
1742
1771
  onAddBlueprintFunction?: (fileId: string, name: string) => boolean
1743
1772
  onAddBlueprintVariable?: (fileId: string, name: string) => boolean
1744
1773
  onAddBlueprintImport?: (fileId: string, raw: string) => boolean
@@ -1755,6 +1784,11 @@ type HUDProps = {
1755
1784
  name?: string
1756
1785
  note: string
1757
1786
  }) => void
1787
+ onToggleBlueprintPointer?: (next: {
1788
+ kind: BlueprintPointerKind
1789
+ path: string
1790
+ name?: string
1791
+ }) => void
1758
1792
  onMapAddFile?: (folderPath: string) => void
1759
1793
  onMapAddFolder?: (folderPath: string) => void
1760
1794
  onInspectFile?: (fileId: string) => void
@@ -1814,6 +1848,7 @@ export function HUD({
1814
1848
  blueprintVariables = [],
1815
1849
  blueprintImports = [],
1816
1850
  blueprintNotes = [],
1851
+ blueprintPointers = [],
1817
1852
  onAddBlueprintFunction,
1818
1853
  onAddBlueprintVariable,
1819
1854
  onAddBlueprintImport,
@@ -1821,6 +1856,7 @@ export function HUD({
1821
1856
  onRemoveBlueprintVariable,
1822
1857
  onRemoveBlueprintImport,
1823
1858
  onSetBlueprintNote,
1859
+ onToggleBlueprintPointer,
1824
1860
  onMapAddFile,
1825
1861
  onMapAddFolder,
1826
1862
  onInspectFile,
@@ -1950,6 +1986,16 @@ export function HUD({
1950
1986
  const selectedFileNote = selected
1951
1987
  ? findBlueprintNote(blueprintNotes, selected.id, 'file')
1952
1988
  : ''
1989
+ const selectedFilePointed = selected
1990
+ ? findBlueprintPointer(blueprintPointers, 'file', selected.id)
1991
+ : false
1992
+ const selectedFolderPointed = selectedFolderNode
1993
+ ? findBlueprintPointer(
1994
+ blueprintPointers,
1995
+ 'folder',
1996
+ selectedFolderNode.path,
1997
+ )
1998
+ : false
1953
1999
  const openFileNote = () => {
1954
2000
  if (!selected || !onSetBlueprintNote) return
1955
2001
  setInstructionsOpen(false)
@@ -2282,6 +2328,20 @@ export function HUD({
2282
2328
  Inspect file
2283
2329
  </button>
2284
2330
  )}
2331
+ {canEditBlueprint && onToggleBlueprintPointer && (
2332
+ <button
2333
+ className="hud-button hud-inspect hud-point"
2334
+ type="button"
2335
+ data-pointed={selectedFilePointed ? 'true' : 'false'}
2336
+ aria-pressed={selectedFilePointed}
2337
+ onClick={() =>
2338
+ onToggleBlueprintPointer({ kind: 'file', path: selected.id })
2339
+ }
2340
+ >
2341
+ <EyeIcon size={15} />
2342
+ {selectedFilePointed ? 'Stop pointing' : 'Point to file'}
2343
+ </button>
2344
+ )}
2285
2345
  {canEditBlueprint && onSetBlueprintNote && (
2286
2346
  <button
2287
2347
  className="hud-button hud-inspect"
@@ -2373,10 +2433,26 @@ export function HUD({
2373
2433
  }
2374
2434
  canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
2375
2435
  canRemove={Boolean(canEditBlueprint && symbol.intended)}
2436
+ pointed={findBlueprintPointer(
2437
+ blueprintPointers,
2438
+ 'function',
2439
+ selected.id,
2440
+ symbol.name,
2441
+ )}
2376
2442
  onRemove={() =>
2377
2443
  onRemoveBlueprintFunction?.(selected.id, symbol.name)
2378
2444
  }
2379
2445
  onOpenNote={() => openSymbolNote('function', symbol.name)}
2446
+ onTogglePoint={
2447
+ onToggleBlueprintPointer
2448
+ ? () =>
2449
+ onToggleBlueprintPointer({
2450
+ kind: 'function',
2451
+ path: selected.id,
2452
+ name: symbol.name,
2453
+ })
2454
+ : undefined
2455
+ }
2380
2456
  />
2381
2457
  ))}
2382
2458
  {extraAddedFunctions.map((item) => (
@@ -2398,7 +2474,23 @@ export function HUD({
2398
2474
  noteEditor.name === item.name
2399
2475
  }
2400
2476
  canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
2477
+ pointed={findBlueprintPointer(
2478
+ blueprintPointers,
2479
+ 'function',
2480
+ selected.id,
2481
+ item.name,
2482
+ )}
2401
2483
  onOpenNote={() => openSymbolNote('function', item.name)}
2484
+ onTogglePoint={
2485
+ onToggleBlueprintPointer
2486
+ ? () =>
2487
+ onToggleBlueprintPointer({
2488
+ kind: 'function',
2489
+ path: selected.id,
2490
+ name: item.name,
2491
+ })
2492
+ : undefined
2493
+ }
2402
2494
  />
2403
2495
  ))}
2404
2496
  </ul>
@@ -2439,10 +2531,26 @@ export function HUD({
2439
2531
  }
2440
2532
  canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
2441
2533
  canRemove={Boolean(canEditBlueprint && symbol.intended)}
2534
+ pointed={findBlueprintPointer(
2535
+ blueprintPointers,
2536
+ 'variable',
2537
+ selected.id,
2538
+ symbol.name,
2539
+ )}
2442
2540
  onRemove={() =>
2443
2541
  onRemoveBlueprintVariable?.(selected.id, symbol.name)
2444
2542
  }
2445
2543
  onOpenNote={() => openSymbolNote('variable', symbol.name)}
2544
+ onTogglePoint={
2545
+ onToggleBlueprintPointer
2546
+ ? () =>
2547
+ onToggleBlueprintPointer({
2548
+ kind: 'variable',
2549
+ path: selected.id,
2550
+ name: symbol.name,
2551
+ })
2552
+ : undefined
2553
+ }
2446
2554
  />
2447
2555
  ))}
2448
2556
  {extraAddedVariables.map((item) => (
@@ -2464,7 +2572,23 @@ export function HUD({
2464
2572
  noteEditor.name === item.name
2465
2573
  }
2466
2574
  canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
2575
+ pointed={findBlueprintPointer(
2576
+ blueprintPointers,
2577
+ 'variable',
2578
+ selected.id,
2579
+ item.name,
2580
+ )}
2467
2581
  onOpenNote={() => openSymbolNote('variable', item.name)}
2582
+ onTogglePoint={
2583
+ onToggleBlueprintPointer
2584
+ ? () =>
2585
+ onToggleBlueprintPointer({
2586
+ kind: 'variable',
2587
+ path: selected.id,
2588
+ name: item.name,
2589
+ })
2590
+ : undefined
2591
+ }
2468
2592
  />
2469
2593
  ))}
2470
2594
  </ul>
@@ -2608,6 +2732,24 @@ export function HUD({
2608
2732
  ))}
2609
2733
  </ul>
2610
2734
  )}
2735
+ {canPlace && onToggleBlueprintPointer && (
2736
+ <button
2737
+ className="hud-button hud-inspect hud-point"
2738
+ type="button"
2739
+ data-pointed={selectedFolderPointed ? 'true' : 'false'}
2740
+ aria-pressed={selectedFolderPointed}
2741
+ disabled={naming || selectedFolderNode.path.startsWith('draft:')}
2742
+ onClick={() =>
2743
+ onToggleBlueprintPointer({
2744
+ kind: 'folder',
2745
+ path: selectedFolderNode.path,
2746
+ })
2747
+ }
2748
+ >
2749
+ <EyeIcon size={15} />
2750
+ {selectedFolderPointed ? 'Stop pointing' : 'Point to folder'}
2751
+ </button>
2752
+ )}
2611
2753
  {canPlace && mapping && onMapAddFile && onMapAddFolder && (
2612
2754
  <div className="hud-decide hud-map-blueprint">
2613
2755
  <button
@@ -2646,6 +2788,20 @@ export function HUD({
2646
2788
  plannedIds={plannedIds}
2647
2789
  createdIds={createdIds}
2648
2790
  deletedIds={deletedIds}
2791
+ pointedFileIds={
2792
+ blueprintHidden
2793
+ ? []
2794
+ : blueprintPointers.flatMap((item) =>
2795
+ item.kind === 'folder' ? [] : [item.path],
2796
+ )
2797
+ }
2798
+ pointedFolderPaths={
2799
+ blueprintHidden
2800
+ ? []
2801
+ : blueprintPointers.flatMap((item) =>
2802
+ item.kind === 'folder' ? [item.path] : [],
2803
+ )
2804
+ }
2649
2805
  onMinimize={() => {
2650
2806
  setThumbnailMaximized(false)
2651
2807
  setThumbnailMinimized((current) => !current)
@@ -10,15 +10,19 @@ export type MapContextMenuState = {
10
10
 
11
11
  type MapContextMenuProps = {
12
12
  menu: MapContextMenuState | null
13
+ pointed?: boolean
13
14
  onAddFile: (folder: string) => void
14
15
  onAddFolder: (folder: string) => void
16
+ onPointToFolder?: (folder: string) => void
15
17
  onClose: () => void
16
18
  }
17
19
 
18
20
  export function MapContextMenu({
19
21
  menu,
22
+ pointed = false,
20
23
  onAddFile,
21
24
  onAddFolder,
25
+ onPointToFolder,
22
26
  onClose,
23
27
  }: MapContextMenuProps) {
24
28
  useEffect(() => {
@@ -48,7 +52,7 @@ export function MapContextMenu({
48
52
 
49
53
  const pad = 8
50
54
  const width = 176
51
- const height = 84
55
+ const height = onPointToFolder ? 126 : 84
52
56
  const left = Math.min(
53
57
  Math.max(pad, menu.x),
54
58
  window.innerWidth - width - pad,
@@ -85,6 +89,19 @@ export function MapContextMenu({
85
89
  >
86
90
  Add folder
87
91
  </button>
92
+ {onPointToFolder && (
93
+ <button
94
+ type="button"
95
+ role="menuitem"
96
+ aria-pressed={pointed}
97
+ onClick={() => {
98
+ onPointToFolder(menu.folder)
99
+ onClose()
100
+ }}
101
+ >
102
+ {pointed ? 'Stop pointing' : 'Point to folder'}
103
+ </button>
104
+ )}
88
105
  </div>,
89
106
  document.body,
90
107
  )