@jkwd/inbase 0.1.10 → 0.1.12

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.
@@ -251,6 +251,19 @@ button {
251
251
  background: rgba(25, 28, 34, 0.94);
252
252
  }
253
253
 
254
+ .map-change-mark {
255
+ pointer-events: none;
256
+ user-select: none;
257
+ color: #f4f7fb;
258
+ font-size: 32px;
259
+ font-weight: 800;
260
+ line-height: 1;
261
+ letter-spacing: 0;
262
+ -webkit-text-stroke: 0.09em #11151c;
263
+ paint-order: stroke fill;
264
+ text-shadow: 0 0 0.12em #11151c;
265
+ }
266
+
254
267
  .map-you-are-here {
255
268
  display: grid;
256
269
  justify-items: center;
@@ -443,6 +456,11 @@ button {
443
456
  flex: none;
444
457
  }
445
458
 
459
+ .hud-icon-button[aria-disabled='true'] {
460
+ opacity: 0.55;
461
+ cursor: default;
462
+ }
463
+
446
464
  .hud-tooltip {
447
465
  position: absolute;
448
466
  right: 0;
@@ -642,6 +660,11 @@ button {
642
660
  margin: -4px 0 8px;
643
661
  }
644
662
 
663
+ .hud-panel-chrome-heading {
664
+ min-width: 0;
665
+ flex: 1 1 auto;
666
+ }
667
+
645
668
  .hud-panel[data-minimized='true'] {
646
669
  overflow: hidden;
647
670
  padding-bottom: 12px;
@@ -663,6 +686,24 @@ button {
663
686
  white-space: nowrap;
664
687
  }
665
688
 
689
+ .hud-panel-planned .hud-panel-chrome-title {
690
+ color: #e7ebf2;
691
+ font-size: 14px;
692
+ letter-spacing: 0.02em;
693
+ text-transform: none;
694
+ }
695
+
696
+ .hud-panel-chrome-subtitle {
697
+ overflow: hidden;
698
+ color: #8b95a5;
699
+ font-size: 10px;
700
+ font-weight: 600;
701
+ letter-spacing: 0.08em;
702
+ text-transform: uppercase;
703
+ text-overflow: ellipsis;
704
+ white-space: nowrap;
705
+ }
706
+
666
707
  .hud-mode-switch {
667
708
  display: flex;
668
709
  align-items: center;
@@ -940,6 +981,52 @@ button {
940
981
  font-size: 11px;
941
982
  }
942
983
 
984
+ .hud-setup {
985
+ display: grid;
986
+ gap: 16px;
987
+ margin-top: 12px;
988
+ }
989
+
990
+ .hud-setup-heading {
991
+ display: flex;
992
+ align-items: center;
993
+ flex-wrap: wrap;
994
+ gap: 8px;
995
+ margin: 0 0 8px;
996
+ color: #8b95a5;
997
+ font-size: 11px;
998
+ font-weight: 600;
999
+ letter-spacing: 0.08em;
1000
+ text-transform: uppercase;
1001
+ }
1002
+
1003
+ .hud-setup-tag {
1004
+ padding: 2px 6px;
1005
+ border: 1px solid #3a4250;
1006
+ border-radius: 3px;
1007
+ color: #8b95a5;
1008
+ font-size: 10px;
1009
+ font-weight: 600;
1010
+ letter-spacing: 0.04em;
1011
+ text-transform: none;
1012
+ }
1013
+
1014
+ .hud-setup-tag[data-ready='true'] {
1015
+ border-color: #5d9ec4;
1016
+ color: #d7eef8;
1017
+ }
1018
+
1019
+ .hud-setup-section p {
1020
+ margin: 0;
1021
+ color: #b7c0ce;
1022
+ font-size: 12px;
1023
+ line-height: 1.45;
1024
+ }
1025
+
1026
+ .hud-setup-section .hud-instruction {
1027
+ margin-top: 0;
1028
+ }
1029
+
943
1030
  .hud-instruction textarea {
944
1031
  width: 100%;
945
1032
  resize: vertical;
@@ -965,6 +1052,43 @@ button {
965
1052
  font-size: 12px;
966
1053
  }
967
1054
 
1055
+ .hud-live {
1056
+ display: flex;
1057
+ align-items: center;
1058
+ flex-wrap: wrap;
1059
+ gap: 9px;
1060
+ margin: 2px 0 10px;
1061
+ padding: 8px 10px;
1062
+ border-radius: 8px;
1063
+ background: rgba(154, 216, 255, 0.08);
1064
+ border: 1px solid rgba(154, 216, 255, 0.22);
1065
+ color: #d7eef8;
1066
+ font-size: 12px;
1067
+ }
1068
+
1069
+ .hud-live[data-busy='false'] {
1070
+ background: rgba(255, 255, 255, 0.04);
1071
+ border-color: rgba(255, 255, 255, 0.12);
1072
+ color: #c5d0d8;
1073
+ }
1074
+
1075
+ .hud-live[data-flash='true'] {
1076
+ animation: hud-live-flash 0.65s ease;
1077
+ }
1078
+
1079
+ .hud-live .hud-button {
1080
+ margin-left: auto;
1081
+ }
1082
+
1083
+ @keyframes hud-live-flash {
1084
+ from {
1085
+ background: rgba(154, 216, 255, 0.32);
1086
+ }
1087
+ to {
1088
+ background: rgba(154, 216, 255, 0.08);
1089
+ }
1090
+ }
1091
+
968
1092
  .hud-working .hud-button {
969
1093
  margin-left: auto;
970
1094
  }
@@ -988,6 +1112,35 @@ button {
988
1112
  margin-top: 14px;
989
1113
  }
990
1114
 
1115
+ .map-context-menu {
1116
+ position: fixed;
1117
+ z-index: 40;
1118
+ display: flex;
1119
+ min-width: 168px;
1120
+ flex-direction: column;
1121
+ gap: 2px;
1122
+ padding: 4px;
1123
+ color: #e7ebf2;
1124
+ background: rgba(25, 28, 34, 0.96);
1125
+ border: 1px solid var(--vc-border);
1126
+ pointer-events: auto;
1127
+ }
1128
+
1129
+ .map-context-menu button {
1130
+ cursor: pointer;
1131
+ padding: 8px 10px;
1132
+ color: inherit;
1133
+ text-align: left;
1134
+ background: transparent;
1135
+ border: 0;
1136
+ }
1137
+
1138
+ .map-context-menu button:hover,
1139
+ .map-context-menu button:focus-visible {
1140
+ background: var(--vc-surface);
1141
+ outline: none;
1142
+ }
1143
+
991
1144
  .hud-decide {
992
1145
  display: flex;
993
1146
  flex-wrap: wrap;
@@ -1,5 +1,6 @@
1
- import { Suspense } from 'react'
1
+ import { Suspense, useRef } from 'react'
2
2
  import { Billboard, Edges, Html, Text } from '@react-three/drei'
3
+ import { useFrame, useThree } from '@react-three/fiber'
3
4
  import { CHANGE_HIGHLIGHT, CONFIG, dimColor, fileColor, FILE_SELECTION, MAP_SELECTION, type ChangeKind } from '../theme'
4
5
  import { NameInput } from '../ui/NameInput'
5
6
  import { MapSelectBorder } from './MapSelectBorder'
@@ -40,6 +41,50 @@ function fileLabel(name: string, changeKind: ChangeKind | null, added: boolean)
40
41
  return name
41
42
  }
42
43
 
44
+ function changeMark(changeKind: ChangeKind | null, added: boolean) {
45
+ if (changeKind === 'remove') return 'D'
46
+ if (changeKind === 'add' || added) return 'A'
47
+ if (changeKind === 'edit') return 'U'
48
+ return null
49
+ }
50
+
51
+ function MapChangeMark({
52
+ mark,
53
+ width,
54
+ depth,
55
+ height,
56
+ }: {
57
+ mark: string
58
+ width: number
59
+ depth: number
60
+ height: number
61
+ }) {
62
+ const camera = useThree((state) => state.camera)
63
+ const markRef = useRef<HTMLDivElement>(null)
64
+
65
+ useFrame(() => {
66
+ const el = markRef.current
67
+ if (!el) return
68
+ const zoom = 'zoom' in camera ? Number(camera.zoom) : 1
69
+ const px = Math.min(width, depth) * 0.78 * zoom
70
+ el.style.fontSize = `${Math.max(px, 1)}px`
71
+ })
72
+
73
+ return (
74
+ <Html
75
+ position={[0, height / 2 + 0.12, 0]}
76
+ center
77
+ occlude={false}
78
+ zIndexRange={[120, 100]}
79
+ style={{ pointerEvents: 'none' }}
80
+ >
81
+ <div ref={markRef} className="map-change-mark">
82
+ {mark}
83
+ </div>
84
+ </Html>
85
+ )
86
+ }
87
+
43
88
  export function FileBlock({
44
89
  file,
45
90
  placed,
@@ -63,9 +108,11 @@ export function FileBlock({
63
108
  changeKind && highlightMapChange && !selected
64
109
  ? CHANGE_HIGHLIGHT[changeKind]
65
110
  : null
66
- const color = added || file.userCreated ? '#7ec8e8' : fileColor(file.language)
111
+ const isAdded = added || Boolean(file.userCreated)
112
+ const color = isAdded ? '#7ec8e8' : fileColor(file.language)
67
113
  const muted = dimColor(color, 0.32)
68
- const label = fileLabel(file.name, changeKind, added || Boolean(file.userCreated))
114
+ const label = fileLabel(file.name, changeKind, isAdded)
115
+ const mark = changeMark(changeKind, isAdded)
69
116
  const labelColor = aimed
70
117
  ? '#9ad8ff'
71
118
  : highlight
@@ -105,7 +152,7 @@ export function FileBlock({
105
152
  ? FILE_SELECTION.emissive
106
153
  : related
107
154
  ? '#1f4a44'
108
- : added || file.userCreated
155
+ : isAdded
109
156
  ? '#2a5064'
110
157
  : dimmed
111
158
  ? muted
@@ -120,7 +167,7 @@ export function FileBlock({
120
167
  ? 0.55
121
168
  : related
122
169
  ? 0.18
123
- : added || file.userCreated
170
+ : isAdded
124
171
  ? 0.22
125
172
  : dimmed
126
173
  ? 0.08
@@ -148,6 +195,9 @@ export function FileBlock({
148
195
  userData={{ fileId: file.id }}
149
196
  />
150
197
  )}
198
+ {mapMode && !naming && mark && (
199
+ <MapChangeMark mark={mark} width={width} depth={depth} height={height} />
200
+ )}
151
201
  {naming && onCommitName && onCancelName && (
152
202
  <Html
153
203
  position={[0, height / 2 + 0.42, 0]}
@@ -6,6 +6,12 @@ import { folderAt, worldBounds } from '../layout'
6
6
  import type { ChangeKind } from '../theme'
7
7
  import type { WorldLayout } from '../types'
8
8
 
9
+ export type MapBlueprintMenu = {
10
+ x: number
11
+ y: number
12
+ folder: string
13
+ }
14
+
9
15
  type MapViewProps = {
10
16
  layout: WorldLayout
11
17
  enabled: boolean
@@ -16,6 +22,7 @@ type MapViewProps = {
16
22
  onSelect: (fileId: string | null) => void
17
23
  onSelectFolder: (folderPath: string | null) => void
18
24
  onTravelTo: (fromId: string, toId: string) => void
25
+ onBlueprintMenu?: (menu: MapBlueprintMenu) => void
19
26
  }
20
27
 
21
28
  export function MapView({
@@ -28,6 +35,7 @@ export function MapView({
28
35
  onSelect,
29
36
  onSelectFolder,
30
37
  onTravelTo,
38
+ onBlueprintMenu,
31
39
  }: MapViewProps) {
32
40
  const size = useThree((state) => state.size)
33
41
  const { camera, gl, invalidate, scene } = useThree()
@@ -172,7 +180,28 @@ export function MapView({
172
180
  }
173
181
 
174
182
  const onContextMenu = (event: MouseEvent) => {
175
- if (event.ctrlKey) event.preventDefault()
183
+ if (event.ctrlKey) {
184
+ event.preventDefault()
185
+ return
186
+ }
187
+ if (!onBlueprintMenu) return
188
+ event.preventDefault()
189
+ const pick = pickAt(event.clientX, event.clientY)
190
+ if (!pick) return
191
+ const hit = new THREE.Vector3()
192
+ const plane = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
193
+ if (!pick.raycaster.ray.intersectPlane(plane, hit)) return
194
+ const folder =
195
+ folderAt(hit.x, hit.z, layout) ??
196
+ (selectedFolder ? layout.folders[selectedFolder] : undefined) ??
197
+ layout.folders['.']
198
+ if (!folder) return
199
+ onSelectFolder(folder.path)
200
+ onBlueprintMenu({
201
+ x: event.clientX,
202
+ y: event.clientY,
203
+ folder: folder.path,
204
+ })
176
205
  }
177
206
 
178
207
  const ground = new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)
@@ -250,10 +279,12 @@ export function MapView({
250
279
  invalidate,
251
280
  layout,
252
281
  onLand,
282
+ onBlueprintMenu,
253
283
  onSelect,
254
284
  onSelectFolder,
255
285
  onTravelTo,
256
286
  scene,
287
+ selectedFolder,
257
288
  ])
258
289
 
259
290
  return (
@@ -3,7 +3,7 @@ import { FileBlock } from './FileBlock'
3
3
  import { Bridge } from './Bridge'
4
4
  import { RelationLines } from './RelationLines'
5
5
  import { Player } from './Player'
6
- import { MapView } from './MapView'
6
+ import { MapView, type MapBlueprintMenu } from './MapView'
7
7
  import { SelectionController } from './SelectionController'
8
8
  import { UserContextTracker } from './UserContextTracker'
9
9
  import { BlockPlacer } from './BlockPlacer'
@@ -60,6 +60,7 @@ type WorldProps = {
60
60
  namingId?: string | null
61
61
  onPlaceBlock?: (spot: { x: number; z: number; folder: string }) => void
62
62
  onPlaceIsland?: (parent: string) => void
63
+ onBlueprintMenu?: (menu: MapBlueprintMenu) => void
63
64
  onCommitName?: (id: string, name: string) => boolean
64
65
  onCancelName?: (id: string) => void
65
66
  userCreatedBlocks?: UserCreatedBlock[]
@@ -100,6 +101,7 @@ export function World({
100
101
  namingIslandId = null,
101
102
  onPlaceBlock,
102
103
  onPlaceIsland,
104
+ onBlueprintMenu,
103
105
  onCommitName,
104
106
  onCancelName,
105
107
  userCreatedBlocks = [],
@@ -171,6 +173,9 @@ export function World({
171
173
  onSelect={onSelect}
172
174
  onSelectFolder={onSelectFolder}
173
175
  onTravelTo={onTravelTo}
176
+ onBlueprintMenu={
177
+ mapping && !placing && onBlueprintMenu ? onBlueprintMenu : undefined
178
+ }
174
179
  />
175
180
 
176
181
  {Object.values(viewLayout.folders).map((folder) => (
@@ -114,6 +114,7 @@ export type UserContext = {
114
114
  z: number
115
115
  }
116
116
  followLook?: boolean
117
+ showBranchChanges?: boolean
117
118
  userCreatedBlocks?: UserCreatedBlock[]
118
119
  userCreatedIslands?: UserCreatedIsland[]
119
120
  }
@@ -160,6 +161,40 @@ export function canStopSession(intent: {
160
161
  return Boolean(intent.sessionId) && intent.status !== 'idle'
161
162
  }
162
163
 
164
+ export function llmIsMakingChanges(intent: {
165
+ working: boolean
166
+ preview: boolean
167
+ status: AgentIntentStatus
168
+ }) {
169
+ return (
170
+ intent.working ||
171
+ intent.preview ||
172
+ intent.status === 'preparing' ||
173
+ intent.status === 'working' ||
174
+ intent.status === 'replanning' ||
175
+ intent.status === 'pending' ||
176
+ intent.status === 'extend' ||
177
+ intent.status === 'extended'
178
+ )
179
+ }
180
+
181
+ export type BranchChanges = {
182
+ available: boolean
183
+ branch: string | null
184
+ base: string | null
185
+ files: string[]
186
+ creates: string[]
187
+ deletes: string[]
188
+ createFolders: string[]
189
+ createLines: Record<string, number>
190
+ imports: PatchImport[]
191
+ addedFunctions: PatchSymbolAddition[]
192
+ addedVariables: PatchSymbolAddition[]
193
+ addedImports: PatchImportAddition[]
194
+ changedFunctions: PatchSymbolAddition[]
195
+ changedVariables: PatchSymbolAddition[]
196
+ }
197
+
163
198
  export type PlanStep = {
164
199
  index: number
165
200
  title: string
@@ -227,6 +262,7 @@ export type AgentIntent = {
227
262
  updatedAt: string | null
228
263
  showMap: boolean
229
264
  status: AgentIntentStatus
265
+ name: string | null
230
266
  feature: string | null
231
267
  steps: PlanStep[]
232
268
  step: number | null
@@ -254,6 +290,14 @@ export type AgentIntent = {
254
290
  working: boolean
255
291
  stalledWait: boolean
256
292
  llmIdle?: boolean
293
+ awaitingAttach?: boolean
294
+ listening?: boolean
295
+ lastAck?: {
296
+ kind: string
297
+ detail: string
298
+ at: string | null
299
+ } | null
300
+ initialInstruction?: string | null
257
301
  creationMode: boolean
258
302
  canEnterBlueprint: boolean
259
303
  blueprintSessionId: string | null