@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.
- package/README.md +1 -1
- package/apps/explorer/scripts/patch-lib.d.ts +1 -0
- package/apps/explorer/scripts/patch-lib.mjs +1 -0
- package/apps/explorer/scripts/scan-ignore.mjs +9 -0
- package/apps/explorer/scripts/session-store.d.ts +3 -0
- package/apps/explorer/scripts/session-store.mjs +54 -2
- package/apps/explorer/src/App.tsx +119 -3
- package/apps/explorer/src/agentIntent.ts +10 -1
- package/apps/explorer/src/index.css +9 -0
- package/apps/explorer/src/scene/FileBlock.tsx +27 -0
- package/apps/explorer/src/scene/FolderArea.tsx +21 -0
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +66 -34
- package/apps/explorer/src/scene/World.tsx +22 -3
- package/apps/explorer/src/types.ts +10 -0
- package/apps/explorer/src/ui/CanvasErrorBoundary.tsx +2 -0
- package/apps/explorer/src/ui/EyeIcon.tsx +26 -0
- package/apps/explorer/src/ui/HUD.tsx +183 -24
- package/apps/explorer/src/ui/MapContextMenu.tsx +18 -1
- package/apps/explorer/src/userCreated.ts +97 -0
- package/apps/explorer/vite.config.ts +4 -0
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +17 -20
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
|
2
2
|
import { persistAddContextFiles, persistInitialInstruction, persistRemoveContextFile } from '../agentIntent'
|
|
3
3
|
import { NameInput } from './NameInput'
|
|
4
4
|
import { SelectionThumbnail } from '../scene/SelectionThumbnail'
|
|
5
|
+
import { CanvasErrorBoundary } from './CanvasErrorBoundary'
|
|
5
6
|
import {
|
|
6
7
|
canStopSession,
|
|
7
8
|
isReviewingIntent,
|
|
@@ -10,6 +11,8 @@ import {
|
|
|
10
11
|
type AimedRelation,
|
|
11
12
|
type BlueprintNote,
|
|
12
13
|
type BlueprintNoteKind,
|
|
14
|
+
type BlueprintPointer,
|
|
15
|
+
type BlueprintPointerKind,
|
|
13
16
|
type BranchChanges,
|
|
14
17
|
type CodebaseGraph,
|
|
15
18
|
type PatchImportAddition,
|
|
@@ -18,7 +21,8 @@ import {
|
|
|
18
21
|
type WorldLayout,
|
|
19
22
|
type WorkflowAction,
|
|
20
23
|
} from '../types'
|
|
21
|
-
import { findBlueprintNote } from '../userCreated'
|
|
24
|
+
import { findBlueprintNote, findBlueprintPointer } from '../userCreated'
|
|
25
|
+
import { EyeIcon } from './EyeIcon'
|
|
22
26
|
import { beginKeyboardIsolation, shouldIgnoreShortcut } from '../keyboard'
|
|
23
27
|
|
|
24
28
|
function reviewTitle(status: AgentIntentStatus) {
|
|
@@ -311,25 +315,43 @@ function BlueprintSymbolRow({
|
|
|
311
315
|
className,
|
|
312
316
|
hasNote,
|
|
313
317
|
noteOpen,
|
|
318
|
+
pointed,
|
|
314
319
|
canEdit,
|
|
315
320
|
canRemove,
|
|
316
321
|
onRemove,
|
|
317
322
|
onOpenNote,
|
|
323
|
+
onTogglePoint,
|
|
318
324
|
}: {
|
|
319
325
|
name: string
|
|
320
326
|
className?: string
|
|
321
327
|
hasNote: boolean
|
|
322
328
|
noteOpen?: boolean
|
|
329
|
+
pointed?: boolean
|
|
323
330
|
canEdit: boolean
|
|
324
331
|
canRemove?: boolean
|
|
325
332
|
onRemove?: () => void
|
|
326
333
|
onOpenNote: () => void
|
|
334
|
+
onTogglePoint?: () => void
|
|
327
335
|
}) {
|
|
328
336
|
return (
|
|
329
337
|
<li>
|
|
330
338
|
<span className={className}>{name}</span>
|
|
331
339
|
{canEdit && (
|
|
332
340
|
<div className="hud-item-actions">
|
|
341
|
+
{onTogglePoint && (
|
|
342
|
+
<button
|
|
343
|
+
className="hud-item-point"
|
|
344
|
+
type="button"
|
|
345
|
+
data-pointed={pointed ? 'true' : 'false'}
|
|
346
|
+
aria-label={
|
|
347
|
+
pointed ? `Stop pointing to ${name}` : `Point to ${name}`
|
|
348
|
+
}
|
|
349
|
+
aria-pressed={Boolean(pointed)}
|
|
350
|
+
onClick={onTogglePoint}
|
|
351
|
+
>
|
|
352
|
+
<EyeIcon size={13} />
|
|
353
|
+
</button>
|
|
354
|
+
)}
|
|
333
355
|
<button
|
|
334
356
|
className="hud-item-note"
|
|
335
357
|
type="button"
|
|
@@ -570,7 +592,8 @@ function blueprintIsDefined(intent: AgentIntent) {
|
|
|
570
592
|
(intent.blueprintFunctions?.length ?? 0) > 0 ||
|
|
571
593
|
(intent.blueprintVariables?.length ?? 0) > 0 ||
|
|
572
594
|
(intent.blueprintImports?.length ?? 0) > 0 ||
|
|
573
|
-
(intent.blueprintNotes?.length ?? 0) > 0
|
|
595
|
+
(intent.blueprintNotes?.length ?? 0) > 0 ||
|
|
596
|
+
(intent.blueprintPointers?.length ?? 0) > 0
|
|
574
597
|
)
|
|
575
598
|
}
|
|
576
599
|
|
|
@@ -680,6 +703,11 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
680
703
|
if (intent.awaitingAttach) {
|
|
681
704
|
return { text: 'Waiting for /inbase in Cursor', busy: false }
|
|
682
705
|
}
|
|
706
|
+
if (intent.status === 'pending') {
|
|
707
|
+
return intent.listening
|
|
708
|
+
? { text: 'LLM is listening — accept or send an instruction', busy: false }
|
|
709
|
+
: { text: 'Review this step', busy: false }
|
|
710
|
+
}
|
|
683
711
|
if (kind === 'execute') {
|
|
684
712
|
return { text: `LLM received ${detail}`, busy: true }
|
|
685
713
|
}
|
|
@@ -708,11 +736,6 @@ function sessionLiveStatus(intent: AgentIntent) {
|
|
|
708
736
|
? { text: 'LLM is listening — run the next step', busy: false }
|
|
709
737
|
: { text: 'Plan ready', busy: false }
|
|
710
738
|
}
|
|
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
739
|
if (
|
|
717
740
|
kind === 'attached' ||
|
|
718
741
|
intent.status === 'blueprint' ||
|
|
@@ -797,13 +820,16 @@ function SessionPanel({
|
|
|
797
820
|
const [contextBusy, setContextBusy] = useState(false)
|
|
798
821
|
const [contextError, setContextError] = useState<string | null>(null)
|
|
799
822
|
const sessionId = intent.sessionId
|
|
800
|
-
const
|
|
823
|
+
const latestEntry = intent.isActiveDiff ? intent.chain.at(-1) : null
|
|
824
|
+
const pending =
|
|
825
|
+
latestEntry?.status === 'pending' ||
|
|
826
|
+
(intent.status === 'pending' && intent.isActiveDiff)
|
|
801
827
|
const askingBlueprint = intent.status === 'blueprint_ask'
|
|
802
828
|
const sendingBlueprint = intent.status === 'blueprint'
|
|
803
829
|
const canPlace = Boolean(intent.creationMode)
|
|
804
830
|
const preparing = intent.status === 'preparing'
|
|
805
|
-
const planReady = intent.status === 'planned'
|
|
806
831
|
const working = intent.status === 'working' || intent.status === 'replanning'
|
|
832
|
+
const planReady = intent.status === 'planned' && !pending
|
|
807
833
|
const previewing = intent.preview
|
|
808
834
|
const chainIndex = intent.chainIndex ?? 0
|
|
809
835
|
const previousDiff = intent.chain[chainIndex - 1]
|
|
@@ -812,10 +838,6 @@ function SessionPanel({
|
|
|
812
838
|
intent.step && intent.steps?.length > 0
|
|
813
839
|
? `Step ${intent.step} of ${intent.steps.length}`
|
|
814
840
|
: 'Patch'
|
|
815
|
-
const lastStep =
|
|
816
|
-
typeof intent.step === 'number' &&
|
|
817
|
-
intent.steps.length > 0 &&
|
|
818
|
-
intent.step >= intent.steps.length
|
|
819
841
|
const stepByStep = intent.stepByStep !== false
|
|
820
842
|
const addedFunctions = intent.addedFunctions ?? []
|
|
821
843
|
const addedVariables = intent.addedVariables ?? []
|
|
@@ -826,25 +848,27 @@ function SessionPanel({
|
|
|
826
848
|
intent.status === 'finished'
|
|
827
849
|
? intent.steps.map((step) => step.index)
|
|
828
850
|
: intent.chain
|
|
829
|
-
.filter(
|
|
830
|
-
(entry) =>
|
|
831
|
-
entry.status === 'applied' || entry.status === 'extended',
|
|
832
|
-
)
|
|
851
|
+
.filter((entry) => entry.status === 'applied')
|
|
833
852
|
.map((entry) => entry.step),
|
|
834
853
|
)
|
|
835
854
|
if (intent.status === 'approved' && typeof intent.step === 'number') {
|
|
836
855
|
acceptedSteps.add(intent.step)
|
|
837
856
|
}
|
|
838
|
-
const proposalStep =
|
|
839
|
-
|
|
857
|
+
const proposalStep = pending
|
|
858
|
+
? (latestEntry?.status === 'pending' ? latestEntry.step : intent.step)
|
|
859
|
+
: null
|
|
840
860
|
const processingStep =
|
|
841
861
|
working && typeof intent.step === 'number' ? intent.step : null
|
|
842
862
|
const invokeStep =
|
|
843
|
-
planReady && !working
|
|
863
|
+
planReady && !working && proposalStep === null
|
|
844
864
|
? (intent.steps.find((step) => !acceptedSteps.has(step.index)) ?? null)
|
|
845
865
|
: null
|
|
846
866
|
const canRunNext = stepByStep && Boolean(invokeStep)
|
|
847
867
|
const canAcceptProposal = proposalStep !== null
|
|
868
|
+
const lastStep =
|
|
869
|
+
typeof proposalStep === 'number' &&
|
|
870
|
+
intent.steps.length > 0 &&
|
|
871
|
+
proposalStep >= intent.steps.length
|
|
848
872
|
const panelDone =
|
|
849
873
|
intent.status === 'finished' ||
|
|
850
874
|
intent.status === 'approved' ||
|
|
@@ -1071,7 +1095,7 @@ function SessionPanel({
|
|
|
1071
1095
|
{intent.steps.map((step) => {
|
|
1072
1096
|
const proposed = proposalStep === step.index
|
|
1073
1097
|
const processing = processingStep === step.index
|
|
1074
|
-
const accepted = acceptedSteps.has(step.index)
|
|
1098
|
+
const accepted = acceptedSteps.has(step.index) && !proposed
|
|
1075
1099
|
const creating = processing && !proposed
|
|
1076
1100
|
const showStepAction =
|
|
1077
1101
|
creating ||
|
|
@@ -1270,7 +1294,7 @@ function SessionPanel({
|
|
|
1270
1294
|
value={instruction}
|
|
1271
1295
|
maxLength={4000}
|
|
1272
1296
|
rows={3}
|
|
1273
|
-
placeholder="Describe what should change in
|
|
1297
|
+
placeholder="Describe what should change in this proposal…"
|
|
1274
1298
|
onChange={(event) => setInstruction(event.target.value)}
|
|
1275
1299
|
onKeyDown={(event) => event.stopPropagation()}
|
|
1276
1300
|
/>
|
|
@@ -1546,6 +1570,11 @@ function explorerInstructions({
|
|
|
1546
1570
|
? [
|
|
1547
1571
|
{ id: 'space', keys: ['Space'], label: 'Place file' },
|
|
1548
1572
|
{ id: 'b-island', keys: ['B'], label: 'Place island' },
|
|
1573
|
+
{
|
|
1574
|
+
id: 'point-to',
|
|
1575
|
+
keys: ['Point to'],
|
|
1576
|
+
label: 'Keep a file, folder, or function in mind',
|
|
1577
|
+
},
|
|
1549
1578
|
]
|
|
1550
1579
|
: []),
|
|
1551
1580
|
...backspace,
|
|
@@ -1629,7 +1658,7 @@ function explorerInstructions({
|
|
|
1629
1658
|
{
|
|
1630
1659
|
id: 'add-file-folder',
|
|
1631
1660
|
keys: ['Right-click'],
|
|
1632
|
-
label: 'Add file or folder',
|
|
1661
|
+
label: 'Add file or folder, or point to a folder',
|
|
1633
1662
|
},
|
|
1634
1663
|
]
|
|
1635
1664
|
: []),
|
|
@@ -1739,6 +1768,7 @@ type HUDProps = {
|
|
|
1739
1768
|
blueprintVariables?: PatchSymbolAddition[]
|
|
1740
1769
|
blueprintImports?: PatchImportAddition[]
|
|
1741
1770
|
blueprintNotes?: BlueprintNote[]
|
|
1771
|
+
blueprintPointers?: BlueprintPointer[]
|
|
1742
1772
|
onAddBlueprintFunction?: (fileId: string, name: string) => boolean
|
|
1743
1773
|
onAddBlueprintVariable?: (fileId: string, name: string) => boolean
|
|
1744
1774
|
onAddBlueprintImport?: (fileId: string, raw: string) => boolean
|
|
@@ -1755,6 +1785,11 @@ type HUDProps = {
|
|
|
1755
1785
|
name?: string
|
|
1756
1786
|
note: string
|
|
1757
1787
|
}) => void
|
|
1788
|
+
onToggleBlueprintPointer?: (next: {
|
|
1789
|
+
kind: BlueprintPointerKind
|
|
1790
|
+
path: string
|
|
1791
|
+
name?: string
|
|
1792
|
+
}) => void
|
|
1758
1793
|
onMapAddFile?: (folderPath: string) => void
|
|
1759
1794
|
onMapAddFolder?: (folderPath: string) => void
|
|
1760
1795
|
onInspectFile?: (fileId: string) => void
|
|
@@ -1814,6 +1849,7 @@ export function HUD({
|
|
|
1814
1849
|
blueprintVariables = [],
|
|
1815
1850
|
blueprintImports = [],
|
|
1816
1851
|
blueprintNotes = [],
|
|
1852
|
+
blueprintPointers = [],
|
|
1817
1853
|
onAddBlueprintFunction,
|
|
1818
1854
|
onAddBlueprintVariable,
|
|
1819
1855
|
onAddBlueprintImport,
|
|
@@ -1821,6 +1857,7 @@ export function HUD({
|
|
|
1821
1857
|
onRemoveBlueprintVariable,
|
|
1822
1858
|
onRemoveBlueprintImport,
|
|
1823
1859
|
onSetBlueprintNote,
|
|
1860
|
+
onToggleBlueprintPointer,
|
|
1824
1861
|
onMapAddFile,
|
|
1825
1862
|
onMapAddFolder,
|
|
1826
1863
|
onInspectFile,
|
|
@@ -1950,6 +1987,16 @@ export function HUD({
|
|
|
1950
1987
|
const selectedFileNote = selected
|
|
1951
1988
|
? findBlueprintNote(blueprintNotes, selected.id, 'file')
|
|
1952
1989
|
: ''
|
|
1990
|
+
const selectedFilePointed = selected
|
|
1991
|
+
? findBlueprintPointer(blueprintPointers, 'file', selected.id)
|
|
1992
|
+
: false
|
|
1993
|
+
const selectedFolderPointed = selectedFolderNode
|
|
1994
|
+
? findBlueprintPointer(
|
|
1995
|
+
blueprintPointers,
|
|
1996
|
+
'folder',
|
|
1997
|
+
selectedFolderNode.path,
|
|
1998
|
+
)
|
|
1999
|
+
: false
|
|
1953
2000
|
const openFileNote = () => {
|
|
1954
2001
|
if (!selected || !onSetBlueprintNote) return
|
|
1955
2002
|
setInstructionsOpen(false)
|
|
@@ -2282,6 +2329,20 @@ export function HUD({
|
|
|
2282
2329
|
Inspect file
|
|
2283
2330
|
</button>
|
|
2284
2331
|
)}
|
|
2332
|
+
{canEditBlueprint && onToggleBlueprintPointer && (
|
|
2333
|
+
<button
|
|
2334
|
+
className="hud-button hud-inspect hud-point"
|
|
2335
|
+
type="button"
|
|
2336
|
+
data-pointed={selectedFilePointed ? 'true' : 'false'}
|
|
2337
|
+
aria-pressed={selectedFilePointed}
|
|
2338
|
+
onClick={() =>
|
|
2339
|
+
onToggleBlueprintPointer({ kind: 'file', path: selected.id })
|
|
2340
|
+
}
|
|
2341
|
+
>
|
|
2342
|
+
<EyeIcon size={15} />
|
|
2343
|
+
{selectedFilePointed ? 'Stop pointing' : 'Point to file'}
|
|
2344
|
+
</button>
|
|
2345
|
+
)}
|
|
2285
2346
|
{canEditBlueprint && onSetBlueprintNote && (
|
|
2286
2347
|
<button
|
|
2287
2348
|
className="hud-button hud-inspect"
|
|
@@ -2373,10 +2434,26 @@ export function HUD({
|
|
|
2373
2434
|
}
|
|
2374
2435
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2375
2436
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2437
|
+
pointed={findBlueprintPointer(
|
|
2438
|
+
blueprintPointers,
|
|
2439
|
+
'function',
|
|
2440
|
+
selected.id,
|
|
2441
|
+
symbol.name,
|
|
2442
|
+
)}
|
|
2376
2443
|
onRemove={() =>
|
|
2377
2444
|
onRemoveBlueprintFunction?.(selected.id, symbol.name)
|
|
2378
2445
|
}
|
|
2379
2446
|
onOpenNote={() => openSymbolNote('function', symbol.name)}
|
|
2447
|
+
onTogglePoint={
|
|
2448
|
+
onToggleBlueprintPointer
|
|
2449
|
+
? () =>
|
|
2450
|
+
onToggleBlueprintPointer({
|
|
2451
|
+
kind: 'function',
|
|
2452
|
+
path: selected.id,
|
|
2453
|
+
name: symbol.name,
|
|
2454
|
+
})
|
|
2455
|
+
: undefined
|
|
2456
|
+
}
|
|
2380
2457
|
/>
|
|
2381
2458
|
))}
|
|
2382
2459
|
{extraAddedFunctions.map((item) => (
|
|
@@ -2398,7 +2475,23 @@ export function HUD({
|
|
|
2398
2475
|
noteEditor.name === item.name
|
|
2399
2476
|
}
|
|
2400
2477
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2478
|
+
pointed={findBlueprintPointer(
|
|
2479
|
+
blueprintPointers,
|
|
2480
|
+
'function',
|
|
2481
|
+
selected.id,
|
|
2482
|
+
item.name,
|
|
2483
|
+
)}
|
|
2401
2484
|
onOpenNote={() => openSymbolNote('function', item.name)}
|
|
2485
|
+
onTogglePoint={
|
|
2486
|
+
onToggleBlueprintPointer
|
|
2487
|
+
? () =>
|
|
2488
|
+
onToggleBlueprintPointer({
|
|
2489
|
+
kind: 'function',
|
|
2490
|
+
path: selected.id,
|
|
2491
|
+
name: item.name,
|
|
2492
|
+
})
|
|
2493
|
+
: undefined
|
|
2494
|
+
}
|
|
2402
2495
|
/>
|
|
2403
2496
|
))}
|
|
2404
2497
|
</ul>
|
|
@@ -2439,10 +2532,26 @@ export function HUD({
|
|
|
2439
2532
|
}
|
|
2440
2533
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2441
2534
|
canRemove={Boolean(canEditBlueprint && symbol.intended)}
|
|
2535
|
+
pointed={findBlueprintPointer(
|
|
2536
|
+
blueprintPointers,
|
|
2537
|
+
'variable',
|
|
2538
|
+
selected.id,
|
|
2539
|
+
symbol.name,
|
|
2540
|
+
)}
|
|
2442
2541
|
onRemove={() =>
|
|
2443
2542
|
onRemoveBlueprintVariable?.(selected.id, symbol.name)
|
|
2444
2543
|
}
|
|
2445
2544
|
onOpenNote={() => openSymbolNote('variable', symbol.name)}
|
|
2545
|
+
onTogglePoint={
|
|
2546
|
+
onToggleBlueprintPointer
|
|
2547
|
+
? () =>
|
|
2548
|
+
onToggleBlueprintPointer({
|
|
2549
|
+
kind: 'variable',
|
|
2550
|
+
path: selected.id,
|
|
2551
|
+
name: symbol.name,
|
|
2552
|
+
})
|
|
2553
|
+
: undefined
|
|
2554
|
+
}
|
|
2446
2555
|
/>
|
|
2447
2556
|
))}
|
|
2448
2557
|
{extraAddedVariables.map((item) => (
|
|
@@ -2464,7 +2573,23 @@ export function HUD({
|
|
|
2464
2573
|
noteEditor.name === item.name
|
|
2465
2574
|
}
|
|
2466
2575
|
canEdit={Boolean(canEditBlueprint && onSetBlueprintNote)}
|
|
2576
|
+
pointed={findBlueprintPointer(
|
|
2577
|
+
blueprintPointers,
|
|
2578
|
+
'variable',
|
|
2579
|
+
selected.id,
|
|
2580
|
+
item.name,
|
|
2581
|
+
)}
|
|
2467
2582
|
onOpenNote={() => openSymbolNote('variable', item.name)}
|
|
2583
|
+
onTogglePoint={
|
|
2584
|
+
onToggleBlueprintPointer
|
|
2585
|
+
? () =>
|
|
2586
|
+
onToggleBlueprintPointer({
|
|
2587
|
+
kind: 'variable',
|
|
2588
|
+
path: selected.id,
|
|
2589
|
+
name: item.name,
|
|
2590
|
+
})
|
|
2591
|
+
: undefined
|
|
2592
|
+
}
|
|
2468
2593
|
/>
|
|
2469
2594
|
))}
|
|
2470
2595
|
</ul>
|
|
@@ -2608,6 +2733,24 @@ export function HUD({
|
|
|
2608
2733
|
))}
|
|
2609
2734
|
</ul>
|
|
2610
2735
|
)}
|
|
2736
|
+
{canPlace && onToggleBlueprintPointer && (
|
|
2737
|
+
<button
|
|
2738
|
+
className="hud-button hud-inspect hud-point"
|
|
2739
|
+
type="button"
|
|
2740
|
+
data-pointed={selectedFolderPointed ? 'true' : 'false'}
|
|
2741
|
+
aria-pressed={selectedFolderPointed}
|
|
2742
|
+
disabled={naming || selectedFolderNode.path.startsWith('draft:')}
|
|
2743
|
+
onClick={() =>
|
|
2744
|
+
onToggleBlueprintPointer({
|
|
2745
|
+
kind: 'folder',
|
|
2746
|
+
path: selectedFolderNode.path,
|
|
2747
|
+
})
|
|
2748
|
+
}
|
|
2749
|
+
>
|
|
2750
|
+
<EyeIcon size={15} />
|
|
2751
|
+
{selectedFolderPointed ? 'Stop pointing' : 'Point to folder'}
|
|
2752
|
+
</button>
|
|
2753
|
+
)}
|
|
2611
2754
|
{canPlace && mapping && onMapAddFile && onMapAddFolder && (
|
|
2612
2755
|
<div className="hud-decide hud-map-blueprint">
|
|
2613
2756
|
<button
|
|
@@ -2634,7 +2777,8 @@ export function HUD({
|
|
|
2634
2777
|
)}
|
|
2635
2778
|
|
|
2636
2779
|
{mapping && thumbnailVisible && (
|
|
2637
|
-
<
|
|
2780
|
+
<CanvasErrorBoundary fallback={null}>
|
|
2781
|
+
<SelectionThumbnail
|
|
2638
2782
|
graph={graph}
|
|
2639
2783
|
layout={layout}
|
|
2640
2784
|
selectedId={selectedId}
|
|
@@ -2646,6 +2790,20 @@ export function HUD({
|
|
|
2646
2790
|
plannedIds={plannedIds}
|
|
2647
2791
|
createdIds={createdIds}
|
|
2648
2792
|
deletedIds={deletedIds}
|
|
2793
|
+
pointedFileIds={
|
|
2794
|
+
blueprintHidden
|
|
2795
|
+
? []
|
|
2796
|
+
: blueprintPointers.flatMap((item) =>
|
|
2797
|
+
item.kind === 'folder' ? [] : [item.path],
|
|
2798
|
+
)
|
|
2799
|
+
}
|
|
2800
|
+
pointedFolderPaths={
|
|
2801
|
+
blueprintHidden
|
|
2802
|
+
? []
|
|
2803
|
+
: blueprintPointers.flatMap((item) =>
|
|
2804
|
+
item.kind === 'folder' ? [item.path] : [],
|
|
2805
|
+
)
|
|
2806
|
+
}
|
|
2649
2807
|
onMinimize={() => {
|
|
2650
2808
|
setThumbnailMaximized(false)
|
|
2651
2809
|
setThumbnailMinimized((current) => !current)
|
|
@@ -2660,6 +2818,7 @@ export function HUD({
|
|
|
2660
2818
|
setThumbnailMaximized(false)
|
|
2661
2819
|
}}
|
|
2662
2820
|
/>
|
|
2821
|
+
</CanvasErrorBoundary>
|
|
2663
2822
|
)}
|
|
2664
2823
|
</div>
|
|
2665
2824
|
|
|
@@ -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
|
)
|
|
@@ -3,6 +3,8 @@ import { folderOfFile, folderParent } from './layout'
|
|
|
3
3
|
import type {
|
|
4
4
|
BlueprintNote,
|
|
5
5
|
BlueprintNoteKind,
|
|
6
|
+
BlueprintPointer,
|
|
7
|
+
BlueprintPointerKind,
|
|
6
8
|
CodebaseGraph,
|
|
7
9
|
FileNode,
|
|
8
10
|
PatchImportAddition,
|
|
@@ -437,6 +439,101 @@ export function dropBlueprintSymbolNote(
|
|
|
437
439
|
)
|
|
438
440
|
}
|
|
439
441
|
|
|
442
|
+
export function blueprintPointerKey(
|
|
443
|
+
pointer: Pick<BlueprintPointer, 'kind' | 'path' | 'name'>,
|
|
444
|
+
) {
|
|
445
|
+
return pointer.kind === 'file' || pointer.kind === 'folder'
|
|
446
|
+
? `${pointer.kind}:${pointer.path}`
|
|
447
|
+
: `${pointer.kind}:${pointer.path}:${pointer.name ?? ''}`
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function parseBlueprintPointer(value: unknown): BlueprintPointer | null {
|
|
451
|
+
if (!value || typeof value !== 'object') return null
|
|
452
|
+
const item = value as Partial<BlueprintPointer>
|
|
453
|
+
const path = typeof item.path === 'string' ? item.path.trim() : ''
|
|
454
|
+
if (!path) return null
|
|
455
|
+
if (item.kind === 'file' || item.kind === 'folder') {
|
|
456
|
+
return { kind: item.kind, path }
|
|
457
|
+
}
|
|
458
|
+
if (item.kind !== 'function' && item.kind !== 'variable') return null
|
|
459
|
+
const name = typeof item.name === 'string' ? item.name.trim() : ''
|
|
460
|
+
if (!name) return null
|
|
461
|
+
return { kind: item.kind, path, name }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export function parseBlueprintPointers(value: unknown): BlueprintPointer[] {
|
|
465
|
+
if (!Array.isArray(value)) return []
|
|
466
|
+
const seen = new Set<string>()
|
|
467
|
+
const pointers: BlueprintPointer[] = []
|
|
468
|
+
for (const item of value) {
|
|
469
|
+
const parsed = parseBlueprintPointer(item)
|
|
470
|
+
if (!parsed) continue
|
|
471
|
+
const key = blueprintPointerKey(parsed)
|
|
472
|
+
if (seen.has(key)) continue
|
|
473
|
+
seen.add(key)
|
|
474
|
+
pointers.push(parsed)
|
|
475
|
+
}
|
|
476
|
+
return pointers
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export function findBlueprintPointer(
|
|
480
|
+
pointers: BlueprintPointer[],
|
|
481
|
+
kind: BlueprintPointerKind,
|
|
482
|
+
path: string,
|
|
483
|
+
name?: string,
|
|
484
|
+
) {
|
|
485
|
+
return pointers.some((item) =>
|
|
486
|
+
kind === 'file' || kind === 'folder'
|
|
487
|
+
? item.kind === kind && item.path === path
|
|
488
|
+
: item.kind === kind && item.path === path && item.name === name,
|
|
489
|
+
)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function toggleBlueprintPointer(
|
|
493
|
+
pointers: BlueprintPointer[],
|
|
494
|
+
next: {
|
|
495
|
+
kind: BlueprintPointerKind
|
|
496
|
+
path: string
|
|
497
|
+
name?: string
|
|
498
|
+
},
|
|
499
|
+
): BlueprintPointer[] {
|
|
500
|
+
const path = next.path.trim()
|
|
501
|
+
if (!path || path.startsWith('draft:')) return pointers
|
|
502
|
+
const key = blueprintPointerKey({ ...next, path })
|
|
503
|
+
const without = pointers.filter((item) => blueprintPointerKey(item) !== key)
|
|
504
|
+
if (without.length !== pointers.length) return without
|
|
505
|
+
if (next.kind === 'file' || next.kind === 'folder') {
|
|
506
|
+
return [...without, { kind: next.kind, path }]
|
|
507
|
+
}
|
|
508
|
+
const name = (next.name ?? '').trim()
|
|
509
|
+
if (!name) return without
|
|
510
|
+
return [...without, { kind: next.kind, path, name }]
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function dropBlueprintFilePointers(
|
|
514
|
+
pointers: BlueprintPointer[],
|
|
515
|
+
fileIds: Iterable<string>,
|
|
516
|
+
folderPaths: Iterable<string> = [],
|
|
517
|
+
) {
|
|
518
|
+
const files = new Set(fileIds)
|
|
519
|
+
const folders = new Set(folderPaths)
|
|
520
|
+
return pointers.filter((item) =>
|
|
521
|
+
item.kind === 'folder' ? !folders.has(item.path) : !files.has(item.path),
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function dropBlueprintSymbolPointer(
|
|
526
|
+
pointers: BlueprintPointer[],
|
|
527
|
+
path: string,
|
|
528
|
+
kind: 'function' | 'variable',
|
|
529
|
+
name: string,
|
|
530
|
+
) {
|
|
531
|
+
return pointers.filter(
|
|
532
|
+
(item) =>
|
|
533
|
+
!(item.kind === kind && item.path === path && item.name === name),
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
|
|
440
537
|
export function parseBlueprintImport(
|
|
441
538
|
raw: string,
|
|
442
539
|
file: string,
|
|
@@ -114,6 +114,7 @@ function blueprintIntentFields() {
|
|
|
114
114
|
blueprintVariables: blueprint.addedVariables,
|
|
115
115
|
blueprintImports: blueprint.addedImports,
|
|
116
116
|
blueprintNotes: blueprint.notes,
|
|
117
|
+
blueprintPointers: blueprint.pointers,
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
@@ -267,6 +268,7 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
267
268
|
addedVariables?: unknown[]
|
|
268
269
|
addedImports?: unknown[]
|
|
269
270
|
notes?: unknown[]
|
|
271
|
+
pointers?: unknown[]
|
|
270
272
|
files?: unknown[]
|
|
271
273
|
fileId?: string
|
|
272
274
|
}
|
|
@@ -360,6 +362,7 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
360
362
|
addedVariables: body.addedVariables,
|
|
361
363
|
addedImports: body.addedImports,
|
|
362
364
|
notes: body.notes,
|
|
365
|
+
pointers: body.pointers,
|
|
363
366
|
})
|
|
364
367
|
} else if (action === 'blueprint_clear') {
|
|
365
368
|
clearBlueprint(dataDir)
|
|
@@ -375,6 +378,7 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
|
|
|
375
378
|
addedVariables: body.addedVariables,
|
|
376
379
|
addedImports: body.addedImports,
|
|
377
380
|
notes: body.notes,
|
|
381
|
+
pointers: body.pointers,
|
|
378
382
|
})
|
|
379
383
|
} else if (action === 'setup_session') {
|
|
380
384
|
const manifest = setupSession(dataDir, {
|