@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.
@@ -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. Optional.
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) || 'a newer session'
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
- Place files and folders for this chat, then send them as a
684
- blueprint for the LLM? You can still add files and islands on
685
- later steps.
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 newest waiting one',
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: 'ctrl-click-walk',
1283
- keys: ['Ctrl', 'Click'],
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 newest waiting one',
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 = Boolean(intent.creationMode)
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
- const target = event.target
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
- const target = event.target
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
- const target = event.target
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
- {namingIsland && onCommitIslandName && onCancelIslandName && (
1751
- <div className="hud-name-gate">
1752
- <NameInput
1753
- placeholder="Folder name"
1754
- onCommit={onCommitIslandName}
1755
- onCancel={onCancelIslandName}
1756
- />
1757
- </div>
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
- <li key={`fn-${symbol.name}`}>
1935
- <span
1936
- className={
1937
- symbolChangeClass(functionChange.get(symbol.name)) ??
1938
- (symbol.intended ? 'hud-intended' : undefined)
1939
- }
1940
- >
1941
- {symbol.name}
1942
- </span>
1943
- {canEditBlueprint && symbol.intended && (
1944
- <button
1945
- className="hud-item-remove"
1946
- type="button"
1947
- aria-label={`Remove ${symbol.name}`}
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
- </li>
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
- <li key={`fn-add-${item.name}`}>
1959
- <span className="hud-file-add">{item.name}</span>
1960
- </li>
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
- <li key={`var-${symbol.name}`}>
1979
- <span
1980
- className={
1981
- symbolChangeClass(variableChange.get(symbol.name)) ??
1982
- (symbol.intended ? 'hud-intended' : undefined)
1983
- }
1984
- >
1985
- {symbol.name}
1986
- </span>
1987
- {canEditBlueprint && symbol.intended && (
1988
- <button
1989
- className="hud-item-remove"
1990
- type="button"
1991
- aria-label={`Remove ${symbol.name}`}
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
- </li>
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
- <li key={`var-add-${item.name}`}>
2003
- <span className="hud-file-add">{item.name}</span>
2004
- </li>
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={() => setInstructionsOpen((open) => !open)}
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 && (