Skip to content

Commit 0a516ae

Browse files
committed
rc4
1 parent 70542ba commit 0a516ae

8 files changed

Lines changed: 148 additions & 70 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "headroom-desktop",
3-
"version": "0.5.0-rc.3",
3+
"version": "0.5.0-rc.4",
44
"private": true,
55
"type": "module",
66
"scripts": {

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "headroom-desktop"
3-
version = "0.5.0-rc.3"
3+
version = "0.5.0-rc.4"
44
description = "Headroom v1 local-first LLM optimization tray app"
55
authors = ["Codex"]
66
license = "MIT"

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Headroom",
4-
"version": "0.5.0-rc.3",
4+
"version": "0.5.0-rc.4",
55
"identifier": "com.extraheadroom.headroom",
66
"build": {
77
"beforeDevCommand": "npm run dev",

src/components/ActivityFeed.test.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { renderToStaticMarkup } from "react-dom/server";
22
import { describe, expect, it, vi } from "vitest";
3-
import { ActivityFeed, diffLines, formatRequestMessages, groupTransforms } from "./ActivityFeed";
3+
import {
4+
ActivityFeed,
5+
collapseDiff,
6+
diffLines,
7+
formatRequestMessages,
8+
groupTransforms
9+
} from "./ActivityFeed";
410
import type {
511
ActivityFeedResponse,
612
ActivityFeedSnapshot,
@@ -853,3 +859,23 @@ describe("diffLines", () => {
853859
expect(diffLines(huge, huge)).toBeNull(); // 6001^2 ≈ 36M > 30M cap
854860
});
855861
});
862+
863+
describe("collapseDiff", () => {
864+
it("collapses long unchanged runs while keeping context around changes", () => {
865+
const original = Array.from({ length: 2000 }, (_, i) => String(i)).join("\n");
866+
const compressed = Array.from({ length: 2000 }, (_, i) =>
867+
i === 1000 ? "CHANGED" : String(i)
868+
).join("\n");
869+
const collapsed = collapseDiff(diffLines(original, compressed)!, 3);
870+
// The removed line survives and sits near the top of the collapsed output.
871+
const delIdx = collapsed.findIndex((l) => l.type === "del" && l.text === "1000");
872+
expect(delIdx).toBeGreaterThanOrEqual(0);
873+
expect(delIdx).toBeLessThan(10);
874+
// Thousands of identical lines are reduced to two skip markers.
875+
expect(collapsed.filter((l) => l.type === "skip").length).toBe(2);
876+
expect(collapsed.some((l) => l.type === "same" && l.text === "0")).toBe(false);
877+
// Context lines immediately around the change are preserved.
878+
expect(collapsed.some((l) => l.type === "same" && l.text === "999")).toBe(true);
879+
expect(collapsed.some((l) => l.type === "same" && l.text === "1001")).toBe(true);
880+
});
881+
});

src/components/ActivityFeed.tsx

Lines changed: 108 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,102 @@ export function diffLines(a: string, b: string): DiffLine[] | null {
341341
return out;
342342
}
343343

344+
export type CollapsedDiffLine = DiffLine | { type: "skip"; text: string };
345+
346+
// Keep `context` unchanged lines around each change and collapse the rest, so
347+
// the removed (red) / added (green) lines are visible the moment the row opens
348+
// instead of buried under hundreds of identical context lines.
349+
const DIFF_CONTEXT = 3;
350+
351+
export function collapseDiff(diff: DiffLine[], context = DIFF_CONTEXT): CollapsedDiffLine[] {
352+
const keep = new Array(diff.length).fill(false);
353+
for (let i = 0; i < diff.length; i++) {
354+
if (diff[i].type === "same") continue;
355+
for (let j = Math.max(0, i - context); j <= Math.min(diff.length - 1, i + context); j++) {
356+
keep[j] = true;
357+
}
358+
}
359+
const out: CollapsedDiffLine[] = [];
360+
let i = 0;
361+
while (i < diff.length) {
362+
if (diff[i].type !== "same" || keep[i]) {
363+
out.push(diff[i]);
364+
i++;
365+
continue;
366+
}
367+
let j = i;
368+
while (j < diff.length && diff[j].type === "same" && !keep[j]) j++;
369+
const n = j - i;
370+
out.push({ type: "skip", text: `... ${n} unchanged line${n === 1 ? "" : "s"}` });
371+
i = j;
372+
}
373+
return out;
374+
}
375+
376+
// Unified line diff of original vs compressed request bodies, so pruned content
377+
// (red) and inserted truncation markers (green) pop instead of two near-identical
378+
// dumps. Returns dt/dd fragment for the detail grid. Shared by the transformation
379+
// and record rows.
380+
function CompressionDiff({
381+
requestMessages,
382+
compressedMessages,
383+
inputTokensOriginal,
384+
inputTokensOptimized
385+
}: {
386+
requestMessages: TransformationRequestMessage[];
387+
compressedMessages: TransformationRequestMessage[];
388+
inputTokensOriginal?: number | null;
389+
inputTokensOptimized?: number | null;
390+
}) {
391+
const original = formatRequestMessages(requestMessages);
392+
const compressed = formatRequestMessages(compressedMessages);
393+
const diff = diffLines(original, compressed);
394+
if (!diff) {
395+
// Too large to diff — fall back to side-by-side dumps.
396+
return (
397+
<>
398+
<dt>Request (original)</dt>
399+
<dd>
400+
<pre className="activity-feed__message-dump">{original}</pre>
401+
</dd>
402+
<dt>Request (compressed)</dt>
403+
<dd>
404+
<pre className="activity-feed__message-dump">{compressed}</pre>
405+
</dd>
406+
</>
407+
);
408+
}
409+
return (
410+
<>
411+
<dt>
412+
Compression diff
413+
{inputTokensOriginal != null && inputTokensOptimized != null
414+
? ` (${inputTokensOriginal.toLocaleString()}${inputTokensOptimized.toLocaleString()} tokens)`
415+
: ""}
416+
</dt>
417+
<dd>
418+
<pre className="activity-feed__message-dump activity-feed__diff">
419+
{collapseDiff(diff).map((line, idx) => (
420+
<div
421+
key={idx}
422+
className={`activity-feed__diff-line activity-feed__diff-line--${line.type}`}
423+
>
424+
{line.type === "del"
425+
? "- "
426+
: line.type === "add"
427+
? "+ "
428+
: line.type === "skip"
429+
? ""
430+
: " "}
431+
{line.text}
432+
</div>
433+
))}
434+
</pre>
435+
</dd>
436+
</>
437+
);
438+
}
439+
344440
function TransformationRow({ event }: { event: TransformationFeedEvent }) {
345441
const saved = event.tokensSaved ?? 0;
346442
const pct = event.savingsPercent ?? 0;
@@ -409,55 +505,12 @@ function TransformationRow({ event }: { event: TransformationFeedEvent }) {
409505
</>
410506
) : null}
411507
{hasRequestMessages && hasCompressedMessages ? (
412-
// New proxy shape: both sides present. Render a unified line diff so
413-
// pruned content (red) and inserted truncation markers (green) pop —
414-
// the two near-identical dumps were hard to tell apart at a glance.
415-
(() => {
416-
const original = formatRequestMessages(event.requestMessages!);
417-
const compressed = formatRequestMessages(event.compressedMessages!);
418-
const diff = diffLines(original, compressed);
419-
const label = (
420-
<dt>
421-
Compression diff
422-
{event.inputTokensOriginal != null && event.inputTokensOptimized != null
423-
? ` (${event.inputTokensOriginal.toLocaleString()}${event.inputTokensOptimized.toLocaleString()} tokens)`
424-
: ""}
425-
</dt>
426-
);
427-
if (!diff) {
428-
// Too large to diff — fall back to side-by-side dumps.
429-
return (
430-
<>
431-
<dt>Request (original)</dt>
432-
<dd>
433-
<pre className="activity-feed__message-dump">{original}</pre>
434-
</dd>
435-
<dt>Request (compressed)</dt>
436-
<dd>
437-
<pre className="activity-feed__message-dump">{compressed}</pre>
438-
</dd>
439-
</>
440-
);
441-
}
442-
return (
443-
<>
444-
{label}
445-
<dd>
446-
<pre className="activity-feed__message-dump activity-feed__diff">
447-
{diff.map((line, idx) => (
448-
<div
449-
key={idx}
450-
className={`activity-feed__diff-line activity-feed__diff-line--${line.type}`}
451-
>
452-
{line.type === "del" ? "- " : line.type === "add" ? "+ " : " "}
453-
{line.text}
454-
</div>
455-
))}
456-
</pre>
457-
</dd>
458-
</>
459-
);
460-
})()
508+
<CompressionDiff
509+
requestMessages={event.requestMessages!}
510+
compressedMessages={event.compressedMessages!}
511+
inputTokensOriginal={event.inputTokensOriginal}
512+
inputTokensOptimized={event.inputTokensOptimized}
513+
/>
461514
) : hasRequestMessages ? (
462515
// Legacy proxy shape: only `requestMessages` exists. Its content may
463516
// actually be the post-compression list (field was inconsistent
@@ -785,20 +838,12 @@ function RecordRow({ event }: { event: RecordEvent }) {
785838
</>
786839
) : null}
787840
{hasRequestMessages && hasCompressedMessages ? (
788-
<>
789-
<dt>Request (original)</dt>
790-
<dd>
791-
<pre className="activity-feed__message-dump">
792-
{formatRequestMessages(event.requestMessages!)}
793-
</pre>
794-
</dd>
795-
<dt>Request (compressed)</dt>
796-
<dd>
797-
<pre className="activity-feed__message-dump">
798-
{formatRequestMessages(event.compressedMessages!)}
799-
</pre>
800-
</dd>
801-
</>
841+
<CompressionDiff
842+
requestMessages={event.requestMessages!}
843+
compressedMessages={event.compressedMessages!}
844+
inputTokensOriginal={event.inputTokensOriginal}
845+
inputTokensOptimized={event.inputTokensOptimized}
846+
/>
802847
) : hasRequestMessages ? (
803848
<>
804849
<dt>Request</dt>

src/styles.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5168,6 +5168,13 @@ html.window-hiding {
51685168
color: var(--accent-strong);
51695169
}
51705170

5171+
.activity-feed__diff-line--skip {
5172+
color: var(--text-muted);
5173+
font-style: italic;
5174+
text-align: center;
5175+
user-select: none;
5176+
}
5177+
51715178
.activity-feed__detail-list {
51725179
list-style: none;
51735180
margin: 0;

0 commit comments

Comments
 (0)