@tomismeta/paperclip-aperture 0.4.1 → 0.4.2

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,10 +1,5 @@
1
1
  // src/ui/index.tsx
2
- import {
3
- usePluginAction,
4
- usePluginData,
5
- usePluginStream
6
- } from "@paperclipai/plugin-sdk/ui";
7
- import { useEffect, useMemo, useRef, useState } from "react";
2
+ import { useEffect as useEffect5, useMemo as useMemo2, useRef as useRef5, useState as useState5 } from "react";
8
3
 
9
4
  // src/aperture/frame-model.ts
10
5
  var AMBIENT_MAX_AGE_MS = 5 * 60 * 1e3;
@@ -14,227 +9,6 @@ function isBudgetOverride(frame) {
14
9
  function frameUpdatedAt(frame, snapshotUpdatedAt) {
15
10
  return frame.timing.updatedAt || snapshotUpdatedAt;
16
11
  }
17
- function parseIsoMillis(value) {
18
- if (!value) return null;
19
- const parsed = Date.parse(value);
20
- return Number.isFinite(parsed) ? parsed : null;
21
- }
22
- function isAmbientExpired(frame, snapshotUpdatedAt, nowIso) {
23
- const updatedAt = parseIsoMillis(frameUpdatedAt(frame, snapshotUpdatedAt));
24
- const now = parseIsoMillis(nowIso);
25
- if (updatedAt === null || now === null) return false;
26
- return now - updatedAt > AMBIENT_MAX_AGE_MS;
27
- }
28
- function frameSemanticWeight(frame) {
29
- const factors = new Set(frame.provenance?.factors ?? []);
30
- let weight = 0;
31
- if (factors.has("run failed")) weight += 20;
32
- if (factors.has("waiting on human")) weight += 16;
33
- if (factors.has("needs clarification")) weight += 16;
34
- if (factors.has("blocked")) weight += 12;
35
- if (factors.has("pending approval")) weight += 12;
36
- if (factors.has("paused work")) weight += 6;
37
- if (factors.has("comment follow-up")) weight += 4;
38
- return weight;
39
- }
40
- function frameSortScore(frame, lane) {
41
- const toneWeight = frame.tone === "critical" ? 40 : frame.tone === "focused" ? 25 : 5;
42
- const consequenceWeight = frame.consequence === "high" ? 30 : frame.consequence === "medium" ? 15 : 0;
43
- const modeWeight = frame.mode === "approval" ? 12 : frame.mode === "choice" ? 8 : 0;
44
- const laneWeight = lane === "now" ? 20 : lane === "next" ? 10 : 0;
45
- const budgetWeight = isBudgetOverride(frame) ? 10 : 0;
46
- return toneWeight + consequenceWeight + modeWeight + laneWeight + budgetWeight + frameSemanticWeight(frame);
47
- }
48
- function emptyLaneCounts() {
49
- return {
50
- now: 0,
51
- next: 0,
52
- ambient: 0,
53
- total: 0
54
- };
55
- }
56
- function compareCandidates(left, right, snapshotUpdatedAt) {
57
- const byScore = frameSortScore(right.frame, right.lane) - frameSortScore(left.frame, left.lane);
58
- if (byScore !== 0) return byScore;
59
- return frameUpdatedAt(right.frame, snapshotUpdatedAt).localeCompare(frameUpdatedAt(left.frame, snapshotUpdatedAt));
60
- }
61
- function frameReviewState(review, taskId) {
62
- return review?.frames[taskId];
63
- }
64
- function isFrameSuppressed(frame, snapshotUpdatedAt, review) {
65
- const suppressedAt = frameReviewState(review, frame.taskId)?.suppressedAt;
66
- if (!suppressedAt) return false;
67
- return frameUpdatedAt(frame, snapshotUpdatedAt).localeCompare(suppressedAt) <= 0;
68
- }
69
- function isFrameUnread(frame, snapshotUpdatedAt, review) {
70
- const updatedAt = frameUpdatedAt(frame, snapshotUpdatedAt);
71
- const seenAt = frameReviewState(review, frame.taskId)?.lastSeenAt ?? review?.lastSeenAt;
72
- if (!seenAt) return true;
73
- return updatedAt.localeCompare(seenAt) > 0;
74
- }
75
- function calculateUnreadCounts(snapshot, review) {
76
- const unread = emptyLaneCounts();
77
- if (snapshot.now && isFrameUnread(snapshot.now, snapshot.updatedAt, review)) unread.now += 1;
78
- unread.next = snapshot.next.filter((frame) => isFrameUnread(frame, snapshot.updatedAt, review)).length;
79
- unread.ambient = snapshot.ambient.filter((frame) => isFrameUnread(frame, snapshot.updatedAt, review)).length;
80
- unread.total = unread.now + unread.next + unread.ambient;
81
- return unread;
82
- }
83
- function attachReviewState(snapshot, review) {
84
- return {
85
- ...snapshot,
86
- review: {
87
- lastSeenAt: review?.lastSeenAt,
88
- unread: calculateUnreadCounts(snapshot, review)
89
- }
90
- };
91
- }
92
- function mergeStoredFrames(snapshot, companyId, candidates, review, nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
93
- const base = snapshot ?? {
94
- companyId,
95
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
96
- now: null,
97
- next: [],
98
- ambient: [],
99
- counts: emptyLaneCounts()
100
- };
101
- const candidateTaskIds = new Set(candidates.map((candidate) => candidate.frame.taskId));
102
- const baseEntries = [
103
- ...base.now ? [{ frame: base.now, lane: "now" }] : [],
104
- ...base.next.map((frame) => ({ frame, lane: "next" })),
105
- ...base.ambient.map((frame) => ({ frame, lane: "ambient" }))
106
- ].filter((candidate) => !candidateTaskIds.has(candidate.frame.taskId));
107
- const filtered = [...baseEntries, ...candidates].filter((candidate) => !isFrameSuppressed(candidate.frame, base.updatedAt, review));
108
- const actionable = filtered.filter((candidate) => candidate.lane !== "ambient").sort((left, right) => compareCandidates(left, right, base.updatedAt));
109
- const ambient = filtered.filter((candidate) => candidate.lane === "ambient").filter((candidate) => !isAmbientExpired(candidate.frame, base.updatedAt, nowIso)).sort((left, right) => compareCandidates(left, right, base.updatedAt));
110
- const now = actionable.shift()?.frame ?? null;
111
- const next = actionable.map((candidate) => candidate.frame);
112
- const ambientFrames = ambient.map((candidate) => candidate.frame);
113
- return attachReviewState({
114
- ...base,
115
- now,
116
- next,
117
- ambient: ambientFrames,
118
- counts: {
119
- now: now ? 1 : 0,
120
- next: next.length,
121
- ambient: ambientFrames.length,
122
- total: (now ? 1 : 0) + next.length + ambientFrames.length
123
- }
124
- }, review);
125
- }
126
-
127
- // src/aperture/explainability.ts
128
- function asRecord(value) {
129
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
130
- }
131
- function readStringArray(value) {
132
- if (!Array.isArray(value)) return [];
133
- return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
134
- }
135
- function readSemanticConfidence(frame) {
136
- const semantic = asRecord(frame.metadata?.semantic);
137
- const confidence = semantic?.confidence;
138
- return confidence === "low" || confidence === "medium" || confidence === "high" ? confidence : null;
139
- }
140
- function readSemanticRelationHints(frame) {
141
- const semantic = asRecord(frame.metadata?.semantic);
142
- const relationHints = semantic?.relationHints;
143
- if (!Array.isArray(relationHints)) return [];
144
- return relationHints.filter((entry) => {
145
- if (typeof entry !== "object" || entry === null) return false;
146
- const hint = entry;
147
- return hint.kind === "same_issue" || hint.kind === "resolves" || hint.kind === "supersedes" || hint.kind === "repeats" || hint.kind === "escalates";
148
- });
149
- }
150
- function relationHintLabel(hint) {
151
- switch (hint.kind) {
152
- case "same_issue":
153
- return "Part of the same thread";
154
- case "resolves":
155
- return "Resolves an earlier blocker";
156
- case "supersedes":
157
- return "Moves the request forward";
158
- case "repeats":
159
- return "Repeats an earlier ask";
160
- case "escalates":
161
- return "Raises the urgency";
162
- }
163
- }
164
- function laneReason(lane) {
165
- switch (lane) {
166
- case "now":
167
- return "This is the most urgent item in the queue right now.";
168
- case "next":
169
- return "This is staged behind the current top item.";
170
- case "ambient":
171
- return "This is visible for awareness without needing action yet.";
172
- }
173
- }
174
- function continuitySummary(frame) {
175
- const episode = asRecord(frame.metadata?.episode);
176
- const size = typeof episode?.size === "number" ? episode.size : null;
177
- const state = typeof episode?.state === "string" ? episode.state.replace(/_/g, " ") : null;
178
- if (!size || size <= 1) return null;
179
- if (state) return `Part of a ${state} thread with ${size} related interactions.`;
180
- return `Part of a thread with ${size} related interactions.`;
181
- }
182
- function frameSignals(frame) {
183
- const attention = asRecord(frame.metadata?.attention);
184
- const rationale = readStringArray(attention?.rationale);
185
- const factors = readStringArray(frame.provenance?.factors);
186
- return [.../* @__PURE__ */ new Set([...rationale.length > 0 ? rationale : factors, ...factors])];
187
- }
188
- function signalStrengthLabel(confidence) {
189
- return `${confidence} confidence`;
190
- }
191
- function explainFrame(frame, lane) {
192
- const relationLabels = [...new Set(readSemanticRelationHints(frame).map(relationHintLabel))];
193
- return {
194
- whyNow: frame.provenance?.whyNow ?? frame.summary ?? null,
195
- laneReason: laneReason(lane),
196
- signalStrength: readSemanticConfidence(frame),
197
- signals: frameSignals(frame),
198
- relationLabels,
199
- continuity: continuitySummary(frame)
200
- };
201
- }
202
-
203
- // src/aperture/attention-context.ts
204
- var ATTENTION_CONTEXT_IDS = {
205
- approvalType: "approval-type",
206
- requestedAmount: "requested-amount",
207
- budgetReason: "budget-reason",
208
- decisionContext: "decision-context",
209
- linkedIssues: "linked-issues",
210
- needsActionFrom: "needs-action-from",
211
- blocksTarget: "blocks-target",
212
- recommendedMove: "recommended-move",
213
- issueStatus: "issue-status",
214
- issuePriority: "issue-priority",
215
- latestComment: "latest-comment",
216
- agentStatus: "agent-status",
217
- pauseReason: "pause-reason",
218
- agentTitle: "agent-title"
219
- };
220
- function item(id, label, value) {
221
- return { id, label, value };
222
- }
223
- function humanizeToken(value) {
224
- return value.split(/[_\-.]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
225
- }
226
- function approvalTypeItem(value) {
227
- return item(ATTENTION_CONTEXT_IDS.approvalType, "Type", value);
228
- }
229
- function requestedAmountItem(value) {
230
- return item(ATTENTION_CONTEXT_IDS.requestedAmount, "Requested amount", value);
231
- }
232
- function budgetReasonItem(value) {
233
- return item(ATTENTION_CONTEXT_IDS.budgetReason, "Reason", value);
234
- }
235
- function decisionContextItem(value) {
236
- return item(ATTENTION_CONTEXT_IDS.decisionContext, "Decision context", value);
237
- }
238
12
 
239
13
  // src/aperture/attention-language.ts
240
14
  var GENERIC_QUEUED_JUDGMENT = "Important enough to keep visible, but not enough to displace now.";
@@ -242,9 +16,6 @@ var GENERIC_NEXT_JUDGMENT = GENERIC_QUEUED_JUDGMENT;
242
16
  function approvalBlockingSummary(budgetOverride) {
243
17
  return budgetOverride ? "Budget controls are blocking work until a board decision lands." : "A board decision is blocking work in Paperclip.";
244
18
  }
245
- function approvalBlockingWhyNow(budgetOverride) {
246
- return budgetOverride ? "Budget controls are blocking work until a board decision lands." : "Paperclip is waiting on a human approval before work can continue.";
247
- }
248
19
  function genericJudgmentLine(frame, lane) {
249
20
  if (lane === "now") {
250
21
  if (isBudgetOverride(frame)) return approvalBlockingSummary(true);
@@ -257,170 +28,15 @@ function genericJudgmentLine(frame, lane) {
257
28
  return "Useful for awareness, but not interrupting.";
258
29
  }
259
30
 
260
- // src/aperture/types.ts
261
- function createEmptySnapshot(companyId) {
262
- return {
263
- companyId,
264
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
265
- now: null,
266
- next: [],
267
- ambient: [],
268
- counts: {
269
- now: 0,
270
- next: 0,
271
- ambient: 0,
272
- total: 0
273
- }
274
- };
275
- }
276
-
277
- // src/aperture/approval-frames.ts
278
- function approvalTitle(record) {
279
- const payload = record.payload ?? {};
280
- const explicitTitle = typeof payload.title === "string" ? payload.title : typeof payload.plan === "string" ? payload.plan : typeof payload.name === "string" ? payload.name : null;
281
- if (explicitTitle) return explicitTitle;
282
- return `${humanizeToken(record.type)} approval`;
283
- }
284
- function isBudgetOverrideRecord(record) {
285
- return record.type === "budget_override_required";
286
- }
287
- function actionableApprovalRecords(records) {
288
- if (!records) return [];
289
- return records.filter((record) => record.status === "pending" || record.status === "revision_requested").sort((left, right) => {
290
- const leftScore = isBudgetOverrideRecord(left) ? 1 : 0;
291
- const rightScore = isBudgetOverrideRecord(right) ? 1 : 0;
292
- if (leftScore !== rightScore) return rightScore - leftScore;
293
- return (right.updatedAt ?? right.createdAt).localeCompare(left.updatedAt ?? left.createdAt);
294
- });
295
- }
296
- function approvalRecordToFrame(record) {
297
- const payload = record.payload ?? {};
298
- const budgetOverride = isBudgetOverrideRecord(record);
299
- const requestedAmount2 = typeof payload.requestedAmount === "string" ? payload.requestedAmount : void 0;
300
- const reason = typeof payload.reason === "string" ? payload.reason : void 0;
301
- const decisionContext = typeof payload.decisionContext === "string" ? payload.decisionContext : void 0;
302
- const summary = typeof payload.summary === "string" ? payload.summary : approvalBlockingSummary(budgetOverride);
303
- const updatedAt = record.updatedAt ?? record.createdAt;
304
- const provenance = {
305
- whyNow: approvalBlockingWhyNow(budgetOverride),
306
- factors: budgetOverride ? ["budget stop", "approval", "operator decision"] : ["approval", "operator decision"]
307
- };
308
- return {
309
- id: `approval-bootstrap:${record.id}`,
310
- taskId: `approval:${record.id}`,
311
- interactionId: `approval:${record.id}:approval`,
312
- source: {
313
- id: "paperclip:approval",
314
- kind: "paperclip",
315
- label: "Paperclip approval"
316
- },
317
- version: 1,
318
- mode: "approval",
319
- tone: "focused",
320
- consequence: budgetOverride ? "high" : "medium",
321
- title: approvalTitle(record),
322
- summary,
323
- context: {
324
- items: [
325
- {
326
- ...approvalTypeItem(humanizeToken(record.type))
327
- },
328
- ...requestedAmount2 ? [requestedAmountItem(requestedAmount2)] : [],
329
- ...reason ? [budgetReasonItem(reason)] : [],
330
- ...decisionContext ? [decisionContextItem(decisionContext)] : []
331
- ]
332
- },
333
- responseSpec: {
334
- kind: "approval",
335
- actions: [
336
- { id: "approve", label: "Approve", kind: "approve", emphasis: "primary" },
337
- { id: "reject", label: "Reject", kind: "reject", emphasis: "danger" },
338
- ...budgetOverride ? [{ id: "request-revision", label: "Request revision", kind: "cancel", emphasis: "secondary" }] : []
339
- ]
340
- },
341
- provenance,
342
- timing: {
343
- createdAt: record.createdAt,
344
- updatedAt
345
- },
346
- metadata: {
347
- approvalStatus: record.status,
348
- approvalType: record.type,
349
- attention: {
350
- rationale: provenance.factors
351
- },
352
- semantic: {
353
- confidence: "high"
354
- }
355
- }
356
- };
357
- }
358
- function mergeSnapshotWithApprovals(snapshot, companyId, approvals, review) {
359
- const approvalFrames = actionableApprovalRecords(approvals).map(approvalRecordToFrame);
360
- const base = snapshot ?? createEmptySnapshot(companyId);
361
- const merged = mergeStoredFrames(base, companyId, [
362
- ...approvalFrames.map((frame, index) => ({ frame, lane: index === 0 ? "now" : "next" }))
363
- ], review);
364
- return merged;
365
- }
366
-
367
- // src/aperture/issue-intelligence.ts
368
- function issueBlocksTargetLine(target) {
369
- return `Blocks ${target} until the review lands.`;
370
- }
371
- function issueNeedsActionFromLine(owner) {
372
- return `Needs action from ${owner}.`;
373
- }
374
-
375
- // src/ui/interaction-copy.ts
376
- function actionErrorMessage(error, fallback) {
377
- return error instanceof Error ? error.message : fallback;
378
- }
379
- function acknowledgeSuccessMessage(title) {
380
- return `Acknowledged ${title}.`;
381
- }
382
- function acknowledgeFailureMessage(error) {
383
- return actionErrorMessage(error, "Failed to acknowledge frame.");
384
- }
385
- function issueFrameUnsupportedMessage() {
386
- return "This frame is not backed by a Paperclip issue.";
387
- }
388
- function commentSuccessMessage(title) {
389
- return `Posted a comment on ${title}.`;
390
- }
391
- function commentFailureMessage(error) {
392
- return actionErrorMessage(error, "Failed to post comment.");
393
- }
394
- function approvalFrameUnsupportedMessage() {
395
- return "This frame is not backed by a Paperclip approval.";
396
- }
397
- function approvalDecisionSuccessMessage(title, decision) {
398
- return `${decision === "approve" ? "Approved" : "Rejected"} ${title}.`;
399
- }
400
- function approvalDecisionFailureMessage(error) {
401
- return actionErrorMessage(error, "Failed to submit approval decision.");
402
- }
403
- function approvalRevisionSuccessMessage(title) {
404
- return `Requested revision for ${title}.`;
405
- }
406
- function approvalRevisionFailureMessage(error) {
407
- return actionErrorMessage(error, "Failed to request approval revision.");
408
- }
409
-
410
- // src/ui/index.tsx
411
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
31
+ // src/ui/chrome.tsx
32
+ import { useEffect, useRef, useState } from "react";
33
+ import { jsx, jsxs } from "react/jsx-runtime";
412
34
  var ACCENT_COLOR = "#007ACC";
413
35
  var ACCENT_BG = `${ACCENT_COLOR}14`;
414
36
  var ACCENT_BORDER = `${ACCENT_COLOR}33`;
415
37
  var ACCENT_BG_STYLE = { backgroundColor: ACCENT_COLOR };
416
- var ATTENTION_UPDATES_STREAM = "attention-updates";
417
- function currentSurfaceBrand() {
418
- return {
419
- key: "focus",
420
- wordmark: "Focus",
421
- headingEmptyState: "No focus state yet.",
422
- loadingLabel: "Loading Focus\u2026"
423
- };
38
+ function cn(...parts) {
39
+ return parts.filter(Boolean).join(" ");
424
40
  }
425
41
  function useAccentColor() {
426
42
  const ref = useRef(null);
@@ -456,259 +72,40 @@ function useWideLayout(minWidth = 1024) {
456
72
  }, [minWidth]);
457
73
  return matches;
458
74
  }
459
- function cn(...parts) {
460
- return parts.filter(Boolean).join(" ");
461
- }
462
- function pluginPagePath(companyPrefix) {
463
- return companyPrefix ? `/${companyPrefix}/aperture` : "/aperture";
464
- }
465
- function formatRelativeTime(value) {
466
- const timestamp = new Date(value).getTime();
467
- if (Number.isNaN(timestamp)) return "recently";
468
- const elapsedMs = timestamp - Date.now();
469
- const elapsedMinutes = Math.round(elapsedMs / 6e4);
470
- const formatter = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
471
- if (Math.abs(elapsedMinutes) < 60) return formatter.format(elapsedMinutes, "minute");
472
- const elapsedHours = Math.round(elapsedMinutes / 60);
473
- if (Math.abs(elapsedHours) < 24) return formatter.format(elapsedHours, "hour");
474
- const elapsedDays = Math.round(elapsedHours / 24);
475
- return formatter.format(elapsedDays, "day");
476
- }
477
- function sourceLabel(frame) {
478
- return frame.source?.label ?? "Paperclip";
479
- }
480
- function contextValue(frame, id) {
481
- const item2 = frame.context?.items?.find((entry) => entry.id === id);
482
- return typeof item2?.value === "string" && item2.value.trim().length > 0 ? item2.value : void 0;
483
- }
484
- function requestedAmount(frame) {
485
- return contextValue(frame, ATTENTION_CONTEXT_IDS.requestedAmount);
486
- }
487
- function budgetReason(frame) {
488
- return contextValue(frame, ATTENTION_CONTEXT_IDS.budgetReason);
75
+ function UnreadDot(props) {
76
+ const tone = props.tone ?? "default";
77
+ if (!props.visible && !props.reserveSpace) return null;
78
+ const style = props.visible ? tone === "muted" ? { backgroundColor: ACCENT_BORDER } : ACCENT_BG_STYLE : { backgroundColor: "transparent" };
79
+ 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 });
489
80
  }
490
- function recommendedMoveValue(frame) {
491
- return contextValue(frame, ATTENTION_CONTEXT_IDS.recommendedMove);
81
+ function ExternalLinkIcon() {
82
+ 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: [
83
+ /* @__PURE__ */ jsx("path", { d: "M6.5 3.5h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3" }),
84
+ /* @__PURE__ */ jsx("path", { d: "M8.5 1.5h6v6" }),
85
+ /* @__PURE__ */ jsx("path", { d: "M14.5 1.5l-7 7" })
86
+ ] });
492
87
  }
493
- function actionOwnerValue(frame) {
494
- return contextValue(frame, ATTENTION_CONTEXT_IDS.needsActionFrom);
88
+ function SkeletonLine(props) {
89
+ return /* @__PURE__ */ jsx("div", { className: cn("animate-pulse rounded bg-secondary/80", props.className), "aria-hidden": "true" });
495
90
  }
496
- function blockingTargetValue(frame) {
497
- return contextValue(frame, ATTENTION_CONTEXT_IDS.blocksTarget);
91
+ function MessageCard(props) {
92
+ return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm", children: [
93
+ /* @__PURE__ */ jsx("div", { className: cn("text-sm font-medium", props.accent && "text-foreground"), children: props.title }),
94
+ /* @__PURE__ */ jsx("div", { className: "mt-1 text-sm text-muted-foreground", children: props.body })
95
+ ] });
498
96
  }
499
- function impactLabel(frame) {
500
- return `${frame.consequence} impact`;
501
- }
502
- function operatorDriverLabel(value) {
503
- switch (value) {
504
- case "in_review":
505
- return "review required";
506
- case "pending_approval":
507
- return "approval required";
508
- default:
509
- return value.replace(/_/g, " ");
510
- }
511
- }
512
- function driverLabel(frame) {
513
- if (isBudgetOverride(frame)) return "budget stop";
514
- const issueStatus = frame.metadata?.issueStatus;
515
- if (typeof issueStatus === "string" && issueStatus.trim().length > 0) {
516
- return operatorDriverLabel(issueStatus);
517
- }
518
- const pauseReason = frame.metadata?.pauseReason;
519
- if (typeof pauseReason === "string" && pauseReason.trim().length > 0) {
520
- return operatorDriverLabel(pauseReason);
521
- }
522
- const agentStatus = frame.metadata?.agentStatus;
523
- if (typeof agentStatus === "string" && agentStatus.trim().length > 0) {
524
- return operatorDriverLabel(agentStatus);
525
- }
526
- return null;
527
- }
528
- function driverBadgeStyle() {
529
- return {
530
- className: "border-transparent",
531
- style: { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER }
532
- };
533
- }
534
- function requestDescriptor(frame) {
535
- const driver = driverLabel(frame);
536
- return driver ? `${sourceLabel(frame)} \xB7 ${driver}` : sourceLabel(frame);
537
- }
538
- function postureForSnapshot(snapshot) {
539
- const current = snapshot.now;
540
- if (current?.tone === "critical" || current?.consequence === "high" || snapshot.counts.now + snapshot.counts.next >= 3) {
541
- return { glyph: "\u25CF", label: "busy" };
542
- }
543
- if (snapshot.counts.now > 0 || snapshot.counts.next > 0 || snapshot.counts.ambient > 0) {
544
- return { glyph: "\u25D0", label: "elevated" };
545
- }
546
- return { glyph: "\u25CB", label: "calm" };
547
- }
548
- function actionableCount(snapshot) {
549
- return (snapshot.now ? 1 : 0) + snapshot.next.length;
550
- }
551
- function actionableLabel(snapshot) {
552
- const actionable = actionableCount(snapshot);
553
- return actionable > 0 ? `${actionable} action${actionable === 1 ? "" : "s"}` : null;
554
- }
555
- function judgmentLine(frame, lane) {
556
- if (frame.provenance?.whyNow) return frame.provenance.whyNow;
557
- return genericJudgmentLine(frame, lane);
558
- }
559
- function nextPrimaryText(frame) {
560
- const recommendedMove = recommendedMoveValue(frame);
561
- if (recommendedMove) return recommendedMove;
562
- const summary = frame.summary?.trim();
563
- if (summary) return summary;
564
- const fallback = judgmentLine(frame, "next");
565
- return fallback === GENERIC_QUEUED_JUDGMENT ? null : fallback;
566
- }
567
- function supportingLine(frame, lane) {
568
- const owner = actionOwnerValue(frame);
569
- const target = blockingTargetValue(frame);
570
- if (target && lane === "now" && entityTypeFromFrame(frame) === "issue" && driverLabel(frame) === "review required") {
571
- return issueBlocksTargetLine(target);
572
- }
573
- if (owner && lane === "now" && entityTypeFromFrame(frame) === "issue") {
574
- return issueNeedsActionFromLine(owner);
575
- }
576
- if (lane === "now") {
577
- const recommendedMove = recommendedMoveValue(frame)?.trim();
578
- const summary = frame.summary?.trim();
579
- const judgment2 = judgmentLine(frame, lane).trim();
580
- if (recommendedMove && summary && summary !== recommendedMove && summary !== judgment2) {
581
- return summary;
582
- }
583
- return null;
584
- }
585
- const judgment = judgmentLine(frame, lane);
586
- return judgment.trim().length > 0 ? judgment : null;
587
- }
588
- function approvalIdForFrame(frame) {
589
- const [kind, id] = frame.taskId.split(":");
590
- return kind === "approval" && id ? id : null;
591
- }
592
- function entityIdFromFrame(frame) {
593
- const parts = frame.taskId.split(":");
594
- return parts.length >= 2 ? parts.slice(1).join(":") : null;
595
- }
596
- function entityTypeFromFrame(frame) {
597
- const parts = frame.taskId.split(":");
598
- return parts.length >= 2 ? parts[0] : null;
599
- }
600
- function itemHref(frame, companyPrefix) {
601
- const entityType = entityTypeFromFrame(frame);
602
- const entityId = entityIdFromFrame(frame);
603
- if (!entityType || !entityId || !companyPrefix) return null;
604
- const pluralType = entityType === "run" ? "runs" : entityType === "approval" ? "approvals" : entityType === "issue" ? "issues" : entityType === "agent" ? "agents" : null;
605
- if (!pluralType) return null;
606
- return `/${companyPrefix}/${pluralType}/${entityId}`;
607
- }
608
- function costsHref(companyPrefix) {
609
- return companyPrefix ? `/${companyPrefix}/costs` : null;
610
- }
611
- function activityHref(frame, companyPrefix) {
612
- if (!companyPrefix) return null;
613
- return frame.metadata?.activityPath ? `/${companyPrefix}/${frame.metadata.activityPath}` : null;
614
- }
615
- function primaryLinkLabel(frame) {
616
- const entityType = entityTypeFromFrame(frame);
617
- switch (entityType) {
618
- case "approval":
619
- return "Open approval";
620
- case "issue":
621
- return "Open issue";
622
- case "run":
623
- return "Open run";
624
- case "agent":
625
- return "Open agent";
626
- default:
627
- return "Open in Paperclip";
628
- }
629
- }
630
- function responseKind(frame, lane) {
631
- if (approvalIdForFrame(frame) && (frame.responseSpec?.kind === "approval" || frame.mode === "approval")) {
632
- return "approval";
633
- }
634
- if (lane !== "ambient" && (frame.responseSpec?.kind === "acknowledge" || frame.mode === "status")) {
635
- return "acknowledge";
636
- }
637
- return "none";
638
- }
639
- function unreadCount(snapshot) {
640
- return snapshot?.review?.unread.total ?? 0;
641
- }
642
- function applyLocalSuppressions(snapshot, suppressedAtByTaskId) {
643
- if (!snapshot || Object.keys(suppressedAtByTaskId).length === 0) return snapshot;
644
- const keepFrame = (frame) => {
645
- const suppressedAt = suppressedAtByTaskId[frame.taskId];
646
- if (!suppressedAt) return true;
647
- return frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(suppressedAt) > 0;
648
- };
649
- const next = snapshot.next.filter(keepFrame);
650
- const ambient = snapshot.ambient.filter(keepFrame);
651
- const nowCandidate = snapshot.now && keepFrame(snapshot.now) ? snapshot.now : null;
652
- const now = nowCandidate ?? next[0] ?? null;
653
- const nextFrames = nowCandidate ? next : next.slice(1);
654
- return {
655
- ...snapshot,
656
- now,
657
- next: nextFrames,
658
- ambient,
659
- counts: {
660
- now: now ? 1 : 0,
661
- next: nextFrames.length,
662
- ambient: ambient.length,
663
- total: (now ? 1 : 0) + nextFrames.length + ambient.length
664
- }
665
- };
666
- }
667
- function suppressionMapFromReview(review) {
668
- if (!review) return {};
669
- return Object.fromEntries(
670
- Object.entries(review.frames).filter(([, state]) => typeof state?.suppressedAt === "string" && state.suppressedAt.length > 0).map(([taskId, state]) => [taskId, state.suppressedAt])
671
- );
672
- }
673
- function isFrameUnreadInSnapshot(frame, snapshot) {
674
- const seenAt = snapshot.review?.lastSeenAt;
675
- if (!seenAt) return true;
676
- return frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(seenAt) > 0;
677
- }
678
- function UnreadDot(props) {
679
- const tone = props.tone ?? "default";
680
- if (!props.visible && !props.reserveSpace) return null;
681
- const style = props.visible ? tone === "muted" ? { backgroundColor: ACCENT_BORDER } : ACCENT_BG_STYLE : { backgroundColor: "transparent" };
682
- 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 });
683
- }
684
- function ExternalLinkIcon() {
685
- 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: [
686
- /* @__PURE__ */ jsx("path", { d: "M6.5 3.5h-3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3" }),
687
- /* @__PURE__ */ jsx("path", { d: "M8.5 1.5h6v6" }),
688
- /* @__PURE__ */ jsx("path", { d: "M14.5 1.5l-7 7" })
689
- ] });
690
- }
691
- function SkeletonLine(props) {
692
- return /* @__PURE__ */ jsx("div", { className: cn("animate-pulse rounded bg-secondary/80", props.className), "aria-hidden": "true" });
693
- }
694
- function MessageCard(props) {
695
- return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card p-4 shadow-sm", children: [
696
- /* @__PURE__ */ jsx("div", { className: cn("text-sm font-medium", props.accent && "text-foreground"), children: props.title }),
697
- /* @__PURE__ */ jsx("div", { className: "mt-1 text-sm text-muted-foreground", children: props.body })
698
- ] });
699
- }
700
- function WidgetLoadingState({ label }) {
701
- return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card px-4 py-3 shadow-sm", children: [
702
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
703
- /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-0.5 h-3 w-3 rounded-full" }),
704
- /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
705
- /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-20" }),
706
- /* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-48" })
707
- ] }),
708
- /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-24" })
709
- ] }),
710
- /* @__PURE__ */ jsx("div", { className: "sr-only", children: label })
711
- ] });
97
+ function WidgetLoadingState({ label }) {
98
+ return /* @__PURE__ */ jsxs("div", { className: "border border-border bg-card px-4 py-3 shadow-sm", children: [
99
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
100
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "mt-0.5 h-3 w-3 rounded-full" }),
101
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-2", children: [
102
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-20" }),
103
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-48" })
104
+ ] }),
105
+ /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-24" })
106
+ ] }),
107
+ /* @__PURE__ */ jsx("div", { className: "sr-only", children: label })
108
+ ] });
712
109
  }
