diff --git a/CLAUDE.md b/CLAUDE.md
index 50f24265..3d6e6c45 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,6 @@
# Claude Code Working Guide
-_Last synced: v2.8.71 (2026-11-04) — artifacts shared with chosen people, per-account folders, and a viewable copy of every page._ 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.72 (2026-11-04) — an update puts back npm-rewritten lockfiles before its `--ff-only` merge; artifacts drag into folders._ 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.
diff --git a/client/src/i18n/locales/en/artifacts.json b/client/src/i18n/locales/en/artifacts.json
index 3e78145a..d1a75976 100644
--- a/client/src/i18n/locales/en/artifacts.json
+++ b/client/src/i18n/locales/en/artifacts.json
@@ -275,5 +275,9 @@
"pdf.failed": "This PDF could not be shown. Download it instead.",
"pdf.more": "Showing the first {{shown}} of {{total}} pages — download it for the rest.",
"exportsSwitch": "Keep a viewable copy of Docs and Slides",
- "exportsSwitchHint": "When Claude publishes a Claude Docs doc, a deck or a design from an AgentHub session, it is asked to also save a copy (Markdown, PDF or HTML) that the Hub shows to the people it is shared with — they need no claude.ai access. Applies to sessions started afterwards."
+ "exportsSwitchHint": "When Claude publishes a Claude Docs doc, a deck or a design from an AgentHub session, it is asked to also save a copy (Markdown, PDF or HTML) that the Hub shows to the people it is shared with — they need no claude.ai access. Applies to sessions started afterwards.",
+ "folders.moved": "Moved to “{{name}}”",
+ "folders.takenOut": "Taken out of its folder",
+ "folders.dropHint": "Drop an artifact here to put it in this folder",
+ "folders.dropOutHint": "Drop an artifact here to take it out of its folder"
}
diff --git a/client/src/i18n/locales/vi/artifacts.json b/client/src/i18n/locales/vi/artifacts.json
index 907acc81..b902bffc 100644
--- a/client/src/i18n/locales/vi/artifacts.json
+++ b/client/src/i18n/locales/vi/artifacts.json
@@ -273,5 +273,9 @@
"pdf.failed": "Không hiển thị được PDF này. Hãy tải về.",
"pdf.more": "Đang hiện {{shown}}/{{total}} trang đầu — tải về để xem hết.",
"exportsSwitch": "Giữ bản xem được của Docs và Slides",
- "exportsSwitchHint": "Khi Claude tạo một Claude Docs, slide hoặc design trong phiên AgentHub, Claude được nhắc lưu thêm một bản sao (Markdown, PDF hoặc HTML) để Hub hiển thị cho người được chia sẻ — họ không cần quyền trên claude.ai. Áp dụng cho các phiên mở sau đó."
+ "exportsSwitchHint": "Khi Claude tạo một Claude Docs, slide hoặc design trong phiên AgentHub, Claude được nhắc lưu thêm một bản sao (Markdown, PDF hoặc HTML) để Hub hiển thị cho người được chia sẻ — họ không cần quyền trên claude.ai. Áp dụng cho các phiên mở sau đó.",
+ "folders.moved": "Đã chuyển vào “{{name}}”",
+ "folders.takenOut": "Đã bỏ khỏi thư mục",
+ "folders.dropHint": "Thả artifact vào đây để đưa vào thư mục này",
+ "folders.dropOutHint": "Thả artifact vào đây để bỏ khỏi thư mục"
}
diff --git a/client/src/pages/Artifacts.tsx b/client/src/pages/Artifacts.tsx
index 3b1f28e8..ebd90a8f 100644
--- a/client/src/pages/Artifacts.tsx
+++ b/client/src/pages/Artifacts.tsx
@@ -131,6 +131,17 @@ const PREVIEWABLE =
const PDF = /\.pdf$/i;
/** What a row can do, worked out once for its card, menu and detail sheet. */
+/** What a dragged artifact carries: its id, for a folder chip to take. */
+const DRAG_TYPE = "application/x-agenthub-artifact";
+const dragProps = (a: Artifact) => ({
+ draggable: true,
+ onDragStart: (e: React.DragEvent) => {
+ e.dataTransfer.setData(DRAG_TYPE, String(a.id));
+ e.dataTransfer.setData("text/plain", a.title);
+ e.dataTransfer.effectAllowed = "move";
+ },
+});
+
/** Shared at all: with everyone (the Hub) or with chosen people. */
const isShared = (a: Artifact) => a.shared || (a.sharedWith?.length ?? 0) > 0;
@@ -834,6 +845,11 @@ export function Artifacts() {
active={activeFolder}
counts={(id) => (loaded ?? []).filter((a) => folders.of[a.id] === id).length}
onPick={setFolder}
+ onDropItem={(id, fid) => {
+ moveToFolder(id, fid);
+ const f = fid ? folders.folders.find((x) => x.id === fid) : null;
+ toast.success(f ? t("folders.moved", { name: f.name }) : t("folders.takenOut"));
+ }}
/>
{items === null ? (
@@ -1539,6 +1555,7 @@ function ArtifactRow({ inPack, ...props }: ItemProps & { inPack?: boolean }) {
className="flex items-center gap-3 px-3 py-2.5 transition-colors hover:bg-surface-2/60"
data-testid={`artifact-${a.id}`}
onContextMenu={(e) => menu.open(e, null)}
+ {...dragProps(a)}
>