@zzusp/ccsm 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +7 -3
  2. package/bin/cli.mjs +52 -52
  3. package/dist/assets/DiskUsage-CKhggLs5.js +2 -0
  4. package/dist/assets/DiskUsage-CKhggLs5.js.map +1 -0
  5. package/dist/assets/{ImportPage-b8NORa8b.js → ImportPage-wge4VhZ-.js} +2 -2
  6. package/dist/assets/{ImportPage-b8NORa8b.js.map → ImportPage-wge4VhZ-.js.map} +1 -1
  7. package/dist/assets/{ProjectMemory-aSV8UzQ9.js → ProjectMemory-Q4XX40j_.js} +2 -2
  8. package/dist/assets/{ProjectMemory-aSV8UzQ9.js.map → ProjectMemory-Q4XX40j_.js.map} +1 -1
  9. package/dist/assets/{charts-A5eNHLjX.js → charts-jxJqXXUr.js} +2 -2
  10. package/dist/assets/{charts-A5eNHLjX.js.map → charts-jxJqXXUr.js.map} +1 -1
  11. package/dist/assets/index-7aMrnHJG.js +7 -0
  12. package/dist/assets/index-7aMrnHJG.js.map +1 -0
  13. package/dist/assets/index-BOeI_J4B.css +1 -0
  14. package/dist/assets/{query-C1K1uQRu.js → query-CS7JQ86v.js} +2 -2
  15. package/dist/assets/{query-C1K1uQRu.js.map → query-CS7JQ86v.js.map} +1 -1
  16. package/dist/assets/{react-W0jzChlo.js → react-CPkiFScu.js} +10 -10
  17. package/dist/assets/{react-W0jzChlo.js.map → react-CPkiFScu.js.map} +1 -1
  18. package/dist/assets/{router-DfbutHY3.js → router-DwaHAh1G.js} +2 -2
  19. package/dist/assets/{router-DfbutHY3.js.map → router-DwaHAh1G.js.map} +1 -1
  20. package/dist/assets/vendor-Cs8vYp-N.js +27 -0
  21. package/dist/assets/vendor-Cs8vYp-N.js.map +1 -0
  22. package/dist/favicon.svg +7 -7
  23. package/dist/index.html +6 -6
  24. package/package.json +83 -72
  25. package/server/index.ts +130 -126
  26. package/server/lib/active-sessions.test.ts +119 -0
  27. package/server/lib/bundle.test.ts +182 -0
  28. package/server/lib/claude-paths.test.ts +126 -0
  29. package/server/lib/claude-paths.ts +19 -12
  30. package/server/lib/cleanup-suggestions.ts +131 -0
  31. package/server/lib/constants.ts +1 -0
  32. package/server/lib/delete.test.ts +244 -0
  33. package/server/lib/delete.ts +5 -16
  34. package/server/lib/disk-usage.ts +6 -8
  35. package/server/lib/export-import-bundle.test.ts +337 -0
  36. package/server/lib/modified-files.test.ts +280 -0
  37. package/server/lib/modified-files.ts +228 -0
  38. package/server/lib/open-folder.ts +22 -15
  39. package/server/lib/parse-jsonl.ts +35 -3
  40. package/server/lib/safe-id.test.ts +41 -0
  41. package/server/lib/safe-remove.test.ts +73 -0
  42. package/server/lib/safe-remove.ts +25 -0
  43. package/server/lib/scan.ts +103 -0
  44. package/server/lib/update.ts +67 -0
  45. package/server/lib/version.test.ts +39 -0
  46. package/server/lib/version.ts +117 -0
  47. package/server/routes/disk-cleanup.ts +54 -0
  48. package/server/routes/sessions.ts +49 -0
  49. package/server/routes/version.ts +34 -0
  50. package/shared/constants.ts +5 -0
  51. package/shared/types.ts +152 -0
  52. package/dist/assets/DiskUsage-Bq4VaoUA.js +0 -2
  53. package/dist/assets/DiskUsage-Bq4VaoUA.js.map +0 -1
  54. package/dist/assets/index-DLATR3tZ.js +0 -5
  55. package/dist/assets/index-DLATR3tZ.js.map +0 -1
  56. package/dist/assets/index-DLDtbkux.css +0 -1
  57. package/dist/assets/vendor-CH80ylbS.js +0 -19
  58. package/dist/assets/vendor-CH80ylbS.js.map +0 -1
