Compare commits

..

4 Commits

Author SHA1 Message Date
nntrivi2001 4a82790734 chore: v2.8.35 — an HTML file's Preview sits beside Whole file 2026-10-29 10:47:39 +07:00
nntrivi2001 80000793f4 feat(files): an HTML file's Preview sits beside Whole file
The Changes panel's view toggle becomes Changes | Whole file | Preview for an
HTML file; the Preview/Raw button on the file's name row stays for markdown.
2026-10-29 10:47:39 +07:00
nntrivi2001 39bea78a9c chore: v2.8.34 — HTML page on Preview in the Changes panel (also from Changes) 2026-10-29 10:38:15 +07:00
nntrivi2001 364fe4a286 fix(files): an HTML file in the Changes panel shows as the page only on Preview
Changes stays the default view and Whole file shows the source, as for any
file. A Preview button — offered on the Changes diff too, where it was
missing — opens the page; Raw, Changes or Whole file go back to the source.
2026-10-29 10:38:15 +07:00
7 changed files with 81 additions and 18 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Claude Code Working Guide
_Last synced: v2.8.33 (2026-10-28) — an HTML file opens as the page (Files panel, Chat preview, File Explorer)._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. **Bump = replace the version/date and a ≤ 1-line summary; never append a changelog here** — details belong in the commit message and `docs/`, because this whole file is loaded into every session.
_Last synced: v2.8.35 (2026-10-29) — an HTML file's Preview sits beside Whole file in the Changes panel._ A change that makes any statement in this file, `AGENTS.md`, or a README wrong — a moved path, a new or changed extension point, a retired rule — updates that file in the same change-set and bumps this marker. **Bump = replace the version/date and a ≤ 1-line summary; never append a changelog here** — details belong in the commit message and `docs/`, because this whole file is loaded into every session.
## Project mission
- Maintain a reliable local-first dashboard for Claude Code session monitoring.
@@ -14,8 +14,9 @@
* is unsaved): Changes folds the unchanged lines away, Whole file
* shows them; any other file opens on its read-only diff; a
* whole markdown file can also be read rendered (Preview), an HTML
* file opens as the page itself (`HtmlFrame`, its relative CSS,
* scripts and images read like downloads), and an
* file shown as the page by Preview beside Whole file
* (`HtmlFrame`, its relative CSS, scripts and images read like
* downloads), and an
* image file is shown as the picture (`GET …/git/raw`)
* history — recent commits; opening one shows that commit's diff
* browse — quick-open by path, or the repo's tracked file tree (the
@@ -891,9 +892,9 @@ export function LaneChangesPanel({
const [whole, setWhole] = useState(false);
// A whole markdown or HTML file shown rendered instead of as source; per
// file, markdown opening as Settings → Appearance says
// (`lib/markdownView.ts`), an HTML page always as the page.
// (`lib/markdownView.ts`), an HTML page only once Preview is clicked.
const [mdPreview, setMdPreview] = useState(readMarkdownPreview);
useEffect(() => setMdPreview(isHtml(openFile) || readMarkdownPreview()), [openFile]);
useEffect(() => setMdPreview(!isHtml(openFile) && readMarkdownPreview()), [openFile]);
const [data, setData] = useState<LaneChanges | null>(null);
// A folder that is not a repo (an empty new workspace, a parent of clones)
// has no changes, review or history — only its files, which Files reads
@@ -2060,6 +2061,8 @@ export function LaneChangesPanel({
const dirty = draft !== null && draft !== text?.lines.join("\n");
// Rendered only where the toggle is offered: a whole markdown or HTML file.
const mdShown = mdPreview && whole && (isMarkdown(openFile) || isHtml(openFile));
// The HTML page is showing (the Preview view of the Changes / Whole file / Preview group).
const htmlShown = mdShown && isHtml(openFile);
// An HTML page's relative CSS/scripts/images, read like a download — only
// from the working tree: a commit's page would get today's files.
const htmlFetch = useMemo(
@@ -2559,8 +2562,11 @@ export function LaneChangesPanel({
<button
key={String(w)}
type="button"
aria-pressed={whole === w}
aria-pressed={whole === w && !htmlShown}
onClick={() => {
// Changes and Whole file show an HTML page's source;
// only Preview runs it.
if (htmlShown) setMdPreview(false);
if (whole === w) return;
// Both views of a working file are the same editor: the
// draft goes along, unsaved or not.
@@ -2574,7 +2580,7 @@ export function LaneChangesPanel({
});
}}
className={`rounded px-2 py-0.5 text-[11px] transition-colors ${
whole === w
whole === w && !htmlShown
? "bg-surface-4 text-fg-primary"
: "text-fg-secondary hover:text-fg-primary"
}`}
@@ -2582,6 +2588,26 @@ export function LaneChangesPanel({
{t(w ? "changes.wholeFile" : "changes.changesView")}
</button>
))}
{/* An HTML file's third view: the page itself. */}
{isHtml(openFile) && (
<button
type="button"
data-testid="lane-file-preview"
aria-pressed={htmlShown}
disabled={dirty}
onClick={() => {
setMdPreview(true);
setWhole(true);
}}
className={`rounded px-2 py-0.5 text-[11px] transition-colors disabled:cursor-not-allowed disabled:opacity-40 ${
htmlShown
? "bg-surface-4 text-fg-primary"
: "text-fg-secondary hover:text-fg-primary"
}`}
>
{t("changes.preview")}
</button>
)}
</div>
)}
</div>
@@ -2993,7 +3019,8 @@ export function LaneChangesPanel({
{t("changes.annotate")}
</button>
)}
{whole && text && !dirty && (isMarkdown(openFile) || isHtml(openFile)) && (
{/* An HTML file's Preview sits beside Whole file instead. */}
{whole && text && !dirty && isMarkdown(openFile) && (
<button
type="button"
data-testid="lane-file-preview"
@@ -544,7 +544,7 @@ describe("LaneChangesPanel", () => {
expect(screen.queryByTestId("lane-file-editor")).toBeNull();
});
it("opens a whole HTML file as the page, its source after Raw", async () => {
it("opens a whole HTML file as its source, the page after Preview, the source after Whole file", async () => {
vi.mocked(api.lanes.gitTree).mockResolvedValue({
available: true,
path: "",
@@ -564,12 +564,47 @@ describe("LaneChangesPanel", () => {
render(<LaneChangesPanel laneId={7} full />);
const sidebar = await screen.findByTestId("lane-changes-sidebar");
await user.click(await within(sidebar).findByText("deck.html"));
expect(await screen.findByTestId("lane-file-editor")).toHaveValue("<p>deck</p>");
expect(screen.queryByTestId("html-frame")).toBeNull();
await user.click(screen.getByTestId("lane-file-preview"));
const frame = await screen.findByTestId("html-frame");
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
expect(screen.getByRole("button", { name: "Preview", pressed: true })).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Whole file", pressed: false }));
expect(await screen.findByTestId("lane-file-editor")).toHaveValue("<p>deck</p>");
expect(screen.queryByTestId("html-frame")).toBeNull();
});
it("opens a changed HTML file on its Changes, the page only after Preview", async () => {
vi.mocked(api.lanes.changes).mockResolvedValue(
changes({
files: [
{ path: "deck.html", status: "??", untracked: true, added: 1, deleted: 0, binary: false },
],
})
);
vi.mocked(api.lanes.gitFile).mockResolvedValue({
available: true,
path: "deck.html",
rev: "work",
binary: false,
truncated: false,
lines: ["<p>deck</p>"],
sha: "c".repeat(40),
});
const user = userEvent.setup();
render(<LaneChangesPanel laneId={7} />);
await user.click(await screen.findByTitle("deck.html"));
await screen.findByRole("button", { name: "Changes", pressed: true });
expect(screen.queryByTestId("html-frame")).toBeNull();
await user.click(await screen.findByTestId("lane-file-preview"));
const frame = await screen.findByTestId("html-frame");
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
expect(frame.getAttribute("sandbox")).not.toContain("allow-same-origin");
await user.click(screen.getByTestId("lane-file-preview"));
expect(await screen.findByTestId("lane-file-editor")).toHaveValue("<p>deck</p>");
expect(screen.queryByTestId("html-frame")).toBeNull();
// Back to Changes: the diff again, not the page.
await user.click(screen.getByRole("button", { name: "Changes", pressed: false }));
await screen.findByRole("button", { name: "Changes", pressed: true });
await waitFor(() => expect(screen.queryByTestId("html-frame")).toBeNull());
});
it("shows an image file as the picture, with no edit control", async () => {
@@ -7316,7 +7316,7 @@ exports[`screen snapshots > Settings — about 1`] = `
<span
class="text-status-warning/90"
>
UI build v2.8.33 (rebuild client to match)
UI build v2.8.35 (rebuild client to match)
</span>
</p>
</div>
+3 -2
View File
@@ -678,8 +678,9 @@ reads the workspace's git surface in five modes:
least one line tall (`MIN_ROW`), so an empty filler row keeps the two
columns aligned. An image file (png, jpg, gif, webp, avif, bmp, ico, svg) is
shown as the picture (`/git/raw`), an HTML file (`.html`/`.htm`) opens
as the page itself — **Raw** shows its source in the editor, **Preview**
brings the page back — and a conflicted file opens in the
like any file (Changes, or Whole file as source), and a third view
beside them, **Changes | Whole file | Preview**, shows it as the page —
and a conflicted file opens in the
conflict view. The page (here, in a Chat file preview, and on a
double-click in the File Explorer, whose right-click menu keeps
**Download**) runs in the chat artifact's sandbox (`HtmlFrame.tsx`:
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "agenthub",
"version": "2.8.33",
"version": "2.8.35",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "agenthub",
"version": "2.8.33",
"version": "2.8.35",
"hasInstallScript": true,
"license": "UNLICENSED",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "agenthub",
"version": "2.8.33",
"version": "2.8.35",
"bin": {
"agenthub": "bin/agenthub.js"
},