713
110
  function PageLoadingState({ label }) {
714
111
  return /* @__PURE__ */ jsxs("div", { className: "space-y-5", "aria-label": label, children: [
@@ -753,124 +150,117 @@ function StatusToast({ message }) {
753
150
  }
754
151
  );
755
152
  }
756
- function readCsrfToken() {
757
- if (typeof document === "undefined") return null;
758
- const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute("content");
759
- return token && token.trim().length > 0 ? token : null;
760
- }
761
- async function paperclipApiFetch(path, options = {}) {
762
- const csrfToken = readCsrfToken();
763
- const {
764
- retries = 0,
765
- expectJson = true,
766
- headers,
767
- ...init
768
- } = options;
769
- let lastError = null;
770
- for (let attempt = 0; attempt <= retries; attempt += 1) {
771
- try {
772
- const response = await window.fetch(path, {
773
- credentials: "same-origin",
774
- ...init,
775
- headers: {
776
- Accept: "application/json",
777
- "X-Requested-With": "XMLHttpRequest",
778
- ...csrfToken ? { "X-CSRF-Token": csrfToken } : {},
779
- ...headers ?? {}
780
- }
781
- });
782
- if (!response.ok) {
783
- const responseBody = await response.text().catch(() => "");
784
- const detail = responseBody.trim().length > 0 ? `: ${responseBody.trim()}` : "";
785
- throw new Error(`Request failed (${response.status})${detail}`);
786
- }
787
- if (!expectJson) return void 0;
788
- return await response.json();
789
- } catch (error) {
790
- lastError = error instanceof Error ? error : new Error(String(error));
791
- if (attempt >= retries) break;
792
- await new Promise((resolve) => window.setTimeout(resolve, 250 * (attempt + 1)));
153
+ function Badge(props) {
154
+ return /* @__PURE__ */ jsx(
155
+ "span",
156
+ {
157
+ className: cn(
158
+ "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap shrink-0",
159
+ props.className
160
+ ),
161
+ style: props.style,
162
+ children: props.children
793
163
  }
794
- }
795
- throw lastError ?? new Error("Request failed.");
796
- }
797
- function compactTitle(frame) {
798
- return frame.title.trim().length > 0 ? frame.title : "Untitled frame";
164
+ );
799
165
  }
800
- function usePendingApprovals(companyId) {
801
- const [data, setData] = useState(null);
802
- const [loading, setLoading] = useState(true);
803
- const [error, setError] = useState(null);
804
- const [refreshVersion, setRefreshVersion] = useState(0);
805
- useEffect(() => {
806
- if (!companyId) {
807
- setData(null);
808
- setLoading(false);
809
- setError(null);
810
- return;
166
+ function ActionButton(props) {
167
+ const [hovered, setHovered] = useState(false);
168
+ 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";
169
+ return /* @__PURE__ */ jsx(
170
+ "button",
171
+ {
172
+ type: "button",
173
+ onClick: props.onClick,
174
+ onMouseEnter: () => setHovered(true),
175
+ onMouseLeave: () => setHovered(false),
176
+ disabled: props.disabled,
177
+ className: cn(
178
+ "inline-flex h-8 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium",
179
+ "transition-[color,background-color,border-color,box-shadow,opacity,filter]",
180
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
181
+ "disabled:pointer-events-none disabled:opacity-50",
182
+ props.disabled && /…|\.\.\./.test(props.label) && "animate-pulse",
183
+ toneClass,
184
+ props.className
185
+ ),
186
+ style: props.tone === "accent" ? { ...ACCENT_BG_STYLE, filter: hovered && !props.disabled ? "brightness(1.08)" : void 0 } : void 0,
187
+ children: props.label
811
188
  }
812
- let cancelled = false;
813
- async function loadApprovals() {
814
- try {
815
- setError(null);
816
- const approvals = await paperclipApiFetch(`/api/companies/${companyId}/approvals?status=pending`, {
817
- method: "GET",
818
- retries: 1
819
- });
820
- if (!cancelled) {
821
- setData(approvals);
822
- setLoading(false);
823
- }
824
- } catch (fetchError) {
825
- if (!cancelled) {
826
- setError({ message: fetchError instanceof Error ? fetchError.message : String(fetchError) });
827
- setLoading(false);
828
- }
829
- }
189
+ );
190
+ }
191
+ function QueueMovementBadge({ movement }) {
192
+ if (!movement) return null;
193
+ return /* @__PURE__ */ jsxs(
194
+ "span",
195
+ {
196
+ className: "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
197
+ style: {
198
+ color: ACCENT_COLOR,
199
+ backgroundColor: ACCENT_BG,
200
+ borderColor: ACCENT_BORDER
201
+ },
202
+ children: [
203
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: movement === "up" ? "\u2191" : "\u2193" }),
204
+ movement === "up" ? "moved up" : "moved down"
205
+ ]
830
206
  }
831
- setLoading(true);
832
- void loadApprovals();
833
- return () => {
834
- cancelled = true;
835
- };
836
- }, [companyId, refreshVersion]);
207
+ );
208
+ }
209
+
210
+ // src/ui/focus-model.tsx
211
+ import { usePluginData, usePluginStream } from "@paperclipai/plugin-sdk/ui";
212
+ import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
213
+ var ATTENTION_UPDATES_STREAM = "attention-updates";
214
+ function currentSurfaceBrand() {
837
215
  return {
838
- data,
839
- loading,
840
- error,
841
- refresh: () => {
842
- setRefreshVersion((value) => value + 1);
843
- }
216
+ key: "focus",
217
+ wordmark: "Focus",
218
+ headingEmptyState: "No focus state yet.",
219
+ loadingLabel: "Loading Focus\u2026"
844
220
  };
845
221
  }
846
- var ENTITY_PATTERN = /\b([A-Z][A-Z0-9]+-\d+)\b|"([^"]+)"/g;
847
- function HighlightedTitle({ text }) {
848
- const parts = [];
849
- let lastIndex = 0;
850
- let match;
851
- const re = new RegExp(ENTITY_PATTERN.source, "g");
852
- while ((match = re.exec(text)) !== null) {
853
- if (match.index > lastIndex) {
854
- parts.push(text.slice(lastIndex, match.index));
855
- }
856
- const token = match[1] ?? match[2] ?? match[0];
857
- parts.push(
858
- /* @__PURE__ */ jsx(Accent, { children: match[1] ? token : `"${token}"` }, match.index)
859
- );
860
- lastIndex = re.lastIndex;
222
+ function pluginPagePath(companyPrefix) {
223
+ return companyPrefix ? `/${companyPrefix}/aperture` : "/aperture";
224
+ }
225
+ function formatRelativeTime(value) {
226
+ const timestamp = new Date(value).getTime();
227
+ if (Number.isNaN(timestamp)) return "recently";
228
+ const elapsedMs = timestamp - Date.now();
229
+ const elapsedMinutes = Math.round(elapsedMs / 6e4);
230
+ const formatter = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
231
+ if (Math.abs(elapsedMinutes) < 60) return formatter.format(elapsedMinutes, "minute");
232
+ const elapsedHours = Math.round(elapsedMinutes / 60);
233
+ if (Math.abs(elapsedHours) < 24) return formatter.format(elapsedHours, "hour");
234
+ const elapsedDays = Math.round(elapsedHours / 24);
235
+ return formatter.format(elapsedDays, "day");
236
+ }
237
+ function sourceLabel(frame) {
238
+ return frame.source?.label ?? "Paperclip";
239
+ }
240
+ function postureForSnapshot(snapshot) {
241
+ const current = snapshot.now;
242
+ if (current?.tone === "critical" || current?.consequence === "high" || snapshot.counts.now + snapshot.counts.next >= 3) {
243
+ return { glyph: "\u25CF", label: "busy" };
861
244
  }
862
- if (lastIndex < text.length) {
863
- parts.push(text.slice(lastIndex));
245
+ if (snapshot.counts.now > 0 || snapshot.counts.next > 0 || snapshot.counts.ambient > 0) {
246
+ return { glyph: "\u25D0", label: "elevated" };
864
247
  }
865
- return /* @__PURE__ */ jsx(Fragment, { children: parts });
248
+ return { glyph: "\u25CB", label: "calm" };
866
249
  }
867
- function renderTitle(frame) {
868
- return /* @__PURE__ */ jsx(HighlightedTitle, { text: compactTitle(frame) });
250
+ function actionableCount(snapshot) {
251
+ return (snapshot.now ? 1 : 0) + snapshot.next.length;
252
+ }
253
+ function actionableLabel(snapshot) {
254
+ const actionable = actionableCount(snapshot);
255
+ return actionable > 0 ? `${actionable} action${actionable === 1 ? "" : "s"}` : null;
256
+ }
257
+ function unreadCount(snapshot) {
258
+ return snapshot?.review?.unread.total ?? 0;
869
259
  }
870
- function useAttentionPolling(companyId, refreshers, intervalMs = 5e3) {
871
- const refreshersRef = useRef(refreshers);
260
+ function useAttentionPolling(companyId, refreshers, intervalMs = 15e3) {
261
+ const refreshersRef = useRef2(refreshers);
872
262
  refreshersRef.current = refreshers;
873
- useEffect(() => {
263
+ useEffect2(() => {
874
264
  if (!companyId) return;
875
265
  function refreshVisible() {
876
266
  if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
@@ -892,10 +282,10 @@ function useAttentionPolling(companyId, refreshers, intervalMs = 5e3) {
892
282
  }, [companyId, intervalMs]);
893
283
  }
894
284
  function useQueueMovement(frames, ttlMs = 3500) {
895
- const previousPositionsRef = useRef({});
896
- const timersRef = useRef({});
897
- const [movementById, setMovementById] = useState({});
898
- useEffect(() => {
285
+ const previousPositionsRef = useRef2({});
286
+ const timersRef = useRef2({});
287
+ const [movementById, setMovementById] = useState2({});
288
+ useEffect2(() => {
899
289
  const nextPositions = Object.fromEntries(frames.map((frame, index) => [frame.interactionId, index]));
900
290
  setMovementById((current) => {
901
291
  const updates = {};
@@ -930,94 +320,801 @@ function useQueueMovement(frames, ttlMs = 3500) {
930
320
  }
931
321
  function useAttentionModel(companyId) {
932
322
  const displayQuery = usePluginData("attention-display", companyId ? { companyId } : void 0);
933
- const approvalsQuery = usePendingApprovals(companyId);
934
323
  const updates = usePluginStream(
935
324
  ATTENTION_UPDATES_STREAM,
936
325
  companyId ? { companyId } : void 0
937
326
  );
938
- useAttentionPolling(companyId, [displayQuery.refresh, approvalsQuery.refresh]);
939
- useEffect(() => {
327
+ useAttentionPolling(companyId, [displayQuery.refresh]);
328
+ useEffect2(() => {
940
329
  if (!companyId || !updates.lastEvent) return;
941
330
  displayQuery.refresh();
942
- approvalsQuery.refresh();
943
331
  }, [companyId, updates.lastEvent?.updatedAt, updates.lastEvent?.eventType]);
944
- const snapshot = useMemo(
945
- () => companyId ? mergeSnapshotWithApprovals(displayQuery.data?.snapshot ?? null, companyId, approvalsQuery.data, displayQuery.data?.reviewState ?? null) : null,
946
- [displayQuery.data, approvalsQuery.data, companyId]
947
- );
948
- return {
949
- snapshot,
332
+ return useMemo(() => ({
333
+ snapshot: displayQuery.data?.snapshot ?? null,
950
334
  review: displayQuery.data?.reviewState ?? null,
951
- loading: displayQuery.loading || approvalsQuery.loading,
952
- error: displayQuery.error ?? approvalsQuery.error,
335
+ loading: displayQuery.loading,
336
+ error: displayQuery.error,
953
337
  refresh() {
954
338
  displayQuery.refresh();
955
- approvalsQuery.refresh();
956
339
  }
340
+ }), [displayQuery]);
341
+ }
342
+
343
+ // src/aperture/contracts.ts
344
+ function asRecord(value) {
345
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
346
+ }
347
+ function readString(value) {
348
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
349
+ }
350
+ function readNullableString(value) {
351
+ if (value === null) return null;
352
+ return readString(value);
353
+ }
354
+ function readStringArray(value) {
355
+ if (!Array.isArray(value)) return void 0;
356
+ const entries = value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
357
+ return entries.length > 0 ? entries : void 0;
358
+ }
359
+ function readRelationHints(value) {
360
+ if (!Array.isArray(value)) return void 0;
361
+ const hints = value.filter((entry) => {
362
+ if (typeof entry !== "object" || entry === null) return false;
363
+ const hint = entry;
364
+ return hint.kind === "same_issue" || hint.kind === "resolves" || hint.kind === "supersedes" || hint.kind === "repeats" || hint.kind === "escalates";
365
+ });
366
+ return hints.length > 0 ? hints : void 0;
367
+ }
368
+ function readFocusMetadata(frame) {
369
+ const metadata = asRecord(frame.metadata);
370
+ const attention = asRecord(metadata?.attention);
371
+ const semantic = asRecord(metadata?.semantic);
372
+ const episode = asRecord(metadata?.episode);
373
+ const issueIntelligence = asRecord(metadata?.issueIntelligence);
374
+ return {
375
+ entityType: readString(metadata?.entityType),
376
+ issueStatus: readString(metadata?.issueStatus),
377
+ issuePriority: readString(metadata?.issuePriority),
378
+ approvalStatus: readString(metadata?.approvalStatus),
379
+ approvalType: readString(metadata?.approvalType),
380
+ agentStatus: readString(metadata?.agentStatus),
381
+ pauseReason: readNullableString(metadata?.pauseReason),
382
+ liveReconciled: typeof metadata?.liveReconciled === "boolean" ? metadata.liveReconciled : void 0,
383
+ activityPath: readString(metadata?.activityPath),
384
+ attention: attention ? {
385
+ rationale: readStringArray(attention.rationale)
386
+ } : void 0,
387
+ semantic: semantic ? {
388
+ confidence: semantic.confidence === "low" || semantic.confidence === "medium" || semantic.confidence === "high" ? semantic.confidence : void 0,
389
+ relationHints: readRelationHints(semantic.relationHints)
390
+ } : void 0,
391
+ episode: episode ? {
392
+ size: typeof episode.size === "number" ? episode.size : void 0,
393
+ state: readString(episode.state)
394
+ } : void 0,
395
+ issueIntelligence: issueIntelligence ? {
396
+ matchedRuleIds: readStringArray(issueIntelligence.matchedRuleIds),
397
+ owner: readNullableString(issueIntelligence.owner),
398
+ ownerKind: issueIntelligence.ownerKind === "agent" || issueIntelligence.ownerKind === "human" ? issueIntelligence.ownerKind : issueIntelligence.ownerKind === null ? null : void 0,
399
+ blockingTarget: readNullableString(issueIntelligence.blockingTarget)
400
+ } : void 0
957
401
  };
958
402
  }
959
- function Badge(props) {
960
- return /* @__PURE__ */ jsx(
961
- "span",
962
- {
963
- className: cn(
964
- "inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap shrink-0",
965
- props.className
966
- ),
967
- style: props.style,
968
- children: props.children
969
- }
970
- );
403
+
404
+ // src/aperture/attention-context.ts
405
+ var ATTENTION_CONTEXT_IDS = {
406
+ approvalType: "approval-type",
407
+ requestedAmount: "requested-amount",
408
+ budgetReason: "budget-reason",
409
+ decisionContext: "decision-context",
410
+ linkedIssues: "linked-issues",
411
+ needsActionFrom: "needs-action-from",
412
+ blocksTarget: "blocks-target",
413
+ recommendedMove: "recommended-move",
414
+ issueStatus: "issue-status",
415
+ issuePriority: "issue-priority",
416
+ latestComment: "latest-comment",
417
+ agentStatus: "agent-status",
418
+ pauseReason: "pause-reason",
419
+ agentTitle: "agent-title"
420
+ };
421
+
422
+ // src/aperture/task-ref.ts
423
+ function decodeComponent(value) {
424
+ try {
425
+ const decoded = decodeURIComponent(value);
426
+ return decoded.trim().length > 0 ? decoded : null;
427
+ } catch {
428
+ return null;
429
+ }
971
430
  }
972
- function ActionButton(props) {
973
- const [hovered, setHovered] = useState(false);
974
- 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";
975
- return /* @__PURE__ */ jsx(
976
- "button",
977
- {
978
- type: "button",
979
- onClick: props.onClick,
980
- onMouseEnter: () => setHovered(true),
981
- onMouseLeave: () => setHovered(false),
982
- disabled: props.disabled,
983
- className: cn(
984
- "inline-flex h-8 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium",
985
- "transition-[color,background-color,border-color,box-shadow,opacity,filter]",
986
- "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
987
- "disabled:pointer-events-none disabled:opacity-50",
988
- props.disabled && /…|\.\.\./.test(props.label) && "animate-pulse",
989
- toneClass,
990
- props.className
991
- ),
992
- style: props.tone === "accent" ? { ...ACCENT_BG_STYLE, filter: hovered && !props.disabled ? "brightness(1.08)" : void 0 } : void 0,
993
- children: props.label
431
+ function parseTaskId(taskId) {
432
+ if (typeof taskId !== "string") return null;
433
+ const separatorIndex = taskId.indexOf(":");
434
+ if (separatorIndex <= 0 || separatorIndex >= taskId.length - 1) return null;
435
+ const kind = decodeComponent(taskId.slice(0, separatorIndex));
436
+ const id = decodeComponent(taskId.slice(separatorIndex + 1));
437
+ if (!kind || !id) return null;
438
+ return { kind, id };
439
+ }
440
+ function taskKind(taskId) {
441
+ return parseTaskId(taskId)?.kind ?? null;
442
+ }
443
+ function taskEntityId(taskId) {
444
+ return parseTaskId(taskId)?.id ?? null;
445
+ }
446
+
447
+ // src/aperture/issue-intelligence.ts
448
+ function issueBlocksTargetLine(target) {
449
+ return `Blocks ${target} until the review lands.`;
450
+ }
451
+ function issueNeedsActionFromLine(owner) {
452
+ return `Needs action from ${owner}.`;
453
+ }
454
+
455
+ // src/ui/frame-helpers.tsx
456
+ import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
457
+ function contextValue(frame, id) {
458
+ const item = frame.context?.items?.find((entry) => entry.id === id);
459
+ return typeof item?.value === "string" && item.value.trim().length > 0 ? item.value : void 0;
460
+ }
461
+ function requestedAmount(frame) {
462
+ return contextValue(frame, ATTENTION_CONTEXT_IDS.requestedAmount);
463
+ }
464
+ function budgetReason(frame) {
465
+ return contextValue(frame, ATTENTION_CONTEXT_IDS.budgetReason);
466
+ }
467
+ function recommendedMoveValue(frame) {
468
+ return contextValue(frame, ATTENTION_CONTEXT_IDS.recommendedMove);
469
+ }
470
+ function actionOwnerValue(frame) {
471
+ return contextValue(frame, ATTENTION_CONTEXT_IDS.needsActionFrom);
472
+ }
473
+ function blockingTargetValue(frame) {
474
+ return contextValue(frame, ATTENTION_CONTEXT_IDS.blocksTarget);
475
+ }
476
+ function impactLabel(frame) {
477
+ return `${frame.consequence} impact`;
478
+ }
479
+ function operatorDriverLabel(value) {
480
+ switch (value) {
481
+ case "in_review":
482
+ return "review required";
483
+ case "pending_approval":
484
+ return "approval required";
485
+ default:
486
+ return value.replace(/_/g, " ");
487
+ }
488
+ }
489
+ function driverLabel(frame) {
490
+ if (isBudgetOverride(frame)) return "budget stop";
491
+ const metadata = readFocusMetadata(frame);
492
+ const issueStatus = metadata.issueStatus;
493
+ if (typeof issueStatus === "string" && issueStatus.trim().length > 0) {
494
+ return operatorDriverLabel(issueStatus);
495
+ }
496
+ const pauseReason = metadata.pauseReason;
497
+ if (typeof pauseReason === "string" && pauseReason.trim().length > 0) {
498
+ return operatorDriverLabel(pauseReason);
499
+ }
500
+ const agentStatus = metadata.agentStatus;
501
+ if (typeof agentStatus === "string" && agentStatus.trim().length > 0) {
502
+ return operatorDriverLabel(agentStatus);
503
+ }
504
+ return null;
505
+ }
506
+ function driverBadgeStyle() {
507
+ return {
508
+ className: "border-transparent",
509
+ style: { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER }
510
+ };
511
+ }
512
+ function requestDescriptor(frame) {
513
+ const driver = driverLabel(frame);
514
+ return driver ? `${sourceLabel(frame)} \xB7 ${driver}` : sourceLabel(frame);
515
+ }
516
+ function judgmentLine(frame, lane) {
517
+ if (frame.provenance?.whyNow) return frame.provenance.whyNow;
518
+ return genericJudgmentLine(frame, lane);
519
+ }
520
+ function nextPrimaryText(frame) {
521
+ const recommendedMove = recommendedMoveValue(frame);
522
+ if (recommendedMove) return recommendedMove;
523
+ const summary = frame.summary?.trim();
524
+ if (summary) return summary;
525
+ const fallback = judgmentLine(frame, "next");
526
+ return fallback === GENERIC_QUEUED_JUDGMENT ? null : fallback;
527
+ }
528
+ function supportingLine(frame, lane) {
529
+ const owner = actionOwnerValue(frame);
530
+ const target = blockingTargetValue(frame);
531
+ if (target && lane === "now" && entityTypeFromFrame(frame) === "issue" && driverLabel(frame) === "review required") {
532
+ return issueBlocksTargetLine(target);
533
+ }
534
+ if (owner && lane === "now" && entityTypeFromFrame(frame) === "issue") {
535
+ return issueNeedsActionFromLine(owner);
536
+ }
537
+ if (lane === "now") {
538
+ const recommendedMove = recommendedMoveValue(frame)?.trim();
539
+ const summary = frame.summary?.trim();
540
+ const judgment2 = judgmentLine(frame, lane).trim();
541
+ if (recommendedMove && summary && summary !== recommendedMove && summary !== judgment2) {
542
+ return summary;
994
543
  }
995
- );
544
+ return null;
545
+ }
546
+ const judgment = judgmentLine(frame, lane);
547
+ return judgment.trim().length > 0 ? judgment : null;
996
548
  }
997
- function QueueMovementBadge({ movement }) {
998
- if (!movement) return null;
999
- return /* @__PURE__ */ jsxs(
1000
- "span",
1001
- {
1002
- className: "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",
1003
- style: {
1004
- color: ACCENT_COLOR,
1005
- backgroundColor: ACCENT_BG,
1006
- borderColor: ACCENT_BORDER
1007
- },
1008
- children: [
1009
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: movement === "up" ? "\u2191" : "\u2193" }),
1010
- movement === "up" ? "moved up" : "moved down"
1011
- ]
549
+ function approvalIdForFrame(frame) {
550
+ const taskRef = parseTaskId(frame.taskId);
551
+ return taskRef?.kind === "approval" ? taskRef.id : null;
552
+ }
553
+ function entityIdFromFrame(frame) {
554
+ return taskEntityId(frame.taskId);
555
+ }
556
+ function entityTypeFromFrame(frame) {
557
+ return taskKind(frame.taskId);
558
+ }
559
+ function itemHref(frame, companyPrefix) {
560
+ const entityType = entityTypeFromFrame(frame);
561
+ const entityId = entityIdFromFrame(frame);
562
+ if (!entityType || !entityId || !companyPrefix) return null;
563
+ const pluralType = entityType === "run" ? "runs" : entityType === "approval" ? "approvals" : entityType === "issue" ? "issues" : entityType === "agent" ? "agents" : null;
564
+ if (!pluralType) return null;
565
+ return `/${companyPrefix}/${pluralType}/${entityId}`;
566
+ }
567
+ function costsHref(companyPrefix) {
568
+ return companyPrefix ? `/${companyPrefix}/costs` : null;
569
+ }
570
+ function activityHref(frame, companyPrefix) {
571
+ if (!companyPrefix) return null;
572
+ const metadata = readFocusMetadata(frame);
573
+ return metadata.activityPath ? `/${companyPrefix}/${metadata.activityPath}` : null;
574
+ }
575
+ function primaryLinkLabel(frame) {
576
+ const entityType = entityTypeFromFrame(frame);
577
+ switch (entityType) {
578
+ case "approval":
579
+ return "Open approval";
580
+ case "issue":
581
+ return "Open issue";
582
+ case "run":
583
+ return "Open run";
584
+ case "agent":
585
+ return "Open agent";
586
+ default:
587
+ return "Open in Paperclip";
588
+ }
589
+ }
590
+ function responseKind(frame, lane) {
591
+ if (approvalIdForFrame(frame) && (frame.responseSpec?.kind === "approval" || frame.mode === "approval")) {
592
+ return "approval";
593
+ }
594
+ if (lane !== "ambient" && (frame.responseSpec?.kind === "acknowledge" || frame.mode === "status")) {
595
+ return "acknowledge";
596
+ }
597
+ return "none";
598
+ }
599
+ function isFrameUnreadInSnapshot(frame, snapshot) {
600
+ const seenAt = snapshot.review?.lastSeenAt;
601
+ if (!seenAt) return true;
602
+ return frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(seenAt) > 0;
603
+ }
604
+ function compactTitle(frame) {
605
+ return frame.title.trim().length > 0 ? frame.title : "Untitled frame";
606
+ }
607
+ var ENTITY_PATTERN = /\b([A-Z][A-Z0-9]+-\d+)\b|"([^"]+)"/g;
608
+ function HighlightedTitle({ text }) {
609
+ const parts = [];
610
+ let lastIndex = 0;
611
+ let match;
612
+ const re = new RegExp(ENTITY_PATTERN.source, "g");
613
+ while ((match = re.exec(text)) !== null) {
614
+ if (match.index > lastIndex) {
615
+ parts.push(text.slice(lastIndex, match.index));
616
+ }
617
+ const token = match[1] ?? match[2] ?? match[0];
618
+ parts.push(
619
+ /* @__PURE__ */ jsx2(Accent, { children: match[1] ? token : `"${token}"` }, match.index)
620
+ );
621
+ lastIndex = re.lastIndex;
622
+ }
623
+ if (lastIndex < text.length) {
624
+ parts.push(text.slice(lastIndex));
625
+ }
626
+ return /* @__PURE__ */ jsx2(Fragment, { children: parts });
627
+ }
628
+ function renderTitle(frame) {
629
+ return /* @__PURE__ */ jsx2(HighlightedTitle, { text: compactTitle(frame) });
630
+ }
631
+ function visibleContextItems(frame) {
632
+ return (frame.context?.items ?? []).filter((item) => item.id !== ATTENTION_CONTEXT_IDS.recommendedMove);
633
+ }
634
+ function isIssueFrame(frame) {
635
+ return entityTypeFromFrame(frame) === "issue";
636
+ }
637
+
638
+ // src/aperture/explainability.ts
639
+ function readStringArray2(value) {
640
+ if (!Array.isArray(value)) return [];
641
+ return value.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
642
+ }
643
+ function readSemanticConfidence(frame) {
644
+ const confidence = readFocusMetadata(frame).semantic?.confidence;
645
+ return confidence === "low" || confidence === "medium" || confidence === "high" ? confidence : null;
646
+ }
647
+ function readSemanticRelationHints(frame) {
648
+ return readFocusMetadata(frame).semantic?.relationHints ?? [];
649
+ }
650
+ function relationHintLabel(hint) {
651
+ switch (hint.kind) {
652
+ case "same_issue":
653
+ return "Part of the same thread";
654
+ case "resolves":
655
+ return "Resolves an earlier blocker";
656
+ case "supersedes":
657
+ return "Moves the request forward";
658
+ case "repeats":
659
+ return "Repeats an earlier ask";
660
+ case "escalates":
661
+ return "Raises the urgency";
662
+ }
663
+ }
664
+ function laneReason(lane) {
665
+ switch (lane) {
666
+ case "now":
667
+ return "This is the most urgent item in the queue right now.";
668
+ case "next":
669
+ return "This is staged behind the current top item.";
670
+ case "ambient":
671
+ return "This is visible for awareness without needing action yet.";
672
+ }
673
+ }
674
+ function continuitySummary(frame) {
675
+ const episode = readFocusMetadata(frame).episode;
676
+ const size = episode?.size ?? null;
677
+ const state = episode?.state?.replace(/_/g, " ") ?? null;
678
+ if (!size || size <= 1) return null;
679
+ if (state) return `Part of a ${state} thread with ${size} related interactions.`;
680
+ return `Part of a thread with ${size} related interactions.`;
681
+ }
682
+ function frameSignals(frame) {
683
+ const rationale = readFocusMetadata(frame).attention?.rationale ?? [];
684
+ const factors = readStringArray2(frame.provenance?.factors);
685
+ return [.../* @__PURE__ */ new Set([...rationale.length > 0 ? rationale : factors, ...factors])];
686
+ }
687
+ function signalStrengthLabel(confidence) {
688
+ return `${confidence} confidence`;
689
+ }
690
+ function explainFrame(frame, lane) {
691
+ const relationLabels = [...new Set(readSemanticRelationHints(frame).map(relationHintLabel))];
692
+ return {
693
+ whyNow: frame.provenance?.whyNow ?? frame.summary ?? null,
694
+ laneReason: laneReason(lane),
695
+ signalStrength: readSemanticConfidence(frame),
696
+ signals: frameSignals(frame),
697
+ relationLabels,
698
+ continuity: continuitySummary(frame)
699
+ };
700
+ }
701
+
702
+ // src/ui/frame-explainability.tsx
703
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
704
+ function ContextItems({ frame }) {
705
+ const items = visibleContextItems(frame);
706
+ if (items.length === 0) return null;
707
+ return /* @__PURE__ */ jsx3("div", { className: "grid gap-2 md:grid-cols-2", children: items.map((item) => /* @__PURE__ */ jsxs2(
708
+ "div",
709
+ {
710
+ className: cn(
711
+ "border border-border bg-secondary/50 px-3 py-2",
712
+ item.id === ATTENTION_CONTEXT_IDS.latestComment && "md:col-span-2"
713
+ ),
714
+ children: [
715
+ /* @__PURE__ */ jsx3("div", { className: "text-xs font-medium text-muted-foreground", children: item.label }),
716
+ /* @__PURE__ */ jsx3("div", { className: "mt-1 text-sm text-foreground", children: item.value ?? "Available" })
717
+ ]
718
+ },
719
+ item.id
720
+ )) });
721
+ }
722
+ function ExplainabilityBadges(props) {
723
+ if (props.values.length === 0) return null;
724
+ return /* @__PURE__ */ jsx3("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx3(
725
+ Badge,
726
+ {
727
+ className: props.accent ? "border-transparent" : "border-border bg-background/70 text-foreground/80",
728
+ style: props.accent ? { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER } : void 0,
729
+ children: value
730
+ },
731
+ value
732
+ )) });
733
+ }
734
+ function InlineExplainability(props) {
735
+ const explanation = explainFrame(props.frame, props.lane);
736
+ const chips = [
737
+ ...explanation.signalStrength ? [signalStrengthLabel(explanation.signalStrength)] : [],
738
+ ...explanation.signals.slice(0, 2),
739
+ ...explanation.relationLabels.slice(0, 1)
740
+ ];
741
+ const label = props.lane === "next" ? "Why next" : props.lane === "ambient" ? "Why ambient" : "Why now";
742
+ const whyNow = explanation.whyNow ?? judgmentLine(props.frame, props.lane);
743
+ const primaryLine = props.lane === "now" ? props.preferLaneReason ? explanation.laneReason : whyNow : whyNow;
744
+ const secondaryLine = props.lane === "now" ? !props.preferLaneReason && whyNow !== explanation.laneReason ? explanation.laneReason : null : explanation.laneReason;
745
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-2 border-l-2 pl-4", style: { borderColor: ACCENT_COLOR }, children: [
746
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
747
+ /* @__PURE__ */ jsx3("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
748
+ secondaryLine ? /* @__PURE__ */ jsx3("p", { className: "text-xs leading-relaxed text-muted-foreground", children: secondaryLine }) : null,
749
+ chips.length > 0 ? /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: chips, accent: true }) : null
750
+ ] });
751
+ }
752
+ function ExplainabilityPanel(props) {
753
+ const explanation = explainFrame(props.frame, props.lane);
754
+ const strength = !props.detailOnly && explanation.signalStrength ? signalStrengthLabel(explanation.signalStrength) : null;
755
+ const signalValues = props.detailOnly ? explanation.signals.slice(2) : explanation.signals;
756
+ const relationValues = props.detailOnly ? explanation.relationLabels.slice(1) : explanation.relationLabels;
757
+ const reasoningLabel = props.lane === "now" ? "Reasoning" : props.lane === "next" ? "Why it sits here" : "Why it stays quiet";
758
+ if (props.detailOnly && signalValues.length === 0 && relationValues.length === 0 && !explanation.continuity) {
759
+ return null;
760
+ }
761
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
762
+ !props.detailOnly ? /* @__PURE__ */ jsxs2("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_16rem]", children: [
763
+ /* @__PURE__ */ jsxs2("div", { children: [
764
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: reasoningLabel }),
765
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: explanation.laneReason })
766
+ ] }),
767
+ strength ? /* @__PURE__ */ jsxs2("div", { children: [
768
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Confidence" }),
769
+ /* @__PURE__ */ jsx3("p", { className: "mt-1 text-sm text-foreground/90", children: strength })
770
+ ] }) : null
771
+ ] }) : null,
772
+ signalValues.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
773
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More signals" : "Signals" }),
774
+ /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: signalValues })
775
+ ] }) : null,
776
+ relationValues.length > 0 ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
777
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More thread context" : "Thread context" }),
778
+ /* @__PURE__ */ jsx3(ExplainabilityBadges, { values: relationValues, accent: true })
779
+ ] }) : null,
780
+ explanation.continuity ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
781
+ /* @__PURE__ */ jsx3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Related activity" }),
782
+ /* @__PURE__ */ jsx3("p", { className: "text-sm leading-relaxed text-muted-foreground", children: explanation.continuity })
783
+ ] }) : null
784
+ ] });
785
+ }
786
+ function ExplainabilityStrip(props) {
787
+ 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 }) });
788
+ }
789
+
790
+ // src/ui/issue-comment-composer.tsx
791
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
792
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
793
+ function IssueCommentComposer(props) {
794
+ const [open, setOpen] = useState3(false);
795
+ const [body, setBody] = useState3("");
796
+ const textareaRef = useRef3(null);
797
+ const issueFrame = isIssueFrame(props.frame);
798
+ const isPending = props.pendingId === props.frame.id;
799
+ useEffect3(() => {
800
+ setOpen(false);
801
+ setBody("");
802
+ }, [props.frame.id]);
803
+ useEffect3(() => {
804
+ if (open && issueFrame) textareaRef.current?.focus();
805
+ }, [open, issueFrame]);
806
+ if (!issueFrame) return null;
807
+ async function submit() {
808
+ const nextBody = body.trim();
809
+ if (!nextBody) return;
810
+ await props.onComment(props.frame, nextBody);
811
+ setBody("");
812
+ setOpen(false);
813
+ }
814
+ if (!open) {
815
+ const label = props.triggerLabel ?? "Leave comment";
816
+ const triggerTone = props.triggerTone ?? "link";
817
+ if (triggerTone === "link") {
818
+ return /* @__PURE__ */ jsx4(
819
+ "button",
820
+ {
821
+ type: "button",
822
+ className: "text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
823
+ onClick: () => setOpen(true),
824
+ children: label
825
+ }
826
+ );
827
+ }
828
+ return /* @__PURE__ */ jsx4(
829
+ ActionButton,
830
+ {
831
+ label,
832
+ tone: triggerTone,
833
+ className: props.triggerFullWidth ? "w-full" : void 0,
834
+ disabled: isPending,
835
+ onClick: () => setOpen(true)
836
+ }
837
+ );
838
+ }
839
+ return /* @__PURE__ */ jsxs3("div", { className: "w-full space-y-2 rounded-md border border-border bg-secondary/40 p-3", children: [
840
+ /* @__PURE__ */ jsxs3("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
841
+ "Commenting on ",
842
+ compactTitle(props.frame)
843
+ ] }),
844
+ /* @__PURE__ */ jsx4("div", { className: "text-[11px] text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
845
+ /* @__PURE__ */ jsx4(
846
+ "textarea",
847
+ {
848
+ ref: textareaRef,
849
+ value: body,
850
+ onChange: (event) => setBody(event.target.value),
851
+ onKeyDown: (event) => {
852
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
853
+ event.preventDefault();
854
+ void submit();
855
+ }
856
+ if (event.key === "Escape") {
857
+ event.preventDefault();
858
+ setOpen(false);
859
+ setBody("");
860
+ }
861
+ },
862
+ rows: 3,
863
+ placeholder: "Add a short operator note back to the issue\u2026",
864
+ 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]"
865
+ }
866
+ ),
867
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-end gap-2", children: [
868
+ /* @__PURE__ */ jsx4(
869
+ ActionButton,
870
+ {
871
+ label: "Cancel",
872
+ tone: "secondary",
873
+ disabled: isPending,
874
+ onClick: () => {
875
+ setOpen(false);
876
+ setBody("");
877
+ }
878
+ }
879
+ ),
880
+ /* @__PURE__ */ jsx4(
881
+ ActionButton,
882
+ {
883
+ label: isPending ? "Posting\u2026" : "Post comment",
884
+ tone: "accent",
885
+ disabled: isPending || body.trim().length === 0,
886
+ onClick: () => void submit()
887
+ }
888
+ )
889
+ ] })
890
+ ] });
891
+ }
892
+
893
+ // src/ui/use-focus-page-actions.ts
894
+ import { usePluginAction } from "@paperclipai/plugin-sdk/ui";
895
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
896
+
897
+ // src/ui/interaction-copy.ts
898
+ function actionErrorMessage(error, fallback) {
899
+ return error instanceof Error ? error.message : fallback;
900
+ }
901
+ function acknowledgeSuccessMessage(title) {
902
+ return `Acknowledged ${title}.`;
903
+ }
904
+ function acknowledgeFailureMessage(error) {
905
+ return actionErrorMessage(error, "Failed to acknowledge frame.");
906
+ }
907
+ function issueFrameUnsupportedMessage() {
908
+ return "This frame is not backed by a Paperclip issue.";
909
+ }
910
+ function commentSuccessMessage(title) {
911
+ return `Posted a comment on ${title}.`;
912
+ }
913
+ function commentFailureMessage(error) {
914
+ return actionErrorMessage(error, "Failed to post comment.");
915
+ }
916
+ function approvalFrameUnsupportedMessage() {
917
+ return "This frame is not backed by a Paperclip approval.";
918
+ }
919
+ function approvalDecisionSuccessMessage(title, decision) {
920
+ return `${decision === "approve" ? "Approved" : "Rejected"} ${title}.`;
921
+ }
922
+ function approvalDecisionFailureMessage(error) {
923
+ return actionErrorMessage(error, "Failed to submit approval decision.");
924
+ }
925
+ function approvalRevisionSuccessMessage(title) {
926
+ return `Requested revision for ${title}.`;
927
+ }
928
+ function approvalRevisionFailureMessage(error) {
929
+ return actionErrorMessage(error, "Failed to request approval revision.");
930
+ }
931
+
932
+ // src/ui/use-focus-page-actions.ts
933
+ var FOCUS_HOLD_CONTEXT_MS = 2e4;
934
+ var FOCUS_HOLD_COMPOSE_MS = 45e3;
935
+ function useFocusPageActions(input) {
936
+ const acknowledge = usePluginAction("acknowledge-frame");
937
+ const commentOnIssue = usePluginAction("comment-on-issue");
938
+ const engageFocus = usePluginAction("engage-focus");
939
+ const markAttentionViewed = usePluginAction("mark-attention-viewed");
940
+ const recordApprovalResponse = usePluginAction("record-approval-response");
941
+ const setFocusPresence = usePluginAction("set-focus-presence");
942
+ const [pendingId, setPendingId] = useState4(null);
943
+ const [statusOverride, setStatusOverride] = useState4(null);
944
+ const focusReleaseTimerRef = useRef4(null);
945
+ const lastViewedInteractionRef = useRef4(null);
946
+ const markAttentionViewedRef = useRef4(markAttentionViewed);
947
+ const setFocusPresenceRef = useRef4(setFocusPresence);
948
+ markAttentionViewedRef.current = markAttentionViewed;
949
+ setFocusPresenceRef.current = setFocusPresence;
950
+ useEffect4(() => {
951
+ if (!statusOverride) return;
952
+ const timer = window.setTimeout(() => {
953
+ setStatusOverride(null);
954
+ }, 4e3);
955
+ return () => {
956
+ window.clearTimeout(timer);
957
+ };
958
+ }, [statusOverride]);
959
+ useEffect4(() => {
960
+ return () => {
961
+ if (focusReleaseTimerRef.current) {
962
+ window.clearTimeout(focusReleaseTimerRef.current);
963
+ }
964
+ };
965
+ }, []);
966
+ useEffect4(() => {
967
+ const companyId = input.companyId;
968
+ if (!companyId) return;
969
+ void setFocusPresenceRef.current({ companyId, presence: "present" }).catch(() => {
970
+ });
971
+ return () => {
972
+ void setFocusPresenceRef.current({ companyId, presence: "absent" }).catch(() => {
973
+ });
974
+ };
975
+ }, [input.companyId]);
976
+ useEffect4(() => {
977
+ const companyId = input.companyId;
978
+ const frame = input.displaySnapshot?.now;
979
+ if (!companyId || !frame) {
980
+ lastViewedInteractionRef.current = null;
981
+ return;
982
+ }
983
+ const viewKey = `${companyId}:${frame.interactionId}`;
984
+ if (lastViewedInteractionRef.current === viewKey) return;
985
+ lastViewedInteractionRef.current = viewKey;
986
+ void markAttentionViewedRef.current({
987
+ companyId,
988
+ taskId: frame.taskId,
989
+ interactionId: frame.interactionId,
990
+ surface: "focus"
991
+ }).catch(() => {
992
+ if (lastViewedInteractionRef.current === viewKey) {
993
+ lastViewedInteractionRef.current = null;
994
+ }
995
+ });
996
+ }, [input.companyId, input.displaySnapshot?.now?.interactionId]);
997
+ async function holdNowFrame(frame, reason) {
998
+ const companyId = input.companyId;
999
+ if (!companyId) return;
1000
+ if (input.displaySnapshot?.now?.interactionId !== frame.interactionId) return;
1001
+ const durationMs = reason === "comment_compose" ? FOCUS_HOLD_COMPOSE_MS : FOCUS_HOLD_CONTEXT_MS;
1002
+ try {
1003
+ await engageFocus({
1004
+ companyId,
1005
+ taskId: frame.taskId,
1006
+ interactionId: frame.interactionId,
1007
+ reason,
1008
+ durationMs
1009
+ });
1010
+ input.refresh();
1011
+ if (focusReleaseTimerRef.current) {
1012
+ window.clearTimeout(focusReleaseTimerRef.current);
1013
+ }
1014
+ focusReleaseTimerRef.current = window.setTimeout(() => {
1015
+ input.refresh();
1016
+ focusReleaseTimerRef.current = null;
1017
+ }, durationMs + 150);
1018
+ } catch {
1019
+ }
1020
+ }
1021
+ async function acknowledgeFrame(frame) {
1022
+ const companyId = input.companyId;
1023
+ if (!companyId) return;
1024
+ setPendingId(frame.id);
1025
+ try {
1026
+ await acknowledge({ companyId, taskId: frame.taskId, interactionId: frame.interactionId });
1027
+ setStatusOverride(acknowledgeSuccessMessage(compactTitle(frame)));
1028
+ input.refresh();
1029
+ } catch (actionError) {
1030
+ setStatusOverride(acknowledgeFailureMessage(actionError));
1031
+ } finally {
1032
+ setPendingId(null);
1033
+ }
1034
+ }
1035
+ async function commentOnIssueFrame(frame, body) {
1036
+ const companyId = input.companyId;
1037
+ const issueId = entityIdFromFrame(frame);
1038
+ if (!companyId || !issueId || entityTypeFromFrame(frame) !== "issue") {
1039
+ setStatusOverride(issueFrameUnsupportedMessage());
1040
+ return;
1041
+ }
1042
+ setPendingId(frame.id);
1043
+ try {
1044
+ await commentOnIssue({ companyId, taskId: frame.taskId, issueId, body });
1045
+ setStatusOverride(commentSuccessMessage(compactTitle(frame)));
1046
+ input.refresh();
1047
+ } catch (actionError) {
1048
+ setStatusOverride(commentFailureMessage(actionError));
1049
+ } finally {
1050
+ setPendingId(null);
1051
+ }
1052
+ }
1053
+ async function submitApprovalDecision(frame, decision) {
1054
+ const companyId = input.companyId;
1055
+ const approvalId = approvalIdForFrame(frame);
1056
+ if (!companyId || !approvalId) {
1057
+ setStatusOverride(approvalFrameUnsupportedMessage());
1058
+ return;
1059
+ }
1060
+ setPendingId(frame.id);
1061
+ try {
1062
+ await recordApprovalResponse({
1063
+ companyId,
1064
+ taskId: frame.taskId,
1065
+ interactionId: frame.interactionId,
1066
+ decision
1067
+ });
1068
+ setStatusOverride(approvalDecisionSuccessMessage(compactTitle(frame), decision));
1069
+ input.refresh();
1070
+ } catch (actionError) {
1071
+ setStatusOverride(approvalDecisionFailureMessage(actionError));
1072
+ } finally {
1073
+ setPendingId(null);
1074
+ }
1075
+ }
1076
+ async function requestApprovalRevision(frame) {
1077
+ const companyId = input.companyId;
1078
+ const approvalId = approvalIdForFrame(frame);
1079
+ if (!companyId || !approvalId) {
1080
+ setStatusOverride(approvalFrameUnsupportedMessage());
1081
+ return;
1082
+ }
1083
+ setPendingId(frame.id);
1084
+ try {
1085
+ await recordApprovalResponse({
1086
+ companyId,
1087
+ taskId: frame.taskId,
1088
+ interactionId: frame.interactionId,
1089
+ decision: "request-revision"
1090
+ });
1091
+ setStatusOverride(approvalRevisionSuccessMessage(compactTitle(frame)));
1092
+ input.refresh();
1093
+ } catch (actionError) {
1094
+ setStatusOverride(approvalRevisionFailureMessage(actionError));
1095
+ } finally {
1096
+ setPendingId(null);
1012
1097
  }
1013
- );
1098
+ }
1099
+ return {
1100
+ pendingId,
1101
+ statusOverride,
1102
+ holdNowFrame,
1103
+ acknowledgeFrame,
1104
+ commentOnIssueFrame,
1105
+ submitApprovalDecision,
1106
+ requestApprovalRevision
1107
+ };
1014
1108
  }
