@edspencer/paddock 0.71.2 → 0.72.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 (106) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/app.js +27 -0
  3. package/packages/server/dist/cli/args.d.ts +56 -5
  4. package/packages/server/dist/cli/args.js +193 -31
  5. package/packages/server/dist/cli/config.d.ts +157 -0
  6. package/packages/server/dist/cli/config.js +462 -0
  7. package/packages/server/dist/cli/paddock.js +53 -3
  8. package/packages/server/dist/config.d.ts +36 -1
  9. package/packages/server/dist/config.js +59 -44
  10. package/packages/server/dist/herdctl.d.ts +45 -0
  11. package/packages/server/dist/herdctl.js +58 -4
  12. package/packages/server/dist/instance-config.d.ts +149 -21
  13. package/packages/server/dist/instance-config.js +228 -5
  14. package/packages/server/dist/last-activity.d.ts +21 -0
  15. package/packages/server/dist/last-activity.js +7 -2
  16. package/packages/server/dist/profiles.d.ts +154 -0
  17. package/packages/server/dist/profiles.js +214 -0
  18. package/packages/server/dist/routes/transcripts.d.ts +61 -0
  19. package/packages/server/dist/routes/transcripts.js +676 -0
  20. package/packages/server/dist/routes.d.ts +3 -0
  21. package/packages/server/dist/routes.js +2 -0
  22. package/packages/server/dist/transcript-move.d.ts +62 -0
  23. package/packages/server/dist/transcript-move.js +179 -0
  24. package/packages/server/dist/transcripts-migration-execute.d.ts +144 -0
  25. package/packages/server/dist/transcripts-migration-execute.js +630 -0
  26. package/packages/server/dist/transcripts-migration.d.ts +336 -0
  27. package/packages/server/dist/transcripts-migration.js +917 -0
  28. package/packages/server/dist/transcripts.d.ts +46 -2
  29. package/packages/server/dist/transcripts.js +126 -3
  30. package/packages/server/package.json +1 -1
  31. package/packages/web/dist/assets/DiscoverPage-DXI9FjmJ.js +2 -0
  32. package/packages/web/dist/assets/DiscoverView-D9oqVkT4.js +2 -0
  33. package/packages/web/dist/assets/InstanceConfigPage-DyH1j5oR.js +2 -0
  34. package/packages/web/dist/assets/{MarkdownRenderer-DA5CpdGI.js → MarkdownRenderer-D95ofnQ8.js} +1 -1
  35. package/packages/web/dist/assets/ProjectMenu-BO70tGwy.js +2 -0
  36. package/packages/web/dist/assets/{ProjectRedirect-k10uM_63.js → ProjectRedirect-BxX_aADU.js} +1 -1
  37. package/packages/web/dist/assets/ProjectView-BbCyKoh5.js +13 -0
  38. package/packages/web/dist/assets/{ProjectsGrid-DrzZ7R8M.js → ProjectsGrid-BafOHL43.js} +1 -1
  39. package/packages/web/dist/assets/RootHome-BGkMypUJ.js +2 -0
  40. package/packages/web/dist/assets/Surfaces-B8SyV4r3.js +2 -0
  41. package/packages/web/dist/assets/{arc-BKAVFR26.js → arc-DGyr--oL.js} +1 -1
  42. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-BNczNNmx.js +37 -0
  43. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-Eu4BV7Ea.js → blockDiagram-GPEHLZMM-DiTvGSrV.js} +1 -1
  44. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-DR8PZaoa.js → c4Diagram-AAUBKEIU-CKZ_jy81.js} +1 -1
  45. package/packages/web/dist/assets/channel-CjwDi1A9.js +2 -0
  46. package/packages/web/dist/assets/{chunk-2J33WTMH-CgvIIrI1.js → chunk-2J33WTMH-5o96lthu.js} +1 -1
  47. package/packages/web/dist/assets/{chunk-4BX2VUAB-CyJWrZqN.js → chunk-4BX2VUAB-38_pZfd8.js} +1 -1
  48. package/packages/web/dist/assets/{chunk-55IACEB6-DXEnZN1d.js → chunk-55IACEB6-Db73LvlE.js} +1 -1
  49. package/packages/web/dist/assets/{chunk-727SXJPM-Co89QTmp.js → chunk-727SXJPM-C-Q1-wUu.js} +1 -1
  50. package/packages/web/dist/assets/{chunk-AQP2D5EJ-BRxTzfev.js → chunk-AQP2D5EJ-ClJigITk.js} +1 -1
  51. package/packages/web/dist/assets/{chunk-FMBD7UC4-DUj18CvF.js → chunk-FMBD7UC4-BVzSX2hw.js} +1 -1
  52. package/packages/web/dist/assets/{chunk-ND2GUHAM-BQMMJobN.js → chunk-ND2GUHAM-DJlL-ELa.js} +1 -1
  53. package/packages/web/dist/assets/{chunk-QZHKN3VN-COx8LZCe.js → chunk-QZHKN3VN-BC0QEk6w.js} +1 -1
  54. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-CxiNtY3i.js +2 -0
  55. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-CxiNtY3i.js +2 -0
  56. package/packages/web/dist/assets/{core-DnpcT0s6.js → core-VUdoTe9w.js} +1 -1
  57. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-DVqArcBy.js +2 -0
  58. package/packages/web/dist/assets/{dagre-BM42HDAG-CuaYJexd.js → dagre-BM42HDAG-BgFGyUdv.js} +1 -1
  59. package/packages/web/dist/assets/{diagram-2AECGRRQ-C9_anxrA.js → diagram-2AECGRRQ-BqdZmJt3.js} +1 -1
  60. package/packages/web/dist/assets/{diagram-5GNKFQAL-Ckf3kFlY.js → diagram-5GNKFQAL-DeLPu8jT.js} +1 -1
  61. package/packages/web/dist/assets/{diagram-KO2AKTUF-BrpUuY5x.js → diagram-KO2AKTUF-BqTiTGay.js} +1 -1
  62. package/packages/web/dist/assets/{diagram-LMA3HP47-B9lyXZXW.js → diagram-LMA3HP47-CbpAvCgl.js} +1 -1
  63. package/packages/web/dist/assets/{diagram-OG6HWLK6-DT7ro32m.js → diagram-OG6HWLK6-C4h0V97s.js} +1 -1
  64. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ZZn9bHQh.js → erDiagram-TEJ5UH35-Dwz77uRx.js} +1 -1
  65. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-BxQ82PY_.js → flowDiagram-I6XJVG4X-DSikJnZR.js} +1 -1
  66. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-DkAGNZb3.js +293 -0
  67. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BlwODdH9.js → gitGraphDiagram-PVQCEYII-BQtzt9hP.js} +1 -1
  68. package/packages/web/dist/assets/index-BN3rLMpD.js +71 -0
  69. package/packages/web/dist/assets/index-Dfzb1DvL.css +1 -0
  70. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-nanSBFyG.js → infoDiagram-5YYISTIA-YcaBUjeC.js} +1 -1
  71. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-yZHr_s28.js → ishikawaDiagram-YF4QCWOH-BfLZB88u.js} +1 -1
  72. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BKjDqQEQ.js → journeyDiagram-JHISSGLW-DwaFhd6C.js} +1 -1
  73. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-QusvHqc6.js → kanban-definition-UN3LZRKU-e9P3WxsD.js} +1 -1
  74. package/packages/web/dist/assets/{linear-4xRoD38Z.js → linear-CsXS5LTz.js} +1 -1
  75. package/packages/web/dist/assets/{mermaid.core-CeulYvDv.js → mermaid.core-DsA72Qyi.js} +4 -4
  76. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-DSjYt2Kp.js → mindmap-definition-RKZ34NQL-DyLOITDI.js} +1 -1
  77. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-Ci0gO3CA.js → pieDiagram-4H26LBE5-C36aM3D-.js} +1 -1
  78. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-y2qHkaHP.js → quadrantDiagram-W4KKPZXB-Cq1zTk6a.js} +1 -1
  79. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-D9hiWbad.js → requirementDiagram-4Y6WPE33-B6MctUk7.js} +1 -1
  80. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-DA3Mvoze.js → sankeyDiagram-5OEKKPKP-CaNRUoPR.js} +1 -1
  81. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DWACrljw.js → sequenceDiagram-3UESZ5HK-DDj_tZiQ.js} +1 -1
  82. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CFGEhoTb.js → stateDiagram-AJRCARHV-DDaMlLyK.js} +1 -1
  83. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bu6Swk2T.js +2 -0
  84. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-Cjygpd9e.js → timeline-definition-PNZ67QCA-bMP-7Wam.js} +1 -1
  85. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-zQnLaxgv.js → vennDiagram-CIIHVFJN-9nzKgc0Y.js} +1 -1
  86. package/packages/web/dist/assets/{wardley-L42UT6IY-DGmt-Lo2.js → wardley-L42UT6IY-DIWbAFW5.js} +1 -1
  87. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-Dj3vDukN.js → wardleyDiagram-YWT4CUSO-CVL3skbs.js} +1 -1
  88. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-C0bGbwbL.js → xychartDiagram-2RQKCTM6-DHoVPvll.js} +1 -1
  89. package/packages/web/dist/index.html +2 -2
  90. package/packages/web/dist/sw.js +1 -1
  91. package/packages/web/dist/assets/Chip-Cp32dDmz.js +0 -2
  92. package/packages/web/dist/assets/DiscoverPage-Cs439wTf.js +0 -2
  93. package/packages/web/dist/assets/DiscoverView-DoGAlUXT.js +0 -2
  94. package/packages/web/dist/assets/InstanceConfigPage-BDypeo_r.js +0 -2
  95. package/packages/web/dist/assets/ProjectMenu-DXXXtXeh.js +0 -2
  96. package/packages/web/dist/assets/ProjectView-BxjcMlSO.js +0 -13
  97. package/packages/web/dist/assets/RootHome-rWodzOC8.js +0 -2
  98. package/packages/web/dist/assets/architectureDiagram-3BPJPVTR-UzIlxaCQ.js +0 -37
  99. package/packages/web/dist/assets/channel-C-sodtn7.js +0 -2
  100. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-D2eBPVdq.js +0 -2
  101. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-D2eBPVdq.js +0 -2
  102. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-B-4NadKI.js +0 -2
  103. package/packages/web/dist/assets/ganttDiagram-6RSMTGT7-BxliloOH.js +0 -293
  104. package/packages/web/dist/assets/index-Cx9y6c3f.js +0 -71
  105. package/packages/web/dist/assets/index-bC5uCujx.css +0 -1
  106. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-CLfbDXLX.js +0 -2
