@jkwd/inbase 0.1.15 → 0.1.17

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.
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  AgentIntent,
3
3
  AgentIntentBundle,
4
+ BlueprintNote,
4
5
  PatchImport,
5
6
  PatchImportAddition,
6
7
  PatchSymbolAddition,
@@ -8,7 +9,11 @@ import type {
8
9
  UserCreatedIsland,
9
10
  WorkflowAction,
10
11
  } from './types'
11
- import { parseUserCreatedBlocks, parseUserCreatedIslands } from './userCreated'
12
+ import {
13
+ parseBlueprintNotes,
14
+ parseUserCreatedBlocks,
15
+ parseUserCreatedIslands,
16
+ } from './userCreated'
12
17
 
13
18
  function normalizeImports(value: unknown): PatchImport[] {
14
19
  if (!Array.isArray(value)) return []
@@ -98,12 +103,15 @@ export const emptyIntent: AgentIntent = {
98
103
  initialInstruction: null,
99
104
  creationMode: false,
100
105
  canEnterBlueprint: false,
106
+ blueprintHidden: false,
107
+ blueprintRevision: 0,
101
108
  blueprintSessionId: null,
102
109
  userCreatedBlocks: [],
103
110
  userCreatedIslands: [],
104
111
  blueprintFunctions: [],
105
112
  blueprintVariables: [],
106
113
  blueprintImports: [],
114
+ blueprintNotes: [],
107
115
  }
108
116
 
109
117
  function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
@@ -149,6 +157,9 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
149
157
  typeof data?.initialInstruction === 'string' ? data.initialInstruction : null,
150
158
  creationMode: Boolean(data?.creationMode),
151
159
  canEnterBlueprint: Boolean(data?.canEnterBlueprint),
160
+ blueprintHidden: Boolean(data?.blueprintHidden),
161
+ blueprintRevision:
162
+ typeof data?.blueprintRevision === 'number' ? data.blueprintRevision : 0,
152
163
  blueprintSessionId:
153
164
  typeof data?.blueprintSessionId === 'string'
154
165
  ? data.blueprintSessionId
@@ -158,18 +169,42 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
158
169
  blueprintFunctions: normalizeSymbolAdditions(data?.blueprintFunctions),
159
170
  blueprintVariables: normalizeSymbolAdditions(data?.blueprintVariables),
160
171
  blueprintImports: normalizeImportAdditions(data?.blueprintImports),
172
+ blueprintNotes: parseBlueprintNotes(data?.blueprintNotes),
173
+ }
174
+ }
175
+
176
+ function normalizeBlueprint(data: Partial<AgentIntentBundle['blueprint']> | null | undefined) {
177
+ return {
178
+ hidden: Boolean(data?.hidden),
179
+ revision: typeof data?.revision === 'number' ? data.revision : 0,
180
+ enabled: Boolean(data?.enabled),
181
+ userCreatedBlocks: parseUserCreatedBlocks(data?.userCreatedBlocks),
182
+ userCreatedIslands: parseUserCreatedIslands(data?.userCreatedIslands),
183
+ addedFunctions: normalizeSymbolAdditions(data?.addedFunctions),
184
+ addedVariables: normalizeSymbolAdditions(data?.addedVariables),
185
+ addedImports: normalizeImportAdditions(data?.addedImports),
186
+ notes: parseBlueprintNotes(data?.notes),
161
187
  }
162
188
  }
163
189
 
164
190
  export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
165
191
  const query = new URLSearchParams({ t: String(Date.now()) })
166
192
  const response = await fetch(`/api/agent-intent?${query}`)
167
- if (!response.ok) return { focusedSessionId: null, nextAttachSessionId: null, intents: [] }
193
+ const emptyBlueprint = normalizeBlueprint(null)
194
+ if (!response.ok) {
195
+ return {
196
+ focusedSessionId: null,
197
+ nextAttachSessionId: null,
198
+ intents: [],
199
+ blueprint: emptyBlueprint,
200
+ }
201
+ }
168
202
  const data = (await response.json()) as {
169
203
  focusedSessionId?: string | null
170
204
  nextAttachSessionId?: string | null
171
205
  intents?: unknown
172
206
  sessionId?: string | null
207
+ blueprint?: Partial<AgentIntentBundle['blueprint']>
173
208
  } & Partial<AgentIntent>
