@jkwd/inbase 0.1.9 → 0.1.11

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,6 +1,7 @@
1
1
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { Canvas } from '@react-three/fiber'
3
- import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint } from './agentIntent'
3
+ import { emptyIntent, fetchAgentIntent, fetchAgentIntents, inspectTargetFile, performAgentAction, persistSessionBlueprint, persistSessionFocus, setupVisualizerSession } from './agentIntent'
4
+ import { emptyBranchChanges, fetchBranchChanges } from './branchChanges'
4
5
  import { fetchCodebase, updateCodebase } from './codebase'
5
6
  import {
6
7
  layoutWorld,
@@ -16,6 +17,7 @@ import { HUD } from './ui/HUD'
16
17
  import {
17
18
  fetchUserContext,
18
19
  persistFollowLook,
20
+ persistShowBranchChanges,
19
21
  persistUserContext,
20
22
  } from './userContext'
21
23
  import {
@@ -34,6 +36,7 @@ import {
34
36
  } from './userCreated'
35
37
  import {
36
38
  isPatchPreview,
39
+ llmIsMakingChanges,
37
40
  type AgentIntent,
38
41
  type AimedRelation,
39
42
  type CodebaseGraph,
@@ -52,7 +55,13 @@ function intentSignature(intent: AgentIntent) {
52
55
  status: intent.status,
53
56
  phase: intent.phase,
54
57
  stalledWait: intent.stalledWait,
58
+ llmIdle: intent.llmIdle,
59
+ awaitingAttach: intent.awaitingAttach,
60
+ listening: intent.listening,
61
+ lastAck: intent.lastAck,
55
62
  sessionId: intent.sessionId,
63
+ name: intent.name,
64
+ feature: intent.feature,
56
65
  creationMode: intent.creationMode,
57
66
  diffId: intent.diffId,
58
67
  chain: intent.chain,
@@ -75,8 +84,15 @@ export default function App() {
75
84
  const [updatingModel, setUpdatingModel] = useState(false)
76
85
  const updatingModelRef = useRef(false)
77
86
 
87
+ const graphSig = useRef<string | null>(null)
78
88
  const applyGraph = useCallback((next: CodebaseGraph | null, failed: string) => {
79
89
  if (next) {
90
+ const signature = JSON.stringify(next)
91
+ if (signature === graphSig.current) {
92
+ setLoadError(null)
93
+ return true
94
+ }
95
+ graphSig.current = signature
80
96
  setGraph(next)
81
97
  setLoadError(null)
82
98
  return true
@@ -136,17 +152,27 @@ function Explorer({
136
152
  }) {
137
153
  const [intents, setIntents] = useState<AgentIntent[]>([])
138
154
  const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
155
+ const [wantBranchChanges, setWantBranchChanges] = useState(false)
156
+ const [branchChanges, setBranchChanges] = useState(emptyBranchChanges)
139
157
  const intent =
140
158
  intents.find((item) => item.sessionId === focusedSessionId) ??
141
159
  intents[0] ??
142
160
  emptyIntent
143
- const creationIntent =
144
- intents.find((item) => item.creationMode) ??
145
- intents.find((item) => item.status === 'blueprint') ??
146
- null
147
- const creationMode = Boolean(creationIntent?.creationMode)
148
- const previewing = intent.preview || isPatchPreview(intent.status)
149
- const plannedCreates = previewing ? intent.creates : []
161
+ const canPlace = Boolean(intent.sessionId && intent.creationMode)
162
+ const llmBusy = intents.some(llmIsMakingChanges)
163
+ const llmPreviewing = intent.preview || isPatchPreview(intent.status)
164
+ const showingBranchChanges =
165
+ wantBranchChanges &&
166
+ !llmBusy &&
167
+ !llmPreviewing &&
168
+ branchChanges.available
169
+ const changeSet = llmPreviewing
170
+ ? intent
171
+ : showingBranchChanges
172
+ ? branchChanges
173
+ : emptyIntent
174
+ const previewing = llmPreviewing || showingBranchChanges
175
+ const plannedCreates = previewing ? changeSet.creates : []
150
176
  const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
151
177
  const [userIslands, setUserIslands] = useState<UserCreatedIsland[]>([])
152
178
  const [blueprintFunctions, setBlueprintFunctions] = useState<
@@ -166,15 +192,15 @@ function Explorer({
166
192
  return withPreviewGraph(
167
193
  graph,
168
194
  plannedCreates,
169
- intent.createLines ?? {},
170
- intent.createFolders ?? [],
171
- intent.imports ?? [],
195
+ changeSet.createLines ?? {},
196
+ changeSet.createFolders ?? [],
197
+ changeSet.imports ?? [],
172
198
  )
173
199
  }, [
200
+ changeSet.createFolders,
201
+ changeSet.createLines,
202
+ changeSet.imports,
174
203
  graph,
175
- intent.createFolders,
176
- intent.createLines,
177
- intent.imports,
178
204
  plannedCreates,
179
205
  previewing,
180
206
  ])
@@ -197,14 +223,20 @@ function Explorer({
197
223
  )
198
224
  const layout = useMemo(() => {
199
225
  const world = layoutWorld(previewGraph)
200
- if (previewing) markCreatedFolders(world, intent.createFolders ?? [])
226
+ if (previewing) markCreatedFolders(world, changeSet.createFolders ?? [])
201
227
  return withUserCreatedLayout(world, userBlocks, userIslands)
202
- }, [intent.createFolders, previewGraph, previewing, userBlocks, userIslands])
228
+ }, [
229
+ changeSet.createFolders,
230
+ previewGraph,
231
+ previewing,
232
+ userBlocks,
233
+ userIslands,
234
+ ])
203
235
  const changeFileIds = useMemo(() => {
204
236
  const ids = new Set<string>()
205
- for (const id of intent.files) ids.add(id)
206
- for (const id of intent.creates) ids.add(id)
207
- for (const id of intent.deletes) ids.add(id)
237
+ for (const id of changeSet.files) ids.add(id)
238
+ for (const id of changeSet.creates) ids.add(id)
239
+ for (const id of changeSet.deletes) ids.add(id)
208
240
  for (const block of userBlocks) ids.add(block.id)
209
241
  for (const item of blueprintFunctions) ids.add(item.file)
210
242
  for (const item of blueprintVariables) ids.add(item.file)
@@ -214,20 +246,20 @@ function Explorer({
214
246
  blueprintFunctions,
215
247
  blueprintImports,
216
248
  blueprintVariables,
217
- intent.creates,
218
- intent.deletes,
219
- intent.files,
249
+ changeSet.creates,
250
+ changeSet.deletes,
251
+ changeSet.files,
220
252
  userBlocks,
221
253
  ])
222
254
  const changeFolderPaths = useMemo(() => {
223
255
  const paths = new Set<string>()
224
- for (const path of intent.createFolders ?? []) paths.add(path)
256
+ for (const path of changeSet.createFolders ?? []) paths.add(path)
225
257
  for (const island of userIslands) {
226
258
  if (island.path) paths.add(island.path)
227
259
  else if (island.id) paths.add(island.id)
228
260
  }
229
261
  return [...paths]
230
- }, [intent.createFolders, userIslands])
262
+ }, [changeSet.createFolders, userIslands])
231
263
  const hasChangeSet =
232
264
  changeFileIds.length > 0 || changeFolderPaths.length > 0
233
265
  const changePathGraph = useMemo(() => {
@@ -242,14 +274,14 @@ function Explorer({
242
274
  if (!hasChangeSet) return layout
243
275
  const world = layoutWorld(changePathGraph)
244
276
  markCreatedFolders(world, [
245
- ...(intent.createFolders ?? []),
277
+ ...(changeSet.createFolders ?? []),
246
278
  ...userIslands.map((island) => island.path || island.id),
247
279
  ])
248
280
  return world
249
281
  }, [
250
282
  changePathGraph,
283
+ changeSet.createFolders,
251
284
  hasChangeSet,
252
- intent.createFolders,
253
285
  layout,
254
286
  userIslands,
255
287
  ])
@@ -273,6 +305,8 @@ function Explorer({
273
305
  const lastIntentSig = useRef<string | null>(null)
274
306
  const viewedDiffId = useRef<Record<string, string | null>>({})
275
307
  const browsingHistory = useRef<Record<string, boolean>>({})
308
+ const loadedBlueprintSession = useRef<string | null>(null)
309
+ const seenSessionIds = useRef<Set<string>>(new Set())
276
310
 
277
311
  const applyIntent = useCallback((next: AgentIntent, sessionId?: string) => {
278
312
  const targetId = next.sessionId ?? sessionId ?? null
@@ -297,6 +331,19 @@ function Explorer({
297
331
  if (targetId && next.sessionId) viewedDiffId.current[targetId] = next.diffId
298
332
  }, [])
299
333
 
334
+ const focusSessionPanel = useCallback((sessionId: string) => {
335
+ setFocusedSessionId(sessionId)
336
+ persistSessionFocus(sessionId)
337
+ }, [])
338
+
339
+ const setupLlmSession = useCallback(async () => {
340
+ const next = await setupVisualizerSession()
341
+ lastIntentSig.current = null
342
+ applyIntent(next, next.sessionId ?? undefined)
343
+ if (next.sessionId) setFocusedSessionId(next.sessionId)
344
+ return next
345
+ }, [applyIntent])
346
+
300
347
  const rememberWalk = useCallback((x: number, z: number) => {
301
348
  walkPos.current = [x, z]
302
349
  }, [])
@@ -414,7 +461,6 @@ function Explorer({
414
461
  lastIntentSig.current = null
415
462
  applyIntent(next, sessionId)
416
463
  if (
417
- action === 'invoke' ||
418
464
  action === 'continue' ||
419
465
  action === 'stop' ||
420
466
  action === 'set_step_by_step'
@@ -505,6 +551,9 @@ function Explorer({
505
551
  if (typeof context?.followLook === 'boolean') {
506
552
  setFollowLook(context.followLook)
507
553
  }
554
+ if (typeof context?.showBranchChanges === 'boolean') {
555
+ setWantBranchChanges(context.showBranchChanges)
556
+ }
508
557
  })
509
558
  return () => {
510
559
  cancelled = true
@@ -512,7 +561,8 @@ function Explorer({
512
561
  }, [])
513
562
 
514
563
  useEffect(() => {
515
- if (!creationIntent?.sessionId) {
564
+ if (!intent.sessionId) {
565
+ loadedBlueprintSession.current = null
516
566
  setUserBlocks([])
517
567
  setUserIslands([])
518
568
  setBlueprintFunctions([])
@@ -522,13 +572,23 @@ function Explorer({
522
572
  }
523
573
  const knownFiles = new Set(graph.files.map((file) => file.id))
524
574
  const knownFolders = new Set(graph.folders.map((folder) => folder.path))
525
- const nextBlocks = parseUserCreatedBlocks(creationIntent.userCreatedBlocks).filter(
575
+ const nextBlocks = parseUserCreatedBlocks(intent.userCreatedBlocks).filter(
526
576
  (block) => !knownFiles.has(block.id),
527
577
  )
528
- const nextIslands = parseUserCreatedIslands(creationIntent.userCreatedIslands).filter(
578
+ const nextIslands = parseUserCreatedIslands(intent.userCreatedIslands).filter(
529
579
  (island) => !knownFolders.has(island.path),
530
580
  )
531
- if (creationIntent.creationMode) {
581
+ const switched = loadedBlueprintSession.current !== intent.sessionId
582
+ if (switched) {
583
+ loadedBlueprintSession.current = intent.sessionId
584
+ setUserBlocks(nextBlocks)
585
+ setUserIslands(nextIslands)
586
+ setBlueprintFunctions(intent.blueprintFunctions)
587
+ setBlueprintVariables(intent.blueprintVariables)
588
+ setBlueprintImports(intent.blueprintImports)
589
+ return
590
+ }
591
+ if (intent.creationMode) {
532
592
  setUserBlocks((current) =>
533
593
  current.some((block) => block.naming) || current.length > 0
534
594
  ? current
@@ -540,29 +600,29 @@ function Explorer({
540
600
  : nextIslands,
541
601
  )
542
602
  setBlueprintFunctions((current) =>
543
- current.length > 0 ? current : creationIntent.blueprintFunctions,
603
+ current.length > 0 ? current : intent.blueprintFunctions,
544
604
  )
545
605
  setBlueprintVariables((current) =>
546
- current.length > 0 ? current : creationIntent.blueprintVariables,
606
+ current.length > 0 ? current : intent.blueprintVariables,
547
607
  )
548
608
  setBlueprintImports((current) =>
549
- current.length > 0 ? current : creationIntent.blueprintImports,
609
+ current.length > 0 ? current : intent.blueprintImports,
550
610
  )
551
611
  return
552
612
  }
553
613
  setUserBlocks(nextBlocks)
554
614
  setUserIslands(nextIslands)
555
- setBlueprintFunctions(creationIntent.blueprintFunctions)
556
- setBlueprintVariables(creationIntent.blueprintVariables)
557
- setBlueprintImports(creationIntent.blueprintImports)
615
+ setBlueprintFunctions(intent.blueprintFunctions)
616
+ setBlueprintVariables(intent.blueprintVariables)
617
+ setBlueprintImports(intent.blueprintImports)
558
618
  }, [
559
- creationIntent?.blueprintFunctions,
560
- creationIntent?.blueprintImports,
561
- creationIntent?.blueprintVariables,
562
- creationIntent?.creationMode,
563
- creationIntent?.sessionId,
564
- creationIntent?.userCreatedBlocks,
565
- creationIntent?.userCreatedIslands,
619
+ intent.blueprintFunctions,
620
+ intent.blueprintImports,
621
+ intent.blueprintVariables,
622
+ intent.creationMode,
623
+ intent.sessionId,
624
+ intent.userCreatedBlocks,
625
+ intent.userCreatedIslands,
566
626
  graph,
567
627
  ])
568
628
 
@@ -574,8 +634,8 @@ function Explorer({
574
634
  variables: PatchSymbolAddition[] = blueprintVariables,
575
635
  imports: PatchImportAddition[] = blueprintImports,
576
636
  ) => {
577
- if (!creationIntent?.sessionId || !creationIntent.creationMode) return
578
- persistSessionBlueprint(creationIntent.sessionId, {
637
+ if (!intent.sessionId || !intent.creationMode) return
638
+ persistSessionBlueprint(intent.sessionId, {
579
639
  userCreatedBlocks: namedCreatedBlocks(blocks),
580
640
  userCreatedIslands: namedCreatedIslands(islands),
581
641
  addedFunctions: functions,
@@ -587,8 +647,8 @@ function Explorer({
587
647
  blueprintFunctions,
588
648
  blueprintImports,
589
649
  blueprintVariables,
590
- creationIntent?.creationMode,
591
- creationIntent?.sessionId,
650
+ intent.creationMode,
651
+ intent.sessionId,
592
652
  userBlocks,
593
653
  userIslands,
594
654
  ],
@@ -596,7 +656,7 @@ function Explorer({
596
656
 
597
657
  const placeBlock = useCallback(
598
658
  (spot: { x: number; z: number; folder: string }) => {
599
- if (!creationMode) return
659
+ if (!canPlace) return
600
660
  setUserBlocks((current) => {
601
661
  if (current.some((block) => block.naming)) return current
602
662
  return [
@@ -614,12 +674,12 @@ function Explorer({
614
674
  })
615
675
  document.exitPointerLock()
616
676
  },
617
- [creationMode],
677
+ [canPlace],
618
678
  )
619
679
 
620
680
  const placeBlockOnFolder = useCallback(
621
681
  (folderPath: string) => {
622
- if (!creationMode) return
682
+ if (!canPlace) return
623
683
  const fileCount = displayGraph.files.filter(
624
684
  (file) => file.folder === folderPath,
625
685
  ).length
@@ -627,7 +687,7 @@ function Explorer({
627
687
  if (!spot) return
628
688
  placeBlock(spot)
629
689
  },
630
- [displayGraph.files, creationMode, layout, placeBlock],
690
+ [displayGraph.files, canPlace, layout, placeBlock],
631
691
  )
632
692
 
633
693
  const commitBlockName = useCallback(
@@ -664,7 +724,7 @@ function Explorer({
664
724
 
665
725
  const placeIsland = useCallback(
666
726
  (parent: string) => {
667
- if (!creationMode) return
727
+ if (!canPlace) return
668
728
  setUserIslands((current) => {
669
729
  if (current.some((island) => island.naming)) return current
670
730
  return [
@@ -680,15 +740,15 @@ function Explorer({
680
740
  })
681
741
  document.exitPointerLock()
682
742
  },
683
- [creationMode],
743
+ [canPlace],
684
744
  )
685
745
 
686
746
  const placeIslandOnFolder = useCallback(
687
747
  (parent: string) => {
688
- if (!creationMode) return
748
+ if (!canPlace) return
689
749
  placeIsland(parent)
690
750
  },
691
- [creationMode, placeIsland],
751
+ [canPlace, placeIsland],
692
752
  )
693
753
 
694
754
  const commitIslandName = useCallback(
@@ -722,7 +782,7 @@ function Explorer({
722
782
  }, [])
723
783
 
724
784
  const deleteSelectedCreatedBlock = useCallback(() => {
725
- if (!creationMode || !selectedId) return false
785
+ if (!canPlace || !selectedId) return false
726
786
  const selected = userBlocks.find((block) => block.id === selectedId)
727
787
  if (!selected || selected.naming) return false
728
788
  const next = userBlocks.filter((block) => block.id !== selectedId)
@@ -730,7 +790,7 @@ function Explorer({
730
790
  persistBlueprint(next, userIslands)
731
791
  setSelectedId(null)
732
792
  return true
733
- }, [creationMode, persistBlueprint, selectedId, userBlocks, userIslands])
793
+ }, [canPlace, persistBlueprint, selectedId, userBlocks, userIslands])
734
794
 
735
795
  const selectFile = useCallback(
736
796
  (fileId: string | null) => {
@@ -739,9 +799,9 @@ function Explorer({
739
799
  setSelectedFolder(null)
740
800
  setSelectedTick((tick) => tick + 1)
741
801
  }
742
- if (fileId && creationMode) document.exitPointerLock()
802
+ if (fileId && canPlace) document.exitPointerLock()
743
803
  },
744
- [creationMode],
804
+ [canPlace],
745
805
  )
746
806
 
747
807
  const inspectBlock = useCallback(
@@ -779,7 +839,7 @@ function Explorer({
779
839
 
780
840
  const addBlueprintFunction = useCallback(
781
841
  (fileId: string, rawName: string) => {
782
- if (!creationMode || fileId.startsWith('draft:')) return false
842
+ if (!canPlace || fileId.startsWith('draft:')) return false
783
843
  const name = rawName.trim()
784
844
  if (!isBlueprintSymbolName(name)) return false
785
845
  const exists =
@@ -802,7 +862,7 @@ function Explorer({
802
862
  blueprintImports,
803
863
  blueprintVariables,
804
864
  displayGraph.files,
805
- creationMode,
865
+ canPlace,
806
866
  persistBlueprint,
807
867
  userBlocks,
808
868
  userIslands,
@@ -811,7 +871,7 @@ function Explorer({
811
871
 
812
872
  const addBlueprintVariable = useCallback(
813
873
  (fileId: string, rawName: string) => {
814
- if (!creationMode || fileId.startsWith('draft:')) return false
874
+ if (!canPlace || fileId.startsWith('draft:')) return false
815
875
  const name = rawName.trim()
816
876
  if (!isBlueprintSymbolName(name)) return false
817
877
  const exists =
@@ -834,7 +894,7 @@ function Explorer({
834
894
  blueprintImports,
835
895
  blueprintVariables,
836
896
  displayGraph.files,
837
- creationMode,
897
+ canPlace,
838
898
  persistBlueprint,
839
899
  userBlocks,
840
900
  userIslands,
@@ -843,7 +903,7 @@ function Explorer({
843
903
 
844
904
  const addBlueprintImport = useCallback(
845
905
  (fileId: string, raw: string) => {
846
- if (!creationMode || fileId.startsWith('draft:')) return false
906
+ if (!canPlace || fileId.startsWith('draft:')) return false
847
907
  const parsed = parseBlueprintImport(
848
908
  raw,
849
909
  fileId,
@@ -873,7 +933,7 @@ function Explorer({
873
933
  blueprintImports,
874
934
  blueprintVariables,
875
935
  displayGraph.files,
876
- creationMode,
936
+ canPlace,
877
937
  persistBlueprint,
878
938
  userBlocks,
879
939
  userIslands,
@@ -882,7 +942,7 @@ function Explorer({
882
942
 
883
943
  const removeBlueprintFunction = useCallback(
884
944
  (fileId: string, name: string) => {
885
- if (!creationMode) return
945
+ if (!canPlace) return
886
946
  const next = blueprintFunctions.filter(
887
947
  (item) => !(item.file === fileId && item.name === name),
888
948
  )
@@ -893,7 +953,7 @@ function Explorer({
893
953
  blueprintFunctions,
894
954
  blueprintImports,
895
955
  blueprintVariables,
896
- creationMode,
956
+ canPlace,
897
957
  persistBlueprint,
898
958
  userBlocks,
899
959
  userIslands,
@@ -902,7 +962,7 @@ function Explorer({
902
962
 
903
963
  const removeBlueprintVariable = useCallback(
904
964
  (fileId: string, name: string) => {
905
- if (!creationMode) return
965
+ if (!canPlace) return
906
966
  const next = blueprintVariables.filter(
907
967
  (item) => !(item.file === fileId && item.name === name),
908
968
  )
@@ -913,7 +973,7 @@ function Explorer({
913
973
  blueprintFunctions,
914
974
  blueprintImports,
915
975
  blueprintVariables,
916
- creationMode,
976
+ canPlace,
917
977
  persistBlueprint,
918
978
  userBlocks,
919
979
  userIslands,
@@ -922,7 +982,7 @@ function Explorer({
922
982
 
923
983
  const removeBlueprintImport = useCallback(
924
984
  (fileId: string, name: string, from: string) => {
925
- if (!creationMode) return
985
+ if (!canPlace) return
926
986
  const next = blueprintImports.filter(
927
987
  (item) =>
928
988
  !(item.file === fileId && item.name === name && item.from === from),
@@ -940,7 +1000,7 @@ function Explorer({
940
1000
  blueprintFunctions,
941
1001
  blueprintImports,
942
1002
  blueprintVariables,
943
- creationMode,
1003
+ canPlace,
944
1004
  persistBlueprint,
945
1005
  userBlocks,
946
1006
  userIslands,
@@ -987,6 +1047,18 @@ function Explorer({
987
1047
  })
988
1048
  }, [])
989
1049
 
1050
+ const canToggleBranchChanges = !llmBusy && (wantBranchChanges || branchChanges.available)
1051
+
1052
+ const toggleShowBranchChanges = useCallback(() => {
1053
+ if (llmBusy) return
1054
+ if (!wantBranchChanges && !branchChanges.available) return
1055
+ setWantBranchChanges((current) => {
1056
+ const next = !current
1057
+ persistShowBranchChanges(next)
1058
+ return next
1059
+ })
1060
+ }, [branchChanges.available, llmBusy, wantBranchChanges])
1061
+
990
1062
  const toggleImportedBy = useCallback(() => {
991
1063
  setImportedBy((current) => !current)
992
1064
  }, [])
@@ -1016,6 +1088,48 @@ function Explorer({
1016
1088
  return () => window.removeEventListener('keydown', onKey)
1017
1089
  }, [toggleImportedBy])
1018
1090
 
1091
+ useEffect(() => {
1092
+ const onKey = (event: KeyboardEvent) => {
1093
+ if (event.repeat || event.code !== 'KeyG') return
1094
+ const target = event.target
1095
+ if (
1096
+ target instanceof HTMLElement &&
1097
+ (target.tagName === 'TEXTAREA' ||
1098
+ target.tagName === 'INPUT' ||
1099
+ target.tagName === 'SELECT' ||
1100
+ target.isContentEditable)
1101
+ ) {
1102
+ return
1103
+ }
1104
+ if (!canToggleBranchChanges) return
1105
+ event.preventDefault()
1106
+ toggleShowBranchChanges()
1107
+ }
1108
+ window.addEventListener('keydown', onKey)
1109
+ return () => window.removeEventListener('keydown', onKey)
1110
+ }, [canToggleBranchChanges, toggleShowBranchChanges])
1111
+
1112
+ useEffect(() => {
1113
+ let cancelled = false
1114
+ const load = async () => {
1115
+ const next = await fetchBranchChanges()
1116
+ if (!cancelled) setBranchChanges(next)
1117
+ }
1118
+ void load()
1119
+ if (!wantBranchChanges || llmBusy) {
1120
+ return () => {
1121
+ cancelled = true
1122
+ }
1123
+ }
1124
+ const timer = window.setInterval(() => {
1125
+ void load()
1126
+ }, 2000)
1127
+ return () => {
1128
+ cancelled = true
1129
+ window.clearInterval(timer)
1130
+ }
1131
+ }, [llmBusy, updatingModel, wantBranchChanges])
1132
+
1019
1133
  useEffect(() => {
1020
1134
  if (mode !== 'map' || !hasChangeSet) return
1021
1135
  const onKey = (event: KeyboardEvent) => {
@@ -1068,15 +1182,24 @@ function Explorer({
1068
1182
  viewedDiffId.current[next.sessionId] = next.diffId
1069
1183
  }
1070
1184
  }
1185
+ const nextIds = new Set(
1186
+ merged
1187
+ .map((item) => item.sessionId)
1188
+ .filter((id): id is string => Boolean(id)),
1189
+ )
1190
+ const serverFocus = bundle.focusedSessionId
1191
+ const appeared =
1192
+ serverFocus != null && !seenSessionIds.current.has(serverFocus)
1193
+ seenSessionIds.current = nextIds
1071
1194
  setFocusedSessionId((current) => {
1072
- if (current && merged.some((item) => item.sessionId === current)) {
1195
+ if (appeared && serverFocus && nextIds.has(serverFocus)) {
1196
+ return serverFocus
1197
+ }
1198
+ if (current && nextIds.has(current)) {
1073
1199
  return current
1074
1200
  }
1075
- if (
1076
- bundle.focusedSessionId &&
1077
- merged.some((item) => item.sessionId === bundle.focusedSessionId)
1078
- ) {
1079
- return bundle.focusedSessionId
1201
+ if (serverFocus && nextIds.has(serverFocus)) {
1202
+ return serverFocus
1080
1203
  }
1081
1204
  return merged[0]?.sessionId ?? null
1082
1205
  })
@@ -1087,23 +1210,23 @@ function Explorer({
1087
1210
  void poll()
1088
1211
  const timer = window.setInterval(() => {
1089
1212
  void poll()
1090
- }, 700)
1213
+ }, 250)
1091
1214
  return () => {
1092
1215
  cancelled = true
1093
1216
  window.clearInterval(timer)
1094
1217
  }
1095
1218
  }, [])
1096
1219
 
1097
- const plannedIds = previewing ? [...intent.files, ...intent.creates] : []
1220
+ const plannedIds = previewing ? [...changeSet.files, ...changeSet.creates] : []
1098
1221
  const blueprintImportEdges = blueprintImports.flatMap((item) => {
1099
1222
  if (!displayGraph.files.some((file) => file.id === item.from)) return []
1100
1223
  return [{ from: item.file, to: item.from }]
1101
1224
  })
1102
1225
  const plannedImports = [
1103
- ...(previewing ? (intent.imports ?? []) : []),
1226
+ ...(previewing ? (changeSet.imports ?? []) : []),
1104
1227
  ...blueprintImportEdges,
1105
1228
  ]
1106
- const deletedIds = previewing ? intent.deletes : []
1229
+ const deletedIds = previewing ? changeSet.deletes : []
1107
1230
 
1108
1231
  return (
1109
1232
  <>
@@ -1138,7 +1261,7 @@ function Explorer({
1138
1261
  plannedImports={plannedImports}
1139
1262
  createdIds={plannedCreates}
1140
1263
  deletedIds={deletedIds}
1141
- createLines={intent.createLines ?? {}}
1264
+ createLines={changeSet.createLines ?? {}}
1142
1265
  flyTo={flyTo}
1143
1266
  aimedRelation={aimedRelation}
1144
1267
  onAimRelation={setAimedRelation}
@@ -1148,8 +1271,8 @@ function Explorer({
1148
1271
  importedBy={importedBy}
1149
1272
  namingId={namingId}
1150
1273
  namingIslandId={namingIslandId}
1151
- onPlaceBlock={creationMode ? placeBlock : undefined}
1152
- onPlaceIsland={creationMode ? placeIsland : undefined}
1274
+ onPlaceBlock={canPlace ? placeBlock : undefined}
1275
+ onPlaceIsland={canPlace ? placeIsland : undefined}
1153
1276
  onCommitName={commitBlockName}
1154
1277
  onCancelName={cancelBlockName}
1155
1278
  userCreatedBlocks={userBlocks}
@@ -1178,13 +1301,19 @@ function Explorer({
1178
1301
  intent={intent}
1179
1302
  intents={intents}
1180
1303
  focusedSessionId={focusedSessionId}
1181
- onFocusSession={setFocusedSessionId}
1304
+ onFocusSession={focusSessionPanel}
1305
+ onSetupSession={setupLlmSession}
1182
1306
  onWorkflowAction={runWorkflowAction}
1183
1307
  onNavigateDiff={navigateDiff}
1184
1308
  onOpenMap={openMap}
1185
1309
  onWalk={openWalk}
1186
1310
  followLook={followLook}
1187
1311
  onToggleFollowLook={toggleFollowLook}
1312
+ showBranchChanges={showingBranchChanges}
1313
+ branchChanges={branchChanges}
1314
+ canShowBranchChanges={canToggleBranchChanges}
1315
+ llmMakingChanges={llmBusy}
1316
+ onToggleShowBranchChanges={toggleShowBranchChanges}
1188
1317
  onUpdateModel={onUpdateModel}
1189
1318
  updatingModel={updatingModel}
1190
1319
  importedBy={importedBy}