@@ -55,6 +55,31 @@ export interface ClaudeHomeTarget {
55
55
  /** The user's own `~/.claude` — the store under `transcripts: "host"`. */
56
56
  userHome: string;
57
57
  }
58
+ /**
59
+ * A `.chats` symlink left behind by a `transcripts: host` period, removed by a
60
+ * boot that is now running `own`. See {@link unplantChatsDir}.
61
+ */
62
+ export interface UnplantedChatsLink {
63
+ /** The `.chats` path that was a symlink and is now a real, empty directory. */
64
+ chatsDir: string;
65
+ /**
66
+ * What it pointed at, resolved — normally `~/.claude/projects/<encoded-cwd>`.
67
+ * Reported, never touched: the transcripts written during the `host` period
68
+ * are still sitting there, exactly as they were.
69
+ */
70
+ target: string;
71
+ }
72
+ /**
73
+ * Is this project's `.chats` still a planted symlink? The corrupted-state
74
+ * predicate {@link unplantChatsDir} exists to clear (#708).
75
+ *
76
+ * Split out because `deleteSession` needs it as a last-resort guard: the healing
77
+ * above is best-effort by design (`ensureProjectChats` swallows its own errors so
78
+ * a layout problem can never block chat), and the one operation that must not run
79
+ * against an unhealed layout is the `rm`. `lstat` only — this never follows the
80
+ * link, so it cannot touch `~/.claude` even when that is where the link goes.
81
+ */
82
+ export declare function chatsDirIsPlanted(chatsHostDir: string): Promise<boolean>;
58
83
  /**
59
84
  * Ensure the project's transcript store exists and that Claude Code's encoded
60
85
  * transcript path for `workingDir` is a symlink pointing at it (migrating an
@@ -82,9 +107,28 @@ export interface ClaudeHomeTarget {
82
107
  * meaning is "share the host's transcripts". #682 was the opposite: planting a
83
108
  * link that silently REDIRECTED the user's future sessions into paddock's store
84
109
  * on an instance that had asked for no such thing. Under `own` — the default —
85
- * nothing under `~/.claude` is created, read or written at all.
110
+ * nothing under `~/.claude` is created or written at all. Since #708 that holds
111
+ * even when the instance USED to run `host`: see {@link unplantChatsDir}.
112
+ *
113
+ * The `own → host` migration (#882) is the one exception under `own`, in both
114
+ * directions, and it happens only on an explicit `POST`:
115
+ *
116
+ * - `transcripts-migration.ts` READS the store, because a net-new chat and one
117
+ * that has diverged from a CLI original are indistinguishable without
118
+ * comparing the two;
119
+ * - `transcripts-migration-execute.ts` WRITES it, moving the transcripts in —
120
+ * which is the operation the user asked for. It never deletes anything and
121
+ * never overwrites a file in place: where paddock's copy supersedes the
122
+ * user's, the user's is moved to `<projectDir>/.chats-pre-migration/` first.
123
+ *
124
+ * Both are behind their own endpoint precisely so they are something the user
125
+ * asks for, rather than something a background poll does silently.
126
+ *
127
+ * Returns the stale `.chats` symlink it had to unplant, so the caller can tell
128
+ * the operator where their host-era transcripts still are, or `null` — the
129
+ * overwhelmingly common case — when the layout already agreed with the mode.
86
130
  */
