@edspencer/paddock 0.66.1 → 0.67.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 (105) hide show
  1. package/package.json +1 -1
  2. package/packages/server/dist/herdctl-jobs.d.ts +20 -0
  3. package/packages/server/dist/herdctl-jobs.js +94 -0
  4. package/packages/server/dist/herdctl.d.ts +46 -0
  5. package/packages/server/dist/herdctl.js +58 -0
  6. package/packages/server/dist/instance-config.d.ts +56 -5
  7. package/packages/server/dist/instance-config.js +206 -24
  8. package/packages/server/dist/project-paths.d.ts +46 -1
  9. package/packages/server/dist/project-paths.js +66 -2
  10. package/packages/server/dist/project-types.d.ts +23 -0
  11. package/packages/server/dist/project-types.js +35 -0
  12. package/packages/server/dist/projects.d.ts +5 -2
  13. package/packages/server/dist/projects.js +71 -17
  14. package/packages/server/dist/queued-message.d.ts +32 -2
  15. package/packages/server/dist/queued-message.js +87 -8
  16. package/packages/server/dist/routes/chats.js +41 -10
  17. package/packages/server/dist/routes/meta.js +32 -5
  18. package/packages/server/dist/routes/projects.js +71 -7
  19. package/packages/server/dist/session-hub.d.ts +10 -0
  20. package/packages/server/dist/session-hub.js +16 -2
  21. package/packages/server/dist/ws-protocol.d.ts +67 -0
  22. package/packages/server/dist/ws-protocol.js +28 -13
  23. package/packages/server/dist/ws.js +90 -24
  24. package/packages/server/package.json +1 -1
  25. package/packages/web/dist/assets/InstanceConfigPage-sDbCoFxC.js +2 -0
  26. package/packages/web/dist/assets/{MarkdownRenderer-CRQP3Cfe.js → MarkdownRenderer-MaSe5aAI.js} +11 -11
  27. package/packages/web/dist/assets/ProjectMenu-DmWpr9od.js +2 -0
  28. package/packages/web/dist/assets/ProjectRedirect-BJeDERUd.js +2 -0
  29. package/packages/web/dist/assets/ProjectView-Bbecs1u7.js +13 -0
  30. package/packages/web/dist/assets/ProjectsGrid-BE4cAtu5.js +2 -0
  31. package/packages/web/dist/assets/Surfaces-DtSsbnLh.js +2 -0
  32. package/packages/web/dist/assets/{arc-DqMSW0As.js → arc-NLb6J_GZ.js} +1 -1
  33. package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CNkQD1jU.js → architectureDiagram-3BPJPVTR-BbiSIctn.js} +1 -1
  34. package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BCMiOmt6.js → blockDiagram-GPEHLZMM-Br1sRzRE.js} +1 -1
  35. package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CU2soCji.js → c4Diagram-AAUBKEIU-DUvJurko.js} +1 -1
  36. package/packages/web/dist/assets/channel-bezC0w5b.js +2 -0
  37. package/packages/web/dist/assets/{chunk-2J33WTMH-nKhlJNTt.js → chunk-2J33WTMH-Cm_E_LAD.js} +1 -1
  38. package/packages/web/dist/assets/{chunk-4BX2VUAB-YRTZj9zI.js → chunk-4BX2VUAB-CK6R2MCN.js} +1 -1
  39. package/packages/web/dist/assets/{chunk-55IACEB6-HO60VE45.js → chunk-55IACEB6-D7HXvxGy.js} +1 -1
  40. package/packages/web/dist/assets/{chunk-727SXJPM-WZJH-boe.js → chunk-727SXJPM-BLwevzlY.js} +1 -1
  41. package/packages/web/dist/assets/{chunk-AQP2D5EJ-PvDC7gYB.js → chunk-AQP2D5EJ-CpLWD5so.js} +1 -1
  42. package/packages/web/dist/assets/{chunk-FMBD7UC4-BNRMTplH.js → chunk-FMBD7UC4-BNlt1t19.js} +1 -1
  43. package/packages/web/dist/assets/{chunk-ND2GUHAM-2Q2ei8TR.js → chunk-ND2GUHAM-CIt5gAq8.js} +1 -1
  44. package/packages/web/dist/assets/{chunk-QZHKN3VN-CG9VV7M6.js → chunk-QZHKN3VN-B1uwIQ9c.js} +1 -1
  45. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DMgrTmQU.js +2 -0
  46. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DMgrTmQU.js +2 -0
  47. package/packages/web/dist/assets/{core-DwSEw_oE.js → core-HXUN76Po.js} +1 -1
  48. package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BlMrBI7K.js → cose-bilkent-S5V4N54A-DJ4_5jxB.js} +1 -1
  49. package/packages/web/dist/assets/{dagre-BM42HDAG-tNoaleNU.js → dagre-BM42HDAG-Bc9-haAu.js} +1 -1
  50. package/packages/web/dist/assets/{diagram-2AECGRRQ-DRVCTpHL.js → diagram-2AECGRRQ-nLOtPDzu.js} +1 -1
  51. package/packages/web/dist/assets/{diagram-5GNKFQAL-BdS-96WV.js → diagram-5GNKFQAL-DThV84R3.js} +1 -1
  52. package/packages/web/dist/assets/{diagram-KO2AKTUF-Dv1yxz3s.js → diagram-KO2AKTUF-C5hcR1vA.js} +1 -1
  53. package/packages/web/dist/assets/{diagram-LMA3HP47-BNXgifuS.js → diagram-LMA3HP47-BEFzrSHy.js} +1 -1
  54. package/packages/web/dist/assets/{diagram-OG6HWLK6-wlO0bxwP.js → diagram-OG6HWLK6-BY5lPsX7.js} +1 -1
  55. package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ChoppAEq.js → erDiagram-TEJ5UH35-y_kB8ETg.js} +1 -1
  56. package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-C__aYyei.js → flowDiagram-I6XJVG4X-Cuhha5cF.js} +1 -1
  57. package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CjRe8foW.js → ganttDiagram-6RSMTGT7-DFJpciK5.js} +1 -1
  58. package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BGDQjiWq.js → gitGraphDiagram-PVQCEYII-FoA0yuh-.js} +1 -1
  59. package/packages/web/dist/assets/index-BxLurJvz.css +1 -0
  60. package/packages/web/dist/assets/index-C_PXsGTD.js +71 -0
  61. package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BP-JaX-8.js → infoDiagram-5YYISTIA-CKYqD8-c.js} +1 -1
  62. package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-YnsAJBvC.js → ishikawaDiagram-YF4QCWOH-CCIxHIBw.js} +1 -1
  63. package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BM6JWTV9.js → journeyDiagram-JHISSGLW-lGYoSyk0.js} +1 -1
  64. package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-gR2O0IaL.js → kanban-definition-UN3LZRKU-B3D7CIwy.js} +1 -1
  65. package/packages/web/dist/assets/{linear-Dolq_eLR.js → linear-BiplJBex.js} +1 -1
  66. package/packages/web/dist/assets/{mermaid.core-D1T_GQW7.js → mermaid.core-BJ7aEinZ.js} +4 -4
  67. package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-C3bzIyLZ.js → mindmap-definition-RKZ34NQL-BiXcOtHo.js} +1 -1
  68. package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BrZ3ANGf.js → pieDiagram-4H26LBE5-tnkj45YF.js} +1 -1
  69. package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-BvLCHAEi.js → quadrantDiagram-W4KKPZXB-LqReq3Gl.js} +1 -1
  70. package/packages/web/dist/assets/queued-CagZVfWU.js +2 -0
  71. package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CCzXAeaZ.js → requirementDiagram-4Y6WPE33-C1uk9Jty.js} +1 -1
  72. package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-2Gf-ERTg.js → sankeyDiagram-5OEKKPKP-eQQ3OKrV.js} +1 -1
  73. package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DMjDe28d.js → sequenceDiagram-3UESZ5HK-C6-a0lH4.js} +1 -1
  74. package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CENtj5dw.js → stateDiagram-AJRCARHV-CsTOkKqz.js} +1 -1
  75. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bsc-VlVI.js +2 -0
  76. package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-BW4gCpBB.js → timeline-definition-PNZ67QCA-DUBXH1x-.js} +1 -1
  77. package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CiIuqcT9.js → vennDiagram-CIIHVFJN-CcQrNnou.js} +1 -1
  78. package/packages/web/dist/assets/{wardley-L42UT6IY-BQ1BPr5R.js → wardley-L42UT6IY-Br823uUV.js} +1 -1
  79. package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DBq9Ailv.js → wardleyDiagram-YWT4CUSO-DRBECQc_.js} +1 -1
  80. package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-D24VDECy.js → xychartDiagram-2RQKCTM6--HtSFP24.js} +1 -1
  81. package/packages/web/dist/fonts/alegreya-latin-italic.woff2 +0 -0
  82. package/packages/web/dist/fonts/alegreya-latin.woff2 +0 -0
  83. package/packages/web/dist/fonts/alegreya-sc-latin-500.woff2 +0 -0
  84. package/packages/web/dist/fonts/alegreya-sc-latin-700.woff2 +0 -0
  85. package/packages/web/dist/fonts/courier-prime-latin-400.woff2 +0 -0
  86. package/packages/web/dist/fonts/courier-prime-latin-700.woff2 +0 -0
  87. package/packages/web/dist/fonts/courier-prime-latin-italic.woff2 +0 -0
  88. package/packages/web/dist/fonts/iosevka-terminal-400.woff2 +0 -0
  89. package/packages/web/dist/fonts/iosevka-terminal-700.woff2 +0 -0
  90. package/packages/web/dist/fonts/martian-mono-latin.woff2 +0 -0
  91. package/packages/web/dist/fonts/saira-latin.woff2 +0 -0
  92. package/packages/web/dist/index.html +61 -12
  93. package/packages/web/dist/sw.js +1 -1
  94. package/packages/web/dist/assets/InstanceConfigPage-1BzMnJPo.js +0 -2
  95. package/packages/web/dist/assets/ProjectRedirect-Dd3h5V8h.js +0 -2
  96. package/packages/web/dist/assets/ProjectView-BRk6l152.js +0 -13
  97. package/packages/web/dist/assets/ProjectsGrid-BaUE8zaF.js +0 -2
  98. package/packages/web/dist/assets/channel-Ynyq4GPh.js +0 -2
  99. package/packages/web/dist/assets/classDiagram-4FO5ZUOK-1Pp25wwy.js +0 -2
  100. package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-1Pp25wwy.js +0 -2
  101. package/packages/web/dist/assets/format-u-YtEZFd.js +0 -2
  102. package/packages/web/dist/assets/index-B3K5nsLe.css +0 -1
  103. package/packages/web/dist/assets/index-Cc5bQxZk.js +0 -71
  104. package/packages/web/dist/assets/queued-DGmoZW59.js +0 -2
  105. package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-F7o6JfFe.js +0 -2
