@tomismeta/paperclip-aperture 0.5.1 → 0.6.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/README.md +95 -152
- package/dist/manifest.js +1 -1
- package/dist/ui/index.js +731 -373
- package/dist/worker.js +23 -23
- package/package.json +21 -27
package/dist/ui/index.js
CHANGED
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
// src/ui/index.tsx
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useHostContext as useHostContext2
|
|
4
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
5
|
+
import { useEffect as useEffect5, useMemo as useMemo2, useState as useState6 } from "react";
|
|
3
6
|
|
|
4
7
|
// src/aperture/contracts.ts
|
|
8
|
+
function parsePaperclipRunMetadata(value) {
|
|
9
|
+
const record = asRecord(value);
|
|
10
|
+
if (!record) return null;
|
|
11
|
+
const runId = readString(record.runId);
|
|
12
|
+
const legacyIdentity = record.legacyIdentity === true;
|
|
13
|
+
if (!runId && !legacyIdentity) return null;
|
|
14
|
+
const status = record.status;
|
|
15
|
+
if (status !== void 0 && status !== "running" && status !== "failed" && status !== "completed" && status !== "cancelled") return null;
|
|
16
|
+
const timestamp = (value2) => {
|
|
17
|
+
const text = readString(value2);
|
|
18
|
+
return text && Number.isFinite(Date.parse(text)) ? new Date(text).toISOString() : void 0;
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
...runId && !legacyIdentity ? { runId } : {},
|
|
22
|
+
...legacyIdentity ? { legacyIdentity: true } : {},
|
|
23
|
+
agentId: readString(record.agentId),
|
|
24
|
+
issueId: readString(record.issueId),
|
|
25
|
+
status,
|
|
26
|
+
hostStatus: readString(record.hostStatus),
|
|
27
|
+
errorCode: readString(record.errorCode),
|
|
28
|
+
startedAt: timestamp(record.startedAt),
|
|
29
|
+
finishedAt: timestamp(record.finishedAt)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function readPaperclipRunMetadata(frame) {
|
|
33
|
+
return parsePaperclipRunMetadata(asRecord(frame.metadata)?.paperclipRun);
|
|
34
|
+
}
|
|
5
35
|
function asRecord(value) {
|
|
6
36
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7
37
|
}
|
|
@@ -26,6 +56,26 @@ function readRelationHints(value) {
|
|
|
26
56
|
});
|
|
27
57
|
return hints.length > 0 ? hints : void 0;
|
|
28
58
|
}
|
|
59
|
+
function readInteractionDecision(value) {
|
|
60
|
+
const record = asRecord(value);
|
|
61
|
+
if (!record) return void 0;
|
|
62
|
+
const policy = record.effectiveResolverPolicy;
|
|
63
|
+
const effectiveResolverPolicy = policy === "anyone" || policy === "not_creator" || policy === "human_only" ? policy : void 0;
|
|
64
|
+
const validBooleans = ["inline", "rejectRequiresReason", "allowDeclineReason", "preparing"].every((key) => typeof record[key] === "boolean");
|
|
65
|
+
const validIdentities = ["addresseeUserId", "createdByUserId"].every((key) => record[key] === void 0 || readString(record[key]) !== void 0);
|
|
66
|
+
return {
|
|
67
|
+
inline: record.inline === true && validBooleans && validIdentities && effectiveResolverPolicy !== void 0,
|
|
68
|
+
acceptLabel: readString(record.acceptLabel) ?? "Confirm",
|
|
69
|
+
rejectLabel: readString(record.rejectLabel) ?? "Decline",
|
|
70
|
+
rejectRequiresReason: record.rejectRequiresReason === true,
|
|
71
|
+
allowDeclineReason: record.allowDeclineReason === true,
|
|
72
|
+
rejectReasonLabel: readString(record.rejectReasonLabel) ?? "Reason",
|
|
73
|
+
preparing: record.preparing === true,
|
|
74
|
+
addresseeUserId: readString(record.addresseeUserId),
|
|
75
|
+
effectiveResolverPolicy,
|
|
76
|
+
createdByUserId: readString(record.createdByUserId)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
29
79
|
function readFocusMetadata(frame) {
|
|
30
80
|
const metadata = asRecord(frame.metadata);
|
|
31
81
|
const attention = asRecord(metadata?.attention);
|
|
@@ -35,6 +85,10 @@ function readFocusMetadata(frame) {
|
|
|
35
85
|
const decision = asRecord(metadata?.decision);
|
|
36
86
|
return {
|
|
37
87
|
entityType: readString(metadata?.entityType),
|
|
88
|
+
issueId: readString(metadata?.issueId),
|
|
89
|
+
paperclipInteractionId: readString(metadata?.paperclipInteractionId),
|
|
90
|
+
interactionKind: readString(metadata?.interactionKind),
|
|
91
|
+
interactionDecision: readInteractionDecision(metadata?.interactionDecision),
|
|
38
92
|
issueStatus: readString(metadata?.issueStatus),
|
|
39
93
|
issuePriority: readString(metadata?.issuePriority),
|
|
40
94
|
approvalStatus: readString(metadata?.approvalStatus),
|
|
@@ -70,6 +124,31 @@ function readFocusMetadata(frame) {
|
|
|
70
124
|
};
|
|
71
125
|
}
|
|
72
126
|
|
|
127
|
+
// src/aperture/task-ref.ts
|
|
128
|
+
function decodeComponent(value) {
|
|
129
|
+
try {
|
|
130
|
+
const decoded = decodeURIComponent(value);
|
|
131
|
+
return decoded.trim().length > 0 ? decoded : null;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function parseTaskId(taskId) {
|
|
137
|
+
if (typeof taskId !== "string") return null;
|
|
138
|
+
const separatorIndex = taskId.indexOf(":");
|
|
139
|
+
if (separatorIndex <= 0 || separatorIndex >= taskId.length - 1) return null;
|
|
140
|
+
const kind = decodeComponent(taskId.slice(0, separatorIndex));
|
|
141
|
+
const id = decodeComponent(taskId.slice(separatorIndex + 1));
|
|
142
|
+
if (!kind || !id) return null;
|
|
143
|
+
return { kind, id };
|
|
144
|
+
}
|
|
145
|
+
function taskKind(taskId) {
|
|
146
|
+
return parseTaskId(taskId)?.kind ?? null;
|
|
147
|
+
}
|
|
148
|
+
function taskEntityId(taskId) {
|
|
149
|
+
return parseTaskId(taskId)?.id ?? null;
|
|
150
|
+
}
|
|
151
|
+
|
|
73
152
|
// src/aperture/frame-model.ts
|
|
74
153
|
var AMBIENT_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
75
154
|
function isBudgetOverride(frame) {
|
|
@@ -277,8 +356,9 @@ function QueueMovementBadge({ movement }) {
|
|
|
277
356
|
}
|
|
278
357
|
|
|
279
358
|
// src/ui/focus-model.tsx
|
|
280
|
-
import { usePluginData } from "@paperclipai/plugin-sdk/ui";
|
|
281
|
-
import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
359
|
+
import { usePluginData, usePluginStream } from "@paperclipai/plugin-sdk/ui";
|
|
360
|
+
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
361
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
282
362
|
function currentSurfaceBrand() {
|
|
283
363
|
return {
|
|
284
364
|
key: "focus",
|
|
@@ -349,6 +429,108 @@ function useAttentionPolling(companyId, refreshers, intervalMs = 15e3) {
|
|
|
349
429
|
};
|
|
350
430
|
}, [companyId, intervalMs]);
|
|
351
431
|
}
|
|
432
|
+
function AttentionStreamSubscription(props) {
|
|
433
|
+
const stream = usePluginStream("attention-updates", { companyId: props.companyId });
|
|
434
|
+
const wasConnected = useRef2(false);
|
|
435
|
+
const everConnected = useRef2(false);
|
|
436
|
+
const disconnected = useRef2(false);
|
|
437
|
+
const rotated = useRef2(false);
|
|
438
|
+
const lastEvent = useRef2(null);
|
|
439
|
+
useEffect2(() => {
|
|
440
|
+
if (stream.connected && !wasConnected.current) {
|
|
441
|
+
everConnected.current = true;
|
|
442
|
+
disconnected.current = false;
|
|
443
|
+
props.onSignal(props.epoch, "open");
|
|
444
|
+
}
|
|
445
|
+
wasConnected.current = stream.connected;
|
|
446
|
+
if (stream.lastEvent && stream.lastEvent !== lastEvent.current) {
|
|
447
|
+
lastEvent.current = stream.lastEvent;
|
|
448
|
+
if (stream.lastEvent.companyId === props.companyId) props.onSignal(props.epoch, "notification");
|
|
449
|
+
}
|
|
450
|
+
if (stream.events.length >= 128 && !rotated.current) {
|
|
451
|
+
rotated.current = true;
|
|
452
|
+
props.onSignal(props.epoch, "rotate");
|
|
453
|
+
} else if (!disconnected.current && (stream.error || everConnected.current && !stream.connecting && !stream.connected)) {
|
|
454
|
+
disconnected.current = true;
|
|
455
|
+
props.onSignal(props.epoch, "disconnect");
|
|
456
|
+
}
|
|
457
|
+
}, [stream.connected, stream.connecting, stream.error, stream.events.length, stream.lastEvent, props.companyId, props.epoch, props.onSignal]);
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
function AttentionLiveUpdates(props) {
|
|
461
|
+
const [epoch, setEpoch] = useState2(0);
|
|
462
|
+
const currentEpoch = useRef2(0);
|
|
463
|
+
const active = useRef2(true);
|
|
464
|
+
const refresh = useRef2(props.refresh);
|
|
465
|
+
refresh.current = props.refresh;
|
|
466
|
+
const dirty = useRef2(false);
|
|
467
|
+
const refreshTimer = useRef2(null);
|
|
468
|
+
const retryTimer = useRef2(null);
|
|
469
|
+
const pendingReconnect = useRef2(false);
|
|
470
|
+
const queueRefresh = useCallback(() => {
|
|
471
|
+
dirty.current = true;
|
|
472
|
+
if (document.visibilityState === "hidden" || refreshTimer.current !== null) return;
|
|
473
|
+
refreshTimer.current = window.setTimeout(() => {
|
|
474
|
+
refreshTimer.current = null;
|
|
475
|
+
if (!active.current || document.visibilityState === "hidden" || !dirty.current) return;
|
|
476
|
+
dirty.current = false;
|
|
477
|
+
refresh.current();
|
|
478
|
+
}, 100);
|
|
479
|
+
}, []);
|
|
480
|
+
const reopen = useCallback(() => {
|
|
481
|
+
if (retryTimer.current !== null) window.clearTimeout(retryTimer.current);
|
|
482
|
+
retryTimer.current = null;
|
|
483
|
+
pendingReconnect.current = false;
|
|
484
|
+
currentEpoch.current += 1;
|
|
485
|
+
setEpoch(currentEpoch.current);
|
|
486
|
+
}, []);
|
|
487
|
+
const onSignal = useCallback((sourceEpoch, signal) => {
|
|
488
|
+
if (!active.current || !props.companyId || sourceEpoch !== currentEpoch.current) return;
|
|
489
|
+
if (signal === "open") {
|
|
490
|
+
if (retryTimer.current !== null) window.clearTimeout(retryTimer.current);
|
|
491
|
+
retryTimer.current = null;
|
|
492
|
+
pendingReconnect.current = false;
|
|
493
|
+
queueRefresh();
|
|
494
|
+
} else if (signal === "notification") {
|
|
495
|
+
queueRefresh();
|
|
496
|
+
} else if (signal === "rotate") {
|
|
497
|
+
queueRefresh();
|
|
498
|
+
reopen();
|
|
499
|
+
} else if (retryTimer.current === null && !pendingReconnect.current) {
|
|
500
|
+
retryTimer.current = window.setTimeout(() => {
|
|
501
|
+
retryTimer.current = null;
|
|
502
|
+
if (!active.current) return;
|
|
503
|
+
if (document.visibilityState === "hidden") {
|
|
504
|
+
pendingReconnect.current = true;
|
|
505
|
+
} else {
|
|
506
|
+
reopen();
|
|
507
|
+
}
|
|
508
|
+
}, 15e3);
|
|
509
|
+
}
|
|
510
|
+
}, [props.companyId, queueRefresh, reopen]);
|
|
511
|
+
useEffect2(() => {
|
|
512
|
+
active.current = true;
|
|
513
|
+
function handleVisibilityChange() {
|
|
514
|
+
if (document.visibilityState !== "visible") return;
|
|
515
|
+
dirty.current = false;
|
|
516
|
+
if (refreshTimer.current !== null) window.clearTimeout(refreshTimer.current);
|
|
517
|
+
refreshTimer.current = null;
|
|
518
|
+
if (pendingReconnect.current) reopen();
|
|
519
|
+
}
|
|
520
|
+
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
521
|
+
return () => {
|
|
522
|
+
active.current = false;
|
|
523
|
+
dirty.current = false;
|
|
524
|
+
pendingReconnect.current = false;
|
|
525
|
+
if (refreshTimer.current !== null) window.clearTimeout(refreshTimer.current);
|
|
526
|
+
if (retryTimer.current !== null) window.clearTimeout(retryTimer.current);
|
|
527
|
+
refreshTimer.current = null;
|
|
528
|
+
retryTimer.current = null;
|
|
529
|
+
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
530
|
+
};
|
|
531
|
+
}, [reopen]);
|
|
532
|
+
return props.companyId ? /* @__PURE__ */ jsx2(AttentionStreamSubscription, { companyId: props.companyId, epoch, onSignal }, `${props.companyId}:${epoch}`) : null;
|
|
533
|
+
}
|
|
352
534
|
function useQueueMovement(frames, ttlMs = 3500) {
|
|
353
535
|
const previousPositionsRef = useRef2({});
|
|
354
536
|
const timersRef = useRef2({});
|
|
@@ -389,15 +571,20 @@ function useQueueMovement(frames, ttlMs = 3500) {
|
|
|
389
571
|
function useAttentionModel(companyId) {
|
|
390
572
|
const displayQuery = usePluginData("attention-display", companyId ? { companyId } : void 0);
|
|
391
573
|
useAttentionPolling(companyId, [displayQuery.refresh]);
|
|
392
|
-
return useMemo(() =>
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
displayQuery.
|
|
399
|
-
|
|
400
|
-
|
|
574
|
+
return useMemo(() => {
|
|
575
|
+
const payload = displayQuery.data?.snapshot.companyId === companyId ? displayQuery.data : null;
|
|
576
|
+
const awaitingCompany = Boolean(displayQuery.data && !payload);
|
|
577
|
+
return {
|
|
578
|
+
snapshot: payload?.snapshot ?? null,
|
|
579
|
+
review: payload?.reviewState ?? null,
|
|
580
|
+
loading: displayQuery.loading || awaitingCompany,
|
|
581
|
+
error: awaitingCompany ? null : displayQuery.error,
|
|
582
|
+
syncError: payload?.reconciliationSyncError ?? payload?.approvalSyncError ?? null,
|
|
583
|
+
refresh() {
|
|
584
|
+
displayQuery.refresh();
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
}, [displayQuery, companyId]);
|
|
401
588
|
}
|
|
402
589
|
|
|
403
590
|
// src/aperture/attention-context.ts
|
|
@@ -425,31 +612,6 @@ var ATTENTION_CONTEXT_IDS = {
|
|
|
425
612
|
agentTitle: "agent-title"
|
|
426
613
|
};
|
|
427
614
|
|
|
428
|
-
// src/aperture/task-ref.ts
|
|
429
|
-
function decodeComponent(value) {
|
|
430
|
-
try {
|
|
431
|
-
const decoded = decodeURIComponent(value);
|
|
432
|
-
return decoded.trim().length > 0 ? decoded : null;
|
|
433
|
-
} catch {
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
function parseTaskId(taskId) {
|
|
438
|
-
if (typeof taskId !== "string") return null;
|
|
439
|
-
const separatorIndex = taskId.indexOf(":");
|
|
440
|
-
if (separatorIndex <= 0 || separatorIndex >= taskId.length - 1) return null;
|
|
441
|
-
const kind = decodeComponent(taskId.slice(0, separatorIndex));
|
|
442
|
-
const id = decodeComponent(taskId.slice(separatorIndex + 1));
|
|
443
|
-
if (!kind || !id) return null;
|
|
444
|
-
return { kind, id };
|
|
445
|
-
}
|
|
446
|
-
function taskKind(taskId) {
|
|
447
|
-
return parseTaskId(taskId)?.kind ?? null;
|
|
448
|
-
}
|
|
449
|
-
function taskEntityId(taskId) {
|
|
450
|
-
return parseTaskId(taskId)?.id ?? null;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
615
|
// src/aperture/issue-intelligence.ts
|
|
454
616
|
function issueBlocksTargetLine(target) {
|
|
455
617
|
return `Blocks ${target} until the review lands.`;
|
|
@@ -459,7 +621,7 @@ function issueNeedsActionFromLine(owner) {
|
|
|
459
621
|
}
|
|
460
622
|
|
|
461
623
|
// src/ui/frame-helpers.tsx
|
|
462
|
-
import { Fragment, jsx as
|
|
624
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
463
625
|
function contextValue(frame, id) {
|
|
464
626
|
const item = frame.context?.items?.find((entry) => entry.id === id);
|
|
465
627
|
return typeof item?.value === "string" && item.value.trim().length > 0 ? item.value : void 0;
|
|
@@ -566,7 +728,21 @@ function itemHref(frame, companyPrefix) {
|
|
|
566
728
|
const entityType = entityTypeFromFrame(frame);
|
|
567
729
|
const entityId = entityIdFromFrame(frame);
|
|
568
730
|
if (!entityType || !entityId || !companyPrefix) return null;
|
|
569
|
-
|
|
731
|
+
if (entityType === "interaction") {
|
|
732
|
+
const issueId = readFocusMetadata(frame).issueId;
|
|
733
|
+
return issueId ? `/${companyPrefix}/issues/${encodeURIComponent(issueId)}` : null;
|
|
734
|
+
}
|
|
735
|
+
if (entityType === "run") {
|
|
736
|
+
const run = readPaperclipRunMetadata(frame);
|
|
737
|
+
if (!run) return null;
|
|
738
|
+
if (run.runId && run.agentId && !run.legacyIdentity) {
|
|
739
|
+
return `/${companyPrefix}/agents/${encodeURIComponent(run.agentId)}/runs/${encodeURIComponent(run.runId)}`;
|
|
740
|
+
}
|
|
741
|
+
if (run.issueId) return `/${companyPrefix}/issues/${encodeURIComponent(run.issueId)}`;
|
|
742
|
+
if (run.agentId) return `/${companyPrefix}/agents/${encodeURIComponent(run.agentId)}`;
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
const pluralType = entityType === "approval" ? "approvals" : entityType === "issue" ? "issues" : entityType === "agent" ? "agents" : null;
|
|
570
746
|
if (!pluralType) return null;
|
|
571
747
|
return `/${companyPrefix}/${pluralType}/${entityId}`;
|
|
572
748
|
}
|
|
@@ -581,6 +757,8 @@ function activityHref(frame, companyPrefix) {
|
|
|
581
757
|
function primaryLinkLabel(frame) {
|
|
582
758
|
const entityType = entityTypeFromFrame(frame);
|
|
583
759
|
switch (entityType) {
|
|
760
|
+
case "interaction":
|
|
761
|
+
return "Open decision in Paperclip";
|
|
584
762
|
case "approval":
|
|
585
763
|
return "Open approval";
|
|
586
764
|
case "issue":
|
|
@@ -594,10 +772,16 @@ function primaryLinkLabel(frame) {
|
|
|
594
772
|
}
|
|
595
773
|
}
|
|
596
774
|
function responseKind(frame, lane) {
|
|
597
|
-
if (
|
|
598
|
-
return "
|
|
775
|
+
if (entityTypeFromFrame(frame) === "interaction") {
|
|
776
|
+
return readFocusMetadata(frame).interactionDecision?.inline ? "interaction" : "none";
|
|
777
|
+
}
|
|
778
|
+
if (entityTypeFromFrame(frame) === "run") {
|
|
779
|
+
return lane !== "ambient" && readPaperclipRunMetadata(frame)?.status === "failed" ? "acknowledge" : "none";
|
|
599
780
|
}
|
|
600
|
-
if (
|
|
781
|
+
if (frame.responseSpec?.kind === "approval" || frame.mode === "approval") {
|
|
782
|
+
return approvalIdForFrame(frame) ? "approval" : "none";
|
|
783
|
+
}
|
|
784
|
+
if (lane !== "ambient" && (frame.responseSpec?.kind === "acknowledge" || frame.mode === "status")) {
|
|
601
785
|
return "acknowledge";
|
|
602
786
|
}
|
|
603
787
|
return "none";
|
|
@@ -622,17 +806,17 @@ function HighlightedTitle({ text }) {
|
|
|
622
806
|
}
|
|
623
807
|
const token = match[1] ?? match[2] ?? match[0];
|
|
624
808
|
parts.push(
|
|
625
|
-
/* @__PURE__ */
|
|
809
|
+
/* @__PURE__ */ jsx3(Accent, { children: match[1] ? token : `"${token}"` }, match.index)
|
|
626
810
|
);
|
|
627
811
|
lastIndex = re.lastIndex;
|
|
628
812
|
}
|
|
629
813
|
if (lastIndex < text.length) {
|
|
630
814
|
parts.push(text.slice(lastIndex));
|
|
631
815
|
}
|
|
632
|
-
return /* @__PURE__ */
|
|
816
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: parts });
|
|
633
817
|
}
|
|
634
818
|
function renderTitle(frame) {
|
|
635
|
-
return /* @__PURE__ */
|
|
819
|
+
return /* @__PURE__ */ jsx3(HighlightedTitle, { text: compactTitle(frame) });
|
|
636
820
|
}
|
|
637
821
|
function visibleContextItems(frame) {
|
|
638
822
|
return (frame.context?.items ?? []).filter((item) => item.id !== ATTENTION_CONTEXT_IDS.recommendedMove);
|
|
@@ -706,11 +890,11 @@ function explainFrame(frame, lane) {
|
|
|
706
890
|
}
|
|
707
891
|
|
|
708
892
|
// src/ui/frame-explainability.tsx
|
|
709
|
-
import { jsx as
|
|
893
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
710
894
|
function ContextItems({ frame }) {
|
|
711
895
|
const items = visibleContextItems(frame);
|
|
712
896
|
if (items.length === 0) return null;
|
|
713
|
-
return /* @__PURE__ */
|
|
897
|
+
return /* @__PURE__ */ jsx4("div", { className: "grid gap-2 md:grid-cols-2", children: items.map((item) => /* @__PURE__ */ jsxs2(
|
|
714
898
|
"div",
|
|
715
899
|
{
|
|
716
900
|
className: cn(
|
|
@@ -718,8 +902,8 @@ function ContextItems({ frame }) {
|
|
|
718
902
|
item.id === ATTENTION_CONTEXT_IDS.latestComment && "md:col-span-2"
|
|
719
903
|
),
|
|
720
904
|
children: [
|
|
721
|
-
/* @__PURE__ */
|
|
722
|
-
/* @__PURE__ */
|
|
905
|
+
/* @__PURE__ */ jsx4("div", { className: "text-xs font-medium text-muted-foreground", children: item.label }),
|
|
906
|
+
/* @__PURE__ */ jsx4("div", { className: "mt-1 text-sm text-foreground", children: item.value ?? "Available" })
|
|
723
907
|
]
|
|
724
908
|
},
|
|
725
909
|
item.id
|
|
@@ -727,7 +911,7 @@ function ContextItems({ frame }) {
|
|
|
727
911
|
}
|
|
728
912
|
function ExplainabilityBadges(props) {
|
|
729
913
|
if (props.values.length === 0) return null;
|
|
730
|
-
return /* @__PURE__ */
|
|
914
|
+
return /* @__PURE__ */ jsx4("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx4(
|
|
731
915
|
Badge,
|
|
732
916
|
{
|
|
733
917
|
className: props.accent ? "border-transparent" : "border-border bg-background/70 text-foreground/80",
|
|
@@ -749,10 +933,10 @@ function InlineExplainability(props) {
|
|
|
749
933
|
const primaryLine = props.lane === "now" ? props.preferLaneReason ? explanation.laneReason : whyNow : whyNow;
|
|
750
934
|
const secondaryLine = props.lane === "now" ? !props.preferLaneReason && whyNow !== explanation.laneReason ? explanation.laneReason : null : explanation.laneReason;
|
|
751
935
|
return /* @__PURE__ */ jsxs2("div", { className: "space-y-2 border-l-2 pl-4", style: { borderColor: ACCENT_COLOR }, children: [
|
|
752
|
-
/* @__PURE__ */
|
|
753
|
-
/* @__PURE__ */
|
|
754
|
-
secondaryLine ? /* @__PURE__ */
|
|
755
|
-
chips.length > 0 ? /* @__PURE__ */
|
|
936
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
|
|
937
|
+
/* @__PURE__ */ jsx4("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
|
|
938
|
+
secondaryLine ? /* @__PURE__ */ jsx4("p", { className: "text-xs leading-relaxed text-muted-foreground", children: secondaryLine }) : null,
|
|
939
|
+
chips.length > 0 ? /* @__PURE__ */ jsx4(ExplainabilityBadges, { values: chips, accent: true }) : null
|
|
756
940
|
] });
|
|
757
941
|
}
|
|
758
942
|
function ExplainabilityPanel(props) {
|
|
@@ -767,61 +951,80 @@ function ExplainabilityPanel(props) {
|
|
|
767
951
|
return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
|
|
768
952
|
!props.detailOnly ? /* @__PURE__ */ jsxs2("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_16rem]", children: [
|
|
769
953
|
/* @__PURE__ */ jsxs2("div", { children: [
|
|
770
|
-
/* @__PURE__ */
|
|
771
|
-
/* @__PURE__ */
|
|
954
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: reasoningLabel }),
|
|
955
|
+
/* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: explanation.laneReason })
|
|
772
956
|
] }),
|
|
773
957
|
strength ? /* @__PURE__ */ jsxs2("div", { children: [
|
|
774
|
-
/* @__PURE__ */
|
|
775
|
-
/* @__PURE__ */
|
|
958
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Confidence" }),
|
|
959
|
+
/* @__PURE__ */ jsx4("p", { className: "mt-1 text-sm text-foreground/90", children: strength })
|
|
776
960
|
] }) : null
|
|
777
961
|
] }) : null,
|
|
778
962
|
signalValues.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
779
|
-
/* @__PURE__ */
|
|
780
|
-
/* @__PURE__ */
|
|
963
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More signals" : "Signals" }),
|
|
964
|
+
/* @__PURE__ */ jsx4(ExplainabilityBadges, { values: signalValues })
|
|
781
965
|
] }) : null,
|
|
782
966
|
relationValues.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
783
|
-
/* @__PURE__ */
|
|
784
|
-
/* @__PURE__ */
|
|
967
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More thread context" : "Thread context" }),
|
|
968
|
+
/* @__PURE__ */ jsx4(ExplainabilityBadges, { values: relationValues, accent: true })
|
|
785
969
|
] }) : null,
|
|
786
970
|
explanation.continuity ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
787
|
-
/* @__PURE__ */
|
|
788
|
-
/* @__PURE__ */
|
|
971
|
+
/* @__PURE__ */ jsx4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Related activity" }),
|
|
972
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm leading-relaxed text-muted-foreground", children: explanation.continuity })
|
|
789
973
|
] }) : null
|
|
790
974
|
] });
|
|
791
975
|
}
|
|
792
976
|
function ExplainabilityStrip(props) {
|
|
793
|
-
return /* @__PURE__ */
|
|
977
|
+
return /* @__PURE__ */ jsx4("div", { className: "space-y-2 border-t border-border/60 pt-3", children: /* @__PURE__ */ jsx4(InlineExplainability, { frame: props.frame, lane: props.lane }) });
|
|
794
978
|
}
|
|
795
979
|
|
|
796
980
|
// src/ui/issue-comment-composer.tsx
|
|
797
981
|
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
798
|
-
import {
|
|
982
|
+
import { useHostContext } from "@paperclipai/plugin-sdk/ui";
|
|
983
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
799
984
|
function IssueCommentComposer(props) {
|
|
985
|
+
const { companyId } = useHostContext();
|
|
800
986
|
const [open, setOpen] = useState3(false);
|
|
801
987
|
const [body, setBody] = useState3("");
|
|
988
|
+
const [submitting, setSubmitting] = useState3(false);
|
|
989
|
+
const submittingRef = useRef3(false);
|
|
990
|
+
const lifecycleRef = useRef3(0);
|
|
802
991
|
const textareaRef = useRef3(null);
|
|
803
992
|
const issueFrame = isIssueFrame(props.frame);
|
|
804
|
-
const isPending = props.pendingId === props.frame.id;
|
|
993
|
+
const isPending = submitting || props.pendingId === props.frame.id;
|
|
805
994
|
useEffect3(() => {
|
|
995
|
+
lifecycleRef.current += 1;
|
|
996
|
+
submittingRef.current = false;
|
|
997
|
+
setSubmitting(false);
|
|
806
998
|
setOpen(false);
|
|
807
999
|
setBody("");
|
|
808
|
-
}, [props.frame.
|
|
1000
|
+
}, [companyId, props.frame.taskId, props.frame.interactionId]);
|
|
809
1001
|
useEffect3(() => {
|
|
810
1002
|
if (open && issueFrame) textareaRef.current?.focus();
|
|
811
1003
|
}, [open, issueFrame]);
|
|
812
1004
|
if (!issueFrame) return null;
|
|
813
1005
|
async function submit() {
|
|
814
1006
|
const nextBody = body.trim();
|
|
815
|
-
if (!nextBody) return;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1007
|
+
if (!nextBody || submittingRef.current || isPending) return;
|
|
1008
|
+
const lifecycle = lifecycleRef.current;
|
|
1009
|
+
submittingRef.current = true;
|
|
1010
|
+
setSubmitting(true);
|
|
1011
|
+
try {
|
|
1012
|
+
await props.onComment(props.frame, nextBody);
|
|
1013
|
+
if (lifecycleRef.current !== lifecycle) return;
|
|
1014
|
+
setBody("");
|
|
1015
|
+
setOpen(false);
|
|
1016
|
+
} finally {
|
|
1017
|
+
if (lifecycleRef.current === lifecycle) {
|
|
1018
|
+
submittingRef.current = false;
|
|
1019
|
+
setSubmitting(false);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
819
1022
|
}
|
|
820
1023
|
if (!open) {
|
|
821
1024
|
const label = props.triggerLabel ?? "Leave comment";
|
|
822
1025
|
const triggerTone = props.triggerTone ?? "link";
|
|
823
1026
|
if (triggerTone === "link") {
|
|
824
|
-
return /* @__PURE__ */
|
|
1027
|
+
return /* @__PURE__ */ jsx5(
|
|
825
1028
|
"button",
|
|
826
1029
|
{
|
|
827
1030
|
type: "button",
|
|
@@ -834,7 +1037,7 @@ function IssueCommentComposer(props) {
|
|
|
834
1037
|
}
|
|
835
1038
|
);
|
|
836
1039
|
}
|
|
837
|
-
return /* @__PURE__ */
|
|
1040
|
+
return /* @__PURE__ */ jsx5(
|
|
838
1041
|
ActionButton,
|
|
839
1042
|
{
|
|
840
1043
|
label,
|
|
@@ -853,12 +1056,13 @@ function IssueCommentComposer(props) {
|
|
|
853
1056
|
"Commenting on ",
|
|
854
1057
|
compactTitle(props.frame)
|
|
855
1058
|
] }),
|
|
856
|
-
/* @__PURE__ */
|
|
857
|
-
/* @__PURE__ */
|
|
1059
|
+
/* @__PURE__ */ jsx5("div", { className: "text-[11px] text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
|
|
1060
|
+
/* @__PURE__ */ jsx5(
|
|
858
1061
|
"textarea",
|
|
859
1062
|
{
|
|
860
1063
|
ref: textareaRef,
|
|
861
1064
|
value: body,
|
|
1065
|
+
disabled: isPending,
|
|
862
1066
|
onChange: (event) => setBody(event.target.value),
|
|
863
1067
|
onKeyDown: (event) => {
|
|
864
1068
|
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
@@ -877,7 +1081,7 @@ function IssueCommentComposer(props) {
|
|
|
877
1081
|
}
|
|
878
1082
|
),
|
|
879
1083
|
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-end gap-2", children: [
|
|
880
|
-
/* @__PURE__ */
|
|
1084
|
+
/* @__PURE__ */ jsx5(
|
|
881
1085
|
ActionButton,
|
|
882
1086
|
{
|
|
883
1087
|
label: "Cancel",
|
|
@@ -889,7 +1093,7 @@ function IssueCommentComposer(props) {
|
|
|
889
1093
|
}
|
|
890
1094
|
}
|
|
891
1095
|
),
|
|
892
|
-
/* @__PURE__ */
|
|
1096
|
+
/* @__PURE__ */ jsx5(
|
|
893
1097
|
ActionButton,
|
|
894
1098
|
{
|
|
895
1099
|
label: isPending ? "Posting\u2026" : "Post comment",
|
|
@@ -934,12 +1138,6 @@ function approvalDecisionSuccessMessage(title, decision) {
|
|
|
934
1138
|
function approvalDecisionFailureMessage(error) {
|
|
935
1139
|
return actionErrorMessage(error, "Failed to submit approval decision.");
|
|
936
1140
|
}
|
|
937
|
-
function approvalRevisionSuccessMessage(title) {
|
|
938
|
-
return `Requested revision for ${title}.`;
|
|
939
|
-
}
|
|
940
|
-
function approvalRevisionFailureMessage(error) {
|
|
941
|
-
return actionErrorMessage(error, "Failed to request approval revision.");
|
|
942
|
-
}
|
|
943
1141
|
|
|
944
1142
|
// src/ui/use-focus-page-actions.ts
|
|
945
1143
|
var FOCUS_HOLD_CONTEXT_MS = 2e4;
|
|
@@ -950,6 +1148,7 @@ function useFocusPageActions(input) {
|
|
|
950
1148
|
const engageFocus = usePluginAction("engage-focus");
|
|
951
1149
|
const markAttentionViewed = usePluginAction("mark-attention-viewed");
|
|
952
1150
|
const recordApprovalResponse = usePluginAction("record-approval-response");
|
|
1151
|
+
const respondToInteraction = usePluginAction("respond-to-interaction");
|
|
953
1152
|
const setFocusPresence = usePluginAction("set-focus-presence");
|
|
954
1153
|
const [pendingId, setPendingId] = useState4(null);
|
|
955
1154
|
const [statusOverride, setStatusOverride] = useState4(null);
|
|
@@ -957,6 +1156,12 @@ function useFocusPageActions(input) {
|
|
|
957
1156
|
const lastViewedInteractionRef = useRef4(null);
|
|
958
1157
|
const markAttentionViewedRef = useRef4(markAttentionViewed);
|
|
959
1158
|
const setFocusPresenceRef = useRef4(setFocusPresence);
|
|
1159
|
+
const companyRef = useRef4(input.companyId);
|
|
1160
|
+
const companyEpochRef = useRef4(0);
|
|
1161
|
+
if (companyRef.current !== input.companyId) {
|
|
1162
|
+
companyRef.current = input.companyId;
|
|
1163
|
+
companyEpochRef.current += 1;
|
|
1164
|
+
}
|
|
960
1165
|
markAttentionViewedRef.current = markAttentionViewed;
|
|
961
1166
|
setFocusPresenceRef.current = setFocusPresence;
|
|
962
1167
|
useEffect4(() => {
|
|
@@ -970,11 +1175,13 @@ function useFocusPageActions(input) {
|
|
|
970
1175
|
}, [statusOverride]);
|
|
971
1176
|
useEffect4(() => {
|
|
972
1177
|
return () => {
|
|
1178
|
+
companyEpochRef.current += 1;
|
|
973
1179
|
if (focusReleaseTimerRef.current) {
|
|
974
1180
|
window.clearTimeout(focusReleaseTimerRef.current);
|
|
1181
|
+
focusReleaseTimerRef.current = null;
|
|
975
1182
|
}
|
|
976
1183
|
};
|
|
977
|
-
}, []);
|
|
1184
|
+
}, [input.companyId]);
|
|
978
1185
|
useEffect4(() => {
|
|
979
1186
|
const companyId = input.companyId;
|
|
980
1187
|
if (!companyId) return;
|
|
@@ -1010,6 +1217,7 @@ function useFocusPageActions(input) {
|
|
|
1010
1217
|
const companyId = input.companyId;
|
|
1011
1218
|
if (!companyId) return;
|
|
1012
1219
|
if (input.displaySnapshot?.now?.interactionId !== frame.interactionId) return;
|
|
1220
|
+
const companyEpoch = companyEpochRef.current;
|
|
1013
1221
|
const durationMs = reason === "comment_compose" ? FOCUS_HOLD_COMPOSE_MS : FOCUS_HOLD_CONTEXT_MS;
|
|
1014
1222
|
try {
|
|
1015
1223
|
await engageFocus({
|
|
@@ -1019,6 +1227,7 @@ function useFocusPageActions(input) {
|
|
|
1019
1227
|
reason,
|
|
1020
1228
|
durationMs
|
|
1021
1229
|
});
|
|
1230
|
+
if (companyEpochRef.current !== companyEpoch) return;
|
|
1022
1231
|
input.refresh();
|
|
1023
1232
|
if (focusReleaseTimerRef.current) {
|
|
1024
1233
|
window.clearTimeout(focusReleaseTimerRef.current);
|
|
@@ -1053,8 +1262,10 @@ function useFocusPageActions(input) {
|
|
|
1053
1262
|
}
|
|
1054
1263
|
setPendingId(frame.id);
|
|
1055
1264
|
try {
|
|
1056
|
-
await commentOnIssue({ companyId, taskId: frame.taskId, issueId, body });
|
|
1057
|
-
setStatusOverride(
|
|
1265
|
+
const result = await commentOnIssue({ companyId, taskId: frame.taskId, issueId, body });
|
|
1266
|
+
setStatusOverride(
|
|
1267
|
+
result && typeof result === "object" && "syncWarning" in result && typeof result.syncWarning === "string" ? result.syncWarning : commentSuccessMessage(compactTitle(frame))
|
|
1268
|
+
);
|
|
1058
1269
|
input.refresh();
|
|
1059
1270
|
} catch (actionError) {
|
|
1060
1271
|
setStatusOverride(commentFailureMessage(actionError));
|
|
@@ -1071,13 +1282,23 @@ function useFocusPageActions(input) {
|
|
|
1071
1282
|
}
|
|
1072
1283
|
setPendingId(frame.id);
|
|
1073
1284
|
try {
|
|
1074
|
-
await recordApprovalResponse({
|
|
1285
|
+
const result = await recordApprovalResponse({
|
|
1075
1286
|
companyId,
|
|
1076
1287
|
taskId: frame.taskId,
|
|
1077
1288
|
interactionId: frame.interactionId,
|
|
1078
1289
|
decision
|
|
1079
1290
|
});
|
|
1080
|
-
|
|
1291
|
+
if (result && typeof result === "object" && "applied" in result && result.applied === false) {
|
|
1292
|
+
await markAttentionViewed({
|
|
1293
|
+
companyId,
|
|
1294
|
+
taskId: frame.taskId,
|
|
1295
|
+
interactionId: frame.interactionId,
|
|
1296
|
+
surface: "focus"
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
setStatusOverride(
|
|
1300
|
+
result && typeof result === "object" && "applied" in result && result.applied === false ? "This approval was already resolved in Paperclip." : approvalDecisionSuccessMessage(compactTitle(frame), decision)
|
|
1301
|
+
);
|
|
1081
1302
|
input.refresh();
|
|
1082
1303
|
} catch (actionError) {
|
|
1083
1304
|
setStatusOverride(approvalDecisionFailureMessage(actionError));
|
|
@@ -1085,25 +1306,31 @@ function useFocusPageActions(input) {
|
|
|
1085
1306
|
setPendingId(null);
|
|
1086
1307
|
}
|
|
1087
1308
|
}
|
|
1088
|
-
async function
|
|
1309
|
+
async function submitInteractionDecision(frame, decision, reason) {
|
|
1089
1310
|
const companyId = input.companyId;
|
|
1090
|
-
const
|
|
1091
|
-
if (!companyId || !
|
|
1092
|
-
|
|
1093
|
-
|
|
1311
|
+
const metadata = readFocusMetadata(frame);
|
|
1312
|
+
if (!companyId || !metadata.issueId || !metadata.paperclipInteractionId) return false;
|
|
1313
|
+
if (decision === "reject" && metadata.interactionDecision?.rejectRequiresReason && !reason?.trim()) {
|
|
1314
|
+
setStatusOverride("A reason is required to decline this decision.");
|
|
1315
|
+
return false;
|
|
1094
1316
|
}
|
|
1095
1317
|
setPendingId(frame.id);
|
|
1096
1318
|
try {
|
|
1097
|
-
await
|
|
1319
|
+
const result = await respondToInteraction({
|
|
1098
1320
|
companyId,
|
|
1099
1321
|
taskId: frame.taskId,
|
|
1100
1322
|
interactionId: frame.interactionId,
|
|
1101
|
-
|
|
1323
|
+
issueId: metadata.issueId,
|
|
1324
|
+
decision,
|
|
1325
|
+
...reason?.trim() ? { reason: reason.trim() } : {}
|
|
1102
1326
|
});
|
|
1103
|
-
|
|
1327
|
+
const converged = result && typeof result === "object" && "applied" in result && result.applied === false;
|
|
1328
|
+
setStatusOverride(converged ? "This decision was already resolved in Paperclip." : decision === "accept" ? "Decision confirmed in Paperclip." : "Decision declined in Paperclip.");
|
|
1104
1329
|
input.refresh();
|
|
1105
|
-
|
|
1106
|
-
|
|
1330
|
+
return true;
|
|
1331
|
+
} catch (error) {
|
|
1332
|
+
setStatusOverride(error instanceof Error ? error.message : "Failed to respond to this decision.");
|
|
1333
|
+
return false;
|
|
1107
1334
|
} finally {
|
|
1108
1335
|
setPendingId(null);
|
|
1109
1336
|
}
|
|
@@ -1115,45 +1342,123 @@ function useFocusPageActions(input) {
|
|
|
1115
1342
|
acknowledgeFrame,
|
|
1116
1343
|
commentOnIssueFrame,
|
|
1117
1344
|
submitApprovalDecision,
|
|
1118
|
-
|
|
1345
|
+
submitInteractionDecision
|
|
1119
1346
|
};
|
|
1120
1347
|
}
|
|
1121
1348
|
|
|
1349
|
+
// src/ui/interaction-actions.tsx
|
|
1350
|
+
import { useId, useState as useState5 } from "react";
|
|
1351
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1352
|
+
function InteractionActions(props) {
|
|
1353
|
+
const decision = readFocusMetadata(props.frame).interactionDecision;
|
|
1354
|
+
const [declining, setDeclining] = useState5(false);
|
|
1355
|
+
const [reason, setReason] = useState5("");
|
|
1356
|
+
const [error, setError] = useState5(null);
|
|
1357
|
+
const reasonId = useId();
|
|
1358
|
+
if (!decision?.inline || !props.userId || decision.addresseeUserId && decision.addresseeUserId !== props.userId || decision.effectiveResolverPolicy === "not_creator" && decision.createdByUserId === props.userId) {
|
|
1359
|
+
return null;
|
|
1360
|
+
}
|
|
1361
|
+
const pending = props.pendingId === props.frame.id;
|
|
1362
|
+
async function decline() {
|
|
1363
|
+
if (!decision) return;
|
|
1364
|
+
if ((decision.rejectRequiresReason || decision.allowDeclineReason) && !declining) {
|
|
1365
|
+
setDeclining(true);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (decision.rejectRequiresReason && !reason.trim()) {
|
|
1369
|
+
setError("A reason is required to decline this decision.");
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
setError(null);
|
|
1373
|
+
if (await props.onRespond(props.frame, "reject", reason.trim() || void 0)) {
|
|
1374
|
+
setDeclining(false);
|
|
1375
|
+
setReason("");
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
return /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
1379
|
+
declining ? /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
|
|
1380
|
+
/* @__PURE__ */ jsx6("label", { htmlFor: reasonId, className: "text-xs font-medium", children: decision.rejectReasonLabel }),
|
|
1381
|
+
/* @__PURE__ */ jsx6(
|
|
1382
|
+
"textarea",
|
|
1383
|
+
{
|
|
1384
|
+
id: reasonId,
|
|
1385
|
+
value: reason,
|
|
1386
|
+
onChange: (event) => {
|
|
1387
|
+
setReason(event.target.value);
|
|
1388
|
+
setError(null);
|
|
1389
|
+
},
|
|
1390
|
+
disabled: pending,
|
|
1391
|
+
required: decision.rejectRequiresReason,
|
|
1392
|
+
"aria-invalid": !!error,
|
|
1393
|
+
"aria-describedby": error ? `${reasonId}-error` : void 0,
|
|
1394
|
+
rows: 3,
|
|
1395
|
+
className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
|
|
1396
|
+
}
|
|
1397
|
+
),
|
|
1398
|
+
error ? /* @__PURE__ */ jsx6("p", { id: `${reasonId}-error`, role: "alert", className: "text-xs text-destructive", children: error }) : null
|
|
1399
|
+
] }) : null,
|
|
1400
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx6(
|
|
1402
|
+
ActionButton,
|
|
1403
|
+
{
|
|
1404
|
+
label: decision.preparing ? "Preparing approval\u2026" : decision.acceptLabel,
|
|
1405
|
+
tone: "accent",
|
|
1406
|
+
disabled: pending || decision.preparing,
|
|
1407
|
+
onClick: () => void props.onRespond(props.frame, "accept")
|
|
1408
|
+
}
|
|
1409
|
+
),
|
|
1410
|
+
/* @__PURE__ */ jsx6(ActionButton, { label: decision.rejectLabel, tone: "accent", disabled: pending, onClick: () => void decline() }),
|
|
1411
|
+
declining ? /* @__PURE__ */ jsx6(ActionButton, { label: "Cancel", tone: "secondary", disabled: pending, onClick: () => {
|
|
1412
|
+
setDeclining(false);
|
|
1413
|
+
setError(null);
|
|
1414
|
+
} }) : null
|
|
1415
|
+
] })
|
|
1416
|
+
] });
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1122
1419
|
// src/ui/index.tsx
|
|
1123
|
-
import { Fragment as Fragment2, jsx as
|
|
1420
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1124
1421
|
function FrameActions(props) {
|
|
1125
1422
|
const actionMode = responseKind(props.frame, props.lane);
|
|
1126
1423
|
const isPending = props.pendingId === props.frame.id;
|
|
1424
|
+
const revisionHref = isBudgetOverride(props.frame) ? itemHref(props.frame, props.companyPrefix) : null;
|
|
1127
1425
|
if (actionMode === "none") return null;
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1426
|
+
if (actionMode === "interaction") {
|
|
1427
|
+
return /* @__PURE__ */ jsx7(InteractionActions, { frame: props.frame, pendingId: props.pendingId, userId: props.userId, onRespond: props.onRespond }, props.frame.interactionId);
|
|
1428
|
+
}
|
|
1429
|
+
return /* @__PURE__ */ jsx7("div", { className: cn("flex items-center gap-2", props.compact && "justify-end"), children: actionMode === "approval" ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1430
|
+
revisionHref ? /* @__PURE__ */ jsxs5(
|
|
1431
|
+
"a",
|
|
1131
1432
|
{
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1433
|
+
href: revisionHref,
|
|
1434
|
+
target: "_blank",
|
|
1435
|
+
rel: "noopener noreferrer",
|
|
1436
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1437
|
+
children: [
|
|
1438
|
+
"Request revision in Paperclip",
|
|
1439
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1440
|
+
]
|
|
1136
1441
|
}
|
|
1137
1442
|
) : null,
|
|
1138
|
-
/* @__PURE__ */
|
|
1443
|
+
/* @__PURE__ */ jsx7(
|
|
1139
1444
|
ActionButton,
|
|
1140
1445
|
{
|
|
1141
1446
|
label: isPending ? "Submitting\u2026" : "Approve",
|
|
1142
|
-
tone: "
|
|
1447
|
+
tone: "accent",
|
|
1143
1448
|
disabled: isPending,
|
|
1144
1449
|
onClick: () => void props.onApprove(props.frame)
|
|
1145
1450
|
}
|
|
1146
1451
|
),
|
|
1147
|
-
/* @__PURE__ */
|
|
1452
|
+
/* @__PURE__ */ jsx7(
|
|
1148
1453
|
ActionButton,
|
|
1149
1454
|
{
|
|
1150
1455
|
label: "Reject",
|
|
1151
|
-
tone: "
|
|
1456
|
+
tone: "accent",
|
|
1152
1457
|
disabled: isPending,
|
|
1153
1458
|
onClick: () => void props.onReject(props.frame)
|
|
1154
1459
|
}
|
|
1155
1460
|
)
|
|
1156
|
-
] }) : /* @__PURE__ */
|
|
1461
|
+
] }) : /* @__PURE__ */ jsx7(
|
|
1157
1462
|
ActionButton,
|
|
1158
1463
|
{
|
|
1159
1464
|
label: isPending ? "Saving\u2026" : "Acknowledge",
|
|
@@ -1169,24 +1474,25 @@ function NowDetails(props) {
|
|
|
1169
1474
|
const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
|
|
1170
1475
|
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1171
1476
|
const hasContext = visibleContextItems(frame).length > 0;
|
|
1172
|
-
return /* @__PURE__ */
|
|
1173
|
-
/* @__PURE__ */
|
|
1174
|
-
/* @__PURE__ */
|
|
1175
|
-
|
|
1176
|
-
/* @__PURE__ */
|
|
1177
|
-
/* @__PURE__ */
|
|
1178
|
-
|
|
1477
|
+
return /* @__PURE__ */ jsxs5("div", { className: "min-w-0 space-y-3 border-t border-border pt-4 [overflow-wrap:anywhere]", children: [
|
|
1478
|
+
props.fallbackSummary ? /* @__PURE__ */ jsx7("p", { className: "whitespace-pre-wrap text-sm leading-relaxed text-muted-foreground", children: props.fallbackSummary }) : null,
|
|
1479
|
+
/* @__PURE__ */ jsx7(ExplainabilityPanel, { frame, lane: "now", detailOnly: true }),
|
|
1480
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1481
|
+
/* @__PURE__ */ jsx7("span", { children: requestDescriptor(frame) }),
|
|
1482
|
+
/* @__PURE__ */ jsx7("span", { children: impactLabel(frame) }),
|
|
1483
|
+
/* @__PURE__ */ jsx7("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
|
|
1484
|
+
requestedAmount(frame) ? /* @__PURE__ */ jsxs5("span", { children: [
|
|
1179
1485
|
"Amount requested: ",
|
|
1180
1486
|
requestedAmount(frame)
|
|
1181
1487
|
] }) : null,
|
|
1182
|
-
budgetReason(frame) ? /* @__PURE__ */
|
|
1488
|
+
budgetReason(frame) ? /* @__PURE__ */ jsx7("span", { children: budgetReason(frame) }) : null
|
|
1183
1489
|
] }),
|
|
1184
|
-
hasContext ? /* @__PURE__ */
|
|
1185
|
-
/* @__PURE__ */
|
|
1186
|
-
/* @__PURE__ */
|
|
1490
|
+
hasContext ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1491
|
+
/* @__PURE__ */ jsx7("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Context" }),
|
|
1492
|
+
/* @__PURE__ */ jsx7(ContextItems, { frame })
|
|
1187
1493
|
] }) : null,
|
|
1188
|
-
/* @__PURE__ */
|
|
1189
|
-
href ? /* @__PURE__ */
|
|
1494
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
1495
|
+
href ? /* @__PURE__ */ jsxs5(
|
|
1190
1496
|
"a",
|
|
1191
1497
|
{
|
|
1192
1498
|
href,
|
|
@@ -1195,11 +1501,11 @@ function NowDetails(props) {
|
|
|
1195
1501
|
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1196
1502
|
children: [
|
|
1197
1503
|
primaryLinkLabel(frame),
|
|
1198
|
-
/* @__PURE__ */
|
|
1504
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1199
1505
|
]
|
|
1200
1506
|
}
|
|
1201
1507
|
) : null,
|
|
1202
|
-
activityLink ? /* @__PURE__ */
|
|
1508
|
+
activityLink ? /* @__PURE__ */ jsxs5(
|
|
1203
1509
|
"a",
|
|
1204
1510
|
{
|
|
1205
1511
|
href: activityLink,
|
|
@@ -1208,11 +1514,11 @@ function NowDetails(props) {
|
|
|
1208
1514
|
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1209
1515
|
children: [
|
|
1210
1516
|
"Open activity",
|
|
1211
|
-
/* @__PURE__ */
|
|
1517
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1212
1518
|
]
|
|
1213
1519
|
}
|
|
1214
1520
|
) : null,
|
|
1215
|
-
budgetHref ? /* @__PURE__ */
|
|
1521
|
+
budgetHref ? /* @__PURE__ */ jsx7(Accent, { children: /* @__PURE__ */ jsxs5(
|
|
1216
1522
|
"a",
|
|
1217
1523
|
{
|
|
1218
1524
|
href: budgetHref,
|
|
@@ -1222,7 +1528,7 @@ function NowDetails(props) {
|
|
|
1222
1528
|
style: { color: "inherit" },
|
|
1223
1529
|
children: [
|
|
1224
1530
|
"Review in Costs",
|
|
1225
|
-
/* @__PURE__ */
|
|
1531
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1226
1532
|
]
|
|
1227
1533
|
}
|
|
1228
1534
|
) }) : null
|
|
@@ -1230,57 +1536,60 @@ function NowDetails(props) {
|
|
|
1230
1536
|
] });
|
|
1231
1537
|
}
|
|
1232
1538
|
function QuietNow() {
|
|
1233
|
-
return /* @__PURE__ */
|
|
1234
|
-
/* @__PURE__ */
|
|
1235
|
-
/* @__PURE__ */
|
|
1539
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex min-h-20 items-center gap-3", children: [
|
|
1540
|
+
/* @__PURE__ */ jsx7(QuietMark, { size: "md" }),
|
|
1541
|
+
/* @__PURE__ */ jsx7("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." })
|
|
1236
1542
|
] });
|
|
1237
1543
|
}
|
|
1238
1544
|
function NowActionRail(props) {
|
|
1239
1545
|
const actionMode = responseKind(props.frame, props.lane);
|
|
1240
1546
|
const isPending = props.pendingId === props.frame.id;
|
|
1241
1547
|
const commentEnabled = actionMode === "acknowledge" && isIssueFrame(props.frame);
|
|
1242
|
-
return /* @__PURE__ */
|
|
1548
|
+
return /* @__PURE__ */ jsxs5(
|
|
1243
1549
|
"div",
|
|
1244
1550
|
{
|
|
1245
1551
|
className: "space-y-3 rounded-md border border-border/60 bg-secondary/20 px-3 py-3",
|
|
1246
1552
|
style: props.wide ? { width: 320, flexShrink: 0, position: "sticky", top: 16, alignSelf: "flex-start" } : void 0,
|
|
1247
1553
|
children: [
|
|
1248
|
-
/* @__PURE__ */
|
|
1249
|
-
actionMode === "approval" ? /* @__PURE__ */
|
|
1250
|
-
/* @__PURE__ */
|
|
1251
|
-
/* @__PURE__ */
|
|
1554
|
+
/* @__PURE__ */ jsx7("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
|
|
1555
|
+
actionMode === "interaction" ? /* @__PURE__ */ jsx7(InteractionActions, { frame: props.frame, pendingId: props.pendingId, userId: props.userId, onRespond: props.onRespond }, props.frame.interactionId) : actionMode === "approval" ? /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
1556
|
+
/* @__PURE__ */ jsxs5("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
1557
|
+
/* @__PURE__ */ jsx7(
|
|
1252
1558
|
ActionButton,
|
|
1253
1559
|
{
|
|
1254
1560
|
label: isPending ? "Submitting\u2026" : "Approve",
|
|
1255
|
-
tone: "
|
|
1561
|
+
tone: "accent",
|
|
1256
1562
|
disabled: isPending,
|
|
1257
1563
|
className: "w-full",
|
|
1258
1564
|
onClick: () => void props.onApprove(props.frame)
|
|
1259
1565
|
}
|
|
1260
1566
|
),
|
|
1261
|
-
/* @__PURE__ */
|
|
1567
|
+
/* @__PURE__ */ jsx7(
|
|
1262
1568
|
ActionButton,
|
|
1263
1569
|
{
|
|
1264
1570
|
label: "Reject",
|
|
1265
|
-
tone: "
|
|
1571
|
+
tone: "accent",
|
|
1266
1572
|
disabled: isPending,
|
|
1267
1573
|
className: "w-full",
|
|
1268
1574
|
onClick: () => void props.onReject(props.frame)
|
|
1269
1575
|
}
|
|
1270
1576
|
)
|
|
1271
1577
|
] }),
|
|
1272
|
-
isBudgetOverride(props.frame) ? /* @__PURE__ */
|
|
1273
|
-
|
|
1578
|
+
isBudgetOverride(props.frame) && props.itemLink ? /* @__PURE__ */ jsxs5(
|
|
1579
|
+
"a",
|
|
1274
1580
|
{
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
className: "
|
|
1279
|
-
|
|
1581
|
+
href: props.itemLink,
|
|
1582
|
+
target: "_blank",
|
|
1583
|
+
rel: "noopener noreferrer",
|
|
1584
|
+
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1585
|
+
children: [
|
|
1586
|
+
"Request revision in Paperclip",
|
|
1587
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1588
|
+
]
|
|
1280
1589
|
}
|
|
1281
1590
|
) : null
|
|
1282
|
-
] }) : actionMode === "acknowledge" ? /* @__PURE__ */
|
|
1283
|
-
commentEnabled ? /* @__PURE__ */
|
|
1591
|
+
] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1592
|
+
commentEnabled ? /* @__PURE__ */ jsx7(
|
|
1284
1593
|
IssueCommentComposer,
|
|
1285
1594
|
{
|
|
1286
1595
|
frame: props.frame,
|
|
@@ -1293,7 +1602,7 @@ function NowActionRail(props) {
|
|
|
1293
1602
|
panelClassName: "w-full space-y-2 rounded-md border border-border bg-background/80 p-3"
|
|
1294
1603
|
}
|
|
1295
1604
|
) : null,
|
|
1296
|
-
/* @__PURE__ */
|
|
1605
|
+
/* @__PURE__ */ jsx7(
|
|
1297
1606
|
ActionButton,
|
|
1298
1607
|
{
|
|
1299
1608
|
label: isPending ? "Saving\u2026" : "Acknowledge",
|
|
@@ -1303,7 +1612,7 @@ function NowActionRail(props) {
|
|
|
1303
1612
|
}
|
|
1304
1613
|
)
|
|
1305
1614
|
] }) : null,
|
|
1306
|
-
props.itemLink ? /* @__PURE__ */
|
|
1615
|
+
props.itemLink ? /* @__PURE__ */ jsxs5(
|
|
1307
1616
|
"a",
|
|
1308
1617
|
{
|
|
1309
1618
|
href: props.itemLink,
|
|
@@ -1312,7 +1621,7 @@ function NowActionRail(props) {
|
|
|
1312
1621
|
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1313
1622
|
children: [
|
|
1314
1623
|
primaryLinkLabel(props.frame),
|
|
1315
|
-
/* @__PURE__ */
|
|
1624
|
+
/* @__PURE__ */ jsx7(ExternalLinkIcon, {})
|
|
1316
1625
|
]
|
|
1317
1626
|
}
|
|
1318
1627
|
) : null
|
|
@@ -1322,7 +1631,7 @@ function NowActionRail(props) {
|
|
|
1322
1631
|
}
|
|
1323
1632
|
function NowPane(props) {
|
|
1324
1633
|
const frame = props.snapshot.now;
|
|
1325
|
-
const [detailsOpen, setDetailsOpen] =
|
|
1634
|
+
const [detailsOpen, setDetailsOpen] = useState6(false);
|
|
1326
1635
|
const wideLayout = useWideLayout(1120);
|
|
1327
1636
|
const activeFrameId = frame?.id ?? null;
|
|
1328
1637
|
const activeUnread = !!frame && (props.snapshot.review?.unread.now ?? 0) > 0;
|
|
@@ -1333,58 +1642,63 @@ function NowPane(props) {
|
|
|
1333
1642
|
useEffect5(() => {
|
|
1334
1643
|
setDetailsOpen(false);
|
|
1335
1644
|
}, [activeFrameId]);
|
|
1336
|
-
return /* @__PURE__ */
|
|
1645
|
+
return /* @__PURE__ */ jsxs5(
|
|
1337
1646
|
"section",
|
|
1338
1647
|
{
|
|
1339
|
-
className: "border border-border bg-card shadow-sm",
|
|
1648
|
+
className: "min-w-0 max-w-full border border-border bg-card shadow-sm [overflow-wrap:anywhere]",
|
|
1340
1649
|
style: frame ? { borderLeftWidth: 3, borderLeftColor: ACCENT_COLOR } : void 0,
|
|
1341
1650
|
children: [
|
|
1342
|
-
/* @__PURE__ */
|
|
1343
|
-
/* @__PURE__ */
|
|
1344
|
-
/* @__PURE__ */
|
|
1345
|
-
/* @__PURE__ */
|
|
1346
|
-
/* @__PURE__ */
|
|
1347
|
-
/* @__PURE__ */
|
|
1651
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b border-border bg-accent px-4 py-3 sm:px-6", children: [
|
|
1652
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
|
|
1653
|
+
/* @__PURE__ */ jsx7(Accent, { className: "text-sm leading-none", children: props.posture.glyph }),
|
|
1654
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2", children: [
|
|
1655
|
+
/* @__PURE__ */ jsx7(Accent, { className: "text-base leading-none font-semibold tracking-[0.04em]", children: props.brand.wordmark }),
|
|
1656
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[11px] leading-none text-muted-foreground", children: props.posture.label })
|
|
1348
1657
|
] })
|
|
1349
1658
|
] }),
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
/* @__PURE__ */
|
|
1659
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
|
|
1660
|
+
/* @__PURE__ */ jsxs5("span", { className: props.counts.now > 0 ? "font-semibold" : "", children: [
|
|
1352
1661
|
"now ",
|
|
1353
|
-
props.counts.now > 0 ? /* @__PURE__ */
|
|
1662
|
+
props.counts.now > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.now }) : props.counts.now
|
|
1354
1663
|
] }),
|
|
1355
|
-
/* @__PURE__ */
|
|
1664
|
+
/* @__PURE__ */ jsxs5("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
|
|
1356
1665
|
"next ",
|
|
1357
|
-
props.counts.next > 0 ? /* @__PURE__ */
|
|
1666
|
+
props.counts.next > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.next }) : props.counts.next
|
|
1358
1667
|
] }),
|
|
1359
|
-
/* @__PURE__ */
|
|
1668
|
+
/* @__PURE__ */ jsxs5("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
|
|
1360
1669
|
"ambient ",
|
|
1361
|
-
props.counts.ambient > 0 ? /* @__PURE__ */
|
|
1670
|
+
props.counts.ambient > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.ambient }) : props.counts.ambient
|
|
1362
1671
|
] })
|
|
1363
1672
|
] })
|
|
1364
1673
|
] }),
|
|
1365
|
-
/* @__PURE__ */
|
|
1366
|
-
/* @__PURE__ */
|
|
1674
|
+
/* @__PURE__ */ jsx7("div", { className: "px-5 py-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx7(QuietNow, {}) : /* @__PURE__ */ jsxs5("div", { className: "space-y-5", children: [
|
|
1675
|
+
/* @__PURE__ */ jsxs5(
|
|
1367
1676
|
"div",
|
|
1368
1677
|
{
|
|
1369
1678
|
className: wideLayout ? "flex items-start gap-6" : "space-y-4",
|
|
1370
1679
|
children: [
|
|
1371
|
-
/* @__PURE__ */
|
|
1372
|
-
/* @__PURE__ */
|
|
1373
|
-
/* @__PURE__ */
|
|
1374
|
-
/* @__PURE__ */
|
|
1680
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 space-y-4", style: wideLayout ? { flex: 1 } : void 0, children: [
|
|
1681
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
|
|
1682
|
+
/* @__PURE__ */ jsx7("span", { children: "Now" }),
|
|
1683
|
+
/* @__PURE__ */ jsx7("span", { className: "normal-case tracking-normal text-muted-foreground/70", children: sourceLabel(frame) })
|
|
1375
1684
|
] }),
|
|
1376
|
-
recommendedMove ? /* @__PURE__ */
|
|
1377
|
-
|
|
1378
|
-
/* @__PURE__ */
|
|
1379
|
-
|
|
1685
|
+
recommendedMove ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1686
|
+
/* @__PURE__ */ jsx7("div", { className: "max-w-4xl text-3xl font-semibold leading-tight text-foreground", children: recommendedMove }),
|
|
1687
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80", children: [
|
|
1688
|
+
/* @__PURE__ */ jsx7(UnreadDot, { visible: activeUnread }),
|
|
1689
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0", children: renderTitle(frame) })
|
|
1690
|
+
] })
|
|
1691
|
+
] }) : /* @__PURE__ */ jsxs5("div", { className: "flex max-w-4xl items-start gap-2 text-3xl font-semibold leading-tight text-foreground", children: [
|
|
1692
|
+
/* @__PURE__ */ jsx7(UnreadDot, { visible: activeUnread }),
|
|
1693
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0", children: renderTitle(frame) })
|
|
1380
1694
|
] }),
|
|
1381
|
-
helperLine ? /* @__PURE__ */
|
|
1382
|
-
/* @__PURE__ */
|
|
1383
|
-
/* @__PURE__ */
|
|
1384
|
-
driverLabel(frame) ? /* @__PURE__ */
|
|
1695
|
+
helperLine ? /* @__PURE__ */ jsx7("p", { className: "max-w-3xl text-sm leading-relaxed text-muted-foreground", children: helperLine }) : null,
|
|
1696
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1697
|
+
/* @__PURE__ */ jsx7(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
|
|
1698
|
+
driverLabel(frame) ? /* @__PURE__ */ jsx7(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
|
|
1385
1699
|
] }),
|
|
1386
|
-
/* @__PURE__ */
|
|
1387
|
-
/* @__PURE__ */
|
|
1700
|
+
/* @__PURE__ */ jsx7(InlineExplainability, { frame, lane: "now", preferLaneReason: !!helperLine }),
|
|
1701
|
+
/* @__PURE__ */ jsx7("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx7(Accent, { children: /* @__PURE__ */ jsxs5(
|
|
1388
1702
|
"button",
|
|
1389
1703
|
{
|
|
1390
1704
|
type: "button",
|
|
@@ -1398,14 +1712,14 @@ function NowPane(props) {
|
|
|
1398
1712
|
className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
|
|
1399
1713
|
style: { color: "inherit" },
|
|
1400
1714
|
children: [
|
|
1401
|
-
/* @__PURE__ */
|
|
1715
|
+
/* @__PURE__ */ jsx7(
|
|
1402
1716
|
"svg",
|
|
1403
1717
|
{
|
|
1404
1718
|
viewBox: "0 0 16 16",
|
|
1405
1719
|
className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
|
|
1406
1720
|
fill: "currentColor",
|
|
1407
1721
|
"aria-hidden": "true",
|
|
1408
|
-
children: /* @__PURE__ */
|
|
1722
|
+
children: /* @__PURE__ */ jsx7("path", { d: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" })
|
|
1409
1723
|
}
|
|
1410
1724
|
),
|
|
1411
1725
|
detailsOpen ? "Hide context" : "Show context"
|
|
@@ -1413,9 +1727,11 @@ function NowPane(props) {
|
|
|
1413
1727
|
}
|
|
1414
1728
|
) }) })
|
|
1415
1729
|
] }),
|
|
1416
|
-
/* @__PURE__ */
|
|
1730
|
+
/* @__PURE__ */ jsx7(
|
|
1417
1731
|
NowActionRail,
|
|
1418
1732
|
{
|
|
1733
|
+
userId: props.userId,
|
|
1734
|
+
onRespond: props.onRespond,
|
|
1419
1735
|
frame,
|
|
1420
1736
|
lane: "now",
|
|
1421
1737
|
wide: wideLayout,
|
|
@@ -1424,7 +1740,6 @@ function NowPane(props) {
|
|
|
1424
1740
|
onEngageFocus: props.onEngageFocus,
|
|
1425
1741
|
onApprove: props.onApprove,
|
|
1426
1742
|
onReject: props.onReject,
|
|
1427
|
-
onRequestRevision: props.onRequestRevision,
|
|
1428
1743
|
onAcknowledge: props.onAcknowledge,
|
|
1429
1744
|
onComment: props.onComment
|
|
1430
1745
|
}
|
|
@@ -1432,12 +1747,13 @@ function NowPane(props) {
|
|
|
1432
1747
|
]
|
|
1433
1748
|
}
|
|
1434
1749
|
),
|
|
1435
|
-
detailsOpen ? /* @__PURE__ */
|
|
1750
|
+
detailsOpen ? /* @__PURE__ */ jsx7(
|
|
1436
1751
|
NowDetails,
|
|
1437
1752
|
{
|
|
1438
1753
|
frame,
|
|
1439
1754
|
snapshotUpdatedAt: props.snapshot.updatedAt,
|
|
1440
|
-
companyPrefix: props.companyPrefix
|
|
1755
|
+
companyPrefix: props.companyPrefix,
|
|
1756
|
+
fallbackSummary: !recommendedMove ? frame.summary ?? null : null
|
|
1441
1757
|
}
|
|
1442
1758
|
) : null
|
|
1443
1759
|
] }) })
|
|
@@ -1447,7 +1763,7 @@ function NowPane(props) {
|
|
|
1447
1763
|
}
|
|
1448
1764
|
function NextRow(props) {
|
|
1449
1765
|
const { frame } = props;
|
|
1450
|
-
const [expanded, setExpanded] =
|
|
1766
|
+
const [expanded, setExpanded] = useState6(false);
|
|
1451
1767
|
const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
|
|
1452
1768
|
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1453
1769
|
const itemLink = itemHref(frame, props.companyPrefix);
|
|
@@ -1457,8 +1773,8 @@ function NextRow(props) {
|
|
|
1457
1773
|
const detailText = judgmentLine(frame, "next");
|
|
1458
1774
|
const showDetailText = detailText.trim().length > 0 && detailText !== secondaryText && detailText !== GENERIC_QUEUED_JUDGMENT;
|
|
1459
1775
|
const rankOpacity = Math.max(0.4, 1 - (props.rank - 1) * 0.14);
|
|
1460
|
-
return /* @__PURE__ */
|
|
1461
|
-
/* @__PURE__ */
|
|
1776
|
+
return /* @__PURE__ */ jsxs5("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
|
|
1777
|
+
/* @__PURE__ */ jsxs5(
|
|
1462
1778
|
"button",
|
|
1463
1779
|
{
|
|
1464
1780
|
type: "button",
|
|
@@ -1466,40 +1782,40 @@ function NextRow(props) {
|
|
|
1466
1782
|
"aria-expanded": expanded,
|
|
1467
1783
|
className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",
|
|
1468
1784
|
children: [
|
|
1469
|
-
/* @__PURE__ */
|
|
1470
|
-
/* @__PURE__ */
|
|
1471
|
-
/* @__PURE__ */
|
|
1472
|
-
/* @__PURE__ */
|
|
1473
|
-
secondaryText ? /* @__PURE__ */
|
|
1785
|
+
/* @__PURE__ */ jsx7(UnreadDot, { visible: unread }),
|
|
1786
|
+
/* @__PURE__ */ jsx7("span", { className: "w-5 shrink-0", style: { opacity: rankOpacity }, children: /* @__PURE__ */ jsx7(Accent, { className: "text-xs font-medium tabular-nums", children: String(props.rank).padStart(2, "0") }) }),
|
|
1787
|
+
/* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
|
|
1788
|
+
/* @__PURE__ */ jsx7("div", { className: "text-sm font-medium leading-5 text-foreground", children: renderTitle(frame) }),
|
|
1789
|
+
secondaryText ? /* @__PURE__ */ jsx7("div", { className: "mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground", children: secondaryText }) : null
|
|
1474
1790
|
] }),
|
|
1475
|
-
/* @__PURE__ */
|
|
1476
|
-
driver ? /* @__PURE__ */
|
|
1477
|
-
/* @__PURE__ */
|
|
1478
|
-
/* @__PURE__ */
|
|
1791
|
+
/* @__PURE__ */ jsx7(QueueMovementBadge, { movement: props.movement }),
|
|
1792
|
+
driver ? /* @__PURE__ */ jsx7(Badge, { className: cn("shrink-0", driverBadge.className), style: driverBadge.style, children: driver }) : null,
|
|
1793
|
+
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.7 }, children: formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt)) }),
|
|
1794
|
+
/* @__PURE__ */ jsx7(
|
|
1479
1795
|
"svg",
|
|
1480
1796
|
{
|
|
1481
1797
|
viewBox: "0 0 16 16",
|
|
1482
1798
|
className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
|
|
1483
1799
|
fill: "currentColor",
|
|
1484
1800
|
"aria-hidden": "true",
|
|
1485
|
-
children: /* @__PURE__ */
|
|
1801
|
+
children: /* @__PURE__ */ jsx7("path", { d: "M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" })
|
|
1486
1802
|
}
|
|
1487
1803
|
)
|
|
1488
1804
|
]
|
|
1489
1805
|
}
|
|
1490
1806
|
),
|
|
1491
|
-
expanded ? /* @__PURE__ */
|
|
1492
|
-
showDetailText ? /* @__PURE__ */
|
|
1493
|
-
/* @__PURE__ */
|
|
1494
|
-
/* @__PURE__ */
|
|
1495
|
-
/* @__PURE__ */
|
|
1496
|
-
driver ? /* @__PURE__ */
|
|
1497
|
-
props.movement ? /* @__PURE__ */
|
|
1498
|
-
/* @__PURE__ */
|
|
1807
|
+
expanded ? /* @__PURE__ */ jsxs5("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
|
|
1808
|
+
showDetailText ? /* @__PURE__ */ jsx7("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
|
|
1809
|
+
/* @__PURE__ */ jsx7(ExplainabilityStrip, { frame, lane: "next" }),
|
|
1810
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1811
|
+
/* @__PURE__ */ jsx7("span", { children: impactLabel(frame) }),
|
|
1812
|
+
driver ? /* @__PURE__ */ jsx7(Accent, { children: driver }) : null,
|
|
1813
|
+
props.movement ? /* @__PURE__ */ jsx7(Accent, { children: props.movement === "up" ? "rising in queue" : "falling in queue" }) : null,
|
|
1814
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
1499
1815
|
"updated ",
|
|
1500
1816
|
formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt))
|
|
1501
1817
|
] }),
|
|
1502
|
-
itemLink ? /* @__PURE__ */
|
|
1818
|
+
itemLink ? /* @__PURE__ */ jsx7(
|
|
1503
1819
|
"a",
|
|
1504
1820
|
{
|
|
1505
1821
|
href: itemLink,
|
|
@@ -1507,7 +1823,7 @@ function NextRow(props) {
|
|
|
1507
1823
|
children: primaryLinkLabel(frame)
|
|
1508
1824
|
}
|
|
1509
1825
|
) : null,
|
|
1510
|
-
activityLink ? /* @__PURE__ */
|
|
1826
|
+
activityLink ? /* @__PURE__ */ jsx7(
|
|
1511
1827
|
"a",
|
|
1512
1828
|
{
|
|
1513
1829
|
href: activityLink,
|
|
@@ -1516,17 +1832,19 @@ function NextRow(props) {
|
|
|
1516
1832
|
}
|
|
1517
1833
|
) : null
|
|
1518
1834
|
] }),
|
|
1519
|
-
/* @__PURE__ */
|
|
1520
|
-
/* @__PURE__ */
|
|
1521
|
-
/* @__PURE__ */
|
|
1835
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
|
|
1836
|
+
/* @__PURE__ */ jsx7(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
|
|
1837
|
+
/* @__PURE__ */ jsx7(
|
|
1522
1838
|
FrameActions,
|
|
1523
1839
|
{
|
|
1840
|
+
userId: props.userId,
|
|
1841
|
+
onRespond: props.onRespond,
|
|
1524
1842
|
frame,
|
|
1525
1843
|
lane: "next",
|
|
1526
1844
|
pendingId: props.pendingId,
|
|
1527
1845
|
onApprove: props.onApprove,
|
|
1528
1846
|
onReject: props.onReject,
|
|
1529
|
-
|
|
1847
|
+
companyPrefix: props.companyPrefix,
|
|
1530
1848
|
onAcknowledge: props.onAcknowledge
|
|
1531
1849
|
}
|
|
1532
1850
|
)
|
|
@@ -1535,11 +1853,13 @@ function NextRow(props) {
|
|
|
1535
1853
|
] });
|
|
1536
1854
|
}
|
|
1537
1855
|
function NextLane(props) {
|
|
1538
|
-
return /* @__PURE__ */
|
|
1539
|
-
/* @__PURE__ */
|
|
1540
|
-
props.rows.length === 0 ? /* @__PURE__ */
|
|
1856
|
+
return /* @__PURE__ */ jsxs5("section", { className: "border border-border/80 bg-card", children: [
|
|
1857
|
+
/* @__PURE__ */ jsx7("div", { className: "border-b border-border/80 px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx7("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Next" }) }),
|
|
1858
|
+
props.rows.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "px-4 py-4 text-sm text-muted-foreground sm:px-6", style: { opacity: 0.7 }, children: "Nothing is staged behind the current focus." }) : props.rows.map((row, i) => /* @__PURE__ */ jsx7(
|
|
1541
1859
|
NextRow,
|
|
1542
1860
|
{
|
|
1861
|
+
userId: props.userId,
|
|
1862
|
+
onRespond: props.onRespond,
|
|
1543
1863
|
rank: i + 1,
|
|
1544
1864
|
frame: row.frame,
|
|
1545
1865
|
movement: props.movement[row.frame.interactionId],
|
|
@@ -1549,11 +1869,10 @@ function NextLane(props) {
|
|
|
1549
1869
|
pendingId: props.pendingId,
|
|
1550
1870
|
onApprove: props.onApprove,
|
|
1551
1871
|
onReject: props.onReject,
|
|
1552
|
-
onRequestRevision: props.onRequestRevision,
|
|
1553
1872
|
onAcknowledge: props.onAcknowledge,
|
|
1554
1873
|
onComment: props.onComment
|
|
1555
1874
|
},
|
|
1556
|
-
row.frame.
|
|
1875
|
+
JSON.stringify([row.frame.taskId, row.frame.interactionId])
|
|
1557
1876
|
))
|
|
1558
1877
|
] });
|
|
1559
1878
|
}
|
|
@@ -1561,21 +1880,21 @@ function AmbientRow(props) {
|
|
|
1561
1880
|
const { frame, snapshotUpdatedAt } = props;
|
|
1562
1881
|
const href = itemHref(frame, props.companyPrefix);
|
|
1563
1882
|
const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
|
|
1564
|
-
const content = /* @__PURE__ */
|
|
1565
|
-
/* @__PURE__ */
|
|
1566
|
-
/* @__PURE__ */
|
|
1567
|
-
/* @__PURE__ */
|
|
1568
|
-
/* @__PURE__ */
|
|
1883
|
+
const content = /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 px-4 py-2.5 sm:px-6", children: [
|
|
1884
|
+
/* @__PURE__ */ jsx7(UnreadDot, { visible: unread, tone: "muted", reserveSpace: true }),
|
|
1885
|
+
/* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate text-[13px] text-muted-foreground", children: renderTitle(frame) }),
|
|
1886
|
+
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-xs text-muted-foreground/80", children: sourceLabel(frame) }),
|
|
1887
|
+
/* @__PURE__ */ jsx7("span", { className: "shrink-0 text-xs text-muted-foreground/70", children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) })
|
|
1569
1888
|
] });
|
|
1570
|
-
return /* @__PURE__ */
|
|
1889
|
+
return /* @__PURE__ */ jsx7("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "last:border-b-0", children: href ? /* @__PURE__ */ jsx7("a", { href, className: "block transition-colors hover:bg-accent/40", children: content }) : content });
|
|
1571
1890
|
}
|
|
1572
1891
|
function AmbientLane(props) {
|
|
1573
|
-
return /* @__PURE__ */
|
|
1574
|
-
/* @__PURE__ */
|
|
1575
|
-
props.rows.length === 0 ? /* @__PURE__ */
|
|
1576
|
-
/* @__PURE__ */
|
|
1577
|
-
/* @__PURE__ */
|
|
1578
|
-
] }) : props.rows.map((row) => /* @__PURE__ */
|
|
1892
|
+
return /* @__PURE__ */ jsxs5("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "mt-2 bg-card", "aria-label": "Ambient attention", children: [
|
|
1893
|
+
/* @__PURE__ */ jsx7("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx7("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground/80", children: "Ambient" }) }),
|
|
1894
|
+
props.rows.length === 0 ? /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 px-4 py-4 text-sm text-muted-foreground sm:px-6", children: [
|
|
1895
|
+
/* @__PURE__ */ jsx7(QuietMark, {}),
|
|
1896
|
+
/* @__PURE__ */ jsx7("span", { children: "Nothing in peripheral view." })
|
|
1897
|
+
] }) : props.rows.map((row) => /* @__PURE__ */ jsx7(
|
|
1579
1898
|
AmbientRow,
|
|
1580
1899
|
{
|
|
1581
1900
|
frame: row.frame,
|
|
@@ -1592,47 +1911,63 @@ function DashboardWidget(props) {
|
|
|
1592
1911
|
const brand = currentSurfaceBrand();
|
|
1593
1912
|
const href = pluginPagePath(props.context.companyPrefix);
|
|
1594
1913
|
const model = useAttentionModel(companyId);
|
|
1914
|
+
const liveUpdates = /* @__PURE__ */ jsx7(AttentionLiveUpdates, { companyId, refresh: model.refresh }, companyId);
|
|
1595
1915
|
const data = model.snapshot;
|
|
1596
1916
|
const loading = model.loading;
|
|
1597
1917
|
const error = model.error;
|
|
1598
|
-
if (!companyId) return /* @__PURE__ */
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1918
|
+
if (!companyId) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1919
|
+
liveUpdates,
|
|
1920
|
+
/* @__PURE__ */ jsx7(MessageCard, { title: `Open ${brand.wordmark}`, body: `Open a company to see ${brand.wordmark}.` })
|
|
1921
|
+
] });
|
|
1922
|
+
if (loading) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1923
|
+
liveUpdates,
|
|
1924
|
+
/* @__PURE__ */ jsx7(WidgetLoadingState, { label: brand.loadingLabel })
|
|
1925
|
+
] });
|
|
1926
|
+
if (error) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1927
|
+
liveUpdates,
|
|
1928
|
+
/* @__PURE__ */ jsx7(MessageCard, { title: "Plugin error", body: error.message })
|
|
1929
|
+
] });
|
|
1930
|
+
if (!data) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1931
|
+
liveUpdates,
|
|
1932
|
+
/* @__PURE__ */ jsx7(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState })
|
|
1933
|
+
] });
|
|
1602
1934
|
const posture = postureForSnapshot(data);
|
|
1603
1935
|
const leadingText = data.now ? recommendedMoveValue(data.now) ?? data.now.title : null;
|
|
1604
1936
|
const actionable = actionableLabel(data);
|
|
1605
1937
|
const borderStyle = posture.label === "busy" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : posture.label === "elevated" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_BORDER } : void 0;
|
|
1606
|
-
return /* @__PURE__ */
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
/* @__PURE__ */
|
|
1610
|
-
/* @__PURE__ */
|
|
1611
|
-
/* @__PURE__ */
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1938
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1939
|
+
liveUpdates,
|
|
1940
|
+
/* @__PURE__ */ jsxs5("a", { href, className: "block border border-border bg-card px-4 py-3 shadow-sm transition-colors hover:bg-accent/40", style: borderStyle, children: [
|
|
1941
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-start gap-2 text-xs", children: [
|
|
1942
|
+
/* @__PURE__ */ jsx7(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
|
|
1943
|
+
/* @__PURE__ */ jsx7("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-end gap-2", children: [
|
|
1944
|
+
/* @__PURE__ */ jsx7(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
|
|
1945
|
+
/* @__PURE__ */ jsx7(Accent, { className: "text-[11px]", children: posture.label }),
|
|
1946
|
+
actionable ? /* @__PURE__ */ jsx7(Accent, { className: "text-[11px]", children: actionable }) : null
|
|
1947
|
+
] }) }),
|
|
1948
|
+
/* @__PURE__ */ jsxs5("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
|
|
1949
|
+
"now ",
|
|
1950
|
+
data.counts.now,
|
|
1951
|
+
" \xB7 next ",
|
|
1952
|
+
data.counts.next,
|
|
1953
|
+
" \xB7 ambient ",
|
|
1954
|
+
data.counts.ambient
|
|
1955
|
+
] })
|
|
1956
|
+
] }),
|
|
1957
|
+
leadingText ? /* @__PURE__ */ jsx7(
|
|
1958
|
+
"div",
|
|
1959
|
+
{
|
|
1960
|
+
className: "mt-2 text-sm font-medium leading-5 text-foreground",
|
|
1961
|
+
style: {
|
|
1962
|
+
display: "-webkit-box",
|
|
1963
|
+
WebkitLineClamp: 2,
|
|
1964
|
+
WebkitBoxOrient: "vertical",
|
|
1965
|
+
overflow: "hidden"
|
|
1966
|
+
},
|
|
1967
|
+
children: leadingText
|
|
1968
|
+
}
|
|
1969
|
+
) : /* @__PURE__ */ jsx7("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
|
|
1970
|
+
] })
|
|
1636
1971
|
] });
|
|
1637
1972
|
}
|
|
1638
1973
|
function AttentionSidebarLink({ context }) {
|
|
@@ -1641,66 +1976,72 @@ function AttentionSidebarLink({ context }) {
|
|
|
1641
1976
|
const href = pluginPagePath(context.companyPrefix);
|
|
1642
1977
|
const isActive = typeof window !== "undefined" && window.location.pathname === href;
|
|
1643
1978
|
const model = useAttentionModel(companyId);
|
|
1979
|
+
const liveUpdates = /* @__PURE__ */ jsx7(AttentionLiveUpdates, { companyId, refresh: model.refresh }, companyId);
|
|
1644
1980
|
const merged = model.snapshot;
|
|
1645
1981
|
const actionable = merged ? actionableCount(merged) : 0;
|
|
1646
1982
|
const unread = unreadCount(merged);
|
|
1647
|
-
return /* @__PURE__ */
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
"
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1983
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1984
|
+
liveUpdates,
|
|
1985
|
+
/* @__PURE__ */ jsxs5(
|
|
1986
|
+
"a",
|
|
1987
|
+
{
|
|
1988
|
+
href,
|
|
1989
|
+
"aria-current": isActive ? "page" : void 0,
|
|
1990
|
+
className: cn(
|
|
1991
|
+
"flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors",
|
|
1992
|
+
isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
|
|
1993
|
+
),
|
|
1994
|
+
children: [
|
|
1995
|
+
/* @__PURE__ */ jsxs5("span", { className: "relative shrink-0", "aria-hidden": "true", children: [
|
|
1996
|
+
/* @__PURE__ */ jsxs5(
|
|
1997
|
+
"svg",
|
|
1998
|
+
{
|
|
1999
|
+
viewBox: "0 0 24 24",
|
|
2000
|
+
className: "h-4 w-4",
|
|
2001
|
+
fill: "none",
|
|
2002
|
+
stroke: "currentColor",
|
|
2003
|
+
strokeWidth: "1.9",
|
|
2004
|
+
strokeLinecap: "round",
|
|
2005
|
+
strokeLinejoin: "round",
|
|
2006
|
+
children: [
|
|
2007
|
+
/* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "9" }),
|
|
2008
|
+
/* @__PURE__ */ jsx7("path", { d: "m14.3 8 5.2 9" }),
|
|
2009
|
+
/* @__PURE__ */ jsx7("path", { d: "M9.7 8h10.4" }),
|
|
2010
|
+
/* @__PURE__ */ jsx7("path", { d: "m7.4 12 5.2-9" }),
|
|
2011
|
+
/* @__PURE__ */ jsx7("path", { d: "m9.7 16-5.2-9" }),
|
|
2012
|
+
/* @__PURE__ */ jsx7("path", { d: "M14.3 16H3.9" }),
|
|
2013
|
+
/* @__PURE__ */ jsx7("path", { d: "m16.6 12-5.2 9" })
|
|
2014
|
+
]
|
|
2015
|
+
}
|
|
2016
|
+
),
|
|
2017
|
+
unread > 0 ? /* @__PURE__ */ jsx7(
|
|
2018
|
+
"span",
|
|
2019
|
+
{
|
|
2020
|
+
className: "absolute -right-1 -top-1 h-2 w-2 rounded-full",
|
|
2021
|
+
style: { backgroundColor: ACCENT_COLOR }
|
|
2022
|
+
}
|
|
2023
|
+
) : null
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ jsx7("span", { className: "flex-1 truncate", children: brand.wordmark }),
|
|
2026
|
+
actionable > 0 ? /* @__PURE__ */ jsx7("span", { className: "inline-flex items-center justify-center rounded-full px-1.5 py-0.5 text-xs leading-none font-medium text-white", style: ACCENT_BG_STYLE, children: actionable }) : unread > 0 ? /* @__PURE__ */ jsx7(
|
|
1680
2027
|
"span",
|
|
1681
2028
|
{
|
|
1682
|
-
className: "
|
|
1683
|
-
style: {
|
|
2029
|
+
className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
|
|
2030
|
+
style: { color: ACCENT_COLOR, borderColor: ACCENT_BORDER, backgroundColor: "transparent" },
|
|
2031
|
+
children: unread
|
|
1684
2032
|
}
|
|
1685
2033
|
) : null
|
|
1686
|
-
]
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
{
|
|
1691
|
-
className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
|
|
1692
|
-
style: { color: ACCENT_COLOR, borderColor: ACCENT_BORDER, backgroundColor: "transparent" },
|
|
1693
|
-
children: unread
|
|
1694
|
-
}
|
|
1695
|
-
) : null
|
|
1696
|
-
]
|
|
1697
|
-
}
|
|
1698
|
-
);
|
|
2034
|
+
]
|
|
2035
|
+
}
|
|
2036
|
+
)
|
|
2037
|
+
] });
|
|
1699
2038
|
}
|
|
1700
2039
|
function AttentionPage(props) {
|
|
2040
|
+
const { userId } = useHostContext2();
|
|
1701
2041
|
const companyId = props.context.companyId;
|
|
1702
2042
|
const brand = currentSurfaceBrand();
|
|
1703
2043
|
const model = useAttentionModel(companyId);
|
|
2044
|
+
const liveUpdates = /* @__PURE__ */ jsx7(AttentionLiveUpdates, { companyId, refresh: model.refresh }, companyId);
|
|
1704
2045
|
const snapshot = model.snapshot;
|
|
1705
2046
|
const loading = model.loading;
|
|
1706
2047
|
const error = model.error;
|
|
@@ -1723,68 +2064,85 @@ function AttentionPage(props) {
|
|
|
1723
2064
|
acknowledgeFrame,
|
|
1724
2065
|
commentOnIssueFrame,
|
|
1725
2066
|
submitApprovalDecision,
|
|
1726
|
-
|
|
2067
|
+
submitInteractionDecision
|
|
1727
2068
|
} = useFocusPageActions({
|
|
1728
2069
|
companyId,
|
|
1729
2070
|
displaySnapshot,
|
|
1730
2071
|
refresh: model.refresh
|
|
1731
2072
|
});
|
|
1732
|
-
if (!companyId) return /* @__PURE__ */
|
|
1733
|
-
|
|
1734
|
-
|
|
2073
|
+
if (!companyId) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2074
|
+
liveUpdates,
|
|
2075
|
+
/* @__PURE__ */ jsx7(MessageCard, { title: `Open ${brand.wordmark}`, body: `Select a company to open ${brand.wordmark}.` })
|
|
2076
|
+
] });
|
|
2077
|
+
if (!displaySnapshot && loading) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2078
|
+
liveUpdates,
|
|
2079
|
+
/* @__PURE__ */ jsx7(PageLoadingState, { label: "Loading attention center" })
|
|
2080
|
+
] });
|
|
2081
|
+
if (!displaySnapshot && error) return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2082
|
+
liveUpdates,
|
|
2083
|
+
/* @__PURE__ */ jsx7(MessageCard, { title: "Plugin error", body: error.message })
|
|
2084
|
+
] });
|
|
1735
2085
|
if (!displaySnapshot || !posture) {
|
|
1736
|
-
return /* @__PURE__ */
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
2086
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2087
|
+
liveUpdates,
|
|
2088
|
+
/* @__PURE__ */ jsx7(
|
|
2089
|
+
MessageCard,
|
|
2090
|
+
{
|
|
2091
|
+
title: `No ${brand.wordmark} State Yet`,
|
|
2092
|
+
body: `No ${brand.key === "focus" ? "focus" : "attention"} state has been captured for this company yet.`
|
|
2093
|
+
}
|
|
2094
|
+
)
|
|
2095
|
+
] });
|
|
1743
2096
|
}
|
|
1744
|
-
return /* @__PURE__ */
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2097
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2098
|
+
liveUpdates,
|
|
2099
|
+
/* @__PURE__ */ jsxs5("div", { className: "space-y-4", children: [
|
|
2100
|
+
/* @__PURE__ */ jsx7(
|
|
2101
|
+
NowPane,
|
|
2102
|
+
{
|
|
2103
|
+
userId,
|
|
2104
|
+
onRespond: submitInteractionDecision,
|
|
2105
|
+
snapshot: displaySnapshot,
|
|
2106
|
+
posture,
|
|
2107
|
+
brand,
|
|
2108
|
+
companyPrefix: props.context.companyPrefix,
|
|
2109
|
+
counts: displaySnapshot.counts,
|
|
2110
|
+
pendingId,
|
|
2111
|
+
onEngageFocus: holdNowFrame,
|
|
2112
|
+
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
2113
|
+
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
2114
|
+
onAcknowledge: acknowledgeFrame,
|
|
2115
|
+
onComment: commentOnIssueFrame
|
|
2116
|
+
}
|
|
2117
|
+
),
|
|
2118
|
+
/* @__PURE__ */ jsx7(
|
|
2119
|
+
NextLane,
|
|
2120
|
+
{
|
|
2121
|
+
userId,
|
|
2122
|
+
onRespond: submitInteractionDecision,
|
|
2123
|
+
snapshot: displaySnapshot,
|
|
2124
|
+
rows: nextRows,
|
|
2125
|
+
movement: nextMovement,
|
|
2126
|
+
snapshotUpdatedAt: displaySnapshot.updatedAt,
|
|
2127
|
+
companyPrefix: props.context.companyPrefix,
|
|
2128
|
+
pendingId,
|
|
2129
|
+
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
2130
|
+
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
2131
|
+
onAcknowledge: acknowledgeFrame,
|
|
2132
|
+
onComment: commentOnIssueFrame
|
|
2133
|
+
}
|
|
2134
|
+
),
|
|
2135
|
+
/* @__PURE__ */ jsx7(
|
|
2136
|
+
AmbientLane,
|
|
2137
|
+
{
|
|
2138
|
+
snapshot: displaySnapshot,
|
|
2139
|
+
rows: ambientRows,
|
|
2140
|
+
snapshotUpdatedAt: displaySnapshot.updatedAt,
|
|
2141
|
+
companyPrefix: props.context.companyPrefix
|
|
2142
|
+
}
|
|
2143
|
+
),
|
|
2144
|
+
/* @__PURE__ */ jsx7(StatusToast, { message: statusOverride ?? backgroundErrorMessage ?? model.syncError })
|
|
2145
|
+
] })
|
|
1788
2146
|
] });
|
|
1789
2147
|
}
|
|
1790
2148
|
export {
|