@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.
- package/README.md +6 -1
- package/apps/explorer/scripts/patch-lib.d.ts +13 -0
- package/apps/explorer/scripts/patch-lib.mjs +162 -25
- package/apps/explorer/scripts/session-store.d.ts +37 -0
- package/apps/explorer/scripts/session-store.mjs +415 -53
- package/apps/explorer/src/App.tsx +307 -88
- package/apps/explorer/src/agentIntent.ts +42 -1
- package/apps/explorer/src/index.css +312 -5
- package/apps/explorer/src/layout.ts +111 -1
- package/apps/explorer/src/scene/Bridge.tsx +23 -5
- package/apps/explorer/src/scene/FileBlock.tsx +113 -144
- package/apps/explorer/src/scene/FolderArea.tsx +35 -16
- package/apps/explorer/src/scene/MapSelectBorder.tsx +3 -1
- package/apps/explorer/src/scene/MapView.tsx +77 -22
- package/apps/explorer/src/scene/Player.tsx +36 -1
- package/apps/explorer/src/scene/RelationLines.tsx +36 -32
- package/apps/explorer/src/scene/SelectionController.tsx +57 -16
- package/apps/explorer/src/scene/SelectionThumbnail.tsx +895 -0
- package/apps/explorer/src/scene/World.tsx +42 -44
- package/apps/explorer/src/theme.ts +17 -4
- package/apps/explorer/src/types.ts +17 -0
- package/apps/explorer/src/ui/HUD.tsx +946 -385
- package/apps/explorer/vite.config.ts +44 -22
- package/bin/session.mjs +34 -8
- package/package.json +1 -1
- package/skill/inbase/SKILL.md +35 -22
|
@@ -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
|
|
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: #
|
|
239
|
-
border: 3px solid #
|
|
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-
|
|
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-
|
|
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-
|
|
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 {
|
|
4
|
-
import
|
|
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 = '
|
|
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={
|
|
269
|
+
color={
|
|
270
|
+
piece.color === LAND_COLOR
|
|
271
|
+
? landColorAt(piece.x, piece.z, folders)
|
|
272
|
+
: piece.color
|
|
273
|
+
}
|
|
256
274
|
/>
|
|
257
275
|
))}
|
|
258
276
|
|