1109
+
1110
+ // src/ui/index.tsx
1111
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1015
1112
  function FrameActions(props) {
1016
1113
  const actionMode = responseKind(props.frame, props.lane);
1017
1114
  const isPending = props.pendingId === props.frame.id;
1018
1115
  if (actionMode === "none") return null;
1019
- return /* @__PURE__ */ jsx("div", { className: cn("flex items-center gap-2", props.compact && "justify-end"), children: actionMode === "approval" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1020
- isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
1116
+ return /* @__PURE__ */ jsx5("div", { className: cn("flex items-center gap-2", props.compact && "justify-end"), children: actionMode === "approval" ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1117
+ isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx5(
1021
1118
  ActionButton,
1022
1119
  {
1023
1120
  label: "Request revision",
@@ -1026,7 +1123,7 @@ function FrameActions(props) {
1026
1123
  onClick: () => void props.onRequestRevision(props.frame)
1027
1124
  }
1028
1125
  ) : null,
1029
- /* @__PURE__ */ jsx(
1126
+ /* @__PURE__ */ jsx5(
1030
1127
  ActionButton,
1031
1128
  {
1032
1129
  label: isPending ? "Submitting\u2026" : "Approve",
@@ -1035,7 +1132,7 @@ function FrameActions(props) {
1035
1132
  onClick: () => void props.onApprove(props.frame)
1036
1133
  }
1037
1134
  ),
1038
- /* @__PURE__ */ jsx(
1135
+ /* @__PURE__ */ jsx5(
1039
1136
  ActionButton,
1040
1137
  {
1041
1138
  label: "Reject",
@@ -1044,7 +1141,7 @@ function FrameActions(props) {
1044
1141
  onClick: () => void props.onReject(props.frame)
1045
1142
  }
1046
1143
  )
1047
- ] }) : /* @__PURE__ */ jsx(
1144
+ ] }) : /* @__PURE__ */ jsx5(
1048
1145
  ActionButton,
1049
1146
  {
1050
1147
  label: isPending ? "Saving\u2026" : "Acknowledge",
@@ -1054,216 +1151,30 @@ function FrameActions(props) {
1054
1151
  }
1055
1152
  ) });
1056
1153
  }
1057
- function visibleContextItems(frame) {
1058
- return (frame.context?.items ?? []).filter((item2) => item2.id !== ATTENTION_CONTEXT_IDS.recommendedMove);
1059
- }
1060
- function ContextItems({ frame }) {
1061
- const items = visibleContextItems(frame);
1062
- if (items.length === 0) return null;
1063
- return /* @__PURE__ */ jsx("div", { className: "grid gap-2 md:grid-cols-2", children: items.map((item2) => /* @__PURE__ */ jsxs(
1064
- "div",
1065
- {
1066
- className: cn(
1067
- "border border-border bg-secondary/50 px-3 py-2",
1068
- item2.id === ATTENTION_CONTEXT_IDS.latestComment && "md:col-span-2"
1069
- ),
1070
- children: [
1071
- /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: item2.label }),
1072
- /* @__PURE__ */ jsx("div", { className: "mt-1 text-sm text-foreground", children: item2.value ?? "Available" })
1073
- ]
1074
- },
1075
- item2.id
1076
- )) });
1077
- }
1078
- function ExplainabilityBadges(props) {
1079
- if (props.values.length === 0) return null;
1080
- return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: props.values.map((value) => /* @__PURE__ */ jsx(
1081
- Badge,
1082
- {
1083
- className: props.accent ? "border-transparent" : "border-border bg-background/70 text-foreground/80",
1084
- style: props.accent ? { color: ACCENT_COLOR, backgroundColor: ACCENT_BG, borderColor: ACCENT_BORDER } : void 0,
1085
- children: value
1086
- },
1087
- value
1088
- )) });
1089
- }
1090
- function InlineExplainability(props) {
1091
- const explanation = explainFrame(props.frame, props.lane);
1092
- const chips = [
1093
- ...explanation.signalStrength ? [signalStrengthLabel(explanation.signalStrength)] : [],
1094
- ...explanation.signals.slice(0, 2),
1095
- ...explanation.relationLabels.slice(0, 1)
1096
- ];
1097
- const label = props.lane === "next" ? "Why next" : props.lane === "ambient" ? "Why ambient" : "Why now";
1098
- const whyNow = explanation.whyNow ?? judgmentLine(props.frame, props.lane);
1099
- const primaryLine = props.lane === "now" ? props.preferLaneReason ? explanation.laneReason : whyNow : whyNow;
1100
- const secondaryLine = props.lane === "now" ? !props.preferLaneReason && whyNow !== explanation.laneReason ? explanation.laneReason : null : explanation.laneReason;
1101
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 pl-4", style: { borderColor: ACCENT_COLOR }, children: [
1102
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
1103
- /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
1104
- secondaryLine ? /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: secondaryLine }) : null,
1105
- chips.length > 0 ? /* @__PURE__ */ jsx(ExplainabilityBadges, { values: chips, accent: true }) : null
1106
- ] });
1107
- }
1108
- function ExplainabilityPanel(props) {
1109
- const explanation = explainFrame(props.frame, props.lane);
1110
- const strength = !props.detailOnly && explanation.signalStrength ? signalStrengthLabel(explanation.signalStrength) : null;
1111
- const signalValues = props.detailOnly ? explanation.signals.slice(2) : explanation.signals;
1112
- const relationValues = props.detailOnly ? explanation.relationLabels.slice(1) : explanation.relationLabels;
1113
- const reasoningLabel = props.lane === "now" ? "Reasoning" : props.lane === "next" ? "Why it sits here" : "Why it stays quiet";
1114
- if (props.detailOnly && signalValues.length === 0 && relationValues.length === 0 && !explanation.continuity) {
1115
- return null;
1116
- }
1117
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1118
- !props.detailOnly ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1fr)_16rem]", children: [
1119
- /* @__PURE__ */ jsxs("div", { children: [
1120
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: reasoningLabel }),
1121
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm leading-relaxed text-muted-foreground", children: explanation.laneReason })
1122
- ] }),
1123
- strength ? /* @__PURE__ */ jsxs("div", { children: [
1124
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Confidence" }),
1125
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-foreground/90", children: strength })
1126
- ] }) : null
1127
- ] }) : null,
1128
- signalValues.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1129
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More signals" : "Signals" }),
1130
- /* @__PURE__ */ jsx(ExplainabilityBadges, { values: signalValues })
1131
- ] }) : null,
1132
- relationValues.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1133
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: props.detailOnly ? "More thread context" : "Thread context" }),
1134
- /* @__PURE__ */ jsx(ExplainabilityBadges, { values: relationValues, accent: true })
1135
- ] }) : null,
1136
- explanation.continuity ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1137
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Related activity" }),
1138
- /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: explanation.continuity })
1139
- ] }) : null
1140
- ] });
1141
- }
1142
- function ExplainabilityStrip(props) {
1143
- 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 }) });
1144
- }
1145
- function isIssueFrame(frame) {
1146
- return entityTypeFromFrame(frame) === "issue";
1147
- }
1148
- function IssueCommentComposer(props) {
1149
- const [open, setOpen] = useState(false);
1150
- const [body, setBody] = useState("");
1151
- const textareaRef = useRef(null);
1152
- const issueFrame = isIssueFrame(props.frame);
1153
- const isPending = props.pendingId === props.frame.id;
1154
- useEffect(() => {
1155
- setOpen(false);
1156
- setBody("");
1157
- }, [props.frame.id]);
1158
- useEffect(() => {
1159
- if (open && issueFrame) textareaRef.current?.focus();
1160
- }, [open, issueFrame]);
1161
- if (!issueFrame) return null;
1162
- async function submit() {
1163
- const nextBody = body.trim();
1164
- if (!nextBody) return;
1165
- await props.onComment(props.frame, nextBody);
1166
- setBody("");
1167
- setOpen(false);
1168
- }
1169
- if (!open) {
1170
- const label = props.triggerLabel ?? "Leave comment";
1171
- const triggerTone = props.triggerTone ?? "link";
1172
- if (triggerTone === "link") {
1173
- return /* @__PURE__ */ jsx(
1174
- "button",
1175
- {
1176
- type: "button",
1177
- className: "text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
1178
- onClick: () => setOpen(true),
1179
- children: label
1180
- }
1181
- );
1182
- }
1183
- return /* @__PURE__ */ jsx(
1184
- ActionButton,
1185
- {
1186
- label,
1187
- tone: triggerTone,
1188
- className: props.triggerFullWidth ? "w-full" : void 0,
1189
- disabled: isPending,
1190
- onClick: () => setOpen(true)
1191
- }
1192
- );
1193
- }
1194
- return /* @__PURE__ */ jsxs("div", { className: "w-full space-y-2 rounded-md border border-border bg-secondary/40 p-3", children: [
1195
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
1196
- /* @__PURE__ */ jsx(
1197
- "textarea",
1198
- {
1199
- ref: textareaRef,
1200
- value: body,
1201
- onChange: (event) => setBody(event.target.value),
1202
- onKeyDown: (event) => {
1203
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
1204
- event.preventDefault();
1205
- void submit();
1206
- }
1207
- if (event.key === "Escape") {
1208
- event.preventDefault();
1209
- setOpen(false);
1210
- setBody("");
1211
- }
1212
- },
1213
- rows: 3,
1214
- placeholder: "Add a short operator note back to the issue\u2026",
1215
- 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]"
1216
- }
1217
- ),
1218
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1219
- /* @__PURE__ */ jsx(
1220
- ActionButton,
1221
- {
1222
- label: "Cancel",
1223
- tone: "secondary",
1224
- disabled: isPending,
1225
- onClick: () => {
1226
- setOpen(false);
1227
- setBody("");
1228
- }
1229
- }
1230
- ),
1231
- /* @__PURE__ */ jsx(
1232
- ActionButton,
1233
- {
1234
- label: isPending ? "Posting\u2026" : "Post comment",
1235
- tone: "accent",
1236
- disabled: isPending || body.trim().length === 0,
1237
- onClick: () => void submit()
1238
- }
1239
- )
1240
- ] })
1241
- ] });
1242
- }
1243
1154
  function NowDetails(props) {
1244
1155
  const { frame, snapshotUpdatedAt } = props;
1245
1156
  const href = itemHref(frame, props.companyPrefix);
1246
1157
  const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
1247
1158
  const activityLink = activityHref(frame, props.companyPrefix);
1248
1159
  const hasContext = visibleContextItems(frame).length > 0;
1249
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
1250
- /* @__PURE__ */ jsx(ExplainabilityPanel, { frame, lane: "now", detailOnly: true }),
1251
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1252
- /* @__PURE__ */ jsx("span", { children: requestDescriptor(frame) }),
1253
- /* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
1254
- /* @__PURE__ */ jsx("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1255
- requestedAmount(frame) ? /* @__PURE__ */ jsxs("span", { children: [
1160
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-3 border-t border-border pt-4", children: [
1161
+ /* @__PURE__ */ jsx5(ExplainabilityPanel, { frame, lane: "now", detailOnly: true }),
1162
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
1163
+ /* @__PURE__ */ jsx5("span", { children: requestDescriptor(frame) }),
1164
+ /* @__PURE__ */ jsx5("span", { children: impactLabel(frame) }),
1165
+ /* @__PURE__ */ jsx5("span", { children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) }),
1166
+ requestedAmount(frame) ? /* @__PURE__ */ jsxs4("span", { children: [
1256
1167
  "Amount requested: ",
1257
1168
  requestedAmount(frame)
1258
1169
  ] }) : null,
1259
- budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
1170
+ budgetReason(frame) ? /* @__PURE__ */ jsx5("span", { children: budgetReason(frame) }) : null
1260
1171
  ] }),
1261
- hasContext ? /* @__PURE__ */ jsxs(Fragment, { children: [
1262
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Context" }),
1263
- /* @__PURE__ */ jsx(ContextItems, { frame })
1172
+ hasContext ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
1173
+ /* @__PURE__ */ jsx5("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Context" }),
1174
+ /* @__PURE__ */ jsx5(ContextItems, { frame })
1264
1175
  ] }) : null,
