@phenx-inc/ctlsurf 0.17.4 → 0.17.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 +1 -1
- package/out/headless/index.mjs +1 -1
- package/out/headless/index.mjs.map +1 -1
- package/out/renderer/assets/{cssMode-BJfoToLX.js → cssMode-LC2bCRa_.js} +3 -3
- package/out/renderer/assets/{freemarker2-T0eMtP1Z.js → freemarker2-Di7O120K.js} +1 -1
- package/out/renderer/assets/{handlebars-Dbbib8fi.js → handlebars-Dt8Fn4oY.js} +1 -1
- package/out/renderer/assets/{html-BRHT_oxc.js → html-wsYLMz8Z.js} +1 -1
- package/out/renderer/assets/{htmlMode-BRlZTxME.js → htmlMode-DAHN3Rfc.js} +3 -3
- package/out/renderer/assets/{index-RCAeuJTr.js → index-BSPboVCX.js} +114 -38
- package/out/renderer/assets/{index-CBWAeUYW.css → index-CmvYnexl.css} +15 -3
- package/out/renderer/assets/{javascript-BbOZOxq9.js → javascript-Ch481vNL.js} +2 -2
- package/out/renderer/assets/{jsonMode-CUvAeYcT.js → jsonMode-BmUlJiUR.js} +3 -3
- package/out/renderer/assets/{liquid-DRIACo_s.js → liquid-DAvcjBM1.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-Ds-3nH9p.js → lspLanguageFeatures-fZZkw0qY.js} +1 -1
- package/out/renderer/assets/{mdx-D76jlpLO.js → mdx-su1pUNh_.js} +1 -1
- package/out/renderer/assets/{python-CMuRPKJi.js → python-U5jDm-Cc.js} +1 -1
- package/out/renderer/assets/{razor-DRoH5mal.js → razor-CE13v8rw.js} +1 -1
- package/out/renderer/assets/{tsMode-dPJzgFD7.js → tsMode-Dtr-mLSh.js} +1 -1
- package/out/renderer/assets/{typescript-D5USKnLj.js → typescript-MRx8M-TQ.js} +1 -1
- package/out/renderer/assets/{xml-qYg1wt2P.js → xml-DYFD_EMc.js} +1 -1
- package/out/renderer/assets/{yaml-Uc-CvnvL.js → yaml-B_Zhj_tf.js} +1 -1
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
- package/scripts/test-work-map-ui.cjs +63 -1
- package/src/renderer/components/RollingCount.tsx +60 -0
- package/src/renderer/components/WorkMapPanel.tsx +41 -10
- package/src/renderer/styles.css +15 -3
|
@@ -55,8 +55,25 @@ async function main() {
|
|
|
55
55
|
}
|
|
56
56
|
win = new BrowserWindow({ show: false, width: 430, height: 860, webPreferences: { backgroundThrottling: false } })
|
|
57
57
|
await win.loadFile(path.join(temp, 'index.html'))
|
|
58
|
+
win.webContents.debugger.attach('1.3')
|
|
59
|
+
await win.webContents.debugger.sendCommand('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'no-preference' }] })
|
|
58
60
|
const run = code => win.webContents.executeJavaScript(code)
|
|
61
|
+
const finishRolls = () => run(`Promise.all(document.querySelector('.work-map-forest').getAnimations({subtree:true}).map(a=>a.finished.catch(()=>{})))`)
|
|
62
|
+
const investigationCount = `document.querySelector('.work-map-activity-count.investigating .work-map-count')`
|
|
63
|
+
const visibleInvestigationCount = `Array.from(${investigationCount}.querySelectorAll('.work-map-counter-reel'),el=>String(Math.round(-new DOMMatrixReadOnly(getComputedStyle(el).transform).m42/parseFloat(getComputedStyle(el).fontSize))%10)).join('')`
|
|
59
64
|
await run(`new Promise(resolve=>setTimeout(resolve,150))`)
|
|
65
|
+
assert.equal(await run(`document.querySelector('.work-map-goal-heading').getAttribute('aria-expanded')`), 'true')
|
|
66
|
+
assert.equal(await run(`document.querySelector('.work-map-branch-heading').getAttribute('aria-expanded')`), 'false')
|
|
67
|
+
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 0, 'event groups start collapsed')
|
|
68
|
+
assert.deepEqual(await run(`[...document.querySelectorAll('.work-map-activity-count')].map(el=>el.getAttribute('aria-label'))`), ['Thinking: 1', 'Investigations: 10, 1 running', 'Edits: 9', 'Tests: 9, 1 failed'])
|
|
69
|
+
assert.equal(await run(`${visibleInvestigationCount}`), '10', 'initial counts are settled immediately')
|
|
70
|
+
await run(`window.emitMap({nodes:window.mapSnapshot().nodes.map(n=>n.id==='step-27'?{...n,status:'succeeded'}:n)})`)
|
|
71
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
72
|
+
assert.equal(await run(`${investigationCount}.dataset.value`), '10', 'a tool result does not add another event')
|
|
73
|
+
assert.equal(await run(`document.querySelector('.work-map-activity-count.investigating').getAttribute('aria-label')`), 'Investigations: 10')
|
|
74
|
+
assert.equal(await run(`${investigationCount}.getAnimations({subtree:true}).length`), 0, 'status changes do not roll unchanged counts')
|
|
75
|
+
await run(`document.querySelector('.work-map-branch-heading').click()`)
|
|
76
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
60
77
|
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 29, 'choices and actions must share one thread')
|
|
61
78
|
assert.equal(await run(`document.querySelector('.work-map-view')`), null, 'there are no separate decision/activity tabs')
|
|
62
79
|
assert.match(await run(`document.querySelector('.work-map-reason').textContent`), /Why.*does not interrupt/)
|
|
@@ -87,6 +104,13 @@ async function main() {
|
|
|
87
104
|
await run(`document.querySelector('.work-map-branch-heading').click()`)
|
|
88
105
|
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
89
106
|
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 0)
|
|
107
|
+
await run(`window.emitMap({nodes:[...window.mapSnapshot().nodes,{...window.mapSnapshot().nodes.at(-1),id:'collapsed-step',status:'running'}]})`)
|
|
108
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
109
|
+
assert.equal(await run(`document.querySelector('.work-map-branch-heading').getAttribute('aria-expanded')`), 'false', 'new activity never reopens a collapsed investigation')
|
|
110
|
+
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 0)
|
|
111
|
+
assert.equal(await run(`${investigationCount}.dataset.value`), '12', 'collapsed summaries still receive live events')
|
|
112
|
+
assert(await run(`${investigationCount}.getAnimations({subtree:true}).length>0`), 'live counts roll upward')
|
|
113
|
+
await finishRolls()
|
|
90
114
|
await run(`document.querySelector('.work-map-branch-heading').click()`)
|
|
91
115
|
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
92
116
|
await run(`document.querySelector('.work-map-step-button').click()`)
|
|
@@ -101,9 +125,44 @@ async function main() {
|
|
|
101
125
|
await run(`document.querySelector('.work-map-scroll').scrollTop=0`)
|
|
102
126
|
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
103
127
|
assert(await run(`document.documentElement.scrollWidth<=window.innerWidth`), 'no horizontal overflow at panel width')
|
|
128
|
+
await run(`document.querySelector('.work-map-branch-heading').click()`)
|
|
129
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
104
130
|
const screenshot = process.env.WORK_MAP_SCREENSHOT || path.join(repo, 'experiments/live-map/.cache/work-map-panel.png')
|
|
105
131
|
await fs.mkdir(path.dirname(screenshot), { recursive: true })
|
|
106
132
|
await fs.writeFile(screenshot, (await win.webContents.capturePage()).toPNG())
|
|
133
|
+
await run(`
|
|
134
|
+
window.beforeCountNodes=window.mapSnapshot().nodes;
|
|
135
|
+
window.withInvestigationCount=count=>window.emitMap({nodes:[
|
|
136
|
+
...window.beforeCountNodes.filter(n=>n.kind!=='step'||n.activity!=='investigating'),
|
|
137
|
+
...Array.from({length:count},(_,i)=>({...window.beforeCountNodes.find(n=>n.kind==='step'&&n.activity==='investigating'),id:'counter-step-'+i}))
|
|
138
|
+
]});
|
|
139
|
+
window.withInvestigationCount(99);
|
|
140
|
+
`)
|
|
141
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
142
|
+
await finishRolls()
|
|
143
|
+
assert.equal(await run(visibleInvestigationCount), '99')
|
|
144
|
+
await run(`window.withInvestigationCount(100)`)
|
|
145
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
146
|
+
assert(await run(`${investigationCount}.getAnimations({subtree:true}).length>0`), 'digit carry animates from 99 to 100')
|
|
147
|
+
await finishRolls()
|
|
148
|
+
assert.equal(await run(visibleInvestigationCount), '100')
|
|
149
|
+
for (const count of [101, 105]) {
|
|
150
|
+
await run(`window.withInvestigationCount(${count})`)
|
|
151
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
152
|
+
}
|
|
153
|
+
await finishRolls()
|
|
154
|
+
assert.equal(await run(visibleInvestigationCount), '105', 'bursts settle on the latest count')
|
|
155
|
+
await run(`window.withInvestigationCount(9)`)
|
|
156
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
157
|
+
assert.equal(await run(visibleInvestigationCount), '9', 'trimming or reclassification can decrease a count')
|
|
158
|
+
await win.webContents.debugger.sendCommand('Emulation.setEmulatedMedia', { features: [{ name: 'prefers-reduced-motion', value: 'reduce' }] })
|
|
159
|
+
await run(`window.withInvestigationCount(10)`)
|
|
160
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
161
|
+
assert.equal(await run(visibleInvestigationCount), '10')
|
|
162
|
+
assert.equal(await run(`${investigationCount}.getAnimations({subtree:true}).length`), 0, 'reduced motion updates counts immediately')
|
|
163
|
+
win.setSize(320, 860)
|
|
164
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
165
|
+
assert(await run(`document.documentElement.scrollWidth<=window.innerWidth`), 'activity icons wrap at narrow panel widths')
|
|
107
166
|
await run(`document.querySelector('.work-map-footer button').click()`)
|
|
108
167
|
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
109
168
|
assert.equal(await run(`document.querySelectorAll('.work-map-goal').length`), 0)
|
|
@@ -111,6 +170,9 @@ async function main() {
|
|
|
111
170
|
await run(`window.emitMap({thinkingStatus:'unavailable',nodes:window.exampleNodes.filter(n=>n.id!=='thought')})`)
|
|
112
171
|
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
113
172
|
assert.match(await run(`document.querySelector('.work-map-thinking-status').textContent`), /omits readable thinking/)
|
|
173
|
+
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 0, 'events collected after clearing also start collapsed')
|
|
174
|
+
await run(`document.querySelector('.work-map-branch-heading').click()`)
|
|
175
|
+
await run(`new Promise(resolve=>setTimeout(resolve,30))`)
|
|
114
176
|
assert.equal(await run(`document.querySelectorAll('.work-map-step').length`), 28, 'activity remains visible when thinking is omitted')
|
|
115
177
|
assert.equal(await run(`document.querySelectorAll('.work-map-intent').length`), 0, 'tool activity must not become invented decisions when thinking is omitted')
|
|
116
178
|
for (const state of ['checking', 'unavailable']) {
|
|
@@ -120,7 +182,7 @@ async function main() {
|
|
|
120
182
|
assert(await run(`document.querySelector('[role=switch]').disabled`))
|
|
121
183
|
assert(await run(`document.querySelector('select').disabled`))
|
|
122
184
|
}
|
|
123
|
-
console.log('PASS
|
|
185
|
+
console.log('PASS collapsed event groups, live activity summaries, rolling counts and carry, interrupted rolls, reduced motion, unified chronological thread, source receipts, controls and scroll')
|
|
124
186
|
console.log('Screenshot:', screenshot)
|
|
125
187
|
} finally {
|
|
126
188
|
win?.destroy()
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/** Each place rolls independently, including the carry from 9 to 10. */
|
|
4
|
+
export function RollingCount({ value, label }: { value: number; label?: string }) {
|
|
5
|
+
const previous = useRef(value)
|
|
6
|
+
const increasing = value > previous.current
|
|
7
|
+
useLayoutEffect(() => { previous.current = value }, [value])
|
|
8
|
+
|
|
9
|
+
return <span className="work-map-count" role="img" aria-label={label || String(value)} data-value={value}>
|
|
10
|
+
<span className="work-map-counter" aria-hidden="true">
|
|
11
|
+
{String(value).split('').map((_, index, digits) => {
|
|
12
|
+
const place = digits.length - index - 1
|
|
13
|
+
const turns = Math.floor(value / 10 ** place)
|
|
14
|
+
return <RollingDigit key={place} turns={turns} initial={increasing ? 0 : turns} />
|
|
15
|
+
})}
|
|
16
|
+
</span>
|
|
17
|
+
</span>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function RollingDigit({ turns, initial }: { turns: number; initial: number }) {
|
|
21
|
+
const reel = useRef<HTMLSpanElement>(null)
|
|
22
|
+
const previous = useRef(initial)
|
|
23
|
+
const animation = useRef<Animation | null>(null)
|
|
24
|
+
const digit = turns % 10
|
|
25
|
+
|
|
26
|
+
useLayoutEffect(() => {
|
|
27
|
+
const el = reel.current!
|
|
28
|
+
const before = previous.current
|
|
29
|
+
previous.current = turns
|
|
30
|
+
let start = before % 10
|
|
31
|
+
// Continue from the visible position when another event arrives mid-roll.
|
|
32
|
+
if (animation.current) {
|
|
33
|
+
const style = getComputedStyle(el)
|
|
34
|
+
start = (-new DOMMatrixReadOnly(style.transform).m42 / parseFloat(style.fontSize)) % 10
|
|
35
|
+
animation.current.cancel()
|
|
36
|
+
animation.current = null
|
|
37
|
+
}
|
|
38
|
+
if (turns <= before || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
|
|
39
|
+
|
|
40
|
+
const distance = Math.min(turns - before, 10 + (turns - before) % 10)
|
|
41
|
+
let end = before % 10 + distance
|
|
42
|
+
while (end <= start) end += 10
|
|
43
|
+
const roll = el.animate([
|
|
44
|
+
{ transform: `translateY(${-start}em)` },
|
|
45
|
+
{ transform: `translateY(${-end}em)` },
|
|
46
|
+
], { duration: 550, easing: 'cubic-bezier(.2, .65, .3, 1)' })
|
|
47
|
+
animation.current = roll
|
|
48
|
+
void roll.finished.then(() => {
|
|
49
|
+
if (animation.current === roll) animation.current = null
|
|
50
|
+
}, () => {})
|
|
51
|
+
}, [turns])
|
|
52
|
+
|
|
53
|
+
useLayoutEffect(() => () => { animation.current?.cancel() }, [])
|
|
54
|
+
|
|
55
|
+
return <span className="work-map-counter-digit">
|
|
56
|
+
<span className="work-map-counter-reel" ref={reel} style={{ transform: `translateY(${-digit}em)` }}>
|
|
57
|
+
{Array.from({ length: 30 }, (_, i) => <span key={i}>{i % 10}</span>)}
|
|
58
|
+
</span>
|
|
59
|
+
</span>
|
|
60
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef, useState, type ReactNode } from 'react'
|
|
2
2
|
import type { MapActivity, MapIntent, MapNode, MapSettings, MapSnapshot } from '../../shared/workMap'
|
|
3
3
|
import { thinkingHighlights } from '../lib/workMapHighlights'
|
|
4
|
+
import { RollingCount } from './RollingCount'
|
|
4
5
|
|
|
5
6
|
const ICONS: Record<MapActivity, string> = { thinking: '✦', investigating: '⌕', editing: '✎', testing: '✓', planning: '◇', reporting: '≡' }
|
|
7
|
+
const ACTIVITIES: Record<MapActivity, string> = { thinking: 'Thinking', investigating: 'Investigations', editing: 'Edits', testing: 'Tests', planning: 'Plans', reporting: 'Updates' }
|
|
6
8
|
const STATUS = { running: 'Running', succeeded: 'Succeeded', failed: 'Failed', unknown: 'Recorded', interrupted: 'Stopped' }
|
|
7
9
|
const INTENTS: Record<MapIntent, string> = { decision: 'Decision', hypothesis: 'Hypothesis', alternative: 'Alternative', next_step: 'Next step', context: 'Thinking' }
|
|
8
10
|
|
|
@@ -10,7 +12,8 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
10
12
|
const [snapshot, setSnapshot] = useState<MapSnapshot | null>(null)
|
|
11
13
|
const [error, setError] = useState<string | null>(null)
|
|
12
14
|
const [saving, setSaving] = useState(false)
|
|
13
|
-
const [
|
|
15
|
+
const [collapsedGoals, setCollapsedGoals] = useState<Set<string>>(new Set())
|
|
16
|
+
const [expandedThreads, setExpandedThreads] = useState<Set<string>>(new Set())
|
|
14
17
|
const [selected, setSelected] = useState<string | null>(null)
|
|
15
18
|
const [following, setFollowing] = useState(true)
|
|
16
19
|
const [paths, setPaths] = useState({ pythonPath: '', modelPath: '' })
|
|
@@ -62,7 +65,14 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
62
65
|
finally { if (alive.current) setSaving(false) }
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
const
|
|
68
|
+
const toggleGoal = (id: string) => setCollapsedGoals(prev => {
|
|
69
|
+
const next = new Set(prev)
|
|
70
|
+
if (next.has(id)) next.delete(id)
|
|
71
|
+
else next.add(id)
|
|
72
|
+
return next
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const toggleThread = (id: string) => setExpandedThreads(prev => {
|
|
66
76
|
const next = new Set(prev)
|
|
67
77
|
if (next.has(id)) next.delete(id)
|
|
68
78
|
else next.add(id)
|
|
@@ -188,20 +198,23 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
188
198
|
: 'Stated decisions, readable thinking, actions, and results appear in one continuous thread. Open any card to inspect its source.'}</p>
|
|
189
199
|
</div> : <div className="work-map-forest">
|
|
190
200
|
{goals.map((goal, i) => <article className="work-map-goal" key={goal.id}>
|
|
191
|
-
<button className="work-map-goal-heading" onClick={() =>
|
|
201
|
+
<button className="work-map-goal-heading" onClick={() => toggleGoal(goal.id)} aria-expanded={!collapsedGoals.has(goal.id)} title={goal.evidence[0]?.text}>
|
|
192
202
|
<span className="work-map-goal-icon" aria-hidden="true">◎</span><span><span className="work-map-kicker">GOAL {String(i + 1).padStart(2, '0')}</span><strong>{goal.title}</strong></span>
|
|
193
|
-
<span className="work-map-caret">{
|
|
203
|
+
<span className="work-map-caret">{collapsedGoals.has(goal.id) ? '▸' : '▾'}</span>
|
|
194
204
|
</button>
|
|
195
|
-
{!
|
|
205
|
+
{!collapsedGoals.has(goal.id) && <div className="work-map-branches">
|
|
196
206
|
{threadRuns(goal.id).map(run => {
|
|
197
207
|
const failed = run.nodes.some(n => n.status === 'failed')
|
|
198
208
|
return <div className="work-map-branch" key={run.id}>
|
|
199
|
-
<button className="work-map-branch-heading" onClick={() =>
|
|
209
|
+
<button className="work-map-branch-heading" onClick={() => toggleThread(run.id)} aria-expanded={expandedThreads.has(run.id)}>
|
|
200
210
|
<span className={`work-map-branch-point ${failed ? 'failed' : ''}`} />
|
|
201
|
-
<span className="work-map-branch-
|
|
202
|
-
|
|
211
|
+
<span className="work-map-branch-copy">
|
|
212
|
+
<span className="work-map-branch-title"><span className="work-map-branch-label">{run.label}</span><RollingCount value={run.nodes.length} label={`${run.nodes.length} events`} /></span>
|
|
213
|
+
<ActivityCounts nodes={run.nodes} />
|
|
214
|
+
</span>
|
|
215
|
+
<span className="work-map-caret">{expandedThreads.has(run.id) ? '▾' : '▸'}</span>
|
|
203
216
|
</button>
|
|
204
|
-
{
|
|
217
|
+
{expandedThreads.has(run.id) && <ul className="work-map-steps" aria-label="Activity thread">{run.nodes.map(renderStep)}</ul>}
|
|
205
218
|
</div>
|
|
206
219
|
})}
|
|
207
220
|
</div>}
|
|
@@ -213,13 +226,31 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
213
226
|
<footer className="work-map-footer"><span>{decisions.length} {decisions.length === 1 ? 'choice' : 'choices'} · {steps.length} events · local</span>
|
|
214
227
|
<button disabled={!steps.length || saving} onClick={async () => {
|
|
215
228
|
setError(null)
|
|
216
|
-
try { accept(await window.worker.clearWorkMap(tabId));
|
|
229
|
+
try { accept(await window.worker.clearWorkMap(tabId)); setCollapsedGoals(new Set()); setExpandedThreads(new Set()); setSelected(null); setFollowing(true) }
|
|
217
230
|
catch (err) { if (alive.current) setError(String(err)) }
|
|
218
231
|
}}>Clear map</button>
|
|
219
232
|
</footer>
|
|
220
233
|
</section>
|
|
221
234
|
}
|
|
222
235
|
|
|
236
|
+
function ActivityCounts({ nodes }: { nodes: MapNode[] }) {
|
|
237
|
+
return <span className="work-map-activity-counts">
|
|
238
|
+
{(Object.keys(ACTIVITIES) as MapActivity[]).map(activity => {
|
|
239
|
+
const events = nodes.filter(node => node.activity === activity)
|
|
240
|
+
if (!events.length) return null
|
|
241
|
+
const failed = events.filter(node => node.status === 'failed').length
|
|
242
|
+
const running = events.filter(node => node.status === 'running').length
|
|
243
|
+
const label = `${ACTIVITIES[activity]}: ${events.length}${running ? `, ${running} running` : ''}${failed ? `, ${failed} failed` : ''}`
|
|
244
|
+
return <span key={activity} className={`work-map-activity-count ${activity}`} role="img" aria-label={label} title={label}>
|
|
245
|
+
<span className={`work-map-activity ${activity}`} aria-hidden="true">{ICONS[activity]}</span>
|
|
246
|
+
<RollingCount value={events.length} />
|
|
247
|
+
{running > 0 && <span className="work-map-activity-running" aria-hidden="true" />}
|
|
248
|
+
{failed > 0 && <span className="work-map-activity-alert" aria-hidden="true">!</span>}
|
|
249
|
+
</span>
|
|
250
|
+
})}
|
|
251
|
+
</span>
|
|
252
|
+
}
|
|
253
|
+
|
|
223
254
|
function highlightSource(text: string, focus?: MapNode['focus']): ReactNode {
|
|
224
255
|
const ranges = thinkingHighlights(text, focus)
|
|
225
256
|
if (!ranges.length) return text
|
package/src/renderer/styles.css
CHANGED
|
@@ -61,11 +61,23 @@
|
|
|
61
61
|
.work-map-branch { position: relative; padding: 0 0 17px; }
|
|
62
62
|
.work-map-branch:last-child { padding-bottom: 0; }
|
|
63
63
|
.work-map-branch::before { content: ''; position: absolute; left: -18px; top: 12px; width: 15px; height: 1px; background: var(--border-dim); }
|
|
64
|
-
.work-map-branch-heading { display: flex; align-items:
|
|
65
|
-
.work-map-branch-point { width: 6px; height: 6px; flex-shrink: 0; border-radius: 2px; background: var(--accent); }
|
|
64
|
+
.work-map-branch-heading { display: flex; align-items: flex-start; gap: 7px; width: 100%; padding: 5px 0; border: 0; background: transparent; text-align: left; color: var(--text-bright); }
|
|
65
|
+
.work-map-branch-point { width: 6px; height: 6px; margin-top: 5px; flex-shrink: 0; border-radius: 2px; background: var(--accent); }
|
|
66
66
|
.work-map-branch-point.failed { background: var(--danger); }
|
|
67
|
+
.work-map-branch-copy { flex: 1; min-width: 0; }
|
|
68
|
+
.work-map-branch-title { display: flex; align-items: center; gap: 7px; line-height: 1.5; }
|
|
67
69
|
.work-map-branch-label { min-width: 0; overflow-wrap: anywhere; font-size: 11px; font-weight: 600; }
|
|
68
|
-
.work-map-count { padding:
|
|
70
|
+
.work-map-count { display: inline-flex; flex-shrink: 0; padding: 2px 5px; border-radius: 4px; background: var(--bg-chip); color: var(--text-soft); font-size: 9px; }
|
|
71
|
+
.work-map-counter { display: inline-flex; height: 1em; line-height: 1; font-variant-numeric: tabular-nums; }
|
|
72
|
+
.work-map-counter-digit { display: inline-block; width: 1ch; height: 1em; overflow: hidden; }
|
|
73
|
+
.work-map-counter-reel { display: flex; flex-direction: column; }
|
|
74
|
+
.work-map-counter-reel > span { flex: 0 0 1em; height: 1em; text-align: center; }
|
|
75
|
+
.work-map-activity-counts { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 7px; }
|
|
76
|
+
.work-map-activity-count { display: inline-flex; align-items: center; gap: 4px; padding: 3px 5px; border: 1px solid var(--border-subtle); border-radius: 5px; background: var(--bg-well); }
|
|
77
|
+
.work-map-activity-count .work-map-activity { width: 14px; height: 14px; font-size: 13px; background: transparent; }
|
|
78
|
+
.work-map-activity-count .work-map-count { padding: 0; background: transparent; font-size: 10px; }
|
|
79
|
+
.work-map-activity-running { width: 4px; height: 4px; border-radius: 50%; background: var(--accent); }
|
|
80
|
+
.work-map-activity-alert { color: var(--danger); font-size: 10px; font-weight: 700; line-height: 1; }
|
|
69
81
|
.work-map-steps { list-style: none; margin: 5px 0 0 2px; padding: 0 0 0 8px; border-left: 1px solid var(--border-subtle); }
|
|
70
82
|
.work-map-step { margin: 5px 0; border: 1px solid var(--border-well); background: var(--bg-well); border-radius: 7px; overflow: hidden; }
|
|
71
83
|
.work-map-step.failed { border-color: color-mix(in srgb, var(--danger) 45%, var(--border-well)); }
|