174
209
  if (Array.isArray(data.intents)) {
175
210
  return {
@@ -182,6 +217,7 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
182
217
  intents: data.intents
183
218
  .map((intent) => normalize(intent as Partial<AgentIntent>))
184
219
  .filter((intent) => Boolean(intent.sessionId)),
220
+ blueprint: normalizeBlueprint(data.blueprint),
185
221
  }
186
222
  }
187
223
  const intent = normalize(data)
@@ -189,6 +225,23 @@ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
189
225
  focusedSessionId: intent.sessionId,
190
226
  nextAttachSessionId: intent.awaitingAttach ? intent.sessionId : null,
191
227
  intents: intent.sessionId ? [intent] : [],
228
+ blueprint: normalizeBlueprint(data.blueprint ?? {
229
+ hidden: intent.blueprintHidden,
230
+ revision: intent.blueprintRevision,
231
+ enabled:
232
+ intent.userCreatedBlocks.length > 0 ||
233
+ intent.userCreatedIslands.length > 0 ||
234
+ intent.blueprintFunctions.length > 0 ||
235
+ intent.blueprintVariables.length > 0 ||
236
+ intent.blueprintImports.length > 0 ||
237
+ intent.blueprintNotes.length > 0,
238
+ userCreatedBlocks: intent.userCreatedBlocks,
239
+ userCreatedIslands: intent.userCreatedIslands,
240
+ addedFunctions: intent.blueprintFunctions,
241
+ addedVariables: intent.blueprintVariables,
242
+ addedImports: intent.blueprintImports,
243
+ notes: intent.blueprintNotes,
244
+ }),
192
245
  }
193
246
  }
194
247
 
@@ -219,6 +272,7 @@ export async function performAgentAction(
219
272
  addedFunctions?: PatchSymbolAddition[]
220
273
  addedVariables?: PatchSymbolAddition[]
221
274
  addedImports?: PatchImportAddition[]
275
+ notes?: BlueprintNote[]
222
276
  } = {},