1265
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
1266
- href ? /* @__PURE__ */ jsxs(
1176
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-3", children: [
1177
+ href ? /* @__PURE__ */ jsxs4(
1267
1178
  "a",
1268
1179
  {
1269
1180
  href,
@@ -1272,11 +1183,11 @@ function NowDetails(props) {
1272
1183
  className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
1273
1184
  children: [
1274
1185
  primaryLinkLabel(frame),
1275
- /* @__PURE__ */ jsx(ExternalLinkIcon, {})
1186
+ /* @__PURE__ */ jsx5(ExternalLinkIcon, {})
1276
1187
  ]
1277
1188
  }
1278
1189
  ) : null,
1279
- activityLink ? /* @__PURE__ */ jsxs(
1190
+ activityLink ? /* @__PURE__ */ jsxs4(
1280
1191
  "a",
1281
1192
  {
1282
1193
  href: activityLink,
@@ -1285,11 +1196,11 @@ function NowDetails(props) {
1285
1196
  className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
1286
1197
  children: [
1287
1198
  "Open activity",
1288
- /* @__PURE__ */ jsx(ExternalLinkIcon, {})
1199
+ /* @__PURE__ */ jsx5(ExternalLinkIcon, {})
1289
1200
  ]
1290
1201
  }
1291
1202
  ) : null,
1292
- budgetHref ? /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
1203
+ budgetHref ? /* @__PURE__ */ jsx5(Accent, { children: /* @__PURE__ */ jsxs4(
1293
1204
  "a",
1294
1205
  {
1295
1206
  href: budgetHref,
@@ -1299,7 +1210,7 @@ function NowDetails(props) {
1299
1210
  style: { color: "inherit" },
1300
1211
  children: [
1301
1212
  "Review in Costs",
1302
- /* @__PURE__ */ jsx(ExternalLinkIcon, {})
1213
+ /* @__PURE__ */ jsx5(ExternalLinkIcon, {})
1303
1214
  ]
1304
1215
  }
1305
1216
  ) }) : null
@@ -1307,23 +1218,23 @@ function NowDetails(props) {
1307
1218
  ] });
1308
1219
  }
1309
1220
  function QuietNow() {
1310
- return /* @__PURE__ */ jsxs("div", { className: "flex min-h-20 items-center gap-3", children: [
1311
- /* @__PURE__ */ jsx(QuietMark, { size: "md" }),
1312
- /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." })
1221
+ return /* @__PURE__ */ jsxs4("div", { className: "flex min-h-20 items-center gap-3", children: [
1222
+ /* @__PURE__ */ jsx5(QuietMark, { size: "md" }),
1223
+ /* @__PURE__ */ jsx5("div", { className: "text-sm text-muted-foreground", children: "Nothing active right now." })
1313
1224
  ] });
1314
1225
  }
1315
1226
  function NowActionRail(props) {
1316
1227
  const actionMode = responseKind(props.frame, props.lane);
1317
1228
  const isPending = props.pendingId === props.frame.id;
1318
1229
  const commentEnabled = actionMode === "acknowledge" && isIssueFrame(props.frame);
1319
- const [commentOpen, setCommentOpen] = useState(false);
1320
- const [commentBody, setCommentBody] = useState("");
1321
- const textareaRef = useRef(null);
1322
- useEffect(() => {
1230
+ const [commentOpen, setCommentOpen] = useState5(false);
1231
+ const [commentBody, setCommentBody] = useState5("");
1232
+ const textareaRef = useRef5(null);
1233
+ useEffect5(() => {
1323
1234
  setCommentOpen(false);
1324
1235
  setCommentBody("");
1325
1236
  }, [props.frame.id]);
1326
- useEffect(() => {
1237
+ useEffect5(() => {
1327
1238
  if (commentOpen) textareaRef.current?.focus();
1328
1239
  }, [commentOpen]);
1329
1240
  async function submitComment() {
@@ -1333,16 +1244,16 @@ function NowActionRail(props) {
1333
1244
  setCommentBody("");
1334
1245
  setCommentOpen(false);
1335
1246
  }
1336
- return /* @__PURE__ */ jsxs(
1247
+ return /* @__PURE__ */ jsxs4(
1337
1248
  "div",
1338
1249
  {
1339
1250
  className: "space-y-3 rounded-md border border-border/60 bg-secondary/20 px-3 py-3",
1340
1251
  style: props.wide ? { width: 320, flexShrink: 0, position: "sticky", top: 16, alignSelf: "flex-start" } : void 0,
1341
1252
  children: [
1342
- /* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
1343
- actionMode === "approval" ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1344
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
1345
- /* @__PURE__ */ jsx(
1253
+ /* @__PURE__ */ jsx5("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: "Actions" }),
1254
+ actionMode === "approval" ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
1255
+ /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-2 gap-2", children: [
1256
+ /* @__PURE__ */ jsx5(
1346
1257
  ActionButton,
1347
1258
  {
1348
1259
  label: isPending ? "Submitting\u2026" : "Approve",
@@ -1352,7 +1263,7 @@ function NowActionRail(props) {
1352
1263
  onClick: () => void props.onApprove(props.frame)
1353
1264
  }
1354
1265
  ),
1355
- /* @__PURE__ */ jsx(
1266
+ /* @__PURE__ */ jsx5(
1356
1267
  ActionButton,
1357
1268
  {
1358
1269
  label: "Reject",
@@ -1363,7 +1274,7 @@ function NowActionRail(props) {
1363
1274
  }
1364
1275
  )
1365
1276
  ] }),
1366
- isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
1277
+ isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx5(
1367
1278
  ActionButton,
1368
1279
  {
1369
1280
  label: "Request revision",
@@ -1373,17 +1284,20 @@ function NowActionRail(props) {
1373
1284
  onClick: () => void props.onRequestRevision(props.frame)
1374
1285
  }
1375
1286
  ) : null
1376
- ] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1377
- commentEnabled ? /* @__PURE__ */ jsx(
1287
+ ] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
1288
+ commentEnabled ? /* @__PURE__ */ jsx5(
1378
1289
  ActionButton,
1379
1290
  {
1380
1291
  label: "Comment",
1381
1292
  tone: "accent",
1382
1293
  disabled: isPending,
1383
- onClick: () => setCommentOpen(true)
1294
+ onClick: () => {
1295
+ props.onEngageFocus(props.frame, "comment_compose");
1296
+ setCommentOpen(true);
1297
+ }
1384
1298
  }
1385
1299
  ) : null,
1386
- /* @__PURE__ */ jsx(
1300
+ /* @__PURE__ */ jsx5(
1387
1301
  ActionButton,
1388
1302
  {
1389
1303
  label: isPending ? "Saving\u2026" : "Acknowledge",
@@ -1393,7 +1307,7 @@ function NowActionRail(props) {
1393
1307
  }
1394
1308
  )
1395
1309
  ] }) : null,
1396
- props.itemLink ? /* @__PURE__ */ jsxs(
1310
+ props.itemLink ? /* @__PURE__ */ jsxs4(
1397
1311
  "a",
1398
1312
  {
1399
1313
  href: props.itemLink,
@@ -1402,17 +1316,17 @@ function NowActionRail(props) {
1402
1316
  className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
1403
1317
  children: [
1404
1318
  primaryLinkLabel(props.frame),
1405
- /* @__PURE__ */ jsx(ExternalLinkIcon, {})
1319
+ /* @__PURE__ */ jsx5(ExternalLinkIcon, {})
1406
1320
  ]
1407
1321
  }
1408
1322
  ) : null,
1409
- commentOpen ? /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md border border-border bg-background/80 p-3", children: [
1410
- /* @__PURE__ */ jsxs("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1323
+ commentOpen ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2 rounded-md border border-border bg-background/80 p-3", children: [
1324
+ /* @__PURE__ */ jsxs4("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1411
1325
  "Commenting on ",
1412
1326
  compactTitle(props.frame)
1413
1327
  ] }),
1414
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
1415
- /* @__PURE__ */ jsx(
1328
+ /* @__PURE__ */ jsx5("div", { className: "text-xs text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
1329
+ /* @__PURE__ */ jsx5(
1416
1330
  "textarea",
1417
1331
  {
1418
1332
  ref: textareaRef,
@@ -1434,8 +1348,8 @@ function NowActionRail(props) {
1434
1348
  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]"
1435
1349
  }
1436
1350
  ),
1437
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
1438
- /* @__PURE__ */ jsx(
1351
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-end gap-2", children: [
1352
+ /* @__PURE__ */ jsx5(
1439
1353
  ActionButton,
1440
1354
  {
1441
1355
  label: "Cancel",
@@ -1447,7 +1361,7 @@ function NowActionRail(props) {
1447
1361
  }
1448
1362
  }
1449
1363
  ),
1450
- /* @__PURE__ */ jsx(
1364
+ /* @__PURE__ */ jsx5(
1451
1365
  ActionButton,
1452
1366
  {
1453
1367
  label: isPending ? "Posting\u2026" : "Post comment",
@@ -1464,7 +1378,7 @@ function NowActionRail(props) {
1464
1378
  }
1465
1379
  function NowPane(props) {
1466
1380
  const frame = props.snapshot.now;
1467
- const [detailsOpen, setDetailsOpen] = useState(false);
1381
+ const [detailsOpen, setDetailsOpen] = useState5(false);
1468
1382
  const wideLayout = useWideLayout(1120);
1469
1383
  const activeFrameId = frame?.id ?? null;
1470
1384
  const activeUnread = !!frame && (props.snapshot.review?.unread.now ?? 0) > 0;
@@ -1472,77 +1386,82 @@ function NowPane(props) {
1472
1386
  const recommendedMove = frame ? recommendedMoveValue(frame) : null;
1473
1387
  const helperLine = frame ? supportingLine(frame, "now") : null;
1474
1388
  const driverBadge = driverBadgeStyle();
1475
- useEffect(() => {
1389
+ useEffect5(() => {
1476
1390
  setDetailsOpen(false);
1477
1391
  }, [activeFrameId]);
1478
- return /* @__PURE__ */ jsxs(
1392
+ return /* @__PURE__ */ jsxs4(
1479
1393
  "section",
1480
1394
  {
1481
1395
  className: "border border-border bg-card shadow-sm",
1482
1396
  style: frame ? { borderLeftWidth: 3, borderLeftColor: ACCENT_COLOR } : void 0,
1483
1397
  children: [
1484
- /* @__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: [
1485
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
1486
- /* @__PURE__ */ jsx(Accent, { className: "text-sm leading-none", children: props.posture.glyph }),
1487
- /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1488
- /* @__PURE__ */ jsx(Accent, { className: "text-base leading-none font-semibold tracking-[0.04em]", children: props.brand.wordmark }),
1489
- /* @__PURE__ */ jsx("span", { className: "text-[11px] leading-none text-muted-foreground", children: props.posture.label })
1398
+ /* @__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: [
1399
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3", children: [
1400
+ /* @__PURE__ */ jsx5(Accent, { className: "text-sm leading-none", children: props.posture.glyph }),
1401
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
1402
+ /* @__PURE__ */ jsx5(Accent, { className: "text-base leading-none font-semibold tracking-[0.04em]", children: props.brand.wordmark }),
1403
+ /* @__PURE__ */ jsx5("span", { className: "text-[11px] leading-none text-muted-foreground", children: props.posture.label })
1490
1404
  ] })
1491
1405
  ] }),
1492
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
1493
- /* @__PURE__ */ jsxs("span", { className: props.counts.now > 0 ? "font-semibold" : "", children: [
1406
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
1407
+ /* @__PURE__ */ jsxs4("span", { className: props.counts.now > 0 ? "font-semibold" : "", children: [
1494
1408
  "now ",
1495
- props.counts.now > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.now }) : props.counts.now
1409
+ props.counts.now > 0 ? /* @__PURE__ */ jsx5(Accent, { children: props.counts.now }) : props.counts.now
1496
1410
  ] }),
1497
- /* @__PURE__ */ jsxs("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
1411
+ /* @__PURE__ */ jsxs4("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
1498
1412
  "next ",
1499
- props.counts.next > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.next }) : props.counts.next
1413
+ props.counts.next > 0 ? /* @__PURE__ */ jsx5(Accent, { children: props.counts.next }) : props.counts.next
1500
1414
  ] }),
1501
- /* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1415
+ /* @__PURE__ */ jsxs4("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
1502
1416
  "ambient ",
1503
- props.counts.ambient > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.ambient }) : props.counts.ambient
1417
+ props.counts.ambient > 0 ? /* @__PURE__ */ jsx5(Accent, { children: props.counts.ambient }) : props.counts.ambient
1504
1418
  ] })
1505
1419
  ] })
1506
1420
  ] }),
1507
- /* @__PURE__ */ jsx("div", { className: "px-5 py-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx(QuietNow, {}) : /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1508
- /* @__PURE__ */ jsxs(
1421
+ /* @__PURE__ */ jsx5("div", { className: "px-5 py-5 sm:px-6", children: !frame ? /* @__PURE__ */ jsx5(QuietNow, {}) : /* @__PURE__ */ jsxs4("div", { className: "space-y-5", children: [
1422
+ /* @__PURE__ */ jsxs4(
1509
1423
  "div",
1510
1424
  {
1511
1425
  className: wideLayout ? "flex items-start gap-6" : "space-y-4",
1512
1426
  children: [
1513
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", style: wideLayout ? { minWidth: 0, flex: 1 } : void 0, children: [
1514
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1515
- /* @__PURE__ */ jsx("span", { children: "Now" }),
1516
- /* @__PURE__ */ jsx("span", { className: "normal-case tracking-normal text-muted-foreground/70", children: sourceLabel(frame) })
1427
+ /* @__PURE__ */ jsxs4("div", { className: "space-y-4", style: wideLayout ? { minWidth: 0, flex: 1 } : void 0, children: [
1428
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: [
1429
+ /* @__PURE__ */ jsx5("span", { children: "Now" }),
1430
+ /* @__PURE__ */ jsx5("span", { className: "normal-case tracking-normal text-muted-foreground/70", children: sourceLabel(frame) })
1517
1431
  ] }),
1518
- 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,
1519
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80", children: [
1520
- /* @__PURE__ */ jsx(UnreadDot, { visible: activeUnread }),
1521
- /* @__PURE__ */ jsx("span", { children: renderTitle(frame) })
1432
+ 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,
1433
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80", children: [
1434
+ /* @__PURE__ */ jsx5(UnreadDot, { visible: activeUnread }),
1435
+ /* @__PURE__ */ jsx5("span", { children: renderTitle(frame) })
1522
1436
  ] }),
1523
- helperLine ? /* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-relaxed text-muted-foreground", children: helperLine }) : null,
1524
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1525
- /* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
1526
- driverLabel(frame) ? /* @__PURE__ */ jsx(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
1437
+ helperLine ? /* @__PURE__ */ jsx5("p", { className: "max-w-3xl text-sm leading-relaxed text-muted-foreground", children: helperLine }) : null,
1438
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2", children: [
1439
+ /* @__PURE__ */ jsx5(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
1440
+ driverLabel(frame) ? /* @__PURE__ */ jsx5(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
1527
1441
  ] }),
1528
- /* @__PURE__ */ jsx(InlineExplainability, { frame, lane: "now", preferLaneReason: !!helperLine }),
1529
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
1442
+ /* @__PURE__ */ jsx5(InlineExplainability, { frame, lane: "now", preferLaneReason: !!helperLine }),
1443
+ /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx5(Accent, { children: /* @__PURE__ */ jsxs4(
1530
1444
  "button",
1531
1445
  {
1532
1446
  type: "button",
1533
- onClick: () => setDetailsOpen(!detailsOpen),
1447
+ onClick: () => {
1448
+ if (!detailsOpen) {
1449
+ props.onEngageFocus(frame, "show_context");
1450
+ }
1451
+ setDetailsOpen(!detailsOpen);
1452
+ },
1534
1453
  "aria-expanded": detailsOpen,
1535
1454
  className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
1536
1455
  style: { color: "inherit" },
1537
1456
  children: [
1538
- /* @__PURE__ */ jsx(
1457
+ /* @__PURE__ */ jsx5(
1539
1458
  "svg",
1540
1459
  {
1541
1460
  viewBox: "0 0 16 16",
1542
1461
  className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
1543
1462
  fill: "currentColor",
1544
1463
  "aria-hidden": "true",
1545
- 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" })
1464
+ 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" })
1546
1465
  }
1547
1466
  ),
1548
1467
  detailsOpen ? "Hide context" : "Show context"
@@ -1550,7 +1469,7 @@ function NowPane(props) {
1550
1469
  }
1551
1470
  ) }) })
1552
1471
  ] }),
1553
- /* @__PURE__ */ jsx(
1472
+ /* @__PURE__ */ jsx5(
1554
1473
  NowActionRail,
1555
1474
  {
1556
1475
  frame,
@@ -1558,6 +1477,7 @@ function NowPane(props) {
1558
1477
  wide: wideLayout,
1559
1478
  pendingId: props.pendingId,
1560
1479
  itemLink,
1480
+ onEngageFocus: props.onEngageFocus,
1561
1481
  onApprove: props.onApprove,
1562
1482
  onReject: props.onReject,
1563
1483
  onRequestRevision: props.onRequestRevision,
@@ -1568,7 +1488,7 @@ function NowPane(props) {
1568
1488
  ]
1569
1489
  }
1570
1490
  ),
1571
- detailsOpen ? /* @__PURE__ */ jsx(
1491
+ detailsOpen ? /* @__PURE__ */ jsx5(
1572
1492
  NowDetails,
1573
1493
  {
1574
1494
  frame,
@@ -1583,7 +1503,7 @@ function NowPane(props) {
1583
1503
  }
1584
1504
  function NextRow(props) {
1585
1505
  const { frame } = props;
1586
- const [expanded, setExpanded] = useState(false);
1506
+ const [expanded, setExpanded] = useState5(false);
1587
1507
  const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
1588
1508
  const activityLink = activityHref(frame, props.companyPrefix);
1589
1509
  const itemLink = itemHref(frame, props.companyPrefix);
@@ -1593,8 +1513,8 @@ function NextRow(props) {
1593
1513
  const detailText = judgmentLine(frame, "next");
1594
1514
  const showDetailText = detailText.trim().length > 0 && detailText !== secondaryText && detailText !== GENERIC_QUEUED_JUDGMENT;
1595
1515
  const rankOpacity = Math.max(0.4, 1 - (props.rank - 1) * 0.14);
1596
- return /* @__PURE__ */ jsxs("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
1597
- /* @__PURE__ */ jsxs(
1516
+ return /* @__PURE__ */ jsxs4("div", { className: "border-b border-border/80 last:border-b-0", style: expanded ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : void 0, children: [
1517
+ /* @__PURE__ */ jsxs4(
1598
1518
  "button",
1599
1519
  {
1600
1520
  type: "button",
@@ -1602,40 +1522,40 @@ function NextRow(props) {
1602
1522
  "aria-expanded": expanded,
1603
1523
  className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",
1604
1524
  children: [
1605
- /* @__PURE__ */ jsx(UnreadDot, { visible: unread }),
1606
- /* @__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") }) }),
1607
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1608
- /* @__PURE__ */ jsx("div", { className: "text-sm font-medium leading-5 text-foreground", children: renderTitle(frame) }),
1609
- secondaryText ? /* @__PURE__ */ jsx("div", { className: "mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground", children: secondaryText }) : null
1525
+ /* @__PURE__ */ jsx5(UnreadDot, { visible: unread }),
1526
+ /* @__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") }) }),
1527
+ /* @__PURE__ */ jsxs4("div", { className: "min-w-0 flex-1", children: [
1528
+ /* @__PURE__ */ jsx5("div", { className: "text-sm font-medium leading-5 text-foreground", children: renderTitle(frame) }),
1529
+ secondaryText ? /* @__PURE__ */ jsx5("div", { className: "mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground", children: secondaryText }) : null
1610
1530
  ] }),
1611
- /* @__PURE__ */ jsx(QueueMovementBadge, { movement: props.movement }),
1612
- driver ? /* @__PURE__ */ jsx(Badge, { className: cn("shrink-0", driverBadge.className), style: driverBadge.style, children: driver }) : null,
1613
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.7 }, children: formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt)) }),
1614
- /* @__PURE__ */ jsx(
1531
+ /* @__PURE__ */ jsx5(QueueMovementBadge, { movement: props.movement }),
1532
+ driver ? /* @__PURE__ */ jsx5(Badge, { className: cn("shrink-0", driverBadge.className), style: driverBadge.style, children: driver }) : null,
1533
+ /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground", style: { opacity: 0.7 }, children: formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt)) }),
1534
+ /* @__PURE__ */ jsx5(
1615
1535
  "svg",
1616
1536
  {
1617
1537
  viewBox: "0 0 16 16",
1618
1538
  className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
1619
1539
  fill: "currentColor",
1620
1540
  "aria-hidden": "true",
1621
- 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" })
1541
+ 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" })
1622
1542
  }
1623
1543
  )
1624
1544
  ]
1625
1545
  }
1626
1546
  ),
1627
- expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
1628
- showDetailText ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
1629
- /* @__PURE__ */ jsx(ExplainabilityStrip, { frame, lane: "next" }),
1630
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
1631
- /* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
1632
- driver ? /* @__PURE__ */ jsx(Accent, { children: driver }) : null,
1633
- props.movement ? /* @__PURE__ */ jsx(Accent, { children: props.movement === "up" ? "rising in queue" : "falling in queue" }) : null,
1634
- /* @__PURE__ */ jsxs("span", { children: [
1547
+ expanded ? /* @__PURE__ */ jsxs4("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
1548
+ showDetailText ? /* @__PURE__ */ jsx5("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
1549
+ /* @__PURE__ */ jsx5(ExplainabilityStrip, { frame, lane: "next" }),
1550
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
1551
+ /* @__PURE__ */ jsx5("span", { children: impactLabel(frame) }),
1552
+ driver ? /* @__PURE__ */ jsx5(Accent, { children: driver }) : null,
1553
+ props.movement ? /* @__PURE__ */ jsx5(Accent, { children: props.movement === "up" ? "rising in queue" : "falling in queue" }) : null,
1554
+ /* @__PURE__ */ jsxs4("span", { children: [
1635
1555
  "updated ",
1636
1556
  formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt))
1637
1557
  ] }),
1638
- itemLink ? /* @__PURE__ */ jsx(
1558
+ itemLink ? /* @__PURE__ */ jsx5(
1639
1559
  "a",
1640
1560
  {
1641
1561
  href: itemLink,
@@ -1643,7 +1563,7 @@ function NextRow(props) {
1643
1563
  children: primaryLinkLabel(frame)
1644
1564
  }
1645
1565
  ) : null,
1646
- activityLink ? /* @__PURE__ */ jsx(
1566
+ activityLink ? /* @__PURE__ */ jsx5(
1647
1567
  "a",
1648
1568
  {
1649
1569
  href: activityLink,
@@ -1652,9 +1572,9 @@ function NextRow(props) {
1652
1572
  }
1653
1573
  ) : null
1654
1574
  ] }),
1655
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
1656
- /* @__PURE__ */ jsx(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
1657
- /* @__PURE__ */ jsx(
1575
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
1576
+ /* @__PURE__ */ jsx5(IssueCommentComposer, { frame, pendingId: props.pendingId, onComment: props.onComment }),
1577
+ /* @__PURE__ */ jsx5(
1658
1578
  FrameActions,
1659
1579
  {
1660
1580
  frame,
@@ -1671,9 +1591,9 @@ function NextRow(props) {
1671
1591
  ] });
1672
1592
  }
1673
1593
  function NextLane(props) {
1674
- return /* @__PURE__ */ jsxs("section", { className: "border border-border/80 bg-card", children: [
1675
- /* @__PURE__ */ jsx("div", { className: "border-b border-border/80 px-4 py-2.5 sm:px-6", children: /* @__PURE__ */ jsx("h2", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground", children: "Next" }) }),
1676
- props.rows.length === 0 ? /* @__PURE__ */ jsx("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__ */ jsx(
1594
+ return /* @__PURE__ */ jsxs4("section", { className: "border border-border/80 bg-card", children: [
1595
+ /* @__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" }) }),
1596
+ 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(
1677
1597
  NextRow,
1678
1598
  {
1679
1599
  rank: i + 1,
@@ -1697,21 +1617,21 @@ function AmbientRow(props) {
1697
1617
  const { frame, snapshotUpdatedAt } = props;
1698
1618
  const href = itemHref(frame, props.companyPrefix);
1699
1619
  const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
1700
- const content = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-2.5 sm:px-6", children: [
1701
- /* @__PURE__ */ jsx(UnreadDot, { visible: unread, tone: "muted", reserveSpace: true }),
1702
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[13px] text-muted-foreground", children: renderTitle(frame) }),
1703
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/80", children: sourceLabel(frame) }),
1704
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs text-muted-foreground/70", children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) })
1620
+ const content = /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-3 px-4 py-2.5 sm:px-6", children: [
1621
+ /* @__PURE__ */ jsx5(UnreadDot, { visible: unread, tone: "muted", reserveSpace: true }),
1622
+ /* @__PURE__ */ jsx5("span", { className: "min-w-0 flex-1 truncate text-[13px] text-muted-foreground", children: renderTitle(frame) }),
1623
+ /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground/80", children: sourceLabel(frame) }),
1624
+ /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs text-muted-foreground/70", children: formatRelativeTime(frameUpdatedAt(frame, snapshotUpdatedAt)) })
1705
1625
  ] });
1706
- 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 });
1626
+ 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 });
1707
1627
  }
1708
1628
  function AmbientLane(props) {
1709
- return /* @__PURE__ */ jsxs("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "mt-2 bg-card", "aria-label": "Ambient attention", children: [
1710
- /* @__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" }) }),
1711
- 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: [
1712
- /* @__PURE__ */ jsx(QuietMark, {}),
1713
- /* @__PURE__ */ jsx("span", { children: "Nothing in peripheral view." })
1714
- ] }) : props.rows.map((row) => /* @__PURE__ */ jsx(
1629
+ return /* @__PURE__ */ jsxs4("section", { style: { borderWidth: 1, borderColor: "var(--border)" }, className: "mt-2 bg-card", "aria-label": "Ambient attention", children: [
1630
+ /* @__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" }) }),
1631
+ 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: [
1632
+ /* @__PURE__ */ jsx5(QuietMark, {}),
1633
+ /* @__PURE__ */ jsx5("span", { children: "Nothing in peripheral view." })
1634
+ ] }) : props.rows.map((row) => /* @__PURE__ */ jsx5(
1715
1635
  AmbientRow,
1716
1636
  {
1717
1637
  frame: row.frame,
@@ -1731,23 +1651,23 @@ function DashboardWidget(props) {
1731
1651
  const data = model.snapshot;
1732
1652
  const loading = model.loading;
1733
1653
  const error = model.error;
1734
- if (!companyId) return /* @__PURE__ */ jsx(MessageCard, { title: `Open ${brand.wordmark}`, body: `Open a company to see ${brand.wordmark}.` });
1735
- if (loading) return /* @__PURE__ */ jsx(WidgetLoadingState, { label: brand.loadingLabel });
1736
- if (error) return /* @__PURE__ */ jsx(MessageCard, { title: "Plugin error", body: error.message });
1737
- if (!data) return /* @__PURE__ */ jsx(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState });
1654
+ if (!companyId) return /* @__PURE__ */ jsx5(MessageCard, { title: `Open ${brand.wordmark}`, body: `Open a company to see ${brand.wordmark}.` });
1655
+ if (loading) return /* @__PURE__ */ jsx5(WidgetLoadingState, { label: brand.loadingLabel });
1656
+ if (error) return /* @__PURE__ */ jsx5(MessageCard, { title: "Plugin error", body: error.message });
1657
+ if (!data) return /* @__PURE__ */ jsx5(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState });
1738
1658
  const posture = postureForSnapshot(data);
1739
1659
  const leadingText = data.now ? recommendedMoveValue(data.now) ?? data.now.title : null;
1740
1660
  const actionable = actionableLabel(data);
1741
1661
  const borderStyle = posture.label === "busy" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : posture.label === "elevated" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_BORDER } : void 0;
1742
- 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: [
1743
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", children: [
1744
- /* @__PURE__ */ jsx(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
1745
- /* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
1746
- /* @__PURE__ */ jsx(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
1747
- /* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: posture.label }),
1748
- actionable ? /* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: actionable }) : null
1662
+ 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: [
1663
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-2 text-xs", children: [
1664
+ /* @__PURE__ */ jsx5(Accent, { className: "pt-0.5 text-sm", children: posture.glyph }),
1665
+ /* @__PURE__ */ jsx5("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
1666
+ /* @__PURE__ */ jsx5(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
1667
+ /* @__PURE__ */ jsx5(Accent, { className: "text-[11px]", children: posture.label }),
1668
+ actionable ? /* @__PURE__ */ jsx5(Accent, { className: "text-[11px]", children: actionable }) : null
1749
1669
  ] }) }),
1750
- /* @__PURE__ */ jsxs("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
1670
+ /* @__PURE__ */ jsxs4("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
1751
1671
  "now ",
1752
1672
  data.counts.now,
1753
1673
  " \xB7 next ",
@@ -1756,7 +1676,7 @@ function DashboardWidget(props) {
1756
1676
  data.counts.ambient
1757
1677
  ] })
1758
1678
  ] }),
1759
- leadingText ? /* @__PURE__ */ jsx(
1679
+ leadingText ? /* @__PURE__ */ jsx5(
1760
1680
  "div",
1761
1681
  {
1762
1682
  className: "mt-2 text-sm font-medium leading-5 text-foreground",
@@ -1768,7 +1688,7 @@ function DashboardWidget(props) {
1768
1688
  },
1769
1689
  children: leadingText
1770
1690
  }
1771
- ) : /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
1691
+ ) : /* @__PURE__ */ jsx5("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
1772
1692
  ] });
1773
1693
  }
1774
1694
  function AttentionSidebarLink({ context }) {
@@ -1780,7 +1700,7 @@ function AttentionSidebarLink({ context }) {
1780
1700
  const merged = model.snapshot;
1781
1701
  const actionable = merged ? actionableCount(merged) : 0;
1782
1702
  const unread = unreadCount(merged);
1783
- return /* @__PURE__ */ jsxs(
1703
+ return /* @__PURE__ */ jsxs4(
1784
1704
  "a",
1785
1705
  {
1786
1706
  href,
@@ -1790,8 +1710,8 @@ function AttentionSidebarLink({ context }) {
1790
1710
  isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
1791
1711
  ),
1792
1712
  children: [
1793
- /* @__PURE__ */ jsxs("span", { className: "relative shrink-0", "aria-hidden": "true", children: [
1794
- /* @__PURE__ */ jsxs(
1713
+ /* @__PURE__ */ jsxs4("span", { className: "relative shrink-0", "aria-hidden": "true", children: [
1714
+ /* @__PURE__ */ jsxs4(
1795
1715
  "svg",
1796
1716
  {
1797
1717
  viewBox: "0 0 24 24",
@@ -1802,17 +1722,17 @@ function AttentionSidebarLink({ context }) {
1802
1722
  strokeLinecap: "round",
1803
1723
  strokeLinejoin: "round",
1804
1724
  children: [
1805
- /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "9" }),
1806
- /* @__PURE__ */ jsx("path", { d: "m14.3 8 5.2 9" }),
1807
- /* @__PURE__ */ jsx("path", { d: "M9.7 8h10.4" }),
1808
- /* @__PURE__ */ jsx("path", { d: "m7.4 12 5.2-9" }),
1809
- /* @__PURE__ */ jsx("path", { d: "m9.7 16-5.2-9" }),
1810
- /* @__PURE__ */ jsx("path", { d: "M14.3 16H3.9" }),
1811
- /* @__PURE__ */ jsx("path", { d: "m16.6 12-5.2 9" })
1725
+ /* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "9" }),
1726
+ /* @__PURE__ */ jsx5("path", { d: "m14.3 8 5.2 9" }),
1727
+ /* @__PURE__ */ jsx5("path", { d: "M9.7 8h10.4" }),
1728
+ /* @__PURE__ */ jsx5("path", { d: "m7.4 12 5.2-9" }),
1729
+ /* @__PURE__ */ jsx5("path", { d: "m9.7 16-5.2-9" }),
1730
+ /* @__PURE__ */ jsx5("path", { d: "M14.3 16H3.9" }),
1731
+ /* @__PURE__ */ jsx5("path", { d: "m16.6 12-5.2 9" })
1812
1732
  ]
1813
1733
  }
1814
1734
  ),
1815
- unread > 0 ? /* @__PURE__ */ jsx(
1735
+ unread > 0 ? /* @__PURE__ */ jsx5(
1816
1736
  "span",
1817
1737
  {
1818
1738
  className: "absolute -right-1 -top-1 h-2 w-2 rounded-full",
@@ -1820,8 +1740,8 @@ function AttentionSidebarLink({ context }) {
1820
1740
  }
1821
1741
  ) : null
1822
1742
  ] }),
1823
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: brand.wordmark }),
1824
- 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: actionable }) : unread > 0 ? /* @__PURE__ */ jsx(
1743
+ /* @__PURE__ */ jsx5("span", { className: "flex-1 truncate", children: brand.wordmark }),
1744
+ 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(
1825
1745
  "span",
1826
1746
  {
1827
1747
  className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
@@ -1840,182 +1760,36 @@ function AttentionPage(props) {
1840
1760
  const snapshot = model.snapshot;
1841
1761
  const loading = model.loading;
1842
1762
  const error = model.error;
1843
- const acknowledge = usePluginAction("acknowledge-frame");
1844
- const commentOnIssue = usePluginAction("comment-on-issue");
1845
- const recordApprovalResponse = usePluginAction("record-approval-response");
1846
- const [pendingId, setPendingId] = useState(null);
1847
- const [statusOverride, setStatusOverride] = useState(null);
1848
- const [localSuppressions, setLocalSuppressions] = useState({});
1849
- useEffect(() => {
1850
- if (!statusOverride) return;
1851
- const timer = window.setTimeout(() => {
1852
- setStatusOverride(null);
1853
- }, 4e3);
1854
- return () => {
1855
- window.clearTimeout(timer);
1856
- };
1857
- }, [statusOverride]);
1858
- const persistedSuppressions = useMemo(
1859
- () => suppressionMapFromReview(model.review),
1860
- [model.review]
1861
- );
1862
- const effectiveSuppressions = useMemo(
1863
- () => ({ ...persistedSuppressions, ...localSuppressions }),
1864
- [persistedSuppressions, localSuppressions]
1865
- );
1866
- const displaySnapshot = useMemo(
1867
- () => applyLocalSuppressions(snapshot, effectiveSuppressions),
1868
- [snapshot, effectiveSuppressions]
1869
- );
1763
+ const displaySnapshot = snapshot;
1870
1764
  const backgroundErrorMessage = displaySnapshot && error ? `Sync issue: ${error.message}` : null;
1871
- const posture = useMemo(() => displaySnapshot ? postureForSnapshot(displaySnapshot) : null, [displaySnapshot]);
1872
- const nextRows = useMemo(
1765
+ const posture = useMemo2(() => displaySnapshot ? postureForSnapshot(displaySnapshot) : null, [displaySnapshot]);
1766
+ const nextRows = useMemo2(
1873
1767
  () => displaySnapshot ? displaySnapshot.next.map((frame) => ({ frame, lane: "next" })) : [],
1874
1768
  [displaySnapshot]
1875
1769
  );
1876
- const ambientRows = useMemo(
1770
+ const ambientRows = useMemo2(
1877
1771
  () => displaySnapshot ? displaySnapshot.ambient.map((frame) => ({ frame, lane: "ambient" })) : [],
1878
1772
  [displaySnapshot]
1879
1773
  );
1880
1774
  const nextMovement = useQueueMovement(nextRows.map((row) => row.frame));
1881
- useEffect(() => {
1882
- if (!snapshot) return;
1883
- setLocalSuppressions((current) => {
1884
- const nextEntries = Object.entries(current).filter(([taskId, suppressedAt]) => {
1885
- const frames = [
1886
- ...snapshot.now ? [snapshot.now] : [],
1887
- ...snapshot.next,
1888
- ...snapshot.ambient
1889
- ];
1890
- const matching = frames.filter((frame) => frame.taskId === taskId);
1891
- if (matching.length === 0 && persistedSuppressions[taskId]) return false;
1892
- if (matching.length === 0) return false;
1893
- return matching.some((frame) => frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(suppressedAt) <= 0);
1894
- });
1895
- if (nextEntries.length === Object.keys(current).length) return current;
1896
- return Object.fromEntries(nextEntries);
1897
- });
1898
- }, [snapshot, persistedSuppressions]);
1899
- function nudgeRefresh() {
1900
- model.refresh();
1901
- window.setTimeout(() => model.refresh(), 500);
1902
- }
1903
- async function acknowledgeFrame(frame) {
1904
- setPendingId(frame.id);
1905
- const suppressedAt = (/* @__PURE__ */ new Date()).toISOString();
1906
- setLocalSuppressions((current) => ({ ...current, [frame.taskId]: suppressedAt }));
1907
- try {
1908
- await acknowledge({ companyId, taskId: frame.taskId, interactionId: frame.interactionId });
1909
- setStatusOverride(acknowledgeSuccessMessage(compactTitle(frame)));
1910
- nudgeRefresh();
1911
- } catch (actionError) {
1912
- setLocalSuppressions((current) => {
1913
- const next = { ...current };
1914
- delete next[frame.taskId];
1915
- return next;
1916
- });
1917
- setStatusOverride(acknowledgeFailureMessage(actionError));
1918
- } finally {
1919
- setPendingId(null);
1920
- }
1921
- }
1922
- async function commentOnIssueFrame(frame, body) {
1923
- const issueId = entityIdFromFrame(frame);
1924
- if (!companyId || !issueId || entityTypeFromFrame(frame) !== "issue") {
1925
- setStatusOverride(issueFrameUnsupportedMessage());
1926
- return;
1927
- }
1928
- setPendingId(frame.id);
1929
- try {
1930
- await commentOnIssue({ companyId, taskId: frame.taskId, issueId, body });
1931
- setStatusOverride(commentSuccessMessage(compactTitle(frame)));
1932
- nudgeRefresh();
1933
- } catch (actionError) {
1934
- setStatusOverride(commentFailureMessage(actionError));
1935
- } finally {
1936
- setPendingId(null);
1937
- }
1938
- }
1939
- async function submitApprovalDecision(frame, decision) {
1940
- const approvalId = approvalIdForFrame(frame);
1941
- if (!approvalId) {
1942
- setStatusOverride(approvalFrameUnsupportedMessage());
1943
- return;
1944
- }
1945
- setPendingId(frame.id);
1946
- const suppressedAt = (/* @__PURE__ */ new Date()).toISOString();
1947
- setLocalSuppressions((current) => ({ ...current, [frame.taskId]: suppressedAt }));
1948
- try {
1949
- const actionPath = decision === "approve" ? "approve" : "reject";
1950
- await paperclipApiFetch(`/api/approvals/${approvalId}/${actionPath}`, {
1951
- method: "POST",
1952
- headers: {
1953
- "Content-Type": "application/json"
1954
- },
1955
- body: JSON.stringify({}),
1956
- expectJson: false
1957
- });
1958
- await recordApprovalResponse({
1959
- companyId,
1960
- taskId: frame.taskId,
1961
- interactionId: frame.interactionId,
1962
- decision
1963
- });
1964
- setStatusOverride(approvalDecisionSuccessMessage(compactTitle(frame), decision));
1965
- nudgeRefresh();
1966
- } catch (actionError) {
1967
- setLocalSuppressions((current) => {
1968
- const next = { ...current };
1969
- delete next[frame.taskId];
1970
- return next;
1971
- });
1972
- setStatusOverride(approvalDecisionFailureMessage(actionError));
1973
- } finally {
1974
- setPendingId(null);
1975
- }
1976
- }
1977
- async function requestApprovalRevision(frame) {
1978
- const approvalId = approvalIdForFrame(frame);
1979
- if (!approvalId) {
1980
- setStatusOverride(approvalFrameUnsupportedMessage());
1981
- return;
1982
- }
1983
- setPendingId(frame.id);
1984
- const suppressedAt = (/* @__PURE__ */ new Date()).toISOString();
1985
- setLocalSuppressions((current) => ({ ...current, [frame.taskId]: suppressedAt }));
1986
- try {
1987
- await paperclipApiFetch(`/api/approvals/${approvalId}/request-revision`, {
1988
- method: "POST",
1989
- headers: {
1990
- "Content-Type": "application/json"
1991
- },
1992
- body: JSON.stringify({}),
1993
- expectJson: false
1994
- });
1995
- await recordApprovalResponse({
1996
- companyId,
1997
- taskId: frame.taskId,
1998
- interactionId: frame.interactionId,
1999
- decision: "request-revision"
2000
- });
2001
- setStatusOverride(approvalRevisionSuccessMessage(compactTitle(frame)));
2002
- nudgeRefresh();
2003
- } catch (actionError) {
2004
- setLocalSuppressions((current) => {
2005
- const next = { ...current };
2006
- delete next[frame.taskId];
2007
- return next;
2008
- });
2009
- setStatusOverride(approvalRevisionFailureMessage(actionError));
2010
- } finally {
2011
- setPendingId(null);
2012
- }
2013
- }
2014
- if (!companyId) return /* @__PURE__ */ jsx(MessageCard, { title: `Open ${brand.wordmark}`, body: `Select a company to open ${brand.wordmark}.` });
2015
- if (!displaySnapshot && loading) return /* @__PURE__ */ jsx(PageLoadingState, { label: "Loading attention center" });
2016
- if (!displaySnapshot && error) return /* @__PURE__ */ jsx(MessageCard, { title: "Plugin error", body: error.message });
1775
+ const {
1776
+ pendingId,
1777
+ statusOverride,
1778
+ holdNowFrame,
1779
+ acknowledgeFrame,
1780
+ commentOnIssueFrame,
1781
+ submitApprovalDecision,
1782
+ requestApprovalRevision
1783
+ } = useFocusPageActions({
1784
+ companyId,
1785
+ displaySnapshot,
1786
+ refresh: model.refresh
1787
+ });
1788
+ if (!companyId) return /* @__PURE__ */ jsx5(MessageCard, { title: `Open ${brand.wordmark}`, body: `Select a company to open ${brand.wordmark}.` });
1789
+ if (!displaySnapshot && loading) return /* @__PURE__ */ jsx5(PageLoadingState, { label: "Loading attention center" });
1790
+ if (!displaySnapshot && error) return /* @__PURE__ */ jsx5(MessageCard, { title: "Plugin error", body: error.message });
2017
1791
  if (!displaySnapshot || !posture) {
2018
- return /* @__PURE__ */ jsx(
1792
+ return /* @__PURE__ */ jsx5(
2019
1793
  MessageCard,
2020
1794
  {
2021
1795
  title: `No ${brand.wordmark} State Yet`,
@@ -2023,8 +1797,8 @@ function AttentionPage(props) {
2023
1797
  }
2024
1798
  );
2025
1799
  }
2026
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2027
- /* @__PURE__ */ jsx(
1800
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-4", children: [
1801
+ /* @__PURE__ */ jsx5(
2028
1802
  NowPane,
2029
1803
  {
2030
1804
  snapshot: displaySnapshot,
@@ -2033,6 +1807,7 @@ function AttentionPage(props) {
2033
1807
  companyPrefix: props.context.companyPrefix,
2034
1808
  counts: displaySnapshot.counts,
2035
1809
  pendingId,
1810
+ onEngageFocus: holdNowFrame,
2036
1811
  onApprove: (frame) => submitApprovalDecision(frame, "approve"),
2037
1812
  onReject: (frame) => submitApprovalDecision(frame, "reject"),
2038
1813
  onRequestRevision: requestApprovalRevision,
@@ -2040,7 +1815,7 @@ function AttentionPage(props) {
2040
1815
  onComment: commentOnIssueFrame
2041
1816
  }
2042
1817
  ),
2043
- /* @__PURE__ */ jsx(
1818
+ /* @__PURE__ */ jsx5(
2044
1819
  NextLane,
2045
1820
  {
2046
1821
  snapshot: displaySnapshot,
@@ -2056,7 +1831,7 @@ function AttentionPage(props) {
2056
1831
  onComment: commentOnIssueFrame
2057
1832
  }
2058
1833
  ),
2059
- /* @__PURE__ */ jsx(
1834
+ /* @__PURE__ */ jsx5(
2060
1835
  AmbientLane,
2061
1836
  {
2062
1837
  snapshot: displaySnapshot,
@@ -2065,7 +1840,7 @@ function AttentionPage(props) {
2065
1840
  companyPrefix: props.context.companyPrefix
2066
1841
  }
2067
1842
  ),
2068
- /* @__PURE__ */ jsx(StatusToast, { message: statusOverride ?? backgroundErrorMessage })
1843
+ /* @__PURE__ */ jsx5(StatusToast, { message: statusOverride ?? backgroundErrorMessage })
2069
1844
  ] });
2070
1845
  }
2071
1846
  export {