@tomismeta/paperclip-aperture 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,6 +64,7 @@ and agents attention now? actually sees to th
64
64
 
65
65
  - a Focus surface inside Paperclip
66
66
  - ranked `now`, `next`, and `ambient` attention lanes
67
+ - embedded explainability in the Focus UI, including `Why now`, `Why next`, confidence, signals, thread context, and related activity
67
68
  - approval handling, including budget-specific approval semantics
68
69
  - issue-aware operator language such as `review required`, `blocked`, and targeted recommended moves
69
70
  - agent-aware routing that distinguishes known company agents from human/operator roles when issue text references them
@@ -75,13 +76,31 @@ and agents attention now? actually sees to th
75
76
  - durable acknowledge/suppression behavior backed by plugin state and ledger replay
76
77
  - a sidebar entry, page, and dashboard widget
77
78
 
79
+ ## Explainability
80
+
81
+ Focus is meant to be more legible than a smart inbox.
82
+
83
+ The current `0.3.x` explainability slice keeps reasoning attached to the cards you are already acting on:
84
+
85
+ - `Why now` on the active card explains why the current item outranks the rest of the queue
86
+ - `Why next` on queued rows explains why something is staged behind the current top item
87
+ - `Confidence` shows how strong the semantic signal is when the plugin has one
88
+ - `Signals` surfaces the specific factors that pushed the item into attention
89
+ - `Thread context` and `Related activity` help explain whether an item is part of a broader episode or continuation
90
+
91
+ The intent is not to expose every internal scoring detail. It is to help an operator answer:
92
+
93
+ - why did this surface?
94
+ - why is it in this lane?
95
+ - how much should I trust this judgment?
96
+
78
97
  ## Package Boundary
79
98
 