87
- export declare function ensureProjectChats(workingDir: string, chatsHostDir: string, home: ClaudeHomeTarget): Promise<void>;
131
+ export declare function ensureProjectChats(workingDir: string, chatsHostDir: string, home: ClaudeHomeTarget): Promise<UnplantedChatsLink | null>;
88
132
  /**
89
133
  * Read a session's FIRST user message text, untruncated, straight from its
90
134
  * transcript JSONL (issue #62). Claude Code's own `preview` is capped at 100
@@ -112,6 +112,103 @@ async function pointChatsDirAt(chatsDir, store) {
112
112
  }
113
113
  await fs.symlink(store, chatsDir);
114
114
  }
115
+ /**
116
+ * The `own`-mode inverse of {@link pointChatsDirAt}: unplant a stale `.chats`
117
+ * symlink and put a real, empty directory back in its place (#708).
118
+ *
119
+ * ## Why this has to exist
120
+ *
121
+ * `pointChatsDirAt` is only ever called under `host`, so before #708 nothing
122
+ * un-planted the link. Flip an instance back to `own` and you keep a two-hop
123
+ * chain into the user's real Claude home:
124
+ *
125
+ * <projectDir>/.chats SYMLINK -> ~/.claude/projects/<enc>
126
+ * <claudeHome>/projects/<enc> SYMLINK -> <projectDir>/.chats
127
+ *
128
+ * `mkdir(store, {recursive:true})` on a symlink-to-an-existing-dir is a silent
129
+ * no-op, which is why three consecutive `own` boots did not heal it. The
130
+ * consequences were verified in a sandbox, not inferred: new chats written under
131
+ * `own` landed in the user's real folder (so `own` stopped isolating at all), and
132
+ * `deleteSession`'s `own` branch — an `rm` of `<claudeHome>/projects/<enc>/<id>.jsonl`
133
+ * — resolved through both hops and unlinked the user's actual terminal history.
134
+ * That is #682's destruction class re-entered through a config flip.
135
+ *
136
+ * ## Why it only unplants, and does not migrate
137
+ *
138
+ * The obvious richer behaviour is to COPY the host folder's transcripts into the
139
+ * new real `.chats` so they stay visible in paddock. Rejected, for three reasons
140
+ * that compound:
141
+ *
142
+ * 1. **It cannot be done without breaking the promise `own` exists to make.**
143
+ * Copying means reading `~/.claude`. The module doc above and
144
+ * `website/…/what-paddock-touches.md` both state that under `own` nothing
145
+ * there is read or written — and the issue this fixes cites the falsification
146
+ * of exactly that line as the bug. A fix whose first act is to falsify it
147
+ * again is not a fix. So this function never touches `target`: it `readlink`s
148
+ * the name for the operator notice and stops. (Deliberately no file count in
149
+ * {@link UnplantedChatsLink} for the same reason — a `readdir` is a read.)
150
+ * The migration preview (#882) is the one read those two documents now
151
+ * admit, and it does not weaken this: it happens because a user asked for
152
+ * it, on a request, not silently at boot. That distinction — asked-for
153
+ * versus automatic — is the whole of what makes one acceptable and this
154
+ * one not.
155
+ * 2. **The host folder is not paddock's to sweep up.** It holds whatever the
156
+ * user ran `claude` on in that directory, including chats they only ever had
157
+ * in a terminal and never in paddock. Copy-everything drags private terminal
158
+ * history into a paddock project; copy-some needs a heuristic to tell
159
+ * paddock's chats from the user's, and `deleteSession` already declines to
160
+ * make that same distinction ("distinguishing them well enough to risk an
161
+ * `rm` is not worth the failure mode of getting it wrong").
162
+ * 3. **Paddock already has this feature, and it is user-driven.** Adoption /
163
+ * `import-chats` copies selected transcripts out of a Claude home into a
164
+ * project (`mode: "copy"`), with the user choosing which. Doing it silently
165
+ * at boot converts a deliberate gesture into an automatic one, and creates
166
+ * the same session id in two stores that then diverge the moment the user
167
+ * resumes one in a terminal.
168
+ *
169
+ * So the split this leaves — host-era chats still on disk but no longer in
170
+ * paddock's list — is real, and is reported by name at boot rather than papered
171
+ * over. It is NOT fully closable by import today, and the boot notice says so:
172
+ * `listAdoptableSessions` excludes any session a run record is attributed to,
173
+ * which is every chat paddock itself drove during the `host` period, so import
174
+ * offers the user's own terminal sessions and not paddock's. Measured on a
175
+ * flipped instance, not assumed. Closing that remainder means rewriting run
176
+ * records as well as moving files — i.e. a migration, which is #882.
177
+ *
178
+ * Refusing to boot instead was also rejected: it leaves the dangerous symlink in
179
+ * place, so the isolation breach and the destructive delete both survive the
180
+ * refusal — it fixes nothing it warns about. And this whole module is built
181
+ * never to fail a boot over transcript layout.
182
+ *
183
+ * Only symlinks are touched. A `.chats` that is already a real directory is the
184
+ * correct `own` shape and is left alone.
185
+ */
186
+ async function unplantChatsDir(chatsDir) {
187
+ const st = await fs.lstat(chatsDir).catch(() => null);
188
+ if (!st?.isSymbolicLink())
189
+ return null;
190
+ const raw = await fs.readlink(chatsDir).catch(() => "");
191
+ const target = path.resolve(path.dirname(chatsDir), raw);
192
+ // `rm` on a symlink removes the LINK, never what it points at — the whole
193
+ // reason this is safe to do unconditionally.
194
+ await fs.rm(chatsDir, { force: true });
195
+ await fs.mkdir(chatsDir, { recursive: true });
196
+ return { chatsDir, target };
197
+ }
198
+ /**
199
+ * Is this project's `.chats` still a planted symlink? The corrupted-state
200
+ * predicate {@link unplantChatsDir} exists to clear (#708).
201
+ *
202
+ * Split out because `deleteSession` needs it as a last-resort guard: the healing
203
+ * above is best-effort by design (`ensureProjectChats` swallows its own errors so
204
+ * a layout problem can never block chat), and the one operation that must not run
205
+ * against an unhealed layout is the `rm`. `lstat` only — this never follows the
206
+ * link, so it cannot touch `~/.claude` even when that is where the link goes.
207
+ */
208
+ export async function chatsDirIsPlanted(chatsHostDir) {
209
+ const st = await fs.lstat(projectChatsDir(chatsHostDir)).catch(() => null);
210
+ return st?.isSymbolicLink() === true;
211
+ }
115
212
  /**
116
213
  * Ensure the project's transcript store exists and that Claude Code's encoded
117
214
  * transcript path for `workingDir` is a symlink pointing at it (migrating an
@@ -139,7 +236,26 @@ async function pointChatsDirAt(chatsDir, store) {
139
236
  * meaning is "share the host's transcripts". #682 was the opposite: planting a
140
237
  * link that silently REDIRECTED the user's future sessions into paddock's store
141
238
  * on an instance that had asked for no such thing. Under `own` — the default —
142
- * nothing under `~/.claude` is created, read or written at all.
239
+ * nothing under `~/.claude` is created or written at all. Since #708 that holds
240
+ * even when the instance USED to run `host`: see {@link unplantChatsDir}.
241
+ *
242
+ * The `own → host` migration (#882) is the one exception under `own`, in both
243
+ * directions, and it happens only on an explicit `POST`:
244
+ *
245
+ * - `transcripts-migration.ts` READS the store, because a net-new chat and one
246
+ * that has diverged from a CLI original are indistinguishable without
247
+ * comparing the two;
248
+ * - `transcripts-migration-execute.ts` WRITES it, moving the transcripts in —
249
+ * which is the operation the user asked for. It never deletes anything and
250
+ * never overwrites a file in place: where paddock's copy supersedes the
251
+ * user's, the user's is moved to `<projectDir>/.chats-pre-migration/` first.
252
+ *
253
+ * Both are behind their own endpoint precisely so they are something the user
254
+ * asks for, rather than something a background poll does silently.
255
+ *
256
+ * Returns the stale `.chats` symlink it had to unplant, so the caller can tell
257
+ * the operator where their host-era transcripts still are, or `null` — the
258
+ * overwhelmingly common case — when the layout already agreed with the mode.
143
259
  */
