@jkwd/inbase 0.1.4 → 0.1.6

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,5 +1,6 @@
1
1
  import type {
2
2
  AgentIntent,
3
+ AgentIntentBundle,
3
4
  PatchImport,
4
5
  PatchImportAddition,
5
6
  PatchSymbolAddition,
@@ -51,6 +52,7 @@ export const emptyIntent: AgentIntent = {
51
52
  feature: null,
52
53
  steps: [],
53
54
  step: null,
55
+ stepByStep: true,
54
56
  files: [],
55
57
  creates: [],
56
58
  deletes: [],
@@ -60,6 +62,8 @@ export const emptyIntent: AgentIntent = {
60
62
  addedFunctions: [],
61
63
  addedVariables: [],
62
64
  addedImports: [],
65
+ changedFunctions: [],
66
+ changedVariables: [],
63
67
  reason: null,
64
68
  sessionId: null,
65
69
  diffId: null,
@@ -70,6 +74,7 @@ export const emptyIntent: AgentIntent = {
70
74
  preview: false,
71
75
  phase: null,
72
76
  working: false,
77
+ stalledWait: false,
73
78
  creationMode: false,
74
79
  canEnterBlueprint: false,
75
80
  blueprintSessionId: null,
@@ -88,6 +93,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
88
93
  feature: data?.feature ?? null,
89
94
  steps: Array.isArray(data?.steps) ? data.steps : [],
90
95
  step: typeof data?.step === 'number' ? data.step : null,
96
+ stepByStep: data?.stepByStep !== false,
91
97
  files: Array.isArray(data?.files) ? data.files : [],
92
98
  creates: Array.isArray(data?.creates) ? data.creates : [],
93
99
  deletes: Array.isArray(data?.deletes) ? data.deletes : [],
@@ -100,6 +106,8 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
100
106
  addedFunctions: normalizeSymbolAdditions(data?.addedFunctions),
101
107
  addedVariables: normalizeSymbolAdditions(data?.addedVariables),
102
108
  addedImports: normalizeImportAdditions(data?.addedImports),
109
+ changedFunctions: normalizeSymbolAdditions(data?.changedFunctions),
110
+ changedVariables: normalizeSymbolAdditions(data?.changedVariables),
103
111
  reason: data?.reason ?? null,
104
112
  sessionId: data?.sessionId ?? null,
105
113
  diffId: data?.diffId ?? null,
@@ -110,6 +118,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
110
118
  preview: Boolean(data?.preview),
111
119
  phase: data?.phase ?? null,
112
120
  working: Boolean(data?.working),
121
+ stalledWait: Boolean(data?.stalledWait),
113
122
  creationMode: Boolean(data?.creationMode),
114
123
  canEnterBlueprint: Boolean(data?.canEnterBlueprint),
115
124
  blueprintSessionId:
@@ -124,8 +133,39 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
124
133
  }
125
134
  }
126
135
 
127
- export async function fetchAgentIntent(diffId?: string): Promise<AgentIntent> {
136
+ export async function fetchAgentIntents(): Promise<AgentIntentBundle> {
128
137
  const query = new URLSearchParams({ t: String(Date.now()) })
138
+ const response = await fetch(`/api/agent-intent?${query}`)
139
+ if (!response.ok) return { focusedSessionId: null, intents: [] }
140
+ const data = (await response.json()) as {
141
+ focusedSessionId?: string | null
142
+ intents?: unknown
143
+ sessionId?: string | null
144
+ } & Partial<AgentIntent>
145
+ if (Array.isArray(data.intents)) {
146
+ return {
147
+ focusedSessionId:
148
+ typeof data.focusedSessionId === 'string' ? data.focusedSessionId : null,
149
+ intents: data.intents
150
+ .map((intent) => normalize(intent as Partial<AgentIntent>))
151
+ .filter((intent) => Boolean(intent.sessionId)),
152
+ }
153
+ }
154
+ const intent = normalize(data)
155
+ return {
156
+ focusedSessionId: intent.sessionId,
157
+ intents: intent.sessionId ? [intent] : [],
158
+ }
159
+ }
160
+
161
+ export async function fetchAgentIntent(
162
+ sessionId: string,
163
+ diffId?: string,
164
+ ): Promise<AgentIntent> {
165
+ const query = new URLSearchParams({
166
+ t: String(Date.now()),
167
+ sessionId,
168
+ })
129
169
  if (diffId) query.set('diffId', diffId)
130
170
  const response = await fetch(`/api/agent-intent?${query}`)
131
171
  if (!response.ok) return emptyIntent
@@ -139,6 +179,7 @@ export async function performAgentAction(
139
179
  diffId?: string
140
180
  instruction?: string
141
181
  step?: number
182
+ stepByStep?: boolean
142
183
  userCreatedBlocks?: UserCreatedBlock[]
143
184
  userCreatedIslands?: UserCreatedIsland[]
144
185
  addedFunctions?: PatchSymbolAddition[]
@@ -235,8 +235,8 @@ button {
235
235
  }
236
236
 
237
237
  .map-folder-label-selected {
238
- color: #f1f5fa;
239
- border: 3px solid #000000;
238
+ color: #f3e8ff;
239
+ border: 3px solid #c026ff;
240
240
  background: rgba(25, 28, 34, 0.94);
241
241
  }
242
242
 
@@ -324,6 +324,308 @@ button {
324
324
  pointer-events: auto;
325
325
  }
326
326
 
327
+ .hud-right-stack {
328
+ position: absolute;
329
+ top: 68px;
330
+ right: 20px;
331
+ bottom: 72px;
332
+ z-index: 5;
333
+ display: flex;
334
+ flex-direction: column;
335
+ gap: 12px;
336
+ width: min(320px, calc(100% - 40px));
337
+ pointer-events: none;
338
+ }
339
+
340
+ .hud-right-stack .hud-panel-info {
341
+ position: relative;
342
+ top: auto;
343
+ right: auto;
344
+ z-index: auto;
345
+ display: flex;
346
+ flex: 0 1 auto;
347
+ flex-direction: column;
348
+ width: 100%;
349
+ min-height: 0;
350
+ max-height: 100%;
351
+ overflow: hidden;
352
+ }
353
+
354
+ .hud-right-stack .hud-panel-info[data-minimized='true'] {
355
+ flex: 0 0 auto;
356
+ }
357
+
358
+ .hud-left-stack {
359
+ position: absolute;
360
+ top: 68px;
361
+ left: 20px;
362
+ bottom: 72px;
363
+ z-index: 5;
364
+ display: flex;
365
+ flex-direction: column;
366
+ gap: 12px;
367
+ width: min(360px, calc(100% - 40px));
368
+ pointer-events: none;
369
+ overflow-x: hidden;
370
+ overflow-y: auto;
371
+ }
372
+
373
+ .hud-left-stack .hud-panel-planned {
374
+ position: relative;
375
+ top: auto;
376
+ left: auto;
377
+ right: auto;
378
+ z-index: auto;
379
+ display: flex;
380
+ flex: 1 1 0;
381
+ flex-direction: column;
382
+ width: 100%;
383
+ min-height: 0;
384
+ max-height: 100%;
385
+ overflow-x: hidden;
386
+ overflow-y: auto;
387
+ }
388
+
389
+ .hud-left-stack .hud-panel-planned[data-minimized='true'] {
390
+ flex: 0 0 auto;
391
+ }
392
+
393
+ .hud-left-stack .hud-panel-planned[data-focused='false'] {
394
+ opacity: 0.86;
395
+ }
396
+
397
+ .hud-left-stack .hud-panel-planned[data-focused='true'] {
398
+ box-shadow: 0 0 0 1px #9ad8ff;
399
+ }
400
+
401
+ .hud-panel-body {
402
+ min-height: 0;
403
+ flex: 1 1 auto;
404
+ overflow-x: hidden;
405
+ overflow-y: auto;
406
+ overscroll-behavior: contain;
407
+ }
408
+
409
+ .hud-right-stack .hud-thumbnail {
410
+ position: relative;
411
+ right: auto;
412
+ bottom: auto;
413
+ left: auto;
414
+ z-index: auto;
415
+ width: 100%;
416
+ margin-top: auto;
417
+ flex: none;
418
+ }
419
+
420
+ .hud-thumbnail {
421
+ position: fixed;
422
+ right: 20px;
423
+ bottom: 72px;
424
+ left: auto;
425
+ z-index: 30;
426
+ width: min(320px, calc(100% - 40px));
427
+ pointer-events: auto;
428
+ overflow: hidden;
429
+ background: rgba(25, 28, 34, 0.96);
430
+ border: 1px solid #5d9ec4;
431
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
432
+ }
433
+
434
+ .hud-thumbnail-bar {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ gap: 8px;
439
+ padding: 4px 6px 4px 12px;
440
+ color: #e7ebf2;
441
+ font-size: 12px;
442
+ letter-spacing: 0.02em;
443
+ }
444
+
445
+ .hud-panel-chrome {
446
+ display: flex;
447
+ align-items: center;
448
+ justify-content: space-between;
449
+ gap: 8px;
450
+ margin: -4px 0 8px;
451
+ }
452
+
453
+ .hud-panel[data-minimized='true'] {
454
+ overflow: hidden;
455
+ padding-bottom: 12px;
456
+ }
457
+
458
+ .hud-panel[data-minimized='true'] .hud-panel-chrome {
459
+ margin-bottom: 0;
460
+ }
461
+
462
+ .hud-panel-chrome-title {
463
+ min-width: 0;
464
+ overflow: hidden;
465
+ color: #b7c0ce;
466
+ font-size: 11px;
467
+ font-weight: 600;
468
+ letter-spacing: 0.08em;
469
+ text-transform: uppercase;
470
+ text-overflow: ellipsis;
471
+ white-space: nowrap;
472
+ }
473
+
474
+ .hud-mode-switch {
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: space-between;
478
+ gap: 10px;
479
+ margin: 0 0 10px;
480
+ color: #b7c0ce;
481
+ font-size: 12px;
482
+ }
483
+
484
+ .hud-mode-hint {
485
+ margin: -4px 0 10px;
486
+ color: #8b95a5;
487
+ font-size: 11px;
488
+ line-height: 1.4;
489
+ }
490
+
491
+ .hud-switch {
492
+ position: relative;
493
+ flex: none;
494
+ width: 32px;
495
+ height: 18px;
496
+ padding: 0;
497
+ cursor: pointer;
498
+ pointer-events: auto;
499
+ border-radius: 999px;
500
+ background: #2a313c;
501
+ border: 1px solid #3a4250;
502
+ }
503
+
504
+ .hud-switch::after {
505
+ content: '';
506
+ position: absolute;
507
+ top: 2px;
508
+ left: 2px;
509
+ width: 12px;
510
+ height: 12px;
511
+ border-radius: 50%;
512
+ background: #8b95a5;
513
+ transition: transform 0.12s ease;
514
+ }
515
+
516
+ .hud-switch[aria-checked='true'] {
517
+ background: #1d4a3a;
518
+ border-color: #3dff78;
519
+ }
520
+
521
+ .hud-switch[aria-checked='true']::after {
522
+ transform: translateX(14px);
523
+ background: #3dff78;
524
+ }
525
+
526
+ .hud-panel-info .hud-panel-chrome-title {
527
+ color: #e7ebf2;
528
+ font-size: 16px;
529
+ letter-spacing: 0.02em;
530
+ text-transform: none;
531
+ }
532
+
533
+ .hud-panel-controls {
534
+ display: flex;
535
+ flex: none;
536
+ gap: 4px;
537
+ }
538
+
539
+ .hud-panel-control {
540
+ width: 28px;
541
+ height: 28px;
542
+ padding: 0;
543
+ font-size: 16px;
544
+ line-height: 1;
545
+ }
546
+
547
+ .hud-thumbnail-hide {
548
+ width: 28px;
549
+ height: 28px;
550
+ font-size: 16px;
551
+ line-height: 1;
552
+ }
553
+
554
+ .hud-thumbnail-stage {
555
+ position: relative;
556
+ width: 100%;
557
+ height: 220px;
558
+ background: #000;
559
+ touch-action: none;
560
+ user-select: none;
561
+ cursor: grab;
562
+ }
563
+
564
+ .hud-thumbnail-stage[data-panning='true'] {
565
+ cursor: grabbing;
566
+ }
567
+
568
+ .hud-thumbnail-stage canvas {
569
+ display: block;
570
+ }
571
+
572
+ .hud-thumbnail-hint {
573
+ position: absolute;
574
+ left: 8px;
575
+ bottom: 6px;
576
+ z-index: 2;
577
+ pointer-events: none;
578
+ color: rgba(231, 235, 242, 0.55);
579
+ font-size: 9px;
580
+ letter-spacing: 0.02em;
581
+ }
582
+
583
+ .hud-thumbnail-nav {
584
+ position: absolute;
585
+ right: 6px;
586
+ bottom: 6px;
587
+ z-index: 2;
588
+ display: flex;
589
+ gap: 4px;
590
+ }
591
+
592
+ .hud-thumbnail-nav .hud-button {
593
+ width: 24px;
594
+ height: 24px;
595
+ padding: 0;
596
+ color: #e7ebf2;
597
+ font-size: 14px;
598
+ line-height: 1;
599
+ background: rgba(18, 20, 24, 0.88);
600
+ }
601
+
602
+ .thumbnail-block-label,
603
+ .thumbnail-folder-label {
604
+ pointer-events: none;
605
+ user-select: none;
606
+ overflow: hidden;
607
+ max-width: 72px;
608
+ color: #ffffff;
609
+ font-weight: 600;
610
+ letter-spacing: 0.01em;
611
+ line-height: 1.15;
612
+ text-overflow: ellipsis;
613
+ white-space: nowrap;
614
+ text-shadow:
615
+ 0 0 3px #000,
616
+ 0 1px 2px #000,
617
+ 0 0 8px rgba(0, 0, 0, 0.85);
618
+ }
619
+
620
+ .thumbnail-block-label {
621
+ font-size: 9px;
622
+ }
623
+
624
+ .thumbnail-folder-label {
625
+ font-size: 10px;
626
+ max-width: 96px;
627
+ }
628
+
327
629
  .hud-panel-planned {
328
630
  left: 20px;
329
631
  right: auto;
@@ -452,12 +754,17 @@ button {
452
754
  .hud-working {
453
755
  display: flex;
454
756
  align-items: center;
757
+ flex-wrap: wrap;
455
758
  gap: 9px;
456
759
  margin-top: 12px;
457
760
  color: #d7eef8;
458
761
  font-size: 12px;
459
762
  }
460
763
 
764
+ .hud-working .hud-button {
765
+ margin-left: auto;
766
+ }
767
+
461
768
  .hud-spinner {
462
769
  width: 14px;
463
770
  height: 14px;
@@ -655,19 +962,19 @@ button {
655
962
  }
656
963
 
657
964
  .hud-file-add,
658
- .hud-panel-planned .hud-section-title.hud-section-title-add {
965
+ .hud-section-title.hud-section-title-add {
659
966
  color: #3dff78;
660
967
  font-weight: 600;
661
968
  }
662
969
 
663
970
  .hud-file-edit,
664
- .hud-panel-planned .hud-section-title.hud-section-title-edit {
971
+ .hud-section-title.hud-section-title-edit {
665
972
  color: #5cb8ff;
666
973
  font-weight: 600;
667
974
  }
668
975
 
669
976
  .hud-file-remove,
670
- .hud-panel-planned .hud-section-title.hud-section-title-remove {
977
+ .hud-section-title.hud-section-title-remove {
671
978
  color: #ff5a72;
672
979
  font-weight: 600;
673
980
  }
@@ -1,4 +1,5 @@
1
1
  import { CONFIG, fileHeight } from './theme'
2
+ import type { ChangeKind } from './theme'
2
3
  import type {
3
4
  CodebaseGraph,
4
5
  FileNode,
@@ -169,6 +170,51 @@ export function folderParent(folderPath: string) {
169
170
  return folderPath.includes('/') ? folderPath.split('/').slice(0, -1).join('/') : '.'
170
171
  }
171
172
 
173
+ export function changeRelatedFolderPaths(
174
+ graph: CodebaseGraph,
175
+ fileIds: Iterable<string>,
176
+ extraFolders: Iterable<string> = [],
177
+ ) {
178
+ const root = graph.root || '.'
179
+ const paths = new Set<string>([root])
180
+ const filesById = new Map(graph.files.map((file) => [file.id, file]))
181
+
182
+ const addAncestors = (start: string | null | undefined) => {
183
+ let current = start
184
+ while (current) {
185
+ paths.add(current)
186
+ current = folderParent(current)
187
+ }
188
+ }
189
+
190
+ for (const id of fileIds) {
191
+ const file = filesById.get(id)
192
+ addAncestors(file?.folder ?? folderOfFile(id))
193
+ }
194
+ for (const folder of extraFolders) addAncestors(folder)
195
+ return paths
196
+ }
197
+
198
+ export function filterGraphToChangePaths(
199
+ graph: CodebaseGraph,
200
+ fileIds: Iterable<string>,
201
+ extraFolders: Iterable<string> = [],
202
+ ): CodebaseGraph {
203
+ const keepFiles = new Set(fileIds)
204
+ const keepFolders = changeRelatedFolderPaths(graph, fileIds, extraFolders)
205
+ return {
206
+ ...graph,
207
+ files: graph.files.filter((file) => keepFiles.has(file.id)),
208
+ folders: graph.folders
209
+ .filter((folder) => keepFolders.has(folder.path))
210
+ .map((folder) => ({
211
+ ...folder,
212
+ files: folder.files.filter((id) => keepFiles.has(id)),
213
+ children: folder.children.filter((path) => keepFolders.has(path)),
214
+ })),
215
+ }
216
+ }
217
+
172
218
  function folderName(folderPath: string, rootName: string) {
173
219
  if (!folderPath || folderPath === '.') return rootName
174
220
  return folderPath.split('/').pop() ?? folderPath
@@ -433,7 +479,7 @@ export function layoutWorld(graph: CodebaseGraph): WorldLayout {
433
479
  export function folderAt(
434
480
  x: number,
435
481
  z: number,
436
- layout: WorldLayout,
482
+ layout: Pick<WorldLayout, 'folders'>,
437
483
  ): PlacedFolder | null {
438
484
  let current: PlacedFolder | null = null
439
485
  for (const folder of Object.values(layout.folders)) {
@@ -444,6 +490,25 @@ export function folderAt(
444
490
  return current
445
491
  }
446
492
 
493
+ export function mapPointOntoFolder(
494
+ x: number,
495
+ z: number,
496
+ fromLayout: Pick<WorldLayout, 'folders'>,
497
+ toLayout: Pick<WorldLayout, 'folders'>,
498
+ fallback: [number, number] | null = null,
499
+ ): [number, number] | null {
500
+ const folder = folderAt(x, z, fromLayout)
501
+ if (!folder) return fallback
502
+ const next = toLayout.folders[folder.path]
503
+ if (!next) return fallback
504
+ const relX = folder.width ? (x - folder.x) / folder.width : 0
505
+ const relZ = folder.depth ? (z - folder.z) / folder.depth : 0
506
+ return [
507
+ next.x + Math.min(Math.max(relX, -0.45), 0.45) * next.width,
508
+ next.z + Math.min(Math.max(relZ, 0), 1) * next.depth,
509
+ ]
510
+ }
511
+
447
512
  function distanceToSegment(
448
513
  px: number,
449
514
  pz: number,
@@ -506,3 +571,48 @@ export function worldBounds(layout: WorldLayout) {
506
571
  depth: maxZ - minZ,
507
572
  }
508
573
  }
574
+
575
+ export function fileChangeKind(
576
+ id: string,
577
+ planned: Set<string>,
578
+ created: Set<string>,
579
+ deleted: Set<string>,
580
+ ): ChangeKind | null {
581
+ if (deleted.has(id)) return 'remove'
582
+ if (created.has(id)) return 'add'
583
+ if (planned.has(id)) return 'edit'
584
+ return null
585
+ }
586
+
587
+ export function folderChangeHighlights(
588
+ planned: Set<string>,
589
+ created: Set<string>,
590
+ deleted: Set<string>,
591
+ folders: Record<string, PlacedFolder>,
592
+ ): Partial<Record<string, ChangeKind>> {
593
+ const highlighted: Partial<Record<string, ChangeKind>> = {}
594
+ const folderKinds = new Map<string, Set<ChangeKind>>()
595
+ const addFolderKind = (id: string, kind: ChangeKind) => {
596
+ const folder = folderOfFile(id)
597
+ const kinds = folderKinds.get(folder) ?? new Set<ChangeKind>()
598
+ kinds.add(kind)
599
+ folderKinds.set(folder, kinds)
600
+ }
601
+ for (const id of planned) {
602
+ addFolderKind(id, created.has(id) ? 'add' : 'edit')
603
+ }
604
+ for (const id of deleted) addFolderKind(id, 'remove')
605
+ for (const [folder, kinds] of folderKinds) {
606
+ if (kinds.size === 1) highlighted[folder] = [...kinds][0]
607
+ }
608
+ if (planned.size > 0 || deleted.size > 0) {
609
+ for (const folder of Object.values(folders)) {
610
+ if (!folder.added) continue
611
+ const kinds = folderKinds.get(folder.path)
612
+ if (!kinds || kinds.size === 1) {
613
+ highlighted[folder.path] ??= 'add'
614
+ }
615
+ }
616
+ }
617
+ return highlighted
618
+ }
@@ -1,10 +1,11 @@
1
1
  import { Suspense } from 'react'
2
2
  import { Text } from '@react-three/drei'
3
- import { CONFIG } from '../theme'
4
- import type { PlacedBridge } from '../types'
3
+ import { folderAt } from '../layout'
4
+ import { CONFIG, folderAisleColor } from '../theme'
5
+ import type { PlacedBridge, PlacedFolder } from '../types'
5
6
 
6
7
  const WALK_Y = 0.02
7
- const LAND_COLOR = '#2a3340'
8
+ const LAND_COLOR = 'hsl(210, 6%, 40%)'
8
9
  const SPAN_COLOR = '#232e3a'
9
10
  const CORNER_SIZE = CONFIG.bridgeWidth
10
11
 
@@ -22,6 +23,19 @@ const SIGN_COLOR = '#323a46'
22
23
 
23
24
  type BridgeProps = {
24
25
  bridge: PlacedBridge
26
+ folders?: Record<string, PlacedFolder>
27
+ }
28
+
29
+ function landColorAt(
30
+ x: number,
31
+ z: number,
32
+ folders: Record<string, PlacedFolder> | undefined,
33
+ ) {
34
+ if (!folders) return LAND_COLOR
35
+ const folder = folderAt(x, z, { folders })
36
+ if (!folder) return LAND_COLOR
37
+ if (folder.added) return '#23485a'
38
+ return folderAisleColor(folder.path)
25
39
  }
26
40
 
27
41
  type Segment = {
@@ -233,7 +247,7 @@ function Gate({
233
247
  )
234
248
  }
235
249
 
236
- export function Bridge({ bridge }: BridgeProps) {
250
+ export function Bridge({ bridge, folders }: BridgeProps) {
237
251
  const segments = segmentsFromPoints(bridge.points)
238
252
  const corners = cornersFromPoints(bridge.points)
239
253
  const start = bridge.points[0]
@@ -252,7 +266,11 @@ export function Bridge({ bridge }: BridgeProps) {
252
266
  z={piece.z}
253
267
  width={piece.width}
254
268
  depth={piece.depth}
255
- color={piece.color}
269
+ color={
270
+ piece.color === LAND_COLOR
271
+ ? landColorAt(piece.x, piece.z, folders)
272
+ : piece.color
273
+ }
256
274
  />
257
275
  ))}
258
276