@edspencer/paddock 0.67.0 → 0.68.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 (102) hide show
  1. package/README.md +24 -30
  2. package/package.json +1 -1
  3. package/packages/server/dist/adoptable.d.ts +57 -0
  4. package/packages/server/dist/adoptable.js +26 -3
  5. package/packages/server/dist/agent-errors.js +1 -1
  6. package/packages/server/dist/claude-home.d.ts +4 -4
  7. package/packages/server/dist/claude-home.js +4 -4
  8. package/packages/server/dist/claude-mcp.d.ts +4 -2
  9. package/packages/server/dist/cli/args.d.ts +41 -2
  10. package/packages/server/dist/cli/args.js +119 -17
  11. package/packages/server/dist/cli/paddock.js +87 -96
  12. package/packages/server/dist/cli/service/backend.d.ts +38 -0
  13. package/packages/server/dist/cli/service/backend.js +19 -0
  14. package/packages/server/dist/cli/service/index.d.ts +28 -0
  15. package/packages/server/dist/cli/service/index.js +179 -0
  16. package/packages/server/dist/cli/service/launchd.d.ts +47 -0
  17. package/packages/server/dist/cli/service/launchd.js +206 -0
  18. package/packages/server/dist/cli/service/spec.d.ts +106 -0
  19. package/packages/server/dist/cli/service/spec.js +124 -0
  20. package/packages/server/dist/cli/service/systemd.d.ts +48 -0
  21. package/packages/server/dist/cli/service/systemd.js +155 -0
  22. package/packages/server/dist/discover.d.ts +170 -0
  23. package/packages/server/dist/discover.js +365 -0
  24. package/packages/server/dist/herdctl.d.ts +25 -2
  25. package/packages/server/dist/herdctl.js +30 -1
  26. package/packages/server/dist/routes/discover.d.ts +3 -0
  27. package/packages/server/dist/routes/discover.js +140 -0
  28. package/packages/server/dist/routes.d.ts +1 -0
  29. package/packages/server/dist/routes.js +2 -0
  30. package/packages/server/package.json +1 -1
  31. package/packages/web/dist/assets/Chip-NMeHUE94.js +2 -0
  32. package/packages/web/dist/assets/DiscoverPage-LY0CRBVz.js +2 -0
  33. package/packages/web/dist/assets/DiscoverView-Bt4FUTjB.js +2 -0
  34. package/packages/web/dist/assets/InstanceConfigPage-DIQJtIYC.js +2 -0
  35. package/packages/web/dist/assets/{MarkdownRenderer-MaSe5aAI.js → MarkdownRenderer-BVMZB_mP.js} +11 -11
  36. package/packages/web/dist/assets/{ProjectMenu-DmWpr9od.js → ProjectMenu-D9Nsqjxy.js} +1 -1
  37. package/packages/web/dist/assets/{ProjectRedirect-BJeDERUd.js → ProjectRedirect-B8rJ9qqe.js} +1 -1
  38. package/packages/web/dist/assets/{ProjectView-Bbecs1u7.js → ProjectView-D-21ULHU.js} +10 -10
  39. package/packages/web/dist/assets/{ProjectsGrid-BE4cAtu5.js → ProjectsGrid-CDqvd4bj.js} +1 -1
  40. package/packages/web/dist/assets/RootHome-CmiJG9wb.js +2 -0
  41. package/packages/web/dist/assets/{Surfaces-DtSsbnLh.js → Surfaces-c410wTbw.js} +1 -1
  42. package/packages/web/dist/assets/{arc-NLb6J_GZ.js → arc-DN2jEPE9.js} +1 -1
  43. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BbiSIctn.js → architectureDiagram-3BPJPVTR-BLMFd81z.js} +1 -1
  44. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-Br1sRzRE.js → blockDiagram-GPEHLZMM-BweEECqE.js} +1 -1
  45. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DUvJurko.js → c4Diagram-AAUBKEIU-DpN_9YFO.js} +1 -1
  46. package/packages/web/dist/assets/channel-B_SiD-tA.js +2 -0
  47. package/packages/web/dist/assets/{chunk-2J33WTMH-Cm_E_LAD.js → chunk-2J33WTMH-BxWEBoli.js} +1 -1
  48. package/packages/web/dist/assets/{chunk-4BX2VUAB-CK6R2MCN.js → chunk-4BX2VUAB-C5nkcPRR.js} +1 -1
  49. package/packages/web/dist/assets/{chunk-55IACEB6-D7HXvxGy.js → chunk-55IACEB6-B8P3CNX8.js} +1 -1
  50. package/packages/web/dist/assets/{chunk-727SXJPM-BLwevzlY.js → chunk-727SXJPM-D3yuzl-m.js} +1 -1
  51. package/packages/web/dist/assets/{chunk-AQP2D5EJ-CpLWD5so.js → chunk-AQP2D5EJ-CzWxcEIw.js} +1 -1
  52. package/packages/web/dist/assets/{chunk-FMBD7UC4-BNlt1t19.js → chunk-FMBD7UC4-CCB91otB.js} +1 -1
  53. package/packages/web/dist/assets/{chunk-ND2GUHAM-CIt5gAq8.js → chunk-ND2GUHAM-BymMfuY4.js} +1 -1
  54. package/packages/web/dist/assets/{chunk-QZHKN3VN-B1uwIQ9c.js → chunk-QZHKN3VN-CezHuNcf.js} +1 -1
  55. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-Detq0lSJ.js +2 -0
  56. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-Detq0lSJ.js +2 -0
  57. package/packages/web/dist/assets/{core-HXUN76Po.js → core-LjceHm3Z.js} +1 -1
  58. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DJ4_5jxB.js → cose-bilkent-S5V4N54A-BWhmWR_V.js} +1 -1
  59. package/packages/web/dist/assets/{dagre-BM42HDAG-Bc9-haAu.js → dagre-BM42HDAG-BTXWnJYV.js} +1 -1
  60. package/packages/web/dist/assets/diagram-2AECGRRQ-BY2LZdf0.js +44 -0
  61. package/packages/web/dist/assets/diagram-5GNKFQAL-BZ78Imy8.js +11 -0
  62. package/packages/web/dist/assets/{diagram-KO2AKTUF-C5hcR1vA.js → diagram-KO2AKTUF-BV4Ax_0D.js} +3 -3
  63. package/packages/web/dist/assets/{diagram-LMA3HP47-BEFzrSHy.js → diagram-LMA3HP47-1kcTal-d.js} +1 -1
  64. package/packages/web/dist/assets/{diagram-OG6HWLK6-BY5lPsX7.js → diagram-OG6HWLK6-CX3RfHlO.js} +1 -1
  65. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-y_kB8ETg.js → erDiagram-TEJ5UH35-BBS46D95.js} +1 -1
  66. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-Cuhha5cF.js → flowDiagram-I6XJVG4X-CpPHahLZ.js} +1 -1
  67. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DFJpciK5.js → ganttDiagram-6RSMTGT7-CxL_QCuL.js} +1 -1
  68. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-FoA0yuh-.js → gitGraphDiagram-PVQCEYII-BeTkPHaG.js} +2 -2
  69. package/packages/web/dist/assets/index-DYccdR9x.css +1 -0
  70. package/packages/web/dist/assets/index-DjhUG9sA.js +71 -0
  71. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-CKYqD8-c.js → infoDiagram-5YYISTIA-CiMnl36I.js} +1 -1
  72. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-CCIxHIBw.js → ishikawaDiagram-YF4QCWOH-CjASKQeF.js} +2 -2
  73. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-lGYoSyk0.js → journeyDiagram-JHISSGLW-BZzehSqh.js} +1 -1
  74. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-B3D7CIwy.js → kanban-definition-UN3LZRKU-Cn-l0UIy.js} +1 -1
  75. package/packages/web/dist/assets/{linear-BiplJBex.js → linear-DyELsH8s.js} +1 -1
  76. package/packages/web/dist/assets/{mermaid.core-BJ7aEinZ.js → mermaid.core-C1adXzqy.js} +5 -5
  77. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-BiXcOtHo.js → mindmap-definition-RKZ34NQL-DP6iSaA_.js} +1 -1
  78. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-tnkj45YF.js → pieDiagram-4H26LBE5-DHpN8Fse.js} +3 -3
  79. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-LqReq3Gl.js → quadrantDiagram-W4KKPZXB-DAfc21Kd.js} +1 -1
  80. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-C1uk9Jty.js → requirementDiagram-4Y6WPE33-X3FfIiGh.js} +3 -3
  81. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-eQQ3OKrV.js → sankeyDiagram-5OEKKPKP-Ch319LBM.js} +4 -4
  82. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-C6-a0lH4.js → sequenceDiagram-3UESZ5HK-CAyajDJc.js} +1 -1
  83. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CsTOkKqz.js → stateDiagram-AJRCARHV-C1G4309_.js} +1 -1
  84. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-C0-BEM28.js +2 -0
  85. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-DUBXH1x-.js → timeline-definition-PNZ67QCA-Bj5ihSj4.js} +1 -1
  86. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CcQrNnou.js → vennDiagram-CIIHVFJN-WIZuVHvV.js} +1 -1
  87. package/packages/web/dist/assets/{wardley-L42UT6IY-Br823uUV.js → wardley-L42UT6IY-CwRQf2fI.js} +1 -1
  88. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DRBECQc_.js → wardleyDiagram-YWT4CUSO-Bug_Zcav.js} +2 -2
  89. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6--HtSFP24.js → xychartDiagram-2RQKCTM6-Cw5xgfwo.js} +1 -1
  90. package/packages/web/dist/index.html +2 -2
  91. package/packages/web/dist/sw.js +1 -1
  92. package/packages/server/dist/cli/here.d.ts +0 -25
  93. package/packages/server/dist/cli/here.js +0 -104
  94. package/packages/web/dist/assets/InstanceConfigPage-sDbCoFxC.js +0 -2
  95. package/packages/web/dist/assets/channel-bezC0w5b.js +0 -2
  96. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DMgrTmQU.js +0 -2
  97. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DMgrTmQU.js +0 -2
  98. package/packages/web/dist/assets/diagram-2AECGRRQ-nLOtPDzu.js +0 -44
  99. package/packages/web/dist/assets/diagram-5GNKFQAL-DThV84R3.js +0 -11
  100. package/packages/web/dist/assets/index-BxLurJvz.css +0 -1
  101. package/packages/web/dist/assets/index-C_PXsGTD.js +0 -71
  102. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bsc-VlVI.js +0 -2
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Instance-level Discovery routes (#745).
3
+ *
4
+ * Two GETs, both read-only:
5
+ *
6
+ * GET /api/discover → ranked candidate directories + counts
7
+ * GET /api/discover/sessions?dir=… → one directory's sessions, for expansion
8
+ *
9
+ * ## Why two paths rather than one with `?dir=`
10
+ *
11
+ * Issue #745 sketches this as a single `/api/discover` that changes shape when
12
+ * `dir` is present. Paddock publishes an OpenAPI 3 document GENERATED from these
13
+ * Fastify route schemas (`scripts/dump-openapi.mjs` → `openapi-site/`), and one
14
+ * path cannot describe two different 200 bodies there — it would have to be
15
+ * declared `additionalProperties: true` with no shape at all, and the published
16
+ * contract would silently stop describing the endpoint. The two responses also
17
+ * fail differently: the listing cannot 400, and the expansion must, on any path
18
+ * outside the discovered set. Same handlers, same module, two honest paths.
19
+ *
20
+ * ## Containment
21
+ *
22
+ * `POST …/adopt-chats` refuses a `sourceCwd` its project does not offer,
23
+ * deliberately, "rather than an invitation to scan arbitrary directories" — and
24
+ * that rule is NOT relaxed here. These routes are a separate, instance-scoped
25
+ * surface whose own containment is that the only paths they will read are ones
26
+ * `<claudeHome>/projects/*` already records, filtered through the same path floor
27
+ * a linked project has to clear (#718–#721). `?dir=` is a lookup into that
28
+ * computed set, not a path parameter: an unrecognised value is a 400 and reads
29
+ * nothing. See `discover.ts` for the rules themselves.
30
+ *
31
+ * ## Exposure
32
+ *
33
+ * The listing reveals which directories this machine's user has run Claude Code
34
+ * in. That is new surface, though narrower than a file browser — it cannot
35
+ * enumerate anything the user has not already opened a session in. On the
36
+ * loopback + `auth: none` default it is the same posture as every other route
37
+ * here; on a shared authenticated instance it is worth knowing about.
38
+ */
39
+ import path from "node:path";
40
+ import { defaultTempRoots, discoverCandidates, discoverSessions, DiscoverPathError, } from "../discover.js";
41
+ import { ROOT_KEY } from "../project-paths.js";
42
+ /** A `?flag=` that is present and not explicitly falsy. */
43
+ function flag(value) {
44
+ return value !== undefined && value !== "0" && value !== "false";
45
+ }
46
+ export function registerDiscoverRoutes(app, ctx) {
47
+ const { herdctl, projects, cfg } = ctx;
48
+ /**
49
+ * Assemble everything the heuristic needs. Rebuilt per request rather than
50
+ * memoised: projects come and go (which is the whole point of the screen —
51
+ * importing one must remove its row from the next scan), and the expensive
52
+ * half is already cached inside `AdoptableIndex`.
53
+ */
54
+ async function context() {
55
+ const existing = [...(await projects.list()), await projects.get(ROOT_KEY)];
56
+ return {
57
+ folders: await herdctl.transcriptFolders(),
58
+ claudeHome: cfg.claudeHome,
59
+ // The user's own home, taken from the one place config already resolves it
60
+ // (`legacyClaudeHome` is `os.homedir()/.claude`, with no override) so this
61
+ // can never disagree with the home adoption reads from.
62
+ homeDir: path.dirname(cfg.legacyClaudeHome),
63
+ // Exactly what `ProjectStore.validatePath` refuses to link inside, plus
64
+ // both Claude homes — a transcript folder is not a project.
65
+ reservedRoots: [cfg.projectsRoot, cfg.dataDir, cfg.claudeHome, cfg.legacyClaudeHome],
66
+ tempRoots: defaultTempRoots(),
67
+ managedDirs: existing.map((p) => p.workingDir),
68
+ takenSlugs: existing.map((p) => p.slug),
69
+ listSessions: (engineCwd) => herdctl.listAdoptableIn(engineCwd),
70
+ };
71
+ }
72
+ app.get("/api/discover", {
73
+ schema: {
74
+ tags: ["System"],
75
+ summary: "Directories on this machine that could become projects",
76
+ description: "Scans `<claudeHome>/projects/*` for directories with existing Claude Code history and returns the ones worth importing, ranked by how many non-noise sessions each holds. Never walks the filesystem: the only directories it can name are ones a transcript already records, and each is put through the same path floor a linked project must clear (no system directories, no temp roots, nothing inside Paddock's own data dir, nothing overlapping an existing project). Two rules are relaxable: `includeNonGit=1` offers directories that are not git checkouts, `includeOutsideHome=1` offers directories outside `$HOME`. `excluded` reports how many folders each rule ate — a container whose Claude home is empty reports `scanned: 0`, which is what distinguishes 'no history' from 'all of it was filtered'. Response: `{ claudeHome, homeDir, scanned, candidates, excluded }`.",
77
+ querystring: {
78
+ type: "object",
79
+ properties: {
80
+ includeNonGit: {
81
+ type: "string",
82
+ description: "Set to `1` to also offer directories with no `.git`.",
83
+ },
84
+ includeOutsideHome: {
85
+ type: "string",
86
+ description: "Set to `1` to also offer directories outside `$HOME`.",
87
+ },
88
+ },
89
+ },
90
+ response: {
91
+ 200: {
92
+ description: "Ranked candidate directories plus the exclusion tally.",
93
+ type: "object",
94
+ additionalProperties: true,
95
+ },
96
+ },
97
+ },
98
+ }, async (req) => {
99
+ const opts = {
100
+ includeNonGit: flag(req.query.includeNonGit),
101
+ includeOutsideHome: flag(req.query.includeOutsideHome),
102
+ };
103
+ return discoverCandidates(await context(), opts);
104
+ });
105
+ app.get("/api/discover/sessions", {
106
+ schema: {
107
+ tags: ["System"],
108
+ summary: "One discovered directory's importable sessions",
109
+ description: "Lists the Claude Code sessions a directory reported by GET /api/discover would import, so a client can expand a row lazily and tick sessions individually. `dir` must be one of that scan's directories — matched on either its resolved path or the cwd its transcripts record — and anything else is a 400 rather than an invitation to read arbitrary paths. The soft `includeNonGit` / `includeOutsideHome` preferences do not apply here: a client can only have learnt a path from a listing that already applied them. Response: `{ path, sessions, filtered }`, where `filtered` carries the noise filter's reason per withheld session (`too-small`, `slash-command-only`, `sweeper-run`) — the same vocabulary as GET …/adoptable-chats, and `sessions[].sessionId` is what `POST …/adopt-chats` takes as `sessionIds`.",
110
+ querystring: {
111
+ type: "object",
112
+ properties: {
113
+ dir: { type: "string", description: "A directory from GET /api/discover." },
114
+ },
115
+ required: ["dir"],
116
+ },
117
+ response: {
118
+ 200: {
119
+ description: "Object `{ path, sessions, filtered }` for that directory.",
120
+ type: "object",
121
+ additionalProperties: true,
122
+ },
123
+ },
124
+ },
125
+ }, async (req, reply) => {
126
+ const dir = req.query.dir;
127
+ if (dir === undefined || dir.trim() === "") {
128
+ return reply.code(400).send({ error: "dir is required", code: "invalid" });
129
+ }
130
+ try {
131
+ return await discoverSessions(await context(), dir);
132
+ }
133
+ catch (err) {
134
+ if (err instanceof DiscoverPathError) {
135
+ return reply.code(400).send({ error: err.message, code: "invalid" });
136
+ }
137
+ throw err;
138
+ }
139
+ });
140
+ }
@@ -15,6 +15,7 @@
15
15
  * /api/chat-files/:id, project chat upload
16
16
  * git — /api/git[/push|/github/*], project git status/diff/commit
17
17
  * projects — projects CRUD + promote, pins, files/changelog/overview/commands
18
+ * discover — /api/discover, /api/discover/sessions (instance-level, #745)
18
19
  * triggers — project triggers CRUD + runtime + run-now
19
20
  * chats — workspace chat lifecycle (list/runs/usage/create/messages/
20
21
  * subagents/context/delete/rename/fork/archive/star/seen/promote)
@@ -5,6 +5,7 @@ import { registerProjectRoutes, registerProjectWorkspaceRoutes } from "./routes/
5
5
  import { registerTriggerWorkspaceRoutes } from "./routes/triggers.js";
6
6
  import { registerChatWorkspaceRoutes } from "./routes/chats.js";
7
7
  import { registerMcpRoutes } from "./routes/mcp.js";
8
+ import { registerDiscoverRoutes } from "./routes/discover.js";
8
9
  import { mountWorkspaceRoutes } from "./routes/workspace-mount.js";
9
10
  export { parseRangeHeader } from "./http-bytes.js";
10
11
  export async function registerRoutes(app, deps) {
@@ -12,6 +13,7 @@ export async function registerRoutes(app, deps) {
12
13
  registerMetaRoutes(app, ctx);
13
14
  registerGitRoutes(app, ctx);
14
15
  registerProjectRoutes(app, ctx);
16
+ registerDiscoverRoutes(app, ctx);
15
17
  // The workspace-scoped half of every group, mounted twice: `/api/root` (the
16
18
  // root workspace, key `""`) and `/api/projects/:slug` (a project). Same
17
19
  // handlers both times — that identity is the point, not an optimisation.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.67.0",
3
+ "version": "0.68.1",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -0,0 +1,2 @@
1
+ import{j as r,l as t,r as d}from"./index-DjhUG9sA.js";const u=["w-full rounded-lg border border-edge-strong bg-surface-sunken px-3 py-2 text-sm text-fg","outline-none motion-fast transition-[border-color,box-shadow]","focus:border-accent focus:ring-2 focus:ring-accent/25","disabled:cursor-not-allowed disabled:opacity-60","aria-[invalid=true]:border-danger aria-[invalid=true]:focus:ring-danger/25"].join(" ");function p({className:e,...s}){return r.jsx("input",{className:t(u,e),...s})}function m({className:e,...s}){return r.jsx("textarea",{className:t(u,"resize-y",e),...s})}function h({className:e,...s}){return r.jsx("select",{className:t(u,e),...s})}function j({checked:e,onChange:s,label:a,disabled:n,id:o,className:c}){const i=d.useId(),l=o??i;return r.jsxs("label",{htmlFor:l,className:t("relative inline-block shrink-0 cursor-pointer",c),children:[r.jsx("input",{id:l,type:"checkbox","aria-label":a,className:"peer sr-only",checked:e,disabled:n,onChange:x=>s(x.target.checked)}),r.jsx("span",{className:t("block h-[18px] w-8 rounded-full bg-edge-strong","motion-fast transition-colors","after:absolute after:left-0.5 after:top-0.5 after:h-3.5 after:w-3.5 after:rounded-full","after:bg-surface-raised after:shadow-2xs after:transition-transform after:content-['']","peer-checked:bg-accent-solid peer-checked:after:translate-x-3.5","peer-focus-visible:ring-2 peer-focus-visible:ring-accent/50","peer-disabled:opacity-50")})]})}function w({label:e,description:s,indeterminate:a=!1,className:n,...o}){const c=d.useRef(null);d.useEffect(()=>{c.current&&(c.current.indeterminate=a)},[a]);const i=r.jsx("input",{ref:c,type:"checkbox",...a?{"aria-checked":"mixed"}:{},className:t("h-4 w-4 shrink-0 cursor-pointer rounded border-edge-strong accent-[var(--accent-solid)]","disabled:cursor-not-allowed disabled:opacity-50",e?"mt-0.5":"",n),...o});return e?r.jsxs("label",{className:"flex cursor-pointer items-start gap-2 text-sm text-fg",children:[i,r.jsxs("span",{className:"min-w-0",children:[e,s&&r.jsx("span",{className:"block text-xs leading-snug text-fg-muted",children:s})]})]}):i}const f={neutral:"bg-surface-active text-fg-muted",accent:"bg-accent-soft text-accent",success:"bg-success-soft text-success",warn:"bg-warn-soft text-warn",danger:"bg-danger-soft text-danger",info:"bg-info-soft text-info",lineage:"bg-lineage-soft text-lineage"},g={neutral:"bg-fg-subtle",accent:"bg-accent-solid",success:"bg-success-solid",warn:"bg-warn-solid",danger:"bg-danger-solid",info:"bg-info-solid",lineage:"bg-lineage-solid"};function N({tone:e="neutral",shape:s="tag",size:a="md",dot:n=!1,icon:o,title:c,className:i,children:l}){return r.jsxs("span",{title:c,className:t("inline-flex items-center gap-1 font-medium leading-none",s==="pill"?"rounded-full px-2 py-0.5":"rounded-md px-1.5 py-0.5",a==="sm"?"text-3xs":"text-2xs",f[e],i),children:[n&&r.jsx("span",{className:t("h-1.5 w-1.5 shrink-0 rounded-full",g[e])}),o,l]})}function k({tone:e="neutral",pulse:s=!1,className:a}){return r.jsx("span",{className:t("inline-block h-2 w-2 shrink-0 rounded-full",g[e],s&&"animate-pulse",a)})}function y({tone:e="info",icon:s,className:a,children:n}){const o={neutral:"border-edge",accent:"border-accent-edge",success:"border-success-edge",warn:"border-warn-edge",danger:"border-danger-edge",info:"border-info-edge",lineage:"border-lineage-edge"};return r.jsxs("div",{role:e==="danger"?"alert":void 0,className:t("flex items-start gap-2 rounded-lg border px-3 py-2 text-sm",f[e],o[e],a),children:[s&&r.jsx("span",{className:"mt-0.5 shrink-0",children:s}),r.jsx("div",{className:"min-w-0",children:n})]})}export{N as C,p as I,k as S,j as T,y as a,h as b,m as c,w as d};
2
+
@@ -0,0 +1,2 @@
1
+ import{j as r}from"./index-DjhUG9sA.js";import{D as o}from"./DiscoverView-Bt4FUTjB.js";import"./Surfaces-c410wTbw.js";import"./Chip-NMeHUE94.js";function s(){return r.jsx(o,{})}export{s as DiscoverPage};
2
+
@@ -0,0 +1,2 @@
1
+ import{a as ne,u as re,H as ae,r as l,b as P,j as s,M as oe,E as ie,D as Q,J as U,K as ce,l as F,O as de,h as le}from"./index-DjhUG9sA.js";import{E as O,B as R,C as he}from"./Surfaces-c410wTbw.js";import{a as W,d as M,C as T,T as K}from"./Chip-NMeHUE94.js";function ue(e){const t={};for(const r of e)t[r.path]={checked:!0,expanded:!1,loading:!1,error:null};return t}function pe(e){if(!e.sessions||!e.selected)return e.checked?"on":"off";const t=e.sessions.sessions.length,r=e.selected.size;return t===0||r===0?"off":r===t?"on":"mixed"}function C(e,t){return e.sessions&&e.selected?e.selected.size:e.checked?t.sessionCount:0}function me(e,t){return t.reduce((r,a)=>r+C(e[a.path]??z(),a),0)}function xe(e,t){return t.filter(r=>C(e[r.path]??z(),r)>0)}function fe(e,t,r){const a=e[t];if(!a)return e;const n={...a,checked:r};return a.sessions&&(n.selected=r?new Set(a.sessions.sessions.map(d=>d.sessionId)):new Set),{...e,[t]:n}}function ge(e,t,r){const a=e[t];if(!a||!a.sessions)return e;const n=new Set(a.selected??[]);return n.delete(r)||n.add(r),{...e,[t]:{...a,selected:n,checked:n.size>0}}}function je(e,t,r){const a=e[t];if(!a)return e;const n=a.checked?new Set(r.sessions.map(d=>d.sessionId)):new Set;return{...e,[t]:{...a,sessions:r,selected:n,loading:!1,error:null}}}function D(e,t,r){const a=e[t];return a?{...e,[t]:{...a,...r}}:e}function be(e,t){const r=e??z();return C(r,t)===0?null:r.sessions&&r.selected?{sessionIds:[...r.selected]}:{}}function z(){return{checked:!1,expanded:!1,loading:!1,error:null}}function ye(e){return e.error!==void 0?e.stage==="create"?"danger":"warn":e.adopted>0?"success":"warn"}async function we(e,t,r){let a;try{a=await e.createProject({name:t.name,slug:t.suggestedSlug,path:t.path,managed:!1})}catch(n){return{stage:"create",adopted:0,skipped:[],error:V(n)}}try{const n=await e.adoptChats(a.slug,{sourceCwd:a.workingDir,...r.sessionIds?{sessionIds:r.sessionIds}:{}});return{project:a,adopted:n.adopted.length,skipped:n.skipped}}catch(n){return{stage:"import",project:a,adopted:0,skipped:[],error:V(n)}}}async function Ne(e,t,r){const a=[];for(const n of t){r(n.candidate.path,"running");const d=await we(e,n.candidate,n.plan);a.push(d),r(n.candidate.path,"done",d)}return a}const ve={sidechain:"sub-agent transcript","already-adopted":"already imported","destination-exists":"a chat with that id already exists here","attributed-to-run":"belongs to a Paddock run",unreadable:"transcript unreadable","placement-failed":"copy failed","record-failed":"could not be recorded"};function J(e){if(e.length===0)return"";const t=[...new Set(e.map(r=>ve[r.reason]??r.reason))];return`${e.length} skipped (${t.join(", ")})`}function ke(e,t){var h;if(e.stage==="create")return`Couldn't create the project: ${e.error}`;const r=((h=e.project)==null?void 0:h.slug)??t.suggestedSlug;if(e.stage==="import")return`Project “${r}” was created, but importing its chats failed: ${e.error}. It is there and empty — you can import them from the project itself.`;if(e.adopted===0){const m=J(e.skipped);return t.recordedPath!==void 0?`Project “${r}” was created but no chats came with it. Its transcripts record ${t.recordedPath}, a different spelling of ${t.path}, and an import matches on the exact path.`:m?`Project “${r}” was created but no chats came with it — ${m}.`:`Project “${r}” was created but no chats came with it.`}const a=e.adopted,n=J(e.skipped),d=`Imported ${a} chat${a===1?"":"s"} into “${r}”`;return n?`${d} — ${n}.`:`${d}.`}function V(e){return e instanceof Error?e.message:String(e)}function ze({firstRun:e=!1,onLeave:t}){const r=ne(),{refresh:a}=re(),n=ae(),[d,h]=l.useState(!1),[m,c]=l.useState(!1),[x,$]=l.useState(null),[g,u]=l.useState(null),[v,I]=l.useState(!0),[j,w]=l.useState({}),[k,A]=l.useState(!1),[X,H]=l.useState({}),[E,B]=l.useState({}),[S,Y]=l.useState(!1);l.useEffect(()=>{let o=!0;return I(!0),u(null),P.discover({includeNonGit:d,includeOutsideHome:m}).then(i=>{o&&($(i),w(ue(i.candidates)),I(!1))}).catch(i=>{o&&(u(i instanceof Error?i.message:"Discovery failed"),I(!1))}),()=>{o=!1}},[d,m]);const y=(x==null?void 0:x.candidates)??[],G=l.useMemo(()=>me(j,y),[j,y]),b=l.useMemo(()=>xe(j,y),[j,y]),Z=l.useCallback(o=>{w(p=>{const f=p[o.path];if(!f)return p;const N=D(p,o.path,{expanded:!f.expanded});return f.expanded||f.sessions||f.loading?N:D(N,o.path,{loading:!0,error:null})});const i=j[o.path];!i||i.expanded||i.sessions||i.loading||P.discoverSessions(o.path).then(p=>w(f=>je(f,o.path,p))).catch(p=>w(f=>D(f,o.path,{loading:!1,error:p instanceof Error?p.message:"Could not read this directory's sessions"})))},[j]),L=l.useCallback(async()=>{if(k)return;A(!0),H(Object.fromEntries(b.map(i=>[i.path,"pending"]))),B({});const o=b.map(i=>({candidate:i,plan:be(j[i.path],i)??{}}));await Ne(P,o,(i,p,f)=>{H(N=>({...N,[i]:p})),f&&B(N=>({...N,[i]:f}))}),A(!1),Y(!0),await a()},[b,j,k,a]),ee=l.useCallback(async()=>{await a(),t==null||t(),r("/")},[r,a,t]),se=Object.values(E).reduce((o,i)=>o+i.adopted,0),te=Object.values(E).filter(o=>o.project).length;return s.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[s.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-edge px-3 pb-2.5 sm:px-6 lg:py-4",children:[s.jsx("button",{type:"button",onClick:()=>n==null?void 0:n.openNav(),"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:s.jsx(oe,{width:20,height:20})}),s.jsx(ie,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),s.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Discover"})]}),s.jsx("div",{className:"flex-1 overflow-y-auto overscroll-contain",children:s.jsxs("div",{className:"mx-auto max-w-3xl px-6 py-6",children:[s.jsx("p",{className:"mb-5 text-sm text-fg-muted",children:e?"Nothing here yet — but you have probably already been using Claude Code in a terminal. These directories have history Paddock can bring in as projects.":"Directories on this machine with existing Claude Code history. Importing one links it as a project and copies its conversations in; your own history is never moved or deleted."}),S&&s.jsx(Ie,{imported:se,projects:te,onGetStarted:ee}),g!==null&&s.jsx(W,{tone:"danger",icon:s.jsx(Q,{width:16,height:16}),children:g}),v&&s.jsx(Ee,{}),!v&&g===null&&x!==null&&s.jsxs(s.Fragment,{children:[x.scanned===0?s.jsx($e,{claudeHome:x.claudeHome}):y.length===0?s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:"Nothing left to import",body:"Every directory with Claude Code history on this machine is already a project, or was filtered out."}):s.jsx("ul",{className:"space-y-2",children:y.map(o=>s.jsx(Se,{candidate:o,row:j[o.path],status:X[o.path],outcome:E[o.path],locked:k||S,onToggleRow:i=>w(p=>fe(p,o.path,i)),onToggleSession:i=>w(p=>ge(p,o.path,i)),onToggleExpanded:()=>Z(o)},o.path))}),!S&&s.jsx(Ce,{result:x,includeNonGit:d,includeOutsideHome:m,disabled:k,onIncludeNonGit:h,onIncludeOutsideHome:c}),y.length>0&&!S&&s.jsxs("div",{className:"mt-5 flex items-center justify-between gap-3 border-t border-edge pt-4",children:[s.jsxs("span",{className:"text-sm text-fg-muted",children:[G," conversation",G===1?"":"s"," from ",b.length," director",b.length===1?"y":"ies"]}),s.jsxs(R,{variant:"primary",onClick:()=>void L(),disabled:b.length===0,loading:k,loadingLabel:"Importing…",children:["Import ",b.length," project",b.length===1?"":"s"]})]})]})]})})]})}function Se({candidate:e,row:t,status:r,outcome:a,locked:n,onToggleRow:d,onToggleSession:h,onToggleExpanded:m}){const c=t??{checked:!1,expanded:!1,loading:!1,error:null},x=pe(c),$=C(c,e),g=a?ye(a):null;return s.jsx("li",{children:s.jsxs(he,{flush:!0,className:F("overflow-hidden",g==="success"&&"border-success-edge bg-success-soft",g==="warn"&&"border-warn-edge bg-warn-soft",g==="danger"&&"border-danger-edge bg-danger-soft",r==="running"&&"border-accent-edge"),"data-testid":`discover-row-${e.suggestedSlug}`,"data-tone":g??void 0,children:[s.jsxs("div",{className:"flex items-start gap-3 px-3 py-2.5",children:[s.jsx(M,{className:"mt-1",checked:x==="on",indeterminate:x==="mixed",disabled:n,"aria-label":`Import ${e.path}`,onChange:u=>d(u.target.checked)}),s.jsxs("div",{className:"min-w-0 flex-1",children:[s.jsx("code",{className:"block break-all text-sm text-fg",children:e.path}),s.jsxs("div",{className:"mt-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-xs text-fg-muted",children:[s.jsxs("span",{className:"tabular",children:[e.sessionCount," conversation",e.sessionCount===1?"":"s"]}),e.filteredCount>0&&s.jsxs("span",{className:"tabular",title:"Empty, slash-command-only or sweeper transcripts, withheld as noise",children:["· ",e.filteredCount," filtered out"]}),e.lastSessionAt!==void 0&&s.jsxs("span",{className:"tabular",children:["· last ",q(e.lastSessionAt)]}),e.gitRemote!==void 0&&s.jsx(T,{tone:"lineage",size:"sm",children:e.gitRemote}),!e.hasGit&&s.jsx(T,{tone:"neutral",size:"sm",children:"no git"}),!e.insideHome&&s.jsx(T,{tone:"neutral",size:"sm",children:"outside home"})]}),e.recordedPath!==void 0&&s.jsx(W,{tone:"warn",className:"mt-2",icon:s.jsx(Q,{width:14,height:14}),children:s.jsxs("span",{className:"text-xs",children:["These transcripts record"," ",s.jsx("code",{className:"break-all",children:e.recordedPath})," — a different spelling of this path. The import matches on the exact path, so it may bring nothing in."]})}),a&&s.jsx("p",{className:F("mt-2 text-xs",g==="success"&&"text-success",g==="warn"&&"text-warn",g==="danger"&&"text-danger"),children:ke(a,e)}),r==="running"&&!a&&s.jsx("p",{className:"mt-2 text-xs text-fg-muted",children:"Importing…"})]}),s.jsxs("span",{className:"flex shrink-0 items-center gap-1",children:[s.jsx("span",{className:"tabular text-xs text-fg-subtle",children:$}),s.jsx(R,{size:"icon-sm",variant:"subtle","aria-label":c.expanded?"Hide conversations":"Show conversations","aria-expanded":c.expanded,onClick:m,children:c.expanded?s.jsx(de,{width:14,height:14}):s.jsx(le,{width:14,height:14})})]})]}),c.expanded&&s.jsxs("div",{className:"border-t border-edge px-3 py-2",children:[c.loading&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Reading transcripts…"}),c.error!==null&&s.jsx("p",{className:"py-2 text-xs text-danger",children:c.error}),c.sessions&&c.sessions.sessions.length===0&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Nothing importable here any more — it may have been imported since the scan."}),c.sessions&&c.sessions.sessions.length>0&&s.jsx("ul",{className:"space-y-1",children:c.sessions.sessions.map(u=>{var v;return s.jsx("li",{children:s.jsxs("label",{className:"flex cursor-pointer items-start gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-surface-hover",children:[s.jsx(M,{checked:((v=c.selected)==null?void 0:v.has(u.sessionId))??!1,disabled:n,"aria-label":_(u),onChange:()=>h(u.sessionId)}),s.jsxs("span",{className:"min-w-0 flex-1",children:[s.jsx("span",{className:"block truncate text-xs text-fg",children:_(u)}),s.jsxs("span",{className:"tabular mt-0.5 block text-3xs text-fg-muted",children:[q(u.mtime)," · ",Te(u.sizeBytes)]})]})]})},u.sessionId)})}),c.sessions&&c.sessions.filtered.length>0&&s.jsxs("p",{className:"mt-2 px-2 text-3xs text-fg-subtle",children:[c.sessions.filtered.length," more withheld as noise (",[...new Set(c.sessions.filtered.map(u=>u.reason))].join(", "),")."]})]})]})})}function Ce({result:e,includeNonGit:t,includeOutsideHome:r,disabled:a,onIncludeNonGit:n,onIncludeOutsideHome:d}){const h=e.excluded["no-git"]??0,m=e.excluded["outside-home"]??0,c=h>0||t,x=m>0||r;return e.scanned===0?null:s.jsxs("div",{className:"mt-5 space-y-2 border-t border-edge pt-4",children:[s.jsx("p",{className:"text-xs text-fg-muted",children:Pe(e)}),c&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:t,disabled:a,onChange:n,label:"Also offer directories without a git repository"}),s.jsxs("span",{children:["Also offer directories without a git repository",h>0&&` (${h} hidden)`]})]}),x&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(K,{checked:r,disabled:a,onChange:d,label:"Also offer directories outside your home directory"}),s.jsxs("span",{children:["Also offer directories outside ",s.jsx("code",{children:e.homeDir}),m>0&&` (${m} hidden)`]})]})]})}function $e({claudeHome:e}){return s.jsx(O,{variant:"panel",icon:s.jsx(ce,{width:22,height:22}),title:"No Claude Code history on this machine",body:s.jsxs(s.Fragment,{children:["Nothing has been recorded under ",s.jsx("code",{className:"break-all",children:e}),". That is normal for a container, whose Claude home is its own rather than yours — bind-mount your history there and re-open this page, or import it headlessly with"," ",s.jsx("code",{children:"npm run import-chats"}),"."]})})}function Ie({imported:e,projects:t,onGetStarted:r}){return s.jsx("div",{className:"mb-6",children:s.jsx(O,{variant:"panel",icon:s.jsx(U,{width:22,height:22}),title:t===0?"Nothing was imported":`${t} project${t===1?"":"s"}, ${e} conversation${e===1?"":"s"}`,body:t===0?"Every row failed. Each one says why below.":"They are in the sidebar now. Anything that did not land says why below.",action:s.jsx(R,{variant:"primary",onClick:()=>void r(),children:"Get started"})})})}function Ee(){return s.jsx("div",{className:"space-y-2","aria-label":"Scanning",role:"status",children:[0,1,2].map(e=>s.jsx("div",{className:"h-16 animate-pulse rounded-2xl bg-surface-active"},e))})}function Pe(e){const t=[],r=[["temp-root",n=>`${n} in temp directories`],["no-git",n=>`${n} without a git repository`],["outside-home",n=>`${n} outside your home directory`],["already-managed",n=>`${n} already a project`],["no-sessions",n=>`${n} with nothing importable`],["no-recorded-cwd",n=>`${n} with no recorded directory`],["missing",n=>`${n} no longer on disk`],["home-root",n=>`${n} your home directory itself`],["system-path",n=>`${n} system directories`],["paddock-internal",n=>`${n} inside Paddock's own data`]];for(const[n,d]of r){const h=e.excluded[n];h!==void 0&&h>0&&t.push(d(h))}const a=`${e.scanned} transcript folder${e.scanned===1?"":"s"} scanned`;return t.length===0?`${a}.`:`${a}; skipped ${t.join(", ")}.`}function _(e){var a,n;const t=(a=e.autoName)==null?void 0:a.trim();if(t!==void 0&&t!=="")return t;const r=(n=e.preview)==null?void 0:n.trim();return r!==void 0&&r!==""?r:e.sessionId}function q(e){const t=new Date(e);return Number.isNaN(t.getTime())?"unknown date":t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Te(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} kB`:`${(e/(1024*1024)).toFixed(1)} MB`}export{ze as D};
2
+
@@ -0,0 +1,2 @@
1
+ import{r as c,n as W,s as Z,A as ee,p as pe,j as e,q as ae,l as g,t as ie,v as be,w as je,x as le,y as ve,z as ye,b as L,D as oe,E as we,X as Ne,F as ke,G as Ce,H as Se,M as Ae,I as Ee}from"./index-DjhUG9sA.js";import{B as R,S as te}from"./Surfaces-c410wTbw.js";import{C as ce,S as O,I as B,a as Re,T as Me,b as $e,c as Pe}from"./Chip-NMeHUE94.js";const Ie=Array.from({length:72},(t,n)=>n*5),S=[...ie].sort((t,n)=>t.hue-n.hue),_=(t,n)=>Math.min(Math.abs(t-n),360-Math.abs(t-n));function ne(t){const n=S.reduce((r,a)=>_(a.hue,t)<_(r.hue,t)?a:r);if(_(n.hue,t)<=8)return n.name;const s=S.find(r=>r.hue>t)??S[0];return`between ${S[(S.indexOf(s)-1+S.length)%S.length].name} and ${s.name}`}function Fe({theme:t,dark:n,hue:s,className:o}){const r=c.useRef(null);return c.useLayoutEffect(()=>{const a=r.current;a&&(s===null?je(a):le(s,a))},[t,n,s]),e.jsx("div",{ref:r,"data-theme":t,className:g("overflow-hidden rounded-lg border border-edge",n&&"dark",o),children:e.jsxs("div",{className:"flex h-full items-center gap-2 bg-surface p-2",children:[e.jsxs("div",{className:"flex-1 rounded-md border border-edge-subtle bg-surface-raised p-1.5",children:[e.jsx("div",{className:"h-1 w-4/5 rounded-full bg-fg"}),e.jsx("div",{className:"mt-1 h-1 w-3/5 rounded-full bg-fg-subtle"})]}),e.jsx("div",{className:"grid h-6 w-6 shrink-0 place-items-center rounded-md bg-accent-solid",children:e.jsx("div",{className:"h-1 w-3 rounded-full bg-accent-fg"})})]})})}function Te({hexes:t,hue:n,active:s,onCommit:o}){const r=c.useRef(null),a=c.useRef(null),m=c.useRef(null),x=c.useRef(0),h=c.useRef(null),p=c.useRef(!1),j=c.useRef(null),u=c.useMemo(()=>{if(t.length===0)return;const d=t.map((b,y)=>`${b} ${(y/t.length*100).toFixed(2)}%`);return d.push(`${t[0]} 100%`),`linear-gradient(to right, ${d.join(", ")})`},[t]),f=c.useCallback(d=>{a.current&&(a.current.style.left=`${d/360*100}%`),m.current&&(m.current.textContent=ne(d))},[]);c.useEffect(()=>{p.current||f(n)},[n,f]);const N=d=>{var M;const b=(M=r.current)==null?void 0:M.getBoundingClientRect();if(!b||b.width===0)return 0;const y=Math.min(1,Math.max(0,(d-b.left)/b.width));return Math.round(y*360)%360},w=c.useCallback(d=>{h.current=d,!x.current&&(x.current=requestAnimationFrame(()=>{x.current=0;const b=h.current;h.current=null,b!==null&&(f(b),le(b,document.documentElement,{context:j.current??void 0,live:!0}))}))},[f]);c.useEffect(()=>()=>cancelAnimationFrame(x.current),[]);const F=d=>{p.current=!1,j.current=null,cancelAnimationFrame(x.current),x.current=0,h.current=null,o(d)};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mt-2 flex items-baseline justify-between gap-2",children:[e.jsx("span",{className:"text-3xs text-fg-subtle",children:"Drag along the strip"}),e.jsx("span",{ref:m,className:"text-3xs font-medium text-fg-muted","aria-hidden":!0})]}),e.jsx("div",{ref:r,role:"slider",tabIndex:0,"aria-label":"Accent colour","aria-valuemin":0,"aria-valuemax":359,"aria-valuenow":n,"aria-valuetext":s?ne(n):"the theme’s own colour",onPointerDown:d=>{d.currentTarget.setPointerCapture(d.pointerId),p.current=!0,j.current=ve(),w(N(d.clientX))},onPointerMove:d=>{p.current&&w(N(d.clientX))},onPointerUp:d=>{p.current&&F(N(d.clientX))},onPointerCancel:()=>{p.current&&F(n)},onKeyDown:d=>{const b=d.key==="PageUp"||d.key==="PageDown"?15:d.shiftKey?10:2;let y=null;d.key==="ArrowLeft"||d.key==="ArrowDown"||d.key==="PageDown"?y=(n-b+360)%360:d.key==="ArrowRight"||d.key==="ArrowUp"||d.key==="PageUp"?y=(n+b)%360:d.key==="Home"?y=0:d.key==="End"&&(y=359),y!==null&&(d.preventDefault(),o(y))},style:{backgroundImage:u,touchAction:"none"},className:g("relative mt-1 h-8 cursor-pointer rounded-lg border border-edge shadow-xs select-none","focus-visible:focus-ring"),children:e.jsx("div",{ref:a,"aria-hidden":!0,style:{left:`${n/360*100}%`},className:g("pointer-events-none absolute top-1/2 h-[calc(100%+6px)] w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full","border border-fg bg-surface-raised shadow-sm",!s&&"opacity-0")})})]})}function Le({className:t}){const[n,s]=c.useState(W),[o,r]=c.useState(null),a=typeof document<"u"&&document.documentElement.classList.contains("dark"),m=c.useRef(n),x=c.useCallback(u=>{const f={...m.current,...u};m.current=f,s(f),r(Z(f))},[]);c.useEffect(()=>{const u=f=>r(f.detail);return window.addEventListener(ee,u),r(Z(W())),()=>window.removeEventListener(ee,u)},[]);const h=c.useMemo(()=>pe(Ie),[n.theme,a,o==null?void 0:o.mode]),p=(o==null?void 0:o.hue)??0,j=n.hue!==null;return e.jsxs("div",{className:g("space-y-5",t),children:[e.jsxs("fieldset",{children:[e.jsx("legend",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Theme"}),e.jsx("div",{className:g("mt-2 grid gap-2","grid-cols-2 sm:grid-cols-4"),children:ae.map(u=>{const f=n.theme===u.id;return e.jsxs("button",{type:"button","aria-pressed":f,title:`${u.label} — ${u.blurb}`,onClick:()=>x({theme:u.id}),className:g("motion-fast rounded-xl border text-left transition-[border-color,box-shadow]","focus-visible:focus-ring","p-1.5",f?"border-accent-edge bg-accent-soft shadow-xs":"border-edge bg-surface-raised hover:border-edge-strong"),children:[e.jsx(Fe,{theme:u.id,dark:a,hue:n.hue,className:"h-10"}),e.jsxs("div",{className:"mt-1.5 min-w-0 px-0.5 pb-0.5",children:[e.jsx("span",{className:"block truncate text-xs font-semibold text-fg",children:u.label}),e.jsx("span",{className:"mt-0.5 line-clamp-2 text-3xs leading-snug text-fg-muted",children:u.blurb})]})]},u.id)})})]}),e.jsxs("div",{className:g("grid gap-5 lg:grid-cols-[minmax(0,1fr)_15rem] lg:items-start"),children:[e.jsxs("fieldset",{children:[e.jsx("legend",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Accent colour"}),e.jsx("p",{className:"mt-1 text-3xs leading-snug text-fg-subtle",children:"Pick any colour. The theme adjusts it so buttons and labels stay readable in both light and dark."}),e.jsx(Te,{hexes:h,hue:p,active:j,onCommit:u=>x({hue:u})}),e.jsxs("div",{className:"mt-2 flex flex-wrap gap-1",children:[e.jsx("button",{type:"button",onClick:()=>x({hue:null}),"aria-pressed":!j,className:g("motion-fast rounded-md border px-2 py-1 text-3xs transition-colors","focus-visible:focus-ring",j?"border-edge text-fg-muted hover:border-edge-strong hover:text-fg":"border-accent-edge bg-accent-soft text-accent"),children:"Theme’s own"}),ie.map(u=>{const f=j&&Math.abs(p-u.hue)<2.5;return e.jsxs("button",{type:"button","aria-pressed":f,onClick:()=>x({hue:u.hue}),className:g("motion-fast inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-3xs transition-colors","focus-visible:focus-ring",f?"border-edge-strong bg-surface-active text-fg":"border-edge text-fg-muted hover:border-edge-strong hover:text-fg"),children:[e.jsx("span",{"aria-hidden":!0,className:"h-2.5 w-2.5 rounded-full",style:{backgroundColor:h[Math.round(u.hue/5)%h.length]}}),u.name]},u.name)})]})]}),e.jsxs("fieldset",{children:[e.jsx("legend",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Tint the background"}),e.jsx("p",{className:"mt-1 text-3xs leading-snug text-fg-subtle",children:"Colours the whole page a little, not just the buttons. Useful for telling two Paddock instances apart at a glance."}),e.jsx("div",{className:"mt-2 inline-flex rounded-lg border border-edge p-0.5",children:be.map(u=>e.jsx("button",{type:"button","aria-pressed":n.tint===u.level,onClick:()=>x({tint:u.level}),className:g("motion-fast rounded-md px-2.5 py-1 text-3xs transition-colors","focus-visible:focus-ring",n.tint===u.level?"bg-surface-active font-medium text-fg":"text-fg-muted hover:text-fg"),children:u.label},u.level))})]})]}),e.jsx(Oe,{})]})}function Oe(){return e.jsxs("div",{children:[e.jsx("p",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Preview"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2 rounded-xl border border-edge bg-surface p-3",children:[e.jsx(R,{variant:"primary",size:"sm",children:"Send"}),e.jsx(R,{variant:"ghost",size:"sm",children:"Cancel"}),e.jsx(R,{variant:"link",size:"sm",children:"A link"}),e.jsx("span",{className:"inline-flex items-center gap-1 rounded-md border border-accent-edge bg-accent-soft px-1.5 py-0.5 text-2xs font-medium text-accent",children:"accent chip"}),e.jsx(ce,{tone:"accent",shape:"pill",dot:!0,children:"running"}),e.jsxs("span",{className:"inline-flex items-center gap-1.5 text-2xs text-fg-subtle",children:[e.jsx("span",{className:"h-4 w-4 rounded-md border border-edge bg-accent-solid-hover","aria-hidden":!0}),"hovered"]}),e.jsxs("span",{className:"ml-auto inline-flex items-center gap-1.5",children:[e.jsx(O,{tone:"success"}),e.jsx(O,{tone:"warn"}),e.jsx(O,{tone:"danger"}),e.jsx(O,{tone:"info"}),e.jsx("span",{className:"text-2xs text-fg-subtle",children:"status hues (theme’s, not yours)"})]})]})]})}const z="mx-auto w-full max-w-4xl",De="sm:w-64";function Ve(){const[t,n]=c.useState(null),[s,o]=c.useState(!0),[r,a]=c.useState(null),[m,x]=c.useState({}),[h,p]=c.useState(!1),[j,u]=c.useState(null),[f,N]=c.useState(!1),[w,F]=c.useState(""),[d,b]=c.useState(!1),[y,M]=c.useState(null),K=c.useRef(null),de=ye("(min-width: 1024px)");c.useEffect(()=>{let i=!0;return o(!0),L.getInstanceConfig().then(l=>{i&&(n(l),a(null))}).catch(l=>i&&a(l instanceof Error?l.message:String(l))).finally(()=>i&&o(!1)),()=>{i=!1}},[]);const ue=(i,l)=>{N(!1),u(null),x(v=>({...v,[i]:l}))},me=i=>se(i,m),H=c.useMemo(()=>t?t.groups.flatMap(i=>i.fields):[],[t]),k=c.useMemo(()=>H.filter(i=>i.editable&&!i.envOverridden).filter(i=>Object.prototype.hasOwnProperty.call(m,i.key)).filter(i=>!re(m[i.key],q(i))).map(i=>i.key),[H,m]),X=c.useMemo(()=>new Set(k),[k]),C=c.useMemo(()=>{if(!t)return[];const i=w.trim().toLowerCase(),l=[];for(const v of t.groups){const P=i!==""&&v.label.toLowerCase().includes(i),I=v.fields.filter(A=>{const T=se(A,m);return d&&re(T,A.default)?!1:i===""||P?!0:Ye(A).includes(i)});I.length>0&&l.push({...v,fields:I})}return l},[t,w,d,m]),J=C.reduce((i,l)=>i+l.fields.length,0),xe=w.trim()!==""||d,U=!d&&(w.trim()===""||We.includes(w.trim().toLowerCase())),$=c.useMemo(()=>U?[V,...C]:C,[U,C]),he=C.some(i=>i.fields.some(l=>l.envOverridden)),Y=Qe(C,X);c.useEffect(()=>{const i=K.current;if(!i||$.length===0)return;const l=$.map(P=>P.id),v=()=>{const P=i.getBoundingClientRect().top;let I=l[0];for(const A of l){const T=document.getElementById(D(A));if(T)if(T.getBoundingClientRect().top-P<=80)I=A;else break}M(I)};return v(),i.addEventListener("scroll",v,{passive:!0}),()=>i.removeEventListener("scroll",v)},[$]);const Q=i=>{var v;const l=document.getElementById(D(i));(v=l==null?void 0:l.scrollIntoView)==null||v.call(l,{behavior:"smooth",block:"start"}),M(i)},fe=async()=>{if(k.length===0||!t)return;const i={};for(const l of k)i[l]=m[l];p(!0),u(null);try{await L.updateInstanceConfig(i,t.configVersion);const l=await L.getInstanceConfig();n(l),x({}),N(!0)}catch(l){if(l instanceof Ce&&l.status===409)try{n(await L.getInstanceConfig())}catch{}u(l instanceof Error?l.message:String(l))}finally{p(!1)}},ge=()=>{x({}),u(null),N(!1)};return e.jsxs("div",{className:"flex min-h-0 flex-1",children:[t&&e.jsx(Be,{groups:$,active:y,onJump:Q,dirtyCount:Y}),e.jsxs("div",{className:"flex min-h-0 min-w-0 flex-1 flex-col",children:[t&&e.jsx("div",{className:"border-b border-edge px-4 py-2.5 sm:px-6",children:e.jsxs("div",{className:z,children:[e.jsx(Ue,{query:w,onQuery:F,modifiedOnly:d,onModifiedOnly:b,visibleCount:J,totalCount:H.length,filtering:xe,autoFocus:de}),e.jsx(He,{groups:$,active:y,onJump:Q,dirtyCount:Y})]})}),e.jsx("div",{ref:K,className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:z,children:[U&&e.jsx("div",{className:"mb-8 border-b border-edge pb-7",children:e.jsx(te,{id:D(V.id),title:V.label,description:V.description,variant:"rule",first:!0,children:e.jsx(Le,{})})}),e.jsx(_e,{saved:f,configPath:t==null?void 0:t.configPath,restartRequired:(t==null?void 0:t.restartRequired)??!1,fileError:t==null?void 0:t.configFileError}),s&&e.jsx("p",{className:"text-sm text-fg-muted",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-danger",role:"alert",children:["Failed to load settings: ",r]}),t&&he&&e.jsx(ze,{}),t&&J===0&&e.jsxs("p",{className:"py-8 text-center text-sm text-fg-muted",children:["No settings match"," ",w.trim()?e.jsxs(e.Fragment,{children:["“",e.jsx("span",{className:"font-medium",children:w.trim()}),"”"]}):"this filter","."]}),C.map(i=>e.jsx(te,{id:D(i.id),title:i.label,description:i.description,variant:"card",flush:!0,children:e.jsx("div",{className:"divide-y divide-edge-subtle",children:i.fields.map(l=>e.jsx(Ge,{field:l,value:me(l),dirty:X.has(l.key),onChange:v=>ue(l.key,v)},l.key))})},i.id))]})}),t&&e.jsx("footer",{className:"border-t border-edge px-4 py-3 sm:px-6",children:e.jsxs("div",{className:`${z} flex items-center gap-3`,children:[j&&e.jsxs("span",{className:"flex min-w-0 items-center gap-1.5 text-sm text-danger",role:"alert",children:[e.jsx(oe,{width:14,height:14,className:"shrink-0"}),e.jsx("span",{className:"truncate",children:j})]}),e.jsx("span",{className:"ml-auto shrink-0 text-xs tabular text-fg-subtle",children:k.length>0?`${k.length} unsaved change${k.length===1?"":"s"}`:"No changes"}),e.jsx(R,{variant:"subtle",onClick:ge,disabled:k.length===0||h,children:"Reset"}),e.jsx(R,{variant:"primary",onClick:fe,disabled:k.length===0,loading:h,loadingLabel:"Saving…",children:"Save changes"})]})})]})]})}function Be({groups:t,active:n,onJump:s,dirtyCount:o}){return e.jsx("nav",{"aria-label":"Config sections",className:"hidden w-52 shrink-0 overflow-y-auto border-r border-edge px-2 py-4 lg:block",children:e.jsx("ul",{className:"space-y-0.5",children:t.map(r=>{const a=r.id===n,m=o[r.id]??0;return e.jsx("li",{children:e.jsxs("button",{type:"button",onClick:()=>s(r.id),"aria-current":a?"true":void 0,className:g("motion-fast flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors",a?"bg-surface-selected font-medium text-fg":"text-fg-muted hover:bg-surface-hover hover:text-fg"),children:[e.jsx("span",{className:"min-w-0 flex-1 truncate",children:r.label}),m>0&&e.jsx("span",{className:"h-1.5 w-1.5 shrink-0 rounded-full bg-accent-solid","aria-label":`${m} unsaved`}),r.fields.length>0&&e.jsx("span",{className:"shrink-0 text-2xs tabular text-fg-subtle",children:r.fields.length})]})},r.id)})})})}function He({groups:t,active:n,onJump:s,dirtyCount:o}){return t.length===0?null:e.jsx("nav",{"aria-label":"Config sections",className:"-mx-1 mt-2 flex gap-1 overflow-x-auto px-1 pb-0.5 lg:hidden",children:t.map(r=>{const a=r.id===n,m=o[r.id]??0;return e.jsxs("button",{type:"button",onClick:()=>s(r.id),"aria-current":a?"true":void 0,className:g("motion-fast flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors",a?"bg-surface-selected font-medium text-fg":"text-fg-muted hover:bg-surface-hover hover:text-fg"),children:[r.label,m>0&&e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-accent-solid","aria-label":`${m} unsaved`})]},r.id)})})}function Ue({query:t,onQuery:n,modifiedOnly:s,onModifiedOnly:o,visibleCount:r,totalCount:a,filtering:m,autoFocus:x}){return e.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[e.jsxs("div",{className:"relative min-w-0 flex-1 sm:max-w-sm",children:[e.jsx(we,{width:14,height:14,className:"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-fg-subtle"}),e.jsx(B,{type:"search","aria-label":"Search settings",placeholder:"Search settings, keys, env vars…",className:"h-8 py-0 pl-8 pr-7 [&::-webkit-search-cancel-button]:appearance-none",value:t,autoFocus:x,onChange:h=>n(h.target.value),onKeyDown:h=>{h.key==="Escape"&&t!==""&&(h.preventDefault(),n(""))}}),t!==""&&e.jsx("button",{type:"button","aria-label":"Clear search",onClick:()=>n(""),className:"absolute right-2 top-1/2 -translate-y-1/2 text-fg-subtle hover:text-fg",children:e.jsx(Ne,{width:13,height:13})})]}),e.jsx("button",{type:"button","aria-pressed":s,onClick:()=>o(!s),className:g("motion-fast shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors",s?"border-accent-edge bg-accent-soft text-accent":"border-edge text-fg-muted hover:text-fg"),children:"Modified only"}),e.jsx("span",{className:"shrink-0 text-xs tabular text-fg-subtle",children:m?`${r} of ${a}`:`${a} settings`})]})}function _e({saved:t,configPath:n,restartRequired:s,fileError:o}){const r=o?"danger":t?"success":"warn";return e.jsx(Re,{tone:r,className:"mb-5 leading-snug",icon:r==="success"?e.jsx(ke,{width:15,height:15}):e.jsx(oe,{width:15,height:15}),children:e.jsx("span",{role:"status",children:o?e.jsxs(e.Fragment,{children:[e.jsxs("strong",{children:["Could not read ",G(n),"."]})," ",o," — the values below are the running instance's; what a restart would load is unknown until the file parses."]}):t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):s?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Restart pending."})," ",e.jsx("code",{className:"font-mono text-xs",children:G(n)})," holds changes the running instance has not picked up — the fields below marked"," ",e.jsx(E,{tone:"warn",children:"restart"})," differ from what is in force right now."]}):e.jsxs(e.Fragment,{children:["Changes here are written to"," ",e.jsx("code",{className:"font-mono text-xs",children:G(n)})," and take effect only after the server restarts — the running instance keeps its current config until then."]})})})}function ze(){return e.jsxs("p",{className:"mb-5 text-xs leading-snug text-fg-muted",children:["Settings marked ",e.jsx(E,{tone:"neutral",children:"env"})," are overridden by an environment variable, which wins over this file — edit that variable (and restart) to change them."]})}function Ge({field:t,value:n,dirty:s,onChange:o}){const r=!t.editable||t.envOverridden,a=`cfg-${t.key}`,m=t.help?`${a}-help`:void 0,x=t.type==="boolean",h=c.useMemo(()=>Je(t,q(t)),[t]),p=x&&!r,j=!t.help&&!t.envOverridden&&!t.pendingRestart,u=j?"items-center":"items-start",f=j?"sm:items-center":"sm:items-start",N=r?e.jsx(Xe,{field:t,value:n}):x?e.jsx(Me,{id:a,checked:!!n,onChange:o,label:t.label}):e.jsx(Ke,{field:t,value:n,onChange:o,inputId:a,describedBy:m});return e.jsxs("div",{className:"relative px-4 py-3",children:[s&&e.jsx("span",{"aria-hidden":!0,className:"absolute inset-y-2 left-0 w-0.5 rounded-full bg-accent-solid"}),e.jsxs("div",{className:g(h?"":p?`flex gap-4 ${u}`:`sm:flex sm:gap-6 ${f}`),children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("label",{htmlFor:r?void 0:a,className:"text-sm font-medium text-fg",children:t.label}),t.sensitive&&e.jsx(E,{tone:"warn",children:"sensitive"}),t.envOverridden&&e.jsx(E,{tone:"neutral",title:`Overridden by environment variable ${t.envVar} — edit that env var (and restart) to change it.`,children:"env"}),!t.editable&&!t.envOverridden&&e.jsx(E,{tone:"neutral",children:"read-only"}),t.pendingRestart&&e.jsx(E,{tone:"warn",title:"The config file and the running instance disagree on this field — restart to apply.",children:"restart"})]}),t.help&&e.jsx("p",{id:m,className:"mt-0.5 text-xs leading-snug text-fg-muted",children:t.help}),t.envOverridden&&e.jsx("p",{className:"mt-1 truncate font-mono text-2xs text-fg-subtle",children:t.envVar}),t.pendingRestart&&e.jsxs("p",{className:"mt-1 text-2xs leading-snug text-warn",children:["In force now: ",e.jsx("span",{className:"font-mono",children:qe(t.value)})]})]}),e.jsx("div",{className:g(h?"mt-2":p?"flex shrink-0 justify-end":`mt-2 sm:mt-0 sm:shrink-0 ${De}`),children:N})]})]})}function qe(t){if(t==null||t==="")return"(not set)";const s=(Array.isArray(t)?t.join(", "):String(t)).replace(/\s+/g," ").trim();return s.length>60?`${s.slice(0,60)}…`:s}function E({children:t,tone:n,title:s}){return e.jsx(ce,{tone:n,size:"sm",title:s,className:"uppercase tracking-wide",children:t})}function Ke({field:t,value:n,onChange:s,inputId:o,describedBy:r}){if(t.type==="enum")return e.jsx($e,{id:o,"aria-describedby":r,value:String(n??""),onChange:a=>s(a.target.value),children:(t.enumValues??[]).map(a=>e.jsx("option",{value:a,children:a},a))});if(t.type==="number")return e.jsx(B,{id:o,type:"number","aria-describedby":r,className:"tabular",value:n==null?"":String(n),placeholder:t.default===null?"default":String(t.default),onChange:a=>s(a.target.value===""?null:Number(a.target.value))});if(t.type==="text"){const a=n==null||n===t.default,m=n==null?String(t.default??""):String(n),x=typeof t.default=="string"&&!a;return e.jsxs("div",{children:[e.jsx(Pe,{id:o,"aria-describedby":r,className:"min-h-[7rem] font-mono text-xs leading-relaxed",rows:6,spellCheck:!1,value:m,onChange:h=>s(h.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-xs text-fg-muted",children:[e.jsx("span",{children:a?"Using the built-in default.":n===""?"Empty — nothing will be appended.":`${m.length.toLocaleString()} characters.`}),x&&e.jsx(R,{variant:"link",size:"sm",className:"shrink-0",onClick:()=>s(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const a=Array.isArray(n)?n.join(", "):String(n??"");return e.jsx(B,{id:o,type:"text","aria-describedby":r,value:a,onChange:m=>s(m.target.value.split(",").map(x=>x.trim()).filter(Boolean))})}return e.jsx(B,{id:o,type:"text","aria-describedby":r,value:n==null?"":String(n),placeholder:t.default===null?"":String(t.default),onChange:a=>s(a.target.value)})}function Xe({field:t,value:n}){const s="rounded-lg bg-surface-sunken px-3 py-2 font-mono text-xs text-fg-muted";return t.type==="boolean"?e.jsx("div",{className:s,children:n?"true":"false"}):n==null||n===""?e.jsx("div",{className:s,children:e.jsx("span",{className:"italic text-fg-subtle",children:t.type==="text"&&n===""?"(empty — nothing appended)":"(not set)"})}):t.type==="text"?e.jsx("div",{className:g(s,"max-h-40 overflow-y-auto whitespace-pre-wrap leading-relaxed"),children:String(n)}):e.jsx("div",{className:g(s,"break-all"),children:Array.isArray(n)?n.join(", "):String(n)})}function Je(t,n){return t.type==="text"||t.type==="string-list"?!0:(Array.isArray(n)?n.join(", "):n==null?"":String(n)).length>38}function Ye(t){return[t.label,t.key,t.help,t.envVar,...t.enumValues??[]].filter(Boolean).join(" ").toLowerCase()}function Qe(t,n){const s={};for(const o of t)s[o.id]=o.fields.filter(r=>n.has(r.key)).length;return s}const D=t=>`cfg-section-${t}`,V={id:"appearance",label:"Appearance",description:"Theme and accent colour for this browser. Applies immediately — no save, no restart.",fields:[]},We=["appearance theme themes colour color accent tint dark light font",...ae.map(t=>t.id)].join(" ");function q(t){return t.pendingValue===void 0?t.value:t.pendingValue}function se(t,n){return Object.prototype.hasOwnProperty.call(n,t.key)?n[t.key]:q(t)}function re(t,n){return Array.isArray(t)&&Array.isArray(n)?t.length===n.length&&t.every((s,o)=>s===n[o]):t===n}function G(t){if(!t)return"paddock.config.yaml";const n=t.split("/");return n[n.length-1]||t}function nt(){const t=Se(),n=(t==null?void 0:t.openNav)??(()=>{});return e.jsxs("div",{className:"flex h-full min-h-0 flex-col",children:[e.jsxs("header",{className:"pt-safe flex items-center gap-2 border-b border-edge px-3 pb-2.5 sm:px-6 lg:py-4",children:[e.jsx("button",{type:"button",onClick:n,"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:e.jsx(Ae,{width:20,height:20})}),e.jsx(Ee,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),e.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Config"})]}),e.jsx(Ve,{})]})}export{nt as InstanceConfigPage};
2
+