80
99
  This plugin treats Paperclip as the host runtime and UI shell, while embedding [Aperture Core](https://github.com/tomismeta/aperture/tree/main/packages/core) through the npm package [`@tomismeta/aperture-core`](https://www.npmjs.com/package/@tomismeta/aperture-core).
81
100
 
82
101
  It is a pure SDK integration: Aperture Core is used as-is inside a self-contained Paperclip plugin, with no changes to Aperture Core or Paperclip core.
83
102
 
84
- For `0.2.x`, the boundary works like this:
103
+ For `0.3.x`, the boundary works like this:
85
104
 
86
105
  - the plugin worker owns Aperture ingestion, replay, review state, and display composition
87
106
  - Paperclip remains the system of record for issue and approval writes
@@ -129,6 +148,8 @@ Then open `http://127.0.0.1:3100/APE/aperture` and verify:
129
148
  - approval actions update Focus correctly
130
149
  - resolved blocker comments downgrade stale `Now` items
131
150
  - attached issue documents downgrade stale `share the memo/spec` review blockers into monitor-only follow-up
151
+ - the active card exposes `Why now`
152
+ - expanded queued rows expose `Why next`
132
153
 
133
154
  ## Links
134
155
 
package/dist/manifest.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const manifest = {
2
2
  id: "tomismeta.paperclip-aperture",
3
3
  apiVersion: 1,
4
- version: "0.2.2",
4
+ version: "0.3.1",
5
5
  displayName: "Paperclip Aperture",
6
6
  description: "The live attention layer for Paperclip, powered by Aperture's deterministic attention engine.",
7
7
  author: "@tomismeta",
package/dist/ui/index.js CHANGED
@@ -123,6 +123,82 @@ function mergeStoredFrames(snapshot, companyId, candidates, review, nowIso = (/*
123
123
  }, review);
124
124
  }
125
125
 
126
+ // src/aperture/explainability.ts
127
+ function asRecord(value) {
128
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
129
+ }
130
+ function readStringArray(value) {
131
+ if (!Array.isArray(value)) return [];
132
+ return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
133
+ }
134
+ function readSemanticConfidence(frame) {
135
+ const semantic = asRecord(frame.metadata?.semantic);
136
+ const confidence = semantic?.confidence;
137
+ return confidence === "low" || confidence === "medium" || confidence === "high" ? confidence : null;
138
+ }
139
+ function readSemanticRelationHints(frame) {
140
+ const semantic = asRecord(frame.metadata?.semantic);
141
+ const relationHints = semantic?.relationHints;
142
+ if (!Array.isArray(relationHints)) return [];
143
+ return relationHints.filter((entry) => {
144
+ if (typeof entry !== "object" || entry === null) return false;
145
+ const hint = entry;
146
+ return hint.kind === "same_issue" || hint.kind === "resolves" || hint.kind === "supersedes" || hint.kind === "repeats" || hint.kind === "escalates";
147
+ });
148
+ }
149
+ function relationHintLabel(hint) {
150
+ switch (hint.kind) {
151
+ case "same_issue":
152
+ return "Part of the same thread";
153
+ case "resolves":
154
+ return "Resolves an earlier blocker";
155
+ case "supersedes":
156
+ return "Moves the request forward";
157
+ case "repeats":
158
+ return "Repeats an earlier ask";
159
+ case "escalates":
160
+ return "Raises the urgency";
161
+ }
162
+ }
163
+ function laneReason(lane) {
164
+ switch (lane) {
165
+ case "active":
166
+ return "This is the most urgent item in the queue right now.";
167
+ case "queued":
168
+ return "This is queued behind the current top item.";
169
+ case "ambient":
170
+ return "This is visible for awareness without needing action yet.";
171
+ }
172
+ }
173
+ function continuitySummary(frame) {
174
+ const episode = asRecord(frame.metadata?.episode);
175
+ const size = typeof episode?.size === "number" ? episode.size : null;
176
+ const state = typeof episode?.state === "string" ? episode.state.replace(/_/g, " ") : null;
177
+ if (!size || size <= 1) return null;
178
+ if (state) return `Part of a ${state} thread with ${size} related interactions.`;
179
+ return `Part of a thread with ${size} related interactions.`;
180
+ }
181
+ function frameSignals(frame) {
182
+ const attention = asRecord(frame.metadata?.attention);
183
+ const rationale = readStringArray(attention?.rationale);
184
+ const factors = readStringArray(frame.provenance?.factors);
185
+ return [.../* @__PURE__ */ new Set([...rationale.length > 0 ? rationale : factors, ...factors])];
186
+ }
187
+ function signalStrengthLabel(confidence) {
188
+ return `${confidence} confidence`;
189
+ }
190
+ function explainFrame(frame, lane) {
191
+ const relationLabels = [...new Set(readSemanticRelationHints(frame).map(relationHintLabel))];
192
+ return {
193
+ whyNow: frame.provenance?.whyNow ?? frame.summary ?? null,
194
+ laneReason: laneReason(lane),
195
+ signalStrength: readSemanticConfidence(frame),
196
+ signals: frameSignals(frame),
197
+ relationLabels,
198
+ continuity: continuitySummary(frame)
199
+ };
200
+ }
201
+
126
202
  // src/aperture/attention-context.ts
127
203
  var ATTENTION_CONTEXT_IDS = {
128
204
  approvalType: "approval-type",
@@ -223,6 +299,10 @@ function approvalRecordToFrame(record) {
223
299
  const decisionContext = typeof payload.decisionContext === "string" ? payload.decisionContext : void 0;
224
300
  const summary = typeof payload.summary === "string" ? payload.summary : approvalBlockingSummary(budgetOverride);
225
301
  const updatedAt = record.updatedAt ?? record.createdAt;
302
+ const provenance = {
303
+ whyNow: approvalBlockingWhyNow(budgetOverride),
304
+ factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
305
+ };
226
306
  return {
227
307
  id: `approval-bootstrap:${record.id}`,
228
308
  taskId: `approval:${record.id}`,
@@ -256,17 +336,20 @@ function approvalRecordToFrame(record) {
256
336
  ...budgetOverride ? [{ id: "request-revision", label: "Request revision", kind: "cancel", emphasis: "secondary" }] : []
257
337
  ]
258
338
  },
259
- provenance: {
260
- whyNow: approvalBlockingWhyNow(budgetOverride),
261
- factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
262
- },
339
+ provenance,
263
340
  timing: {
264
341
  createdAt: record.createdAt,
265
342
  updatedAt
266
343
  },
267
344
  metadata: {
268
345
  approvalStatus: record.status,
269
- approvalType: record.type
346
+ approvalType: record.type,
347
+ attention: {
348
+ rationale: provenance.factors
349
+ },
350
+ semantic: {
351
+ confidence: "high"
352
+ }
270
353
  }
271
354
  };
272
355
  }
@@ -325,12 +408,13 @@ function approvalRevisionFailureMessage(error) {
325
408
  // src/ui/index.tsx
326
409
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
327
410
  var ACCENT_COLOR = "#007ACC";
411
+ var ACCENT_BG = `${ACCENT_COLOR}14`;
412
+ var ACCENT_BORDER = `${ACCENT_COLOR}33`;
328
413
  var ACCENT_BG_STYLE = { backgroundColor: ACCENT_COLOR };
329
414
  function currentSurfaceBrand() {
330
415
  return {
331
416
  key: "focus",
332
417
  wordmark: "Focus",
333
- supportCopy: "Powered by Aperture",
334
418
  headingEmptyState: "No focus state yet.",
335
419
  loadingLabel: "Loading Focus\u2026"
336
420
  };
@@ -346,6 +430,29 @@ function Accent({ children, className }) {
346
430
  const ref = useAccentColor();
347
431
  return /* @__PURE__ */ jsx("span", { ref, className, children });
348
432
  }
433
+ function QuietMark({ size = "sm" }) {
434
+ const dimension = size === "md" ? 10 : 8;
435
+ return /* @__PURE__ */ jsx(
436
+ "span",
437
+ {
438
+ "aria-hidden": "true",
439
+ className: "inline-block shrink-0 rounded-full border",
440
+ style: { width: dimension, height: dimension, borderColor: ACCENT_BORDER }
441
+ }
442
+ );
443
+ }
444
+ function useWideLayout(minWidth = 1024) {
445
+ const [matches, setMatches] = useState(false);
446
+ useEffect(() => {
447
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
448
+ const query = window.matchMedia(`(min-width: ${minWidth}px)`);
449
+ const update = () => setMatches(query.matches);
450
+ update();
451
+ query.addEventListener("change", update);
452
+ return () => query.removeEventListener("change", update);
453
+ }, [minWidth]);
454
+ return matches;
455
+ }
349
456
  function cn(...parts) {
350
457
  return parts.filter(Boolean).join(" ");
351
458
  }
@@ -418,7 +525,7 @@ function driverLabel(frame) {
418
525
  function driverBadgeStyle() {
419
526
  return {
420
527
  className: "border-transparent",
421
- style: { color: ACCENT_COLOR, backgroundColor: `${ACCENT_COLOR}14`, borderColor: `${ACCENT_COLOR}40` }
528
+ style: { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER }
422
529
  };
423
530
  }
424
531
  function requestDescriptor(frame) {
@@ -459,6 +566,15 @@ function supportingLine(frame, lane) {
459
566
  if (owner && lane === "active" && entityTypeFromFrame(frame) === "issue") {
460
567
  return issueNeedsActionFromLine(owner);
461
568
  }
569
+ if (lane === "active") {
570
+ const recommendedMove = recommendedMoveValue(frame)?.trim();
571
+ const summary = frame.summary?.trim();
572
+ const judgment2 = judgmentLine(frame, lane).trim();
573
+ if (recommendedMove && summary && summary !== recommendedMove && summary !== judgment2) {
574
+ return summary;
575
+ }
576
+ return null;
577
+ }
462
578
  const judgment = judgmentLine(frame, lane);
463
579
  return judgment.trim().length > 0 ? judgment : null;
464
580
  }
@@ -552,9 +668,11 @@ function isFrameUnreadInSnapshot(frame, snapshot) {
552
668
  if (!seenAt) return true;
553
669
  return frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(seenAt) > 0;
554
670
  }
555
- function UnreadDot({ visible }) {
556
- if (!visible) return null;
557
- return /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-full shrink-0", style: ACCENT_BG_STYLE, "aria-label": "Unread attention item" });
671
+ function UnreadDot(props) {
672
+ const tone = props.tone ?? "default";
673
+ if (!props.visible && !props.reserveSpace) return null;
674
+ const style = props.visible ? tone === "muted" ? { backgroundColor: ACCENT_BORDER } : ACCENT_BG_STYLE : { backgroundColor: "transparent" };
675
+ return /* @__PURE__ */ jsx("span", { className: "inline-block h-2 w-2 rounded-full shrink-0", style, "aria-label": props.visible ? "Unread attention item" : void 0 });
558
676
  }
559
677
  function ExternalLinkIcon() {
560
678
  return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", className: "h-3 w-3", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: [
@@ -619,7 +737,7 @@ function StatusToast({ message }) {
619
737
  "div",
620
738
  {
621
739
  className: cn(
622
- "pointer-events-none fixed bottom-6 right-6 z-50 transition-all duration-200",
740
+ "pointer-events-none fixed bottom-6 left-1/2 z-50 -translate-x-1/2 transition-all duration-200",
623
741
  message ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0"
624
742
  ),
625
743
  "aria-live": "polite",
@@ -836,21 +954,26 @@ function Badge(props) {
836
954
  );
837
955
  }
838
956
  function ActionButton(props) {
839
- const toneClass = props.tone === "primary" ? "bg-green-700 text-white hover:bg-green-600" : props.tone === "danger" ? "bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60" : "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50";
957
+ const [hovered, setHovered] = useState(false);
958
+ const toneClass = props.tone === "primary" ? "bg-green-700 text-white hover:bg-green-600" : props.tone === "accent" ? "text-white" : props.tone === "danger" ? "bg-destructive text-white hover:bg-destructive/90 dark:bg-destructive/60" : "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50";
840
959
  return /* @__PURE__ */ jsx(
841
960
  "button",
842
961
  {
843
962
  type: "button",
844
963
  onClick: props.onClick,
964
+ onMouseEnter: () => setHovered(true),
965
+ onMouseLeave: () => setHovered(false),
845
966
  disabled: props.disabled,
846
967
  className: cn(
847
968
  "inline-flex h-8 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium",
848
- "transition-[color,background-color,border-color,box-shadow,opacity]",
969
+ "transition-[color,background-color,border-color,box-shadow,opacity,filter]",
849
970
  "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
850
971
  "disabled:pointer-events-none disabled:opacity-50",
972
+ props.disabled && /…|\.\.\./.test(props.label) && "animate-pulse",
851
973
  toneClass,
852
974
  props.className
853
975
  ),
976
+ style: props.tone === "accent" ? { ...ACCENT_BG_STYLE, filter: hovered && !props.disabled ? "brightness(1.08)" : void 0 } : void 0,
854
977
  children: props.label
855
978
  }
856
979
  );
@@ -863,8 +986,8 @@ function QueueMovementBadge({ movement }) {
863
986
  className: "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
864
987
  style: {
865
988
  color: ACCENT_COLOR,
866
- backgroundColor: `${ACCENT_COLOR}12`,
867
- borderColor: `${ACCENT_COLOR}33`
989
+ backgroundColor: ACCENT_BG,
990
+ borderColor: ACCENT_BORDER
868
991
  },
869
992
  children: [
870
993
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: movement === "up" ? "\u2191" : "\u2193" }),
@@ -909,21 +1032,24 @@ function FrameActions(props) {
909
1032
  ActionButton,
910
1033
  {
911
1034
  label: isPending ? "Saving\u2026" : "Acknowledge",
912
- tone: "primary",
1035
+ tone: "accent",
913
1036
  disabled: isPending,
914
1037
  onClick: () => void props.onAcknowledge(props.frame)
915
1038
  }
916
1039
  ) });
917
1040
  }
1041
+ function visibleContextItems(frame) {
1042
+ return (frame.context?.items ?? []).filter((item2) => item2.id !== ATTENTION_CONTEXT_IDS.recommendedMove);
1043
+ }
918
1044
  function ContextItems({ frame }) {
919
- const items = frame.context?.items ?? [];
1045
+ const items = visibleContextItems(frame);
920
1046
  if (items.length === 0) return null;
921
1047
  return /* @__PURE__ */ jsx("div", { className: "grid gap-2 md:grid-cols-2", children: items.map((item2) => /* @__PURE__ */ jsxs(
922
1048
  "div",
923
1049
  {
924
1050
  className: cn(
925
1051
  "border border-border bg-secondary/50 px-3 py-2",
926
- (item2.id === ATTENTION_CONTEXT_IDS.latestComment || item2.id === ATTENTION_CONTEXT_IDS.recommendedMove) && "md:col-span-2"
1052
+ item2.id === ATTENTION_CONTEXT_IDS.latestComment && "md:col-span-2"
927
1053
  ),
928
1054
  children: [
929
1055
  /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: item2.label }),
@@ -933,14 +1059,90 @@ function ContextItems({ frame }) {
933
1059
  item2.id
934
1060
  )) });
935
1061
  }
1062
+ function ExplainabilityBadges(props) {
1063
+ if (props.values.length === 0) return null;
1064
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx(
1065
+ Badge,
1066
+ {
1067
+ className: props.accent ? "border-transparent" : "border-border bg-background/70 text-foreground/80",
1068
+ style: props.accent ? { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER } : void 0,
1069
+ children: value
1070
+ },
1071
+ value
1072
+ )) });
1073
+ }
1074
+ function InlineExplainability(props) {
1075
+ const explanation = explainFrame(props.frame, props.lane);
1076
+ const chips = [
1077
+ ...explanation.signalStrength ? [signalStrengthLabel(explanation.signalStrength)] : [],
1078
+ ...explanation.signals.slice(0, 2),
1079
+ ...explanation.relationLabels.slice(0, 1)
1080
+ ];
1081
+ const label = props.lane === "queued" ? "Why next" : props.lane === "ambient" ? "Why ambient" : "Why now";
1082
+ const whyNow = explanation.whyNow ?? judgmentLine(props.frame, props.lane);
1083
+ const primaryLine = props.lane === "active" ? props.preferLaneReason ? explanation.laneReason : whyNow : whyNow;
1084
+ const secondaryLine = props.lane === "active" ? !props.preferLaneReason && whyNow !== explanation.laneReason ? explanation.laneReason : null : explanation.laneReason;
1085
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 pl-4", style: { borderColor: ACCENT_COLOR }, children: [
1086
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
1087
+ /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
1088
+ secondaryLine ? /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: secondaryLine }) : null,
1089
+ chips.length > 0 ? /* @__PURE__ */ jsx(ExplainabilityBadges, { values: chips, accent: true }) : null
1090
+ ] });
1091
+ }
1092
+ function ExplainabilityPanel(props) {
1093
+ const explanation = explainFrame(props.frame, props.lane);
1094
+ const strength = !props.detailOnly && explanation.signalStrength ? signalStrengthLabel(explanation.signalStrength) : null;
1095
+ const signalValues = props.detailOnly ? explanation.signals.slice(2) : explanation.signals;
1096
+ const relationValues = props.detailOnly ? explanation.relationLabels.slice(1) : explanation.relationLabels;
1097
+ const reasoningLabel = props.lane === "active" ? "Reasoning" : props.lane === "queued" ? "Why it sits here" : "Why it stays quiet";
1098
+ if (props.detailOnly && signalValues.length === 0 && relationValues.length === 0 && !explanation.continuity) {
1099
+ return null;
1100
+ }
1101
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1102
+ !props.detailOnly ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_16rem]", children: [
1103
+ /* @__PURE__ */ jsxs("div", { children: [
1104
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: reasoningLabel }),
1105
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: explanation.laneReason })
1106
+ ] }),
1107
+ strength ? /* @__PURE__ */ jsxs("div", { children: [
1108
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Confidence" }),
1109
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-foreground/90", children: strength })
1110
+ ] }) : null
1111
+ ] }) : null,
1112
+ signalValues.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1113
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More signals" : "Signals" }),
1114
+ /* @__PURE__ */ jsx(ExplainabilityBadges, { values: signalValues })
1115
+ ] }) : null,
1116
+ relationValues.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1117
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More thread context" : "Thread context" }),
1118
+ /* @__PURE__ */ jsx(ExplainabilityBadges, { values: relationValues, accent: true })
1119
+ ] }) : null,
1120
+ explanation.continuity ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1121
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Related activity" }),
1122
+ /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: explanation.continuity })
1123
+ ] }) : null
1124
+ ] });
1125
+ }
1126
+ function ExplainabilityStrip(props) {
1127
+ return /* @__PURE__ */ jsx("div", { className: "space-y-2 border-t border-border/60 pt-3", children: /* @__PURE__ */ jsx(InlineExplainability, { frame: props.frame, lane: props.lane }) });
1128
+ }
936
1129
  function isIssueFrame(frame) {
937
1130
  return entityTypeFromFrame(frame) === "issue";
938
1131
  }