144
260
  export async function ensureProjectChats(workingDir, chatsHostDir, home) {
145
261
  try {
@@ -147,6 +263,11 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
147
263
  const store = home.transcripts === "host"
148
264
  ? encodedTranscriptDir(home.userHome, workingDir)
149
265
  : chatsDir;
266
+ // BEFORE the mkdir, which is the ordering the bug turned on: under `own` the
267
+ // store IS `.chats`, and `mkdir(recursive)` over a symlink-to-a-real-dir
268
+ // succeeds without doing anything — which is why the stale link survived
269
+ // every subsequent boot rather than being healed by one.
270
+ const unplanted = home.transcripts === "own" ? await unplantChatsDir(chatsDir) : null;
150
271
  await fs.mkdir(store, { recursive: true });
151
272
  if (home.transcripts === "host")
152
273
  await pointChatsDirAt(chatsDir, store);
@@ -162,7 +283,7 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
162
283
  await fs.rm(encoded, { force: true });
163
284
  await fs.symlink(store, encoded);
164
285
  }
165
- return;
286
+ return unplanted;
166
287
  }
167
288
  // A real directory of existing transcripts — migrate into the store, then
168
289
  // link. This is inside a home paddock owns, so such a directory can only be
@@ -189,13 +310,15 @@ export async function ensureProjectChats(workingDir, chatsHostDir, home) {
189
310
  }
