@jkwd/inbase 0.1.10 → 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, persistSessionFocus } 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,
@@ -53,7 +56,12 @@ function intentSignature(intent: AgentIntent) {
53
56
  phase: intent.phase,
54
57
  stalledWait: intent.stalledWait,
55
58
  llmIdle: intent.llmIdle,
59
+ awaitingAttach: intent.awaitingAttach,
60
+ listening: intent.listening,
61
+ lastAck: intent.lastAck,
56
62
  sessionId: intent.sessionId,
63
+ name: intent.name,
64
+ feature: intent.feature,
57
65
  creationMode: intent.creationMode,
58
66
  diffId: intent.diffId,
59
67
  chain: intent.chain,
@@ -76,8 +84,15 @@ export default function App() {
76
84
  const [updatingModel, setUpdatingModel] = useState(false)
77
85
  const updatingModelRef = useRef(false)
78
86
 
87
+ const graphSig = useRef<string | null>(null)
79
88
  const applyGraph = useCallback((next: CodebaseGraph | null, failed: string) => {
80
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
81
96
  setGraph(next)
82
97
  setLoadError(null)
83
98
  return true
@@ -137,13 +152,27 @@ function Explorer({
137
152
  }) {
138
153
  const [intents, setIntents] = useState<AgentIntent[]>([])
139
154
  const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)
155
+ const [wantBranchChanges, setWantBranchChanges] = useState(false)
156
+ const [branchChanges, setBranchChanges] = useState(emptyBranchChanges)
140
157
  const intent =
141
158
  intents.find((item) => item.sessionId === focusedSessionId) ??
142
159
  intents[0] ??
143
160
  emptyIntent
144
161
  const canPlace = Boolean(intent.sessionId && intent.creationMode)
145
- const previewing = intent.preview || isPatchPreview(intent.status)
146
- const plannedCreates = previewing ? intent.creates : []
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 : []
147
176
  const [userBlocks, setUserBlocks] = useState<UserCreatedBlock[]>([])
148
177
  const [userIslands, setUserIslands] = useState<UserCreatedIsland[]>([])
149
178
  const [blueprintFunctions, setBlueprintFunctions] = useState<
@@ -163,15 +192,15 @@ function Explorer({
163
192
  return withPreviewGraph(
164
193
  graph,
165
194
  plannedCreates,
166
- intent.createLines ?? {},
167
- intent.createFolders ?? [],
168
- intent.imports ?? [],
195
+ changeSet.createLines ?? {},
196
+ changeSet.createFolders ?? [],
197
+ changeSet.imports ?? [],
169
198
  )
170
199
  }, [
200
+ changeSet.createFolders,
201
+ changeSet.createLines,
202
+ changeSet.imports,
171
203
  graph,
172
- intent.createFolders,
173
- intent.createLines,
174
- intent.imports,
175
204
  plannedCreates,
176
205
  previewing,
177
206
  ])
@@ -194,14 +223,20 @@ function Explorer({
194
223
  )
195
224
  const layout = useMemo(() => {
196
225
  const world = layoutWorld(previewGraph)
197
- if (previewing) markCreatedFolders(world, intent.createFolders ?? [])
226
+ if (previewing) markCreatedFolders(world, changeSet.createFolders ?? [])
198
227
  return withUserCreatedLayout(world, userBlocks, userIslands)
199
- }, [intent.createFolders, previewGraph, previewing, userBlocks, userIslands])
228
+ }, [
229
+ changeSet.createFolders,
230
+ previewGraph,
231
+ previewing,
232
+ userBlocks,
233
+ userIslands,
234
+ ])
200
235
  const changeFileIds = useMemo(() => {
201
236
  const ids = new Set<string>()
202
- for (const id of intent.files) ids.add(id)
203
- for (const id of intent.creates) ids.add(id)
204
- 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)
205
240
  for (const block of userBlocks) ids.add(block.id)
206
241
  for (const item of blueprintFunctions) ids.add(item.file)
207
242
  for (const item of blueprintVariables) ids.add(item.file)
@@ -211,20 +246,20 @@ function Explorer({
211
246
  blueprintFunctions,
212
247
  blueprintImports,
213
248
  blueprintVariables,
214
- intent.creates,
215
- intent.deletes,
216
- intent.files,
249
+ changeSet.creates,
250
+ changeSet.deletes,
251
+ changeSet.files,
217
252
  userBlocks,
218
253
  ])
219
254
  const changeFolderPaths = useMemo(() => {
220
255
  const paths = new Set<string>()
221
- for (const path of intent.createFolders ?? []) paths.add(path)
256
+ for (const path of changeSet.createFolders ?? []) paths.add(path)
222
257
  for (const island of userIslands) {
223
258
  if (island.path) paths.add(island.path)
224
259
  else if (island.id) paths.add(island.id)
225
260
  }
226
261
  return [...paths]
227
- }, [intent.createFolders, userIslands])
262
+ }, [changeSet.createFolders, userIslands])
228
263
  const hasChangeSet =
229
264
  changeFileIds.length > 0 || changeFolderPaths.length > 0
230
265
  const changePathGraph = useMemo(() => {
@@ -239,14 +274,14 @@ function Explorer({
239
274
  if (!hasChangeSet) return layout
240
275
  const world = layoutWorld(changePathGraph)
241
276
  markCreatedFolders(world, [
242
- ...(intent.createFolders ?? []),
277
+ ...(changeSet.createFolders ?? []),
243
278
  ...userIslands.map((island) => island.path || island.id),
244
279
  ])
245
280
  return world
246
281
  }, [
247
282
  changePathGraph,
283
+ changeSet.createFolders,
248
284
  hasChangeSet,
249
- intent.createFolders,
250
285
  layout,
251
286
  userIslands,
252
287
  ])
@@ -301,6 +336,14 @@ function Explorer({
301
336
  persistSessionFocus(sessionId)
302
337
  }, [])
303
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
+
304
347
  const rememberWalk = useCallback((x: number, z: number) => {
305
348
  walkPos.current = [x, z]
306
349
  }, [])
@@ -418,7 +461,6 @@ function Explorer({
418
461
  lastIntentSig.current = null
419
462
  applyIntent(next, sessionId)
420
463
  if (
421
- action === 'invoke' ||
422
464
  action === 'continue' ||
423
465
  action === 'stop' ||
424
466
  action === 'set_step_by_step'
@@ -509,6 +551,9 @@ function Explorer({
509
551
  if (typeof context?.followLook === 'boolean') {
510
552
  setFollowLook(context.followLook)
511
553
  }
554
+ if (typeof context?.showBranchChanges === 'boolean') {
555
+ setWantBranchChanges(context.showBranchChanges)
556
+ }
512
557
  })
513
558
  return () => {
514
559
  cancelled = true
@@ -1002,6 +1047,18 @@ function Explorer({
1002
1047
  })
1003
1048
  }, [])
1004
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
+
1005
1062
  const toggleImportedBy = useCallback(() => {
1006
1063
  setImportedBy((current) => !current)
1007
1064
  }, [])
@@ -1031,6 +1088,48 @@ function Explorer({
1031
1088
  return () => window.removeEventListener('keydown', onKey)
1032
1089
  }, [toggleImportedBy])
1033
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
+
1034
1133
  useEffect(() => {
1035
1134
  if (mode !== 'map' || !hasChangeSet) return
1036
1135
  const onKey = (event: KeyboardEvent) => {
@@ -1111,23 +1210,23 @@ function Explorer({
1111
1210
  void poll()
1112
1211
  const timer = window.setInterval(() => {
1113
1212
  void poll()
1114
- }, 700)
1213
+ }, 250)
1115
1214
  return () => {
1116
1215
  cancelled = true
1117
1216
  window.clearInterval(timer)
1118
1217
  }
1119
1218
  }, [])
1120
1219
 
1121
- const plannedIds = previewing ? [...intent.files, ...intent.creates] : []
1220
+ const plannedIds = previewing ? [...changeSet.files, ...changeSet.creates] : []
1122
1221
  const blueprintImportEdges = blueprintImports.flatMap((item) => {
1123
1222
  if (!displayGraph.files.some((file) => file.id === item.from)) return []
1124
1223
  return [{ from: item.file, to: item.from }]
1125
1224
  })
1126
1225
  const plannedImports = [
1127
- ...(previewing ? (intent.imports ?? []) : []),
1226
+ ...(previewing ? (changeSet.imports ?? []) : []),
1128
1227
  ...blueprintImportEdges,
1129
1228
  ]
1130
- const deletedIds = previewing ? intent.deletes : []
1229
+ const deletedIds = previewing ? changeSet.deletes : []
1131
1230
 
1132
1231
  return (
1133
1232
  <>
@@ -1162,7 +1261,7 @@ function Explorer({
1162
1261
  plannedImports={plannedImports}
1163
1262
  createdIds={plannedCreates}
1164
1263
  deletedIds={deletedIds}
1165
- createLines={intent.createLines ?? {}}
1264
+ createLines={changeSet.createLines ?? {}}
1166
1265
  flyTo={flyTo}
1167
1266
  aimedRelation={aimedRelation}
1168
1267
  onAimRelation={setAimedRelation}
@@ -1203,12 +1302,18 @@ function Explorer({
1203
1302
  intents={intents}
1204
1303
  focusedSessionId={focusedSessionId}
1205
1304
  onFocusSession={focusSessionPanel}
1305
+ onSetupSession={setupLlmSession}
1206
1306
  onWorkflowAction={runWorkflowAction}
1207
1307
  onNavigateDiff={navigateDiff}
1208
1308
  onOpenMap={openMap}
1209
1309
  onWalk={openWalk}
1210
1310
  followLook={followLook}
1211
1311
  onToggleFollowLook={toggleFollowLook}
1312
+ showBranchChanges={showingBranchChanges}
1313
+ branchChanges={branchChanges}
1314
+ canShowBranchChanges={canToggleBranchChanges}
1315
+ llmMakingChanges={llmBusy}
1316
+ onToggleShowBranchChanges={toggleShowBranchChanges}
1212
1317
  onUpdateModel={onUpdateModel}
1213
1318
  updatingModel={updatingModel}
1214
1319
  importedBy={importedBy}
@@ -45,10 +45,26 @@ function normalizeImportAdditions(value: unknown): PatchImportAddition[] {
45
45
  })
46
46
  }
47
47
 
48
+ function normalizeAck(
49
+ value: unknown,
50
+ ): AgentIntent['lastAck'] {
51
+ if (!value || typeof value !== 'object') return null
52
+ const kind = (value as { kind?: unknown }).kind
53
+ if (typeof kind !== 'string' || kind.trim() === '') return null
54
+ const detail = (value as { detail?: unknown }).detail
55
+ const at = (value as { at?: unknown }).at
56
+ return {
57
+ kind,
58
+ detail: typeof detail === 'string' ? detail : '',
59
+ at: typeof at === 'string' ? at : null,
60
+ }
61
+ }
62
+
48
63
  export const emptyIntent: AgentIntent = {
49
64
  updatedAt: null,
50
65
  showMap: false,
51
66
  status: 'idle',
67
+ name: null,
52
68
  feature: null,
53
69
  steps: [],
54
70
  step: null,
@@ -76,6 +92,10 @@ export const emptyIntent: AgentIntent = {
76
92
  working: false,
77
93
  stalledWait: false,
78
94
  llmIdle: false,
95
+ awaitingAttach: false,
96
+ listening: false,
97
+ lastAck: null,
98
+ initialInstruction: null,
79
99
  creationMode: false,
80
100
  canEnterBlueprint: false,
81
101
  blueprintSessionId: null,
@@ -91,6 +111,7 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
91
111
  updatedAt: data?.updatedAt ?? null,
92
112
  showMap: Boolean(data?.showMap),
93
113
  status: data?.status ?? 'idle',
114
+ name: data?.name ?? null,
94
115
  feature: data?.feature ?? null,
95
116
  steps: Array.isArray(data?.steps) ? data.steps : [],
96
117
  step: typeof data?.step === 'number' ? data.step : null,
@@ -121,6 +142,11 @@ function normalize(data: Partial<AgentIntent> | null | undefined): AgentIntent {
121
142
  working: Boolean(data?.working),
122
143
  stalledWait: Boolean(data?.stalledWait),
123
144
  llmIdle: Boolean(data?.llmIdle),
145
+ awaitingAttach: Boolean(data?.awaitingAttach),
146
+ listening: Boolean(data?.listening),
147
+ lastAck: normalizeAck(data?.lastAck),
148
+ initialInstruction:
149
+ typeof data?.initialInstruction === 'string' ? data.initialInstruction : null,
124
150
  creationMode: Boolean(data?.creationMode),
125
151
  canEnterBlueprint: Boolean(data?.canEnterBlueprint),
126
152
  blueprintSessionId:
@@ -224,6 +250,20 @@ export function persistSessionBlueprint(
224
250
  })
225
251
  }
226
252
 
253
+ export function persistInitialInstruction(sessionId: string, instruction: string) {
254
+ fetch('/api/agent-intent', {
255
+ method: 'POST',
256
+ headers: { 'Content-Type': 'application/json' },
257
+ body: JSON.stringify({
258
+ action: 'set_initial_instruction',
259
+ sessionId,
260
+ instruction,
261
+ }),
262
+ }).catch(() => {
263
+ // Keep the local instruction if the session handshake is no longer open.
264
+ })
265
+ }
266
+
227
267
  export function persistSessionFocus(sessionId: string) {
228
268
  fetch('/api/agent-intent', {
229
269
  method: 'POST',
@@ -237,6 +277,29 @@ export function persistSessionFocus(sessionId: string) {
237
277
  })
238
278
  }
239
279
 
280
+ export async function setupVisualizerSession(name?: string) {
281
+ const response = await fetch('/api/agent-intent', {
282
+ method: 'POST',
283
+ headers: { 'Content-Type': 'application/json' },
284
+ body: JSON.stringify({
285
+ action: 'setup_session',
286
+ name,
287
+ }),
288
+ })
289
+ if (!response.ok) {
290
+ const detail = await response.text()
291
+ let message = detail || 'Could not set up the LLM session'
292
+ try {
293
+ const parsed = JSON.parse(detail) as { error?: string }
294
+ if (parsed?.error) message = parsed.error
295
+ } catch {
296
+ // Use the raw body when it is not JSON.
297
+ }
298
+ throw new Error(message)
299
+ }
300
+ return normalize((await response.json()) as AgentIntent)
301
+ }
302
+
240
303
  export async function inspectTargetFile(payload: {
241
304
  sessionId?: string | null
242
305
  diffId?: string | null
@@ -0,0 +1,54 @@
1
+ import type { BranchChanges } from './types'
2
+
3
+ export const emptyBranchChanges: BranchChanges = {
4
+ available: false,
5
+ branch: null,
6
+ base: null,
7
+ files: [],
8
+ creates: [],
9
+ deletes: [],
10
+ createFolders: [],
11
+ createLines: {},
12
+ imports: [],
13
+ addedFunctions: [],
14
+ addedVariables: [],
15
+ addedImports: [],
16
+ changedFunctions: [],
17
+ changedVariables: [],
18
+ }
19
+
20
+ function normalize(data: Partial<BranchChanges> | null | undefined): BranchChanges {
21
+ return {
22
+ available: Boolean(data?.available),
23
+ branch: typeof data?.branch === 'string' ? data.branch : null,
24
+ base: typeof data?.base === 'string' ? data.base : null,
25
+ files: Array.isArray(data?.files) ? data.files : [],
26
+ creates: Array.isArray(data?.creates) ? data.creates : [],
27
+ deletes: Array.isArray(data?.deletes) ? data.deletes : [],
28
+ createFolders: Array.isArray(data?.createFolders) ? data.createFolders : [],
29
+ createLines:
30
+ data?.createLines && typeof data.createLines === 'object'
31
+ ? data.createLines
32
+ : {},
33
+ imports: Array.isArray(data?.imports) ? data.imports : [],
34
+ addedFunctions: Array.isArray(data?.addedFunctions) ? data.addedFunctions : [],
35
+ addedVariables: Array.isArray(data?.addedVariables) ? data.addedVariables : [],
36
+ addedImports: Array.isArray(data?.addedImports) ? data.addedImports : [],
37
+ changedFunctions: Array.isArray(data?.changedFunctions)
38
+ ? data.changedFunctions
39
+ : [],
40
+ changedVariables: Array.isArray(data?.changedVariables)
41
+ ? data.changedVariables
42
+ : [],
43
+ }
44
+ }
45
+
46
+ export async function fetchBranchChanges(): Promise<BranchChanges> {
47
+ try {
48
+ const response = await fetch(`/api/branch-changes?t=${Date.now()}`)
49
+ if (!response.ok) return emptyBranchChanges
50
+ return normalize((await response.json()) as BranchChanges)
51
+ } catch {
52
+ return emptyBranchChanges
53
+ }
54
+ }
@@ -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
  }