@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/dist/ui/index.js CHANGED
@@ -1,7 +1,37 @@
1
1
  // src/ui/index.tsx
2
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
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
- snapshot: displayQuery.data?.snapshot ?? null,
394
- review: displayQuery.data?.reviewState ?? null,
395
- loading: displayQuery.loading,
396
- error: displayQuery.error,
397
- refresh() {
398
- displayQuery.refresh();
399
- }
400
- }), [displayQuery]);
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 jsx2 } from "react/jsx-runtime";
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
- const pluralType = entityType === "run" ? "runs" : entityType === "approval" ? "approvals" : entityType === "issue" ? "issues" : entityType === "agent" ? "agents" : null;
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 (approvalIdForFrame(frame) && (frame.responseSpec?.kind === "approval" || frame.mode === "approval")) {
598
- return "approval";
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 (lane !== "ambient" && (frame.responseSpec?.kind === "acknowledge" || frame.mode === "status" || frame.responseSpec?.kind === "approval")) {
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__ */ jsx2(Accent, { children: match[1] ? token : `"${token}"` }, match.index)
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__ */ jsx2(Fragment, { children: parts });
816
+ return /* @__PURE__ */ jsx3(Fragment, { children: parts });
633
817
  }
634
818
  function renderTitle(frame) {
635
- return /* @__PURE__ */ jsx2(HighlightedTitle, { text: compactTitle(frame) });
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 jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
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__ */ jsx3("div", { className: "grid gap-2 md:grid-cols-2", children: items.map((item) => /* @__PURE__ */ jsxs2(
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__ */ jsx3("div", { className: "text-xs font-medium text-muted-foreground", children: item.label }),
722
- /* @__PURE__ */ jsx3("div", { className: "mt-1 text-sm text-foreground", children: item.value ?? "Available" })
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__ */ jsx3("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx3(
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
753
- /* @__PURE__ */ jsx3("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
754
- secondaryLine ? /* @__PURE__ */ jsx3("p", { className: "text-xs leading-relaxed text-muted-foreground", children: secondaryLine }) : null,
755
- chips.length > 0 ? /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: chips, accent: true }) : null
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: reasoningLabel }),
771
- /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: explanation.laneReason })
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Confidence" }),
775
- /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-foreground/90", children: strength })
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More signals" : "Signals" }),
780
- /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: signalValues })
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More thread context" : "Thread context" }),
784
- /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: relationValues, accent: true })
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__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Related activity" }),
788
- /* @__PURE__ */ jsx3("p", { className: "text-sm leading-relaxed text-muted-foreground", children: explanation.continuity })
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__ */ jsx3("div", { className: "space-y-2 border-t border-border/60 pt-3", children: /* @__PURE__ */ jsx3(InlineExplainability, { frame: props.frame, lane: props.lane }) });
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 { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
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.id]);
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
- await props.onComment(props.frame, nextBody);
817
- setBody("");
818
- setOpen(false);
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__ */ jsx4(
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__ */ jsx4(
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__ */ jsx4("div", { className: "text-[11px] text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
857
- /* @__PURE__ */ jsx4(
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__ */ jsx4(
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__ */ jsx4(
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(commentSuccessMessage(compactTitle(frame)));
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
- setStatusOverride(approvalDecisionSuccessMessage(compactTitle(frame), decision));
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 requestApprovalRevision(frame) {
1309
+ async function submitInteractionDecision(frame, decision, reason) {
1089
1310
  const companyId = input.companyId;
1090
- const approvalId = approvalIdForFrame(frame);
1091
- if (!companyId || !approvalId) {
1092
- setStatusOverride(approvalFrameUnsupportedMessage());
1093
- return;
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 recordApprovalResponse({
1319
+ const result = await respondToInteraction({
1098
1320
  companyId,
1099
1321
  taskId: frame.taskId,
1100
1322
  interactionId: frame.interactionId,
1101
- decision: "request-revision"
1323
+ issueId: metadata.issueId,
1324
+ decision,
1325
+ ...reason?.trim() ? { reason: reason.trim() } : {}
1102
1326
  });
1103
- setStatusOverride(approvalRevisionSuccessMessage(compactTitle(frame)));
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
- } catch (actionError) {
1106
- setStatusOverride(approvalRevisionFailureMessage(actionError));
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
- requestApprovalRevision
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 jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
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
- return /* @__PURE__ */ jsx5("div", { className: cn("flex items-center gap-2", props.compact && "justify-end"), children: actionMode === "approval" ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1129
- isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx5(
1130
- ActionButton,
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
- label: "Request revision",
1133
- tone: "secondary",
1134
- disabled: isPending,
1135
- onClick: () => void props.onRequestRevision(props.frame)
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__ */ jsx5(
1443
+ /* @__PURE__ */ jsx7(
1139
1444
  ActionButton,
1140
1445
  {
1141
1446
  label: isPending ? "Submitting\u2026" : "Approve",
1142
- tone: "primary",
1447
+ tone: "accent",
1143
1448
  disabled: isPending,
1144
1449
  onClick: () => void props.onApprove(props.frame)
1145
1450
  }
1146
1451
  ),
1147
- /* @__PURE__ */ jsx5(
1452
+ /* @__PURE__ */ jsx7(
1148
1453
  ActionButton,
1149
1454
  {
1150
1455
  label: "Reject",
1151
- tone: "danger",
1456
+ tone: "accent",
1152
1457
  disabled: isPending,
1153
1458
  onClick: () => void props.onReject(props.frame)
1154
1459
  }
1155
1460
  )
1156
- ] }) : /* @__PURE__ */ jsx5(
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__ */ jsxs4("div", { className: "space-y-3 border-t border-border pt-4", children: [
1173
- /* @__PURE__ */ jsx5(ExplainabilityPanel, { frame, lane: "now", detailOnly: true }),
1174
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1175
- /* @__PURE__ */ jsx5("span", { children: requestDescriptor(frame) }),
1176
- /* @__PURE__ */ jsx5("span", { children: impactLabel(frame) }),
1177
- /* @__PURE__ */ jsx5("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1178
- requestedAmount(frame) ? /* @__PURE__ */ jsxs4("span", { children: [
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__ */ jsx5("span", { children: budgetReason(frame) }) : null
1488
+ budgetReason(frame) ? /* @__PURE__ */ jsx7("span", { children: budgetReason(frame) }) : null
1183
1489
  ] }),
1184
- hasContext ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1185
- /* @__PURE__ */ jsx5("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Context" }),
1186
- /* @__PURE__ */ jsx5(ContextItems, { frame })
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__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-3", children: [
1189
- href ? /* @__PURE__ */ jsxs4(
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__ */ jsx5(ExternalLinkIcon, {})
1504
+ /* @__PURE__ */ jsx7(ExternalLinkIcon, {})
1199
1505
  ]
1200
1506
  }
1201
1507
  ) : null,
1202
- activityLink ? /* @__PURE__ */ jsxs4(
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__ */ jsx5(ExternalLinkIcon, {})
1517
+ /* @__PURE__ */ jsx7(ExternalLinkIcon, {})
1212
1518
  ]
1213
1519
  }
1214
1520
  ) : null,
1215
- budgetHref ? /* @__PURE__ */ jsx5(Accent, { children: /* @__PURE__ */ jsxs4(
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__ */ jsx5(ExternalLinkIcon, {})
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__ */ jsxs4("div", { className: "flex min-h-20 items-center gap-3", children: [
1234
- /* @__PURE__ */ jsx5(QuietMark, { size: "md" }),
1235
- /* @__PURE__ */ jsx5("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." })
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__ */ jsxs4(
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__ */ jsx5("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
1249
- actionMode === "approval" ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
1250
- /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-2 gap-2", children: [
1251
- /* @__PURE__ */ jsx5(
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: "primary",
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__ */ jsx5(
1567
+ /* @__PURE__ */ jsx7(
1262
1568
  ActionButton,
1263
1569
  {
1264
1570
  label: "Reject",
1265
- tone: "danger",
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__ */ jsx5(
1273
- ActionButton,
1578
+ isBudgetOverride(props.frame) && props.itemLink ? /* @__PURE__ */ jsxs5(
1579
+ "a",
1274
1580
  {
1275
- label: "Request revision",
1276
- tone: "secondary",
1277
- disabled: isPending,
1278
- className: "w-full",
1279
- onClick: () => void props.onRequestRevision(props.frame)
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__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
1283
- commentEnabled ? /* @__PURE__ */ jsx5(
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__ */ jsx5(
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__ */ jsxs4(
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__ */ jsx5(ExternalLinkIcon, {})
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] = useState5(false);
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__ */ jsxs4(
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__ */ jsxs4("div", { className: "flex items-center justify-between gap-3 border-b border-border bg-accent px-4 py-3 sm:px-6", children: [
1343
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
1344
- /* @__PURE__ */ jsx5(Accent, { className: "text-sm leading-none", children: props.posture.glyph }),
1345
- /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
1346
- /* @__PURE__ */ jsx5(Accent, { className: "text-base leading-none font-semibold tracking-[0.04em]", children: props.brand.wordmark }),
1347
- /* @__PURE__ */ jsx5("span", { className: "text-[11px] leading-none text-muted-foreground", children: props.posture.label })
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__ */ jsxs4("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
1351
- /* @__PURE__ */ jsxs4("span", { className: props.counts.now > 0 ? "font-semibold" : "", children: [
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__ */ jsx5(Accent, { children: props.counts.now }) : props.counts.now
1662
+ props.counts.now > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.now }) : props.counts.now
1354
1663
  ] }),
1355
- /* @__PURE__ */ jsxs4("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
1664
+ /* @__PURE__ */ jsxs5("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
1356
1665
  "next ",
1357
- props.counts.next > 0 ? /* @__PURE__ */ jsx5(Accent, { children: props.counts.next }) : props.counts.next
1666
+ props.counts.next > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.next }) : props.counts.next
1358
1667
  ] }),
1359
- /* @__PURE__ */ jsxs4("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1668
+ /* @__PURE__ */ jsxs5("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1360
1669
  "ambient ",
1361
- props.counts.ambient > 0 ? /* @__PURE__ */ jsx5(Accent, { children: props.counts.ambient }) : props.counts.ambient
1670
+ props.counts.ambient > 0 ? /* @__PURE__ */ jsx7(Accent, { children: props.counts.ambient }) : props.counts.ambient
1362
1671
  ] })
1363
1672
  ] })
1364
1673
  ] }),
1365
- /* @__PURE__ */ jsx5("div", { className: "px-5 py-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx5(QuietNow, {}) : /* @__PURE__ */ jsxs4("div", { className: "space-y-5", children: [
1366
- /* @__PURE__ */ jsxs4(
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__ */ jsxs4("div", { className: "space-y-4", style: wideLayout ? { minWidth: 0, flex: 1 } : void 0, children: [
1372
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1373
- /* @__PURE__ */ jsx5("span", { children: "Now" }),
1374
- /* @__PURE__ */ jsx5("span", { className: "normal-case tracking-normal text-muted-foreground/70", children: sourceLabel(frame) })
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__ */ jsx5("div", { className: "max-w-4xl text-3xl font-semibold leading-tight text-foreground", children: recommendedMove }) : frame.summary ? /* @__PURE__ */ jsx5("div", { className: "max-w-4xl text-3xl font-semibold leading-tight text-foreground", children: frame.summary }) : null,
1377
- /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80", children: [
1378
- /* @__PURE__ */ jsx5(UnreadDot, { visible: activeUnread }),
1379
- /* @__PURE__ */ jsx5("span", { children: renderTitle(frame) })
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__ */ jsx5("p", { className: "max-w-3xl text-sm leading-relaxed text-muted-foreground", children: helperLine }) : null,
1382
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
1383
- /* @__PURE__ */ jsx5(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
1384
- driverLabel(frame) ? /* @__PURE__ */ jsx5(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
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__ */ jsx5(InlineExplainability, { frame, lane: "now", preferLaneReason: !!helperLine }),
1387
- /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx5(Accent, { children: /* @__PURE__ */ jsxs4(
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__ */ jsx5(
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__ */ jsx5("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" })
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__ */ jsx5(
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__ */ jsx5(
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] = useState5(false);
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__ */ jsxs4("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
1461
- /* @__PURE__ */ jsxs4(
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__ */ jsx5(UnreadDot, { visible: unread }),
1470
- /* @__PURE__ */ jsx5("span", { className: "w-5 shrink-0", style: { opacity: rankOpacity }, children: /* @__PURE__ */ jsx5(Accent, { className: "text-xs font-medium tabular-nums", children: String(props.rank).padStart(2, "0") }) }),
1471
- /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
1472
- /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium leading-5 text-foreground", children: renderTitle(frame) }),
1473
- secondaryText ? /* @__PURE__ */ jsx5("div", { className: "mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground", children: secondaryText }) : null
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__ */ jsx5(QueueMovementBadge, { movement: props.movement }),
1476
- driver ? /* @__PURE__ */ jsx5(Badge, { className: cn("shrink-0", driverBadge.className), style: driverBadge.style, children: driver }) : null,
1477
- /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.7 }, children: formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt)) }),
1478
- /* @__PURE__ */ jsx5(
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__ */ jsx5("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" })
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__ */ jsxs4("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
1492
- showDetailText ? /* @__PURE__ */ jsx5("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
1493
- /* @__PURE__ */ jsx5(ExplainabilityStrip, { frame, lane: "next" }),
1494
- /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
1495
- /* @__PURE__ */ jsx5("span", { children: impactLabel(frame) }),
1496
- driver ? /* @__PURE__ */ jsx5(Accent, { children: driver }) : null,
1497
- props.movement ? /* @__PURE__ */ jsx5(Accent, { children: props.movement === "up" ? "rising in queue" : "falling in queue" }) : null,
1498
- /* @__PURE__ */ jsxs4("span", { children: [
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__ */ jsx5(
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__ */ jsx5(
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__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
1520
- /* @__PURE__ */ jsx5(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
1521
- /* @__PURE__ */ jsx5(
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
- onRequestRevision: props.onRequestRevision,
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__ */ jsxs4("section", { className: "border border-border/80 bg-card", children: [
1539
- /* @__PURE__ */ jsx5("div", { className: "border-b border-border/80 px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx5("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Next" }) }),
1540
- props.rows.length === 0 ? /* @__PURE__ */ jsx5("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__ */ jsx5(
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.id
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__ */ jsxs4("div", { className: "flex items-center gap-3 px-4 py-2.5 sm:px-6", children: [
1565
- /* @__PURE__ */ jsx5(UnreadDot, { visible: unread, tone: "muted", reserveSpace: true }),
1566
- /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate text-[13px] text-muted-foreground", children: renderTitle(frame) }),
1567
- /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground/80", children: sourceLabel(frame) }),
1568
- /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground/70", children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) })
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__ */ jsx5("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "last:border-b-0", children: href ? /* @__PURE__ */ jsx5("a", { href, className: "block transition-colors hover:bg-accent/40", children: content }) : content });
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__ */ jsxs4("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "mt-2 bg-card", "aria-label": "Ambient attention", children: [
1574
- /* @__PURE__ */ jsx5("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx5("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground/80", children: "Ambient" }) }),
1575
- props.rows.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3 px-4 py-4 text-sm text-muted-foreground sm:px-6", children: [
1576
- /* @__PURE__ */ jsx5(QuietMark, {}),
1577
- /* @__PURE__ */ jsx5("span", { children: "Nothing in peripheral view." })
1578
- ] }) : props.rows.map((row) => /* @__PURE__ */ jsx5(
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__ */ jsx5(MessageCard, { title: `Open ${brand.wordmark}`, body: `Open a company to see ${brand.wordmark}.` });
1599
- if (loading) return /* @__PURE__ */ jsx5(WidgetLoadingState, { label: brand.loadingLabel });
1600
- if (error) return /* @__PURE__ */ jsx5(MessageCard, { title: "Plugin error", body: error.message });
1601
- if (!data) return /* @__PURE__ */ jsx5(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState });
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__ */ jsxs4("a", { href, className: "block border border-border bg-card px-4 py-3 shadow-sm transition-colors hover:bg-accent/40", style: borderStyle, children: [
1607
- /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2 text-xs", children: [
1608
- /* @__PURE__ */ jsx5(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
1609
- /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
1610
- /* @__PURE__ */ jsx5(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
1611
- /* @__PURE__ */ jsx5(Accent, { className: "text-[11px]", children: posture.label }),
1612
- actionable ? /* @__PURE__ */ jsx5(Accent, { className: "text-[11px]", children: actionable }) : null
1613
- ] }) }),
1614
- /* @__PURE__ */ jsxs4("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
1615
- "now ",
1616
- data.counts.now,
1617
- " \xB7 next ",
1618
- data.counts.next,
1619
- " \xB7 ambient ",
1620
- data.counts.ambient
1621
- ] })
1622
- ] }),
1623
- leadingText ? /* @__PURE__ */ jsx5(
1624
- "div",
1625
- {
1626
- className: "mt-2 text-sm font-medium leading-5 text-foreground",
1627
- style: {
1628
- display: "-webkit-box",
1629
- WebkitLineClamp: 2,
1630
- WebkitBoxOrient: "vertical",
1631
- overflow: "hidden"
1632
- },
1633
- children: leadingText
1634
- }
1635
- ) : /* @__PURE__ */ jsx5("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
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__ */ jsxs4(
1648
- "a",
1649
- {
1650
- href,
1651
- "aria-current": isActive ? "page" : void 0,
1652
- className: cn(
1653
- "flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors",
1654
- isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
1655
- ),
1656
- children: [
1657
- /* @__PURE__ */ jsxs4("span", { className: "relative shrink-0", "aria-hidden": "true", children: [
1658
- /* @__PURE__ */ jsxs4(
1659
- "svg",
1660
- {
1661
- viewBox: "0 0 24 24",
1662
- className: "h-4 w-4",
1663
- fill: "none",
1664
- stroke: "currentColor",
1665
- strokeWidth: "1.9",
1666
- strokeLinecap: "round",
1667
- strokeLinejoin: "round",
1668
- children: [
1669
- /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "9" }),
1670
- /* @__PURE__ */ jsx5("path", { d: "m14.3 8 5.2 9" }),
1671
- /* @__PURE__ */ jsx5("path", { d: "M9.7 8h10.4" }),
1672
- /* @__PURE__ */ jsx5("path", { d: "m7.4 12 5.2-9" }),
1673
- /* @__PURE__ */ jsx5("path", { d: "m9.7 16-5.2-9" }),
1674
- /* @__PURE__ */ jsx5("path", { d: "M14.3 16H3.9" }),
1675
- /* @__PURE__ */ jsx5("path", { d: "m16.6 12-5.2 9" })
1676
- ]
1677
- }
1678
- ),
1679
- unread > 0 ? /* @__PURE__ */ jsx5(
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: "absolute -right-1 -top-1 h-2 w-2 rounded-full",
1683
- style: { backgroundColor: ACCENT_COLOR }
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
- /* @__PURE__ */ jsx5("span", { className: "flex-1 truncate", children: brand.wordmark }),
1688
- actionable > 0 ? /* @__PURE__ */ jsx5("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__ */ jsx5(
1689
- "span",
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
- requestApprovalRevision
2067
+ submitInteractionDecision
1727
2068
  } = useFocusPageActions({
1728
2069
  companyId,
1729
2070
  displaySnapshot,
1730
2071
  refresh: model.refresh
1731
2072
  });
1732
- if (!companyId) return /* @__PURE__ */ jsx5(MessageCard, { title: `Open ${brand.wordmark}`, body: `Select a company to open ${brand.wordmark}.` });
1733
- if (!displaySnapshot && loading) return /* @__PURE__ */ jsx5(PageLoadingState, { label: "Loading attention center" });
1734
- if (!displaySnapshot && error) return /* @__PURE__ */ jsx5(MessageCard, { title: "Plugin error", body: error.message });
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__ */ jsx5(
1737
- MessageCard,
1738
- {
1739
- title: `No ${brand.wordmark} State Yet`,
1740
- body: `No ${brand.key === "focus" ? "focus" : "attention"} state has been captured for this company yet.`
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__ */ jsxs4("div", { className: "space-y-4", children: [
1745
- /* @__PURE__ */ jsx5(
1746
- NowPane,
1747
- {
1748
- snapshot: displaySnapshot,
1749
- posture,
1750
- brand,
1751
- companyPrefix: props.context.companyPrefix,
1752
- counts: displaySnapshot.counts,
1753
- pendingId,
1754
- onEngageFocus: holdNowFrame,
1755
- onApprove: (frame) => submitApprovalDecision(frame, "approve"),
1756
- onReject: (frame) => submitApprovalDecision(frame, "reject"),
1757
- onRequestRevision: requestApprovalRevision,
1758
- onAcknowledge: acknowledgeFrame,
1759
- onComment: commentOnIssueFrame
1760
- }
1761
- ),
1762
- /* @__PURE__ */ jsx5(
1763
- NextLane,
1764
- {
1765
- snapshot: displaySnapshot,
1766
- rows: nextRows,
1767
- movement: nextMovement,
1768
- snapshotUpdatedAt: displaySnapshot.updatedAt,
1769
- companyPrefix: props.context.companyPrefix,
1770
- pendingId,
1771
- onApprove: (frame) => submitApprovalDecision(frame, "approve"),
1772
- onReject: (frame) => submitApprovalDecision(frame, "reject"),
1773
- onRequestRevision: requestApprovalRevision,
1774
- onAcknowledge: acknowledgeFrame,
1775
- onComment: commentOnIssueFrame
1776
- }
1777
- ),
1778
- /* @__PURE__ */ jsx5(
1779
- AmbientLane,
1780
- {
1781
- snapshot: displaySnapshot,
1782
- rows: ambientRows,
1783
- snapshotUpdatedAt: displaySnapshot.updatedAt,
1784
- companyPrefix: props.context.companyPrefix
1785
- }
1786
- ),
1787
- /* @__PURE__ */ jsx5(StatusToast, { message: statusOverride ?? backgroundErrorMessage })
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 {