@phenx-inc/ctlsurf 0.17.5 → 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-CRJW4vYa.js → cssMode-LC2bCRa_.js} +3 -3
- package/out/renderer/assets/{freemarker2-IgsORiC6.js → freemarker2-Di7O120K.js} +1 -1
- package/out/renderer/assets/{handlebars-pDu2AR4b.js → handlebars-Dt8Fn4oY.js} +1 -1
- package/out/renderer/assets/{html-DNF7NUE5.js → html-wsYLMz8Z.js} +1 -1
- package/out/renderer/assets/{htmlMode-gbfJICtW.js → htmlMode-DAHN3Rfc.js} +3 -3
- package/out/renderer/assets/{index-CbuISbRh.js → index-BSPboVCX.js} +97 -29
- package/out/renderer/assets/{index-CBWAeUYW.css → index-CmvYnexl.css} +15 -3
- package/out/renderer/assets/{javascript-CzPWOwBB.js → javascript-Ch481vNL.js} +2 -2
- package/out/renderer/assets/{jsonMode-CWOEvILb.js → jsonMode-BmUlJiUR.js} +3 -3
- package/out/renderer/assets/{liquid-CP-HUzpK.js → liquid-DAvcjBM1.js} +1 -1
- package/out/renderer/assets/{lspLanguageFeatures-DsiaqQq_.js → lspLanguageFeatures-fZZkw0qY.js} +1 -1
- package/out/renderer/assets/{mdx-DerBk5VK.js → mdx-su1pUNh_.js} +1 -1
- package/out/renderer/assets/{python-DkUMRAVL.js → python-U5jDm-Cc.js} +1 -1
- package/out/renderer/assets/{razor-C6ynuc0X.js → razor-CE13v8rw.js} +1 -1
- package/out/renderer/assets/{tsMode-DXrOkNVu.js → tsMode-Dtr-mLSh.js} +1 -1
- package/out/renderer/assets/{typescript-BmhhpI43.js → typescript-MRx8M-TQ.js} +1 -1
- package/out/renderer/assets/{xml-CTMRAgAB.js → xml-DYFD_EMc.js} +1 -1
- package/out/renderer/assets/{yaml-DYNNafNI.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 +55 -1
- package/src/renderer/components/RollingCount.tsx +60 -0
- package/src/renderer/components/WorkMapPanel.tsx +24 -1
- package/src/renderer/styles.css +15 -3
|
@@ -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
|
|
|
@@ -206,7 +208,10 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
206
208
|
return <div className="work-map-branch" key={run.id}>
|
|
207
209
|
<button className="work-map-branch-heading" onClick={() => toggleThread(run.id)} aria-expanded={expandedThreads.has(run.id)}>
|
|
208
210
|
<span className={`work-map-branch-point ${failed ? 'failed' : ''}`} />
|
|
209
|
-
<span className="work-map-branch-
|
|
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>
|
|
210
215
|
<span className="work-map-caret">{expandedThreads.has(run.id) ? '▾' : '▸'}</span>
|
|
211
216
|
</button>
|
|
212
217
|
{expandedThreads.has(run.id) && <ul className="work-map-steps" aria-label="Activity thread">{run.nodes.map(renderStep)}</ul>}
|
|
@@ -228,6 +233,24 @@ export function WorkMapPanel({ tabId }: { tabId: string }) {
|
|
|
228
233
|
</section>
|
|
229
234
|
}
|
|
230
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
|
+
|
|
231
254
|
function highlightSource(text: string, focus?: MapNode['focus']): ReactNode {
|
|
232
255
|
const ranges = thinkingHighlights(text, focus)
|
|
233
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)); }
|