939
1132
  function IssueCommentComposer(props) {
940
1133
  const [open, setOpen] = useState(false);
941
1134
  const [body, setBody] = useState("");
942
- if (!isIssueFrame(props.frame)) return null;
1135
+ const textareaRef = useRef(null);
1136
+ const issueFrame = isIssueFrame(props.frame);
943
1137
  const isPending = props.pendingId === props.frame.id;
1138
+ useEffect(() => {
1139
+ setOpen(false);
1140
+ setBody("");
1141
+ }, [props.frame.id]);
1142
+ useEffect(() => {
1143
+ if (open && issueFrame) textareaRef.current?.focus();
1144
+ }, [open, issueFrame]);
1145
+ if (!issueFrame) return null;
944
1146
  async function submit() {
945
1147
  const nextBody = body.trim();
946
1148
  if (!nextBody) return;
@@ -967,30 +1169,42 @@ function IssueCommentComposer(props) {
967
1169
  {
968
1170
  label,
969
1171
  tone: triggerTone,
970
- className: "w-full",
1172
+ className: props.triggerFullWidth ? "w-full" : void 0,
971
1173
  disabled: isPending,
972
1174
  onClick: () => setOpen(true)
973
1175
  }
974
1176
  );
975
1177
  }
976
- return /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2 border border-border bg-secondary/40 p-3", children: [
1178
+ return /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2 rounded-md border border-border bg-secondary/40 p-3", children: [
1179
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
977
1180
  /* @__PURE__ */ jsx(
978
1181
  "textarea",
979
1182
  {
1183
+ ref: textareaRef,
980
1184
  value: body,
981
1185
  onChange: (event) => setBody(event.target.value),
1186
+ onKeyDown: (event) => {
1187
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1188
+ event.preventDefault();
1189
+ void submit();
1190
+ }
1191
+ if (event.key === "Escape") {
1192
+ event.preventDefault();
1193
+ setOpen(false);
1194
+ setBody("");
1195
+ }
1196
+ },
982
1197
  rows: 3,
983
1198
  placeholder: "Add a short operator note back to the issue\u2026",
984
1199
  className: "w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs outline-none transition focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
985
1200
  }
986
1201
  ),
