Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a82790734 | |||
| 80000793f4 |
@@ -1,6 +1,6 @@
|
|||||||
# Claude Code Working Guide
|
# Claude Code Working Guide
|
||||||
|
|
||||||
_Last synced: v2.8.34 (2026-10-29) — the Changes panel shows an HTML page only on Preview (also from Changes)._ 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
|
## Project mission
|
||||||
- Maintain a reliable local-first dashboard for Claude Code session monitoring.
|
- Maintain a reliable local-first dashboard for Claude Code session monitoring.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* is unsaved): Changes folds the unchanged lines away, Whole file
|
* is unsaved): Changes folds the unchanged lines away, Whole file
|
||||||
* shows them; any other file opens on its read-only diff; a
|
* shows them; any other file opens on its read-only diff; a
|
||||||
* whole markdown file can also be read rendered (Preview), an HTML
|
* whole markdown file can also be read rendered (Preview), an HTML
|
||||||
* file shown as the page on Preview, from Changes too
|
* file shown as the page by Preview beside Whole file
|
||||||
* (`HtmlFrame`, its relative CSS, scripts and images read like
|
* (`HtmlFrame`, its relative CSS, scripts and images read like
|
||||||
* downloads), and an
|
* downloads), and an
|
||||||
* image file is shown as the picture (`GET …/git/raw`)
|
* image file is shown as the picture (`GET …/git/raw`)
|
||||||
@@ -2061,6 +2061,8 @@ export function LaneChangesPanel({
|
|||||||
const dirty = draft !== null && draft !== text?.lines.join("\n");
|
const dirty = draft !== null && draft !== text?.lines.join("\n");
|
||||||
// Rendered only where the toggle is offered: a whole markdown or HTML file.
|
// Rendered only where the toggle is offered: a whole markdown or HTML file.
|
||||||
const mdShown = mdPreview && whole && (isMarkdown(openFile) || isHtml(openFile));
|
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
|
// 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.
|
// from the working tree: a commit's page would get today's files.
|
||||||
const htmlFetch = useMemo(
|
const htmlFetch = useMemo(
|
||||||
@@ -2560,12 +2562,12 @@ export function LaneChangesPanel({
|
|||||||
<button
|
<button
|
||||||
key={String(w)}
|
key={String(w)}
|
||||||
type="button"
|
type="button"
|
||||||
aria-pressed={whole === w}
|
aria-pressed={whole === w && !htmlShown}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (whole === w) return;
|
|
||||||
// Changes and Whole file show an HTML page's source;
|
// Changes and Whole file show an HTML page's source;
|
||||||
// only Preview runs it.
|
// only Preview runs it.
|
||||||
if (isHtml(openFile)) setMdPreview(false);
|
if (htmlShown) setMdPreview(false);
|
||||||
|
if (whole === w) return;
|
||||||
// Both views of a working file are the same editor: the
|
// Both views of a working file are the same editor: the
|
||||||
// draft goes along, unsaved or not.
|
// draft goes along, unsaved or not.
|
||||||
if (editable && draft !== null) return setWhole(w);
|
if (editable && draft !== null) return setWhole(w);
|
||||||
@@ -2578,7 +2580,7 @@ export function LaneChangesPanel({
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className={`rounded px-2 py-0.5 text-[11px] transition-colors ${
|
className={`rounded px-2 py-0.5 text-[11px] transition-colors ${
|
||||||
whole === w
|
whole === w && !htmlShown
|
||||||
? "bg-surface-4 text-fg-primary"
|
? "bg-surface-4 text-fg-primary"
|
||||||
: "text-fg-secondary hover:text-fg-primary"
|
: "text-fg-secondary hover:text-fg-primary"
|
||||||
}`}
|
}`}
|
||||||
@@ -2586,6 +2588,26 @@ export function LaneChangesPanel({
|
|||||||
{t(w ? "changes.wholeFile" : "changes.changesView")}
|
{t(w ? "changes.wholeFile" : "changes.changesView")}
|
||||||
</button>
|
</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>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -2997,21 +3019,17 @@ export function LaneChangesPanel({
|
|||||||
{t("changes.annotate")}
|
{t("changes.annotate")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{/* Offered on Changes too: from there it opens the rendered whole file. */}
|
{/* An HTML file's Preview sits beside Whole file instead. */}
|
||||||
{(!whole || text) && !dirty && (isMarkdown(openFile) || isHtml(openFile)) && (
|
{whole && text && !dirty && isMarkdown(openFile) && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
data-testid="lane-file-preview"
|
data-testid="lane-file-preview"
|
||||||
aria-pressed={whole && mdPreview}
|
aria-pressed={mdPreview}
|
||||||
onClick={() => {
|
onClick={() => setMdPreview((v) => !v)}
|
||||||
if (whole) return setMdPreview((v) => !v);
|
|
||||||
setMdPreview(true);
|
|
||||||
setWhole(true);
|
|
||||||
}}
|
|
||||||
className="flex shrink-0 items-center gap-1 self-center rounded border border-border-light px-1.5 py-0.5 text-[11px] text-fg-secondary hover:bg-surface-2 hover:text-fg-primary"
|
className="flex shrink-0 items-center gap-1 self-center rounded border border-border-light px-1.5 py-0.5 text-[11px] text-fg-secondary hover:bg-surface-2 hover:text-fg-primary"
|
||||||
>
|
>
|
||||||
{whole && mdPreview ? <Code2 className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
|
{mdPreview ? <Code2 className="h-3 w-3" /> : <Eye className="h-3 w-3" />}
|
||||||
{whole && mdPreview ? t("changes.raw") : t("changes.preview")}
|
{mdPreview ? t("changes.raw") : t("changes.preview")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{!local && range !== "commit" && openFile && (
|
{!local && range !== "commit" && openFile && (
|
||||||
|
|||||||
@@ -544,7 +544,7 @@ describe("LaneChangesPanel", () => {
|
|||||||
expect(screen.queryByTestId("lane-file-editor")).toBeNull();
|
expect(screen.queryByTestId("lane-file-editor")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens a whole HTML file as its source, the page after Preview, the 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({
|
vi.mocked(api.lanes.gitTree).mockResolvedValue({
|
||||||
available: true,
|
available: true,
|
||||||
path: "",
|
path: "",
|
||||||
@@ -569,7 +569,8 @@ describe("LaneChangesPanel", () => {
|
|||||||
await user.click(screen.getByTestId("lane-file-preview"));
|
await user.click(screen.getByTestId("lane-file-preview"));
|
||||||
const frame = await screen.findByTestId("html-frame");
|
const frame = await screen.findByTestId("html-frame");
|
||||||
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
|
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
|
||||||
await user.click(screen.getByTestId("lane-file-preview"));
|
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(await screen.findByTestId("lane-file-editor")).toHaveValue("<p>deck</p>");
|
||||||
expect(screen.queryByTestId("html-frame")).toBeNull();
|
expect(screen.queryByTestId("html-frame")).toBeNull();
|
||||||
});
|
});
|
||||||
@@ -600,9 +601,9 @@ describe("LaneChangesPanel", () => {
|
|||||||
const frame = await screen.findByTestId("html-frame");
|
const frame = await screen.findByTestId("html-frame");
|
||||||
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
|
expect(frame.getAttribute("srcdoc")).toContain("<p>deck</p>");
|
||||||
expect(frame.getAttribute("sandbox")).not.toContain("allow-same-origin");
|
expect(frame.getAttribute("sandbox")).not.toContain("allow-same-origin");
|
||||||
// Back to Changes, then Whole file: the source again, not the page.
|
// Back to Changes: the diff again, not the page.
|
||||||
await user.click(screen.getByRole("button", { name: "Changes", pressed: false }));
|
await user.click(screen.getByRole("button", { name: "Changes", pressed: false }));
|
||||||
await user.click(screen.getByRole("button", { name: "Whole file", pressed: false }));
|
await screen.findByRole("button", { name: "Changes", pressed: true });
|
||||||
await waitFor(() => expect(screen.queryByTestId("html-frame")).toBeNull());
|
await waitFor(() => expect(screen.queryByTestId("html-frame")).toBeNull());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7316,7 +7316,7 @@ exports[`screen snapshots > Settings — about 1`] = `
|
|||||||
<span
|
<span
|
||||||
class="text-status-warning/90"
|
class="text-status-warning/90"
|
||||||
>
|
>
|
||||||
UI build v2.8.34 (rebuild client to match)
|
UI build v2.8.35 (rebuild client to match)
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
-3
@@ -678,9 +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
|
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
|
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
|
shown as the picture (`/git/raw`), an HTML file (`.html`/`.htm`) opens
|
||||||
like any file (Changes, or Whole file as source) with a **Preview**
|
like any file (Changes, or Whole file as source), and a third view
|
||||||
button — on Changes too — that shows it as the page; **Raw** goes back to
|
beside them, **Changes | Whole file | Preview**, shows it as the page —
|
||||||
its source — and a conflicted file opens in the
|
and a conflicted file opens in the
|
||||||
conflict view. The page (here, in a Chat file preview, and on a
|
conflict view. The page (here, in a Chat file preview, and on a
|
||||||
double-click in the File Explorer, whose right-click menu keeps
|
double-click in the File Explorer, whose right-click menu keeps
|
||||||
**Download**) runs in the chat artifact's sandbox (`HtmlFrame.tsx`:
|
**Download**) runs in the chat artifact's sandbox (`HtmlFrame.tsx`:
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.34",
|
"version": "2.8.35",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.34",
|
"version": "2.8.35",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.34",
|
"version": "2.8.35",
|
||||||
"bin": {
|
"bin": {
|
||||||
"agenthub": "bin/agenthub.js"
|
"agenthub": "bin/agenthub.js"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user