@@ -57,7 +57,7 @@ import { keeperAgentName, keeperSlugFromAgent, } from "./herdctl.js";
57
57
  import { consumeResumedTurn } from "./resume-drain.js";
58
58
  import { isKnownModel, getContextLimit, DEFAULT_MODEL, isKnownDriveMode, } from "./models.js";
59
59
  import { SessionHub } from "./session-hub.js";
60
- import { partsOf } from "./queued-message.js";
60
+ import { hasQueuedContent, partsOf, } from "./queued-message.js";
61
61
  import { resolveAttachmentsConfig } from "./attachments-config.js";
62
62
  import { wrapAttachments, inferAttachmentKind } from "./attachments-hint.js";
63
63
  import { sendFileServerDef, SEND_FILE_SERVER_KEY } from "./send-file-mcp.js";
@@ -215,6 +215,7 @@ export function makeChatHandler(deps) {
215
215
  sessionId: info.sessionId,
216
216
  jobId: info.jobId,
217
217
  running: info.running,
218
+ startedAt: info.startedAt,
218
219
  },
219
220
  });
220
221
  hub.onActive = (info) => {
@@ -476,15 +477,16 @@ export function makeChatHandler(deps) {
476
477
  void deps.queuedMessage
477
478
  .get(keeperAgentName(slug), sessionId)
478
479
  .then((queued) => {
479
- if (!queued?.text)
480
+ if (!hasQueuedContent(queued))
480
481
  return;
481
482
  send({
482
483
  type: "chat:queued_state",
483
484
  payload: {
484
485
  projectSlug: slug,
485
486
  sessionId,
486
- text: queued.text,
487
+ text: queued.text || null,
487
488
  ...(queued.id ? { qid: queued.id } : {}),
489
+ ...(queued.attachments?.length ? { attachments: queued.attachments } : {}),
488
490
  },
489
491
  });
490
492
  })
@@ -499,6 +501,29 @@ export function makeChatHandler(deps) {
499
501
  // `take` makes the read+clear atomic so two concurrent drains can never both
500
502
  // send it; the flushed-parts ledger skips what was already drained (a stale
501
503
  // client re-assert on reload) so it isn't sent twice.
504
+ /**
505
+ * Put attachments the drain could not carry back onto the (now empty) slot as
506
+ * an attachments-only queued message, and tell every client (#728). Only the
507
+ * slash-command path uses this: a command cannot carry files, and dropping
508
+ * them here would be the silent loss this whole change is about.
509
+ */
510
+ const restageAttachments = async (slug, sessionId, attachments) => {
511
+ if (!deps.queuedMessage)
512
+ return;
513
+ const agent = keeperAgentName(slug);
514
+ await deps.queuedMessage
515
+ .set(agent, sessionId, {
516
+ text: "",
517
+ createdAtMs: Date.now(),
518
+ id: randomUUID(),
519
+ attachments,
520
+ })
521
+ .catch(() => undefined);
522
+ hub.broadcast(sessionId, {
523
+ type: "chat:queued_state",
524
+ payload: { projectSlug: slug, sessionId, text: null, attachments },
525
+ });
526
+ };
502
527
  const drainQueue = async (slug, sessionId) => {
503
528
  if (!deps.queuedMessage)
504
529
  return;
@@ -509,7 +534,9 @@ export function makeChatHandler(deps) {
509
534
  return;
510
535
  const agent = keeperAgentName(slug);
511
536
  const queued = await deps.queuedMessage.take(agent, sessionId).catch(() => null);
512
- if (!queued?.text)
537
+ // A slot with no prose but staged files is a real message (#328/#728), so
538
+ // "is there anything here" is content, not text.
539
+ if (!hasQueuedContent(queued) || !queued)
513
540
  return;
514
541
  const markerKey = `${agent}${KEY_SEP}${sessionId}`;
515
542
  // Parked by a Stop we couldn't hand back (see returnQueueToComposer): the
@@ -539,28 +566,44 @@ export function makeChatHandler(deps) {
539
566
  const parts = partsOf(queued);
540
567
  const stale = parts.some((p) => ledger.get(p.id)?.has(p.text));
541
568
  const text = stale ? "" : queued.text;
569
+ // A leading-slash queued message is a slash command (e.g. "/compact"): the
570
+ // CLI dispatches it, and a command carries no attachments — exactly as the
571
+ // live composer refuses to attach files to one. Keep them on the slot rather
572
+ // than destroying them: they stay visible as a queued message of their own
573
+ // and go out after the command's turn, which is the only outcome here that
574
+ // isn't silent loss (#728). Unreachable from the current client, which never
575
+ // stages files onto a slash-command queue; reachable if one ever appends
576
+ // prose + a file behind an already-queued command.
577
+ const isCommand = !stale && text.startsWith("/");
578
+ const attachments = stale || isCommand ? [] : (queued.attachments ?? []);
579
+ // An attachments-only slot has no text but is still a real message
580
+ // (#328/#728), so what makes this a send is CONTENT, not prose.
581
+ const sending = !stale && (Boolean(text) || attachments.length > 0);
542
582
  // Tell every attached client (origin + reconnected sockets) to clear its copy
543
- // of this message. When we're really sending it, carry the text so the client
544
- // renders the sent bubble in-transcript; on a stale re-assert we only clear.
583
+ // of this message. When we're really sending it, carry the text + attachments
584
+ // so the client renders the sent bubble in-transcript exactly as a live send
585
+ // does; on a stale re-assert we only clear.
545
586
  hub.broadcast(sessionId, {
546
587
  type: "chat:queued_flushed",
547
588
  payload: {
548
589
  projectSlug: slug,
549
590
  sessionId,
550
- ...(text ? { text } : {}),
591
+ ...(sending && text ? { text } : {}),
592
+ ...(sending && attachments.length > 0 ? { attachments } : {}),
551
593
  },
552
594
  });
553
- if (!text)
595
+ if (!sending)
554
596
  return;
555
597
  // Every identity this slot carried is now flushed, so a re-assert from any
556
598
  // client that held one of them is recognised rather than sent again.
557
599
  for (const p of parts)
558
600
  rememberFlushed(ledger, p.id, p.text);
559
601
  // Broadcast the flush frame BEFORE kicking the turn so the user bubble renders
560
- // above the reply. Run it detached, like a human send. A leading-slash queued
561
- // message is a slash command (e.g. "/compact"): route it through the command
562
- // path so the CLI dispatches it — matching how the composer sends one live.
563
- if (text.startsWith("/")) {
602
+ // above the reply. Run it detached, like a human send.
603
+ if (isCommand) {
604
+ if ((queued.attachments?.length ?? 0) > 0) {
605
+ void restageAttachments(slug, sessionId, queued.attachments);
606
+ }
564
607
  void onChatCommand({
565
608
  type: "chat:command",
566
609
  payload: { projectSlug: slug, command: text, sessionId },
@@ -569,7 +612,14 @@ export function makeChatHandler(deps) {
569
612
  else {
570
613
  void onChatSend({
571
614
  type: "chat:send",
572
- payload: { projectSlug: slug, sessionId, message: text },
615
+ payload: {
616
+ projectSlug: slug,
617
+ sessionId,
618
+ message: text,
619
+ // The files the user staged behind this message (#728). They used to
620
+ // stay in the composer tray and ride whatever was sent NEXT.
621
+ ...(attachments.length > 0 ? { attachments } : {}),
622
+ },
573
623
  });
574
624
  }
575
625
  };
@@ -582,6 +632,9 @@ export function makeChatHandler(deps) {
582
632
  return;
583
633
  const sessionId = msg.payload.sessionId ?? null;
584
634
  const text = msg.payload.text ?? null;
635
+ // The composer's staged files ride the queued message (#728). They merge
636
+ // additively into the slot, so a write that carries none never removes any.
637
+ const attachments = msg.payload.attachments ?? [];
585
638
  // Every chat belongs to a workspace keeper.
586
639
  const agent = keeperAgentName(slug);
587
640
  if (!sessionId) {
@@ -589,12 +642,15 @@ export function makeChatHandler(deps) {
589
642
  // re-asserts it (same qid) once the id resolves, so it persists then.
590
643
  return;
591
644
  }
592
- // Store or clear the queued message.
593
- if (text && text.trim().length > 0) {
645
+ // Store or clear the queued message. A slot with staged files but no prose is
646
+ // a real message (#328: an attachment-only send is valid), so "is there
647
+ // anything to store" is text OR attachments — it used to be text alone, which
648
+ // is why an attachment-only submit during a live turn was a silent no-op.
649
+ if ((text && text.trim().length > 0) || attachments.length > 0) {
594
650
  // A queued message is persisted verbatim and the whole sidecar is rewritten
595
651
  // on every mutation, so an unbounded one is a cheap way to bloat the data
596
652
  // dir. Refuse it loudly rather than silently truncating the user's text.
597
- if (text.length > MAX_QUEUED_CHARS) {
653
+ if (text != null && text.length > MAX_QUEUED_CHARS) {
598
654
  send({
599
655
  type: "chat:error",
600
656
  payload: {
@@ -611,7 +667,7 @@ export function makeChatHandler(deps) {
611
667
  // first client's message with no signal to anyone — and then rendered it,
612
668
  // in that client's own transcript, as a user bubble they never typed.
613
669
  const next = await deps.queuedMessage
614
- .upsert(agent, sessionId, { id: queueIdOf(msg.payload), text }, Date.now(), randomUUID)
670
+ .upsert(agent, sessionId, { id: queueIdOf(msg.payload), text: text ?? "", attachments }, Date.now(), randomUUID)
615
671
  .catch(() => undefined);
616
672
  // The queue is shared chat state, so announce it: every attached client
617
673
  // renders the same slot, and echoing `qid` back on its next write is how a
@@ -623,8 +679,9 @@ export function makeChatHandler(deps) {
623
679
  payload: {
624
680
  projectSlug: slug,
625
681
  sessionId,
626
- text: next?.text ?? null,
682
+ text: next?.text || null,
627
683
  ...(next?.id ? { qid: next.id } : {}),
684
+ ...(next?.attachments?.length ? { attachments: next.attachments } : {}),
628
685
  },
629
686
  });
630
687
  }
@@ -1247,8 +1304,9 @@ export function makeChatHandler(deps) {
1247
1304
  return;
1248
1305
  const agent = keeperAgentName(slug);
1249
1306
  const queued = await deps.queuedMessage.take(agent, sessionId).catch(() => null);
1250
- if (!queued?.text)
1307
+ if (!hasQueuedContent(queued) || !queued)
1251
1308
  return;
1309
+ const attachments = queued.attachments ?? [];
1252
1310
  const live = to && to.readyState === to.OPEN ? to : null;
1253
1311
  if (!live) {
1254
1312
  // Nobody to hand it to — the tab closed between the Stop and this turn
@@ -1268,18 +1326,26 @@ export function makeChatHandler(deps) {
1268
1326
  payload: {
1269
1327
  projectSlug: slug,
1270
1328
  sessionId,
1271
- text: queued.text,
1329
+ text: queued.text || null,
1272
1330
  ...(queued.id ? { qid: queued.id } : {}),
1331
+ ...(attachments.length > 0 ? { attachments } : {}),
1273
1332
  },
1274
1333
  });
1275
1334
  return;
1276
1335
  }
1277
- // The stopping client gets the text; everyone else just sees the shared slot
1278
- // clear, WITH a reason — a chip vanishing from under you with no explanation
1279
- // is the same silence that made #629 bite.
1336
+ // The stopping client gets the text AND the files staged behind it (#728) —
1337
+ // Stop returns the whole message, and a file left on the cleared slot would be
1338
+ // stranded server-side. Everyone else just sees the shared slot clear, WITH a
1339
+ // reason — a chip vanishing from under you with no explanation is the same
1340
+ // silence that made #629 bite.
1280
1341
  writeFrame(live, {
1281
1342
  type: "chat:queued_returned",
1282
- payload: { projectSlug: slug, sessionId, text: queued.text },
1343
+ payload: {
1344
+ projectSlug: slug,
1345
+ sessionId,
1346
+ text: queued.text,
1347
+ ...(attachments.length > 0 ? { attachments } : {}),
1348
+ },
1283
1349
  });
1284
1350
  hub.broadcast(sessionId, {
1285
1351
  type: "chat:queued_state",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paddock/server",
3
- "version": "0.66.1",
3
+ "version": "0.67.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -0,0 +1,2 @@
1
+ import{j as e,l as f,r as c,q as Z,s as ee,t as te,v as ve,w as ie,x as le,y as ye,z as we,D as oe,E as Ne,F as ke,b as F,A as ce,G as Ce,X as Se,H as Ae,I as Ee,J as Re,K as $e,O as Me}from"./index-C_PXsGTD.js";import{B as R,S as ne}from"./Surfaces-DtSsbnLh.js";const K=["w-full rounded-lg border border-edge-strong bg-surface-sunken px-3 py-2 text-sm text-fg","outline-none motion-fast transition-[border-color,box-shadow]","focus:border-accent focus:ring-2 focus:ring-accent/25","disabled:cursor-not-allowed disabled:opacity-60","aria-[invalid=true]:border-danger aria-[invalid=true]:focus:ring-danger/25"].join(" ");function B({className:t,...n}){return e.jsx("input",{className:f(K,t),...n})}function Oe({className:t,...n}){return e.jsx("textarea",{className:f(K,"resize-y",t),...n})}function Ie({className:t,...n}){return e.jsx("select",{className:f(K,t),...n})}function Pe({checked:t,onChange:n,label:s,disabled:l,id:r,className:a}){const x=c.useId(),m=r??x;return e.jsxs("label",{htmlFor:m,className:f("relative inline-block shrink-0 cursor-pointer",a),children:[e.jsx("input",{id:m,type:"checkbox","aria-label":s,className:"peer sr-only",checked:t,disabled:l,onChange:g=>n(g.target.checked)}),e.jsx("span",{className:f("block h-[18px] w-8 rounded-full bg-edge-strong","motion-fast transition-colors","after:absolute after:left-0.5 after:top-0.5 after:h-3.5 after:w-3.5 after:rounded-full","after:bg-surface-raised after:shadow-2xs after:transition-transform after:content-['']","peer-checked:bg-accent-solid peer-checked:after:translate-x-3.5","peer-focus-visible:ring-2 peer-focus-visible:ring-accent/50","peer-disabled:opacity-50")})]})}const de={neutral:"bg-surface-active text-fg-muted",accent:"bg-accent-soft text-accent",success:"bg-success-soft text-success",warn:"bg-warn-soft text-warn",danger:"bg-danger-soft text-danger",info:"bg-info-soft text-info",lineage:"bg-lineage-soft text-lineage"},ue={neutral:"bg-fg-subtle",accent:"bg-accent-solid",success:"bg-success-solid",warn:"bg-warn-solid",danger:"bg-danger-solid",info:"bg-info-solid",lineage:"bg-lineage-solid"};function xe({tone:t="neutral",shape:n="tag",size:s="md",dot:l=!1,icon:r,title:a,className:x,children:m}){return e.jsxs("span",{title:a,className:f("inline-flex items-center gap-1 font-medium leading-none",n==="pill"?"rounded-full px-2 py-0.5":"rounded-md px-1.5 py-0.5",s==="sm"?"text-3xs":"text-2xs",de[t],x),children:[l&&e.jsx("span",{className:f("h-1.5 w-1.5 shrink-0 rounded-full",ue[t])}),r,m]})}function L({tone:t="neutral",pulse:n=!1,className:s}){return e.jsx("span",{className:f("inline-block h-2 w-2 shrink-0 rounded-full",ue[t],n&&"animate-pulse",s)})}function Te({tone:t="info",icon:n,className:s,children:l}){const r={neutral:"border-edge",accent:"border-accent-edge",success:"border-success-edge",warn:"border-warn-edge",danger:"border-danger-edge",info:"border-info-edge",lineage:"border-lineage-edge"};return e.jsxs("div",{role:t==="danger"?"alert":void 0,className:f("flex items-start gap-2 rounded-lg border px-3 py-2 text-sm",de[t],r[t],s),children:[n&&e.jsx("span",{className:"mt-0.5 shrink-0",children:n}),e.jsx("div",{className:"min-w-0",children:l})]})}const Fe=Array.from({length:72},(t,n)=>n*5),S=[...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=S.reduce((r,a)=>z(a.hue,t)<z(r.hue,t)?a:r);if(z(n.hue,t)<=8)return n.name;const s=S.find(r=>r.hue>t)??S[0];return`between ${S[(S.indexOf(s)-1+S.length)%S.length].name} and ${s.name}`}function Le({theme:t,dark:n,hue:s,className:l}){const r=c.useRef(null);return c.useLayoutEffect(()=>{const a=r.current;a&&(s===null?we(a):oe(s,a))},[t,n,s]),e.jsx("div",{ref:r,"data-theme":t,className:f("overflow-hidden rounded-lg border border-edge",n&&"dark",l),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:s,onCommit:l}){const r=c.useRef(null),a=c.useRef(null),x=c.useRef(null),m=c.useRef(0),g=c.useRef(null),p=c.useRef(!1),j=c.useRef(null),u=c.useMemo(()=>{if(t.length===0)return;const d=t.map((b,y)=>`${b} ${(y/t.length*100).toFixed(2)}%`);return d.push(`${t[0]} 100%`),`linear-gradient(to right, ${d.join(", ")})`},[t]),h=c.useCallback(d=>{a.current&&(a.current.style.left=`${d/360*100}%`),x.current&&(x.current.textContent=se(d))},[]);c.useEffect(()=>{p.current||h(n)},[n,h]);const N=d=>{var $;const b=($=r.current)==null?void 0:$.getBoundingClientRect();if(!b||b.width===0)return 0;const y=Math.min(1,Math.max(0,(d-b.left)/b.width));return Math.round(y*360)%360},w=c.useCallback(d=>{g.current=d,!m.current&&(m.current=requestAnimationFrame(()=>{m.current=0;const b=g.current;g.current=null,b!==null&&(h(b),oe(b,document.documentElement,{context:j.current??void 0,live:!0}))}))},[h]);c.useEffect(()=>()=>cancelAnimationFrame(m.current),[]);const P=d=>{p.current=!1,j.current=null,cancelAnimationFrame(m.current),m.current=0,g.current=null,l(d)};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"mt-2 flex items-baseline justify-between gap-2",children:[e.jsx("span",{className:"text-3xs text-fg-subtle",children:"Drag along the strip"}),e.jsx("span",{ref:x,className:"text-3xs font-medium text-fg-muted","aria-hidden":!0})]}),e.jsx("div",{ref:r,role:"slider",tabIndex:0,"aria-label":"Accent colour","aria-valuemin":0,"aria-valuemax":359,"aria-valuenow":n,"aria-valuetext":s?se(n):"the theme’s own colour",onPointerDown:d=>{d.currentTarget.setPointerCapture(d.pointerId),p.current=!0,j.current=Ne(),w(N(d.clientX))},onPointerMove:d=>{p.current&&w(N(d.clientX))},onPointerUp:d=>{p.current&&P(N(d.clientX))},onPointerCancel:()=>{p.current&&P(n)},onKeyDown:d=>{const b=d.key==="PageUp"||d.key==="PageDown"?15:d.shiftKey?10:2;let y=null;d.key==="ArrowLeft"||d.key==="ArrowDown"||d.key==="PageDown"?y=(n-b+360)%360:d.key==="ArrowRight"||d.key==="ArrowUp"||d.key==="PageUp"?y=(n+b)%360:d.key==="Home"?y=0:d.key==="End"&&(y=359),y!==null&&(d.preventDefault(),l(y))},style:{backgroundImage:u,touchAction:"none"},className:f("relative mt-1 h-8 cursor-pointer rounded-lg border border-edge shadow-xs select-none","focus-visible:focus-ring"),children:e.jsx("div",{ref:a,"aria-hidden":!0,style:{left:`${n/360*100}%`},className:f("pointer-events-none absolute top-1/2 h-[calc(100%+6px)] w-1.5 -translate-x-1/2 -translate-y-1/2 rounded-full","border border-fg bg-surface-raised shadow-sm",!s&&"opacity-0")})})]})}function Ve({className:t}){const[n,s]=c.useState(Z),[l,r]=c.useState(null),a=typeof document<"u"&&document.documentElement.classList.contains("dark"),x=c.useRef(n),m=c.useCallback(u=>{const h={...x.current,...u};x.current=h,s(h),r(ee(h))},[]);c.useEffect(()=>{const u=h=>r(h.detail);return window.addEventListener(te,u),r(ee(Z())),()=>window.removeEventListener(te,u)},[]);const g=c.useMemo(()=>ve(Fe),[n.theme,a,l==null?void 0:l.mode]),p=(l==null?void 0:l.hue)??0,j=n.hue!==null;return e.jsxs("div",{className:f("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:f("mt-2 grid gap-2","grid-cols-2 sm:grid-cols-4"),children:ie.map(u=>{const h=n.theme===u.id;return e.jsxs("button",{type:"button","aria-pressed":h,title:`${u.label} — ${u.blurb}`,onClick:()=>m({theme:u.id}),className:f("motion-fast rounded-xl border text-left transition-[border-color,box-shadow]","focus-visible:focus-ring","p-1.5",h?"border-accent-edge bg-accent-soft shadow-xs":"border-edge bg-surface-raised hover:border-edge-strong"),children:[e.jsx(Le,{theme:u.id,dark:a,hue:n.hue,className:"h-10"}),e.jsxs("div",{className:"mt-1.5 min-w-0 px-0.5 pb-0.5",children:[e.jsx("span",{className:"block truncate text-xs font-semibold text-fg",children:u.label}),e.jsx("span",{className:"mt-0.5 line-clamp-2 text-3xs leading-snug text-fg-muted",children:u.blurb})]})]},u.id)})})]}),e.jsxs("div",{className:f("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:g,hue:p,active:j,onCommit:u=>m({hue:u})}),e.jsxs("div",{className:"mt-2 flex flex-wrap gap-1",children:[e.jsx("button",{type:"button",onClick:()=>m({hue:null}),"aria-pressed":!j,className:f("motion-fast rounded-md border px-2 py-1 text-3xs transition-colors","focus-visible:focus-ring",j?"border-edge text-fg-muted hover:border-edge-strong hover:text-fg":"border-accent-edge bg-accent-soft text-accent"),children:"Theme’s own"}),le.map(u=>{const h=j&&Math.abs(p-u.hue)<2.5;return e.jsxs("button",{type:"button","aria-pressed":h,onClick:()=>m({hue:u.hue}),className:f("motion-fast inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-3xs transition-colors","focus-visible:focus-ring",h?"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:g[Math.round(u.hue/5)%g.length]}}),u.name]},u.name)})]})]}),e.jsxs("fieldset",{children:[e.jsx("legend",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Tint the background"}),e.jsx("p",{className:"mt-1 text-3xs leading-snug text-fg-subtle",children:"Colours the whole page a little, not just the buttons. Useful for telling two Paddock instances apart at a glance."}),e.jsx("div",{className:"mt-2 inline-flex rounded-lg border border-edge p-0.5",children:ye.map(u=>e.jsx("button",{type:"button","aria-pressed":n.tint===u.level,onClick:()=>m({tint:u.level}),className:f("motion-fast rounded-md px-2.5 py-1 text-3xs transition-colors","focus-visible:focus-ring",n.tint===u.level?"bg-surface-active font-medium text-fg":"text-fg-muted hover:text-fg"),children:u.label},u.level))})]})]}),e.jsx(Be,{})]})}function Be(){return e.jsxs("div",{children:[e.jsx("p",{className:"text-2xs font-semibold uppercase tracking-wide text-fg-muted",children:"Preview"}),e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-2 rounded-xl border border-edge bg-surface p-3",children:[e.jsx(R,{variant:"primary",size:"sm",children:"Send"}),e.jsx(R,{variant:"ghost",size:"sm",children:"Cancel"}),e.jsx(R,{variant:"link",size:"sm",children:"A link"}),e.jsx("span",{className:"inline-flex items-center gap-1 rounded-md border border-accent-edge bg-accent-soft px-1.5 py-0.5 text-2xs font-medium text-accent",children:"accent chip"}),e.jsx(xe,{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",He="sm:w-64";function Ue(){const[t,n]=c.useState(null),[s,l]=c.useState(!0),[r,a]=c.useState(null),[x,m]=c.useState({}),[g,p]=c.useState(!1),[j,u]=c.useState(null),[h,N]=c.useState(!1),[w,P]=c.useState(""),[d,b]=c.useState(!1),[y,$]=c.useState(null),X=c.useRef(null),me=ke("(min-width: 1024px)");c.useEffect(()=>{let i=!0;return l(!0),F.getInstanceConfig().then(o=>{i&&(n(o),a(null))}).catch(o=>i&&a(o instanceof Error?o.message:String(o))).finally(()=>i&&l(!1)),()=>{i=!1}},[]);const fe=(i,o)=>{N(!1),u(null),m(v=>({...v,[i]:o}))},ge=i=>re(i,x),H=c.useMemo(()=>t?t.groups.flatMap(i=>i.fields):[],[t]),k=c.useMemo(()=>H.filter(i=>i.editable&&!i.envOverridden).filter(i=>Object.prototype.hasOwnProperty.call(x,i.key)).filter(i=>!ae(x[i.key],q(i))).map(i=>i.key),[H,x]),J=c.useMemo(()=>new Set(k),[k]),C=c.useMemo(()=>{if(!t)return[];const i=w.trim().toLowerCase(),o=[];for(const v of t.groups){const O=i!==""&&v.label.toLowerCase().includes(i),I=v.fields.filter(A=>{const T=re(A,x);return d&&ae(T,A.default)?!1:i===""||O?!0:Ze(A).includes(i)});I.length>0&&o.push({...v,fields:I})}return o},[t,w,d,x]),Y=C.reduce((i,o)=>i+o.fields.length,0),he=w.trim()!==""||d,U=!d&&(w.trim()===""||tt.includes(w.trim().toLowerCase())),M=c.useMemo(()=>U?[V,...C]:C,[U,C]),pe=C.some(i=>i.fields.some(o=>o.envOverridden)),Q=et(C,J);c.useEffect(()=>{const i=X.current;if(!i||M.length===0)return;const o=M.map(O=>O.id),v=()=>{const O=i.getBoundingClientRect().top;let I=o[0];for(const A of o){const T=document.getElementById(D(A));if(T)if(T.getBoundingClientRect().top-O<=80)I=A;else break}$(I)};return v(),i.addEventListener("scroll",v,{passive:!0}),()=>i.removeEventListener("scroll",v)},[M]);const W=i=>{var v;const o=document.getElementById(D(i));(v=o==null?void 0:o.scrollIntoView)==null||v.call(o,{behavior:"smooth",block:"start"}),$(i)},be=async()=>{if(k.length===0||!t)return;const i={};for(const o of k)i[o]=x[o];p(!0),u(null);try{await F.updateInstanceConfig(i,t.configVersion);const o=await F.getInstanceConfig();n(o),m({}),N(!0)}catch(o){if(o instanceof Ee&&o.status===409)try{n(await F.getInstanceConfig())}catch{}u(o instanceof Error?o.message:String(o))}finally{p(!1)}},je=()=>{m({}),u(null),N(!1)};return e.jsxs("div",{className:"flex min-h-0 flex-1",children:[t&&e.jsx(ze,{groups:M,active:y,onJump:W,dirtyCount:Q}),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(_e,{query:w,onQuery:P,modifiedOnly:d,onModifiedOnly:b,visibleCount:Y,totalCount:H.length,filtering:he,autoFocus:me}),e.jsx(Ge,{groups:M,active:y,onJump:W,dirtyCount:Q})]})}),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:[U&&e.jsx("div",{className:"mb-8 border-b border-edge pb-7",children:e.jsx(ne,{id:D(V.id),title:V.label,description:V.description,variant:"rule",first:!0,children:e.jsx(Ve,{})})}),e.jsx(Ke,{saved:h,configPath:t==null?void 0:t.configPath,restartRequired:(t==null?void 0:t.restartRequired)??!1,fileError:t==null?void 0:t.configFileError}),s&&e.jsx("p",{className:"text-sm text-fg-muted",children:"Loading…"}),r&&e.jsxs("p",{className:"text-sm text-danger",role:"alert",children:["Failed to load settings: ",r]}),t&&pe&&e.jsx(qe,{}),t&&Y===0&&e.jsxs("p",{className:"py-8 text-center text-sm text-fg-muted",children:["No settings match"," ",w.trim()?e.jsxs(e.Fragment,{children:["“",e.jsx("span",{className:"font-medium",children:w.trim()}),"”"]}):"this filter","."]}),C.map(i=>e.jsx(ne,{id:D(i.id),title:i.label,description:i.description,variant:"card",flush:!0,children:e.jsx("div",{className:"divide-y divide-edge-subtle",children:i.fields.map(o=>e.jsx(Xe,{field:o,value:ge(o),dirty:J.has(o.key),onChange:v=>fe(o.key,v)},o.key))})},i.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:[j&&e.jsxs("span",{className:"flex min-w-0 items-center gap-1.5 text-sm text-danger",role:"alert",children:[e.jsx(ce,{width:14,height:14,className:"shrink-0"}),e.jsx("span",{className:"truncate",children:j})]}),e.jsx("span",{className:"ml-auto shrink-0 text-xs tabular text-fg-subtle",children:k.length>0?`${k.length} unsaved change${k.length===1?"":"s"}`:"No changes"}),e.jsx(R,{variant:"subtle",onClick:je,disabled:k.length===0||g,children:"Reset"}),e.jsx(R,{variant:"primary",onClick:be,disabled:k.length===0,loading:g,loadingLabel:"Saving…",children:"Save changes"})]})})]})]})}function ze({groups:t,active:n,onJump:s,dirtyCount:l}){return e.jsx("nav",{"aria-label":"Config sections",className:"hidden w-52 shrink-0 overflow-y-auto border-r border-edge px-2 py-4 lg:block",children:e.jsx("ul",{className:"space-y-0.5",children:t.map(r=>{const a=r.id===n,x=l[r.id]??0;return e.jsx("li",{children:e.jsxs("button",{type:"button",onClick:()=>s(r.id),"aria-current":a?"true":void 0,className:f("motion-fast flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm transition-colors",a?"bg-surface-selected font-medium text-fg":"text-fg-muted hover:bg-surface-hover hover:text-fg"),children:[e.jsx("span",{className:"min-w-0 flex-1 truncate",children:r.label}),x>0&&e.jsx("span",{className:"h-1.5 w-1.5 shrink-0 rounded-full bg-accent-solid","aria-label":`${x} unsaved`}),r.fields.length>0&&e.jsx("span",{className:"shrink-0 text-2xs tabular text-fg-subtle",children:r.fields.length})]})},r.id)})})})}function Ge({groups:t,active:n,onJump:s,dirtyCount:l}){return t.length===0?null:e.jsx("nav",{"aria-label":"Config sections",className:"-mx-1 mt-2 flex gap-1 overflow-x-auto px-1 pb-0.5 lg:hidden",children:t.map(r=>{const a=r.id===n,x=l[r.id]??0;return e.jsxs("button",{type:"button",onClick:()=>s(r.id),"aria-current":a?"true":void 0,className:f("motion-fast flex shrink-0 items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors",a?"bg-surface-selected font-medium text-fg":"text-fg-muted hover:bg-surface-hover hover:text-fg"),children:[r.label,x>0&&e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-accent-solid","aria-label":`${x} unsaved`})]},r.id)})})}function _e({query:t,onQuery:n,modifiedOnly:s,onModifiedOnly:l,visibleCount:r,totalCount:a,filtering:x,autoFocus:m}){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(Ce,{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:m,onChange:g=>n(g.target.value),onKeyDown:g=>{g.key==="Escape"&&t!==""&&(g.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":s,onClick:()=>l(!s),className:f("motion-fast shrink-0 rounded-md border px-2.5 py-1 text-xs transition-colors",s?"border-accent-edge bg-accent-soft text-accent":"border-edge text-fg-muted hover:text-fg"),children:"Modified only"}),e.jsx("span",{className:"shrink-0 text-xs tabular text-fg-subtle",children:x?`${r} of ${a}`:`${a} settings`})]})}function Ke({saved:t,configPath:n,restartRequired:s,fileError:l}){const r=l?"danger":t?"success":"warn";return e.jsx(Te,{tone:r,className:"mb-5 leading-snug",icon:r==="success"?e.jsx(Ae,{width:15,height:15}):e.jsx(ce,{width:15,height:15}),children:e.jsx("span",{role:"status",children:l?e.jsxs(e.Fragment,{children:[e.jsxs("strong",{children:["Could not read ",_(n),"."]})," ",l," — the values below are the running instance's; what a restart would load is unknown until the file parses."]}):t?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Saved to disk."})," These changes take effect only after the server restarts."]}):s?e.jsxs(e.Fragment,{children:[e.jsx("strong",{children:"Restart pending."})," ",e.jsx("code",{className:"font-mono text-xs",children:_(n)})," holds changes the running instance has not picked up — the fields below marked"," ",e.jsx(E,{tone:"warn",children:"restart"})," differ from what is in force right now."]}):e.jsxs(e.Fragment,{children:["Changes here are written to"," ",e.jsx("code",{className:"font-mono text-xs",children:_(n)})," and take effect only after the server restarts — the running instance keeps its current config until then."]})})})}function qe(){return e.jsxs("p",{className:"mb-5 text-xs leading-snug text-fg-muted",children:["Settings marked ",e.jsx(E,{tone:"neutral",children:"env"})," are overridden by an environment variable, which wins over this file — edit that variable (and restart) to change them."]})}function Xe({field:t,value:n,dirty:s,onChange:l}){const r=!t.editable||t.envOverridden,a=`cfg-${t.key}`,x=t.help?`${a}-help`:void 0,m=t.type==="boolean",g=c.useMemo(()=>We(t,q(t)),[t]),p=m&&!r,j=!t.help&&!t.envOverridden&&!t.pendingRestart,u=j?"items-center":"items-start",h=j?"sm:items-center":"sm:items-start",N=r?e.jsx(Qe,{field:t,value:n}):m?e.jsx(Pe,{id:a,checked:!!n,onChange:l,label:t.label}):e.jsx(Ye,{field:t,value:n,onChange:l,inputId:a,describedBy:x});return e.jsxs("div",{className:"relative px-4 py-3",children:[s&&e.jsx("span",{"aria-hidden":!0,className:"absolute inset-y-2 left-0 w-0.5 rounded-full bg-accent-solid"}),e.jsxs("div",{className:f(g?"":p?`flex gap-4 ${u}`:`sm:flex sm:gap-6 ${h}`),children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-1.5",children:[e.jsx("label",{htmlFor:r?void 0:a,className:"text-sm font-medium text-fg",children:t.label}),t.sensitive&&e.jsx(E,{tone:"warn",children:"sensitive"}),t.envOverridden&&e.jsx(E,{tone:"neutral",title:`Overridden by environment variable ${t.envVar} — edit that env var (and restart) to change it.`,children:"env"}),!t.editable&&!t.envOverridden&&e.jsx(E,{tone:"neutral",children:"read-only"}),t.pendingRestart&&e.jsx(E,{tone:"warn",title:"The config file and the running instance disagree on this field — restart to apply.",children:"restart"})]}),t.help&&e.jsx("p",{id: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:Je(t.value)})]})]}),e.jsx("div",{className:f(g?"mt-2":p?"flex shrink-0 justify-end":`mt-2 sm:mt-0 sm:shrink-0 ${He}`),children:N})]})]})}function Je(t){if(t==null||t==="")return"(not set)";const s=(Array.isArray(t)?t.join(", "):String(t)).replace(/\s+/g," ").trim();return s.length>60?`${s.slice(0,60)}…`:s}function E({children:t,tone:n,title:s}){return e.jsx(xe,{tone:n,size:"sm",title:s,className:"uppercase tracking-wide",children:t})}function Ye({field:t,value:n,onChange:s,inputId:l,describedBy:r}){if(t.type==="enum")return e.jsx(Ie,{id:l,"aria-describedby":r,value:String(n??""),onChange:a=>s(a.target.value),children:(t.enumValues??[]).map(a=>e.jsx("option",{value:a,children:a},a))});if(t.type==="number")return e.jsx(B,{id:l,type:"number","aria-describedby":r,className:"tabular",value:n==null?"":String(n),placeholder:t.default===null?"default":String(t.default),onChange:a=>s(a.target.value===""?null:Number(a.target.value))});if(t.type==="text"){const a=n==null||n===t.default,x=n==null?String(t.default??""):String(n),m=typeof t.default=="string"&&!a;return e.jsxs("div",{children:[e.jsx(Oe,{id:l,"aria-describedby":r,className:"min-h-[7rem] font-mono text-xs leading-relaxed",rows:6,spellCheck:!1,value:x,onChange:g=>s(g.target.value)}),e.jsxs("div",{className:"mt-1 flex items-center justify-between gap-3 text-xs text-fg-muted",children:[e.jsx("span",{children:a?"Using the built-in default.":n===""?"Empty — nothing will be appended.":`${x.length.toLocaleString()} characters.`}),m&&e.jsx(R,{variant:"link",size:"sm",className:"shrink-0",onClick:()=>s(null),children:"Restore default"})]})]})}if(t.type==="string-list"){const a=Array.isArray(n)?n.join(", "):String(n??"");return e.jsx(B,{id:l,type:"text","aria-describedby":r,value:a,onChange:x=>s(x.target.value.split(",").map(m=>m.trim()).filter(Boolean))})}return e.jsx(B,{id:l,type:"text","aria-describedby":r,value:n==null?"":String(n),placeholder:t.default===null?"":String(t.default),onChange:a=>s(a.target.value)})}function Qe({field:t,value:n}){const s="rounded-lg bg-surface-sunken px-3 py-2 font-mono text-xs text-fg-muted";return t.type==="boolean"?e.jsx("div",{className:s,children:n?"true":"false"}):n==null||n===""?e.jsx("div",{className:s,children:e.jsx("span",{className:"italic text-fg-subtle",children:t.type==="text"&&n===""?"(empty — nothing appended)":"(not set)"})}):t.type==="text"?e.jsx("div",{className:f(s,"max-h-40 overflow-y-auto whitespace-pre-wrap leading-relaxed"),children:String(n)}):e.jsx("div",{className:f(s,"break-all"),children:Array.isArray(n)?n.join(", "):String(n)})}function We(t,n){return t.type==="text"||t.type==="string-list"?!0:(Array.isArray(n)?n.join(", "):n==null?"":String(n)).length>38}function Ze(t){return[t.label,t.key,t.help,t.envVar,...t.enumValues??[]].filter(Boolean).join(" ").toLowerCase()}function et(t,n){const s={};for(const l of t)s[l.id]=l.fields.filter(r=>n.has(r.key)).length;return s}const D=t=>`cfg-section-${t}`,V={id:"appearance",label:"Appearance",description:"Theme and accent colour for this browser. Applies immediately — no save, no restart.",fields:[]},tt=["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((s,l)=>s===n[l]):t===n}function _(t){if(!t)return"paddock.config.yaml";const n=t.split("/");return n[n.length-1]||t}function rt(){const t=Re(),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($e,{width:20,height:20})}),e.jsx(Me,{width:18,height:18,className:"shrink-0 text-fg-subtle"}),e.jsx("h1",{className:"text-md font-semibold tracking-tight",children:"Config"})]}),e.jsx(Ue,{})]})}export{rt as InstanceConfigPage};
2
+