@jkwd/inbase 0.1.1 → 0.1.4

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,4 +1,4 @@
1
- import { useEffect, useRef, useState } from 'react'
1
+ import { useEffect, useRef, useState, type ReactNode } from 'react'
2
2
  import { NameInput } from './NameInput'
3
3
  import {
4
4
  isReviewingIntent,
@@ -72,6 +72,25 @@ function PanelList({
72
72
  )
73
73
  }
74
74
 
75
+ function MutationFold({
76
+ hasContent,
77
+ children,
78
+ }: {
79
+ hasContent: boolean
80
+ children: ReactNode
81
+ }) {
82
+ if (!hasContent) return null
83
+ return (
84
+ <details
85
+ className="hud-fold"
86
+ onKeyDown={(event) => event.stopPropagation()}
87
+ >
88
+ <summary className="hud-section-title hud-fold-summary">Mutations</summary>
89
+ {children}
90
+ </details>
91
+ )
92
+ }
93
+
75
94
  function AddIntentRow({
76
95
  placeholder,
77
96
  onAdd,
@@ -109,6 +128,7 @@ type HUDProps = {
109
128
  mode: ViewMode
110
129
  locked: boolean
111
130
  selectedId: string | null
131
+ selectedTick?: number
112
132
  selectedFolder?: string | null
113
133
  aimedRelation: AimedRelation | null
114
134
  currentFolder: string
@@ -143,6 +163,7 @@ type HUDProps = {
143
163
  ) => void
144
164
  onMapAddFile?: (folderPath: string) => void
145
165
  onMapAddFolder?: (folderPath: string) => void
166
+ onInspectFile?: (fileId: string) => void
146
167
  }
147
168
 
148
169
  export function HUD({
@@ -150,6 +171,7 @@ export function HUD({
150
171
  mode,
151
172
  locked,
152
173
  selectedId,
174
+ selectedTick = 0,
153
175
  selectedFolder = null,
154
176
  aimedRelation,
155
177
  currentFolder,
@@ -177,6 +199,7 @@ export function HUD({
177
199
  onRemoveBlueprintImport,
178
200
  onMapAddFile,
179
201
  onMapAddFolder,
202
+ onInspectFile,
180
203
  }: HUDProps) {
181
204
  const selected = graph.files.find((file) => file.id === selectedId)
182
205
  const selectedFolderNode = graph.folders.find(
@@ -280,6 +303,11 @@ export function HUD({
280
303
  const canRunNext =
281
304
  Boolean(nextStep) && (planReady || pending) && !working
282
305
  const canComplete = pending && lastStep
306
+ const canInspectFile = (fileId: string, userCreated = false) =>
307
+ Boolean(onInspectFile) &&
308
+ !fileId.startsWith('draft:') &&
309
+ !(previewing && (intent.deletes ?? []).includes(fileId)) &&
310
+ (!userCreated || (intent.creates ?? []).includes(fileId))
283
311
  const panelDone =
284
312
  intent.status === 'finished' ||
285
313
  intent.status === 'approved' ||
@@ -313,8 +341,8 @@ export function HUD({
313
341
  }, [selectedId, selectedFolder])
314
342
 
315
343
  useEffect(() => {
316
- if (sessionMode && selectedId) setInfoVisible(true)
317
- }, [selectedId, sessionMode])
344
+ if (selectedId) setInfoVisible(true)
345
+ }, [selectedId, selectedTick])
318
346
 
319
347
  useEffect(() => {
320
348
  if (selectedFolder) setInfoVisible(true)
@@ -383,7 +411,7 @@ export function HUD({
383
411
  , <kbd>Space</kbd> place a file, <kbd>B</kbd> place an island
384
412
  </>
385
413
  ) : null}
386
- , click a block for imports.
414
+ , click a block for info.
387
415
  </p>
388
416
  </div>
389
417
  </div>
@@ -539,6 +567,7 @@ export function HUD({
539
567
  nextStep?.index === step.index && !doneSteps.has(step.index)
540
568
  }
541
569
  >
570
+ <span className="hud-step-index">{step.index}.</span>
542
571
  <span className="hud-step-main">
543
572
  <span className="hud-step-title">{step.title}</span>
544
573
  {((canRunNext && nextStep?.index === step.index) ||
@@ -587,81 +616,91 @@ export function HUD({
587
616
  </button>
588
617
  </div>
589
618
  )}
590
- {previewing && intent.files.length > 0 && (
591
- <>
592
- <div className="hud-section-title hud-section-title-edit">Changed</div>
593
- <ul>
594
- {intent.files.map((id) => (
595
- <li className="hud-file-edit" key={id}>
596
- {id}
597
- </li>
598
- ))}
599
- </ul>
600
- </>
601
- )}
602
- {previewing && (intent.createFolders ?? []).length > 0 && (
603
- <>
604
- <div className="hud-section-title hud-section-title-add">Added islands</div>
605
- <ul>
606
- {intent.createFolders.map((id) => (
607
- <li className="hud-file-add" key={id}>
608
- {id}/
609
- </li>
610
- ))}
611
- </ul>
612
- </>
613
- )}
614
- {previewing && intent.creates.length > 0 && (
615
- <>
616
- <div className="hud-section-title hud-section-title-add">Added</div>
617
- <ul>
618
- {intent.creates.map((id) => (
619
- <li className="hud-file-add" key={id}>
620
- {id}
621
- </li>
622
- ))}
623
- </ul>
624
- </>
625
- )}
626
- {previewing && intent.deletes.length > 0 && (
627
- <>
628
- <div className="hud-section-title hud-section-title-remove">Removed</div>
629
- <ul>
630
- {intent.deletes.map((id) => (
631
- <li className="hud-file-remove" key={id}>
632
- {id}
633
- </li>
634
- ))}
635
- </ul>
636
- </>
637
- )}
638
- {previewing && (
639
- <>
640
- <PanelList
641
- title="Functions"
642
- items={symbolLabels(addedFunctions)}
643
- />
644
- <PanelList
645
- title="Variables"
646
- items={symbolLabels(addedVariables)}
647
- />
648
- </>
649
- )}
650
- {previewing && addedImports.length > 0 && (
651
- <PanelList title="Imports" items={importLabels(addedImports)} />
652
- )}
653
- {previewing && addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
654
- <>
655
- <div className="hud-section-title">Imports</div>
656
- <ul>
657
- {intent.imports.map((edge) => (
658
- <li key={`${edge.from}->${edge.to}`}>
659
- {edge.from.split('/').pop()} {edge.to.split('/').pop()}
660
- </li>
661
- ))}
662
- </ul>
663
- </>
664
- )}
619
+ <MutationFold
620
+ hasContent={
621
+ previewing &&
622
+ (intent.files.length > 0 ||
623
+ (intent.createFolders ?? []).length > 0 ||
624
+ intent.creates.length > 0 ||
625
+ intent.deletes.length > 0 ||
626
+ addedFunctions.length > 0 ||
627
+ addedVariables.length > 0 ||
628
+ addedImports.length > 0 ||
629
+ (intent.imports ?? []).length > 0)
630
+ }
631
+ >
632
+ {intent.files.length > 0 && (
633
+ <>
634
+ <div className="hud-section-title hud-section-title-edit">Changed</div>
635
+ <ul>
636
+ {intent.files.map((id) => (
637
+ <li className="hud-file-edit" key={id}>
638
+ {id}
639
+ </li>
640
+ ))}
641
+ </ul>
642
+ </>
643
+ )}
644
+ {(intent.createFolders ?? []).length > 0 && (
645
+ <>
646
+ <div className="hud-section-title hud-section-title-add">Added islands</div>
647
+ <ul>
648
+ {intent.createFolders.map((id) => (
649
+ <li className="hud-file-add" key={id}>
650
+ {id}/
651
+ </li>
652
+ ))}
653
+ </ul>
654
+ </>
655
+ )}
656
+ {intent.creates.length > 0 && (
657
+ <>
658
+ <div className="hud-section-title hud-section-title-add">Added</div>
659
+ <ul>
660
+ {intent.creates.map((id) => (
661
+ <li className="hud-file-add" key={id}>
662
+ {id}
663
+ </li>
664
+ ))}
665
+ </ul>
666
+ </>
667
+ )}
668
+ {intent.deletes.length > 0 && (
669
+ <>
670
+ <div className="hud-section-title hud-section-title-remove">Removed</div>
671
+ <ul>
672
+ {intent.deletes.map((id) => (
673
+ <li className="hud-file-remove" key={id}>
674
+ {id}
675
+ </li>
676
+ ))}
677
+ </ul>
678
+ </>
679
+ )}
680
+ <PanelList
681
+ title="Functions"
682
+ items={symbolLabels(addedFunctions)}
683
+ />
684
+ <PanelList
685
+ title="Variables"
686
+ items={symbolLabels(addedVariables)}
687
+ />
688
+ {addedImports.length > 0 && (
689
+ <PanelList title="Imports" items={importLabels(addedImports)} />
690
+ )}
691
+ {addedImports.length === 0 && (intent.imports ?? []).length > 0 && (
692
+ <>
693
+ <div className="hud-section-title">Imports</div>
694
+ <ul>
695
+ {intent.imports.map((edge) => (
696
+ <li key={`${edge.from}->${edge.to}`}>
697
+ {edge.from.split('/').pop()} → {edge.to.split('/').pop()}
698
+ </li>
699
+ ))}
700
+ </ul>
701
+ </>
702
+ )}
703
+ </MutationFold>
665
704
  {(planReady || preparing) && (
666
705
  <div className="hud-decide">
667
706
  <button
@@ -727,6 +766,15 @@ export function HUD({
727
766
  <p>
728
767
  {selected.lines} lines · {selected.language}
729
768
  </p>
769
+ {canInspectFile(selected.id, selected.userCreated) && (
770
+ <button
771
+ className="hud-button hud-inspect"
772
+ type="button"
773
+ onClick={() => onInspectFile?.(selected.id)}
774
+ >
775
+ Inspect file
776
+ </button>
777
+ )}
730
778
  {selectedClasses.length > 0 && (
731
779
  <>
732
780
  <div className="hud-section-title">Classes</div>
@@ -925,7 +973,18 @@ export function HUD({
925
973
  ) : (
926
974
  <ul>
927
975
  {folderFiles.map((file) => (
928
- <li key={file.id}>{file.path || file.id}</li>
976
+ <li key={file.id}>
977
+ <span>{file.path || file.id}</span>
978
+ {canInspectFile(file.id, file.userCreated) && (
979
+ <button
980
+ className="hud-item-inspect"
981
+ type="button"
982
+ onClick={() => onInspectFile?.(file.id)}
983
+ >
984
+ Inspect
985
+ </button>
986
+ )}
987
+ </li>
929
988
  ))}
930
989
  </ul>
931
990
  )}
@@ -958,11 +1017,7 @@ export function HUD({
958
1017
  <>
959
1018
  <span>Scroll zoom</span>
960
1019
  <span>Drag pan</span>
961
- <span>
962
- {sessionMode
963
- ? 'Click a block for info'
964
- : 'Click a block for relations'}
965
- </span>
1020
+ <span>Click a block for info</span>
966
1021
  <span>Click an island for its files</span>
967
1022
  {creatingBlueprint && (
968
1023
  <>
@@ -971,19 +1026,11 @@ export function HUD({
971
1026
  </>
972
1027
  )}
973
1028
  <span>Click a line to fly there</span>
974
- <span>Double-click ground to walk</span>
1029
+ <span>Ctrl-click an island to walk</span>
975
1030
  {selected?.userCreated && creatingBlueprint && (
976
1031
  <span>Backspace delete</span>
977
1032
  )}
978
- <span>
979
- {sessionMode
980
- ? infoVisible
981
- ? 'I hide info'
982
- : 'Click a block for info'
983
- : infoVisible
984
- ? 'I hide info'
985
- : 'I show info'}
986
- </span>
1033
+ <span>{infoVisible ? 'I hide info' : 'I show info'}</span>
987
1034
  {infoVisible && <span>↑↓ scroll info</span>}
988
1035
  <span>
989
1036
  {importedBy ? 'K show imports' : 'K show imported by'}
@@ -1004,21 +1051,9 @@ export function HUD({
1004
1051
  {selected?.userCreated && creatingBlueprint && (
1005
1052
  <span>Backspace delete</span>
1006
1053
  )}
1007
- <span>
1008
- {sessionMode
1009
- ? 'Click a block for info'
1010
- : 'Click block for relations'}
1011
- </span>
1054
+ <span>Click a block for info</span>
1012
1055
  <span>Aim a line to fly</span>
1013
- <span>
1014
- {sessionMode
1015
- ? infoVisible
1016
- ? 'I hide info'
1017
- : 'Click a block for info'
1018
- : infoVisible
1019
- ? 'I hide info'
1020
- : 'I show info'}
1021
- </span>
1056
+ <span>{infoVisible ? 'I hide info' : 'I show info'}</span>
1022
1057
  {infoVisible && <span>↑↓ scroll info</span>}
1023
1058
  <span>
1024
1059
  {importedBy ? 'K show imports' : 'K show imported by'}
@@ -7,9 +7,11 @@ import { fileURLToPath } from 'node:url'
7
7
  import type { IncomingMessage, ServerResponse } from 'node:http'
8
8
  import { emptyIntent } from './scripts/patch-lib.mjs'
9
9
  import { dataDir, targetRoot } from './scripts/target-config.mjs'
10
+ import { editorFileUri, openInEditor } from './scripts/open-editor.mjs'
10
11
  import {
11
12
  answerBlueprint,
12
13
  continueDiff,
14
+ inspectTargetFile,
13
15
  invokeStep,
14
16
  readActiveSession,
15
17
  requestReplan,
@@ -105,10 +107,46 @@ function jsonFilePlugin(): Plugin {
105
107
 
106
108
  next()
107
109
  })
110
+
111
+ server.middlewares.use('/api/inspect-file', (req, res, next) => {
112
+ if (req.method === 'POST') {
113
+ void inspectFile(req, res)
114
+ return
115
+ }
116
+ next()
117
+ })
108
118
  },
109
119
  }
110
120
  }
111
121
 
122
+ async function inspectFile(req: IncomingMessage, res: ServerResponse) {
123
+ try {
124
+ const body = JSON.parse(await readBody(req)) as {
125
+ sessionId?: string
126
+ diffId?: string
127
+ fileId?: string
128
+ }
129
+ const filePath = inspectTargetFile(dataDir, targetRoot, {
130
+ sessionId: body.sessionId,
131
+ diffId: body.diffId,
132
+ fileId: body.fileId,
133
+ })
134
+ if (!filePath) {
135
+ sendJson(res, 200, { path: null, uri: null, opened: false })
136
+ return
137
+ }
138
+ const opened = openInEditor(filePath)
139
+ sendJson(res, 200, {
140
+ path: filePath,
141
+ uri: editorFileUri(filePath),
142
+ opened,
143
+ })
144
+ } catch (error) {
145
+ const message = error instanceof Error ? error.message : 'invalid request'
146
+ sendJson(res, 400, { error: message })
147
+ }
148
+ }
149
+
112
150
  async function decideIntent(req: IncomingMessage, res: ServerResponse) {
113
151
  try {
114
152
  const body = JSON.parse(await readBody(req)) as {
@@ -209,7 +247,7 @@ async function decideIntent(req: IncomingMessage, res: ServerResponse) {
209
247
  addedImports: body.addedImports,
210
248
  })
211
249
  } else {
212
- stopSession(dataDir, body.sessionId, body.diffId)
250
+ stopSession(dataDir, body.sessionId, targetRoot)
213
251
  }
214
252
  const next = sessionIntent(dataDir, body.sessionId, knownFileIds())
215
253
  sendJson(res, 200, next ?? { ...emptyIntent })
package/bin/session.mjs CHANGED
@@ -201,7 +201,7 @@ export async function proposePatch(args) {
201
201
 
202
202
  if (clear) {
203
203
  if (!sessionId) usage('propose-patch', '--session <cursor-chat-id> --clear')
204
- store.stopSession(config.dataDir, sessionId)
204
+ store.stopSession(config.dataDir, sessionId, config.targetRoot)
205
205
  console.log(
206
206
  `Cleared session ${sessionId}; stored diffs and blueprint drafts were removed.`,
207
207
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jkwd/inbase",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "description": "A first-person 3D map of a codebase, with a visual coding workflow for Cursor.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,6 +29,8 @@
29
29
  "apps/explorer/src/scene",
30
30
  "apps/explorer/src/ui",
31
31
  "apps/explorer/scripts/js-source.mjs",
32
+ "apps/explorer/scripts/open-editor.d.ts",
33
+ "apps/explorer/scripts/open-editor.mjs",
32
34
  "apps/explorer/scripts/patch-lib.d.ts",
33
35
  "apps/explorer/scripts/patch-lib.mjs",
34
36
  "apps/explorer/scripts/scan-target.mjs",