@jkwd/inbase 0.1.1 → 0.1.3

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.
@@ -21,6 +21,7 @@ body,
21
21
  .stage {
22
22
  position: absolute;
23
23
  inset: 0;
24
+ z-index: 0;
24
25
  }
25
26
 
26
27
  * {
@@ -35,7 +36,7 @@ button {
35
36
  position: fixed;
36
37
  inset: 0;
37
38
  pointer-events: none;
38
- z-index: 2;
39
+ z-index: 10;
39
40
  }
40
41
 
41
42
  .hud-top,
@@ -243,6 +244,7 @@ button {
243
244
  left: 0;
244
245
  right: 0;
245
246
  bottom: 0;
247
+ z-index: 3;
246
248
  display: flex;
247
249
  align-items: center;
248
250
  justify-content: space-between;
@@ -306,11 +308,15 @@ button {
306
308
  position: absolute;
307
309
  top: 68px;
308
310
  right: 20px;
311
+ z-index: 4;
309
312
  width: min(320px, calc(100% - 40px));
310
- max-height: calc(100dvh - 132px);
311
- overflow: auto;
313
+ height: auto;
314
+ max-height: calc(100dvh - 168px);
315
+ overflow-x: hidden;
316
+ overflow-y: auto;
312
317
  overscroll-behavior: contain;
313
- padding: 16px;
318
+ padding: 16px 16px 20px;
319
+ background: var(--vc-chrome);
314
320
  }
315
321
 
316
322
  .hud-panel-info {
@@ -352,18 +358,31 @@ button {
352
358
 
353
359
  .hud-steps {
354
360
  margin: 10px 0 0;
355
- padding-left: 18px;
361
+ padding: 0;
362
+ list-style: none;
356
363
  color: #8b95a5;
357
364
  }
358
365
 
359
366
  .hud-steps li {
360
- margin: 0 0 6px;
367
+ display: grid;
368
+ grid-template-columns: 1.6em minmax(0, 1fr);
369
+ align-items: start;
370
+ justify-content: start;
371
+ gap: 8px;
372
+ margin: 0 0 8px;
373
+ }
374
+
375
+ .hud-step-index {
376
+ font-variant-numeric: tabular-nums;
377
+ line-height: 1.4;
361
378
  }
362
379
 
363
380
  .hud-step-main {
364
- display: inline-flex;
381
+ display: flex;
382
+ flex-wrap: wrap;
365
383
  align-items: baseline;
366
384
  gap: 8px;
385
+ min-width: 0;
367
386
  }
368
387
 
369
388
  .hud-steps li[data-next='true'] {
@@ -488,6 +507,39 @@ button {
488
507
  opacity: 0.35;
489
508
  }
490
509
 
510
+ .hud-fold {
511
+ margin-top: 12px;
512
+ }
513
+
514
+ .hud-fold-summary {
515
+ cursor: pointer;
516
+ list-style: none;
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 8px;
520
+ user-select: none;
521
+ }
522
+
523
+ .hud-fold-summary::-webkit-details-marker,
524
+ .hud-fold-summary::marker {
525
+ display: none;
526
+ content: '';
527
+ }
528
+
529
+ .hud-fold-summary::before {
530
+ content: '';
531
+ width: 0;
532
+ height: 0;
533
+ border-top: 4px solid transparent;
534
+ border-bottom: 4px solid transparent;
535
+ border-left: 6px solid currentColor;
536
+ transition: transform 0.12s ease;
537
+ }
538
+
539
+ .hud-fold[open] > .hud-fold-summary::before {
540
+ transform: rotate(90deg);
541
+ }
542
+
491
543
  .hud-button-approve {
492
544
  border-color: #9ad8ff;
493
545
  color: #d7eef8;
@@ -519,7 +571,7 @@ button {
519
571
  padding-left: 18px;
520
572
  }
521
573
 
522
- .hud-panel li {
574
+ .hud-panel ul li {
523
575
  margin: 0 0 4px;
524
576
  display: flex;
525
577
  align-items: center;
@@ -95,7 +95,7 @@ export function FileBlock({
95
95
  onCancelName,
96
96
  }: FileBlockProps) {
97
97
  const [width, height, depth] = placed.size
98
- const highlight = changeKind ? CHANGE_HIGHLIGHT[changeKind] : null
98
+ const highlight = changeKind && mapMode ? CHANGE_HIGHLIGHT[changeKind] : null
99
99
  const color = added || file.userCreated ? '#7ec8e8' : fileColor(file.language)
100
100
  const muted = dimColor(color, 0.32)
101
101
  const walkSelected = selected && !mapMode
@@ -8,6 +8,7 @@ type FolderAreaProps = {
8
8
  folder: PlacedFolder
9
9
  naming?: boolean
10
10
  selected?: boolean
11
+ mapMode?: boolean
11
12
  highlightKind?: ChangeKind | null
12
13
  }
13
14
 
@@ -15,10 +16,12 @@ export function FolderArea({
15
16
  folder,
16
17
  naming = false,
17
18
  selected = false,
19
+ mapMode = false,
18
20
  highlightKind = null,
19
21
  }: FolderAreaProps) {
20
22
  const added = Boolean(folder.added)
21
- const highlight = highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
23
+ const highlight =
24
+ mapMode && highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
22
25
  const addedOnly = added && !highlight
23
26
  const outline = highlight ? highlight.color : addedOnly ? '#7ec8e8' : null
24
27
  const color = highlight
@@ -48,7 +51,7 @@ export function FolderArea({
48
51
  <planeGeometry args={[folder.width, folder.depth]} />
49
52
  <meshBasicMaterial color={color} />
50
53
  </mesh>
51
- {selected && (
54
+ {selected && mapMode && (
52
55
  <MapSelectBorder
53
56
  width={folder.width}
54
57
  depth={folder.depth}
@@ -34,25 +34,32 @@ export function MapView({
34
34
  const bounds = useMemo(() => worldBounds(layout), [layout])
35
35
  const drag = useRef({ x: 0, y: 0, moved: false, active: false })
36
36
  const sized = size.width > 16 && size.height > 16
37
+ const hudReserve = 88
38
+ const topReserve = 28
39
+ const viewWidth = Math.max(size.width, 1)
40
+ const viewHeight = Math.max(size.height - hudReserve - topReserve, 1)
37
41
 
38
42
  const fitZoom = sized
39
43
  ? Math.min(
40
- size.width / Math.max(bounds.width + 24, 1),
41
- size.height / Math.max(bounds.depth + 24, 1),
44
+ viewWidth / Math.max(bounds.width + 36, 1),
45
+ viewHeight / Math.max(bounds.depth + 36, 1),
42
46
  )
43
47
  : 8
44
- const zoom = Math.max(fitZoom * 0.92, 1)
48
+ const zoom = Math.max(fitZoom * 0.92, 0.08)
49
+ // Ortho up is -Z, so +Z is down the screen. Shift the view so the map sits
50
+ // above the bottom HUD instead of centering under it.
51
+ const cz = bounds.cz + hudReserve / 2 / zoom
45
52
 
46
53
  useLayoutEffect(() => {
47
54
  if (!enabled || !(camera instanceof THREE.OrthographicCamera)) return
48
55
  camera.up.set(0, 0, -1)
49
- camera.position.set(bounds.cx, 120, bounds.cz)
50
- camera.lookAt(bounds.cx, 0, bounds.cz)
56
+ camera.position.set(bounds.cx, 120, cz)
57
+ camera.lookAt(bounds.cx, 0, cz)
51
58
  camera.near = 1
52
59
  camera.far = 2000
53
60
  camera.zoom = zoom
54
61
  camera.updateProjectionMatrix()
55
- }, [bounds.cx, bounds.cz, camera, enabled, zoom])
62
+ }, [bounds.cx, camera, cz, enabled, zoom])
56
63
 
57
64
  useEffect(() => {
58
65
  if (!enabled) return
@@ -86,12 +93,31 @@ export function MapView({
86
93
  return { raycaster, relationHit, fileHit }
87
94
  }
88
95
 
96
+ const landAtPointer = (clientX: number, clientY: number) => {
97
+ const pick = pickAt(clientX, clientY)
98
+ if (!pick) return false
99
+ const { raycaster, relationHit, fileHit } = pick
100
+ if (relationHit || fileHit) return false
101
+
102
+ const hit = new THREE.Vector3()
103
+ const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
104
+ if (!raycaster.ray.intersectPlane(plane, hit)) return false
105
+ const lock = element.requestPointerLock()
106
+ if (lock && typeof lock.catch === 'function') {
107
+ void lock.catch(() => {})
108
+ }
109
+ onLand(hit.x, hit.z)
110
+ return true
111
+ }
112
+
89
113
  const onUp = (event: PointerEvent) => {
90
114
  element.style.cursor = 'grab'
91
115
  const startedOnCanvas = drag.current.active
92
116
  drag.current.active = false
93
117
  if (!startedOnCanvas || event.button !== 0 || drag.current.moved) return
94
118
 
119
+ if (event.shiftKey && landAtPointer(event.clientX, event.clientY)) return
120
+
95
121
  const pick = pickAt(event.clientX, event.clientY)
96
122
  if (!pick) return
97
123
  const { relationHit, fileHit } = pick
@@ -123,34 +149,14 @@ export function MapView({
123
149
  onSelectFolder(null)
124
150
  }
125
151
 
126
- const onDblClick = (event: MouseEvent) => {
127
- if (event.button !== 0 || drag.current.moved) return
128
- const pick = pickAt(event.clientX, event.clientY)
129
- if (!pick) return
130
- const { raycaster, relationHit, fileHit } = pick
131
- if (relationHit || fileHit) return
132
-
133
- const hit = new THREE.Vector3()
134
- const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
135
- if (raycaster.ray.intersectPlane(plane, hit)) {
136
- const lock = element.requestPointerLock()
137
- if (lock && typeof lock.catch === 'function') {
138
- void lock.catch(() => {})
139
- }
140
- onLand(hit.x, hit.z)
141
- }
142
- }
143
-
144
152
  element.addEventListener('pointerdown', onDown)
145
153
  window.addEventListener('pointermove', onMove)
146
154
  window.addEventListener('pointerup', onUp)
147
- element.addEventListener('dblclick', onDblClick)
148
155
  return () => {
149
156
  element.style.cursor = ''
150
157
  element.removeEventListener('pointerdown', onDown)
151
158
  window.removeEventListener('pointermove', onMove)
152
159
  window.removeEventListener('pointerup', onUp)
153
- element.removeEventListener('dblclick', onDblClick)
154
160
  }
155
161
  }, [
156
162
  camera,
@@ -169,7 +175,7 @@ export function MapView({
169
175
  {enabled && (
170
176
  <OrthographicCamera
171
177
  makeDefault
172
- position={[bounds.cx, 120, bounds.cz]}
178
+ position={[bounds.cx, 120, cz]}
173
179
  zoom={zoom}
174
180
  near={1}
175
181
  far={2000}
@@ -183,9 +189,9 @@ export function MapView({
183
189
  dampingFactor={0.12}
184
190
  screenSpacePanning
185
191
  zoomSpeed={1.15}
186
- minZoom={Math.max(fitZoom * 0.45, 1)}
192
+ minZoom={Math.max(fitZoom * 0.35, 0.05)}
187
193
  maxZoom={Math.max(fitZoom * 10, 20)}
188
- target={[bounds.cx, 0, bounds.cz]}
194
+ target={[bounds.cx, 0, cz]}
189
195
  />
190
196
  )}
191
197
  {enabled &&
@@ -194,6 +200,7 @@ export function MapView({
194
200
  key={folder.path}
195
201
  position={[folder.x, 14, folder.z + 1.35]}
196
202
  center
203
+ zIndexRange={[1, 0]}
197
204
  style={{ pointerEvents: 'none' }}
198
205
  >
199
206
  <div
@@ -8,7 +8,7 @@ import { SelectionController } from './SelectionController'
8
8
  import { UserContextTracker } from './UserContextTracker'
9
9
  import { BlockPlacer } from './BlockPlacer'
10
10
  import { IslandPlacer } from './IslandPlacer'
11
- import { folderOfFile, filesImporting } from '../layout'
11
+ import { folderOfFile, filesImporting, worldBounds } from '../layout'
12
12
  import { EDITOR_GREY } from '../theme'
13
13
  import type { ChangeKind } from '../theme'
14
14
  import type {
@@ -159,18 +159,26 @@ export function World({
159
159
  return null
160
160
  }
161
161
 
162
+ const bounds = worldBounds(layout)
163
+ const groundPad = 120
164
+ const groundWidth = Math.max(bounds.width + groundPad * 2, 400)
165
+ const groundDepth = Math.max(bounds.depth + groundPad * 2, 400)
166
+
162
167
  return (
163
168
  <>
164
- <color attach="background" args={[EDITOR_GREY.editor]} />
165
- {!mapping && <fog attach="fog" args={[EDITOR_GREY.editor, 38, 160]} />}
169
+ <color attach="background" args={[EDITOR_GREY.chrome]} />
170
+ {!mapping && <fog attach="fog" args={[EDITOR_GREY.chrome, 38, 160]} />}
166
171
  <hemisphereLight args={['#d7e2ee', '#2a3038', mapping ? 1.1 : 0.85]} />
167
172
  <directionalLight
168
173
  position={mapping ? [8, 60, 8] : [12, 22, 8]}
169
174
  intensity={mapping ? 1.35 : 0.55}
170
175
  />
171
176
  <ambientLight intensity={mapping ? 0.7 : 0.42} />
172
- <mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.06, 40]}>
173
- <planeGeometry args={[400, 400]} />
177
+ <mesh
178
+ rotation={[-Math.PI / 2, 0, 0]}
179
+ position={[bounds.cx, -0.06, bounds.cz]}
180
+ >
181
+ <planeGeometry args={[groundWidth, groundDepth]} />
174
182
  <meshBasicMaterial color={EDITOR_GREY.chrome} />
175
183
  </mesh>
176
184
  <MapView
@@ -191,6 +199,7 @@ export function World({
191
199
  folder={folder}
192
200
  naming={folder.path === namingIslandId}
193
201
  selected={folder.path === selectedFolder}
202
+ mapMode={mapping}
194
203
  highlightKind={highlightedFolders[folder.path] ?? null}
195
204
  />
196
205
  ))}
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from 'react'
1
+ import { useEffect, useRef, useState, type ReactNode } from 'react'
2
2
  import { NameInput } from './NameInput'
3
3
  import {
4
4
  isReviewingIntent,
@@ -72,6 +72,25 @@ function PanelList({
72
72
  )
73
73
  }
74
74
 
75
+ function MutationFold({
76
+ hasContent,
77
+ children,
78
+ }: {
79
+ hasContent: boolean
80
+ children: ReactNode
81
+ }) {
82
+ if (!hasContent) return null
83
+ return (
84
+ <details
85
+ className="hud-fold"
86
+ onKeyDown={(event) => event.stopPropagation()}
87
+ >
88
+ <summary className="hud-section-title hud-fold-summary">Mutations</summary>
89
+ {children}
90
+ </details>
91
+ )
92
+ }
93
+
75
94
  function AddIntentRow({
76
95
  placeholder,
77
96
  onAdd,
@@ -539,6 +558,7 @@ export function HUD({
539
558
  nextStep?.index === step.index && !doneSteps.has(step.index)
540
559
  }
541
560
  >
561
+ <span className="hud-step-index">{step.index}.</span>
542
562
  <span className="hud-step-main">
543
563
  <span className="hud-step-title">{step.title}</span>
544
564
  {((canRunNext && nextStep?.index === step.index) ||
@@ -587,81 +607,91 @@ export function HUD({
587
607
  </button>
588
608
  </div>
589
609
  )}
590
- {previewing && intent.files.length > 0 && (
591
- <>
592
- <div className="hud-section-title hud-section-title-edit">Changed</div>
593
- <ul>
594
- {intent.files.map((id) => (
595
- <li className="hud-file-edit" key={id}>
596
- {id}
597
- </li>
598
- ))}
599
- </ul>
600
- </>
601
- )}
602
- {previewing && (intent.createFolders ?? []).length > 0 && (
603
- <>
604
- <div className="hud-section-title hud-section-title-add">Added islands</div>
605
- <ul>
606
- {intent.createFolders.map((id) => (
607
- <li className="hud-file-add" key={id}>
608
- {id}/
609
- </li>
610
- ))}
611
- </ul>
612
- </>
613
- )}
614
- {previewing && intent.creates.length > 0 && (
615
- <>
616
- <div className="hud-section-title hud-section-title-add">Added</div>
617
- <ul>
618
- {intent.creates.map((id) => (
619
- <li className="hud-file-add" key={id}>
620
- {id}
621
- </li>
622
- ))}
623
- </ul>
624
- </>
625
- )}
626
- {previewing && intent.deletes.length > 0 && (
627
- <>
628
- <div className="hud-section-title hud-section-title-remove">Removed</div>
629
- <ul>
630
- {intent.deletes.map((id) => (
631
- <li className="hud-file-remove" key={id}>
632
- {id}
633
- </li>
634
- ))}
635
- </ul>
636
- </>
637
- )}
638
- {previewing && (
639
- <>
640
- <PanelList
641
- title="Functions"
642
- items={symbolLabels(addedFunctions)}
643
- />
644
- <PanelList
645
- title="Variables"
646
- items={symbolLabels(addedVariables)}
647
- />
648
- </>
649
- )}
650
- {previewing && addedImports.length > 0 && (
651
- <PanelList title="Imports" items={importLabels(addedImports)} />
652
- )}
653
- {previewing && addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
654
- <>
655
- <div className="hud-section-title">Imports</div>
656
- <ul>
657
- {intent.imports.map((edge) => (
658
- <li key={`${edge.from}->${edge.to}`}>
659
- {edge.from.split('/').pop()} {edge.to.split('/').pop()}
660
- </li>
661
- ))}
662
- </ul>
663
- </>
664
- )}
610
+ <MutationFold
611
+ hasContent={
612
+ previewing &&
613
+ (intent.files.length > 0 ||
614
+ (intent.createFolders ?? []).length > 0 ||
615
+ intent.creates.length > 0 ||
616
+ intent.deletes.length > 0 ||
617
+ addedFunctions.length > 0 ||
618
+ addedVariables.length > 0 ||
619
+ addedImports.length > 0 ||
620
+ (intent.imports ?? []).length > 0)
621
+ }
622
+ >
623
+ {intent.files.length > 0 && (
624
+ <>
625
+ <div className="hud-section-title hud-section-title-edit">Changed</div>
626
+ <ul>
627
+ {intent.files.map((id) => (
628
+ <li className="hud-file-edit" key={id}>
629
+ {id}
630
+ </li>
631
+ ))}
632
+ </ul>
633
+ </>
634
+ )}
635
+ {(intent.createFolders ?? []).length > 0 && (
636
+ <>
637
+ <div className="hud-section-title hud-section-title-add">Added islands</div>
638
+ <ul>
639
+ {intent.createFolders.map((id) => (
640
+ <li className="hud-file-add" key={id}>
641
+ {id}/
642
+ </li>
643
+ ))}
644
+ </ul>
645
+ </>
646
+ )}
647
+ {intent.creates.length > 0 && (
648
+ <>
649
+ <div className="hud-section-title hud-section-title-add">Added</div>
650
+ <ul>
651
+ {intent.creates.map((id) => (
652
+ <li className="hud-file-add" key={id}>
653
+ {id}
654
+ </li>
655
+ ))}
656
+ </ul>
657
+ </>
658
+ )}
659
+ {intent.deletes.length > 0 && (
660
+ <>
661
+ <div className="hud-section-title hud-section-title-remove">Removed</div>
662
+ <ul>
663
+ {intent.deletes.map((id) => (
664
+ <li className="hud-file-remove" key={id}>
665
+ {id}
666
+ </li>
667
+ ))}
668
+ </ul>
669
+ </>
670
+ )}
671
+ <PanelList
672
+ title="Functions"
673
+ items={symbolLabels(addedFunctions)}
674
+ />
675
+ <PanelList
676
+ title="Variables"
677
+ items={symbolLabels(addedVariables)}
678
+ />
679
+ {addedImports.length > 0 && (
680
+ <PanelList title="Imports" items={importLabels(addedImports)} />
681
+ )}
682
+ {addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
683
+ <>
684
+ <div className="hud-section-title">Imports</div>
685
+ <ul>
686
+ {intent.imports.map((edge) => (
687
+ <li key={`${edge.from}->${edge.to}`}>
688
+ {edge.from.split('/').pop()} → {edge.to.split('/').pop()}
689
+ </li>
690
+ ))}
691
+ </ul>
692
+ </>
693
+ )}
694
+ </MutationFold>
665
695
  {(planReady || preparing) && (
666
696
  <div className="hud-decide">
667
697
  <button
@@ -971,7 +1001,7 @@ export function HUD({
971
1001
  </>
972
1002
  )}
973
1003
  <span>Click a line to fly there</span>
974
- <span>Double-click ground to walk</span>
1004
+ <span>Shift-click ground to walk</span>
975
1005
  {selected?.userCreated && creatingBlueprint && (
976
1006
  <span>Backspace delete</span>
977
1007
  )}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jkwd/inbase",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A first-person 3D map of a codebase, with a visual coding workflow for Cursor.",
5
5
  "repository": {
6
6
  "type": "git",