@edspencer/paddock 0.75.0 → 0.77.0

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 (78) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/instance-config.js +2 -2
  3. package/packages/server/dist/routes/chats.d.ts +17 -0
  4. package/packages/server/dist/routes/chats.js +32 -4
  5. package/packages/server/package.json +1 -1
  6. package/packages/web/dist/assets/{DeleteProjectDialog-BVjEiE8h.js → DeleteProjectDialog-cHTdNUlT.js} +1 -1
  7. package/packages/web/dist/assets/DiscoverPage-DzR0ltxH.js +2 -0
  8. package/packages/web/dist/assets/DiscoverView-CiTYHesP.js +2 -0
  9. package/packages/web/dist/assets/InstanceConfigPage-C9cJA3mw.js +2 -0
  10. package/packages/web/dist/assets/{MarkdownRenderer-A5ROjF2W.js → MarkdownRenderer-5uaTbABu.js} +2 -2
  11. package/packages/web/dist/assets/{ProjectRedirect-CNQm3371.js → ProjectRedirect-BscldZqe.js} +1 -1
  12. package/packages/web/dist/assets/ProjectView-Bj95W0f4.js +13 -0
  13. package/packages/web/dist/assets/ProjectsGrid-BYgv4KBI.js +2 -0
  14. package/packages/web/dist/assets/RootHome-2SxtceM_.js +2 -0
  15. package/packages/web/dist/assets/{Surfaces-CF0yyXrm.js → Surfaces-B__G3SGB.js} +1 -1
  16. package/packages/web/dist/assets/{arc-DrUfzi6A.js → arc-DeEeXqHG.js} +1 -1
  17. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-BYqov1Zc.js → architectureDiagram-3BPJPVTR-DAEVJcxx.js} +1 -1
  18. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-DmMOVO_f.js → blockDiagram-GPEHLZMM-BDsc1L6N.js} +1 -1
  19. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-Czfa53Zk.js → c4Diagram-AAUBKEIU-BRAkt0wF.js} +1 -1
  20. package/packages/web/dist/assets/channel-xkQ7S92i.js +2 -0
  21. package/packages/web/dist/assets/{chunk-2J33WTMH-C0V1Q1vH.js → chunk-2J33WTMH-CbWDgMLI.js} +1 -1
  22. package/packages/web/dist/assets/{chunk-4BX2VUAB-DnjnNYey.js → chunk-4BX2VUAB-BcEZCLPu.js} +1 -1
  23. package/packages/web/dist/assets/{chunk-55IACEB6-BcFZPZvh.js → chunk-55IACEB6-DCqQh6q8.js} +1 -1
  24. package/packages/web/dist/assets/{chunk-727SXJPM-D3f-Sjzp.js → chunk-727SXJPM-JweiY_B4.js} +1 -1
  25. package/packages/web/dist/assets/{chunk-AQP2D5EJ-DQHhDP9y.js → chunk-AQP2D5EJ-BYrqLbEv.js} +1 -1
  26. package/packages/web/dist/assets/{chunk-FMBD7UC4-Dj5WjiyS.js → chunk-FMBD7UC4-dJDuyJ2V.js} +1 -1
  27. package/packages/web/dist/assets/{chunk-ND2GUHAM-Bl7cQ3SB.js → chunk-ND2GUHAM-DsJrI4kA.js} +1 -1
  28. package/packages/web/dist/assets/{chunk-QZHKN3VN-C7SPUOKr.js → chunk-QZHKN3VN-D6xgEpXd.js} +1 -1
  29. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-C1trK71k.js +2 -0
  30. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-C1trK71k.js +2 -0
  31. package/packages/web/dist/assets/{core-DD3lQR7a.js → core-afW9-UwG.js} +1 -1
  32. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-DsUrPBsx.js → cose-bilkent-S5V4N54A-BhZOj7HZ.js} +1 -1
  33. package/packages/web/dist/assets/{dagre-BM42HDAG--oW-opUg.js → dagre-BM42HDAG-DCfvbRPP.js} +1 -1
  34. package/packages/web/dist/assets/{diagram-2AECGRRQ-PBABAEds.js → diagram-2AECGRRQ-Ci0M1D7l.js} +1 -1
  35. package/packages/web/dist/assets/{diagram-5GNKFQAL-DHgapxzA.js → diagram-5GNKFQAL-BZ6SW1yc.js} +1 -1
  36. package/packages/web/dist/assets/{diagram-KO2AKTUF-BlAviCxX.js → diagram-KO2AKTUF-B4-cJTIk.js} +1 -1
  37. package/packages/web/dist/assets/{diagram-LMA3HP47-CY6_XHw1.js → diagram-LMA3HP47-B2OF1ipy.js} +1 -1
  38. package/packages/web/dist/assets/{diagram-OG6HWLK6-6kwM_03D.js → diagram-OG6HWLK6-D70TzZwD.js} +1 -1
  39. package/packages/web/dist/assets/documentTitle-Wl8Djqre.js +2 -0
  40. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-bh-0n04U.js → erDiagram-TEJ5UH35-CDTl1U_L.js} +1 -1
  41. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-BVkoiXeP.js → flowDiagram-I6XJVG4X-BpuTkLzx.js} +1 -1
  42. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-DnfAgVxK.js → ganttDiagram-6RSMTGT7-zfON_pzp.js} +1 -1
  43. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BLyvLMXz.js → gitGraphDiagram-PVQCEYII-CXJlr4Aa.js} +1 -1
  44. package/packages/web/dist/assets/index-BPqHA_fy.js +71 -0
  45. package/packages/web/dist/assets/index-Uc957im9.css +1 -0
  46. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-D6B9ugJD.js → infoDiagram-5YYISTIA-1K1LG6E2.js} +1 -1
  47. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-CZ7CzKek.js → ishikawaDiagram-YF4QCWOH-srZJnYw8.js} +1 -1
  48. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-C7XkLc9i.js → journeyDiagram-JHISSGLW-3E_CsMIN.js} +1 -1
  49. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-B1IONnOY.js → kanban-definition-UN3LZRKU-BUPFeskA.js} +1 -1
  50. package/packages/web/dist/assets/{linear-Evefwzlv.js → linear-CS75OsJ4.js} +1 -1
  51. package/packages/web/dist/assets/{mermaid.core-DFz_RqY2.js → mermaid.core-AX6KCjdW.js} +4 -4
  52. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DCHEHB1t.js → mindmap-definition-RKZ34NQL-CvtTMpsJ.js} +1 -1
  53. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-CAgAPCtd.js → pieDiagram-4H26LBE5-DyWKFZLR.js} +1 -1
  54. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-Qx0hVImF.js → quadrantDiagram-W4KKPZXB-DlLXpUjd.js} +1 -1
  55. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-Cag9Mvg9.js → requirementDiagram-4Y6WPE33-DuF2w7X7.js} +1 -1
  56. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DCdGTeQa.js → sankeyDiagram-5OEKKPKP-jQdUQ_IA.js} +1 -1
  57. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-Dwpsy94j.js → sequenceDiagram-3UESZ5HK-4RN9tEo0.js} +1 -1
  58. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-aGMHRRAE.js → stateDiagram-AJRCARHV-CkknRhAq.js} +1 -1
  59. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-1UhTuOCG.js +2 -0
  60. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-BzPnsQmY.js → timeline-definition-PNZ67QCA-CtDo_Km4.js} +1 -1
  61. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CZ1fY8y7.js → vennDiagram-CIIHVFJN-BtHweUMq.js} +1 -1
  62. package/packages/web/dist/assets/{wardley-L42UT6IY-C41nLzIr.js → wardley-L42UT6IY-C_OWlnET.js} +1 -1
  63. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-CoUrc0lU.js → wardleyDiagram-YWT4CUSO-CI_iXYGs.js} +1 -1
  64. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-DCOTYBX6.js → xychartDiagram-2RQKCTM6-zPP4izdU.js} +1 -1
  65. package/packages/web/dist/index.html +2 -2
  66. package/packages/web/dist/sw.js +1 -1
  67. package/packages/web/dist/assets/DiscoverPage-Pqr0B6n7.js +0 -2
  68. package/packages/web/dist/assets/DiscoverView-BWfSBjLX.js +0 -2
  69. package/packages/web/dist/assets/InstanceConfigPage-ClXUufoD.js +0 -2
  70. package/packages/web/dist/assets/ProjectView-BdzJ3aKK.js +0 -13
  71. package/packages/web/dist/assets/ProjectsGrid-DR1fmWxh.js +0 -2
  72. package/packages/web/dist/assets/RootHome-CTLglUiz.js +0 -2
  73. package/packages/web/dist/assets/channel-CEHEe3UY.js +0 -2
  74. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CeAhIB_Q.js +0 -2
  75. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CeAhIB_Q.js +0 -2
  76. package/packages/web/dist/assets/index-CQsrBw44.js +0 -71
  77. package/packages/web/dist/assets/index-LM9Ga0Wn.css +0 -1
  78. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-DhdrFtkn.js +0 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edspencer/paddock",
