Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db5a7776cc | |||
| 6c2c094f64 |
@@ -1,6 +1,6 @@
|
|||||||
# Claude Code Working Guide
|
# Claude Code Working Guide
|
||||||
|
|
||||||
_Last synced: v2.8.36 (2026-10-29) — an HTML page opens in a modal over the screen (Changes Preview, Chat)._ 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.37 (2026-10-29) — the Artifacts page's installs list floats over the page, never clipped._ 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.
|
||||||
|
|||||||
@@ -48,7 +48,8 @@
|
|||||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useCallback, useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@@ -1629,12 +1630,41 @@ function DetailDialog({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** "N installs" on a card; a click lists who installed it, when, and where. */
|
/** "N installs" on a card; a click lists who installed it, when, and where.
|
||||||
|
* The list floats over the page (a portal at the button, below it or above
|
||||||
|
* when there is no room, kept inside the window): inside the card list's
|
||||||
|
* `overflow-hidden` it was cut off at the last card. */
|
||||||
function InstallsButton({ item }: { item: Artifact }) {
|
function InstallsButton({ item }: { item: Artifact }) {
|
||||||
const { t } = useTranslation("artifacts");
|
const { t } = useTranslation("artifacts");
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [data, setData] = useState<ArtifactInstalls | null>(null);
|
const [data, setData] = useState<ArtifactInstalls | null>(null);
|
||||||
const box = useRef<HTMLSpanElement>(null);
|
const box = useRef<HTMLSpanElement>(null);
|
||||||
|
const pop = useRef<HTMLDivElement>(null);
|
||||||
|
const [at, setAt] = useState<{ left: number; top: number } | null>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!open) return setAt(null);
|
||||||
|
const place = () => {
|
||||||
|
const b = box.current?.getBoundingClientRect();
|
||||||
|
const p = pop.current;
|
||||||
|
if (!b || !p) return;
|
||||||
|
const w = p.offsetWidth;
|
||||||
|
const h = p.offsetHeight;
|
||||||
|
const gap = 4;
|
||||||
|
const below = b.bottom + gap + h <= window.innerHeight - 8 || b.top - gap - h < 8;
|
||||||
|
setAt({
|
||||||
|
left: Math.max(8, Math.min(b.left, window.innerWidth - w - 8)),
|
||||||
|
top: below ? b.bottom + gap : b.top - gap - h,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
place();
|
||||||
|
window.addEventListener("resize", place);
|
||||||
|
window.addEventListener("scroll", place, true);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", place);
|
||||||
|
window.removeEventListener("scroll", place, true);
|
||||||
|
};
|
||||||
|
}, [open, data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
@@ -1647,7 +1677,8 @@ function InstallsButton({ item }: { item: Artifact }) {
|
|||||||
toast.error(err);
|
toast.error(err);
|
||||||
});
|
});
|
||||||
const onDown = (e: MouseEvent) => {
|
const onDown = (e: MouseEvent) => {
|
||||||
if (!box.current?.contains(e.target as Node)) setOpen(false);
|
const n = e.target as Node;
|
||||||
|
if (!box.current?.contains(n) && !pop.current?.contains(n)) setOpen(false);
|
||||||
};
|
};
|
||||||
const onKey = (e: KeyboardEvent) => {
|
const onKey = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape") setOpen(false);
|
if (e.key === "Escape") setOpen(false);
|
||||||
@@ -1670,12 +1701,15 @@ function InstallsButton({ item }: { item: Artifact }) {
|
|||||||
>
|
>
|
||||||
{t("installs", { count: item.installs })}
|
{t("installs", { count: item.installs })}
|
||||||
</button>
|
</button>
|
||||||
{open && (
|
{open &&
|
||||||
|
createPortal(
|
||||||
<div
|
<div
|
||||||
|
ref={pop}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label={t("installsTitle")}
|
aria-label={t("installsTitle")}
|
||||||
data-testid="artifact-installs"
|
data-testid="artifact-installs"
|
||||||
className="absolute left-0 z-30 mt-1 w-80 max-w-[80vw] rounded-lg border border-border bg-surface-1 p-2 shadow-xl"
|
style={at ? { left: at.left, top: at.top } : { left: 0, top: 0, visibility: "hidden" }}
|
||||||
|
className="fixed z-[60] w-80 max-w-[80vw] rounded-lg border border-border bg-surface-1 p-2 shadow-xl"
|
||||||
>
|
>
|
||||||
<p className="mb-1.5 px-1 text-xs font-medium text-fg-primary">{t("installsTitle")}</p>
|
<p className="mb-1.5 px-1 text-xs font-medium text-fg-primary">{t("installsTitle")}</p>
|
||||||
{data === null ? (
|
{data === null ? (
|
||||||
@@ -1728,7 +1762,8 @@ function InstallsButton({ item }: { item: Artifact }) {
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>,
|
||||||
|
document.body
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -362,6 +362,11 @@ describe("Artifacts", () => {
|
|||||||
expect(await within(box).findByText("/h/.claude/skills/deploy")).toBeInTheDocument();
|
expect(await within(box).findByText("/h/.claude/skills/deploy")).toBeInTheDocument();
|
||||||
expect(within(box).getByText(/khoa/)).toBeInTheDocument();
|
expect(within(box).getByText(/khoa/)).toBeInTheDocument();
|
||||||
expect(within(box).getByText(/1 earlier installs/)).toBeInTheDocument();
|
expect(within(box).getByText(/1 earlier installs/)).toBeInTheDocument();
|
||||||
|
// Over the page, outside the card list that clips its overflow — and a
|
||||||
|
// click inside it keeps it open.
|
||||||
|
expect(box.parentElement).toBe(document.body);
|
||||||
|
await user.click(within(box).getByText(/khoa/));
|
||||||
|
expect(screen.getByTestId("artifact-installs")).toBeInTheDocument();
|
||||||
await user.keyboard("{Escape}");
|
await user.keyboard("{Escape}");
|
||||||
expect(screen.queryByTestId("artifact-installs")).toBeNull();
|
expect(screen.queryByTestId("artifact-installs")).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.36 (rebuild client to match)
|
UI build v2.8.37 (rebuild client to match)
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.36",
|
"version": "2.8.37",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.36",
|
"version": "2.8.37",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "agenthub",
|
"name": "agenthub",
|
||||||
"version": "2.8.36",
|
"version": "2.8.37",
|
||||||
"bin": {
|
"bin": {
|
||||||
"agenthub": "bin/agenthub.js"
|
"agenthub": "bin/agenthub.js"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user