987
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1202
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
988
1203
  /* @__PURE__ */ jsx(
989
1204
  ActionButton,
990
1205
  {
991
1206
  label: "Cancel",
992
1207
  tone: "secondary",
993
- className: "w-full",
994
1208
  disabled: isPending,
995
1209
  onClick: () => {
996
1210
  setOpen(false);
@@ -1002,8 +1216,7 @@ function IssueCommentComposer(props) {
1002
1216
  ActionButton,
1003
1217
  {
1004
1218
  label: isPending ? "Posting\u2026" : "Post comment",
1005
- tone: "primary",
1006
- className: "w-full",
1219
+ tone: "accent",
1007
1220
  disabled: isPending || body.trim().length === 0,
1008
1221
  onClick: () => void submit()
1009
1222
  }
@@ -1016,22 +1229,23 @@ function NowDetails(props) {
1016
1229
  const href = itemHref(frame, props.companyPrefix);
1017
1230
  const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
1018
1231
  const activityLink = activityHref(frame, props.companyPrefix);
1232
+ const hasContext = visibleContextItems(frame).length > 0;
1019
1233
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
1020
- /* @__PURE__ */ jsxs("div", { className: "border border-border bg-secondary/50 px-3 py-2", children: [
1021
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: "Judgment" }),
1022
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-foreground/90", children: judgmentLine(frame, "active") }),
1023
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1024
- /* @__PURE__ */ jsx("span", { children: requestDescriptor(frame) }),
1025
- /* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
1026
- /* @__PURE__ */ jsx("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1027
- requestedAmount(frame) ? /* @__PURE__ */ jsxs("span", { children: [
1028
- "Amount requested: ",
1029
- requestedAmount(frame)
1030
- ] }) : null,
1031
- budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
1032
- ] })
1234
+ /* @__PURE__ */ jsx(ExplainabilityPanel, { frame, lane: "active", detailOnly: true }),
1235
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1236
+ /* @__PURE__ */ jsx("span", { children: requestDescriptor(frame) }),
1237
+ /* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
1238
+ /* @__PURE__ */ jsx("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1239
+ requestedAmount(frame) ? /* @__PURE__ */ jsxs("span", { children: [
1240
+ "Amount requested: ",
1241
+ requestedAmount(frame)
1242
+ ] }) : null,
1243
+ budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
1033
1244
  ] }),
1034
- /* @__PURE__ */ jsx(ContextItems, { frame }),
1245
+ hasContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
1246
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Context" }),
1247
+ /* @__PURE__ */ jsx(ContextItems, { frame })
1248
+ ] }) : null,
1035
1249
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
1036
1250
  href ? /* @__PURE__ */ jsxs(
1037
1251
  "a",
@@ -1077,41 +1291,41 @@ function NowDetails(props) {
1077
1291
  ] });
1078
1292
  }
1079
1293
  function QuietNow() {
1080
- return /* @__PURE__ */ jsx("div", { className: "flex min-h-16 items-center", children: /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." }) });
1294
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-20 items-center gap-3", children: [
1295
+ /* @__PURE__ */ jsx(QuietMark, { size: "md" }),
1296
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." })
1297
+ ] });
1081
1298
  }
1082
1299
  function NowActionRail(props) {
1083
1300
  const actionMode = responseKind(props.frame, props.lane);
1084
1301
  const isPending = props.pendingId === props.frame.id;
1085
1302
  const commentEnabled = actionMode === "acknowledge" && isIssueFrame(props.frame);
1303
+ const [commentOpen, setCommentOpen] = useState(false);
1304
+ const [commentBody, setCommentBody] = useState("");
1305
+ const textareaRef = useRef(null);
1306
+ useEffect(() => {
1307
+ setCommentOpen(false);
1308
+ setCommentBody("");
1309
+ }, [props.frame.id]);
1310
+ useEffect(() => {
1311
+ if (commentOpen) textareaRef.current?.focus();
1312
+ }, [commentOpen]);
1313
+ async function submitComment() {
1314
+ const nextBody = commentBody.trim();
1315
+ if (!nextBody) return;
1316
+ await props.onComment(props.frame, nextBody);
1317
+ setCommentBody("");
1318
+ setCommentOpen(false);
1319
+ }
1086
1320
  return /* @__PURE__ */ jsxs(
1087
- "aside",
1321
+ "div",
1088
1322
  {
1089
- className: "space-y-4 border border-border bg-secondary/20 p-4",
1090
- style: { flex: "0 1 20rem", minWidth: "18rem" },
1323
+ className: "space-y-3 rounded-md border border-border/60 bg-secondary/20 px-3 py-3",
1324
+ style: props.wide ? { width: 320, flexShrink: 0, position: "sticky", top: 16, alignSelf: "flex-start" } : void 0,
1091
1325
  children: [
1092
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
1093
- /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1094
- commentEnabled ? /* @__PURE__ */ jsx(
1095
- IssueCommentComposer,
1096
- {
1097
- frame: props.frame,
1098
- pendingId: props.pendingId,
1099
- onComment: props.onComment,
1100
- triggerTone: "primary",
1101
- triggerLabel: "Comment"
1102
- }
1103
- ) : null,
1104
- actionMode === "approval" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1105
- isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
1106
- ActionButton,
1107
- {
1108
- label: "Request revision",
1109
- tone: "secondary",
1110
- disabled: isPending,
1111
- className: "w-full",
1112
- onClick: () => void props.onRequestRevision(props.frame)
1113
- }
1114
- ) : null,
1326
+ /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
1327
+ actionMode === "approval" ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1328
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1115
1329
  /* @__PURE__ */ jsx(
1116
1330
  ActionButton,
1117
1331
  {
@@ -1132,17 +1346,37 @@ function NowActionRail(props) {
1132
1346
  onClick: () => void props.onReject(props.frame)
1133
1347
  }
1134
1348
  )
1135
- ] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsx(
1349
+ ] }),
1350
+ isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
1136
1351
  ActionButton,
1137
1352
  {
1138
- label: isPending ? "Saving\u2026" : "Acknowledge",
1139
- tone: commentEnabled ? "secondary" : "primary",
1353
+ label: "Request revision",
1354
+ tone: "secondary",
1140
1355
  disabled: isPending,
1141
1356
  className: "w-full",
1142
- onClick: () => void props.onAcknowledge(props.frame)
1357
+ onClick: () => void props.onRequestRevision(props.frame)
1143
1358
  }
1144
1359
  ) : null