package/README.md CHANGED
@@ -28,14 +28,14 @@ A local web UI to view and clean up Claude Code session history stored under `~/
28
28
  | Page | What you can do |
29
29
  |---|---|
30
30
  | **Projects** (`/`) | See every Claude Code project (one per `cwd`) with session count, total bytes on disk, last-activity time. |
31
- | **Project detail** (`/projects/:id`) | Browse all sessions in one project. Multi-select + cascade-delete. Each row shows title, message count, byte breakdown, and a status badge (`live · pid N` / `recently active` / `idle`). Inline rename appends a `custom-title` record to the session's `.jsonl` (refused while a live PID owns the session). *Open folder* reveals the project's working directory in the OS file manager (Explorer / Finder / `xdg-open`). *Export* bundles the selected sessions (or all) plus the project's memory into a portable folder. |
31
+ | **Project detail** (`/projects/:id`) | Browse all sessions in one project. Multi-select + cascade-delete. Each row shows title, message count, byte breakdown, and a status badge (`working` / `live · pid N` / `recently active` / `idle`) — *working* narrows *live* to a session Claude is actively processing right now (live PID + an unfinished last turn). Inline rename appends a `custom-title` record to the session's `.jsonl` (refused while a live PID owns the session). *Open folder* reveals the project's working directory in the OS file manager (Explorer / Finder / `xdg-open`). *Export* bundles the selected sessions (or all) plus the project's memory into a portable folder. |
32
32
  | **Import** (`/import`) | Bring a project's sessions + memory from another device. The bundle is path-independent; on import you pick the local target folder and paths are remapped to this machine. A dry-run preview shows exactly what will be created / skipped / overwritten before any write. See [Cross-device sharing](#cross-device-sharing). |
33
- | **Session detail** (`/projects/:id/sessions/:sid`) | Full message timeline: text, tool calls (collapsible), tool results, thinking blocks. Sticky search bar with client-side highlight. Toggle to show or hide system messages (`<command-name>` etc.). Inline *Delete* (top-right of the masthead) removes the current session and returns to the project list. |
33
+ | **Session detail** (`/projects/:id/sessions/:sid`) | Full message timeline: text, tool calls (collapsible), tool results, thinking blocks. Sticky search bar with client-side highlight. Toggle to show or hide system messages (`<command-name>` etc.). Inline *Delete* (top-right of the masthead) removes the current session and returns to the project list. While the session is live the timeline auto-tails newly written messages; while Claude is actively processing, a *working* masthead badge and a trailing “Claude is working…” row mark the open turn. |
34
34
  | **Project memory** (`/projects/:id/memory`) | Two-pane reader for `~/.claude/projects/<encoded-cwd>/memory/`: searchable file list (sort by index / recent / name / size) on the left, rendered Markdown on the right, with `MEMORY.md` pinned as the index. |
35
35
  | **Disk usage** (`/disk`) | Pie chart by project, monthly bar chart, top-20 largest sessions with deep links. |
36
36
  | **Cross-session search** (⌘K / Ctrl+K) | Global modal that streams matches from every project as you type. Searches text, tool calls, and thinking blocks; each result deep-links into the session at the matched message. |
37
37
 
38
- The persistent sidebar carries the search trigger plus locale (zh / en) and theme (light / dark) toggles. The HTTP listener exposes the same content as a single SPA, so deep links like `/projects/:id/sessions/:sid?q=foo` are sharable between browser tabs on the same machine.
38
+ The persistent sidebar carries the search trigger plus locale (zh / en) and theme (light / dark) toggles, and a **version indicator** at the bottom. On open it checks GitHub for the latest release (cached for an hour); when a newer version exists it shows an amber "new version" pill. Clicking it opens a dialog with the release notes and links to the release page and repository, plus a one-click **Update now** that runs `npm install -g @zzusp/ccsm@latest` on the server and prompts you to restart `ccsm`. If the check fails (offline, or no GitHub release published yet) it silently falls back to showing just the current version. The HTTP listener exposes the same content as a single SPA, so deep links like `/projects/:id/sessions/:sid?q=foo` are sharable between browser tabs on the same machine.
39
39
 
40
40
  ## Quick start
41
41
 
@@ -227,6 +227,10 @@ There is no authentication. If you're paranoid, run behind a firewall rule or on
227
227
 
228
228
  See [`docs/spec/session-manager-design.md`](docs/spec/session-manager-design.md) for the full design rationale (data model, routing decisions, cross-platform strategy, future work).
229
229
 
230
+ ## Contributing & releases
231
+
232
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by a `commit-msg` hook), versions follow [SemVer](https://semver.org/), and releases are cut locally with `release-it`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [docs/spec/release-process.md](docs/spec/release-process.md) for the full policy.
233
+
230
234
  ## License
231
235
 
232
236
  MIT — see [LICENSE](LICENSE).
package/bin/cli.mjs CHANGED
@@ -1,52 +1,52 @@
1
- #!/usr/bin/env node
2
- // ccsm — Claude Code Session Manager CLI launcher.
3
- //
4
- // Plain JS so it runs under a bare `node` (global install / npx) with no build
5
- // step. --help/--version are answered here without loading the server. For the
6
- // real thing we register tsx's ESM loader (resolved from THIS package, so it
7
- // works regardless of the user's cwd) and import the TypeScript server entry —
8
- // the same path `npm run start` (`tsx server/index.ts`) takes.
9
- import { readFileSync } from 'node:fs';
10
- import { dirname, join } from 'node:path';
11
- import { fileURLToPath, pathToFileURL } from 'node:url';
12
-
13
- const __dirname = dirname(fileURLToPath(import.meta.url));
14
- const pkgRoot = join(__dirname, '..');
15
- const args = process.argv.slice(2);
16
-
17
- if (args.includes('-v') || args.includes('--version')) {
18
- const { version } = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'));
19
- console.log(version);
20
- process.exit(0);
21
- }
22
-
23
- if (args.includes('-h') || args.includes('--help')) {
24
- printHelp();
25
- process.exit(0);
26
- }
27
-
28
- const { register } = await import('tsx/esm/api');
29
- register();
30
- await import(pathToFileURL(join(pkgRoot, 'server', 'index.ts')).href);
31
-
32
- function printHelp() {
33
- console.log(`ccsm — Claude Code Session Manager
34
-
35
- A local web UI to browse and clean up your Claude Code session history (~/.claude/).
36
-
37
- Usage:
38
- ccsm [options]
39
-
40
- Options:
41
- -p, --port <number> Port to listen on. Default: first free port in 3131-3140.
42
- If the given port is busy, ccsm exits (it won't pick another).
43
- --host <host> Host to bind. Default: 127.0.0.1 (loopback only).
44
- Pass 0.0.0.0 to expose the UI on your LAN. There is NO
45
- authentication, so only do this on a network you trust.
46
- -o, --open Open the UI in your default browser once it's listening.
47
- -h, --help Show this help and exit.
48
- -v, --version Print the version and exit.
49
-
50
- The server binds to 127.0.0.1 by default and is unreachable from the network.
51
- Once it's up, open http://127.0.0.1:<port> in your browser.`);
52
- }
1
+ #!/usr/bin/env node
2
+ // ccsm — Claude Code Session Manager CLI launcher.
3
+ //
4
+ // Plain JS so it runs under a bare `node` (global install / npx) with no build
5
+ // step. --help/--version are answered here without loading the server. For the
6
+ // real thing we register tsx's ESM loader (resolved from THIS package, so it
7
+ // works regardless of the user's cwd) and import the TypeScript server entry —
8
+ // the same path `npm run start` (`tsx server/index.ts`) takes.
9
+ import { readFileSync } from 'node:fs';
10
+ import { dirname, join } from 'node:path';
11
+ import { fileURLToPath, pathToFileURL } from 'node:url';
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const pkgRoot = join(__dirname, '..');
15
+ const args = process.argv.slice(2);
16
+
17
+ if (args.includes('-v') || args.includes('--version')) {
18
+ const { version } = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'));
19
+ console.log(version);
20
+ process.exit(0);
21
+ }
22
+
23
+ if (args.includes('-h') || args.includes('--help')) {
24
+ printHelp();
25
+ process.exit(0);
26
+ }
27
+
28
+ const { register } = await import('tsx/esm/api');
29
+ register();
30
+ await import(pathToFileURL(join(pkgRoot, 'server', 'index.ts')).href);
31
+
32
+ function printHelp() {
33
+ console.log(`ccsm — Claude Code Session Manager
34
+
35
+ A local web UI to browse and clean up your Claude Code session history (~/.claude/).
36
+
37
+ Usage:
38
+ ccsm [options]
39
+
40
+ Options:
41
+ -p, --port <number> Port to listen on. Default: first free port in 3131-3140.
42
+ If the given port is busy, ccsm exits (it won't pick another).
43
+ --host <host> Host to bind. Default: 127.0.0.1 (loopback only).
44
+ Pass 0.0.0.0 to expose the UI on your LAN. There is NO
45
+ authentication, so only do this on a network you trust.
46
+ -o, --open Open the UI in your default browser once it's listening.
47
+ -h, --help Show this help and exit.
48
+ -v, --version Print the version and exit.
49
+
50
+ The server binds to 127.0.0.1 by default and is unreachable from the network.
51
+ Once it's up, open http://127.0.0.1:<port> in your browser.`);
52
+ }
@@ -0,0 +1,2 @@
1
+ import{j as e,r as x}from"./react-CPkiFScu.js";import{u as q,b as H,a as O}from"./query-CS7JQ86v.js";import{u as p,L as K,f as _,a as c,q as y,b as S,s as Q,c as b,M as N,S as $}from"./index-7aMrnHJG.js";import{L as v}from"./router-DwaHAh1G.js";import{H as h}from"./vendor-Cs8vYp-N.js";import{R as E,P as V,a as W,C as X,T as L,A as Y,X as G,Y as J,b as Z}from"./charts-jxJqXXUr.js";function ee(){const s=p(),{data:t,isLoading:a,error:o}=q({queryKey:y.diskCleanupSuggestions(),queryFn:()=>S("/api/disk-cleanup/suggestions")});return e.jsxs("section",{className:"surface-card mt-12 p-6",children:[e.jsxs("header",{className:"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",children:[e.jsx("h2",{className:"font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]",children:s("cleanup.title")}),e.jsx("p",{className:"min-w-0 flex-1 font-display text-[13px] italic leading-snug text-[var(--color-fg-muted)]",children:s("cleanup.tagline")})]}),e.jsx("div",{className:"rule-dotted mt-3","aria-hidden":!0}),a&&e.jsx(K,{label:s("common.computing"),className:"mt-6"}),o&&e.jsx("p",{className:"mt-6 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-4 py-3 text-sm text-[var(--color-danger)]",children:s("cleanup.failed",{msg:o.message})}),t&&e.jsxs("div",{className:"mt-6 space-y-8",children:[e.jsx(te,{data:t.largeSessions}),e.jsx(R,{kind:"file-history",title:s("cleanup.section.orphanFileHistory"),hint:s("cleanup.section.orphanFileHistory.hint"),rows:t.orphanFileHistory}),e.jsx(R,{kind:"session-env",title:s("cleanup.section.orphanSessionEnv"),hint:s("cleanup.section.orphanSessionEnv.hint"),rows:t.orphanSessionEnv})]})]})}function U({title:s,hint:t}){return e.jsxs("div",{className:"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1 rounded-[var(--radius-input)] bg-[var(--color-sunken)] px-3 py-2",children:[e.jsx("h3",{className:"font-display text-[15px] font-light tracking-tight text-[var(--color-fg-primary)]",children:s}),e.jsx("span",{className:"font-mono text-[10.5px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]",children:t})]})}function te({data:s}){const t=p();return e.jsxs("div",{children:[e.jsx(U,{title:t("cleanup.section.largeSessions"),hint:t("cleanup.section.largeSessions.hint",{n:s.length||10})}),s.length===0?e.jsx("p",{className:"mt-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)]",children:t("cleanup.empty.largeSessions")}):e.jsx("div",{className:"mt-3 -mx-6 overflow-x-auto px-6",children:e.jsxs("table",{className:"w-full table-fixed text-sm",children:[e.jsxs("colgroup",{children:[e.jsx("col",{}),e.jsx("col",{className:"w-[20rem]"}),e.jsx("col",{className:"w-24"}),e.jsx("col",{className:"w-24"}),e.jsx("col",{className:"w-20"})]}),e.jsx("thead",{children:e.jsxs("tr",{className:"text-left",children:[e.jsx("th",{className:"px-2 py-2 eyebrow",children:t("cleanup.col.session")}),e.jsx("th",{className:"px-2 py-2 eyebrow",children:t("cleanup.col.project")}),e.jsx("th",{className:"px-2 py-2 eyebrow text-right",children:t("cleanup.col.last")}),e.jsx("th",{className:"px-2 py-2 eyebrow text-right",children:t("cleanup.col.size")}),e.jsx("th",{className:"px-2 py-2 eyebrow text-right",children:t("cleanup.col.actions")})]})}),e.jsx("tbody",{className:"border-t border-[var(--color-hairline)]",children:s.map(a=>{const o=a.customTitle??a.title;return e.jsxs("tr",{className:"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]",children:[e.jsxs("td",{className:"px-2 py-2.5 align-top",children:[e.jsx("div",{className:"truncate font-medium text-[var(--color-fg-primary)]",title:o,children:o}),e.jsx("div",{className:"mt-0.5 truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]",title:a.sessionId,children:a.sessionId})]}),e.jsx("td",{className:"px-2 py-2.5 align-top font-mono text-[12px] text-[var(--color-fg-muted)]",children:e.jsx(v,{to:`/projects/${encodeURIComponent(a.projectId)}`,className:"block truncate hover:text-[var(--color-fg-primary)]",title:a.projectPath,children:ae(a.projectPath)})}),e.jsx("td",{className:"px-2 py-2.5 text-right align-top font-mono text-[12px] text-[var(--color-fg-secondary)]",children:_(a.lastActivity)}),e.jsx("td",{className:"px-2 py-2.5 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]",children:c(a.sizeBytes)}),e.jsx("td",{className:"px-2 py-2.5 text-right align-top",children:e.jsx(v,{to:`/projects/${encodeURIComponent(a.projectId)}/sessions/${a.sessionId}`,className:"inline-block rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-2.5 py-1 text-[11.5px] font-medium text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] hover:text-[var(--color-fg-primary)]",children:t("cleanup.action.view")})})]},`${a.projectId}/${a.sessionId}`)})})]})})]})}function R({kind:s,title:t,hint:a,rows:o}){const n=p(),[l,d]=x.useState(null);return e.jsxs("div",{children:[e.jsx(U,{title:t,hint:a}),o.length===0?e.jsx("p",{className:"mt-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)]",children:n("cleanup.empty.orphan")}):e.jsx("div",{className:"mt-3 -mx-6 overflow-x-auto px-6",children:e.jsxs("table",{className:"w-full table-fixed text-sm",children:[e.jsxs("colgroup",{children:[e.jsx("col",{}),e.jsx("col",{className:"w-24"}),e.jsx("col",{className:"w-24"})]}),e.jsx("thead",{children:e.jsxs("tr",{className:"text-left",children:[e.jsx("th",{className:"px-2 py-2 eyebrow",children:n("cleanup.col.sid")}),e.jsx("th",{className:"px-2 py-2 eyebrow text-right",children:n("cleanup.col.size")}),e.jsx("th",{className:"px-2 py-2 eyebrow text-right",children:n("cleanup.col.actions")})]})}),e.jsx("tbody",{className:"border-t border-[var(--color-hairline)]",children:o.map(i=>e.jsxs("tr",{className:"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]",children:[e.jsx("td",{className:"px-2 py-2.5 align-top font-mono text-[12px] text-[var(--color-fg-secondary)]",title:i.sessionId,children:e.jsx("span",{className:"block truncate",children:i.sessionId})}),e.jsx("td",{className:"px-2 py-2.5 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]",children:c(i.sizeBytes)}),e.jsx("td",{className:"px-2 py-2.5 text-right align-top",children:e.jsx("button",{type:"button",onClick:()=>d(i),className:"inline-block rounded-[var(--radius-control)] border border-[var(--color-danger)]/40 px-2.5 py-1 text-[11.5px] font-medium text-[var(--color-danger)] hover:bg-[var(--color-danger-soft)]",children:n("cleanup.action.delete")})})]},i.sessionId))})]})}),l&&e.jsx(se,{kind:s,orphan:l,onClose:()=>d(null)})]})}function se({kind:s,orphan:t,onClose:a}){const o=p(),n=H(),l=O({mutationFn:()=>S(`/api/disk-cleanup/orphan/${s}/${encodeURIComponent(t.sessionId)}`,{method:"DELETE"}),onSuccess:()=>{n.invalidateQueries({queryKey:y.diskCleanupSuggestions()}),n.invalidateQueries({queryKey:y.diskUsage()}),a()}}),d=x.useRef(l.isPending);return d.current=l.isPending,x.useEffect(()=>{function i(u){u.key==="Escape"&&!d.current&&a()}return window.addEventListener("keydown",i),()=>window.removeEventListener("keydown",i)},[a]),e.jsx(h.div,{initial:{opacity:0},animate:{opacity:1},exit:{opacity:0},transition:{duration:.18},className:"fixed inset-0 z-[60] flex items-start justify-center bg-[oklch(0.16_0.006_85_/_0.55)] backdrop-blur-[2px] px-4 py-12",onClick:()=>!l.isPending&&a(),children:e.jsxs(h.div,{initial:{y:8,opacity:0},animate:{y:0,opacity:1},transition:{duration:.24,ease:[.16,1,.3,1]},className:"w-full max-w-md overflow-hidden rounded-[var(--radius-panel)] border border-[var(--color-hairline)] bg-[var(--color-surface)] shadow-[var(--shadow-pop)]",onClick:i=>i.stopPropagation(),children:[e.jsxs("header",{className:"border-b border-[var(--color-hairline)] px-6 py-5",children:[e.jsx("p",{className:"eyebrow text-[var(--color-danger)]",children:o("delete.eyebrow.confirm")}),e.jsx("h2",{className:"mt-1 font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]",children:o("cleanup.confirm.title")}),e.jsx("p",{className:"mt-2 text-sm text-[var(--color-fg-muted)]",children:o("cleanup.confirm.body",{kind:s,sid:t.sessionId,size:c(t.sizeBytes)})})]}),l.error&&e.jsx("p",{className:"mx-6 mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]",children:l.error.message}),e.jsxs("footer",{className:"flex justify-end gap-2 border-t border-[var(--color-hairline)] px-6 py-4",children:[e.jsx("button",{type:"button",onClick:a,disabled:l.isPending,className:"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-1.5 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50",children:o("cleanup.confirm.cancel")}),e.jsx("button",{type:"button",onClick:()=>l.mutate(),disabled:l.isPending,className:"rounded-[var(--radius-control)] bg-[var(--color-danger)] px-4 py-1.5 text-sm font-medium text-white shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",children:l.isPending?o("cleanup.action.deleting"):o("cleanup.confirm.confirm")})]})]})})}function ae(s){const t=s.split(/[\\/]+/).filter(Boolean);return t.length<=2?s:"…/"+t.slice(-2).join("/")}function k({label:s,value:t,unit:a,trail:o,accent:n}){return e.jsxs("div",{className:"surface-card is-interactive group relative overflow-hidden p-5 "+(n?"border-[var(--color-accent)]/40 hover:border-[var(--color-accent)]/60":""),children:[n&&e.jsx("span",{"aria-hidden":!0,className:"pointer-events-none absolute -right-12 -top-12 h-32 w-32 rounded-full bg-[var(--color-accent-soft)] opacity-70 blur-2xl"}),e.jsx("div",{className:"eyebrow",children:s}),e.jsxs("div",{className:"mt-3 flex items-baseline gap-1.5",children:[e.jsx("span",{className:"font-mono text-4xl font-light leading-none tracking-[-0.02em] tabular-nums text-[var(--color-fg-primary)]",children:t}),a&&e.jsx("span",{className:"font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]",children:a})]}),o&&e.jsx("div",{className:"mt-3 font-mono text-[11px] text-[var(--color-fg-muted)]",children:o})]})}const M=["--color-accent","--color-moss","--color-iris","--color-fg-secondary","--color-accent-ink","--color-fg-muted"],oe=["--color-accent","--color-fg-muted","--color-hairline","--color-surface","--color-fg-primary"];function F(s){const[t,a]=x.useState(()=>A(s));return x.useEffect(()=>{const o=new MutationObserver(()=>a(A(s)));return o.observe(document.documentElement,{attributes:!0,attributeFilter:["class"]}),()=>o.disconnect()},[s]),t}function A(s){const t=getComputedStyle(document.documentElement),a={};for(const o of s)a[o]=t.getPropertyValue(o).trim()||"#888";return a}function pe(){var B;const s=p(),{data:t,isLoading:a,error:o}=q({queryKey:y.diskUsage(),queryFn:()=>S("/api/disk-usage")}),n=F(oe),l=n["--color-accent"],d=n["--color-fg-muted"],i=n["--color-hairline"],u=n["--color-surface"],C=n["--color-fg-primary"],I=F(M),g=x.useMemo(()=>M.map(r=>I[r]),[I]),f=x.useMemo(()=>t?t.byProject.map(r=>({name:D(r.decodedCwd),value:r.totalBytes,sessions:r.sessionCount})):[],[t]),P=x.useMemo(()=>t?t.byMonth.map(r=>({month:r.month,MB:+(r.totalBytes/1048576).toFixed(2)})):[],[t]);return e.jsxs("section",{children:[e.jsx("div",{className:"surface-card p-6",children:e.jsx(re,{title:s("disk.title"),tagline:s("disk.tagline"),stats:t?{totalBytes:t.totalBytes,projectCount:t.byProject.length,totalSessions:t.totalSessions}:null})}),a&&e.jsx(K,{label:s("common.computing"),className:"mt-10"}),o&&e.jsxs("p",{className:"mt-10 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-4 py-3 text-sm text-[var(--color-danger)]",children:[s("common.failed"),": ",o.message]}),t&&e.jsxs(e.Fragment,{children:[e.jsxs(h.div,{initial:"hidden",animate:"show",variants:Q,className:"mt-8 grid gap-3 sm:grid-cols-3",children:[e.jsx(h.div,{variants:b,children:e.jsx(k,{accent:!0,label:s("disk.stat.total"),value:c(t.totalBytes).split(" ")[0],unit:c(t.totalBytes).split(" ")[1],trail:s("disk.stat.acrossProjects",{n:t.byProject.length})})}),e.jsx(h.div,{variants:b,children:e.jsx(k,{label:s("disk.stat.sessions"),value:t.totalSessions.toLocaleString(),trail:t.topSessions[0]?s("disk.stat.largest",{size:c(t.topSessions[0].totalBytes)}):void 0})}),e.jsx(h.div,{variants:b,children:e.jsx(k,{label:s("disk.stat.months"),value:t.byMonth.length,trail:t.byMonth[0]?`${t.byMonth[0].month} → ${((B=t.byMonth.at(-1))==null?void 0:B.month)??t.byMonth[0].month}`:void 0})})]}),e.jsxs("div",{className:"mt-12 grid gap-8 lg:grid-cols-5",children:[e.jsx(z,{title:s("disk.composition.title"),subtitle:s("disk.composition.subtitle"),className:"lg:col-span-3",children:f.length===0?e.jsx(w,{}):e.jsxs("div",{className:"grid gap-6 md:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)] md:items-center",children:[e.jsxs("div",{className:"relative mx-auto aspect-square w-full max-w-[260px]",children:[e.jsx(E,{width:"100%",height:"100%",children:e.jsxs(V,{margin:{top:0,right:0,bottom:0,left:0},children:[e.jsx(W,{data:f,dataKey:"value",nameKey:"name",cx:"50%",cy:"50%",innerRadius:"58%",outerRadius:"92%",paddingAngle:1.5,stroke:u,strokeWidth:2,isAnimationActive:!1,children:f.map((r,m)=>e.jsx(X,{fill:g[m%g.length]},m))}),e.jsx(L,{contentStyle:T(u,i,C),formatter:r=>c(r)})]})}),e.jsxs("div",{className:"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center",children:[e.jsx("span",{className:"eyebrow",children:s("disk.composition.total")}),e.jsx("span",{className:"mt-1 font-mono text-2xl font-light tabular-nums text-[var(--color-fg-primary)]",children:c(t.totalBytes)})]})]}),e.jsx("ol",{className:"space-y-1.5 text-sm",children:f.slice(0,8).map((r,m)=>{const j=(r.value/t.totalBytes*100).toFixed(1);return e.jsxs("li",{className:"grid grid-cols-[14px_1fr_auto] items-baseline gap-2",children:[e.jsx("span",{"aria-hidden":!0,className:"block h-2.5 w-2.5 self-center rounded-sm",style:{background:g[m%g.length]}}),e.jsx("span",{className:"truncate font-mono text-xs text-[var(--color-fg-secondary)]",title:r.name,children:r.name}),e.jsxs("span",{className:"font-mono tabular-nums text-xs text-[var(--color-fg-primary)]",children:[j,"% ",e.jsxs("span",{className:"text-[var(--color-fg-faint)]",children:["· ",c(r.value)]})]})]},m)})})]})}),e.jsx(z,{title:s("disk.cadence.title"),subtitle:s("disk.cadence.subtitle"),className:"lg:col-span-2",children:P.length===0?e.jsx(w,{}):e.jsx("div",{className:"h-72",children:e.jsx(E,{width:"100%",height:"100%",children:e.jsxs(Y,{data:P,margin:{top:10,right:8,bottom:0,left:-10},children:[e.jsx("defs",{children:e.jsxs("linearGradient",{id:"cadenceFill",x1:"0",y1:"0",x2:"0",y2:"1",children:[e.jsx("stop",{offset:"0%",stopColor:l,stopOpacity:.55}),e.jsx("stop",{offset:"100%",stopColor:l,stopOpacity:0})]})}),e.jsx(G,{dataKey:"month",tick:{fontSize:11,fill:d,fontFamily:"var(--font-mono)"},tickLine:!1,axisLine:{stroke:i}}),e.jsx(J,{tick:{fontSize:11,fill:d,fontFamily:"var(--font-mono)"},tickLine:!1,axisLine:!1,width:36}),e.jsx(L,{contentStyle:T(u,i,C),formatter:r=>`${r.toFixed(2)} MB`,cursor:{stroke:i,strokeDasharray:"2 3"}}),e.jsx(Z,{type:"monotone",dataKey:"MB",stroke:l,strokeWidth:1.6,fill:"url(#cadenceFill)"})]})})})})]}),e.jsxs("div",{className:"surface-card mt-12 p-6",children:[e.jsxs("div",{className:"flex items-baseline justify-between",children:[e.jsx("h2",{className:"font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]",children:s("disk.heaviest.title")}),t.topSessions.length>0&&e.jsx("span",{className:"font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]",children:s("disk.heaviest.top",{n:t.topSessions.length})})]}),e.jsx("div",{className:"rule-dotted mt-3","aria-hidden":!0}),t.topSessions.length===0?e.jsx(w,{className:"mt-6"}):e.jsx("div",{className:"mt-4 -mx-6 overflow-x-auto px-6",children:e.jsxs("table",{className:"w-full table-fixed text-sm",children:[e.jsxs("colgroup",{children:[e.jsx("col",{className:"w-10"}),e.jsx("col",{}),e.jsx("col",{className:"w-[22rem]"}),e.jsx("col",{className:"w-24"}),e.jsx("col",{className:"w-24"})]}),e.jsx("thead",{children:e.jsxs("tr",{className:"text-left",children:[e.jsx("th",{className:"px-2 py-3 eyebrow",children:s("disk.col.num")}),e.jsx("th",{className:"px-2 py-3 eyebrow",children:s("disk.col.title")}),e.jsx("th",{className:"px-2 py-3 eyebrow",children:s("disk.col.project")}),e.jsx("th",{className:"px-2 py-3 eyebrow text-right",children:s("disk.col.last")}),e.jsx("th",{className:"px-2 py-3 eyebrow text-right",children:s("disk.col.size")})]})}),e.jsx("tbody",{className:"border-t border-[var(--color-hairline)]",children:t.topSessions.map((r,m)=>{const j=r.customTitle??r.title;return e.jsxs("tr",{className:"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]",children:[e.jsx("td",{className:"px-2 py-3 align-top font-mono text-[11px] text-[var(--color-fg-faint)]",children:String(m+1).padStart(2,"0")}),e.jsx("td",{className:"px-2 py-3 align-top",children:e.jsx(v,{to:`/projects/${encodeURIComponent(r.projectId)}/sessions/${r.sessionId}`,className:"block truncate font-medium text-[var(--color-fg-primary)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]",title:j,children:j})}),e.jsx("td",{className:"px-2 py-3 align-top font-mono text-[12px] text-[var(--color-fg-muted)]",children:e.jsx(v,{to:`/projects/${encodeURIComponent(r.projectId)}`,className:"block truncate hover:text-[var(--color-fg-primary)]",title:r.projectId,children:ne(t,r.projectId)})}),e.jsx("td",{className:"px-2 py-3 text-right align-top font-mono text-[12.5px] text-[var(--color-fg-secondary)]",children:_(r.lastAt)}),e.jsx("td",{className:"px-2 py-3 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]",children:c(r.totalBytes)})]},`${r.projectId}/${r.sessionId}`)})})]})})]}),e.jsx(ee,{})]})]})}function re({title:s,tagline:t,stats:a}){const o=p();return e.jsxs("header",{className:"relative",children:[e.jsxs("div",{className:"flex flex-wrap items-baseline gap-x-4 gap-y-1",children:[e.jsxs("h1",{className:"font-display text-[clamp(1.75rem,3.5vw,2.25rem)] font-light leading-[1.1] tracking-[-0.02em] text-[var(--color-fg-primary)]",children:[s,e.jsx("span",{className:"text-[var(--color-accent)]",children:"."})]}),e.jsx("p",{className:"min-w-0 flex-1 font-display text-[13px] italic leading-snug text-[var(--color-fg-muted)]",children:t})]}),a&&e.jsxs("div",{className:"mt-3 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xs",children:[e.jsx(N,{label:o("disk.meta.total"),value:c(a.totalBytes)}),e.jsx($,{}),e.jsx(N,{label:o("disk.meta.projects"),value:a.projectCount}),e.jsx($,{}),e.jsx(N,{label:o("disk.meta.sessions"),value:a.totalSessions.toLocaleString()})]})]})}function T(s,t,a){return{background:s,border:`1px solid ${t}`,borderRadius:8,fontFamily:"var(--font-mono)",fontSize:11,color:a,boxShadow:"var(--shadow-pop)"}}function ne(s,t){const a=s.byProject.find(o=>o.projectId===t);return a?D(a.decodedCwd):t}function D(s){const t=s.split(/[\\/]+/).filter(Boolean);return t.length<=2?s:"…/"+t.slice(-2).join("/")}function z({title:s,subtitle:t,children:a,className:o=""}){return e.jsxs("section",{className:`surface-card p-5 ${o}`,children:[e.jsxs("header",{className:"mb-4 flex items-baseline justify-between gap-3",children:[e.jsx("h3",{className:"font-display text-lg font-light tracking-tight text-[var(--color-fg-primary)]",children:s}),t&&e.jsx("span",{className:"font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--color-fg-muted)]",children:t})]}),a]})}function w({className:s=""}){const t=p();return e.jsx("p",{className:`font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)] ${s}`,children:t("common.noData")})}export{pe as default};
2
+ //# sourceMappingURL=DiskUsage-CKhggLs5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DiskUsage-CKhggLs5.js","sources":["../../web/src/components/CleanupSuggestions.tsx","../../web/src/components/StatCard.tsx","../../web/src/routes/DiskUsage.tsx"],"sourcesContent":["import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\r\nimport { motion } from 'motion/react';\r\nimport { useEffect, useRef, useState } from 'react';\r\nimport { Link } from 'react-router-dom';\r\nimport {\r\n api,\r\n type DiskCleanupOrphan,\r\n type DiskCleanupSuggestions,\r\n type DiskOrphanDeleteResult,\r\n type DiskOrphanKind,\r\n} from '../lib/api.ts';\r\nimport { formatBytes, formatRelativeTime } from '../lib/format.ts';\r\nimport { useT } from '../lib/i18n.ts';\r\nimport { queryKeys } from '../lib/query-keys.ts';\r\nimport { Loading } from './Loading.tsx';\r\n\r\n/**\r\n * \"清理建议\" 区块:从 /api/disk-cleanup/suggestions 拉取最大的会话 + 两类孤儿目录,\r\n * 用户逐条确认后通过 DELETE /api/disk-cleanup/orphan/:kind/:sid 单条清理。\r\n * 不做批量、不做自动清理。\r\n */\r\nexport default function CleanupSuggestions() {\r\n const t = useT();\r\n const { data, isLoading, error } = useQuery({\r\n queryKey: queryKeys.diskCleanupSuggestions(),\r\n queryFn: () => api<DiskCleanupSuggestions>('/api/disk-cleanup/suggestions'),\r\n });\r\n\r\n return (\r\n <section className=\"surface-card mt-12 p-6\">\r\n <header className=\"flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1\">\r\n <h2 className=\"font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]\">\r\n {t('cleanup.title')}\r\n </h2>\r\n <p className=\"min-w-0 flex-1 font-display text-[13px] italic leading-snug text-[var(--color-fg-muted)]\">\r\n {t('cleanup.tagline')}\r\n </p>\r\n </header>\r\n <div className=\"rule-dotted mt-3\" aria-hidden />\r\n\r\n {isLoading && <Loading label={t('common.computing')} className=\"mt-6\" />}\r\n {error && (\r\n <p className=\"mt-6 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-4 py-3 text-sm text-[var(--color-danger)]\">\r\n {t('cleanup.failed', { msg: (error as Error).message })}\r\n </p>\r\n )}\r\n\r\n {data && (\r\n <div className=\"mt-6 space-y-8\">\r\n <LargeSessionsTable data={data.largeSessions} />\r\n <OrphanTable\r\n kind=\"file-history\"\r\n title={t('cleanup.section.orphanFileHistory')}\r\n hint={t('cleanup.section.orphanFileHistory.hint')}\r\n rows={data.orphanFileHistory}\r\n />\r\n <OrphanTable\r\n kind=\"session-env\"\r\n title={t('cleanup.section.orphanSessionEnv')}\r\n hint={t('cleanup.section.orphanSessionEnv.hint')}\r\n rows={data.orphanSessionEnv}\r\n />\r\n </div>\r\n )}\r\n </section>\r\n );\r\n}\r\n\r\nfunction SectionHeader({ title, hint }: { title: string; hint: string }) {\r\n return (\r\n <div\r\n className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1 rounded-[var(--radius-input)] bg-[var(--color-sunken)] px-3 py-2\"\r\n >\r\n <h3 className=\"font-display text-[15px] font-light tracking-tight text-[var(--color-fg-primary)]\">\r\n {title}\r\n </h3>\r\n <span className=\"font-mono text-[10.5px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]\">\r\n {hint}\r\n </span>\r\n </div>\r\n );\r\n}\r\n\r\nfunction LargeSessionsTable({\r\n data,\r\n}: {\r\n data: DiskCleanupSuggestions['largeSessions'];\r\n}) {\r\n const t = useT();\r\n return (\r\n <div>\r\n <SectionHeader\r\n title={t('cleanup.section.largeSessions')}\r\n hint={t('cleanup.section.largeSessions.hint', { n: data.length || 10 })}\r\n />\r\n {data.length === 0 ? (\r\n <p className=\"mt-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)]\">\r\n {t('cleanup.empty.largeSessions')}\r\n </p>\r\n ) : (\r\n <div className=\"mt-3 -mx-6 overflow-x-auto px-6\">\r\n <table className=\"w-full table-fixed text-sm\">\r\n <colgroup>\r\n <col />\r\n <col className=\"w-[20rem]\" />\r\n <col className=\"w-24\" />\r\n <col className=\"w-24\" />\r\n <col className=\"w-20\" />\r\n </colgroup>\r\n <thead>\r\n <tr className=\"text-left\">\r\n <th className=\"px-2 py-2 eyebrow\">{t('cleanup.col.session')}</th>\r\n <th className=\"px-2 py-2 eyebrow\">{t('cleanup.col.project')}</th>\r\n <th className=\"px-2 py-2 eyebrow text-right\">{t('cleanup.col.last')}</th>\r\n <th className=\"px-2 py-2 eyebrow text-right\">{t('cleanup.col.size')}</th>\r\n <th className=\"px-2 py-2 eyebrow text-right\">{t('cleanup.col.actions')}</th>\r\n </tr>\r\n </thead>\r\n <tbody className=\"border-t border-[var(--color-hairline)]\">\r\n {data.map((row) => {\r\n const display = row.customTitle ?? row.title;\r\n return (\r\n <tr\r\n key={`${row.projectId}/${row.sessionId}`}\r\n className=\"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]\"\r\n >\r\n <td className=\"px-2 py-2.5 align-top\">\r\n <div\r\n className=\"truncate font-medium text-[var(--color-fg-primary)]\"\r\n title={display}\r\n >\r\n {display}\r\n </div>\r\n <div\r\n className=\"mt-0.5 truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]\"\r\n title={row.sessionId}\r\n >\r\n {row.sessionId}\r\n </div>\r\n </td>\r\n <td\r\n className=\"px-2 py-2.5 align-top font-mono text-[12px] text-[var(--color-fg-muted)]\"\r\n >\r\n <Link\r\n to={`/projects/${encodeURIComponent(row.projectId)}`}\r\n className=\"block truncate hover:text-[var(--color-fg-primary)]\"\r\n title={row.projectPath}\r\n >\r\n {shortCwd(row.projectPath)}\r\n </Link>\r\n </td>\r\n <td className=\"px-2 py-2.5 text-right align-top font-mono text-[12px] text-[var(--color-fg-secondary)]\">\r\n {formatRelativeTime(row.lastActivity)}\r\n </td>\r\n <td className=\"px-2 py-2.5 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]\">\r\n {formatBytes(row.sizeBytes)}\r\n </td>\r\n <td className=\"px-2 py-2.5 text-right align-top\">\r\n <Link\r\n to={`/projects/${encodeURIComponent(row.projectId)}/sessions/${row.sessionId}`}\r\n className=\"inline-block rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-2.5 py-1 text-[11.5px] font-medium text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] hover:text-[var(--color-fg-primary)]\"\r\n >\r\n {t('cleanup.action.view')}\r\n </Link>\r\n </td>\r\n </tr>\r\n );\r\n })}\r\n </tbody>\r\n </table>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nfunction OrphanTable({\r\n kind,\r\n title,\r\n hint,\r\n rows,\r\n}: {\r\n kind: DiskOrphanKind;\r\n title: string;\r\n hint: string;\r\n rows: DiskCleanupOrphan[];\r\n}) {\r\n const t = useT();\r\n const [confirming, setConfirming] = useState<DiskCleanupOrphan | null>(null);\r\n return (\r\n <div>\r\n <SectionHeader title={title} hint={hint} />\r\n {rows.length === 0 ? (\r\n <p className=\"mt-3 font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)]\">\r\n {t('cleanup.empty.orphan')}\r\n </p>\r\n ) : (\r\n <div className=\"mt-3 -mx-6 overflow-x-auto px-6\">\r\n <table className=\"w-full table-fixed text-sm\">\r\n <colgroup>\r\n <col />\r\n <col className=\"w-24\" />\r\n <col className=\"w-24\" />\r\n </colgroup>\r\n <thead>\r\n <tr className=\"text-left\">\r\n <th className=\"px-2 py-2 eyebrow\">{t('cleanup.col.sid')}</th>\r\n <th className=\"px-2 py-2 eyebrow text-right\">{t('cleanup.col.size')}</th>\r\n <th className=\"px-2 py-2 eyebrow text-right\">{t('cleanup.col.actions')}</th>\r\n </tr>\r\n </thead>\r\n <tbody className=\"border-t border-[var(--color-hairline)]\">\r\n {rows.map((row) => (\r\n <tr\r\n key={row.sessionId}\r\n className=\"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]\"\r\n >\r\n <td\r\n className=\"px-2 py-2.5 align-top font-mono text-[12px] text-[var(--color-fg-secondary)]\"\r\n title={row.sessionId}\r\n >\r\n <span className=\"block truncate\">{row.sessionId}</span>\r\n </td>\r\n <td className=\"px-2 py-2.5 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]\">\r\n {formatBytes(row.sizeBytes)}\r\n </td>\r\n <td className=\"px-2 py-2.5 text-right align-top\">\r\n <button\r\n type=\"button\"\r\n onClick={() => setConfirming(row)}\r\n className=\"inline-block rounded-[var(--radius-control)] border border-[var(--color-danger)]/40 px-2.5 py-1 text-[11.5px] font-medium text-[var(--color-danger)] hover:bg-[var(--color-danger-soft)]\"\r\n >\r\n {t('cleanup.action.delete')}\r\n </button>\r\n </td>\r\n </tr>\r\n ))}\r\n </tbody>\r\n </table>\r\n </div>\r\n )}\r\n {confirming && (\r\n <OrphanConfirmDialog\r\n kind={kind}\r\n orphan={confirming}\r\n onClose={() => setConfirming(null)}\r\n />\r\n )}\r\n </div>\r\n );\r\n}\r\n\r\nfunction OrphanConfirmDialog({\r\n kind,\r\n orphan,\r\n onClose,\r\n}: {\r\n kind: DiskOrphanKind;\r\n orphan: DiskCleanupOrphan;\r\n onClose: () => void;\r\n}) {\r\n const t = useT();\r\n const queryClient = useQueryClient();\r\n const mutation = useMutation({\r\n mutationFn: () =>\r\n api<DiskOrphanDeleteResult>(\r\n `/api/disk-cleanup/orphan/${kind}/${encodeURIComponent(orphan.sessionId)}`,\r\n { method: 'DELETE' },\r\n ),\r\n onSuccess: () => {\r\n queryClient.invalidateQueries({ queryKey: queryKeys.diskCleanupSuggestions() });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.diskUsage() });\r\n onClose();\r\n },\r\n });\r\n\r\n const isPendingRef = useRef(mutation.isPending);\r\n isPendingRef.current = mutation.isPending;\r\n useEffect(() => {\r\n function onKey(e: KeyboardEvent) {\r\n if (e.key === 'Escape' && !isPendingRef.current) onClose();\r\n }\r\n window.addEventListener('keydown', onKey);\r\n return () => window.removeEventListener('keydown', onKey);\r\n }, [onClose]);\r\n\r\n return (\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n transition={{ duration: 0.18 }}\r\n className=\"fixed inset-0 z-[60] flex items-start justify-center bg-[oklch(0.16_0.006_85_/_0.55)] backdrop-blur-[2px] px-4 py-12\"\r\n onClick={() => !mutation.isPending && onClose()}\r\n >\r\n <motion.div\r\n initial={{ y: 8, opacity: 0 }}\r\n animate={{ y: 0, opacity: 1 }}\r\n transition={{ duration: 0.24, ease: [0.16, 1, 0.3, 1] }}\r\n className=\"w-full max-w-md overflow-hidden rounded-[var(--radius-panel)] border border-[var(--color-hairline)] bg-[var(--color-surface)] shadow-[var(--shadow-pop)]\"\r\n onClick={(e) => e.stopPropagation()}\r\n >\r\n <header className=\"border-b border-[var(--color-hairline)] px-6 py-5\">\r\n <p className=\"eyebrow text-[var(--color-danger)]\">{t('delete.eyebrow.confirm')}</p>\r\n <h2 className=\"mt-1 font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]\">\r\n {t('cleanup.confirm.title')}\r\n </h2>\r\n <p className=\"mt-2 text-sm text-[var(--color-fg-muted)]\">\r\n {t('cleanup.confirm.body', {\r\n kind,\r\n sid: orphan.sessionId,\r\n size: formatBytes(orphan.sizeBytes),\r\n })}\r\n </p>\r\n </header>\r\n {mutation.error && (\r\n <p className=\"mx-6 mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]\">\r\n {(mutation.error as Error).message}\r\n </p>\r\n )}\r\n <footer className=\"flex justify-end gap-2 border-t border-[var(--color-hairline)] px-6 py-4\">\r\n <button\r\n type=\"button\"\r\n onClick={onClose}\r\n disabled={mutation.isPending}\r\n className=\"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-1.5 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50\"\r\n >\r\n {t('cleanup.confirm.cancel')}\r\n </button>\r\n <button\r\n type=\"button\"\r\n onClick={() => mutation.mutate()}\r\n disabled={mutation.isPending}\r\n className=\"rounded-[var(--radius-control)] bg-[var(--color-danger)] px-4 py-1.5 text-sm font-medium text-white shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n {mutation.isPending ? t('cleanup.action.deleting') : t('cleanup.confirm.confirm')}\r\n </button>\r\n </footer>\r\n </motion.div>\r\n </motion.div>\r\n );\r\n}\r\n\r\nfunction shortCwd(cwd: string): string {\r\n const parts = cwd.split(/[\\\\/]+/).filter(Boolean);\r\n if (parts.length <= 2) return cwd;\r\n return '…/' + parts.slice(-2).join('/');\r\n}\r\n","import type { ReactNode } from 'react';\r\n\r\ninterface Props {\r\n label: string;\r\n value: ReactNode;\r\n unit?: ReactNode;\r\n trail?: ReactNode;\r\n accent?: boolean;\r\n}\r\n\r\nexport default function StatCard({ label, value, unit, trail, accent }: Props) {\r\n return (\r\n <div\r\n className={\r\n 'surface-card is-interactive group relative overflow-hidden p-5 ' +\r\n (accent ? 'border-[var(--color-accent)]/40 hover:border-[var(--color-accent)]/60' : '')\r\n }\r\n >\r\n {accent && (\r\n <span\r\n aria-hidden\r\n className=\"pointer-events-none absolute -right-12 -top-12 h-32 w-32 rounded-full bg-[var(--color-accent-soft)] opacity-70 blur-2xl\"\r\n />\r\n )}\r\n <div className=\"eyebrow\">{label}</div>\r\n <div className=\"mt-3 flex items-baseline gap-1.5\">\r\n <span className=\"font-mono text-4xl font-light leading-none tracking-[-0.02em] tabular-nums text-[var(--color-fg-primary)]\">\r\n {value}\r\n </span>\r\n {unit && (\r\n <span className=\"font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]\">\r\n {unit}\r\n </span>\r\n )}\r\n </div>\r\n {trail && (\r\n <div className=\"mt-3 font-mono text-[11px] text-[var(--color-fg-muted)]\">{trail}</div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { useQuery } from '@tanstack/react-query';\r\nimport { motion } from 'motion/react';\r\nimport { useEffect, useMemo, useState } from 'react';\r\nimport { Link } from 'react-router-dom';\r\nimport {\r\n Area,\r\n AreaChart,\r\n Cell,\r\n Pie,\r\n PieChart,\r\n ResponsiveContainer,\r\n Tooltip,\r\n XAxis,\r\n YAxis,\r\n} from 'recharts';\r\nimport CleanupSuggestions from '../components/CleanupSuggestions.tsx';\r\nimport { Loading } from '../components/Loading.tsx';\r\nimport { MetaItem, Sep } from '../components/PageHeader.tsx';\r\nimport StatCard from '../components/StatCard.tsx';\r\nimport { api, type DiskUsage } from '../lib/api.ts';\r\nimport { formatBytes, formatRelativeTime } from '../lib/format.ts';\r\nimport { useT } from '../lib/i18n.ts';\r\nimport { fadeUpItem, staggerParent } from '../lib/motion.ts';\r\nimport { queryKeys } from '../lib/query-keys.ts';\r\n\r\nconst PALETTE_VARS = [\r\n '--color-accent',\r\n '--color-moss',\r\n '--color-iris',\r\n '--color-fg-secondary',\r\n '--color-accent-ink',\r\n '--color-fg-muted',\r\n];\r\n\r\nconst CHART_VARS = [\r\n '--color-accent',\r\n '--color-fg-muted',\r\n '--color-hairline',\r\n '--color-surface',\r\n '--color-fg-primary',\r\n] as const;\r\n\r\n// Resolve a list of CSS variables on <html> and re-resolve whenever the\r\n// theme class flips. Recharts needs concrete colors; CSS vars don't traverse SVG cleanly.\r\nfunction useThemeColors<T extends readonly string[]>(vars: T): Record<T[number], string> {\r\n const [snapshot, setSnapshot] = useState(() => readVars(vars));\r\n useEffect(() => {\r\n const observer = new MutationObserver(() => setSnapshot(readVars(vars)));\r\n observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\r\n return () => observer.disconnect();\r\n }, [vars]);\r\n return snapshot;\r\n}\r\n\r\nfunction readVars<T extends readonly string[]>(vars: T): Record<T[number], string> {\r\n const cs = getComputedStyle(document.documentElement);\r\n const out = {} as Record<T[number], string>;\r\n for (const v of vars) (out as Record<string, string>)[v] = cs.getPropertyValue(v).trim() || '#888';\r\n return out;\r\n}\r\n\r\nexport default function DiskUsageRoute() {\r\n const t = useT();\r\n const { data, isLoading, error } = useQuery({\r\n queryKey: queryKeys.diskUsage(),\r\n queryFn: () => api<DiskUsage>('/api/disk-usage'),\r\n });\r\n\r\n const colors = useThemeColors(CHART_VARS);\r\n const accent = colors['--color-accent'];\r\n const muted = colors['--color-fg-muted'];\r\n const hairline = colors['--color-hairline'];\r\n const surface = colors['--color-surface'];\r\n const fgPrimary = colors['--color-fg-primary'];\r\n\r\n const palette = useThemeColors(PALETTE_VARS);\r\n const resolvedPalette = useMemo(() => PALETTE_VARS.map((v) => palette[v]), [palette]);\r\n\r\n const pieData = useMemo(() => {\r\n if (!data) return [];\r\n return data.byProject.map((p) => ({\r\n name: shortCwd(p.decodedCwd),\r\n value: p.totalBytes,\r\n sessions: p.sessionCount,\r\n }));\r\n }, [data]);\r\n\r\n const monthData = useMemo(() => {\r\n if (!data) return [];\r\n return data.byMonth.map((m) => ({ month: m.month, MB: +(m.totalBytes / 1_048_576).toFixed(2) }));\r\n }, [data]);\r\n\r\n return (\r\n <section>\r\n <div className=\"surface-card p-6\">\r\n <Masthead\r\n title={t('disk.title')}\r\n tagline={t('disk.tagline')}\r\n stats={\r\n data\r\n ? {\r\n totalBytes: data.totalBytes,\r\n projectCount: data.byProject.length,\r\n totalSessions: data.totalSessions,\r\n }\r\n : null\r\n }\r\n />\r\n </div>\r\n\r\n {isLoading && <Loading label={t('common.computing')} className=\"mt-10\" />}\r\n {error && (\r\n <p className=\"mt-10 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-4 py-3 text-sm text-[var(--color-danger)]\">\r\n {t('common.failed')}: {(error as Error).message}\r\n </p>\r\n )}\r\n\r\n {data && (\r\n <>\r\n <motion.div\r\n initial=\"hidden\"\r\n animate=\"show\"\r\n variants={staggerParent}\r\n className=\"mt-8 grid gap-3 sm:grid-cols-3\"\r\n >\r\n <motion.div variants={fadeUpItem}>\r\n <StatCard\r\n accent\r\n label={t('disk.stat.total')}\r\n value={formatBytes(data.totalBytes).split(' ')[0]}\r\n unit={formatBytes(data.totalBytes).split(' ')[1]}\r\n trail={t('disk.stat.acrossProjects', { n: data.byProject.length })}\r\n />\r\n </motion.div>\r\n <motion.div variants={fadeUpItem}>\r\n <StatCard\r\n label={t('disk.stat.sessions')}\r\n value={data.totalSessions.toLocaleString()}\r\n trail={\r\n data.topSessions[0]\r\n ? t('disk.stat.largest', { size: formatBytes(data.topSessions[0].totalBytes) })\r\n : undefined\r\n }\r\n />\r\n </motion.div>\r\n <motion.div variants={fadeUpItem}>\r\n <StatCard\r\n label={t('disk.stat.months')}\r\n value={data.byMonth.length}\r\n trail={\r\n data.byMonth[0]\r\n ? `${data.byMonth[0].month} → ${data.byMonth.at(-1)?.month ?? data.byMonth[0].month}`\r\n : undefined\r\n }\r\n />\r\n </motion.div>\r\n </motion.div>\r\n\r\n <div className=\"mt-12 grid gap-8 lg:grid-cols-5\">\r\n <Card\r\n title={t('disk.composition.title')}\r\n subtitle={t('disk.composition.subtitle')}\r\n className=\"lg:col-span-3\"\r\n >\r\n {pieData.length === 0 ? (\r\n <Empty />\r\n ) : (\r\n <div className=\"grid gap-6 md:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)] md:items-center\">\r\n <div className=\"relative mx-auto aspect-square w-full max-w-[260px]\">\r\n <ResponsiveContainer width=\"100%\" height=\"100%\">\r\n <PieChart margin={{ top: 0, right: 0, bottom: 0, left: 0 }}>\r\n <Pie\r\n data={pieData}\r\n dataKey=\"value\"\r\n nameKey=\"name\"\r\n cx=\"50%\"\r\n cy=\"50%\"\r\n innerRadius=\"58%\"\r\n outerRadius=\"92%\"\r\n paddingAngle={1.5}\r\n stroke={surface}\r\n strokeWidth={2}\r\n isAnimationActive={false}\r\n >\r\n {pieData.map((_, i) => (\r\n <Cell key={i} fill={resolvedPalette[i % resolvedPalette.length]} />\r\n ))}\r\n </Pie>\r\n <Tooltip\r\n contentStyle={tooltipStyle(surface, hairline, fgPrimary)}\r\n formatter={(value: number) => formatBytes(value)}\r\n />\r\n </PieChart>\r\n </ResponsiveContainer>\r\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center\">\r\n <span className=\"eyebrow\">{t('disk.composition.total')}</span>\r\n <span className=\"mt-1 font-mono text-2xl font-light tabular-nums text-[var(--color-fg-primary)]\">\r\n {formatBytes(data.totalBytes)}\r\n </span>\r\n </div>\r\n </div>\r\n <ol className=\"space-y-1.5 text-sm\">\r\n {pieData.slice(0, 8).map((p, i) => {\r\n const pct = ((p.value / data.totalBytes) * 100).toFixed(1);\r\n return (\r\n <li key={i} className=\"grid grid-cols-[14px_1fr_auto] items-baseline gap-2\">\r\n <span\r\n aria-hidden\r\n className=\"block h-2.5 w-2.5 self-center rounded-sm\"\r\n style={{ background: resolvedPalette[i % resolvedPalette.length] }}\r\n />\r\n <span className=\"truncate font-mono text-xs text-[var(--color-fg-secondary)]\" title={p.name}>\r\n {p.name}\r\n </span>\r\n <span className=\"font-mono tabular-nums text-xs text-[var(--color-fg-primary)]\">\r\n {pct}% <span className=\"text-[var(--color-fg-faint)]\">· {formatBytes(p.value)}</span>\r\n </span>\r\n </li>\r\n );\r\n })}\r\n </ol>\r\n </div>\r\n )}\r\n </Card>\r\n\r\n <Card\r\n title={t('disk.cadence.title')}\r\n subtitle={t('disk.cadence.subtitle')}\r\n className=\"lg:col-span-2\"\r\n >\r\n {monthData.length === 0 ? (\r\n <Empty />\r\n ) : (\r\n <div className=\"h-72\">\r\n <ResponsiveContainer width=\"100%\" height=\"100%\">\r\n <AreaChart data={monthData} margin={{ top: 10, right: 8, bottom: 0, left: -10 }}>\r\n <defs>\r\n <linearGradient id=\"cadenceFill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\r\n <stop offset=\"0%\" stopColor={accent} stopOpacity={0.55} />\r\n <stop offset=\"100%\" stopColor={accent} stopOpacity={0} />\r\n </linearGradient>\r\n </defs>\r\n <XAxis\r\n dataKey=\"month\"\r\n tick={{ fontSize: 11, fill: muted, fontFamily: 'var(--font-mono)' }}\r\n tickLine={false}\r\n axisLine={{ stroke: hairline }}\r\n />\r\n <YAxis\r\n tick={{ fontSize: 11, fill: muted, fontFamily: 'var(--font-mono)' }}\r\n tickLine={false}\r\n axisLine={false}\r\n width={36}\r\n />\r\n <Tooltip\r\n contentStyle={tooltipStyle(surface, hairline, fgPrimary)}\r\n formatter={(value: number) => `${value.toFixed(2)} MB`}\r\n cursor={{ stroke: hairline, strokeDasharray: '2 3' }}\r\n />\r\n <Area\r\n type=\"monotone\"\r\n dataKey=\"MB\"\r\n stroke={accent}\r\n strokeWidth={1.6}\r\n fill=\"url(#cadenceFill)\"\r\n />\r\n </AreaChart>\r\n </ResponsiveContainer>\r\n </div>\r\n )}\r\n </Card>\r\n </div>\r\n\r\n <div className=\"surface-card mt-12 p-6\">\r\n <div className=\"flex items-baseline justify-between\">\r\n <h2 className=\"font-display text-xl font-light tracking-tight text-[var(--color-fg-primary)]\">\r\n {t('disk.heaviest.title')}\r\n </h2>\r\n {data.topSessions.length > 0 && (\r\n <span className=\"font-mono text-[11px] uppercase tracking-[0.16em] text-[var(--color-fg-muted)]\">\r\n {t('disk.heaviest.top', { n: data.topSessions.length })}\r\n </span>\r\n )}\r\n </div>\r\n <div className=\"rule-dotted mt-3\" aria-hidden />\r\n {data.topSessions.length === 0 ? (\r\n <Empty className=\"mt-6\" />\r\n ) : (\r\n <div className=\"mt-4 -mx-6 overflow-x-auto px-6\">\r\n <table className=\"w-full table-fixed text-sm\">\r\n <colgroup>\r\n <col className=\"w-10\" />\r\n <col />\r\n <col className=\"w-[22rem]\" />\r\n <col className=\"w-24\" />\r\n <col className=\"w-24\" />\r\n </colgroup>\r\n <thead>\r\n <tr className=\"text-left\">\r\n <th className=\"px-2 py-3 eyebrow\">{t('disk.col.num')}</th>\r\n <th className=\"px-2 py-3 eyebrow\">{t('disk.col.title')}</th>\r\n <th className=\"px-2 py-3 eyebrow\">{t('disk.col.project')}</th>\r\n <th className=\"px-2 py-3 eyebrow text-right\">{t('disk.col.last')}</th>\r\n <th className=\"px-2 py-3 eyebrow text-right\">{t('disk.col.size')}</th>\r\n </tr>\r\n </thead>\r\n <tbody className=\"border-t border-[var(--color-hairline)]\">\r\n {data.topSessions.map((s, i) => {\r\n const displayTitle = s.customTitle ?? s.title;\r\n return (\r\n <tr\r\n key={`${s.projectId}/${s.sessionId}`}\r\n className=\"ribbon-row border-b border-[var(--color-hairline)] hover:bg-[var(--color-sunken)]\"\r\n >\r\n <td className=\"px-2 py-3 align-top font-mono text-[11px] text-[var(--color-fg-faint)]\">\r\n {String(i + 1).padStart(2, '0')}\r\n </td>\r\n <td className=\"px-2 py-3 align-top\">\r\n <Link\r\n to={`/projects/${encodeURIComponent(s.projectId)}/sessions/${s.sessionId}`}\r\n className=\"block truncate font-medium text-[var(--color-fg-primary)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]\"\r\n title={displayTitle}\r\n >\r\n {displayTitle}\r\n </Link>\r\n </td>\r\n <td className=\"px-2 py-3 align-top font-mono text-[12px] text-[var(--color-fg-muted)]\">\r\n <Link\r\n to={`/projects/${encodeURIComponent(s.projectId)}`}\r\n className=\"block truncate hover:text-[var(--color-fg-primary)]\"\r\n title={s.projectId}\r\n >\r\n {projectCwdLabel(data, s.projectId)}\r\n </Link>\r\n </td>\r\n <td className=\"px-2 py-3 text-right align-top font-mono text-[12.5px] text-[var(--color-fg-secondary)]\">\r\n {formatRelativeTime(s.lastAt)}\r\n </td>\r\n <td className=\"px-2 py-3 text-right align-top font-mono tabular-nums text-[var(--color-fg-primary)]\">\r\n {formatBytes(s.totalBytes)}\r\n </td>\r\n </tr>\r\n );\r\n })}\r\n </tbody>\r\n </table>\r\n </div>\r\n )}\r\n </div>\r\n\r\n <CleanupSuggestions />\r\n </>\r\n )}\r\n </section>\r\n );\r\n}\r\n\r\nfunction Masthead({\r\n title,\r\n tagline,\r\n stats,\r\n}: {\r\n title: string;\r\n tagline: string;\r\n stats: {\r\n totalBytes: number;\r\n projectCount: number;\r\n totalSessions: number;\r\n } | null;\r\n}) {\r\n const t = useT();\r\n return (\r\n <header className=\"relative\">\r\n <div className=\"flex flex-wrap items-baseline gap-x-4 gap-y-1\">\r\n <h1 className=\"font-display text-[clamp(1.75rem,3.5vw,2.25rem)] font-light leading-[1.1] tracking-[-0.02em] text-[var(--color-fg-primary)]\">\r\n {title}\r\n <span className=\"text-[var(--color-accent)]\">.</span>\r\n </h1>\r\n <p className=\"min-w-0 flex-1 font-display text-[13px] italic leading-snug text-[var(--color-fg-muted)]\">\r\n {tagline}\r\n </p>\r\n </div>\r\n {stats && (\r\n <div className=\"mt-3 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-xs\">\r\n <MetaItem label={t('disk.meta.total')} value={formatBytes(stats.totalBytes)} />\r\n <Sep />\r\n <MetaItem label={t('disk.meta.projects')} value={stats.projectCount} />\r\n <Sep />\r\n <MetaItem label={t('disk.meta.sessions')} value={stats.totalSessions.toLocaleString()} />\r\n </div>\r\n )}\r\n </header>\r\n );\r\n}\r\n\r\nfunction tooltipStyle(surface: string, hairline: string, fg: string): React.CSSProperties {\r\n return {\r\n background: surface,\r\n border: `1px solid ${hairline}`,\r\n borderRadius: 8,\r\n fontFamily: 'var(--font-mono)',\r\n fontSize: 11,\r\n color: fg,\r\n boxShadow: 'var(--shadow-pop)',\r\n };\r\n}\r\n\r\nfunction projectCwdLabel(data: DiskUsage, projectId: string): string {\r\n const p = data.byProject.find((row) => row.projectId === projectId);\r\n return p ? shortCwd(p.decodedCwd) : projectId;\r\n}\r\n\r\nfunction shortCwd(cwd: string): string {\r\n const parts = cwd.split(/[\\\\/]+/).filter(Boolean);\r\n if (parts.length <= 2) return cwd;\r\n return '…/' + parts.slice(-2).join('/');\r\n}\r\n\r\nfunction Card({\r\n title,\r\n subtitle,\r\n children,\r\n className = '',\r\n}: {\r\n title: string;\r\n subtitle?: string;\r\n children: React.ReactNode;\r\n className?: string;\r\n}) {\r\n return (\r\n <section className={`surface-card p-5 ${className}`}>\r\n <header className=\"mb-4 flex items-baseline justify-between gap-3\">\r\n <h3 className=\"font-display text-lg font-light tracking-tight text-[var(--color-fg-primary)]\">\r\n {title}\r\n </h3>\r\n {subtitle && (\r\n <span className=\"font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--color-fg-muted)]\">\r\n {subtitle}\r\n </span>\r\n )}\r\n </header>\r\n {children}\r\n </section>\r\n );\r\n}\r\n\r\nfunction Empty({ className = '' }: { className?: string }) {\r\n const t = useT();\r\n return (\r\n <p className={`font-mono text-xs uppercase tracking-[0.18em] text-[var(--color-fg-muted)] ${className}`}>\r\n {t('common.noData')}\r\n </p>\r\n );\r\n}\r\n"],"names":["CleanupSuggestions","t","useT","data","isLoading","error","useQuery","queryKeys","api","jsxs","jsx","Loading","LargeSessionsTable","OrphanTable","SectionHeader","title","hint","row","display","Link","shortCwd","formatRelativeTime","formatBytes","kind","rows","confirming","setConfirming","useState","OrphanConfirmDialog","orphan","onClose","queryClient","useQueryClient","mutation","useMutation","isPendingRef","useRef","useEffect","onKey","e","motion","cwd","parts","StatCard","label","value","unit","trail","accent","PALETTE_VARS","CHART_VARS","useThemeColors","vars","snapshot","setSnapshot","readVars","observer","cs","out","v","DiskUsageRoute","colors","muted","hairline","surface","fgPrimary","palette","resolvedPalette","useMemo","pieData","p","monthData","m","Masthead","Fragment","staggerParent","fadeUpItem","_a","Card","Empty","ResponsiveContainer","PieChart","Pie","_","i","Cell","Tooltip","tooltipStyle","pct","AreaChart","XAxis","YAxis","Area","s","displayTitle","projectCwdLabel","tagline","stats","MetaItem","Sep","fg","projectId","subtitle","children","className"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{r as x,j as e}from"./react-W0jzChlo.js";import{u as P,a as h}from"./query-C1K1uQRu.js";import{u as C,P as I,c as b,q as u}from"./index-DLATR3tZ.js";import{L as S}from"./router-DfbutHY3.js";import"./vendor-CH80ylbS.js";const E=["skip","overwrite-if-newer","keep-both"],K={skip:"import.policy.skip","overwrite-if-newer":"import.policy.overwrite-if-newer","keep-both":"import.policy.keep-both"},L={create:"import.action.create",overwrite:"import.action.overwrite","keep-both":"import.action.keep-both",skip:"import.action.skip"},y={"existing-project":"import.suggestion.existing-project","original-path":"import.suggestion.original-path","same-basename":"import.suggestion.same-basename"},T={create:"import.memory.create",skip:"import.memory.skip",conflict:"import.memory.conflict"};function q(o){switch(o){case"create":return"border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] text-[var(--color-fg-primary)]";case"overwrite":return"border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] text-[var(--color-danger)]";case"keep-both":return"border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)] text-[var(--color-accent-ink)] dark:text-[var(--color-accent)]";default:return"border-[var(--color-hairline-strong)] bg-[var(--color-sunken)] text-[var(--color-fg-muted)]"}}function A(){const o=C(),d=P(),[l,j]=x.useState(""),[i,v]=x.useState(""),[s,w]=x.useState("skip"),[t,g]=x.useState(null),n=h({mutationFn:r=>b("/api/import/preview",{method:"POST",body:JSON.stringify({bundleDir:l.trim(),targetCwd:r.targetCwd,collisionPolicy:r.collisionPolicy})}),onSuccess:r=>{g(r),v(r.remap.targetCwd)}}),a=h({mutationFn:()=>b("/api/import",{method:"POST",body:JSON.stringify({bundleDir:l.trim(),targetCwd:i.trim(),collisionPolicy:s})}),onSuccess:r=>{d.invalidateQueries({queryKey:u.projects()}),d.invalidateQueries({queryKey:u.projectSessions(r.targetProjectId)}),d.invalidateQueries({queryKey:u.projectMemory(r.targetProjectId)}),d.invalidateQueries({queryKey:u.diskUsage()})}});function f(){l.trim()!==""&&(g(null),a.reset(),n.mutate({targetCwd:void 0,collisionPolicy:s}))}function m(r,k){a.reset(),n.mutate({targetCwd:r.trim()||void 0,collisionPolicy:k})}function N(r){w(r),t&&m(i,r)}const c=a.data,p=n.isPending;return e.jsxs("section",{children:[e.jsxs("div",{className:"surface-card p-6",children:[e.jsx(I,{eyebrow:o("nav.import"),title:o("import.title")}),e.jsx("p",{className:"mt-2 max-w-2xl text-sm text-[var(--color-fg-muted)]",children:o("import.tagline")}),e.jsxs("div",{className:"mt-5 flex flex-col gap-2 sm:flex-row",children:[e.jsx("input",{type:"text",value:l,onChange:r=>j(r.target.value),onKeyDown:r=>r.key==="Enter"&&f(),placeholder:o("import.bundlePlaceholder"),spellCheck:!1,"aria-label":o("import.bundleLabel"),className:"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]"}),e.jsx("button",{type:"button",onClick:f,disabled:l.trim()===""||p,className:"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",children:o(p?"import.btn.loading":t?"import.btn.recheck":"import.btn.load")})]}),n.error&&e.jsx("p",{className:"mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]",children:n.error.message}),!t&&!n.error&&e.jsx("p",{className:"mt-4 text-sm text-[var(--color-fg-muted)]",children:o("import.empty")})]}),t&&e.jsxs("div",{className:"surface-card mt-6 space-y-6 p-6",children:[e.jsx("p",{className:"break-all font-mono text-xs text-[var(--color-fg-muted)]",children:o("import.source",{platform:t.source.platform,cwd:t.source.cwd})}),e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.targetLabel")}),e.jsxs("div",{className:"mt-1.5 flex flex-col gap-2 sm:flex-row",children:[e.jsx("input",{type:"text",value:i,onChange:r=>v(r.target.value),onKeyDown:r=>r.key==="Enter"&&m(i,s),spellCheck:!1,"aria-label":o("import.targetLabel"),className:"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]"}),e.jsx("button",{type:"button",onClick:()=>m(i,s),disabled:p,className:"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-2 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50",children:o("import.btn.recheck")})]}),e.jsx("p",{className:"mt-1.5 text-xs text-[var(--color-fg-muted)]",children:o("import.targetHint")}),e.jsx("p",{className:"mt-1 font-mono text-[11px] text-[var(--color-fg-faint)]",children:o("import.targetId",{id:t.remap.targetProjectId})}),t.suggestions.length>0&&e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"eyebrow mr-1",children:o("import.suggestions")}),t.suggestions.map(r=>e.jsxs("button",{type:"button",onClick:()=>{v(r.cwd),m(r.cwd,s)},className:"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] bg-[var(--color-surface)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]",title:o(y[r.reason]),children:[e.jsx("span",{className:"max-w-[20rem] truncate",children:r.cwd}),e.jsxs("span",{className:"text-[var(--color-fg-faint)]",children:["· ",o(y[r.reason])]})]},r.projectId))]})]}),e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.policyLabel")}),e.jsx("div",{className:"mt-1.5 inline-flex flex-wrap gap-1 rounded-[var(--radius-input)] border border-[var(--color-hairline)] bg-[var(--color-sunken)] p-1",children:E.map(r=>e.jsx("button",{type:"button",onClick:()=>N(r),"aria-pressed":s===r,className:"rounded-[var(--radius-control)] px-3 py-1.5 text-xs font-medium transition "+(s===r?"bg-[var(--color-surface)] text-[var(--color-fg-primary)] shadow-[var(--shadow-rise)]":"text-[var(--color-fg-muted)] hover:text-[var(--color-fg-primary)]"),children:o(K[r])},r))})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-baseline justify-between",children:[e.jsx("span",{className:"eyebrow",children:o("import.sessions.heading")}),e.jsx("span",{className:"font-mono text-[11px] text-[var(--color-fg-faint)]",children:o("import.history",{n:t.historyLinesToAdd})})]}),t.sessions.length===0?e.jsx("p",{className:"mt-2 text-sm text-[var(--color-fg-muted)]",children:o("import.sessions.empty")}):e.jsx("ul",{className:"mt-2 space-y-1.5",children:t.sessions.map(r=>e.jsxs("li",{className:"flex items-center gap-3 rounded-md border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-3 py-2",children:[e.jsx("span",{className:"shrink-0 rounded-[var(--radius-control)] border px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.12em] "+q(r.action),children:o(L[r.action])}),e.jsxs("span",{className:"min-w-0 flex-1",children:[e.jsx("span",{className:"block truncate text-sm text-[var(--color-fg-primary)]",children:r.title}),e.jsx("span",{className:"block truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]",children:r.newSessionId??r.sessionId})]}),r.reason&&e.jsx("span",{className:"shrink-0 text-[11px] text-[var(--color-fg-muted)]",children:r.reason})]},r.sessionId))})]}),t.memory.length>0&&e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.memory.heading")}),e.jsx("ul",{className:"mt-2 flex flex-wrap gap-1.5",children:t.memory.map(r=>e.jsxs("li",{className:"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)]",children:[e.jsx("span",{className:"text-[var(--color-fg-primary)]",children:r.filename}),e.jsxs("span",{className:"text-[var(--color-fg-faint)]",children:["·"," ",r.action==="conflict"?o("import.memory.conflict",{name:r.writtenAs??""}):o(T[r.action])]})]},r.filename))})]}),a.error&&e.jsx("p",{className:"rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]",children:a.error.message}),c?e.jsxs("div",{className:"space-y-3 rounded-[var(--radius-card)] border border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] px-4 py-3",children:[e.jsx("p",{className:"font-display text-lg font-light text-[var(--color-fg-primary)]",children:o("import.result.title")}),e.jsx("p",{className:"text-sm text-[var(--color-fg-secondary)]",children:o("import.result.summary",{n:c.imported.length,skipped:c.skipped.length,memory:c.memoryWritten.length,lines:c.historyLinesAdded})}),e.jsx(S,{to:`/projects/${encodeURIComponent(c.targetProjectId)}`,className:"inline-flex w-fit items-center gap-2 rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-1.5 text-sm font-medium text-[var(--color-canvas)] hover:opacity-90",children:o("import.result.viewProject")})]}):e.jsx("div",{className:"flex justify-end border-t border-[var(--color-hairline)] pt-4",children:e.jsx("button",{type:"button",onClick:()=>a.mutate(),disabled:i.trim()===""||p||a.isPending,className:"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-5 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",children:a.isPending?o("import.btn.committing"):o("import.btn.commit")})})]})]})}export{A as default};
2
- //# sourceMappingURL=ImportPage-b8NORa8b.js.map
1
+ import{r as x,j as e}from"./react-CPkiFScu.js";import{b as P,a as b}from"./query-CS7JQ86v.js";import{u as C,P as I,b as h,q as u}from"./index-7aMrnHJG.js";import{L as S}from"./router-DwaHAh1G.js";import"./vendor-Cs8vYp-N.js";const E=["skip","overwrite-if-newer","keep-both"],K={skip:"import.policy.skip","overwrite-if-newer":"import.policy.overwrite-if-newer","keep-both":"import.policy.keep-both"},L={create:"import.action.create",overwrite:"import.action.overwrite","keep-both":"import.action.keep-both",skip:"import.action.skip"},y={"existing-project":"import.suggestion.existing-project","original-path":"import.suggestion.original-path","same-basename":"import.suggestion.same-basename"},T={create:"import.memory.create",skip:"import.memory.skip",conflict:"import.memory.conflict"};function q(o){switch(o){case"create":return"border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] text-[var(--color-fg-primary)]";case"overwrite":return"border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] text-[var(--color-danger)]";case"keep-both":return"border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)] text-[var(--color-accent-ink)] dark:text-[var(--color-accent)]";default:return"border-[var(--color-hairline-strong)] bg-[var(--color-sunken)] text-[var(--color-fg-muted)]"}}function A(){const o=C(),d=P(),[l,j]=x.useState(""),[i,v]=x.useState(""),[s,w]=x.useState("skip"),[t,g]=x.useState(null),n=b({mutationFn:r=>h("/api/import/preview",{method:"POST",body:JSON.stringify({bundleDir:l.trim(),targetCwd:r.targetCwd,collisionPolicy:r.collisionPolicy})}),onSuccess:r=>{g(r),v(r.remap.targetCwd)}}),a=b({mutationFn:()=>h("/api/import",{method:"POST",body:JSON.stringify({bundleDir:l.trim(),targetCwd:i.trim(),collisionPolicy:s})}),onSuccess:r=>{d.invalidateQueries({queryKey:u.projects()}),d.invalidateQueries({queryKey:u.projectSessions(r.targetProjectId)}),d.invalidateQueries({queryKey:u.projectMemory(r.targetProjectId)}),d.invalidateQueries({queryKey:u.diskUsage()})}});function f(){l.trim()!==""&&(g(null),a.reset(),n.mutate({targetCwd:void 0,collisionPolicy:s}))}function m(r,k){a.reset(),n.mutate({targetCwd:r.trim()||void 0,collisionPolicy:k})}function N(r){w(r),t&&m(i,r)}const c=a.data,p=n.isPending;return e.jsxs("section",{children:[e.jsxs("div",{className:"surface-card p-6",children:[e.jsx(I,{eyebrow:o("nav.import"),title:o("import.title")}),e.jsx("p",{className:"mt-2 max-w-2xl text-sm text-[var(--color-fg-muted)]",children:o("import.tagline")}),e.jsxs("div",{className:"mt-5 flex flex-col gap-2 sm:flex-row",children:[e.jsx("input",{type:"text",value:l,onChange:r=>j(r.target.value),onKeyDown:r=>r.key==="Enter"&&f(),placeholder:o("import.bundlePlaceholder"),spellCheck:!1,"aria-label":o("import.bundleLabel"),className:"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]"}),e.jsx("button",{type:"button",onClick:f,disabled:l.trim()===""||p,className:"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",children:o(p?"import.btn.loading":t?"import.btn.recheck":"import.btn.load")})]}),n.error&&e.jsx("p",{className:"mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]",children:n.error.message}),!t&&!n.error&&e.jsx("p",{className:"mt-4 text-sm text-[var(--color-fg-muted)]",children:o("import.empty")})]}),t&&e.jsxs("div",{className:"surface-card mt-6 space-y-6 p-6",children:[e.jsx("p",{className:"break-all font-mono text-xs text-[var(--color-fg-muted)]",children:o("import.source",{platform:t.source.platform,cwd:t.source.cwd})}),e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.targetLabel")}),e.jsxs("div",{className:"mt-1.5 flex flex-col gap-2 sm:flex-row",children:[e.jsx("input",{type:"text",value:i,onChange:r=>v(r.target.value),onKeyDown:r=>r.key==="Enter"&&m(i,s),spellCheck:!1,"aria-label":o("import.targetLabel"),className:"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]"}),e.jsx("button",{type:"button",onClick:()=>m(i,s),disabled:p,className:"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-2 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50",children:o("import.btn.recheck")})]}),e.jsx("p",{className:"mt-1.5 text-xs text-[var(--color-fg-muted)]",children:o("import.targetHint")}),e.jsx("p",{className:"mt-1 font-mono text-[11px] text-[var(--color-fg-faint)]",children:o("import.targetId",{id:t.remap.targetProjectId})}),t.suggestions.length>0&&e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-1.5",children:[e.jsx("span",{className:"eyebrow mr-1",children:o("import.suggestions")}),t.suggestions.map(r=>e.jsxs("button",{type:"button",onClick:()=>{v(r.cwd),m(r.cwd,s)},className:"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] bg-[var(--color-surface)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]",title:o(y[r.reason]),children:[e.jsx("span",{className:"max-w-[20rem] truncate",children:r.cwd}),e.jsxs("span",{className:"text-[var(--color-fg-faint)]",children:["· ",o(y[r.reason])]})]},r.projectId))]})]}),e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.policyLabel")}),e.jsx("div",{className:"mt-1.5 inline-flex flex-wrap gap-1 rounded-[var(--radius-input)] border border-[var(--color-hairline)] bg-[var(--color-sunken)] p-1",children:E.map(r=>e.jsx("button",{type:"button",onClick:()=>N(r),"aria-pressed":s===r,className:"rounded-[var(--radius-control)] px-3 py-1.5 text-xs font-medium transition "+(s===r?"bg-[var(--color-surface)] text-[var(--color-fg-primary)] shadow-[var(--shadow-rise)]":"text-[var(--color-fg-muted)] hover:text-[var(--color-fg-primary)]"),children:o(K[r])},r))})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-baseline justify-between",children:[e.jsx("span",{className:"eyebrow",children:o("import.sessions.heading")}),e.jsx("span",{className:"font-mono text-[11px] text-[var(--color-fg-faint)]",children:o("import.history",{n:t.historyLinesToAdd})})]}),t.sessions.length===0?e.jsx("p",{className:"mt-2 text-sm text-[var(--color-fg-muted)]",children:o("import.sessions.empty")}):e.jsx("ul",{className:"mt-2 space-y-1.5",children:t.sessions.map(r=>e.jsxs("li",{className:"flex items-center gap-3 rounded-md border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-3 py-2",children:[e.jsx("span",{className:"shrink-0 rounded-[var(--radius-control)] border px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.12em] "+q(r.action),children:o(L[r.action])}),e.jsxs("span",{className:"min-w-0 flex-1",children:[e.jsx("span",{className:"block truncate text-sm text-[var(--color-fg-primary)]",children:r.title}),e.jsx("span",{className:"block truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]",children:r.newSessionId??r.sessionId})]}),r.reason&&e.jsx("span",{className:"shrink-0 text-[11px] text-[var(--color-fg-muted)]",children:r.reason})]},r.sessionId))})]}),t.memory.length>0&&e.jsxs("div",{children:[e.jsx("span",{className:"eyebrow",children:o("import.memory.heading")}),e.jsx("ul",{className:"mt-2 flex flex-wrap gap-1.5",children:t.memory.map(r=>e.jsxs("li",{className:"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)]",children:[e.jsx("span",{className:"text-[var(--color-fg-primary)]",children:r.filename}),e.jsxs("span",{className:"text-[var(--color-fg-faint)]",children:["·"," ",r.action==="conflict"?o("import.memory.conflict",{name:r.writtenAs??""}):o(T[r.action])]})]},r.filename))})]}),a.error&&e.jsx("p",{className:"rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]",children:a.error.message}),c?e.jsxs("div",{className:"space-y-3 rounded-[var(--radius-card)] border border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] px-4 py-3",children:[e.jsx("p",{className:"font-display text-lg font-light text-[var(--color-fg-primary)]",children:o("import.result.title")}),e.jsx("p",{className:"text-sm text-[var(--color-fg-secondary)]",children:o("import.result.summary",{n:c.imported.length,skipped:c.skipped.length,memory:c.memoryWritten.length,lines:c.historyLinesAdded})}),e.jsx(S,{to:`/projects/${encodeURIComponent(c.targetProjectId)}`,className:"inline-flex w-fit items-center gap-2 rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-1.5 text-sm font-medium text-[var(--color-canvas)] hover:opacity-90",children:o("import.result.viewProject")})]}):e.jsx("div",{className:"flex justify-end border-t border-[var(--color-hairline)] pt-4",children:e.jsx("button",{type:"button",onClick:()=>a.mutate(),disabled:i.trim()===""||p||a.isPending,className:"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-5 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",children:a.isPending?o("import.btn.committing"):o("import.btn.commit")})})]})]})}export{A as default};
2
+ //# sourceMappingURL=ImportPage-wge4VhZ-.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImportPage-b8NORa8b.js","sources":["../../web/src/routes/ImportPage.tsx"],"sourcesContent":["import { useMutation, useQueryClient } from '@tanstack/react-query';\r\nimport { useState } from 'react';\r\nimport { Link } from 'react-router-dom';\r\nimport PageHeader from '../components/PageHeader.tsx';\r\nimport {\r\n api,\r\n type ImportCollisionPolicy,\r\n type ImportPreviewResult,\r\n type ImportResult,\r\n type ImportSessionAction,\r\n} from '../lib/api.ts';\r\nimport { useT } from '../lib/i18n.ts';\r\nimport { queryKeys } from '../lib/query-keys.ts';\r\n\r\nconst POLICIES: ImportCollisionPolicy[] = ['skip', 'overwrite-if-newer', 'keep-both'];\r\n\r\nconst POLICY_KEY = {\r\n skip: 'import.policy.skip',\r\n 'overwrite-if-newer': 'import.policy.overwrite-if-newer',\r\n 'keep-both': 'import.policy.keep-both',\r\n} as const;\r\n\r\nconst ACTION_KEY = {\r\n create: 'import.action.create',\r\n overwrite: 'import.action.overwrite',\r\n 'keep-both': 'import.action.keep-both',\r\n skip: 'import.action.skip',\r\n} as const;\r\n\r\nconst SUGGEST_KEY = {\r\n 'existing-project': 'import.suggestion.existing-project',\r\n 'original-path': 'import.suggestion.original-path',\r\n 'same-basename': 'import.suggestion.same-basename',\r\n} as const;\r\n\r\nconst MEM_KEY = {\r\n create: 'import.memory.create',\r\n skip: 'import.memory.skip',\r\n conflict: 'import.memory.conflict',\r\n} as const;\r\n\r\nfunction actionTone(action: ImportSessionAction): string {\r\n switch (action) {\r\n case 'create':\r\n return 'border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] text-[var(--color-fg-primary)]';\r\n case 'overwrite':\r\n return 'border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] text-[var(--color-danger)]';\r\n case 'keep-both':\r\n return 'border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)] text-[var(--color-accent-ink)] dark:text-[var(--color-accent)]';\r\n default:\r\n return 'border-[var(--color-hairline-strong)] bg-[var(--color-sunken)] text-[var(--color-fg-muted)]';\r\n }\r\n}\r\n\r\nexport default function ImportPage() {\r\n const t = useT();\r\n const queryClient = useQueryClient();\r\n\r\n const [bundleDir, setBundleDir] = useState('');\r\n const [targetCwd, setTargetCwd] = useState('');\r\n const [policy, setPolicy] = useState<ImportCollisionPolicy>('skip');\r\n const [preview, setPreview] = useState<ImportPreviewResult | null>(null);\r\n\r\n const previewMutation = useMutation({\r\n mutationFn: (vars: { targetCwd?: string; collisionPolicy: ImportCollisionPolicy }) =>\r\n api<ImportPreviewResult>('/api/import/preview', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n bundleDir: bundleDir.trim(),\r\n targetCwd: vars.targetCwd,\r\n collisionPolicy: vars.collisionPolicy,\r\n }),\r\n }),\r\n onSuccess: (data) => {\r\n setPreview(data);\r\n setTargetCwd(data.remap.targetCwd);\r\n },\r\n });\r\n\r\n const commitMutation = useMutation({\r\n mutationFn: () =>\r\n api<ImportResult>('/api/import', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n bundleDir: bundleDir.trim(),\r\n targetCwd: targetCwd.trim(),\r\n collisionPolicy: policy,\r\n }),\r\n }),\r\n onSuccess: (data) => {\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projects() });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projectSessions(data.targetProjectId) });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projectMemory(data.targetProjectId) });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.diskUsage() });\r\n },\r\n });\r\n\r\n function loadBundle() {\r\n if (bundleDir.trim() === '') return;\r\n setPreview(null);\r\n commitMutation.reset();\r\n previewMutation.mutate({ targetCwd: undefined, collisionPolicy: policy });\r\n }\r\n\r\n function recheck(nextTarget: string, nextPolicy: ImportCollisionPolicy) {\r\n commitMutation.reset();\r\n previewMutation.mutate({\r\n targetCwd: nextTarget.trim() || undefined,\r\n collisionPolicy: nextPolicy,\r\n });\r\n }\r\n\r\n function changePolicy(p: ImportCollisionPolicy) {\r\n setPolicy(p);\r\n if (preview) recheck(targetCwd, p);\r\n }\r\n\r\n const result = commitMutation.data;\r\n const busy = previewMutation.isPending;\r\n\r\n return (\r\n <section>\r\n <div className=\"surface-card p-6\">\r\n <PageHeader eyebrow={t('nav.import')} title={t('import.title')} />\r\n <p className=\"mt-2 max-w-2xl text-sm text-[var(--color-fg-muted)]\">\r\n {t('import.tagline')}\r\n </p>\r\n\r\n <div className=\"mt-5 flex flex-col gap-2 sm:flex-row\">\r\n <input\r\n type=\"text\"\r\n value={bundleDir}\r\n onChange={(e) => setBundleDir(e.target.value)}\r\n onKeyDown={(e) => e.key === 'Enter' && loadBundle()}\r\n placeholder={t('import.bundlePlaceholder')}\r\n spellCheck={false}\r\n aria-label={t('import.bundleLabel')}\r\n className=\"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={loadBundle}\r\n disabled={bundleDir.trim() === '' || busy}\r\n className=\"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n {busy ? t('import.btn.loading') : preview ? t('import.btn.recheck') : t('import.btn.load')}\r\n </button>\r\n </div>\r\n\r\n {previewMutation.error && (\r\n <p className=\"mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]\">\r\n {(previewMutation.error as Error).message}\r\n </p>\r\n )}\r\n\r\n {!preview && !previewMutation.error && (\r\n <p className=\"mt-4 text-sm text-[var(--color-fg-muted)]\">{t('import.empty')}</p>\r\n )}\r\n </div>\r\n\r\n {preview && (\r\n <div className=\"surface-card mt-6 space-y-6 p-6\">\r\n <p className=\"break-all font-mono text-xs text-[var(--color-fg-muted)]\">\r\n {t('import.source', { platform: preview.source.platform, cwd: preview.source.cwd })}\r\n </p>\r\n\r\n {/* target */}\r\n <div>\r\n <span className=\"eyebrow\">{t('import.targetLabel')}</span>\r\n <div className=\"mt-1.5 flex flex-col gap-2 sm:flex-row\">\r\n <input\r\n type=\"text\"\r\n value={targetCwd}\r\n onChange={(e) => setTargetCwd(e.target.value)}\r\n onKeyDown={(e) => e.key === 'Enter' && recheck(targetCwd, policy)}\r\n spellCheck={false}\r\n aria-label={t('import.targetLabel')}\r\n className=\"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => recheck(targetCwd, policy)}\r\n disabled={busy}\r\n className=\"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-2 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50\"\r\n >\r\n {t('import.btn.recheck')}\r\n </button>\r\n </div>\r\n <p className=\"mt-1.5 text-xs text-[var(--color-fg-muted)]\">{t('import.targetHint')}</p>\r\n <p className=\"mt-1 font-mono text-[11px] text-[var(--color-fg-faint)]\">\r\n {t('import.targetId', { id: preview.remap.targetProjectId })}\r\n </p>\r\n\r\n {preview.suggestions.length > 0 && (\r\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\r\n <span className=\"eyebrow mr-1\">{t('import.suggestions')}</span>\r\n {preview.suggestions.map((s) => (\r\n <button\r\n key={s.projectId}\r\n type=\"button\"\r\n onClick={() => {\r\n setTargetCwd(s.cwd);\r\n recheck(s.cwd, policy);\r\n }}\r\n className=\"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] bg-[var(--color-surface)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]\"\r\n title={t(SUGGEST_KEY[s.reason])}\r\n >\r\n <span className=\"max-w-[20rem] truncate\">{s.cwd}</span>\r\n <span className=\"text-[var(--color-fg-faint)]\">· {t(SUGGEST_KEY[s.reason])}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* policy */}\r\n <div>\r\n <span className=\"eyebrow\">{t('import.policyLabel')}</span>\r\n <div className=\"mt-1.5 inline-flex flex-wrap gap-1 rounded-[var(--radius-input)] border border-[var(--color-hairline)] bg-[var(--color-sunken)] p-1\">\r\n {POLICIES.map((p) => (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => changePolicy(p)}\r\n aria-pressed={policy === p}\r\n className={\r\n 'rounded-[var(--radius-control)] px-3 py-1.5 text-xs font-medium transition ' +\r\n (policy === p\r\n ? 'bg-[var(--color-surface)] text-[var(--color-fg-primary)] shadow-[var(--shadow-rise)]'\r\n : 'text-[var(--color-fg-muted)] hover:text-[var(--color-fg-primary)]')\r\n }\r\n >\r\n {t(POLICY_KEY[p])}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {/* sessions */}\r\n <div>\r\n <div className=\"flex items-baseline justify-between\">\r\n <span className=\"eyebrow\">{t('import.sessions.heading')}</span>\r\n <span className=\"font-mono text-[11px] text-[var(--color-fg-faint)]\">\r\n {t('import.history', { n: preview.historyLinesToAdd })}\r\n </span>\r\n </div>\r\n {preview.sessions.length === 0 ? (\r\n <p className=\"mt-2 text-sm text-[var(--color-fg-muted)]\">\r\n {t('import.sessions.empty')}\r\n </p>\r\n ) : (\r\n <ul className=\"mt-2 space-y-1.5\">\r\n {preview.sessions.map((s) => (\r\n <li\r\n key={s.sessionId}\r\n className=\"flex items-center gap-3 rounded-md border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-3 py-2\"\r\n >\r\n <span\r\n className={\r\n 'shrink-0 rounded-[var(--radius-control)] border px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.12em] ' +\r\n actionTone(s.action)\r\n }\r\n >\r\n {t(ACTION_KEY[s.action])}\r\n </span>\r\n <span className=\"min-w-0 flex-1\">\r\n <span className=\"block truncate text-sm text-[var(--color-fg-primary)]\">\r\n {s.title}\r\n </span>\r\n <span className=\"block truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]\">\r\n {s.newSessionId ?? s.sessionId}\r\n </span>\r\n </span>\r\n {s.reason && (\r\n <span className=\"shrink-0 text-[11px] text-[var(--color-fg-muted)]\">\r\n {s.reason}\r\n </span>\r\n )}\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n {/* memory */}\r\n {preview.memory.length > 0 && (\r\n <div>\r\n <span className=\"eyebrow\">{t('import.memory.heading')}</span>\r\n <ul className=\"mt-2 flex flex-wrap gap-1.5\">\r\n {preview.memory.map((m) => (\r\n <li\r\n key={m.filename}\r\n className=\"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)]\"\r\n >\r\n <span className=\"text-[var(--color-fg-primary)]\">{m.filename}</span>\r\n <span className=\"text-[var(--color-fg-faint)]\">\r\n ·{' '}\r\n {m.action === 'conflict'\r\n ? t('import.memory.conflict', { name: m.writtenAs ?? '' })\r\n : t(MEM_KEY[m.action])}\r\n </span>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n )}\r\n\r\n {commitMutation.error && (\r\n <p className=\"rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]\">\r\n {(commitMutation.error as Error).message}\r\n </p>\r\n )}\r\n\r\n {result ? (\r\n <div className=\"space-y-3 rounded-[var(--radius-card)] border border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] px-4 py-3\">\r\n <p className=\"font-display text-lg font-light text-[var(--color-fg-primary)]\">\r\n {t('import.result.title')}\r\n </p>\r\n <p className=\"text-sm text-[var(--color-fg-secondary)]\">\r\n {t('import.result.summary', {\r\n n: result.imported.length,\r\n skipped: result.skipped.length,\r\n memory: result.memoryWritten.length,\r\n lines: result.historyLinesAdded,\r\n })}\r\n </p>\r\n <Link\r\n to={`/projects/${encodeURIComponent(result.targetProjectId)}`}\r\n className=\"inline-flex w-fit items-center gap-2 rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-1.5 text-sm font-medium text-[var(--color-canvas)] hover:opacity-90\"\r\n >\r\n {t('import.result.viewProject')}\r\n </Link>\r\n </div>\r\n ) : (\r\n <div className=\"flex justify-end border-t border-[var(--color-hairline)] pt-4\">\r\n <button\r\n type=\"button\"\r\n onClick={() => commitMutation.mutate()}\r\n disabled={targetCwd.trim() === '' || busy || commitMutation.isPending}\r\n className=\"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-5 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n {commitMutation.isPending ? t('import.btn.committing') : t('import.btn.commit')}\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </section>\r\n );\r\n}\r\n"],"names":["POLICIES","POLICY_KEY","ACTION_KEY","SUGGEST_KEY","MEM_KEY","actionTone","action","ImportPage","t","useT","queryClient","useQueryClient","bundleDir","setBundleDir","useState","targetCwd","setTargetCwd","policy","setPolicy","preview","setPreview","previewMutation","useMutation","vars","api","data","commitMutation","queryKeys","loadBundle","recheck","nextTarget","nextPolicy","changePolicy","p","result","busy","jsxs","jsx","PageHeader","e","s","m","Link"],"mappings":"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"}
1
+ {"version":3,"file":"ImportPage-wge4VhZ-.js","sources":["../../web/src/routes/ImportPage.tsx"],"sourcesContent":["import { useMutation, useQueryClient } from '@tanstack/react-query';\r\nimport { useState } from 'react';\r\nimport { Link } from 'react-router-dom';\r\nimport PageHeader from '../components/PageHeader.tsx';\r\nimport {\r\n api,\r\n type ImportCollisionPolicy,\r\n type ImportPreviewResult,\r\n type ImportResult,\r\n type ImportSessionAction,\r\n} from '../lib/api.ts';\r\nimport { useT } from '../lib/i18n.ts';\r\nimport { queryKeys } from '../lib/query-keys.ts';\r\n\r\nconst POLICIES: ImportCollisionPolicy[] = ['skip', 'overwrite-if-newer', 'keep-both'];\r\n\r\nconst POLICY_KEY = {\r\n skip: 'import.policy.skip',\r\n 'overwrite-if-newer': 'import.policy.overwrite-if-newer',\r\n 'keep-both': 'import.policy.keep-both',\r\n} as const;\r\n\r\nconst ACTION_KEY = {\r\n create: 'import.action.create',\r\n overwrite: 'import.action.overwrite',\r\n 'keep-both': 'import.action.keep-both',\r\n skip: 'import.action.skip',\r\n} as const;\r\n\r\nconst SUGGEST_KEY = {\r\n 'existing-project': 'import.suggestion.existing-project',\r\n 'original-path': 'import.suggestion.original-path',\r\n 'same-basename': 'import.suggestion.same-basename',\r\n} as const;\r\n\r\nconst MEM_KEY = {\r\n create: 'import.memory.create',\r\n skip: 'import.memory.skip',\r\n conflict: 'import.memory.conflict',\r\n} as const;\r\n\r\nfunction actionTone(action: ImportSessionAction): string {\r\n switch (action) {\r\n case 'create':\r\n return 'border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] text-[var(--color-fg-primary)]';\r\n case 'overwrite':\r\n return 'border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] text-[var(--color-danger)]';\r\n case 'keep-both':\r\n return 'border-[var(--color-accent)]/40 bg-[var(--color-accent-soft)] text-[var(--color-accent-ink)] dark:text-[var(--color-accent)]';\r\n default:\r\n return 'border-[var(--color-hairline-strong)] bg-[var(--color-sunken)] text-[var(--color-fg-muted)]';\r\n }\r\n}\r\n\r\nexport default function ImportPage() {\r\n const t = useT();\r\n const queryClient = useQueryClient();\r\n\r\n const [bundleDir, setBundleDir] = useState('');\r\n const [targetCwd, setTargetCwd] = useState('');\r\n const [policy, setPolicy] = useState<ImportCollisionPolicy>('skip');\r\n const [preview, setPreview] = useState<ImportPreviewResult | null>(null);\r\n\r\n const previewMutation = useMutation({\r\n mutationFn: (vars: { targetCwd?: string; collisionPolicy: ImportCollisionPolicy }) =>\r\n api<ImportPreviewResult>('/api/import/preview', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n bundleDir: bundleDir.trim(),\r\n targetCwd: vars.targetCwd,\r\n collisionPolicy: vars.collisionPolicy,\r\n }),\r\n }),\r\n onSuccess: (data) => {\r\n setPreview(data);\r\n setTargetCwd(data.remap.targetCwd);\r\n },\r\n });\r\n\r\n const commitMutation = useMutation({\r\n mutationFn: () =>\r\n api<ImportResult>('/api/import', {\r\n method: 'POST',\r\n body: JSON.stringify({\r\n bundleDir: bundleDir.trim(),\r\n targetCwd: targetCwd.trim(),\r\n collisionPolicy: policy,\r\n }),\r\n }),\r\n onSuccess: (data) => {\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projects() });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projectSessions(data.targetProjectId) });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.projectMemory(data.targetProjectId) });\r\n queryClient.invalidateQueries({ queryKey: queryKeys.diskUsage() });\r\n },\r\n });\r\n\r\n function loadBundle() {\r\n if (bundleDir.trim() === '') return;\r\n setPreview(null);\r\n commitMutation.reset();\r\n previewMutation.mutate({ targetCwd: undefined, collisionPolicy: policy });\r\n }\r\n\r\n function recheck(nextTarget: string, nextPolicy: ImportCollisionPolicy) {\r\n commitMutation.reset();\r\n previewMutation.mutate({\r\n targetCwd: nextTarget.trim() || undefined,\r\n collisionPolicy: nextPolicy,\r\n });\r\n }\r\n\r\n function changePolicy(p: ImportCollisionPolicy) {\r\n setPolicy(p);\r\n if (preview) recheck(targetCwd, p);\r\n }\r\n\r\n const result = commitMutation.data;\r\n const busy = previewMutation.isPending;\r\n\r\n return (\r\n <section>\r\n <div className=\"surface-card p-6\">\r\n <PageHeader eyebrow={t('nav.import')} title={t('import.title')} />\r\n <p className=\"mt-2 max-w-2xl text-sm text-[var(--color-fg-muted)]\">\r\n {t('import.tagline')}\r\n </p>\r\n\r\n <div className=\"mt-5 flex flex-col gap-2 sm:flex-row\">\r\n <input\r\n type=\"text\"\r\n value={bundleDir}\r\n onChange={(e) => setBundleDir(e.target.value)}\r\n onKeyDown={(e) => e.key === 'Enter' && loadBundle()}\r\n placeholder={t('import.bundlePlaceholder')}\r\n spellCheck={false}\r\n aria-label={t('import.bundleLabel')}\r\n className=\"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={loadBundle}\r\n disabled={bundleDir.trim() === '' || busy}\r\n className=\"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n {busy ? t('import.btn.loading') : preview ? t('import.btn.recheck') : t('import.btn.load')}\r\n </button>\r\n </div>\r\n\r\n {previewMutation.error && (\r\n <p className=\"mt-3 rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]\">\r\n {(previewMutation.error as Error).message}\r\n </p>\r\n )}\r\n\r\n {!preview && !previewMutation.error && (\r\n <p className=\"mt-4 text-sm text-[var(--color-fg-muted)]\">{t('import.empty')}</p>\r\n )}\r\n </div>\r\n\r\n {preview && (\r\n <div className=\"surface-card mt-6 space-y-6 p-6\">\r\n <p className=\"break-all font-mono text-xs text-[var(--color-fg-muted)]\">\r\n {t('import.source', { platform: preview.source.platform, cwd: preview.source.cwd })}\r\n </p>\r\n\r\n {/* target */}\r\n <div>\r\n <span className=\"eyebrow\">{t('import.targetLabel')}</span>\r\n <div className=\"mt-1.5 flex flex-col gap-2 sm:flex-row\">\r\n <input\r\n type=\"text\"\r\n value={targetCwd}\r\n onChange={(e) => setTargetCwd(e.target.value)}\r\n onKeyDown={(e) => e.key === 'Enter' && recheck(targetCwd, policy)}\r\n spellCheck={false}\r\n aria-label={t('import.targetLabel')}\r\n className=\"min-w-0 flex-1 rounded-[var(--radius-input)] border border-[var(--color-hairline-strong)] bg-[var(--color-canvas)] px-3 py-2 font-mono text-sm text-[var(--color-fg-primary)] outline-none focus:border-[var(--color-accent)]\"\r\n />\r\n <button\r\n type=\"button\"\r\n onClick={() => recheck(targetCwd, policy)}\r\n disabled={busy}\r\n className=\"rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] px-4 py-2 text-sm text-[var(--color-fg-secondary)] hover:bg-[var(--color-sunken)] disabled:opacity-50\"\r\n >\r\n {t('import.btn.recheck')}\r\n </button>\r\n </div>\r\n <p className=\"mt-1.5 text-xs text-[var(--color-fg-muted)]\">{t('import.targetHint')}</p>\r\n <p className=\"mt-1 font-mono text-[11px] text-[var(--color-fg-faint)]\">\r\n {t('import.targetId', { id: preview.remap.targetProjectId })}\r\n </p>\r\n\r\n {preview.suggestions.length > 0 && (\r\n <div className=\"mt-2 flex flex-wrap items-center gap-1.5\">\r\n <span className=\"eyebrow mr-1\">{t('import.suggestions')}</span>\r\n {preview.suggestions.map((s) => (\r\n <button\r\n key={s.projectId}\r\n type=\"button\"\r\n onClick={() => {\r\n setTargetCwd(s.cwd);\r\n recheck(s.cwd, policy);\r\n }}\r\n className=\"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline-strong)] bg-[var(--color-surface)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)] hover:border-[var(--color-accent)] hover:text-[var(--color-accent-ink)] dark:hover:text-[var(--color-accent)]\"\r\n title={t(SUGGEST_KEY[s.reason])}\r\n >\r\n <span className=\"max-w-[20rem] truncate\">{s.cwd}</span>\r\n <span className=\"text-[var(--color-fg-faint)]\">· {t(SUGGEST_KEY[s.reason])}</span>\r\n </button>\r\n ))}\r\n </div>\r\n )}\r\n </div>\r\n\r\n {/* policy */}\r\n <div>\r\n <span className=\"eyebrow\">{t('import.policyLabel')}</span>\r\n <div className=\"mt-1.5 inline-flex flex-wrap gap-1 rounded-[var(--radius-input)] border border-[var(--color-hairline)] bg-[var(--color-sunken)] p-1\">\r\n {POLICIES.map((p) => (\r\n <button\r\n key={p}\r\n type=\"button\"\r\n onClick={() => changePolicy(p)}\r\n aria-pressed={policy === p}\r\n className={\r\n 'rounded-[var(--radius-control)] px-3 py-1.5 text-xs font-medium transition ' +\r\n (policy === p\r\n ? 'bg-[var(--color-surface)] text-[var(--color-fg-primary)] shadow-[var(--shadow-rise)]'\r\n : 'text-[var(--color-fg-muted)] hover:text-[var(--color-fg-primary)]')\r\n }\r\n >\r\n {t(POLICY_KEY[p])}\r\n </button>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n {/* sessions */}\r\n <div>\r\n <div className=\"flex items-baseline justify-between\">\r\n <span className=\"eyebrow\">{t('import.sessions.heading')}</span>\r\n <span className=\"font-mono text-[11px] text-[var(--color-fg-faint)]\">\r\n {t('import.history', { n: preview.historyLinesToAdd })}\r\n </span>\r\n </div>\r\n {preview.sessions.length === 0 ? (\r\n <p className=\"mt-2 text-sm text-[var(--color-fg-muted)]\">\r\n {t('import.sessions.empty')}\r\n </p>\r\n ) : (\r\n <ul className=\"mt-2 space-y-1.5\">\r\n {preview.sessions.map((s) => (\r\n <li\r\n key={s.sessionId}\r\n className=\"flex items-center gap-3 rounded-md border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-3 py-2\"\r\n >\r\n <span\r\n className={\r\n 'shrink-0 rounded-[var(--radius-control)] border px-2 py-0.5 text-[10px] font-medium uppercase tracking-[0.12em] ' +\r\n actionTone(s.action)\r\n }\r\n >\r\n {t(ACTION_KEY[s.action])}\r\n </span>\r\n <span className=\"min-w-0 flex-1\">\r\n <span className=\"block truncate text-sm text-[var(--color-fg-primary)]\">\r\n {s.title}\r\n </span>\r\n <span className=\"block truncate font-mono text-[10.5px] text-[var(--color-fg-faint)]\">\r\n {s.newSessionId ?? s.sessionId}\r\n </span>\r\n </span>\r\n {s.reason && (\r\n <span className=\"shrink-0 text-[11px] text-[var(--color-fg-muted)]\">\r\n {s.reason}\r\n </span>\r\n )}\r\n </li>\r\n ))}\r\n </ul>\r\n )}\r\n </div>\r\n\r\n {/* memory */}\r\n {preview.memory.length > 0 && (\r\n <div>\r\n <span className=\"eyebrow\">{t('import.memory.heading')}</span>\r\n <ul className=\"mt-2 flex flex-wrap gap-1.5\">\r\n {preview.memory.map((m) => (\r\n <li\r\n key={m.filename}\r\n className=\"inline-flex items-center gap-1.5 rounded-[var(--radius-control)] border border-[var(--color-hairline)] bg-[var(--color-canvas)] px-2.5 py-1 font-mono text-[11px] text-[var(--color-fg-secondary)]\"\r\n >\r\n <span className=\"text-[var(--color-fg-primary)]\">{m.filename}</span>\r\n <span className=\"text-[var(--color-fg-faint)]\">\r\n ·{' '}\r\n {m.action === 'conflict'\r\n ? t('import.memory.conflict', { name: m.writtenAs ?? '' })\r\n : t(MEM_KEY[m.action])}\r\n </span>\r\n </li>\r\n ))}\r\n </ul>\r\n </div>\r\n )}\r\n\r\n {commitMutation.error && (\r\n <p className=\"rounded-md border border-[var(--color-danger)]/40 bg-[var(--color-danger-soft)] px-3 py-2 text-sm text-[var(--color-danger)]\">\r\n {(commitMutation.error as Error).message}\r\n </p>\r\n )}\r\n\r\n {result ? (\r\n <div className=\"space-y-3 rounded-[var(--radius-card)] border border-[var(--color-moss)]/40 bg-[var(--color-moss-soft)] px-4 py-3\">\r\n <p className=\"font-display text-lg font-light text-[var(--color-fg-primary)]\">\r\n {t('import.result.title')}\r\n </p>\r\n <p className=\"text-sm text-[var(--color-fg-secondary)]\">\r\n {t('import.result.summary', {\r\n n: result.imported.length,\r\n skipped: result.skipped.length,\r\n memory: result.memoryWritten.length,\r\n lines: result.historyLinesAdded,\r\n })}\r\n </p>\r\n <Link\r\n to={`/projects/${encodeURIComponent(result.targetProjectId)}`}\r\n className=\"inline-flex w-fit items-center gap-2 rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-4 py-1.5 text-sm font-medium text-[var(--color-canvas)] hover:opacity-90\"\r\n >\r\n {t('import.result.viewProject')}\r\n </Link>\r\n </div>\r\n ) : (\r\n <div className=\"flex justify-end border-t border-[var(--color-hairline)] pt-4\">\r\n <button\r\n type=\"button\"\r\n onClick={() => commitMutation.mutate()}\r\n disabled={targetCwd.trim() === '' || busy || commitMutation.isPending}\r\n className=\"rounded-[var(--radius-control)] bg-[var(--color-fg-primary)] px-5 py-2 text-sm font-medium text-[var(--color-canvas)] shadow-[var(--shadow-rise)] hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\r\n >\r\n {commitMutation.isPending ? t('import.btn.committing') : t('import.btn.commit')}\r\n </button>\r\n </div>\r\n )}\r\n </div>\r\n )}\r\n </section>\r\n );\r\n}\r\n"],"names":["POLICIES","POLICY_KEY","ACTION_KEY","SUGGEST_KEY","MEM_KEY","actionTone","action","ImportPage","t","useT","queryClient","useQueryClient","bundleDir","setBundleDir","useState","targetCwd","setTargetCwd","policy","setPolicy","preview","setPreview","previewMutation","useMutation","vars","api","data","commitMutation","queryKeys","loadBundle","recheck","nextTarget","nextPolicy","changePolicy","p","result","busy","jsxs","jsx","PageHeader","e","s","m","Link"],"mappings":"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"}