223
277
  ) {
224
278
  const response = await fetch('/api/agent-intent', {
@@ -234,16 +288,17 @@ export async function performAgentAction(
234
288
  }
235
289
 
236
290
  export function persistSessionBlueprint(
237
- sessionId: string,
291
+ sessionId: string | null | undefined,
238
292
  payload: {
239
293
  userCreatedBlocks: UserCreatedBlock[]
240
294
  userCreatedIslands: UserCreatedIsland[]
241
295
  addedFunctions?: PatchSymbolAddition[]
242
296
  addedVariables?: PatchSymbolAddition[]
243
297
  addedImports?: PatchImportAddition[]
298
+ notes?: BlueprintNote[]
244
299
  },
245
300
  ) {
246
- fetch('/api/agent-intent', {
301
+ return fetch('/api/agent-intent', {
247
302
  method: 'POST',
248
303
  headers: { 'Content-Type': 'application/json' },
249
304
  body: JSON.stringify({
@@ -252,7 +307,34 @@ export function persistSessionBlueprint(
252
307
  ...payload,
253
308
  }),
254
309
  }).catch(() => {
255
- // Keep local drafts if the session handshake is no longer open.
310
+ // Keep local drafts if the visualizer could not save the shared blueprint.
311
+ })
312
+ }
313
+
314
+ export function persistBlueprintHidden(hidden: boolean) {
315
+ return fetch('/api/agent-intent', {
316
+ method: 'POST',
317
+ headers: { 'Content-Type': 'application/json' },
318
+ body: JSON.stringify({
319
+ action: 'blueprint_set_hidden',
320
+ hidden,
321
+ }),
322
+ })
323
+ }
324
+
325
+ export function persistBlueprintClear() {
326
+ return fetch('/api/agent-intent', {
327
+ method: 'POST',
328
+ headers: { 'Content-Type': 'application/json' },
329
+ body: JSON.stringify({ action: 'blueprint_clear' }),
330
+ })
331
+ }
332
+
333
+ export function persistBlueprintCleanup() {
334
+ return fetch('/api/agent-intent', {
335
+ method: 'POST',
336
+ headers: { 'Content-Type': 'application/json' },
337
+ body: JSON.stringify({ action: 'blueprint_cleanup' }),
256
338
  })
257
339
  }
258
340
 
@@ -188,6 +188,7 @@ button {
188
188
  display: flex;
189
189
  gap: 8px;
190
190
  flex: none;
191
+ flex-wrap: wrap;
191
192
  }
192
193
 
193
194
  .hud-chip,
@@ -1389,6 +1390,102 @@ button {
1389
1390
  border-color: #9ad8ff;
1390
1391
  }
1391
1392
 
1393
+ .hud-item-actions {
1394
+ display: flex;
1395
+ align-items: center;
1396
+ gap: 2px;
1397
+ flex: none;
1398
+ }
1399
+
1400
+ .hud-item-note {
1401
+ flex: none;
1402
+ padding: 2px 6px;
1403
+ color: #b7c0ce;
1404
+ background: transparent;
1405
+ border: 1px solid #3a4250;
1406
+ cursor: pointer;
1407
+ font-size: 11px;
1408
+ }
1409
+
1410
+ .hud-item-note:hover,
1411
+ .hud-item-note:focus-visible,
1412
+ .hud-item-note[data-open='true'] {
1413
+ border-color: #9ad8ff;
1414
+ color: #d7eef8;
1415
+ }
1416
+
1417
+ .hud-item-note[data-has-note='true'],
1418
+ .hud-button[data-has-note='true'] {
1419
+ border-color: #5d9ec4;
1420
+ color: #d7eef8;
1421
+ }
1422
+
1423
+ .hud-note-overlay {
1424
+ position: absolute;
1425
+ inset: 0;
1426
+ z-index: 22;
1427
+ display: grid;
1428
+ place-items: center;
1429
+ padding: 24px;
1430
+ background: rgba(12, 14, 18, 0.82);
1431
+ pointer-events: auto;
1432
+ }
1433
+
1434
+ .hud-note-card {
1435
+ width: min(1400px, 100%);
1436
+ height: min(92dvh, 100%);
1437
+ display: flex;
1438
+ flex-direction: column;
1439
+ gap: 16px;
1440
+ padding: 24px 24px 20px;
1441
+ background: var(--vc-editor);
1442
+ border: 1px solid var(--vc-border);
1443
+ }
1444
+
1445
+ .hud-note-header {
1446
+ display: flex;
1447
+ align-items: flex-start;
1448
+ justify-content: space-between;
1449
+ gap: 16px;
1450
+ flex: none;
1451
+ }
1452
+
1453
+ .hud-note-heading {
1454
+ min-width: 0;
1455
+ }
1456
+
1457
+ .hud-note-header h1 {
1458
+ margin: 0;
1459
+ font-size: 20px;
1460
+ font-weight: 600;
1461
+ }
1462
+
1463
+ .hud-note-subtitle {
1464
+ margin: 6px 0 0;
1465
+ color: #8b95a5;
1466
+ font-size: 13px;
1467
+ line-height: 1.4;
1468
+ word-break: break-all;
1469
+ }
1470
+
1471
+ .hud-note-field {
1472
+ width: 100%;
1473
+ min-height: 0;
1474
+ flex: 1;
1475
+ resize: none;
1476
+ box-sizing: border-box;
1477
+ padding: 16px;
1478
+ color: #e7ebf2;
1479
+ background: rgba(25, 28, 34, 0.96);
1480
+ border: 1px solid #3a4250;
1481
+ font: 15px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace;
1482
+ outline: none;
1483
+ }
1484
+
1485
+ .hud-note-field:focus {
1486
+ border-color: #9ad8ff;
1487
+ }
1488
+
1392
1489
  .hud-add-row {
1393
1490
  display: flex;
1394
1491
  gap: 6px;
@@ -1441,11 +1538,6 @@ button {
1441
1538
  font-weight: 600;
1442
1539
  }
1443
1540
 
1444
- .block-name-overlay {
1445
- z-index: 20 !important;
1446
- pointer-events: auto;
1447
- }
1448
-
1449
1541
  .hud-name-gate {
1450
1542
  position: absolute;
1451
1543
  top: 42%;
@@ -1455,6 +1547,15 @@ button {
1455
1547
  z-index: 4;
1456
1548
  }
1457
1549
 
1550
+ .block-name-overlay {
1551
+ z-index: 90 !important;
1552
+ pointer-events: auto;
1553
+ }
1554
+
1555
+ .block-name-overlay-map .block-name-form {
1556
+ transform: translate(-50%, calc(-100% - 8px));
1557
+ }
1558
+
1458
1559
  .block-name-form {
1459
1560
  pointer-events: auto;
1460
1561
  }
@@ -0,0 +1,26 @@
1
+ let isolation = 0
2
+
3
+ export function beginKeyboardIsolation() {
4
+ isolation += 1
5
+ return () => {
6
+ isolation = Math.max(0, isolation - 1)
7
+ }
8
+ }
9
+
10
+ export function isKeyboardIsolated() {
11
+ return isolation > 0
12
+ }
13
+
14
+ export function isTypingInField(target: EventTarget | null) {
15
+ return (
16
+ target instanceof HTMLElement &&
17
+ (target.tagName === 'TEXTAREA' ||
18
+ target.tagName === 'INPUT' ||
19
+ target.tagName === 'SELECT' ||
20
+ target.isContentEditable)
21
+ )
22
+ }
23
+
24
+ export function shouldIgnoreShortcut(event: KeyboardEvent) {
25
+ return isKeyboardIsolated() || isTypingInField(event.target)
26
+ }
@@ -2,6 +2,7 @@ import { useEffect } from 'react'
2
2
  import { useThree } from '@react-three/fiber'
3
3
  import * as THREE from 'three'
4
4
  import { folderAt } from '../layout'
5
+ import { shouldIgnoreShortcut } from '../keyboard'
5
6
  import type { WorldLayout } from '../types'
6
7
 
7
8
  type BlockPlacerProps = {
@@ -19,16 +20,6 @@ const forward = new THREE.Vector3()
19
20
  const PLACE_MIN = 3
20
21
  const PLACE_MAX = 22
21
22
 
22
- function typingInField(target: EventTarget | null) {
23
- return (
24
- target instanceof HTMLElement &&
25
- (target.tagName === 'TEXTAREA' ||
26
- target.tagName === 'INPUT' ||
27
- target.tagName === 'SELECT' ||
28
- target.isContentEditable)
29
- )
30
- }
31
-
32
23
  function lookPoint(camera: THREE.Camera): { x: number; z: number } {
33
24
  raycaster.setFromCamera(ndc, camera)
34
25
  const reached = raycaster.ray.intersectPlane(ground, hit)
@@ -70,7 +61,7 @@ export function BlockPlacer({ enabled, layout, onPlace }: BlockPlacerProps) {
70
61
  useEffect(() => {
71
62
  const onKey = (event: KeyboardEvent) => {
72
63
  if (!enabled || event.repeat || event.code !== 'Space') return
73
- if (typingInField(event.target)) return
64
+ if (shouldIgnoreShortcut(event)) return
74
65
  event.preventDefault()
75
66
  const { x, z } = lookPoint(camera)
76
67
  const island =
@@ -193,11 +193,19 @@ export const FileBlock = memo(function FileBlock({
193
193
  )}
194
194
  {naming && onCommitName && onCancelName && (
195
195
  <Html
196
- position={[0, height / 2 + 0.42, 0]}
197
- center
196
+ position={
197
+ mapMode
198
+ ? [0, height / 2 + 0.04, -depth / 2]
199
+ : [0, height / 2 + 0.42, 0]
200
+ }
201
+ center={!mapMode}
198
202
  occlude={false}
199
- wrapperClass="block-name-overlay"
200
- zIndexRange={[100, 0]}
203
+ wrapperClass={
204
+ mapMode
205
+ ? 'block-name-overlay block-name-overlay-map'
206
+ : 'block-name-overlay'
207
+ }
208
+ zIndexRange={[120, 0]}
201
209
  >
202
210
  <NameInput
203
211
  placeholder="File name"
@@ -10,6 +10,7 @@ import {
10
10
  } from '../theme'
11
11
  import type { PlacedFolder } from '../types'
12
12
  import { MapSelectBorder } from './MapSelectBorder'
13
+ import { NameInput } from '../ui/NameInput'
13
14
 
14
15
  type FolderAreaProps = {
15
16
  folder: PlacedFolder
@@ -19,6 +20,8 @@ type FolderAreaProps = {
19
20
  highlightKind?: ChangeKind | null
20
21
  previewLabels?: boolean
21
22
  labelVisible?: boolean
23
+ onCommitName?: (name: string) => void
24
+ onCancelName?: () => void
22
25
  }
23
26
 
24
27
  export function FolderArea({
@@ -29,6 +32,8 @@ export function FolderArea({
29
32
  highlightKind = null,
30
33
  previewLabels = false,
31
34
  labelVisible = true,
35
+ onCommitName,
36
+ onCancelName,
32
37
  }: FolderAreaProps) {
33
38
  const added = Boolean(folder.added)
34
39
  const highlight = highlightKind ? CHANGE_HIGHLIGHT[highlightKind] : null
@@ -88,6 +93,21 @@ export function FolderArea({
88
93
  <meshBasicMaterial color={aisle} />
89
94
  </mesh>
90
95
  )}
96
+ {naming && mapMode && onCommitName && onCancelName && (
97
+ <Html
98
+ position={[0, 1.35, -folder.depth / 2 + 1.35]}
99
+ center
100
+ occlude={false}
101
+ wrapperClass="block-name-overlay"
102
+ zIndexRange={[120, 0]}
103
+ >
104
+ <NameInput
105
+ placeholder="Folder name"
106
+ onCommit={onCommitName}
107
+ onCancel={onCancelName}
108
+ />
109
+ </Html>
110
+ )}
91
111
  <Suspense fallback={null}>
92
112
  {!naming && previewLabels && labelVisible && (
93
113
  <Html
@@ -1,6 +1,7 @@
1
1
  import { useEffect } from 'react'
2
2
  import { useThree } from '@react-three/fiber'
3
3
  import { folderAt } from '../layout'
4
+ import { shouldIgnoreShortcut } from '../keyboard'
4
5
  import type { WorldLayout } from '../types'
5
6
 
6
7
  type IslandPlacerProps = {
@@ -9,23 +10,13 @@ type IslandPlacerProps = {
9
10
  onPlace: (parent: string) => void
10
11
  }
11
12
 
12
- function typingInField(target: EventTarget | null) {
13
- return (
14
- target instanceof HTMLElement &&
15
- (target.tagName === 'TEXTAREA' ||
16
- target.tagName === 'INPUT' ||
17
- target.tagName === 'SELECT' ||
18
- target.isContentEditable)
19
- )
20
- }
21
-
22
13
  export function IslandPlacer({ enabled, layout, onPlace }: IslandPlacerProps) {
23
14
  const { camera } = useThree()
24
15
 
25
16
  useEffect(() => {
26
17
  const onKey = (event: KeyboardEvent) => {
27
18
  if (!enabled || event.repeat || event.code !== 'KeyB') return
28
- if (typingInField(event.target)) return
19
+ if (shouldIgnoreShortcut(event)) return
29
20
  const island = folderAt(camera.position.x, camera.position.z, layout)
30
21
  if (!island) return
31
22
  event.preventDefault()
@@ -18,6 +18,7 @@ type MapViewProps = {
18
18
  marker: [number, number] | null
19
19
  highlightedFolders?: Partial<Record<string, ChangeKind>>
20
20
  selectedFolder?: string | null
21
+ namingFolderPath?: string | null
21
22
  onLand: (x: number, z: number) => void
22
23
  onSelect: (fileId: string | null) => void
23
24
  onSelectFolder: (folderPath: string | null) => void
@@ -31,6 +32,7 @@ export function MapView({
31
32
  marker,
32
33
  highlightedFolders,
33
34
  selectedFolder = null,
35
+ namingFolderPath = null,
34
36
  onLand,
35
37
  onSelect,
36
38
  onSelectFolder,
@@ -83,10 +85,9 @@ export function MapView({
83
85
  const element = gl.domElement
84
86
  element.style.cursor = 'grab'
85
87
 
86
- const isWalkClick = (event: PointerEvent | MouseEvent) => event.ctrlKey
88
+ const isWalkClick = (event: PointerEvent | MouseEvent) => event.altKey
87
89
 
88
- const isWalkButton = (event: PointerEvent) =>
89
- event.button === 0 || (event.ctrlKey && event.button === 2)
90
+ const isWalkButton = (event: PointerEvent) => event.button === 0
90
91
 
91
92
  const onDown = (event: PointerEvent) => {
92
93
  if (!isWalkButton(event)) return
@@ -183,7 +184,7 @@ export function MapView({
183
184
  }
184
185
 
185
186
  const onContextMenu = (event: MouseEvent) => {
186
- if (event.ctrlKey) {
187
+ if (event.altKey) {
187
188
  event.preventDefault()
188
189
  return
189
190
  }
@@ -313,6 +314,7 @@ export function MapView({
313
314
  folders={layout.folders}
314
315
  highlightedFolders={highlightedFolders}
315
316
  selectedFolder={selectedFolder}
317
+ namingFolderPath={namingFolderPath}
316
318
  />
317
319
  )}
318
320
  {enabled && marker && <LandMarker marker={marker} />}
@@ -322,6 +324,23 @@ export function MapView({
322
324
 
323
325
  const PROJECT = new THREE.Vector3()
324
326
  const MIN_FOLDER_LABEL_PX = 28
327
+ const FOLDER_ENTRANCE_Z = 1.35
328
+
329
+ function projectToScreen(
330
+ x: number,
331
+ y: number,
332
+ z: number,
333
+ camera: THREE.Camera,
334
+ width: number,
335
+ height: number,
336
+ ) {
337
+ PROJECT.set(x, y, z).project(camera)
338
+ return {
339
+ x: (PROJECT.x * 0.5 + 0.5) * width,
340
+ y: (-PROJECT.y * 0.5 + 0.5) * height,
341
+ behind: PROJECT.z < -1 || PROJECT.z > 1,
342
+ }
343
+ }
325
344
 
326
345
  function folderLabelClass(
327
346
  folder: PlacedFolder,
@@ -346,10 +365,12 @@ function MapFolderLabels({
346
365
  folders,
347
366
  highlightedFolders,
348
367
  selectedFolder,
368
+ namingFolderPath,
349
369
  }: {
350
370
  folders: Record<string, PlacedFolder>
351
371
  highlightedFolders?: Partial<Record<string, ChangeKind>>
352
372
  selectedFolder: string | null
373
+ namingFolderPath: string | null
353
374
  }) {
354
375
  const camera = useThree((state) => state.camera)
355
376
  const gl = useThree((state) => state.gl)
@@ -398,21 +419,31 @@ function MapFolderLabels({
398
419
  const el = nodes[i] as HTMLElement | undefined
399
420
  const folder = items[i]
400
421
  if (!el || !folder) continue
401
- PROJECT.set(folder.x, 14, folder.z + 1.35).project(camera)
402
- const x = (PROJECT.x * 0.5 + 0.5) * size.width
403
- const y = (-PROJECT.y * 0.5 + 0.5) * size.height
422
+ const screen = projectToScreen(
423
+ folder.x,
424
+ 14,
425
+ folder.z + FOLDER_ENTRANCE_Z,
426
+ camera,
427
+ size.width,
428
+ size.height,
429
+ )
430
+ const x = screen.x
431
+ const y = screen.y
404
432
  const span = Math.max(folder.width, folder.depth) * zoom
405
433
  const force =
406
434
  selectedFolder === folder.path ||
407
435
  Boolean(highlightedFolders?.[folder.path] || folder.added)
408
436
  const onScreen =
409
- PROJECT.z >= -1 &&
410
- PROJECT.z <= 1 &&
437
+ !screen.behind &&
411
438
  x > -120 &&
412
439
  x < size.width + 120 &&
413
440
  y > -40 &&
414
441
  y < size.height + 40
415
- if (!onScreen || (!force && span < MIN_FOLDER_LABEL_PX)) {
442
+ if (
443
+ folder.path === namingFolderPath ||
444
+ !onScreen ||
445
+ (!force && span < MIN_FOLDER_LABEL_PX)
446
+ ) {
416
447
  if (el.style.visibility !== 'hidden') el.style.visibility = 'hidden'
417
448
  continue
418
449
  }
@@ -4,6 +4,7 @@ import { PerspectiveCamera, PointerLockControls } from '@react-three/drei'
4
4
  import type { PointerLockControls as PointerLockControlsImpl } from 'three-stdlib'
5
5
  import * as THREE from 'three'
6
6
  import { CONFIG } from '../theme'
7
+ import { isKeyboardIsolated, shouldIgnoreShortcut } from '../keyboard'
7
8
  import type { FlyTo, ViewMode, WorldLayout } from '../types'
8
9
 
9
10
  const lookDir = new THREE.Vector3()
@@ -143,15 +144,7 @@ export function Player({
143
144
 
144
145
  useEffect(() => {
145
146
  const onKey = (event: KeyboardEvent, down: boolean) => {
146
- if (
147
- event.target instanceof HTMLElement &&
148
- (event.target.tagName === 'TEXTAREA' ||
149
- event.target.tagName === 'INPUT' ||
150
- event.target.tagName === 'SELECT' ||
151
- event.target.isContentEditable)
152
- ) {
153
- return
154
- }
147
+ if (shouldIgnoreShortcut(event)) return
155
148
  if (event.code === 'KeyW' || event.code === 'ArrowUp') keys.current.forward = down
156
149
  if (event.code === 'KeyS' || event.code === 'ArrowDown') keys.current.back = down
157
150
  if (event.code === 'KeyA' || event.code === 'ArrowLeft') keys.current.left = down
@@ -235,6 +228,13 @@ export function Player({
235
228
  setSteering(true)
236
229
  }
237
230
  } else if (walking && locked) {
231
+ if (isKeyboardIsolated()) {
232
+ keys.current.forward = false
233
+ keys.current.back = false
234
+ keys.current.left = false
235
+ keys.current.right = false
236
+ keys.current.sprint = false
237
+ }
238
238
  camera.getWorldDirection(front.current)
239
239
  front.current.y = 0
240
240
  front.current.normalize()
@@ -9,6 +9,7 @@ import {
9
9
  folderOfFile,
10
10
  } from '../layout'
11
11
  import { CONFIG, WORLD_VOID, type ChangeKind } from '../theme'
12
+ import { shouldIgnoreShortcut } from '../keyboard'
12
13
  import type { CodebaseGraph, PlacedFile, PlacedFolder, WorldLayout } from '../types'
13
14
  import { FileBlock } from './FileBlock'
14
15
  import { FolderArea } from './FolderArea'
@@ -903,6 +904,7 @@ export function SelectionThumbnail({
903
904
  if (!maximized) return
904
905
  const onKeyDown = (event: KeyboardEvent) => {
905
906
  if (event.key !== 'Escape') return
907
+ if (shouldIgnoreShortcut(event)) return
906
908
  event.preventDefault()
907
909
  onMaximize?.()
908
910
  }