1145
- ] }),
1360
+ ] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1361
+ commentEnabled ? /* @__PURE__ */ jsx(
1362
+ ActionButton,
1363
+ {
1364
+ label: "Comment",
1365
+ tone: "accent",
1366
+ disabled: isPending,
1367
+ onClick: () => setCommentOpen(true)
1368
+ }
1369
+ ) : null,
1370
+ /* @__PURE__ */ jsx(
1371
+ ActionButton,
1372
+ {
1373
+ label: isPending ? "Saving\u2026" : "Acknowledge",
1374
+ tone: "accent",
1375
+ disabled: isPending,
1376
+ onClick: () => void props.onAcknowledge(props.frame)
1377
+ }
1378
+ )
1379
+ ] }) : null,
1146
1380
  props.itemLink ? /* @__PURE__ */ jsxs(
1147
1381
  "a",
1148
1382
  {
@@ -1155,7 +1389,59 @@ function NowActionRail(props) {
1155
1389
  /* @__PURE__ */ jsx(ExternalLinkIcon, {})
1156
1390
  ]
1157
1391
  }
1158
- ) : null
1392
+ ) : null,
1393
+ commentOpen ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border bg-background/80 p-3", children: [
1394
+ /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1395
+ "Commenting on ",
1396
+ compactTitle(props.frame)
1397
+ ] }),
1398
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
1399
+ /* @__PURE__ */ jsx(
1400
+ "textarea",
1401
+ {
1402
+ ref: textareaRef,
1403
+ value: commentBody,
1404
+ onChange: (event) => setCommentBody(event.target.value),
1405
+ onKeyDown: (event) => {
1406
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1407
+ event.preventDefault();
1408
+ void submitComment();
1409
+ }
1410
+ if (event.key === "Escape") {
1411
+ event.preventDefault();
1412
+ setCommentOpen(false);
1413
+ setCommentBody("");
1414
+ }
1415
+ },
1416
+ rows: 4,
1417
+ placeholder: "Add a short operator note back to the issue\u2026",
1418
+ className: "w-full resize-y rounded-md border border-input bg-background px-3 py-2 text-sm text-foreground shadow-xs outline-none transition focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
1419
+ }
1420
+ ),
1421
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1422
+ /* @__PURE__ */ jsx(
1423
+ ActionButton,
1424
+ {
1425
+ label: "Cancel",
1426
+ tone: "secondary",
1427
+ disabled: isPending,
1428
+ onClick: () => {
1429
+ setCommentOpen(false);
1430
+ setCommentBody("");
1431
+ }
1432
+ }
1433
+ ),
1434
+ /* @__PURE__ */ jsx(
1435
+ ActionButton,
1436
+ {
1437
+ label: isPending ? "Posting\u2026" : "Post comment",
1438
+ tone: "accent",
1439
+ disabled: isPending || commentBody.trim().length === 0,
1440
+ onClick: () => void submitComment()
1441
+ }
1442
+ )
1443
+ ] })
1444
+ ] }) : null
1159
1445
  ]
1160
1446
  }
1161
1447
  );
