@jkwd/inbase 0.1.13 → 0.1.16
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.
- package/README.md +2 -2
- package/apps/explorer/scripts/branch-changes.mjs +2 -19
- package/apps/explorer/scripts/patch-lib.d.ts +3 -0
- package/apps/explorer/scripts/patch-lib.mjs +3 -0
- package/apps/explorer/scripts/scan-ignore.mjs +156 -0
- package/apps/explorer/scripts/scan-target.mjs +67 -29
- package/apps/explorer/scripts/session-store.d.ts +24 -3
- package/apps/explorer/scripts/session-store.mjs +180 -74
- package/apps/explorer/src/App.tsx +258 -147
- package/apps/explorer/src/agentIntent.ts +87 -5
- package/apps/explorer/src/index.css +106 -5
- package/apps/explorer/src/keyboard.ts +26 -0
- package/apps/explorer/src/scene/BlockPlacer.tsx +2 -11
- package/apps/explorer/src/scene/FileBlock.tsx +12 -4
- package/apps/explorer/src/scene/FolderArea.tsx +20 -0
- package/apps/explorer/src/scene/IslandPlacer.tsx +2 -11
- package/apps/explorer/src/scene/MapView.tsx +74 -37
- package/apps/explorer/src/scene/Player.tsx +9 -9
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +2 -0
- package/apps/explorer/src/scene/World.tsx +46 -1
- package/apps/explorer/src/types.ts +28 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +4 -0
- package/apps/explorer/src/ui/HUD.tsx +435 -99
- package/apps/explorer/src/ui/MapContextMenu.tsx +2 -0
- package/apps/explorer/src/userCreated.ts +98 -0
- package/apps/explorer/vite.config.ts +71 -5
- package/bin/session.mjs +40 -12
- package/package.json +2 -1
- package/skill/commands/inbase.md +1 -1
- package/skill/inbase/SKILL.md +24 -22
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
type AgentIntent,
|
|
9
9
|
type AgentIntentStatus,
|
|
10
10
|
type AimedRelation,
|
|
11
|
+
type BlueprintNote,
|
|
12
|
+
type BlueprintNoteKind,
|
|
11
13
|
type BranchChanges,
|
|
12
14
|
type CodebaseGraph,
|
|
13
15
|
type PatchImportAddition,
|
|
@@ -16,6 +18,8 @@ import {
|
|
|
16
18
|
type WorldLayout,
|
|
17
19
|
type WorkflowAction,
|
|
18
20
|
} from '../types'
|
|
21
|
+
import { findBlueprintNote } from '../userCreated'
|
|
22
|
+
import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
|
|
19
23
|
|
|
20
24
|
function reviewTitle(status: AgentIntentStatus) {
|
|
21
25
|
if (status === 'blueprint_ask') return 'Setup blueprint'
|
|
@@ -214,6 +218,144 @@ function AddIntentRow({
|
|
|
214
218
|
)
|
|
215
219
|
}
|
|
216
220
|
|
|
221
|
+
function BlueprintNoteModal({
|
|
222
|
+
title,
|
|
223
|
+
subtitle,
|
|
224
|
+
value,
|
|
225
|
+
placeholder,
|
|
226
|
+
onChange,
|
|
227
|
+
onClose,
|
|
228
|
+
}: {
|
|
229
|
+
title: string
|
|
230
|
+
subtitle: string
|
|
231
|
+
value: string
|
|
232
|
+
placeholder: string
|
|
233
|
+
onChange: (value: string) => void
|
|
234
|
+
onClose: () => void
|
|
235
|
+
}) {
|
|
236
|
+
const fieldRef = useRef<HTMLTextAreaElement>(null)
|
|
237
|
+
const onCloseRef = useRef(onClose)
|
|
238
|
+
onCloseRef.current = onClose
|
|
239
|
+
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
const release = beginKeyboardIsolation()
|
|
242
|
+
document.exitPointerLock()
|
|
243
|
+
const field = fieldRef.current
|
|
244
|
+
field?.focus()
|
|
245
|
+
if (field) {
|
|
246
|
+
const end = field.value.length
|
|
247
|
+
field.setSelectionRange(end, end)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
251
|
+
if (event.code === 'Escape') {
|
|
252
|
+
event.preventDefault()
|
|
253
|
+
event.stopImmediatePropagation()
|
|
254
|
+
onCloseRef.current()
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
if (event.key !== 'Tab') return
|
|
258
|
+
event.preventDefault()
|
|
259
|
+
event.stopImmediatePropagation()
|
|
260
|
+
if (document.activeElement !== fieldRef.current) {
|
|
261
|
+
fieldRef.current?.focus()
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
window.addEventListener('keydown', onKeyDown, true)
|
|
265
|
+
return () => {
|
|
266
|
+
window.removeEventListener('keydown', onKeyDown, true)
|
|
267
|
+
release()
|
|
268
|
+
}
|
|
269
|
+
}, [])
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<div className="hud-note-overlay" onClick={onClose}>
|
|
273
|
+
<div
|
|
274
|
+
className="hud-note-card"
|
|
275
|
+
role="dialog"
|
|
276
|
+
aria-modal="true"
|
|
277
|
+
aria-labelledby="hud-note-title"
|
|
278
|
+
onClick={(event) => event.stopPropagation()}
|
|
279
|
+
>
|
|
280
|
+
<div className="hud-note-header">
|
|
281
|
+
<div className="hud-note-heading">
|
|
282
|
+
<h1 id="hud-note-title">{title}</h1>
|
|
283
|
+
<p className="hud-note-subtitle">{subtitle}</p>
|
|
284
|
+
</div>
|
|
285
|
+
<button className="hud-button" type="button" onClick={onClose}>
|
|
286
|
+
Close
|
|
287
|
+
</button>
|
|
288
|
+
</div>
|
|
289
|
+
<textarea
|
|
290
|
+
ref={fieldRef}
|
|
291
|
+
className="hud-note-field"
|
|
292
|
+
data-blueprint-note="true"
|
|
293
|
+
defaultValue={value}
|
|
294
|
+
maxLength={8000}
|
|
295
|
+
placeholder={placeholder}
|
|
296
|
+
aria-label={title}
|
|
297
|
+
spellCheck={false}
|
|
298
|
+
autoComplete="off"
|
|
299
|
+
autoCorrect="off"
|
|
300
|
+
autoCapitalize="off"
|
|
301
|
+
autoFocus
|
|
302
|
+
onChange={(event) => onChange(event.target.value)}
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
</div>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function BlueprintSymbolRow({
|
|
310
|
+
name,
|
|
311
|
+
className,
|
|
312
|
+
hasNote,
|
|
313
|
+
noteOpen,
|
|
314
|
+
canEdit,
|
|
315
|
+
canRemove,
|
|
316
|
+
onRemove,
|
|
317
|
+
onOpenNote,
|
|
318
|
+
}: {
|
|
319
|
+
name: string
|
|
320
|
+
className?: string
|
|
321
|
+
hasNote: boolean
|
|
322
|
+
noteOpen?: boolean
|
|
323
|
+
canEdit: boolean
|
|
324
|
+
canRemove?: boolean
|
|
325
|
+
onRemove?: () => void
|
|
326
|
+
onOpenNote: () => void
|
|
327
|
+
}) {
|
|
328
|
+
return (
|
|
329
|
+
<li>
|
|
330
|
+
<span className={className}>{name}</span>
|
|
331
|
+
{canEdit && (
|
|
332
|
+
<div className="hud-item-actions">
|
|
333
|
+
<button
|
|
334
|
+
className="hud-item-note"
|
|
335
|
+
type="button"
|
|
336
|
+
data-has-note={hasNote ? 'true' : 'false'}
|
|
337
|
+
data-open={noteOpen ? 'true' : 'false'}
|
|
338
|
+
aria-label={`Edit note for ${name}`}
|
|
339
|
+
onClick={onOpenNote}
|
|
340
|
+
>
|
|
341
|
+
Note
|
|
342
|
+
</button>
|
|
343
|
+
{canRemove && (
|
|
344
|
+
<button
|
|
345
|
+
className="hud-item-remove"
|
|
346
|
+
type="button"
|
|
347
|
+
aria-label={`Remove ${name}`}
|
|
348
|
+
onClick={onRemove}
|
|
349
|
+
>
|
|
350
|
+
×
|
|
351
|
+
</button>
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</li>
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
217
359
|
function AttachStateBadge({ attached }: { attached: boolean }) {
|
|
218
360
|
return (
|
|
219
361
|
<span
|
|
@@ -314,7 +456,8 @@ function blueprintIsDefined(intent: AgentIntent) {
|
|
|
314
456
|
(intent.userCreatedIslands?.length ?? 0) > 0 ||
|
|
315
457
|
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
316
458
|
(intent.blueprintVariables?.length ?? 0) > 0 ||
|
|
317
|
-
(intent.blueprintImports?.length ?? 0) > 0
|
|
459
|
+
(intent.blueprintImports?.length ?? 0) > 0 ||
|
|
460
|
+
(intent.blueprintNotes?.length ?? 0) > 0
|
|
318
461
|
)
|
|
319
462
|
}
|
|
320
463
|
|
|
@@ -352,7 +495,9 @@ function HandshakeSetup({
|
|
|
352
495
|
</h2>
|
|
353
496
|
<p>
|
|
354
497
|
Press <kbd>Space</kbd> for a file and <kbd>B</kbd> for an island.
|
|
355
|
-
On the map, right-click to add a file or folder.
|
|
498
|
+
On the map, right-click to add a file or folder. Open a file's info
|
|
499
|
+
panel to add functions, vars, and notes (instructions or pseudo
|
|
500
|
+
code). The blueprint is shared across sessions.
|
|
356
501
|
</p>
|
|
357
502
|
</section>
|
|
358
503
|
<section className="hud-setup-section">
|
|
@@ -585,7 +730,7 @@ function SessionPanel({
|
|
|
585
730
|
intent.awaitingAttach &&
|
|
586
731
|
nextAttachSession &&
|
|
587
732
|
nextAttachSession.sessionId !== sessionId
|
|
588
|
-
? sessionLabel(nextAttachSession) || '
|
|
733
|
+
? sessionLabel(nextAttachSession) || 'an earlier session'
|
|
589
734
|
: null
|
|
590
735
|
|
|
591
736
|
const act = (
|
|
@@ -680,9 +825,9 @@ function SessionPanel({
|
|
|
680
825
|
{askingBlueprint && !handshakeSetup && !showConnectedProgress ? (
|
|
681
826
|
<>
|
|
682
827
|
<p>
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
828
|
+
A shared blueprint is already available on the map. Send this
|
|
829
|
+
session to the LLM, or skip and let it continue with the current
|
|
830
|
+
layout.
|
|
686
831
|
</p>
|
|
687
832
|
<div className="hud-decide">
|
|
688
833
|
<button
|
|
@@ -1225,7 +1370,22 @@ function explorerInstructions({
|
|
|
1225
1370
|
{
|
|
1226
1371
|
id: 'setup-session',
|
|
1227
1372
|
keys: ['Setup LLM session'],
|
|
1228
|
-
label: 'Open a session; /inbase attaches the
|
|
1373
|
+
label: 'Open a session; /inbase attaches the oldest waiting one',
|
|
1374
|
+
},
|
|
1375
|
+
{
|
|
1376
|
+
id: 'blueprint-toggle',
|
|
1377
|
+
keys: ['Hide/Show blueprint'],
|
|
1378
|
+
label: 'Toggle the shared blueprint overlay',
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
id: 'blueprint-clear',
|
|
1382
|
+
keys: ['Clear blueprint'],
|
|
1383
|
+
label: 'Remove every planned file and folder',
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
id: 'blueprint-cleanup',
|
|
1387
|
+
keys: ['Cleanup blueprint'],
|
|
1388
|
+
label: 'Drop blueprint files and folders that already exist',
|
|
1229
1389
|
},
|
|
1230
1390
|
{ id: 'toggle-map', keys: ['M'], label: 'Toggle map' },
|
|
1231
1391
|
{
|
|
@@ -1279,8 +1439,8 @@ function explorerInstructions({
|
|
|
1279
1439
|
: []),
|
|
1280
1440
|
{ id: 'click-line', keys: ['Click'], label: 'A line to fly there' },
|
|
1281
1441
|
{
|
|
1282
|
-
id: '
|
|
1283
|
-
keys: ['
|
|
1442
|
+
id: 'option-click-walk',
|
|
1443
|
+
keys: ['Option', 'Click'],
|
|
1284
1444
|
label: 'An island to walk',
|
|
1285
1445
|
},
|
|
1286
1446
|
{
|
|
@@ -1312,7 +1472,22 @@ function explorerInstructions({
|
|
|
1312
1472
|
{
|
|
1313
1473
|
id: 'setup-session',
|
|
1314
1474
|
keys: ['Setup LLM session'],
|
|
1315
|
-
label: 'Open a session; /inbase attaches the
|
|
1475
|
+
label: 'Open a session; /inbase attaches the oldest waiting one',
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
id: 'blueprint-toggle',
|
|
1479
|
+
keys: ['Hide/Show blueprint'],
|
|
1480
|
+
label: 'Toggle the shared blueprint overlay',
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
id: 'blueprint-clear',
|
|
1484
|
+
keys: ['Clear blueprint'],
|
|
1485
|
+
label: 'Remove every planned file and folder',
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
id: 'blueprint-cleanup',
|
|
1489
|
+
keys: ['Cleanup blueprint'],
|
|
1490
|
+
label: 'Drop blueprint files and folders that already exist',
|
|
1316
1491
|
},
|
|
1317
1492
|
{ id: 'map-walk', keys: ['M'], label: 'Back to walk' },
|
|
1318
1493
|
...stop,
|
|
@@ -1368,6 +1543,7 @@ type HUDProps = {
|
|
|
1368
1543
|
blueprintFunctions?: PatchSymbolAddition[]
|
|
1369
1544
|
blueprintVariables?: PatchSymbolAddition[]
|
|
1370
1545
|
blueprintImports?: PatchImportAddition[]
|
|
1546
|
+
blueprintNotes?: BlueprintNote[]
|
|
1371
1547
|
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
1372
1548
|
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
1373
1549
|
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
@@ -1378,6 +1554,12 @@ type HUDProps = {
|
|
|
1378
1554
|
name: string,
|
|
1379
1555
|
from: string,
|
|
1380
1556
|
) => void
|
|
1557
|
+
onSetBlueprintNote?: (next: {
|
|
1558
|
+
file: string
|
|
1559
|
+
kind: BlueprintNoteKind
|
|
1560
|
+
name?: string
|
|
1561
|
+
note: string
|
|
1562
|
+
}) => void
|
|
1381
1563
|
onMapAddFile?: (folderPath: string) => void
|
|
1382
1564
|
onMapAddFolder?: (folderPath: string) => void
|
|
1383
1565
|
onInspectFile?: (fileId: string) => void
|
|
@@ -1385,6 +1567,12 @@ type HUDProps = {
|
|
|
1385
1567
|
plannedIds?: string[]
|
|
1386
1568
|
createdIds?: string[]
|
|
1387
1569
|
deletedIds?: string[]
|
|
1570
|
+
blueprintHidden?: boolean
|
|
1571
|
+
blueprintHasContent?: boolean
|
|
1572
|
+
blueprintCanCleanup?: boolean
|
|
1573
|
+
onToggleBlueprintHidden?: () => void
|
|
1574
|
+
onClearBlueprint?: () => void
|
|
1575
|
+
onCleanupBlueprint?: () => void
|
|
1388
1576
|
}
|
|
1389
1577
|
|
|
1390
1578
|
export function HUD({
|
|
@@ -1430,12 +1618,14 @@ export function HUD({
|
|
|
1430
1618
|
blueprintFunctions = [],
|
|
1431
1619
|
blueprintVariables = [],
|
|
1432
1620
|
blueprintImports = [],
|
|
1621
|
+
blueprintNotes = [],
|
|
1433
1622
|
onAddBlueprintFunction,
|
|
1434
1623
|
onAddBlueprintVariable,
|
|
1435
1624
|
onAddBlueprintImport,
|
|
1436
1625
|
onRemoveBlueprintFunction,
|
|
1437
1626
|
onRemoveBlueprintVariable,
|
|
1438
1627
|
onRemoveBlueprintImport,
|
|
1628
|
+
onSetBlueprintNote,
|
|
1439
1629
|
onMapAddFile,
|
|
1440
1630
|
onMapAddFolder,
|
|
1441
1631
|
onInspectFile,
|
|
@@ -1443,6 +1633,12 @@ export function HUD({
|
|
|
1443
1633
|
plannedIds = [],
|
|
1444
1634
|
createdIds = [],
|
|
1445
1635
|
deletedIds = [],
|
|
1636
|
+
blueprintHidden = false,
|
|
1637
|
+
blueprintHasContent = false,
|
|
1638
|
+
blueprintCanCleanup = false,
|
|
1639
|
+
onToggleBlueprintHidden,
|
|
1640
|
+
onClearBlueprint,
|
|
1641
|
+
onCleanupBlueprint,
|
|
1446
1642
|
}: HUDProps) {
|
|
1447
1643
|
const selected = graph.files.find((file) => file.id === selectedId)
|
|
1448
1644
|
const selectedFolderNode = graph.folders.find(
|
|
@@ -1471,6 +1667,14 @@ export function HUD({
|
|
|
1471
1667
|
const [walkIntro, setWalkIntro] = useState(false)
|
|
1472
1668
|
const walkIntroSeen = useRef(false)
|
|
1473
1669
|
const [instructionsOpen, setInstructionsOpen] = useState(false)
|
|
1670
|
+
const [noteEditor, setNoteEditor] = useState<{
|
|
1671
|
+
file: string
|
|
1672
|
+
kind: BlueprintNoteKind
|
|
1673
|
+
name?: string
|
|
1674
|
+
title: string
|
|
1675
|
+
subtitle: string
|
|
1676
|
+
placeholder: string
|
|
1677
|
+
} | null>(null)
|
|
1474
1678
|
const [setupError, setSetupError] = useState<string | null>(null)
|
|
1475
1679
|
const [setupBusy, setSetupBusy] = useState(false)
|
|
1476
1680
|
const [infoVisible, setInfoVisible] = useState(false)
|
|
@@ -1479,7 +1683,7 @@ export function HUD({
|
|
|
1479
1683
|
const [thumbnailMinimized, setThumbnailMinimized] = useState(false)
|
|
1480
1684
|
const [thumbnailMaximized, setThumbnailMaximized] = useState(false)
|
|
1481
1685
|
const infoPanelRef = useRef<HTMLDivElement>(null)
|
|
1482
|
-
const canPlace =
|
|
1686
|
+
const canPlace = true
|
|
1483
1687
|
const overlay = showBranchChanges && branchChanges ? branchChanges : intent
|
|
1484
1688
|
const previewing = intent.preview || showBranchChanges
|
|
1485
1689
|
const addedFunctions = overlay.addedFunctions ?? []
|
|
@@ -1548,6 +1752,35 @@ export function HUD({
|
|
|
1548
1752
|
)
|
|
1549
1753
|
const canEditBlueprint =
|
|
1550
1754
|
canPlace && Boolean(selected) && !selected?.id.startsWith('draft:')
|
|
1755
|
+
const selectedFileNote = selected
|
|
1756
|
+
? findBlueprintNote(blueprintNotes, selected.id, 'file')
|
|
1757
|
+
: ''
|
|
1758
|
+
const openFileNote = () => {
|
|
1759
|
+
if (!selected || !onSetBlueprintNote) return
|
|
1760
|
+
setInstructionsOpen(false)
|
|
1761
|
+
setNoteEditor({
|
|
1762
|
+
file: selected.id,
|
|
1763
|
+
kind: 'file',
|
|
1764
|
+
title: `Note · ${selected.name}`,
|
|
1765
|
+
subtitle: selected.path,
|
|
1766
|
+
placeholder: 'Extra instructions or pseudo code for this file',
|
|
1767
|
+
})
|
|
1768
|
+
}
|
|
1769
|
+
const openSymbolNote = (
|
|
1770
|
+
kind: 'function' | 'variable',
|
|
1771
|
+
name: string,
|
|
1772
|
+
) => {
|
|
1773
|
+
if (!selected || !onSetBlueprintNote) return
|
|
1774
|
+
setInstructionsOpen(false)
|
|
1775
|
+
setNoteEditor({
|
|
1776
|
+
file: selected.id,
|
|
1777
|
+
kind,
|
|
1778
|
+
name,
|
|
1779
|
+
title: `Note · ${name}`,
|
|
1780
|
+
subtitle: `${kind} in ${selected.path}`,
|
|
1781
|
+
placeholder: `Instructions or pseudo code for ${name}`,
|
|
1782
|
+
})
|
|
1783
|
+
}
|
|
1551
1784
|
const canInspectFile = (fileId: string, userCreated = false) =>
|
|
1552
1785
|
Boolean(onInspectFile) &&
|
|
1553
1786
|
!fileId.startsWith('draft:') &&
|
|
@@ -1608,16 +1841,7 @@ export function HUD({
|
|
|
1608
1841
|
useEffect(() => {
|
|
1609
1842
|
const onKey = (event: KeyboardEvent) => {
|
|
1610
1843
|
if (event.repeat || event.code !== 'KeyI') return
|
|
1611
|
-
|
|
1612
|
-
if (
|
|
1613
|
-
target instanceof HTMLElement &&
|
|
1614
|
-
(target.tagName === 'TEXTAREA' ||
|
|
1615
|
-
target.tagName === 'INPUT' ||
|
|
1616
|
-
target.tagName === 'SELECT' ||
|
|
1617
|
-
target.isContentEditable)
|
|
1618
|
-
) {
|
|
1619
|
-
return
|
|
1620
|
-
}
|
|
1844
|
+
if (shouldIgnoreShortcut(event)) return
|
|
1621
1845
|
event.preventDefault()
|
|
1622
1846
|
if (infoVisible) {
|
|
1623
1847
|
setInfoVisible(false)
|
|
@@ -1640,16 +1864,7 @@ export function HUD({
|
|
|
1640
1864
|
if (!mapping) return
|
|
1641
1865
|
const onKey = (event: KeyboardEvent) => {
|
|
1642
1866
|
if (event.repeat || event.code !== 'KeyT') return
|
|
1643
|
-
|
|
1644
|
-
if (
|
|
1645
|
-
target instanceof HTMLElement &&
|
|
1646
|
-
(target.tagName === 'TEXTAREA' ||
|
|
1647
|
-
target.tagName === 'INPUT' ||
|
|
1648
|
-
target.tagName === 'SELECT' ||
|
|
1649
|
-
target.isContentEditable)
|
|
1650
|
-
) {
|
|
1651
|
-
return
|
|
1652
|
-
}
|
|
1867
|
+
if (shouldIgnoreShortcut(event)) return
|
|
1653
1868
|
event.preventDefault()
|
|
1654
1869
|
setThumbnailVisible((visible) => {
|
|
1655
1870
|
if (!visible) {
|
|
@@ -1669,16 +1884,7 @@ export function HUD({
|
|
|
1669
1884
|
if (!infoVisible || (!selectedId && !selectedFolder)) return
|
|
1670
1885
|
const onKey = (event: KeyboardEvent) => {
|
|
1671
1886
|
if (event.code !== 'ArrowUp' && event.code !== 'ArrowDown') return
|
|
1672
|
-
|
|
1673
|
-
if (
|
|
1674
|
-
target instanceof HTMLElement &&
|
|
1675
|
-
(target.tagName === 'TEXTAREA' ||
|
|
1676
|
-
target.tagName === 'INPUT' ||
|
|
1677
|
-
target.tagName === 'SELECT' ||
|
|
1678
|
-
target.isContentEditable)
|
|
1679
|
-
) {
|
|
1680
|
-
return
|
|
1681
|
-
}
|
|
1887
|
+
if (shouldIgnoreShortcut(event)) return
|
|
1682
1888
|
const panel = infoPanelRef.current
|
|
1683
1889
|
if (!panel || panel.scrollHeight <= panel.clientHeight + 1) return
|
|
1684
1890
|
event.preventDefault()
|
|
@@ -1695,6 +1901,7 @@ export function HUD({
|
|
|
1695
1901
|
document.exitPointerLock()
|
|
1696
1902
|
const onKey = (event: KeyboardEvent) => {
|
|
1697
1903
|
if (event.code !== 'Escape') return
|
|
1904
|
+
if (shouldIgnoreShortcut(event)) return
|
|
1698
1905
|
event.preventDefault()
|
|
1699
1906
|
event.stopPropagation()
|
|
1700
1907
|
setInstructionsOpen(false)
|
|
@@ -1747,15 +1954,18 @@ export function HUD({
|
|
|
1747
1954
|
</div>
|
|
1748
1955
|
)}
|
|
1749
1956
|
|
|
1750
|
-
{
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1957
|
+
{mode === 'walk' &&
|
|
1958
|
+
namingIsland &&
|
|
1959
|
+
onCommitIslandName &&
|
|
1960
|
+
onCancelIslandName && (
|
|
1961
|
+
<div className="hud-name-gate">
|
|
1962
|
+
<NameInput
|
|
1963
|
+
placeholder="Folder name"
|
|
1964
|
+
onCommit={onCommitIslandName}
|
|
1965
|
+
onCancel={onCancelIslandName}
|
|
1966
|
+
/>
|
|
1967
|
+
</div>
|
|
1968
|
+
)}
|
|
1759
1969
|
|
|
1760
1970
|
{mode === 'walk' && locked && (
|
|
1761
1971
|
<div className="crosshair" data-aim={Boolean(aimed)} />
|
|
@@ -1877,6 +2087,21 @@ export function HUD({
|
|
|
1877
2087
|
Inspect file
|
|
1878
2088
|
</button>
|
|
1879
2089
|
)}
|
|
2090
|
+
{canEditBlueprint && onSetBlueprintNote && (
|
|
2091
|
+
<button
|
|
2092
|
+
className="hud-button hud-inspect"
|
|
2093
|
+
type="button"
|
|
2094
|
+
data-has-note={selectedFileNote ? 'true' : 'false'}
|
|
2095
|
+
data-open={
|
|
2096
|
+
noteEditor?.kind === 'file' && noteEditor.file === selected.id
|
|
2097
|
+
? 'true'
|
|
2098
|
+
: 'false'
|
|
2099
|
+
}
|
|
2100
|
+
onClick={openFileNote}
|
|
2101
|
+
>
|
|
2102
|
+
{selectedFileNote ? 'Edit file note' : 'Add file note'}
|
|
2103
|
+
</button>
|
|
2104
|
+
)}
|
|
1880
2105
|
{previewing &&
|
|
1881
2106
|
(selectedChangedFunctions.length > 0 ||
|
|
1882
2107
|
selectedAddedFunctions.length > 0 ||
|
|
@@ -1931,33 +2156,55 @@ export function HUD({
|
|
|
1931
2156
|
) : (
|
|
1932
2157
|
<ul>
|
|
1933
2158
|
{selectedFunctions.map((symbol) => (
|
|
1934
|
-
<
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
onClick={() =>
|
|
1949
|
-
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
1950
|
-
}
|
|
1951
|
-
>
|
|
1952
|
-
×
|
|
1953
|
-
</button>
|
|
2159
|
+
<BlueprintSymbolRow
|
|
2160
|
+
key={`fn-${symbol.name}`}
|
|
2161
|
+
name={symbol.name}
|
|
2162
|
+
className={
|
|
2163
|
+
symbolChangeClass(functionChange.get(symbol.name)) ??
|
|
2164
|
+
(symbol.intended ? 'hud-intended' : undefined)
|
|
2165
|
+
}
|
|
2166
|
+
hasNote={Boolean(
|
|
2167
|
+
findBlueprintNote(
|
|
2168
|
+
blueprintNotes,
|
|
2169
|
+
selected.id,
|
|
2170
|
+
'function',
|
|
2171
|
+
symbol.name,
|
|
2172
|
+
),
|
|
1954
2173
|
)}
|
|
1955
|
-
|
|
2174
|
+
noteOpen={
|
|
2175
|
+
noteEditor?.kind === 'function' &&
|
|
2176
|
+
noteEditor.file === selected.id &&
|
|
2177
|
+
noteEditor.name === symbol.name
|
|
2178
|
+
}
|
|
2179
|
+
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2180
|
+
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2181
|
+
onRemove={() =>
|
|
2182
|
+
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
2183
|
+
}
|
|
2184
|
+
onOpenNote={() => openSymbolNote('function', symbol.name)}
|
|
2185
|
+
/>
|
|
1956
2186
|
))}
|
|
1957
2187
|
{extraAddedFunctions.map((item) => (
|
|
1958
|
-
<
|
|
1959
|
-
|
|
1960
|
-
|
|
2188
|
+
<BlueprintSymbolRow
|
|
2189
|
+
key={`fn-add-${item.name}`}
|
|
2190
|
+
name={item.name}
|
|
2191
|
+
className="hud-file-add"
|
|
2192
|
+
hasNote={Boolean(
|
|
2193
|
+
findBlueprintNote(
|
|
2194
|
+
blueprintNotes,
|
|
2195
|
+
selected.id,
|
|
2196
|
+
'function',
|
|
2197
|
+
item.name,
|
|
2198
|
+
),
|
|
2199
|
+
)}
|
|
2200
|
+
noteOpen={
|
|
2201
|
+
noteEditor?.kind === 'function' &&
|
|
2202
|
+
noteEditor.file === selected.id &&
|
|
2203
|
+
noteEditor.name === item.name
|
|
2204
|
+
}
|
|
2205
|
+
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2206
|
+
onOpenNote={() => openSymbolNote('function', item.name)}
|
|
2207
|
+
/>
|
|
1961
2208
|
))}
|
|
1962
2209
|
</ul>
|
|
1963
2210
|
)}
|
|
@@ -1975,33 +2222,55 @@ export function HUD({
|
|
|
1975
2222
|
) : (
|
|
1976
2223
|
<ul>
|
|
1977
2224
|
{selectedVariables.map((symbol) => (
|
|
1978
|
-
<
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
onClick={() =>
|
|
1993
|
-
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
1994
|
-
}
|
|
1995
|
-
>
|
|
1996
|
-
×
|
|
1997
|
-
</button>
|
|
2225
|
+
<BlueprintSymbolRow
|
|
2226
|
+
key={`var-${symbol.name}`}
|
|
2227
|
+
name={symbol.name}
|
|
2228
|
+
className={
|
|
2229
|
+
symbolChangeClass(variableChange.get(symbol.name)) ??
|
|
2230
|
+
(symbol.intended ? 'hud-intended' : undefined)
|
|
2231
|
+
}
|
|
2232
|
+
hasNote={Boolean(
|
|
2233
|
+
findBlueprintNote(
|
|
2234
|
+
blueprintNotes,
|
|
2235
|
+
selected.id,
|
|
2236
|
+
'variable',
|
|
2237
|
+
symbol.name,
|
|
2238
|
+
),
|
|
1998
2239
|
)}
|
|
1999
|
-
|
|
2240
|
+
noteOpen={
|
|
2241
|
+
noteEditor?.kind === 'variable' &&
|
|
2242
|
+
noteEditor.file === selected.id &&
|
|
2243
|
+
noteEditor.name === symbol.name
|
|
2244
|
+
}
|
|
2245
|
+
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2246
|
+
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2247
|
+
onRemove={() =>
|
|
2248
|
+
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
2249
|
+
}
|
|
2250
|
+
onOpenNote={() => openSymbolNote('variable', symbol.name)}
|
|
2251
|
+
/>
|
|
2000
2252
|
))}
|
|
2001
2253
|
{extraAddedVariables.map((item) => (
|
|
2002
|
-
<
|
|
2003
|
-
|
|
2004
|
-
|
|
2254
|
+
<BlueprintSymbolRow
|
|
2255
|
+
key={`var-add-${item.name}`}
|
|
2256
|
+
name={item.name}
|
|
2257
|
+
className="hud-file-add"
|
|
2258
|
+
hasNote={Boolean(
|
|
2259
|
+
findBlueprintNote(
|
|
2260
|
+
blueprintNotes,
|
|
2261
|
+
selected.id,
|
|
2262
|
+
'variable',
|
|
2263
|
+
item.name,
|
|
2264
|
+
),
|
|
2265
|
+
)}
|
|
2266
|
+
noteOpen={
|
|
2267
|
+
noteEditor?.kind === 'variable' &&
|
|
2268
|
+
noteEditor.file === selected.id &&
|
|
2269
|
+
noteEditor.name === item.name
|
|
2270
|
+
}
|
|
2271
|
+
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2272
|
+
onOpenNote={() => openSymbolNote('variable', item.name)}
|
|
2273
|
+
/>
|
|
2005
2274
|
))}
|
|
2006
2275
|
</ul>
|
|
2007
2276
|
)}
|
|
@@ -2199,6 +2468,30 @@ export function HUD({
|
|
|
2199
2468
|
)}
|
|
2200
2469
|
</div>
|
|
2201
2470
|
|
|
2471
|
+
{noteEditor && onSetBlueprintNote && (
|
|
2472
|
+
<BlueprintNoteModal
|
|
2473
|
+
key={`${noteEditor.kind}:${noteEditor.file}:${noteEditor.name ?? ''}`}
|
|
2474
|
+
title={noteEditor.title}
|
|
2475
|
+
subtitle={noteEditor.subtitle}
|
|
2476
|
+
value={findBlueprintNote(
|
|
2477
|
+
blueprintNotes,
|
|
2478
|
+
noteEditor.file,
|
|
2479
|
+
noteEditor.kind,
|
|
2480
|
+
noteEditor.name,
|
|
2481
|
+
)}
|
|
2482
|
+
placeholder={noteEditor.placeholder}
|
|
2483
|
+
onChange={(note) =>
|
|
2484
|
+
onSetBlueprintNote({
|
|
2485
|
+
file: noteEditor.file,
|
|
2486
|
+
kind: noteEditor.kind,
|
|
2487
|
+
name: noteEditor.name,
|
|
2488
|
+
note,
|
|
2489
|
+
})
|
|
2490
|
+
}
|
|
2491
|
+
onClose={() => setNoteEditor(null)}
|
|
2492
|
+
/>
|
|
2493
|
+
)}
|
|
2494
|
+
|
|
2202
2495
|
{instructionsOpen && (
|
|
2203
2496
|
<div
|
|
2204
2497
|
className="hud-instructions-overlay"
|
|
@@ -2251,7 +2544,10 @@ export function HUD({
|
|
|
2251
2544
|
type="button"
|
|
2252
2545
|
aria-haspopup="dialog"
|
|
2253
2546
|
aria-expanded={instructionsOpen}
|
|
2254
|
-
onClick={() =>
|
|
2547
|
+
onClick={() => {
|
|
2548
|
+
setNoteEditor(null)
|
|
2549
|
+
setInstructionsOpen((open) => !open)
|
|
2550
|
+
}}
|
|
2255
2551
|
>
|
|
2256
2552
|
Instructions
|
|
2257
2553
|
</button>
|
|
@@ -2289,6 +2585,46 @@ export function HUD({
|
|
|
2289
2585
|
{setupBusy ? 'Starting…' : 'Setup LLM session'}
|
|
2290
2586
|
</button>
|
|
2291
2587
|
)}
|
|
2588
|
+
{onToggleBlueprintHidden && (
|
|
2589
|
+
<button
|
|
2590
|
+
className="hud-button"
|
|
2591
|
+
type="button"
|
|
2592
|
+
data-active={!blueprintHidden}
|
|
2593
|
+
aria-label={blueprintHidden ? 'Show blueprint' : 'Hide blueprint'}
|
|
2594
|
+
title={
|
|
2595
|
+
blueprintHidden
|
|
2596
|
+
? 'Show the shared blueprint overlay'
|
|
2597
|
+
: 'Hide the shared blueprint overlay'
|
|
2598
|
+
}
|
|
2599
|
+
onClick={onToggleBlueprintHidden}
|
|
2600
|
+
>
|
|
2601
|
+
{blueprintHidden ? 'Show blueprint' : 'Hide blueprint'}
|
|
2602
|
+
</button>
|
|
2603
|
+
)}
|
|
2604
|
+
{onClearBlueprint && (
|
|
2605
|
+
<button
|
|
2606
|
+
className="hud-button"
|
|
2607
|
+
type="button"
|
|
2608
|
+
aria-label="Clear blueprint"
|
|
2609
|
+
title="Remove every planned file, folder, and symbol"
|
|
2610
|
+
disabled={!blueprintHasContent}
|
|
2611
|
+
onClick={onClearBlueprint}
|
|
2612
|
+
>
|
|
2613
|
+
Clear blueprint
|
|
2614
|
+
</button>
|
|
2615
|
+
)}
|
|
2616
|
+
{onCleanupBlueprint && (
|
|
2617
|
+
<button
|
|
2618
|
+
className="hud-button"
|
|
2619
|
+
type="button"
|
|
2620
|
+
aria-label="Cleanup blueprint"
|
|
2621
|
+
title="Remove blueprint files and folders that already exist"
|
|
2622
|
+
disabled={!blueprintCanCleanup}
|
|
2623
|
+
onClick={onCleanupBlueprint}
|
|
2624
|
+
>
|
|
2625
|
+
Cleanup blueprint
|
|
2626
|
+
</button>
|
|
2627
|
+
)}
|
|
2292
2628
|
</div>
|
|
2293
2629
|
<div className="hud-icon-row">
|
|
2294
2630
|
{mapping && hasChangeSet && onToggleChangePathsOnly && (
|