@jkwd/inbase 0.1.19 → 0.1.21

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.
@@ -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]}
@@ -11,6 +11,7 @@ import {
11
11
  import { CONFIG, WORLD_VOID, type ChangeKind } from '../theme'
12
12
  import { shouldIgnoreShortcut } from '../keyboard'
13
13
  import type { CodebaseGraph, PlacedFile, PlacedFolder, WorldLayout } from '../types'
14
+ import { CanvasErrorBoundary } from '../ui/CanvasErrorBoundary'
14
15
  import { FileBlock } from './FileBlock'
15
16
  import { FolderArea } from './FolderArea'
16
17
  import { RelationLines } from './RelationLines'
@@ -30,6 +31,8 @@ type SelectionThumbnailProps = {
30
31
  plannedIds?: string[]
31
32
  createdIds?: string[]
32
33
  deletedIds?: string[]
34
+ pointedFileIds?: string[]
35
+ pointedFolderPaths?: string[]
33
36
  onMinimize?: () => void
34
37
  onMaximize?: () => void
35
38
  onHide: () => void
@@ -464,6 +467,8 @@ function ThumbnailScene({
464
467
  planned,
465
468
  created,
466
469
  deleted,
470
+ pointedFiles,
471
+ pointedFolders,
467
472
  zoom,
468
473
  zoomRef,
469
474
  panRef,
@@ -481,6 +486,8 @@ function ThumbnailScene({
481
486
  planned: Set<string>
482
487
  created: Set<string>
483
488
  deleted: Set<string>
489
+ pointedFiles: Set<string>
490
+ pointedFolders: Set<string>
484
491
  zoom: number
485
492
  zoomRef: { current: number }
486
493
  panRef: { current: Vec3 }
@@ -496,15 +503,18 @@ function ThumbnailScene({
496
503
  const selected = file.id === target.selectedFileId
497
504
  const related = target.relatedIds.has(file.id)
498
505
  const changeKind = fileChangeKind(file.id, planned, created, deleted)
506
+ const pointed = pointedFiles.has(file.id)
499
507
  const dimmed =
500
508
  Boolean(target.selectedFileId) &&
501
509
  !selected &&
502
510
  !related &&
503
- !changeKind
511
+ !changeKind &&
512
+ !pointed
504
513
  let priority = placed.size[1]
505
514
  if (selected) priority += 1000
506
515
  else if (related) priority += 400
507
516
  else if (changeKind) priority += 300
517
+ else if (pointed) priority += 250
508
518
  else if (file.userCreated) priority += 80
509
519
  if (dimmed) priority -= 200
510
520
  return {
@@ -517,7 +527,7 @@ function ThumbnailScene({
517
527
  priority,
518
528
  }
519
529
  }),
520
- [created, deleted, planned, target],
530
+ [created, deleted, planned, pointedFiles, target],
521
531
  )
522
532
 
523
533
  return (
@@ -554,12 +564,14 @@ function ThumbnailScene({
554
564
  <FolderArea
555
565
  folder={target.folder}
556
566
  highlightKind={highlightedFolders[target.folder.path] ?? null}
567
+ pointed={pointedFolders.has(target.folder.path)}
557
568
  previewLabels={zoom < 1.45}
558
569
  />
559
570
  {target.files.map(({ file, placed }) => {
560
571
  const selected = file.id === target.selectedFileId
561
572
  const related = target.relatedIds.has(file.id)
562
573
  const changeKind = fileChangeKind(file.id, planned, created, deleted)
574
+ const pointed = pointedFiles.has(file.id)
563
575
  return (
564
576
  <FileBlock
565
577
  key={file.id}
@@ -570,6 +582,7 @@ function ThumbnailScene({
570
582
  planned={Boolean(changeKind)}
571
583
  changeKind={changeKind}
572
584
  added={created.has(file.id) || file.userCreated}
585
+ pointed={pointed}
573
586
  highlightMapChange
574
587
  previewLabels
575
588
  labelVisible={visibleLabelIds.has(file.id)}
@@ -577,7 +590,8 @@ function ThumbnailScene({
577
590
  Boolean(target.selectedFileId) &&
578
591
  !selected &&
579
592
  !related &&
580
- !changeKind
593
+ !changeKind &&
594
+ !pointed
581
595
  }
582
596
  />
583
597
  )
@@ -632,6 +646,7 @@ export function SelectionThumbnail({
632
646
  const [zoom, setZoom] = useState(1)
633
647
  const [panning, setPanning] = useState(false)
634
648
  const [orbiting, setOrbiting] = useState(false)
649
+ const [canvasReady, setCanvasReady] = useState(false)
635
650
  const [visibleLabelIds, setVisibleLabelIds] = useState<Set<string>>(
636
651
  () => new Set(),
637
652
  )
@@ -657,6 +672,11 @@ export function SelectionThumbnail({
657
672
 
658
673
  const panningRef = useRef(false)
659
674
 
675
+ useEffect(() => {
676
+ const frame = window.requestAnimationFrame(() => setCanvasReady(true))
677
+ return () => window.cancelAnimationFrame(frame)
678
+ }, [])
679
+
660
680
  useEffect(() => {
661
681
  zoomRef.current = 1
662
682
  panRef.current = ZERO_PAN
@@ -960,37 +980,49 @@ export function SelectionThumbnail({
960
980
  data-panning={panning}
961
981
  data-orbiting={orbiting}
962
982
  >
963
- <Canvas
964
- shadows={false}
965
- dpr={[1, 1.5]}
966
- resize={{ offsetSize: true }}
967
- gl={{ antialias: true, toneMappingExposure: 1.25 }}
968
- camera={{
969
- fov: 50,
970
- near: 0.1,
971
- far: 400,
972
- position: target.camera.position,
973
- }}
974
- >
975
- <ThumbnailScene
976
- graph={graph}
977
- layout={layout}
978
- importedBy={importedBy}
979
- target={target}
980
- highlightedFolders={highlightedFolders}
981
- planned={planned}
982
- created={created}
983
- deleted={deleted}
984
- zoom={zoom}
985
- zoomRef={zoomRef}
986
- panRef={panRef}
987
- orbitRef={orbitRef}
988
- cameraRef={cameraRef}
989
- frozenRef={panningRef}
990
- visibleLabelIds={visibleLabelIds}
991
- onVisibleLabels={setVisibleLabelIds}
992
- />
993
- </Canvas>
983
+ {canvasReady ? (
984
+ <CanvasErrorBoundary
985
+ fallback={
986
+ <div className="hud-thumbnail-hint">3D preview unavailable</div>
987
+ }
988
+ >
989
+ <Canvas
990
+ shadows={false}
991
+ dpr={[1, 1]}
992
+ resize={{ offsetSize: true }}
993
+ gl={{
994
+ antialias: false,
995
+ powerPreference: 'low-power',
996
+ toneMappingExposure: 1.25,
997
+ }}
998
+ camera={{
999
+ fov: 50,
1000
+ near: 0.1,
1001
+ far: 400,
1002
+ position: target.camera.position,
1003
+ }}
1004
+ >
1005
+ <ThumbnailScene
1006
+ graph={graph}
1007
+ layout={layout}
1008
+ importedBy={importedBy}
1009
+ target={target}
1010
+ highlightedFolders={highlightedFolders}
1011
+ planned={planned}
1012
+ created={created}
1013
+ deleted={deleted}
1014
+ zoom={zoom}
1015
+ zoomRef={zoomRef}
1016
+ panRef={panRef}
1017
+ orbitRef={orbitRef}
1018
+ cameraRef={cameraRef}
1019
+ frozenRef={panningRef}
1020
+ visibleLabelIds={visibleLabelIds}
1021
+ onVisibleLabels={setVisibleLabelIds}
1022
+ />
1023
+ </Canvas>
1024
+ </CanvasErrorBoundary>
1025
+ ) : null}
994
1026
  <div className="hud-thumbnail-hint">
995
1027
  Scroll zoom · drag pan · ⌘ drag rotate
996
1028
  </div>
@@ -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
  }
@@ -2,6 +2,7 @@ import { Component, type ReactNode } from 'react'
2
2
 
3
3
  type Props = {
4
4
  children: ReactNode
5
+ fallback?: ReactNode
5
6
  }
6
7
 
7
8
  type State = {
@@ -21,6 +22,7 @@ export class CanvasErrorBoundary extends Component<Props, State> {
21
22
 
22
23
  render() {
23
24
  if (this.state.failed) {
25
+ if (this.props.fallback !== undefined) return this.props.fallback
24
26
  return (
25
27
  <div className="canvas-error">
26
28
  <div className="hud-gate-card">
@@ -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
+ }