transcript-viewer 0.11.3__tar.gz → 0.12.0__tar.gz
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.
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/PKG-INFO +1 -1
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/pyproject.toml +1 -1
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/page.html +53 -13
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/page.test.js +46 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/uv.lock +1 -1
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/.coverage +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/.github/workflows/publish.yml +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/.github/workflows/test.yml +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/.gitignore +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/README.md +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/Anchor.dc.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/Dense.dc.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/Hover.dc.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/Main.dc.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/Quiet.dc.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/canvas.json +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/design/delegation-timeline.html +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/__init__.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/ai.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/cli.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/config.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/corpus.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/fetch.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/library.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/store.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/src/transcript_viewer/viewer.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/fixtures/big-command.json +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_ai.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_cli.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_config.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_corpus.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_fetch.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_library.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_page.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_readme.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_store.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_stream.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_style.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_tidiness.py +0 -0
- {transcript_viewer-0.11.3 → transcript_viewer-0.12.0}/tests/test_viewer.py +0 -0
|
@@ -388,14 +388,17 @@ pre{background:var(--sunk);border-radius:7px;padding:10px;overflow:auto;font-siz
|
|
|
388
388
|
#lanes>summary .fine em{font-style:normal;color:var(--accent)}
|
|
389
389
|
.lanes-body{padding:2px 16px 12px;border-top:1px solid var(--line)}
|
|
390
390
|
.lane{display:grid;grid-template-columns:150px 1fr;gap:0 12px;align-items:center}
|
|
391
|
-
|
|
391
|
+
/* Denser than the roomy version, but still a row you can aim at: thirty of
|
|
392
|
+
these is a screenful, and a 13px row with an 8px bar read as a scatter
|
|
393
|
+
plot rather than a set of lanes. */
|
|
394
|
+
.lane.tight{grid-template-columns:150px 1fr;gap:0 12px}
|
|
392
395
|
.lane-name{display:flex;align-items:baseline;gap:7px;min-width:0}
|
|
393
396
|
.lane-name .kind{font:12px var(--font-mono);color:var(--accent);overflow:hidden;
|
|
394
397
|
text-overflow:ellipsis;white-space:nowrap}
|
|
395
398
|
.lane-name .kind.main{color:var(--ink);font-weight:600}
|
|
396
399
|
.lane-name .n{font:10px var(--font-mono);color:var(--faint);margin-left:auto;flex:none}
|
|
397
400
|
.track{position:relative;height:22px}
|
|
398
|
-
.lane.tight .track{height:
|
|
401
|
+
.lane.tight .track{height:19px}
|
|
399
402
|
.track .rule{position:absolute;left:0;right:0;top:50%;height:1px;background:var(--line)}
|
|
400
403
|
.track .tick{position:absolute;top:0;bottom:0;width:1px;background:var(--line);opacity:.45}
|
|
401
404
|
/* A floor, so a four-minute subagent in a thirty-hour run is still a target.
|
|
@@ -403,7 +406,7 @@ pre{background:var(--sunk);border-radius:7px;padding:10px;overflow:auto;font-siz
|
|
|
403
406
|
the tooltip carry the real one. */
|
|
404
407
|
.bar{position:absolute;top:50%;transform:translateY(-50%);height:14px;min-width:7px;
|
|
405
408
|
border-radius:3px;background:var(--accent);opacity:.85;cursor:pointer;border:0;padding:0}
|
|
406
|
-
.lane.tight .bar{height:
|
|
409
|
+
.lane.tight .bar{height:11px}
|
|
407
410
|
.bar:hover,.bar:focus-visible{opacity:1;outline:1px solid var(--ink);outline-offset:1px}
|
|
408
411
|
.bar.main{background:var(--ink);opacity:.85;cursor:default}
|
|
409
412
|
.bar.main:hover{outline:none;opacity:.85}
|
|
@@ -416,11 +419,13 @@ pre{background:var(--sunk);border-radius:7px;padding:10px;overflow:auto;font-siz
|
|
|
416
419
|
rather than repeated into each row. */
|
|
417
420
|
.lanes-tracks{position:relative}
|
|
418
421
|
.bandlayer{position:absolute;left:162px;right:0;top:0;bottom:0;pointer-events:none}
|
|
419
|
-
.lane.tight ~ .bandlayer,.lanes-tracks:has(.lane.tight) .bandlayer{left:
|
|
420
|
-
.
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
.lane.tight ~ .bandlayer,.lanes-tracks:has(.lane.tight) .bandlayer{left:162px}
|
|
423
|
+
/* Where the axis was cut. One thin line, and only for the cuts worth seeing —
|
|
424
|
+
one session skips 152 quiet stretches, and marking every one turned the
|
|
425
|
+
whole chart into vertical stripes. The rest are still skipped; they are
|
|
426
|
+
simply not each announced. */
|
|
427
|
+
.gapmark{position:absolute;top:0;bottom:0;width:0;
|
|
428
|
+
border-left:1px dashed var(--line);opacity:.7}
|
|
424
429
|
/* Conversations that belong to the record rather than to any step. */
|
|
425
430
|
.loose{margin-top:18px;padding-top:12px;border-top:1px solid var(--line)}
|
|
426
431
|
.loose-head{font:11px var(--font-mono);color:var(--muted);margin-bottom:8px}
|
|
@@ -2272,11 +2277,39 @@ function pick(key){
|
|
|
2272
2277
|
fetch("/api/trajectory?id="+encodeURIComponent(key)).then(r=>r.json()).then(t=>{
|
|
2273
2278
|
if(t.error){main.innerHTML=`<div class="empty">${esc(t.error)}</div>`;return}
|
|
2274
2279
|
traj=t;limit=PAGE_SIZE;query="";lens="all";tab="trajectory";extra=null;RENAMING=false;render();
|
|
2280
|
+
// The first screenful is up; bring the rest along behind it.
|
|
2281
|
+
fillRest();
|
|
2275
2282
|
});
|
|
2276
2283
|
}
|
|
2277
2284
|
|
|
2278
2285
|
const toggle=r=>{show[r]=!show[r];limit=PAGE_SIZE;render()};
|
|
2279
|
-
|
|
2286
|
+
/* The rest of a long run, drawn without being asked for.
|
|
2287
|
+
|
|
2288
|
+
A transcript used to stop at 250 steps behind a Show more button. Nobody
|
|
2289
|
+
wants a button; they want the transcript. But the largest run here is 8,445
|
|
2290
|
+
steps and 34 MB of markup, which is a second of the page doing nothing if it
|
|
2291
|
+
is all drawn at once — so the first screenful still paints immediately and
|
|
2292
|
+
the rest arrives in the gaps between frames, keeping the reader's place each
|
|
2293
|
+
time. By the time anyone scrolls, it is there. */
|
|
2294
|
+
const FILL_CHUNK = 500;
|
|
2295
|
+
let FILLING = 0;
|
|
2296
|
+
|
|
2297
|
+
function fillRest() {
|
|
2298
|
+
clearTimeout(FILLING);
|
|
2299
|
+
if (!traj || tab !== "trajectory") return;
|
|
2300
|
+
const idle = globalThis.requestIdleCallback || ((fn) => setTimeout(fn, 32));
|
|
2301
|
+
FILLING = setTimeout(() => {
|
|
2302
|
+
idle(() => {
|
|
2303
|
+
if (!traj || tab !== "trajectory") return;
|
|
2304
|
+
if (limit >= traj.steps.length) return;
|
|
2305
|
+
const at = main.scrollTop;
|
|
2306
|
+
limit += FILL_CHUNK;
|
|
2307
|
+
render();
|
|
2308
|
+
main.scrollTop = at;
|
|
2309
|
+
fillRest();
|
|
2310
|
+
});
|
|
2311
|
+
}, 0);
|
|
2312
|
+
}
|
|
2280
2313
|
const toggleRaw=()=>{
|
|
2281
2314
|
raw=!raw;
|
|
2282
2315
|
// Boxes flipped by hand were disagreeing with the old setting, not this one.
|
|
@@ -2526,7 +2559,7 @@ function timeScale(lo, hi, busy) {
|
|
|
2526
2559
|
return {
|
|
2527
2560
|
posOf,
|
|
2528
2561
|
room,
|
|
2529
|
-
gaps: gaps.map(([a, b]) => ({ at: posOf(a), took: duration(b - a) })),
|
|
2562
|
+
gaps: gaps.map(([a, b]) => ({ at: posOf(a), ms: b - a, took: duration(b - a) })),
|
|
2530
2563
|
kept,
|
|
2531
2564
|
skipped,
|
|
2532
2565
|
};
|
|
@@ -2591,7 +2624,14 @@ function drawLanes(t) {
|
|
|
2591
2624
|
const scale = timeScale(lo, hi, busy);
|
|
2592
2625
|
const bands = together(timed);
|
|
2593
2626
|
|
|
2594
|
-
|
|
2627
|
+
// Every quiet stretch is skipped; only the large ones are marked. Drawing a
|
|
2628
|
+
// line for all 152 of one session's turned the chart into vertical stripes,
|
|
2629
|
+
// and a mark every few pixels tells a reader nothing they can act on.
|
|
2630
|
+
const worth = scale.gaps
|
|
2631
|
+
.slice()
|
|
2632
|
+
.sort((a, b) => b.ms - a.ms)
|
|
2633
|
+
.slice(0, 8);
|
|
2634
|
+
const breaks = worth.map((g) =>
|
|
2595
2635
|
`<span class="gapmark" style="left:${g.at}%" title="nothing running for ${esc(g.took)}"></span>`
|
|
2596
2636
|
).join("");
|
|
2597
2637
|
|
|
@@ -2927,8 +2967,8 @@ function trajectoryTab(t,e,branches,branchSteps,counts,q){
|
|
|
2927
2967
|
</div>
|
|
2928
2968
|
${shown.map(({s,i})=>step(s,t,0,i,"")).join("")
|
|
2929
2969
|
||`<div class="empty">Nothing matches.</div>`}
|
|
2930
|
-
${total>shown.length?`<div class="more"><
|
|
2931
|
-
${num(shown.length)} of ${num(total)} steps</
|
|
2970
|
+
${total>shown.length?`<div class="more"><span class="fine">
|
|
2971
|
+
drawing the rest — ${num(shown.length)} of ${num(total)} steps</span></div>`:""}
|
|
2932
2972
|
${(()=>{
|
|
2933
2973
|
// Only in the plain view: a lens or a search is a question about the
|
|
2934
2974
|
// steps of this run, and answering it with whole other conversations
|
|
@@ -2175,3 +2175,49 @@ test("the box a lane opened is marked, and the mark is a real rule", () => {
|
|
|
2175
2175
|
const script = page.slice(page.indexOf("<script>"), page.lastIndexOf("</script>"));
|
|
2176
2176
|
assert.match(script, /classList\.add\("found"\)/, "and something must set it");
|
|
2177
2177
|
});
|
|
2178
|
+
|
|
2179
|
+
test("a long run fills itself in rather than offering a button", () => {
|
|
2180
|
+
const steps = [];
|
|
2181
|
+
for (let i = 0; i < 900; i++) {
|
|
2182
|
+
steps.push({ step_id: i + 1, source: "agent", message: "working" });
|
|
2183
|
+
}
|
|
2184
|
+
const html = run(`
|
|
2185
|
+
traj = {steps: ${JSON.stringify(steps)}};
|
|
2186
|
+
cur = "k"; INDEX = [{key:"k"}]; lens = "all"; query = ""; limit = 250;
|
|
2187
|
+
return trajectoryTab(traj, {}, [], new Set(), {all: 900}, "");
|
|
2188
|
+
`);
|
|
2189
|
+
assert.ok(!/Show more/.test(html), "nobody wants a button; they want the transcript");
|
|
2190
|
+
assert.match(html, /drawing the rest/, "but it should say more is coming");
|
|
2191
|
+
assert.match(html, /250 of 900 steps/);
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
test("once everything is drawn it says nothing at all", () => {
|
|
2195
|
+
const steps = [{ step_id: 1, source: "user", message: "hi" }];
|
|
2196
|
+
const html = run(`
|
|
2197
|
+
traj = {steps: ${JSON.stringify(steps)}};
|
|
2198
|
+
cur = "k"; INDEX = [{key:"k"}]; lens = "all"; query = ""; limit = 250;
|
|
2199
|
+
return trajectoryTab(traj, {}, [], new Set(), {all: 1}, "");
|
|
2200
|
+
`);
|
|
2201
|
+
assert.ok(!/drawing the rest/.test(html));
|
|
2202
|
+
});
|
|
2203
|
+
|
|
2204
|
+
test("filling holds the reader's place, and stops when the run is done", () => {
|
|
2205
|
+
const out = run(`
|
|
2206
|
+
const steps = [];
|
|
2207
|
+
for (let i = 0; i < 1200; i++) steps.push({step_id:i+1, source:"agent", message:"x"});
|
|
2208
|
+
traj = {steps}; cur = "k"; INDEX = [{key:"k"}];
|
|
2209
|
+
lens = "all"; query = ""; tab = "trajectory"; limit = 250;
|
|
2210
|
+
main.scrollTop = 400;
|
|
2211
|
+
|
|
2212
|
+
// Run the scheduler by hand: each tick draws one more chunk.
|
|
2213
|
+
let ticks = 0;
|
|
2214
|
+
globalThis.setTimeout = (fn) => { if (ticks++ < 50) fn(); return 0; };
|
|
2215
|
+
globalThis.clearTimeout = () => {};
|
|
2216
|
+
globalThis.requestIdleCallback = (fn) => fn();
|
|
2217
|
+
|
|
2218
|
+
fillRest();
|
|
2219
|
+
return { limit, steps: traj.steps.length, at: main.scrollTop };
|
|
2220
|
+
`);
|
|
2221
|
+
assert.strictEqual(out.limit >= out.steps, true, `stopped at ${out.limit} of ${out.steps}`);
|
|
2222
|
+
assert.strictEqual(out.at, 400, "the reader stays where they were");
|
|
2223
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|