@@ -1163,6 +1449,7 @@ function NowActionRail(props) {
1163
1449
  function NowPane(props) {
1164
1450
  const frame = props.snapshot.active;
1165
1451
  const [detailsOpen, setDetailsOpen] = useState(false);
1452
+ const wideLayout = useWideLayout(1120);
1166
1453
  const activeFrameId = frame?.id ?? null;
1167
1454
  const activeUnread = !!frame && (props.snapshot.review?.unread.active ?? 0) > 0;
1168
1455
  const itemLink = frame ? itemHref(frame, props.companyPrefix) : null;
@@ -1172,100 +1459,111 @@ function NowPane(props) {
1172
1459
  useEffect(() => {
1173
1460
  setDetailsOpen(false);
1174
1461
  }, [activeFrameId]);
1175
- return /* @__PURE__ */ jsxs("section", { className: "border border-border bg-card shadow-sm", children: [
1176
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border bg-accent px-4 py-2.5 sm:px-6", children: [
1177
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1178
- /* @__PURE__ */ jsx(Accent, { className: "text-base leading-none", children: props.posture.glyph }),
1179
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
1180
- /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1181
- /* @__PURE__ */ jsx(Accent, { className: "text-[18px] leading-none font-bold tracking-[0.04em]", children: props.brand.wordmark }),
1182
- /* @__PURE__ */ jsx(Accent, { className: "text-xs leading-none", children: props.posture.label })
1462
+ return /* @__PURE__ */ jsxs(
1463
+ "section",
1464
+ {
1465
+ className: "border border-border bg-card shadow-sm",
1466
+ style: frame ? { borderLeftWidth: 3, borderLeftColor: ACCENT_COLOR } : void 0,
1467
+ children: [
1468
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-border bg-accent px-4 py-3 sm:px-6", children: [
1469
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1470
+ /* @__PURE__ */ jsx(Accent, { className: "text-sm leading-none", children: props.posture.glyph }),
1471
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1472
+ /* @__PURE__ */ jsx(Accent, { className: "text-base leading-none font-semibold tracking-[0.04em]", children: props.brand.wordmark }),
1473
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-none text-muted-foreground", children: props.posture.label })
1474
+ ] })
1183
1475
  ] }),
1184
- /* @__PURE__ */ jsx("div", { className: "text-[10px] leading-none text-muted-foreground/60", children: props.brand.supportCopy })
1185
- ] })
1186
- ] }),
1187
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
1188
- /* @__PURE__ */ jsxs("span", { className: props.counts.active > 0 ? "font-semibold" : "", children: [
1189
- "now ",
1190
- props.counts.active > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.active }) : props.counts.active
1191
- ] }),
1192
- /* @__PURE__ */ jsxs("span", { className: props.counts.queued > 0 ? "font-semibold" : "", children: [
1193
- "next ",
1194
- props.counts.queued > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.queued }) : props.counts.queued
1476
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
1477
+ /* @__PURE__ */ jsxs("span", { className: props.counts.active > 0 ? "font-semibold" : "", children: [
1478
+ "now ",
1479
+ props.counts.active > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.active }) : props.counts.active
1480
+ ] }),
1481
+ /* @__PURE__ */ jsxs("span", { className: props.counts.queued > 0 ? "font-semibold" : "", children: [
1482
+ "next ",
1483
+ props.counts.queued > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.queued }) : props.counts.queued
1484
+ ] }),
1485
+ /* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1486
+ "ambient ",
1487
+ props.counts.ambient > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.ambient }) : props.counts.ambient
1488
+ ] })
1489
+ ] })
1195
1490
  ] }),
1196
- /* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1197
- "ambient ",
1198
- props.counts.ambient > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.ambient }) : props.counts.ambient
1199
- ] })
1200
- ] })
1201
- ] }),
1202
- /* @__PURE__ */ jsx("div", { style: { height: "0.75rem" }, "aria-hidden": "true" }),
1203
- /* @__PURE__ */ jsx("div", { className: "px-4 pb-4 sm:px-6", children: !frame ? /* @__PURE__ */ jsx(QuietNow, {}) : /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1204
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start gap-6", children: [
1205
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-4", style: { flex: "1 1 40rem" }, children: [
1206
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs font-medium uppercase tracking-wider text-muted-foreground", children: [
1207
- /* @__PURE__ */ jsx("span", { children: "Now" }),
1208
- /* @__PURE__ */ jsx("span", { className: "normal-case tracking-normal", style: { opacity: 0.6 }, children: sourceLabel(frame) })
1209
- ] }),
1210
- recommendedMove ? /* @__PURE__ */ jsx("div", { className: "max-w-4xl text-[2rem] font-semibold leading-tight text-foreground", children: recommendedMove }) : frame.summary ? /* @__PURE__ */ jsx("div", { className: "max-w-4xl text-[2rem] font-semibold leading-tight text-foreground", children: frame.summary }) : null,
1211
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-[1.75rem] font-medium leading-snug text-foreground/90", children: [
1212
- /* @__PURE__ */ jsx(UnreadDot, { visible: activeUnread }),
1213
- /* @__PURE__ */ jsx("span", { children: renderTitle(frame) })
1214
- ] }),
1215
- helperLine ? /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-[0.95rem] leading-relaxed text-muted-foreground", children: helperLine }) : null,
1216
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1217
- /* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
1218
- driverLabel(frame) ? /* @__PURE__ */ jsx(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
1219
- ] }),
1220
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4 pt-1", children: /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
1221
- "button",
1491
+ /* @__PURE__ */ jsx("div", { className: "px-5 py-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx(QuietNow, {}) : /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1492
+ /* @__PURE__ */ jsxs(
1493
+ "div",
1222
1494
  {
1223
- type: "button",
1224
- onClick: () => setDetailsOpen(!detailsOpen),
1225
- "aria-expanded": detailsOpen,
1226
- className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
1227
- style: { color: "inherit" },
1495
+ className: wideLayout ? "flex items-start gap-6" : "space-y-4",
1228
1496
  children: [
1497
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", style: wideLayout ? { minWidth: 0, flex: 1 } : void 0, children: [
1498
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1499
+ /* @__PURE__ */ jsx("span", { children: "Now" }),
1500
+ /* @__PURE__ */ jsx("span", { className: "normal-case tracking-normal text-muted-foreground/70", children: sourceLabel(frame) })
1501
+ ] }),
1502
+ recommendedMove ? /* @__PURE__ */ jsx("div", { className: "max-w-4xl text-3xl font-semibold leading-tight text-foreground", children: recommendedMove }) : frame.summary ? /* @__PURE__ */ jsx("div", { className: "max-w-4xl text-3xl font-semibold leading-tight text-foreground", children: frame.summary }) : null,
1503
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80", children: [
1504
+ /* @__PURE__ */ jsx(UnreadDot, { visible: activeUnread }),
1505
+ /* @__PURE__ */ jsx("span", { children: renderTitle(frame) })
1506
+ ] }),
1507
+ helperLine ? /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-relaxed text-muted-foreground", children: helperLine }) : null,
1508
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1509
+ /* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
1510
+ driverLabel(frame) ? /* @__PURE__ */ jsx(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
1511
+ ] }),
1512
+ /* @__PURE__ */ jsx(InlineExplainability, { frame, lane: "active", preferLaneReason: !!helperLine }),
1513
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
1514
+ "button",
1515
+ {
1516
+ type: "button",
1517
+ onClick: () => setDetailsOpen(!detailsOpen),
1518
+ "aria-expanded": detailsOpen,
1519
+ className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
1520
+ style: { color: "inherit" },
1521
+ children: [
1522
+ /* @__PURE__ */ jsx(
1523
+ "svg",
1524
+ {
1525
+ viewBox: "0 0 16 16",
1526
+ className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
1527
+ fill: "currentColor",
1528
+ "aria-hidden": "true",
1529
+ children: /* @__PURE__ */ jsx("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" })
1530
+ }
1531
+ ),
1532
+ detailsOpen ? "Hide context" : "Show context"
1533
+ ]
1534
+ }
1535
+ ) }) })
1536
+ ] }),
1229
1537
  /* @__PURE__ */ jsx(
1230
- "svg",
1538
+ NowActionRail,
1231
1539
  {
1232
- viewBox: "0 0 16 16",
1233
- className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
1234
- fill: "currentColor",
1235
- "aria-hidden": "true",
1236
- children: /* @__PURE__ */ jsx("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" })
1540
+ frame,
1541
+ lane: "active",
1542
+ wide: wideLayout,
1543
+ pendingId: props.pendingId,
1544
+ itemLink,
1545
+ onApprove: props.onApprove,
1546
+ onReject: props.onReject,
1547
+ onRequestRevision: props.onRequestRevision,
1548
+ onAcknowledge: props.onAcknowledge,
1549
+ onComment: props.onComment
1237
1550
  }
1238
- ),
1239
- detailsOpen ? "Hide details" : "Show details"
1551
+ )
1240
1552
  ]
1241
1553
  }
1242
- ) }) })
1243
- ] }),
1244
- /* @__PURE__ */ jsx(
1245
- NowActionRail,
1246
- {
1247
- frame,
1248
- lane: "active",
1249
- pendingId: props.pendingId,
1250
- itemLink,
1251
- onApprove: props.onApprove,
1252
- onReject: props.onReject,
1253
- onRequestRevision: props.onRequestRevision,
1254
- onAcknowledge: props.onAcknowledge,
1255
- onComment: props.onComment
1256
- }
1257
- )
1258
- ] }),
1259
- detailsOpen ? /* @__PURE__ */ jsx(
1260
- NowDetails,
1261
- {
1262
- frame,
1263
- snapshotUpdatedAt: props.snapshot.updatedAt,
1264
- companyPrefix: props.companyPrefix
1265
- }
1266
- ) : null
1267
- ] }) })
1268
- ] });
1554
+ ),
1555
+ detailsOpen ? /* @__PURE__ */ jsx(
1556
+ NowDetails,
1557
+ {
1558
+ frame,
1559
+ snapshotUpdatedAt: props.snapshot.updatedAt,
1560
+ companyPrefix: props.companyPrefix
1561
+ }
1562
+ ) : null
1563
+ ] }) })
1564
+ ]
1565
+ }
1566
+ );
1269
1567
  }
