@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.
- package/package.json +1 -1
- package/packages/server/dist/herdctl-jobs.d.ts +20 -0
- package/packages/server/dist/herdctl-jobs.js +94 -0
- package/packages/server/dist/herdctl.d.ts +46 -0
- package/packages/server/dist/herdctl.js +58 -0
- package/packages/server/dist/instance-config.d.ts +56 -5
- package/packages/server/dist/instance-config.js +206 -24
- package/packages/server/dist/project-paths.d.ts +46 -1
- package/packages/server/dist/project-paths.js +66 -2
- package/packages/server/dist/project-types.d.ts +23 -0
- package/packages/server/dist/project-types.js +35 -0
- package/packages/server/dist/projects.d.ts +5 -2
- package/packages/server/dist/projects.js +71 -17
- package/packages/server/dist/queued-message.d.ts +32 -2
- package/packages/server/dist/queued-message.js +87 -8
- package/packages/server/dist/routes/chats.js +41 -10
- package/packages/server/dist/routes/meta.js +32 -5
- package/packages/server/dist/routes/projects.js +71 -7
- package/packages/server/dist/session-hub.d.ts +10 -0
- package/packages/server/dist/session-hub.js +16 -2
- package/packages/server/dist/ws-protocol.d.ts +67 -0
- package/packages/server/dist/ws-protocol.js +28 -13
- package/packages/server/dist/ws.js +90 -24
- package/packages/server/package.json +1 -1
- package/packages/web/dist/assets/InstanceConfigPage-sDbCoFxC.js +2 -0
- package/packages/web/dist/assets/{MarkdownRenderer-CRQP3Cfe.js → MarkdownRenderer-MaSe5aAI.js} +11 -11
- package/packages/web/dist/assets/ProjectMenu-DmWpr9od.js +2 -0
- package/packages/web/dist/assets/ProjectRedirect-BJeDERUd.js +2 -0
- package/packages/web/dist/assets/ProjectView-Bbecs1u7.js +13 -0
- package/packages/web/dist/assets/ProjectsGrid-BE4cAtu5.js +2 -0
- package/packages/web/dist/assets/Surfaces-DtSsbnLh.js +2 -0
- package/packages/web/dist/assets/{arc-DqMSW0As.js → arc-NLb6J_GZ.js} +1 -1
- package/packages/web/dist/assets/{architectureDiagram-3BPJPVTR-CNkQD1jU.js → architectureDiagram-3BPJPVTR-BbiSIctn.js} +1 -1
- package/packages/web/dist/assets/{blockDiagram-GPEHLZMM-BCMiOmt6.js → blockDiagram-GPEHLZMM-Br1sRzRE.js} +1 -1
- package/packages/web/dist/assets/{c4Diagram-AAUBKEIU-CU2soCji.js → c4Diagram-AAUBKEIU-DUvJurko.js} +1 -1
- package/packages/web/dist/assets/channel-bezC0w5b.js +2 -0
- package/packages/web/dist/assets/{chunk-2J33WTMH-nKhlJNTt.js → chunk-2J33WTMH-Cm_E_LAD.js} +1 -1
- package/packages/web/dist/assets/{chunk-4BX2VUAB-YRTZj9zI.js → chunk-4BX2VUAB-CK6R2MCN.js} +1 -1
- package/packages/web/dist/assets/{chunk-55IACEB6-HO60VE45.js → chunk-55IACEB6-D7HXvxGy.js} +1 -1
- package/packages/web/dist/assets/{chunk-727SXJPM-WZJH-boe.js → chunk-727SXJPM-BLwevzlY.js} +1 -1
- package/packages/web/dist/assets/{chunk-AQP2D5EJ-PvDC7gYB.js → chunk-AQP2D5EJ-CpLWD5so.js} +1 -1
- package/packages/web/dist/assets/{chunk-FMBD7UC4-BNRMTplH.js → chunk-FMBD7UC4-BNlt1t19.js} +1 -1
- package/packages/web/dist/assets/{chunk-ND2GUHAM-2Q2ei8TR.js → chunk-ND2GUHAM-CIt5gAq8.js} +1 -1
- package/packages/web/dist/assets/{chunk-QZHKN3VN-CG9VV7M6.js → chunk-QZHKN3VN-B1uwIQ9c.js} +1 -1
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-DMgrTmQU.js +2 -0
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-DMgrTmQU.js +2 -0
- package/packages/web/dist/assets/{core-DwSEw_oE.js → core-HXUN76Po.js} +1 -1
- package/packages/web/dist/assets/{cose-bilkent-S5V4N54A-BlMrBI7K.js → cose-bilkent-S5V4N54A-DJ4_5jxB.js} +1 -1
- package/packages/web/dist/assets/{dagre-BM42HDAG-tNoaleNU.js → dagre-BM42HDAG-Bc9-haAu.js} +1 -1
- package/packages/web/dist/assets/{diagram-2AECGRRQ-DRVCTpHL.js → diagram-2AECGRRQ-nLOtPDzu.js} +1 -1
- package/packages/web/dist/assets/{diagram-5GNKFQAL-BdS-96WV.js → diagram-5GNKFQAL-DThV84R3.js} +1 -1
- package/packages/web/dist/assets/{diagram-KO2AKTUF-Dv1yxz3s.js → diagram-KO2AKTUF-C5hcR1vA.js} +1 -1
- package/packages/web/dist/assets/{diagram-LMA3HP47-BNXgifuS.js → diagram-LMA3HP47-BEFzrSHy.js} +1 -1
- package/packages/web/dist/assets/{diagram-OG6HWLK6-wlO0bxwP.js → diagram-OG6HWLK6-BY5lPsX7.js} +1 -1
- package/packages/web/dist/assets/{erDiagram-TEJ5UH35-ChoppAEq.js → erDiagram-TEJ5UH35-y_kB8ETg.js} +1 -1
- package/packages/web/dist/assets/{flowDiagram-I6XJVG4X-C__aYyei.js → flowDiagram-I6XJVG4X-Cuhha5cF.js} +1 -1
- package/packages/web/dist/assets/{ganttDiagram-6RSMTGT7-CjRe8foW.js → ganttDiagram-6RSMTGT7-DFJpciK5.js} +1 -1
- package/packages/web/dist/assets/{gitGraphDiagram-PVQCEYII-BGDQjiWq.js → gitGraphDiagram-PVQCEYII-FoA0yuh-.js} +1 -1
- package/packages/web/dist/assets/index-BxLurJvz.css +1 -0
- package/packages/web/dist/assets/index-C_PXsGTD.js +71 -0
- package/packages/web/dist/assets/{infoDiagram-5YYISTIA-BP-JaX-8.js → infoDiagram-5YYISTIA-CKYqD8-c.js} +1 -1
- package/packages/web/dist/assets/{ishikawaDiagram-YF4QCWOH-YnsAJBvC.js → ishikawaDiagram-YF4QCWOH-CCIxHIBw.js} +1 -1
- package/packages/web/dist/assets/{journeyDiagram-JHISSGLW-BM6JWTV9.js → journeyDiagram-JHISSGLW-lGYoSyk0.js} +1 -1
- package/packages/web/dist/assets/{kanban-definition-UN3LZRKU-gR2O0IaL.js → kanban-definition-UN3LZRKU-B3D7CIwy.js} +1 -1
- package/packages/web/dist/assets/{linear-Dolq_eLR.js → linear-BiplJBex.js} +1 -1
- package/packages/web/dist/assets/{mermaid.core-D1T_GQW7.js → mermaid.core-BJ7aEinZ.js} +4 -4
- package/packages/web/dist/assets/{mindmap-definition-RKZ34NQL-C3bzIyLZ.js → mindmap-definition-RKZ34NQL-BiXcOtHo.js} +1 -1
- package/packages/web/dist/assets/{pieDiagram-4H26LBE5-BrZ3ANGf.js → pieDiagram-4H26LBE5-tnkj45YF.js} +1 -1
- package/packages/web/dist/assets/{quadrantDiagram-W4KKPZXB-BvLCHAEi.js → quadrantDiagram-W4KKPZXB-LqReq3Gl.js} +1 -1
- package/packages/web/dist/assets/queued-CagZVfWU.js +2 -0
- package/packages/web/dist/assets/{requirementDiagram-4Y6WPE33-CCzXAeaZ.js → requirementDiagram-4Y6WPE33-C1uk9Jty.js} +1 -1
- package/packages/web/dist/assets/{sankeyDiagram-5OEKKPKP-2Gf-ERTg.js → sankeyDiagram-5OEKKPKP-eQQ3OKrV.js} +1 -1
- package/packages/web/dist/assets/{sequenceDiagram-3UESZ5HK-DMjDe28d.js → sequenceDiagram-3UESZ5HK-C6-a0lH4.js} +1 -1
- package/packages/web/dist/assets/{stateDiagram-AJRCARHV-CENtj5dw.js → stateDiagram-AJRCARHV-CsTOkKqz.js} +1 -1
- package/packages/web/dist/assets/stateDiagram-v2-BHNVJYJU-Bsc-VlVI.js +2 -0
- package/packages/web/dist/assets/{timeline-definition-PNZ67QCA-BW4gCpBB.js → timeline-definition-PNZ67QCA-DUBXH1x-.js} +1 -1
- package/packages/web/dist/assets/{vennDiagram-CIIHVFJN-CiIuqcT9.js → vennDiagram-CIIHVFJN-CcQrNnou.js} +1 -1
- package/packages/web/dist/assets/{wardley-L42UT6IY-BQ1BPr5R.js → wardley-L42UT6IY-Br823uUV.js} +1 -1
- package/packages/web/dist/assets/{wardleyDiagram-YWT4CUSO-DBq9Ailv.js → wardleyDiagram-YWT4CUSO-DRBECQc_.js} +1 -1
- package/packages/web/dist/assets/{xychartDiagram-2RQKCTM6-D24VDECy.js → xychartDiagram-2RQKCTM6--HtSFP24.js} +1 -1
- package/packages/web/dist/fonts/alegreya-latin-italic.woff2 +0 -0
- package/packages/web/dist/fonts/alegreya-latin.woff2 +0 -0
- package/packages/web/dist/fonts/alegreya-sc-latin-500.woff2 +0 -0
- package/packages/web/dist/fonts/alegreya-sc-latin-700.woff2 +0 -0
- package/packages/web/dist/fonts/courier-prime-latin-400.woff2 +0 -0
- package/packages/web/dist/fonts/courier-prime-latin-700.woff2 +0 -0
- package/packages/web/dist/fonts/courier-prime-latin-italic.woff2 +0 -0
- package/packages/web/dist/fonts/iosevka-terminal-400.woff2 +0 -0
- package/packages/web/dist/fonts/iosevka-terminal-700.woff2 +0 -0
- package/packages/web/dist/fonts/martian-mono-latin.woff2 +0 -0
- package/packages/web/dist/fonts/saira-latin.woff2 +0 -0
- package/packages/web/dist/index.html +61 -12
- package/packages/web/dist/sw.js +1 -1
- package/packages/web/dist/assets/InstanceConfigPage-1BzMnJPo.js +0 -2
- package/packages/web/dist/assets/ProjectRedirect-Dd3h5V8h.js +0 -2
- package/packages/web/dist/assets/ProjectView-BRk6l152.js +0 -13
- package/packages/web/dist/assets/ProjectsGrid-BaUE8zaF.js +0 -2
- package/packages/web/dist/assets/channel-Ynyq4GPh.js +0 -2
- package/packages/web/dist/assets/classDiagram-4FO5ZUOK-1Pp25wwy.js +0 -2
- package/packages/web/dist/assets/classDiagram-v2-Q7XG4LA2-1Pp25wwy.js +0 -2
- package/packages/web/dist/assets/format-u-YtEZFd.js +0 -2
- package/packages/web/dist/assets/index-B3K5nsLe.css +0 -1
- package/packages/web/dist/assets/index-Cc5bQxZk.js +0 -71
- package/packages/web/dist/assets/queued-DGmoZW59.js +0 -2
- 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
|
|
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
|
-
|
|
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
|
|
544
|
-
// renders the sent bubble in-transcript
|
|
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 (!
|
|
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.
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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: {
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1278
|
-
//
|
|
1279
|
-
//
|
|
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: {
|
|
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",
|
|
@@ -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
|
+
|