190
311
  await fs.rmdir(encoded).catch(() => undefined);
191
312
  await fs.symlink(store, encoded);
192
- return;
313
+ return unplanted;
193
314
  }
194
315
  // Nothing there yet — just create the symlink so future turns land in the store.
195
316
  await fs.symlink(store, encoded);
317
+ return unplanted;
196
318
  }
197
319
  catch {
198
320
  /* non-fatal: fall back to Claude Code's default location for this project */
321
+ return null;
199
322
  }
200
323
  }
201
324
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.71.2",
3
+ "version": "0.72.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -0,0 +1,2 @@
1
+ import{j as r}from"./index-BN3rLMpD.js";import{D as o}from"./DiscoverView-D9oqVkT4.js";import"./Surfaces-B8SyV4r3.js";function s(){return r.jsx(o,{})}export{s as DiscoverPage};
2
+
@@ -0,0 +1,2 @@
1
+ import{a as oe,u as ie,V as de,r as u,b as A,j as s,J as X,G as Y,a2 as Z,z as $,W as ce,H as le,P as he,a3 as ue,a4 as J,D,l as K,a5 as pe,h as xe,M as W}from"./index-BN3rLMpD.js";import{E as R,C as me}from"./Surfaces-B8SyV4r3.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]??z(),r),0)}function be(e,t){return t.filter(n=>C(e[n.path]??z(),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 T(e,t,n){const r=e[t];return r?{...e,[t]:{...r,...n}}:e}function Ne(e,t){const n=e??z();return C(n,t)===0?null:n.sessions&&n.selected?{sessionIds:[...n.selected]}:{}}function z(){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:q(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:q(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 _(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=_(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=_(e.skipped),c=`Adopted ${r} chat${r===1?"":"s"} into “${n}”`;return a?`${c} — ${a}.`:`${c}.`}function q(e){return e instanceof Error?e.message:String(e)}function He({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,O]=u.useState(null),[G,P]=u.useState(!0),[f,v]=u.useState({}),[k,H]=u.useState(!1),[L,F]=u.useState({}),[E,B]=u.useState({}),[S,ee]=u.useState(!1);u.useEffect(()=>{let o=!0;return P(!0),O(null),A.discover({includeNonGit:m,includeOutsideHome:j}).then(i=>{o&&(x(i),v(ge(i.candidates)),P(!1))}).catch(i=>{o&&(O(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=T(p,o.path,{expanded:!g.expanded});return g.expanded||g.sessions||g.loading?N:T(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=>T(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;H(!0),F(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)=>{F(N=>({...N,[i]:p})),g&&B(N=>({...N,[i]:g}))}),H(!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(De,{imported:ae,projects:re,onGetStarted:ne}),y!==null&&s.jsx(X,{tone:"danger",icon:s.jsx(Y,{width:16,height:16}),children:y}),G&&s.jsx(Te,{}),!G&&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(D,{tone:"lineage",size:"sm",children:e.gitRemote}),!e.hasGit&&s.jsx(D,{tone:"neutral",size:"sm",children:"no git"}),!e.insideHome&&s.jsx(D,{tone:"neutral",size:"sm",children:"outside home"})]}),e.recordedPath!==void 0&&s.jsx(X,{tone:"warn",className:"mt-2",icon:s.jsx(Y,{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)," · ",ze(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(W,{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(W,{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 De({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 Te(){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 ze(e){return e<1024?`${e} B`:e<1024*1024?`${Math.round(e/1024)} kB`:`${(e/(1024*1024)).toFixed(1)} MB`}export{He as D};
2
+
@@ -0,0 +1,2 @@
1
+ import{r as d,n as Z,s as ee,A as te,p as je,j as e,q as ie,l as b,t as le,v as ve,w as ye,x as oe,y as we,z as E,D as ce,E as L,F as Ne,b as D,G as de,H as ke,I as B,X as Se,J as Ce,K as Ae,M as Ee,O as Re,Q as Pe,U as Me,V as $e,W as Fe,Y as Oe,Z as Ie}from"./index-BN3rLMpD.js";import{S as ne}from"./Surfaces-B8SyV4r3.js";const Te=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 Le({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 De({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 Ve({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(Te),[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(Le,{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(De,{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(Ue,{})]})}function Ue(){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 _e(){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),X=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),M=d.useMemo(()=>t?t.groups.flatMap(r=>r.fields):[],[t]),k=d.useMemo(()=>M.filter(r=>r.editable&&!r.envOverridden).filter(r=>Object.prototype.hasOwnProperty.call(o,r.key)).filter(r=>!ae(o[r.key],q(r))).map(r=>r.key),[M,o]),J=d.useMemo(()=>new Set(k),[k]),S=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:tt(R).includes(r)});O.length>0&&l.push({...v,fields:O})}return l},[t,y,m,o]),W=S.reduce((r,l)=>r+l.fields.length,0),he=y.trim()!==""||m,_=!m&&(y.trim()===""||st.includes(y.trim().toLowerCase())),$=d.useMemo(()=>_?[U,...S]:S,[_,S]),fe=S.some(r=>r.fields.some(l=>l.envOverridden)),H=d.useMemo(()=>{const r=M.find(l=>l.key==="profile");return typeof(r==null?void 0:r.value)=="string"?r.value:null},[M]),ge=S.some(r=>r.fields.some(l=>l.fromProfile)),Y=nt(S,J);d.useEffect(()=>{const r=X.current;if(!r||$.length===0)return;const l=$.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)},[$]);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(He,{groups:$,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(Ge,{query:y,onQuery:I,modifiedOnly:m,onModifiedOnly:j,visibleCount:W,totalCount:M.length,filtering:he,autoFocus:ue}),e.jsx(ze,{groups:$,active:w,onJump:Q,dirtyCount:Y})]})}),e.jsx("div",{ref:X,className:"min-h-0 flex-1 overflow-y-auto px-4 py-5 sm:px-6",children:e.jsxs("div",{className:G,children:[_&&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(Ve,{})})}),e.jsx(Ke,{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(Je,{}),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","."]}),S.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(We,{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 He({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 ze({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 Ge({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(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(Se,{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 Ke({saved:t,configPath:n,restartRequired:a,fileError:u}){const i=u?"danger":t?"success":"warn";return e.jsx(Ce,{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(C,{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(C,{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:Xe,target:"_blank",rel:"noreferrer",children:"What the profiles do"})]})}const Xe="https://paddock.edspencer.net/configuration/profiles/";function Je(){return e.jsxs("p",{className:"mb-5 text-xs leading-snug text-fg-muted",children:["Settings marked ",e.jsx(C,{tone:"neutral",children:"env"})," are overridden by an environment variable, which wins over this file — edit that variable (and restart) to change them."]})}function We({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(()=>et(t,q(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(Ze,{field:t,value:n}):h?e.jsx(Ee,{id:o,checked:!!n,onChange:i,label:t.label}):e.jsx(Qe,{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(C,{tone:"warn",children:"sensitive"}),t.envOverridden&&e.jsx(C,{tone:"neutral",title:`Overridden by environment variable ${t.envVar} — edit that env var (and restart) to change it.`,children:"env"}),t.fromProfile&&e.jsx(C,{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(C,{tone:"neutral",children:"read-only"}),t.pendingRestart&&e.jsx(C,{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:Ye(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 Ye(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 C({children:t,tone:n,title:a}){return e.jsx(ce,{tone:n,size:"sm",title:a,className:"uppercase tracking-wide",children:t})}function Qe({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(B,{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(Me,{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(B,{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(B,{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 Ze({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 et(t,n){return t.type==="text"||t.type==="string-list"?!0:(Array.isArray(n)?n.join(", "):n==null?"":String(n)).length>38}function tt(t){return[t.label,t.key,t.help,t.envVar,...t.enumValues??[]].filter(Boolean).join(" ").toLowerCase()}function nt(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:[]},st=["appearance theme themes colour color accent tint dark light font",...ie.map(t=>t.id)].join(" ");function q(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]:q(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 it(){const t=$e(),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(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(_e,{})]})}export{it as InstanceConfigPage};
2
+