1270
1568
  function NextRow(props) {
1271
1569
  const { frame } = props;
@@ -1278,6 +1576,7 @@ function NextRow(props) {
1278
1576
  const driverBadge = driverBadgeStyle();
1279
1577
  const detailText = judgmentLine(frame, "queued");
1280
1578
  const showDetailText = detailText.trim().length > 0 && detailText !== secondaryText && detailText !== GENERIC_QUEUED_JUDGMENT;
1579
+ const rankOpacity = Math.max(0.4, 1 - (props.rank - 1) * 0.14);
1281
1580
  return /* @__PURE__ */ jsxs("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
1282
1581
  /* @__PURE__ */ jsxs(
1283
1582
  "button",
@@ -1287,11 +1586,11 @@ function NextRow(props) {
1287
1586
  "aria-expanded": expanded,
1288
1587
  className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",
1289
1588
  children: [
1290
- /* @__PURE__ */ jsx(Accent, { className: "w-5 shrink-0 text-xs font-medium tabular-nums", children: String(props.rank).padStart(2, "0") }),
1291
1589
  /* @__PURE__ */ jsx(UnreadDot, { visible: unread }),
1590
+ /* @__PURE__ */ jsx("span", { className: "w-5 shrink-0", style: { opacity: rankOpacity }, children: /* @__PURE__ */ jsx(Accent, { className: "text-xs font-medium tabular-nums", children: String(props.rank).padStart(2, "0") }) }),
1292
1591
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1293
- /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium text-foreground", children: renderTitle(frame) }),
1294
- secondaryText ? /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: secondaryText }) : null
1592
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium leading-5 text-foreground", children: renderTitle(frame) }),
1593
+ secondaryText ? /* @__PURE__ */ jsx("div", { className: "mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground", children: secondaryText }) : null
1295
1594
  ] }),
1296
1595
  /* @__PURE__ */ jsx(QueueMovementBadge, { movement: props.movement }),
1297
1596
  driver ? /* @__PURE__ */ jsx(Badge, { className: cn("shrink-0", driverBadge.className), style: driverBadge.style, children: driver }) : null,
@@ -1309,8 +1608,9 @@ function NextRow(props) {
1309
1608
  ]
1310
1609
  }
1311
1610
  ),
1312
- expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-3 pl-12 sm:px-6 sm:pl-14", children: [
1611
+ expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
1313
1612
  showDetailText ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
1613
+ /* @__PURE__ */ jsx(ExplainabilityStrip, { frame, lane: "queued" }),
1314
1614
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
1315
1615
  /* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
1316
1616
  driver ? /* @__PURE__ */ jsx(Accent, { children: driver }) : null,
@@ -1336,19 +1636,21 @@ function NextRow(props) {
1336
1636
  }
1337
1637
  ) : null
1338
1638
  ] }),
1339
- /* @__PURE__ */ jsx(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
1340
- /* @__PURE__ */ jsx(
1341
- FrameActions,
1342
- {
1343
- frame,
1344
- lane: "queued",
1345
- pendingId: props.pendingId,
1346
- onApprove: props.onApprove,
1347
- onReject: props.onReject,
1348
- onRequestRevision: props.onRequestRevision,
1349
- onAcknowledge: props.onAcknowledge
1350
- }
1351
- )
1639
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
1640
+ /* @__PURE__ */ jsx(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
1641
+ /* @__PURE__ */ jsx(
1642
+ FrameActions,
1643
+ {
1644
+ frame,
1645
+ lane: "queued",
1646
+ pendingId: props.pendingId,
1647
+ onApprove: props.onApprove,
1648
+ onReject: props.onReject,
1649
+ onRequestRevision: props.onRequestRevision,
1650
+ onAcknowledge: props.onAcknowledge
1651
+ }
1652
+ )
1653
+ ] })
1352
1654
  ] }) : null
1353
1655
  ] });
1354
1656
  }
@@ -1377,65 +1679,23 @@ function NextLane(props) {
1377
1679
  }
1378
1680
  function AmbientRow(props) {
1379
1681
  const { frame, snapshotUpdatedAt } = props;
1380
- const [expanded, setExpanded] = useState(false);
1381
1682
  const href = itemHref(frame, props.companyPrefix);
1382
1683
  const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
1383
- const activityLink = activityHref(frame, props.companyPrefix);
1384
- return /* @__PURE__ */ jsxs("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "last:border-b-0", children: [
1385
- /* @__PURE__ */ jsxs(
1386
- "button",
1387
- {
1388
- type: "button",
1389
- onClick: () => setExpanded(!expanded),
1390
- "aria-expanded": expanded,
1391
- className: "flex w-full items-center gap-3 px-4 py-2.5 text-left transition-colors hover:bg-accent/50 sm:px-6",
1392
- children: [
1393
- /* @__PURE__ */ jsx(UnreadDot, { visible: unread }),
1394
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-muted-foreground/80", children: renderTitle(frame) }),
1395
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.6 }, children: sourceLabel(frame) }),
1396
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.55 }, children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1397
- /* @__PURE__ */ jsx(
1398
- "svg",
1399
- {
1400
- viewBox: "0 0 16 16",
1401
- className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
1402
- fill: "currentColor",
1403
- style: { opacity: 0.75 },
1404
- "aria-hidden": "true",
1405
- children: /* @__PURE__ */ jsx("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" })
1406
- }
1407
- )
1408
- ]
1409
- }
1410
- ),
1411
- expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-3 sm:px-6", children: [
1412
- /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: frame.summary ?? judgmentLine(frame, "ambient") }),
1413
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1414
- href ? /* @__PURE__ */ jsx(
1415
- "a",
1416
- {
1417
- href,
1418
- className: "text-xs font-medium underline underline-offset-2 text-muted-foreground hover:text-foreground",
1419
- children: primaryLinkLabel(frame)
1420
- }
1421
- ) : null,
1422
- activityLink ? /* @__PURE__ */ jsx(
1423
- "a",
1424
- {
1425
- href: activityLink,
1426
- className: "text-xs font-medium underline underline-offset-2 text-muted-foreground hover:text-foreground",
1427
- children: "Open activity"
1428
- }
1429
- ) : null,
1430
- /* @__PURE__ */ jsx("span", { className: "ml-auto text-xs uppercase tracking-[0.16em] text-muted-foreground/60", children: "Awareness only" })
1431
- ] })
1432
- ] }) : null
1684
+ const content = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-2.5 sm:px-6", children: [
1685
+ /* @__PURE__ */ jsx(UnreadDot, { visible: unread, tone: "muted", reserveSpace: true }),
1686
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[13px] text-muted-foreground", children: renderTitle(frame) }),
1687
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/80", children: sourceLabel(frame) }),
1688
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/70", children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) })
1433
1689
  ] });
1690
+ return /* @__PURE__ */ jsx("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "last:border-b-0", children: href ? /* @__PURE__ */ jsx("a", { href, className: "block transition-colors hover:bg-accent/40", children: content }) : content });
1434
1691
  }