3
- "version": "0.75.0",
3
+ "version": "0.77.0",
4
4
  "description": "Project-first launchpad for Claude Code: server-hosted, persistent, resumable sessions organized by project.",
5
5
  "keywords": [
6
6
  "claude",
@@ -276,8 +276,8 @@ export const FIELDS = [
276
276
  { key: "ui.transcriptRenderLimit", group: "ui", label: "Transcript render limit", help: "How many recent messages a chat renders on open. 0 = no limit. Older messages stay on disk and are not deleted.", type: "number", envVars: ["PADDOCK_UI_TRANSCRIPT_RENDER_LIMIT"], default: DEFAULT_UI.transcriptRenderLimit, editable: true, coerce: nonNegInt },
277
277
  // Branding (issue #34).
278
278
  { key: "brand.name", group: "branding", label: "Name", type: "string", envVars: ["PADDOCK_BRAND_NAME"], default: "Paddock", editable: true, coerce: nonEmptyString },
279
- { key: "brand.logo", group: "branding", label: "Logo", help: "An emoji/glyph, or a URL/path to an image.", type: "string", envVars: ["PADDOCK_BRAND_LOGO"], default: "🐎", editable: true, coerce: nonEmptyString },
280
- { key: "brand.accent", group: "branding", label: "Accent color", type: "string", envVars: ["PADDOCK_BRAND_ACCENT"], default: "#c2603c", editable: true, coerce: hexColor },
279
+ { key: "brand.logo", group: "branding", label: "Logo", help: "An emoji/glyph, or a URL/path to an image. With the accent colour, it draws this instance's browser-tab icon — what tells its tabs apart from other instances'.", type: "string", envVars: ["PADDOCK_BRAND_LOGO"], default: "🐎", editable: true, coerce: nonEmptyString },
280
+ { key: "brand.accent", group: "branding", label: "Accent color", help: "Left at the default, a renamed instance's tab icon and logo chip get a colour derived from its name.", type: "string", envVars: ["PADDOCK_BRAND_ACCENT"], default: "#c2603c", editable: true, coerce: hexColor },
281
281
  // Transcription (voice dictation). endpoint is semi-sensitive; apiKey is a
282
282
  // secret and deliberately NOT surfaced here.
283
283
  { key: "transcription.mode", group: "transcription", label: "Mode", type: "enum", enumValues: ["off", "local", "remote"], envVars: ["PADDOCK_WHISPER_MODE"], default: "off", editable: true, coerce: oneOf(["off", "local", "remote"]) },
@@ -1,5 +1,21 @@
1
1
  import type { FastifyInstance } from "fastify";
2
2
  import type { RouteCtx } from "../route-context.js";
3
+ /**
4
+ * How many rows the attention feed's `recent` list carries. Home renders a
5
+ * screenful and offers the rest behind "Show more"; past this the list is the
6
+ * sidebar's job, not the front door's.
7
+ */
8
+ export declare const RECENT_LIMIT = 50;
9
+ /** The two timestamps a chat DTO carries that say something happened in it. */
10
+ interface ChatTimes {
11
+ updatedAt?: string;
12
+ lastTurnCompletedAt?: string;
13
+ }
14
+ /**
15
+ * When a chat last DID something — the later of its last message and its last
16
+ * completed turn, as epoch ms (0 when neither parses). Exported for the tests.
17
+ */
18
+ export declare function activityAt(c: ChatTimes): number;
3
19
  /**
4
20
  * Workspace-scoped chat routes: paths are declared RELATIVE to the workspace
5
21
  * (e.g. `/chats/:sessionId`), and this plugin is mounted TWICE — once at
@@ -10,3 +26,4 @@ import type { RouteCtx } from "../route-context.js";
10
26
  * ["slug"]` schemas below keep validating unchanged on both mounts.
11
27
  */
12
28
  export declare function registerChatWorkspaceRoutes(app: FastifyInstance, ctx: RouteCtx): void;
29
+ export {};
@@ -26,6 +26,21 @@ import { sendProjectError } from "../route-errors.js";
26
26
  import { mapWithConcurrency, FILE_FANOUT_CONCURRENCY } from "../concurrency.js";
27
27
  import { CHAT_USAGE_SCOPES, SAFE_SESSION_ID, RUNS_SEEN_SESSION, clampRunsLimit, toChatUsage, buildProjectChats, makeTriggerResolver, makeParentResolver, normalizeBatchSessionIds, BATCH_SESSIONS_MAX, } from "../chat-dto.js";
28
28
  import { quiesceSession, turnRunningError } from "../turn-interlock.js";
29
+ /**
30
+ * How many rows the attention feed's `recent` list carries. Home renders a
31
+ * screenful and offers the rest behind "Show more"; past this the list is the
32
+ * sidebar's job, not the front door's.
33
+ */
34
+ export const RECENT_LIMIT = 50;
35
+ /**
36
+ * When a chat last DID something — the later of its last message and its last
37
+ * completed turn, as epoch ms (0 when neither parses). Exported for the tests.
38
+ */
39
+ export function activityAt(c) {
40
+ const a = c.updatedAt ? Date.parse(c.updatedAt) : NaN;
41
+ const b = c.lastTurnCompletedAt ? Date.parse(c.lastTurnCompletedAt) : NaN;
42
+ return Math.max(Number.isFinite(a) ? a : 0, Number.isFinite(b) ? b : 0);
43
+ }
29
44
  /**
30
45
  * Workspace-scoped chat routes: paths are declared RELATIVE to the workspace
31
46
  * (e.g. `/chats/:sessionId`), and this plugin is mounted TWICE — once at
@@ -150,8 +165,8 @@ export function registerChatWorkspaceRoutes(app, ctx) {
150
165
  app.get("/chats/attention", {
151
166
  schema: {
152
167
  tags: ["Chats"],
153
- summary: "List the chats in this workspace's subtree that are running or unread",
154
- description: "Returns `{ running, unread }` — the chats in this workspace AND its descendants that currently have a live turn (`running`) or hold an unread reply (`unread`). Each row is a chat DTO plus the `projectSlug`/`projectName` it belongs to, so a fleet-wide list stays attributable. On the root mount (`/api/root/chats/attention`) the subtree is the whole instance, because the root workspace's key (`\"\"`) prefixes every workspace key; on a project mount it is that project alone. A chat is never in both lists: a live turn hasn't landed a reply yet, so running wins. `running` rows additionally carry resolved `contextTokens`/`contextLimit` so a caller can draw a context gauge for live work; `unread` rows do not, because resolving usage streams a transcript per chat and only the running set is bounded by how many turns can be in flight at once.",
168
+ summary: "List the chats in this workspace's subtree that are running, unread, or recently active",
169
+ description: "Returns `{ running, unread, recent }` — the chats in this workspace AND its descendants that currently have a live turn (`running`), hold an unread reply (`unread`), or are simply the most recently active (`recent`: every non-running, non-archived chat, read or not, newest first, capped at 50; a superset of the newest `unread` rows). All three lists are ordered newest activity first (the later of `updatedAt` and `lastTurnCompletedAt`). Each row is a chat DTO plus the `projectSlug`/`projectName` it belongs to, so a fleet-wide list stays attributable. On the root mount (`/api/root/chats/attention`) the subtree is the whole instance, because the root workspace's key (`\"\"`) prefixes every workspace key; on a project mount it is that project alone. A chat is never in both lists: a live turn hasn't landed a reply yet, so running wins. `running` rows additionally carry resolved `contextTokens`/`contextLimit` so a caller can draw a context gauge for live work; `unread` rows do not, because resolving usage streams a transcript per chat and only the running set is bounded by how many turns can be in flight at once.",
155
170
  params: {
156
171
  type: "object",
157
172
  properties: {
@@ -161,7 +176,7 @@ export function registerChatWorkspaceRoutes(app, ctx) {
161
176
  },
162
177
  response: {
163
178
  200: {
164
- description: "Object `{ running, unread }`, each an array of chat DTOs enriched with `projectSlug` and `projectName`.",
179
+ description: "Object `{ running, unread, recent }`, each an array of chat DTOs enriched with `projectSlug` and `projectName`.",
165
180
  type: "object",
166
181
  additionalProperties: true,
167
182
  },
@@ -186,6 +201,10 @@ export function registerChatWorkspaceRoutes(app, ctx) {
186
201
  const lastTurnAt = await herdctl.lastTurnCompletedAt().catch(() => new Map());
187
202
  const running = [];
188
203
  const unreadRows = [];
204
+ // Every non-running, non-archived chat, read or not — the "Recent" half
205
+ // of Home's Running & Recent feed. Capped after sorting, so it is the
206
+ // newest RECENT_LIMIT across the whole subtree, not per workspace.
207
+ const recentRows = [];
189
208
  for (const p of subtree) {
190
209
  const sessions = await herdctl.listSessions(p).catch(() => []);
191
210
  if (sessions.length === 0)
@@ -226,9 +245,18 @@ export function registerChatWorkspaceRoutes(app, ctx) {
226
245
  const seen = c.lastSeen ?? 0;
227
246
  if (c.unread || (Number.isFinite(completed) && completed > seen))
228
247
  unreadRows.push(row);
248
+ recentRows.push(row);
229
249
  }
230
250
  }
231
- return { running, unread: unreadRows };
251
+ // Newest activity first, in every list. These used to come back in
252
+ // workspace-then-transcript order, which on the root put a project's
253
+ // weeks-old unread above a reply that landed a minute ago — an order
254
+ // the reader could not see any reason for.
255
+ const byActivity = (a, b) => activityAt(b) - activityAt(a);
256
+ running.sort(byActivity);
257
+ unreadRows.sort(byActivity);
258
+ recentRows.sort(byActivity);
259
+ return { running, unread: unreadRows, recent: recentRows.slice(0, RECENT_LIMIT) };
232
260
  }
233
261
  catch (err) {
234
262
  return sendProjectError(reply, err);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.75.0",
3
+ "version": "0.77.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,2 +1,2 @@
1
- import{r as l,d as k,j as s,A as y,b as p,a as w}from"./index-CQsrBw44.js";import{c as N}from"./lastTab-CU1zsF0j.js";function v({open:e,title:t,message:c,confirmLabel:n="Delete",cancelLabel:o="Cancel",danger:a=!0,wide:h=!1,dismissOnBackdrop:b=!0,onConfirm:g,onClose:d}){const[r,u]=l.useState(!1),[f,m]=l.useState(null);if(l.useEffect(()=>{e&&m(null)},[e]),k(e&&!r,d),!e)return null;const j=async()=>{u(!0),m(null);try{await g()}catch(i){m(i instanceof Error?i.message:"Action failed"),u(!1)}};return s.jsx("div",{className:"fixed inset-0 z-[60] flex items-center justify-center bg-overlay p-4 backdrop-blur-sm",onClick:()=>b&&!r&&d(),children:s.jsxs("div",{className:`w-full ${h?"max-w-md":"max-w-sm"} animate-scale-in rounded-2xl border border-edge bg-surface-raised p-6 shadow-2xl`,onClick:i=>i.stopPropagation(),role:"alertdialog","aria-modal":"true",children:[s.jsxs("div",{className:"flex items-start gap-3",children:[a&&s.jsx("span",{className:"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-danger-soft text-danger",children:s.jsx(y,{width:18,height:18})}),s.jsxs("div",{className:"min-w-0",children:[s.jsx("h2",{className:"text-base font-semibold",children:t}),s.jsx("div",{className:"mt-1.5 text-sm text-fg-muted",children:c})]})]}),f&&s.jsx("p",{className:"mt-4 rounded-lg bg-danger-soft px-3 py-2 text-sm text-danger",children:f}),s.jsxs("div",{className:"mt-5 flex justify-end gap-2",children:[s.jsx("button",{type:"button",className:"btn-ghost",onClick:d,disabled:r,children:o}),s.jsx("button",{type:"button",className:a?"btn btn-danger":"btn-primary",onClick:j,disabled:r,children:r?"Working…":n})]})]})})}function P(e,t){return e===t?!0:e.startsWith(t.endsWith("/")?t:`${t}/`)}function x(e){return!P(e.workingDir,e.dir)}function C({project:e}){const t=s.jsx("span",{className:"font-medium text-fg",children:e.name});return x(e)?s.jsxs(s.Fragment,{children:[t," will be unlinked. Paddock’s notes, settings and chats for it are permanently removed, and this cannot be undone — but the directory it works in is left alone:",s.jsx("span",{className:"mt-1.5 block break-all font-mono text-xs text-fg",children:e.workingDir}),s.jsx("span",{className:"mt-1.5 block",children:"Nothing in there is deleted."})]}):s.jsxs(s.Fragment,{children:[t," and all its chats and files will be permanently removed. This cannot be undone."]})}function D(){const{remove:e}=p();return l.useCallback(async t=>{await w.deleteProject(t),e(t),N(t)},[e])}function W({project:e,open:t,onClose:c,onDeleted:n}){const o=D(),a=x(e);return s.jsx(v,{open:t,title:"Delete project?",message:s.jsx(C,{project:e}),confirmLabel:"Delete project",wide:a,dismissOnBackdrop:!a,onConfirm:async()=>{await o(e.slug),n==null||n()},onClose:c})}export{v as C,W as D,x as d};
1
+ import{r as l,e as k,j as s,A as y,d as p,a as w}from"./index-BPqHA_fy.js";import{c as N}from"./lastTab-CU1zsF0j.js";function v({open:e,title:t,message:c,confirmLabel:n="Delete",cancelLabel:o="Cancel",danger:a=!0,wide:h=!1,dismissOnBackdrop:b=!0,onConfirm:g,onClose:d}){const[r,u]=l.useState(!1),[f,m]=l.useState(null);if(l.useEffect(()=>{e&&m(null)},[e]),k(e&&!r,d),!e)return null;const j=async()=>{u(!0),m(null);try{await g()}catch(i){m(i instanceof Error?i.message:"Action failed"),u(!1)}};return s.jsx("div",{className:"fixed inset-0 z-[60] flex items-center justify-center bg-overlay p-4 backdrop-blur-sm",onClick:()=>b&&!r&&d(),children:s.jsxs("div",{className:`w-full ${h?"max-w-md":"max-w-sm"} animate-scale-in rounded-2xl border border-edge bg-surface-raised p-6 shadow-2xl`,onClick:i=>i.stopPropagation(),role:"alertdialog","aria-modal":"true",children:[s.jsxs("div",{className:"flex items-start gap-3",children:[a&&s.jsx("span",{className:"mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-danger-soft text-danger",children:s.jsx(y,{width:18,height:18})}),s.jsxs("div",{className:"min-w-0",children:[s.jsx("h2",{className:"text-base font-semibold",children:t}),s.jsx("div",{className:"mt-1.5 text-sm text-fg-muted",children:c})]})]}),f&&s.jsx("p",{className:"mt-4 rounded-lg bg-danger-soft px-3 py-2 text-sm text-danger",children:f}),s.jsxs("div",{className:"mt-5 flex justify-end gap-2",children:[s.jsx("button",{type:"button",className:"btn-ghost",onClick:d,disabled:r,children:o}),s.jsx("button",{type:"button",className:a?"btn btn-danger":"btn-primary",onClick:j,disabled:r,children:r?"Working…":n})]})]})})}function P(e,t){return e===t?!0:e.startsWith(t.endsWith("/")?t:`${t}/`)}function x(e){return!P(e.workingDir,e.dir)}function C({project:e}){const t=s.jsx("span",{className:"font-medium text-fg",children:e.name});return x(e)?s.jsxs(s.Fragment,{children:[t," will be unlinked. Paddock’s notes, settings and chats for it are permanently removed, and this cannot be undone — but the directory it works in is left alone:",s.jsx("span",{className:"mt-1.5 block break-all font-mono text-xs text-fg",children:e.workingDir}),s.jsx("span",{className:"mt-1.5 block",children:"Nothing in there is deleted."})]}):s.jsxs(s.Fragment,{children:[t," and all its chats and files will be permanently removed. This cannot be undone."]})}function D(){const{remove:e}=p();return l.useCallback(async t=>{await w.deleteProject(t),e(t),N(t)},[e])}function W({project:e,open:t,onClose:c,onDeleted:n}){const o=D(),a=x(e);return s.jsx(v,{open:t,title:"Delete project?",message:s.jsx(C,{project:e}),confirmLabel:"Delete project",wide:a,dismissOnBackdrop:!a,onConfirm:async()=>{await o(e.slug),n==null||n()},onClose:c})}export{v as C,W as D,x as d};
2
2
 
@@ -0,0 +1,2 @@
1
+ import{j as o}from"./index-BPqHA_fy.js";import{D as r}from"./DiscoverView-CiTYHesP.js";import{u as e}from"./documentTitle-Wl8Djqre.js";import"./Surfaces-B__G3SGB.js";function p(){return e(["Discover"]),o.jsx(r,{})}export{p as DiscoverPage};
2
+
@@ -0,0 +1,2 @@
1
+ import{h as oe,d as ie,a0 as de,r as u,a as A,j as s,V as W,A as X,a4 as Z,I as $,a1 as ce,Q as le,i as he,a5 as ue,a6 as J,J as T,c as K,a7 as pe,t as xe,Y as Q}from"./index-BPqHA_fy.js";import{E as R,C as me}from"./Surfaces-B__G3SGB.js";function ge(e){const t={};for(const n of e)t[n.path]={checked:!0,expanded:!1,loading:!1,error:null};return t}function fe(e){if(!e.sessions||!e.selected)return e.checked?"on":"off";const t=e.sessions.sessions.length,n=e.selected.size;return t===0||n===0?"off":n===t?"on":"mixed"}function C(e,t){return e.sessions&&e.selected?e.selected.size:e.checked?t.sessionCount:0}function je(e,t){return t.reduce((n,r)=>n+C(e[r.path]??O(),r),0)}function be(e,t){return t.filter(n=>C(e[n.path]??O(),n)>0)}function we(e,t,n){const r=e[t];if(!r)return e;const a={...r,checked:n};return r.sessions&&(a.selected=n?new Set(r.sessions.sessions.map(c=>c.sessionId)):new Set),{...e,[t]:a}}function ye(e,t,n){const r=e[t];if(!r||!r.sessions)return e;const a=new Set(r.selected??[]);return a.delete(n)||a.add(n),{...e,[t]:{...r,selected:a,checked:a.size>0}}}function ve(e,t,n){const r=e[t];if(!r)return e;const a=r.checked?new Set(n.sessions.map(c=>c.sessionId)):new Set;return{...e,[t]:{...r,sessions:n,selected:a,loading:!1,error:null}}}function D(e,t,n){const r=e[t];return r?{...e,[t]:{...r,...n}}:e}function Ne(e,t){const n=e??O();return C(n,t)===0?null:n.sessions&&n.selected?{sessionIds:[...n.selected]}:{}}function O(){return{checked:!1,expanded:!1,loading:!1,error:null}}function ke(e){return e.error!==void 0?e.stage==="create"?"danger":"warn":e.adopted>0?"success":"warn"}async function Se(e,t,n){let r;try{r=await e.createProject({name:t.name,slug:t.suggestedSlug,path:t.path,managed:!1})}catch(a){return{stage:"create",adopted:0,skipped:[],error:_(a)}}try{const a=await e.adoptChats(r.slug,{sourceCwd:r.workingDir,...n.sessionIds?{sessionIds:n.sessionIds}:{}});return{project:r,adopted:a.adopted.length,skipped:a.skipped}}catch(a){return{stage:"import",project:r,adopted:0,skipped:[],error:_(a)}}}async function $e(e,t,n){const r=[];for(const a of t){n(a.candidate.path,"running");const c=await Se(e,a.candidate,a.plan);r.push(c),n(a.candidate.path,"done",c)}return r}const Ce={sidechain:"sub-agent transcript","already-adopted":"already adopted","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 Y(e){if(e.length===0)return"";const t=[...new Set(e.map(n=>Ce[n.reason]??n.reason))];return`${e.length} skipped (${t.join(", ")})`}function Ie(e,t){var l;if(e.stage==="create")return`Couldn't create the project: ${e.error}`;const n=((l=e.project)==null?void 0:l.slug)??t.suggestedSlug;if(e.stage==="import")return`Project “${n}” 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=Y(e.skipped);return t.recordedPath!==void 0?`Project “${n}” 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 “${n}” was created but no chats came with it — ${m}.`:`Project “${n}” was created but no chats came with it.`}const r=e.adopted,a=Y(e.skipped),c=`Adopted ${r} chat${r===1?"":"s"} into “${n}”`;return a?`${c} — ${a}.`:`${c}.`}function _(e){return e instanceof Error?e.message:String(e)}function Ge({firstRun:e=!1,embedded:t=!1,onLeave:n,onStartChat:r}){const a=oe(),{refresh:c}=ie(),l=de(),[m,d]=u.useState(!1),[j,I]=u.useState(!1),[h,x]=u.useState(null),[y,z]=u.useState(null),[F,P]=u.useState(!0),[f,v]=u.useState({}),[k,G]=u.useState(!1),[L,H]=u.useState({}),[E,B]=u.useState({}),[S,ee]=u.useState(!1);u.useEffect(()=>{let o=!0;return P(!0),z(null),A.discover({includeNonGit:m,includeOutsideHome:j}).then(i=>{o&&(x(i),v(ge(i.candidates)),P(!1))}).catch(i=>{o&&(z(i instanceof Error?i.message:"Discovery failed"),P(!1))}),()=>{o=!1}},[m,j]);const w=(h==null?void 0:h.candidates)??[],M=u.useMemo(()=>je(f,w),[f,w]),b=u.useMemo(()=>be(f,w),[f,w]),se=u.useCallback(o=>{v(p=>{const g=p[o.path];if(!g)return p;const N=D(p,o.path,{expanded:!g.expanded});return g.expanded||g.sessions||g.loading?N:D(N,o.path,{loading:!0,error:null})});const i=f[o.path];!i||i.expanded||i.sessions||i.loading||A.discoverSessions(o.path).then(p=>v(g=>ve(g,o.path,p))).catch(p=>v(g=>D(g,o.path,{loading:!1,error:p instanceof Error?p.message:"Could not read this directory's sessions"})))},[f]),te=u.useCallback(async()=>{if(k)return;G(!0),H(Object.fromEntries(b.map(i=>[i.path,"pending"]))),B({});const o=b.map(i=>({candidate:i,plan:Ne(f[i.path],i)??{}}));await $e(A,o,(i,p,g)=>{H(N=>({...N,[i]:p})),g&&B(N=>({...N,[i]:g}))}),G(!1),ee(!0),await c()},[b,f,k,c]),ne=u.useCallback(async()=>{await c(),n==null||n(),a("/")},[a,c,n]),ae=Object.values(E).reduce((o,i)=>o+i.adopted,0),re=Object.values(E).filter(o=>o.project).length,V=s.jsxs(s.Fragment,{children:[s.jsx("p",{className:"mb-5 text-sm text-fg-muted",children:e?"If you have used Claude Code in a terminal on this machine, Paddock can adopt that history: each directory becomes a project with its conversations already in it.":"Directories on this machine with existing Claude Code history. Adopting one links it as a project and brings its conversations in; your own history is never moved or deleted."}),S&&s.jsx(Te,{imported:ae,projects:re,onGetStarted:ne}),y!==null&&s.jsx(W,{tone:"danger",icon:s.jsx(X,{width:16,height:16}),children:y}),F&&s.jsx(De,{}),!F&&y===null&&h!==null&&s.jsxs(s.Fragment,{children:[h.scanned===0?s.jsx(Ae,{claudeHome:h.claudeHome,onStartChat:r}):w.length===0?s.jsx(R,{variant:"panel",icon:s.jsx(Z,{width:22,height:22}),title:"Nothing to adopt",body:"Paddock found Claude Code history on this machine, but none of it is available to adopt right now. The tally below says what was skipped, and the two toggles relax the rules that are safe to relax."}):s.jsx("ul",{className:"space-y-2",children:w.map(o=>s.jsx(Pe,{candidate:o,row:f[o.path],status:L[o.path],outcome:E[o.path],locked:k||S,onToggleRow:i=>v(p=>we(p,o.path,i)),onToggleSession:i=>v(p=>ye(p,o.path,i)),onToggleExpanded:()=>se(o)},o.path))}),!S&&h.scanned>0&&s.jsx(Ee,{result:h,includeNonGit:m,includeOutsideHome:j,disabled:k,onIncludeNonGit:d,onIncludeOutsideHome:I}),w.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:[M," conversation",M===1?"":"s"," from ",b.length," director",b.length===1?"y":"ies"]}),s.jsxs($,{variant:"primary",onClick:()=>void te(),disabled:b.length===0,loading:k,loadingLabel:"Adopting…",children:["Adopt ",b.length," project",b.length===1?"":"s"]})]})]})]});return t?V: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:()=>l==null?void 0:l.openNav(),"aria-label":"Open menu",className:"btn-subtle -ml-1 shrink-0 px-2 py-1.5 lg:hidden",children:s.jsx(ce,{width:20,height:20})}),s.jsx(le,{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.jsx("div",{className:"mx-auto max-w-3xl px-6 py-6",children:V})})]})}function Pe({candidate:e,row:t,status:n,outcome:r,locked:a,onToggleRow:c,onToggleSession:l,onToggleExpanded:m}){const d=t??{checked:!1,expanded:!1,loading:!1,error:null},j=fe(d),I=C(d,e),h=r?ke(r):null;return s.jsx("li",{children:s.jsxs(me,{flush:!0,className:K("overflow-hidden",h==="success"&&"border-success-edge bg-success-soft",h==="warn"&&"border-warn-edge bg-warn-soft",h==="danger"&&"border-danger-edge bg-danger-soft",n==="running"&&"border-accent-edge"),"data-testid":`discover-row-${e.suggestedSlug}`,"data-tone":h??void 0,children:[s.jsxs("div",{className:"flex items-start gap-3 px-3 py-2.5",children:[s.jsx(J,{className:"mt-1",checked:j==="on",indeterminate:j==="mixed",disabled:a,"aria-label":`Adopt ${e.path}`,onChange:x=>c(x.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 ",U(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(X,{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."]})}),r&&s.jsx("p",{className:K("mt-2 text-xs",h==="success"&&"text-success",h==="warn"&&"text-warn",h==="danger"&&"text-danger"),children:Ie(r,e)}),n==="running"&&!r&&s.jsx("p",{className:"mt-2 text-xs text-fg-muted",children:"Adopting…"})]}),s.jsxs("span",{className:"flex shrink-0 items-center gap-1",children:[s.jsx("span",{className:"tabular text-xs text-fg-subtle",children:I}),s.jsx($,{size:"icon-sm",variant:"subtle","aria-label":d.expanded?"Hide conversations":"Show conversations","aria-expanded":d.expanded,onClick:m,children:d.expanded?s.jsx(pe,{width:14,height:14}):s.jsx(xe,{width:14,height:14})})]})]}),d.expanded&&s.jsxs("div",{className:"border-t border-edge px-3 py-2",children:[d.loading&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Reading transcripts…"}),d.error!==null&&s.jsx("p",{className:"py-2 text-xs text-danger",children:d.error}),d.sessions&&d.sessions.sessions.length===0&&s.jsx("p",{className:"py-2 text-xs text-fg-muted",children:"Nothing to adopt here any more — it may have been adopted since the scan."}),d.sessions&&d.sessions.sessions.length>0&&s.jsx("ul",{className:"space-y-1",children:d.sessions.sessions.map(x=>{var y;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(J,{checked:((y=d.selected)==null?void 0:y.has(x.sessionId))??!1,disabled:a,"aria-label":q(x),onChange:()=>l(x.sessionId)}),s.jsxs("span",{className:"min-w-0 flex-1",children:[s.jsx("span",{className:"block truncate text-xs text-fg",children:q(x)}),s.jsxs("span",{className:"tabular mt-0.5 block text-3xs text-fg-muted",children:[U(x.mtime)," · ",Oe(x.sizeBytes)]})]})]})},x.sessionId)})}),d.sessions&&d.sessions.filtered.length>0&&s.jsxs("p",{className:"mt-2 px-2 text-3xs text-fg-subtle",children:[d.sessions.filtered.length," more withheld as noise (",[...new Set(d.sessions.filtered.map(x=>x.reason))].join(", "),")."]})]})]})})}function Ee({result:e,includeNonGit:t,includeOutsideHome:n,disabled:r,onIncludeNonGit:a,onIncludeOutsideHome:c}){const l=e.excluded["no-git"]??0,m=e.excluded["outside-home"]??0,d=l>0||t,j=m>0||n;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:Re(e)}),d&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(Q,{checked:t,disabled:r,onChange:a,label:"Also offer directories without a git repository"}),s.jsxs("span",{children:["Also offer directories without a git repository",l>0&&` (${l} hidden)`]})]}),j&&s.jsxs("div",{className:"flex items-center gap-2 text-xs text-fg-muted",children:[s.jsx(Q,{checked:n,disabled:r,onChange:c,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 Ae({claudeHome:e,onStartChat:t}){return s.jsx(R,{variant:"panel",icon:s.jsx(ue,{width:22,height:22}),title:"Nothing to adopt yet",body:s.jsxs(s.Fragment,{children:["No Claude Code history has been recorded under"," ",s.jsx("code",{className:"break-all",children:e}),". That is normal on a new install, and on a container whose Claude home is its own rather than yours — nothing is wrong. Start a chat here and Paddock writes its own history; or bind-mount an existing one there and re-open this page, or import it headlessly with ",s.jsx("code",{children:"npm run import-chats"}),"."]}),action:t&&s.jsx($,{variant:"primary",icon:s.jsx(he,{width:14,height:14}),onClick:t,children:"Start a chat"})})}function Te({imported:e,projects:t,onGetStarted:n}){return s.jsx("div",{className:"mb-6",children:s.jsx(R,{variant:"panel",icon:s.jsx(Z,{width:22,height:22}),title:t===0?"Nothing was adopted":`${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($,{variant:"primary",onClick:()=>void n(),children:"Get started"})})})}function De(){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 Re(e){const t=[],n=[["temp-root",a=>`${a} in temp directories`],["no-git",a=>`${a} without a git repository`],["outside-home",a=>`${a} outside your home directory`],["already-managed",a=>`${a} already a project`],["no-sessions",a=>`${a} with nothing to adopt`],["no-recorded-cwd",a=>`${a} with no recorded directory`],["missing",a=>`${a} no longer on disk`],["home-root",a=>`${a} your home directory itself`],["system-path",a=>`${a} system directories`],["paddock-internal",a=>`${a} inside Paddock's own data`]];for(const[a,c]of n){const l=e.excluded[a];l!==void 0&&l>0&&t.push(c(l))}const r=`${e.scanned} transcript folder${e.scanned===1?"":"s"} scanned`;return t.length===0?`${r}.`:`${r}; skipped ${t.join(", ")}.`}function q(e){var r,a;const t=(r=e.autoName)==null?void 0:r.trim();if(t!==void 0&&t!=="")return t;const n=(a=e.preview)==null?void 0:a.trim();return n!==void 0&&n!==""?n:e.sessionId}function U(e){const t=new Date(e);return Number.isNaN(t.getTime())?"unknown date":t.toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"})}function Oe(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} kB`:`${(e/(1024*1024)).toFixed(1)} MB`}export{Ge as D};
2
+
@@ -0,0 +1,2 @@
1
+ import{r as d,v as Z,w as ee,x as te,y as je,j as e,z as ie,c as b,D as le,E as ve,F as ye,G as oe,H as we,I as E,J as ce,K as L,O as Ne,a as D,A as de,Q as ke,U as _,X as Ce,V as Se,W as Ae,Y as Ee,Z as Re,_ as Pe,$ as $e,a0 as Me,a1 as Fe,a2 as Oe,a3 as Ie}from"./index-BPqHA_fy.js";import{S as ne}from"./Surfaces-B__G3SGB.js";import{u as Te}from"./documentTitle-Wl8Djqre.js";const Le=Array.from({length:72},(t,n)=>n*5),A=[...le].sort((t,n)=>t.hue-n.hue),z=(t,n)=>Math.min(Math.abs(t-n),360-Math.abs(t-n));function se(t){const n=A.reduce((i,s)=>z(s.hue,t)<z(i.hue,t)?s:i);if(z(n.hue,t)<=8)return n.name;const a=A.find(i=>i.hue>t)??A[0];return`between ${A[(A.indexOf(a)-1+A.length)%A.length].name} and ${a.name}`}function De({theme:t,dark:n,hue:a,className:u}){const i=d.useRef(null);return d.useLayoutEffect(()=>{const s=i.current;s&&(a===null?ye(s):oe(a,s))},[t,n,a]),e.jsx("div",{ref:i,"data-theme":t,className:b("overflow-hidden rounded-lg border border-edge",n&&"dark",u),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 Ve({hexes:t,hue:n,active:a,onCommit:u}){const i=d.useRef(null),s=d.useRef(null),o=d.useRef(null),x=d.useRef(0),h=d.useRef(null),p=d.useRef(!1),g=d.useRef(null),c=d.useMemo(()=>{if(t.length===0)return;const m=t.map((j,w)=>`${j} ${(w/t.length*100).toFixed(2)}%`);return m.push(`${t[0]} 100%`),`linear-gradient(to right, ${m.join(", ")})`},[t]),f=d.useCallback(m=>{s.current&&(s.current.style.left=`${m/360*100}%`),o.current&&(o.current.textContent=se(m))},[]);d.useEffect(()=>{p.current||f(n)},[n,f]);const N=m=>{var P;const j=(P=i.current)==null?void 0:P.getBoundingClientRect();if(!j||j.width===0)return 0;const w=Math.min(1,Math.max(0,(m-j.left)/j.width));return Math.round(w*360)%360},y=d.useCallback(m=>{h.current=m,!x.current&&(x.current=requestAnimationFrame(()=>{x.current=0;const j=h.current;h.current=null,j!==null&&(f(j),oe(j,document.documentElement,{context:g.current??void 0,live:!0}))}))},[f]);d.useEffect(()=>()=>cancelAnimationFrame(x.current),[]);const I=m=>{p.current=!1,g.current=null,cancelAnimationFrame(x.current),x.current=0,h.current=null,u(m)};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:o,className:"text-3xs font-medium text-fg-muted","aria-hidden":!0})]}),e.jsx("div",{ref:i,role:"slider",tabIndex:0,"aria-label":"Accent colour","aria-valuemin":0,"aria-valuemax":359,"aria-valuenow":n,"aria-valuetext":a?se(n):"the theme’s own colour",onPointerDown:m=>{m.currentTarget.setPointerCapture(m.pointerId),p.current=!0,g.current=we(),y(N(m.clientX))},onPointerMove:m=>{p.current&&y(N(m.clientX))},onPointerUp:m=>{p.current&&I(N(m.clientX))},onPointerCancel:()=>{p.current&&I(n)},onKeyDown:m=>{const j=m.key==="PageUp"||m.key==="PageDown"?15:m.shiftKey?10:2;let w=null;m.key==="ArrowLeft"||m.key==="ArrowDown"||m.key==="PageDown"?w=(n-j+360)%360:m.key==="ArrowRight"||m.key==="ArrowUp"||m.key==="PageUp"?w=(n+j)%360:m.key==="Home"?w=0:m.key==="End"&&(w=359),w!==null&&(m.preventDefault(),u(w))},style:{backgroundImage:c,touchAction:"none"},className:b("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:s,"aria-hidden":!0,style:{left:`${n/360*100}%`},className:b("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",!a&&"opacity-0")})})]})}function Ue({className:t}){const[n,a]=d.useState(Z),[u,i]=d.useState(null),s=typeof document<"u"&&document.documentElement.classList.contains("dark"),o=d.useRef(n),x=d.useCallback(c=>{const f={...o.current,...c};o.current=f,a(f),i(ee(f))},[]);d.useEffect(()=>{const c=f=>i(f.detail);return window.addEventListener(te,c),i(ee(Z())),()=>window.removeEventListener(te,c)},[]);const h=d.useMemo(()=>je(Le),[n.theme,s,u==null?void 0:u.mode]),p=(u==null?void 0:u.hue)??0,g=n.hue!==null;return e.jsxs("div",{className:b("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:b("mt-2 grid gap-2","grid-cols-2 sm:grid-cols-4"),children:ie.map(c=>{const f=n.theme===c.id;return e.jsxs("button",{type:"button","aria-pressed":f,title:`${c.label} — ${c.blurb}`,onClick:()=>x({theme:c.id}),className:b("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(De,{theme:c.id,dark:s,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:c.label}),e.jsx("span",{className:"mt-0.5 line-clamp-2 text-3xs leading-snug text-fg-muted",children:c.blurb})]})]},c.id)})})]}),e.jsxs("div",{className:b("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(Ve,{hexes:h,hue:p,active:g,onCommit:c=>x({hue:c})}),e.jsxs("div",{className:"mt-2 flex flex-wrap gap-1",children:[e.jsx("button",{type:"button",onClick:()=>x({hue:null}),"aria-pressed":!g,className:b("motion-fast rounded-md border px-2 py-1 text-3xs transition-colors","focus-visible:focus-ring",g?"border-edge text-fg-muted hover:border-edge-strong hover:text-fg":"border-accent-edge bg-accent-soft text-accent"),children:"Theme’s own"}),le.map(c=>{const f=g&&Math.abs(p-c.hue)<2.5;return e.jsxs("button",{type:"button","aria-pressed":f,onClick:()=>x({hue:c.hue}),className:b("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(c.hue/5)%h.length]}}),c.name]},c.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:ve.map(c=>e.jsx("button",{type:"button","aria-pressed":n.tint===c.level,onClick:()=>x({tint:c.level}),className:b("motion-fast rounded-md px-2.5 py-1 text-3xs transition-colors","focus-visible:focus-ring",n.tint===c.level?"bg-surface-active font-medium text-fg":"text-fg-muted hover:text-fg"),children:c.label},c.level))})]})]}),e.jsx(_e,{})]})}function _e(){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(E,{variant:"primary",size:"sm",children:"Send"}),e.jsx(E,{variant:"ghost",size:"sm",children:"Cancel"}),e.jsx(E,{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(L,{tone:"success"}),e.jsx(L,{tone:"warn"}),e.jsx(L,{tone:"danger"}),e.jsx(L,{tone:"info"}),e.jsx("span",{className:"text-2xs text-fg-subtle",children:"status hues (theme’s, not yours)"})]})]})]})}const G="mx-auto w-full max-w-4xl",Be="sm:w-64";function He(){const[t,n]=d.useState(null),[a,u]=d.useState(!0),[i,s]=d.useState(null),[o,x]=d.useState({}),[h,p]=d.useState(!1),[g,c]=d.useState(null),[f,N]=d.useState(!1),[y,I]=d.useState(""),[m,j]=d.useState(!1),[w,P]=d.useState(null),q=d.useRef(null),ue=Ne("(min-width: 1024px)");d.useEffect(()=>{let r=!0;return u(!0),D.getInstanceConfig().then(l=>{r&&(n(l),s(null))}).catch(l=>r&&s(l instanceof Error?l.message:String(l))).finally(()=>r&&u(!1)),()=>{r=!1}},[]);const me=(r,l)=>{N(!1),c(null),x(v=>({...v,[r]:l}))},xe=r=>re(r,o),$=d.useMemo(()=>t?t.groups.flatMap(r=>r.fields):[],[t]),k=d.useMemo(()=>$.filter(r=>r.editable&&!r.envOverridden).filter(r=>Object.prototype.hasOwnProperty.call(o,r.key)).filter(r=>!ae(o[r.key],X(r))).map(r=>r.key),[$,o]),J=d.useMemo(()=>new Set(k),[k]),C=d.useMemo(()=>{if(!t)return[];const r=y.trim().toLowerCase(),l=[];for(const v of t.groups){const F=r!==""&&v.label.toLowerCase().includes(r),O=v.fields.filter(R=>{const T=re(R,o);return m&&ae(T,R.default)?!1:r===""||F?!0:nt(R).includes(r)});O.length>0&&l.push({...v,fields:O})}return l},[t,y,m,o]),W=C.reduce((r,l)=>r+l.fields.length,0),he=y.trim()!==""||m,B=!m&&(y.trim()===""||rt.includes(y.trim().toLowerCase())),M=d.useMemo(()=>B?[U,...C]:C,[B,C]),fe=C.some(r=>r.fields.some(l=>l.envOverridden)),H=d.useMemo(()=>{const r=$.find(l=>l.key==="profile");return typeof(r==null?void 0:r.value)=="string"?r.value:null},[$]),ge=C.some(r=>r.fields.some(l=>l.fromProfile)),Y=st(C,J);d.useEffect(()=>{const r=q.current;if(!r||M.length===0)return;const l=M.map(F=>F.id),v=()=>{const F=r.getBoundingClientRect().top;let O=l[0];for(const R of l){const T=document.getElementById(V(R));if(T)if(T.getBoundingClientRect().top-F<=80)O=R;else break}P(O)};return v(),r.addEventListener("scroll",v,{passive:!0}),()=>r.removeEventListener("scroll",v)},[M]);const Q=r=>{var v;const l=document.getElementById(V(r));(v=l==null?void 0:l.scrollIntoView)==null||v.call(l,{behavior:"smooth",block:"start"}),P(r)},pe=async()=>{if(k.length===0||!t)return;const r={};for(const l of k)r[l]=o[l];p(!0),c(null);try{await D.updateInstanceConfig(r,t.configVersion);const l=await D.getInstanceConfig();n(l),x({}),N(!0)}catch(l){if(l instanceof Re&&l.status===409)try{n(await D.getInstanceConfig())}catch{}c(l instanceof Error?l.message:String(l))}finally{p(!1)}},be=()=>{x({}),c(null),N(!1)};return e.jsxs("div",{className:"flex min-h-0 flex-1",children:[t&&e.jsx(ze,{groups:M,active:w,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:G,children:[e.jsx(Ke,{query:y,onQuery:I,modifiedOnly:m,onModifiedOnly:j,visibleCount:W,totalCount:$.length,filtering:he,autoFocus:ue}),e.jsx(Ge,{groups:M,active:w,onJump:Q,dirtyCount:Y})]})}),e.jsx("div",{ref:q,className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:G,children:[B&&e.jsx("div",{className:"mb-8 border-b border-edge pb-7",children:e.jsx(ne,{id:V(U.id),title:U.label,description:U.description,variant:"rule",first:!0,children:e.jsx(Ue,{})})}),e.jsx(Xe,{saved:f,configPath:t==null?void 0:t.configPath,restartRequired:(t==null?void 0:t.restartRequired)??!1,fileError:t==null?void 0:t.configFileError}),a&&e.jsx("p",{className:"text-sm text-fg-muted",children:"Loading…"}),i&&e.jsxs("p",{className:"text-sm text-danger",role:"alert",children:["Failed to load settings: ",i]}),t&&ge&&H&&e.jsx(qe,{name:H}),t&&fe&&e.jsx(We,{}),t&&W===0&&e.jsxs("p",{className:"py-8 text-center text-sm text-fg-muted",children:["No settings match"," ",y.trim()?e.jsxs(e.Fragment,{children:["“",e.jsx("span",{className:"font-medium",children:y.trim()}),"”"]}):"this filter","."]}),C.map(r=>e.jsx(ne,{id:V(r.id),title:r.label,description:r.description,variant:"card",flush:!0,children:e.jsx("div",{className:"divide-y divide-edge-subtle",children:r.fields.map(l=>e.jsx(Ye,{field:l,value:xe(l),dirty:J.has(l.key),profileName:H,onChange:v=>me(l.key,v)},l.key))})},r.id))]})}),t&&e.jsx("footer",{className:"border-t border-edge px-4 py-3 sm:px-6",children:e.jsxs("div",{className:`${G} flex items-center gap-3`,children:[g&&e.jsxs("span",{className:"flex min-w-0 items-center gap-1.5 text-sm text-danger",role:"alert",children:[e.jsx(de,{width:14,height:14,className:"shrink-0"}),e.jsx("span",{className:"truncate",children:g})]}),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(E,{variant:"subtle",onClick:be,disabled:k.length===0||h,children:"Reset"}),e.jsx(E,{variant:"primary",onClick:pe,disabled:k.length===0,loading:h,loadingLabel:"Saving…",children:"Save changes"})]})})]})]})}function ze({groups:t,active:n,onJump:a,dirtyCount:u}){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(i=>{const s=i.id===n,o=u[i.id]??0;return e.jsx("li",{children:e.jsxs("button",{type:"button",onClick:()=>a(i.id),"aria-current":s?"true":void 0,className:b("motion-fast flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors",s?"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:i.label}),o>0&&e.jsx("span",{className:"h-1.5 w-1.5 shrink-0 rounded-full bg-accent-solid","aria-label":`${o} unsaved`}),i.fields.length>0&&e.jsx("span",{className:"shrink-0 text-2xs tabular text-fg-subtle",children:i.fields.length})]})},i.id)})})})}function Ge({groups:t,active:n,onJump:a,dirtyCount:u}){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(i=>{const s=i.id===n,o=u[i.id]??0;return e.jsxs("button",{type:"button",onClick:()=>a(i.id),"aria-current":s?"true":void 0,className:b("motion-fast flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors",s?"bg-surface-selected font-medium text-fg":"text-fg-muted hover:bg-surface-hover hover:text-fg"),children:[i.label,o>0&&e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-accent-solid","aria-label":`${o} unsaved`})]},i.id)})})}function Ke({query:t,onQuery:n,modifiedOnly:a,onModifiedOnly:u,visibleCount:i,totalCount:s,filtering:o,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(ke,{width:14,height:14,className:"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-fg-subtle"}),e.jsx(_,{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(Ce,{width:13,height:13})})]}),e.jsx("button",{type:"button","aria-pressed":a,onClick:()=>u(!a),className:b("motion-fast shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors",a?"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:o?`${i} of ${s}`:`${s} settings`})]})}function Xe({saved:t,configPath:n,restartRequired:a,fileError:u}){const i=u?"danger":t?"success":"warn";return e.jsx(Se,{tone:i,className:"mb-5 leading-snug",icon:i==="success"?e.jsx(Ae,{width:15,height:15}):e.jsx(de,{width:15,height:15}),children:e.jsx("span",{role:"status",children:u?e.jsxs(e.Fragment,{children:[e.jsxs("strong",{children:["Could not read ",K(n),"."]})," ",u," — 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."]}):a?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Restart pending."})," ",e.jsx("code",{className:"font-mono text-xs",children:K(n)})," holds changes the running instance has not picked up — the fields below marked"," ",e.jsx(S,{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:K(n)})," and take effect only after the server restarts — the running instance keeps its current config until then."]})})})}function qe({name:t}){return e.jsxs("p",{className:"mb-3 text-xs leading-snug text-fg-muted",children:["This instance's posture profile is ",e.jsx("span",{className:"font-medium text-fg",children:t}),". Settings marked ",e.jsx(S,{tone:"neutral",children:"profile"})," are the ones it chose; anything you set yourself, here or in the environment, wins over it."," ",e.jsx("a",{className:"underline decoration-edge underline-offset-2 hover:text-fg",href:Je,target:"_blank",rel:"noreferrer",children:"What the profiles do"})]})}const Je="https://paddock.edspencer.net/configuration/profiles/";function We(){return e.jsxs("p",{className:"mb-5 text-xs leading-snug text-fg-muted",children:["Settings marked ",e.jsx(S,{tone:"neutral",children:"env"})," are overridden by an environment variable, which wins over this file — edit that variable (and restart) to change them."]})}function Ye({field:t,value:n,dirty:a,profileName:u,onChange:i}){const s=!t.editable||t.envOverridden,o=`cfg-${t.key}`,x=t.help?`${o}-help`:void 0,h=t.type==="boolean",p=d.useMemo(()=>tt(t,X(t)),[t]),g=h&&!s,c=!t.help&&!t.envOverridden&&!t.pendingRestart,f=c?"items-center":"items-start",N=c?"sm:items-center":"sm:items-start",y=s?e.jsx(et,{field:t,value:n}):h?e.jsx(Ee,{id:o,checked:!!n,onChange:i,label:t.label}):e.jsx(Ze,{field:t,value:n,onChange:i,inputId:o,describedBy:x});return e.jsxs("div",{className:"relative px-4 py-3",children:[a&&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:b(p?"":g?`flex gap-4 ${f}`:`sm:flex sm:gap-6 ${N}`),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:s?void 0:o,className:"text-sm font-medium text-fg",children:t.label}),t.sensitive&&e.jsx(S,{tone:"warn",children:"sensitive"}),t.envOverridden&&e.jsx(S,{tone:"neutral",title:`Overridden by environment variable ${t.envVar} — edit that env var (and restart) to change it.`,children:"env"}),t.fromProfile&&e.jsx(S,{tone:"neutral",title:u?`Set by the "${u}" posture profile — neither this config file nor an environment variable overrides it.`:"Set by this instance's posture profile — neither this config file nor an environment variable overrides it.",children:"profile"}),!t.editable&&!t.envOverridden&&e.jsx(S,{tone:"neutral",children:"read-only"}),t.pendingRestart&&e.jsx(S,{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:x,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:b(p?"mt-2":g?"flex shrink-0 justify-end":`mt-2 sm:mt-0 sm:shrink-0 ${Be}`),children:y})]})]})}function Qe(t){if(t==null||t==="")return"(not set)";const a=(Array.isArray(t)?t.join(", "):String(t)).replace(/\s+/g," ").trim();return a.length>60?`${a.slice(0,60)}…`:a}function S({children:t,tone:n,title:a}){return e.jsx(ce,{tone:n,size:"sm",title:a,className:"uppercase tracking-wide",children:t})}function Ze({field:t,value:n,onChange:a,inputId:u,describedBy:i}){if(t.type==="enum")return e.jsx(Pe,{id:u,"aria-describedby":i,value:String(n??""),onChange:s=>a(s.target.value),children:(t.enumValues??[]).map(s=>e.jsx("option",{value:s,children:s},s))});if(t.type==="number")return e.jsx(_,{id:u,type:"number","aria-describedby":i,className:"tabular",value:n==null?"":String(n),placeholder:t.default===null?"default":String(t.default),onChange:s=>a(s.target.value===""?null:Number(s.target.value))});if(t.type==="text"){const s=n==null||n===t.default,o=n==null?String(t.default??""):String(n),x=typeof t.default=="string"&&!s;return e.jsxs("div",{children:[e.jsx($e,{id:u,"aria-describedby":i,className:"min-h-[7rem] font-mono text-xs leading-relaxed",rows:6,spellCheck:!1,value:o,onChange:h=>a(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:s?"Using the built-in default.":n===""?"Empty — nothing will be appended.":`${o.length.toLocaleString()} characters.`}),x&&e.jsx(E,{variant:"link",size:"sm",className:"shrink-0",onClick:()=>a(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const s=Array.isArray(t.default)?t.default.map(String):null,o=Array.isArray(n)?n.map(String):null,x=o===null||s!==null&&o.length===s.length&&o.every((g,c)=>g===s[c]),h=o!==null?o.join(", "):(s==null?void 0:s.join(", "))??"",p=s!==null&&!x;return e.jsxs("div",{children:[e.jsx(_,{id:u,type:"text","aria-describedby":i,value:h,placeholder:(s==null?void 0:s.join(", "))??"",onChange:g=>a(g.target.value.split(",").map(c=>c.trim()).filter(Boolean))}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-xs text-fg-muted",children:[e.jsx("span",{children:x?"Using the built-in default.":o!==null&&o.length===0?"Empty — none offered.":`${(o==null?void 0:o.length)??0} in the list.`}),p&&e.jsx(E,{variant:"link",size:"sm",className:"shrink-0",onClick:()=>a(null),children:"Restore default"})]})]})}return e.jsx(_,{id:u,type:"text","aria-describedby":i,value:n==null?"":String(n),placeholder:t.default===null?"":String(t.default),onChange:s=>a(s.target.value)})}function et({field:t,value:n}){const a="rounded-lg bg-surface-sunken px-3 py-2 font-mono text-xs text-fg-muted";return t.type==="boolean"?e.jsx("div",{className:a,children:n?"true":"false"}):n==null||n===""?e.jsx("div",{className:a,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:b(a,"max-h-40 overflow-y-auto whitespace-pre-wrap leading-relaxed"),children:String(n)}):e.jsx("div",{className:b(a,"break-all"),children:Array.isArray(n)?n.join(", "):String(n)})}function tt(t,n){return t.type==="text"||t.type==="string-list"?!0:(Array.isArray(n)?n.join(", "):n==null?"":String(n)).length>38}function nt(t){return[t.label,t.key,t.help,t.envVar,...t.enumValues??[]].filter(Boolean).join(" ").toLowerCase()}function st(t,n){const a={};for(const u of t)a[u.id]=u.fields.filter(i=>n.has(i.key)).length;return a}const V=t=>`cfg-section-${t}`,U={id:"appearance",label:"Appearance",description:"Theme and accent colour for this browser. Applies immediately — no save, no restart.",fields:[]},rt=["appearance theme themes colour color accent tint dark light font",...ie.map(t=>t.id)].join(" ");function X(t){return t.pendingValue===void 0?t.value:t.pendingValue}function re(t,n){return Object.prototype.hasOwnProperty.call(n,t.key)?n[t.key]:X(t)}function ae(t,n){return Array.isArray(t)&&Array.isArray(n)?t.length===n.length&&t.every((a,u)=>a===n[u]):t===n}function K(t){if(!t)return"paddock.config.yaml";const n=t.split("/");return n[n.length-1]||t}function ot(){const t=Me(),n=(t==null?void 0:t.openNav)??(()=>{});return Te(["Config"]),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(Fe,{width:20,height:20})}),e.jsx(Oe,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),e.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Config"})]}),e.jsx(Ie,{}),e.jsx(He,{})]})}export{ot as InstanceConfigPage};
2
+