1435
1692
  function AmbientLane(props) {
1436
- return /* @__PURE__ */ jsxs("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "bg-card", "aria-label": "Ambient attention", children: [
1437
- /* @__PURE__ */ jsx("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", style: { opacity: 0.6 }, children: "Ambient" }) }),
1438
- props.rows.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-sm text-muted-foreground sm:px-6", style: { opacity: 0.4 }, children: "Nothing in peripheral view." }) : props.rows.map((row) => /* @__PURE__ */ jsx(
1693
+ return /* @__PURE__ */ jsxs("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "mt-2 bg-card", "aria-label": "Ambient attention", children: [
1694
+ /* @__PURE__ */ jsx("div", { style: { borderBottomWidth: 1, borderBottomColor: "var(--border)" }, className: "px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground/80", children: "Ambient" }) }),
1695
+ props.rows.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-4 text-sm text-muted-foreground sm:px-6", children: [
1696
+ /* @__PURE__ */ jsx(QuietMark, {}),
1697
+ /* @__PURE__ */ jsx("span", { children: "Nothing in peripheral view." })
1698
+ ] }) : props.rows.map((row) => /* @__PURE__ */ jsx(
1439
1699
  AmbientRow,
1440
1700
  {
1441
1701
  frame: row.frame,
@@ -1450,6 +1710,7 @@ function AmbientLane(props) {
1450
1710
  function DashboardWidget(props) {
1451
1711
  const companyId = props.context.companyId;
1452
1712
  const brand = currentSurfaceBrand();
1713
+ const href = pluginPagePath(props.context.companyPrefix);
1453
1714
  const model = useAttentionModel(companyId);
1454
1715
  const data = model.snapshot;
1455
1716
  const loading = model.loading;
@@ -1459,20 +1720,19 @@ function DashboardWidget(props) {
1459
1720
  if (error) return /* @__PURE__ */ jsx(MessageCard, { title: "Plugin error", body: error.message });
1460
1721
  if (!data) return /* @__PURE__ */ jsx(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState });
1461
1722
  const posture = postureForSnapshot(data);
1462
- return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card px-4 py-2.5 shadow-sm", children: [
1723
+ const leadingText = data.active ? recommendedMoveValue(data.active) ?? data.active.title : null;
1724
+ const borderStyle = posture.label === "busy" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : posture.label === "elevated" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_BORDER } : void 0;
1725
+ return /* @__PURE__ */ jsxs("a", { href, className: "block border border-border bg-card px-4 py-3 shadow-sm transition-colors hover:bg-accent/40", style: borderStyle, children: [
1463
1726
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", children: [
1464
1727
  /* @__PURE__ */ jsx(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
1465
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1466
- /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1467
- /* @__PURE__ */ jsx(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
1468
- /* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: posture.label }),
1469
- unreadCount(data) > 0 ? /* @__PURE__ */ jsxs(Accent, { className: "text-[11px]", children: [
1470
- "new ",
1471
- unreadCount(data)
1472
- ] }) : null
1473
- ] }),
1474
- /* @__PURE__ */ jsx("div", { className: "mt-0.5 text-[10px] leading-none text-muted-foreground/60", children: brand.supportCopy })
1475
- ] }),
1728
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1729
+ /* @__PURE__ */ jsx(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
1730
+ /* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: posture.label }),
1731
+ unreadCount(data) > 0 ? /* @__PURE__ */ jsxs(Accent, { className: "text-[11px]", children: [
1732
+ "new ",
1733
+ unreadCount(data)
1734
+ ] }) : null
1735
+ ] }) }),
1476
1736
  /* @__PURE__ */ jsxs("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
1477
1737
  "now ",
1478
1738
  data.counts.active,
@@ -1482,7 +1742,19 @@ function DashboardWidget(props) {
1482
1742
  data.counts.ambient
1483
1743
  ] })
1484
1744
  ] }),
1485
- data.active ? /* @__PURE__ */ jsx("div", { className: "mt-2 truncate text-sm font-medium text-foreground", children: data.active.title }) : /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
1745
+ leadingText ? /* @__PURE__ */ jsx(
1746
+ "div",
1747
+ {
1748
+ className: "mt-2 text-sm font-medium leading-5 text-foreground",
1749
+ style: {
1750
+ display: "-webkit-box",
1751
+ WebkitLineClamp: 2,
1752
+ WebkitBoxOrient: "vertical",
1753
+ overflow: "hidden"
1754
+ },
1755
+ children: leadingText
1756
+ }
1757
+ ) : /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
1486
1758
  ] });
1487
1759
  }
1488
1760
  function AttentionSidebarLink({ context }) {
@@ -1526,7 +1798,14 @@ function AttentionSidebarLink({ context }) {
1526
1798
  }
1527
1799
  ) }),
1528
1800
  /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: brand.wordmark }),
1529
- unread > 0 || actionable > 0 ? /* @__PURE__ */ jsx("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: unread > 0 ? unread : actionable }) : null
1801
+ unread > 0 || actionable > 0 ? unread > 0 ? /* @__PURE__ */ jsx("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: unread }) : /* @__PURE__ */ jsx(
1802
+ "span",
1803
+ {
1804
+ className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
1805
+ style: { color: ACCENT_COLOR, borderColor: ACCENT_BORDER, backgroundColor: "transparent" },
1806
+ children: actionable
1807
+ }
1808
+ ) : null
1530
1809
  ]
1531
1810
  }
1532
1811
  );
@@ -1721,7 +2000,7 @@ function AttentionPage(props) {
1721
2000
  }
1722
2001
  );
1723
2002
  }
1724
- return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2003
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1725
2004
  /* @__PURE__ */ jsx(
1726
2005
  NowPane,
1727
2006
  {
package/dist/worker.js CHANGED
@@ -12556,6 +12556,13 @@ function issueWhyNow(issue, analysis, documentSignal) {
12556
12556
 
12557
12557
  // src/aperture/reconciliation.ts
12558
12558
  var COMMENT_LOOKUP_CONCURRENCY = 6;
12559
+ function semanticMetadata(confidence, relationHints) {
12560
+ const semantic = {
12561
+ ...confidence ? { confidence } : {},
12562
+ ...relationHints.length > 0 ? { relationHints } : {}
12563
+ };
12564
+ return Object.keys(semantic).length > 0 ? semantic : void 0;
12565
+ }
12559
12566
  function toIsoString2(value) {
12560
12567
  if (!value) return void 0;
12561
12568
  if (typeof value === "string") return value;
@@ -12623,6 +12630,7 @@ function issueFrame(issue, comment, documents, directory) {
12623
12630
  const owner = hasIntent(analysis, "resolution") || documentSignal.resolvesArtifactRequest ? null : analysis.owner;
12624
12631
  const move = issueRecommendedMove(issue, analysis, documentSignal);
12625
12632
  const target = analysis.blockingTarget;
12633
+ const semantic = semanticMetadata(analysis.semanticConfidence, analysis.relationHints);
12626
12634
  return {
12627
12635
  lane,
12628
12636
  frame: {
@@ -12661,7 +12669,11 @@ function issueFrame(issue, comment, documents, directory) {
12661
12669
  issueStatus: issue.status,
12662
12670
  issuePriority: issue.priority,
12663
12671
  liveReconciled: true,
12664
- activityPath: comment ? "activity" : void 0
12672
+ activityPath: comment ? "activity" : void 0,
12673
+ attention: {
12674
+ rationale: provenance.factors ?? []
12675
+ },
12676
+ ...semantic ? { semantic } : {}
12665
12677
  }
12666
12678
  }
12667
12679
  };
@@ -12719,7 +12731,13 @@ function agentFrame(agent) {
12719
12731
  agentStatus: agent.status,
12720
12732
  pauseReason: agent.pauseReason,
12721
12733
  liveReconciled: true,
12722
- activityPath: agent.status === "error" ? "activity" : void 0
12734
+ activityPath: agent.status === "error" ? "activity" : void 0,
12735
+ attention: {
12736
+ rationale: provenance.factors ?? []
12737
+ },
12738
+ semantic: {
12739
+ confidence: "high"
12740
+ }
12723
12741
  }
12724
12742
  }
12725
12743
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomismeta/paperclip-aperture",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "The live attention layer for Paperclip, powered by Aperture's deterministic attention engine.",