@tomismeta/paperclip-aperture 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -14
- package/dist/manifest.js +1 -73
- package/dist/ui/index.js +1 -2076
- package/dist/worker.js +11 -17005
- package/package.json +11 -10
package/dist/ui/index.js
CHANGED
|
@@ -1,2076 +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";
|
|
8
|
-
|
|
9
|
-
// src/aperture/frame-model.ts
|
|
10
|
-
var AMBIENT_MAX_AGE_MS = 5 * 60 * 1e3;
|
|
11
|
-
function isBudgetOverride(frame) {
|
|
12
|
-
return frame.provenance?.factors?.includes("budget stop") ?? false;
|
|
13
|
-
}
|
|
14
|
-
function frameUpdatedAt(frame, snapshotUpdatedAt) {
|
|
15
|
-
return frame.timing.updatedAt || snapshotUpdatedAt;
|
|
16
|
-
}
|
|
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
|
-
|
|
239
|
-
// src/aperture/attention-language.ts
|
|
240
|
-
var GENERIC_QUEUED_JUDGMENT = "Important enough to keep visible, but not enough to displace now.";
|
|
241
|
-
var GENERIC_NEXT_JUDGMENT = GENERIC_QUEUED_JUDGMENT;
|
|
242
|
-
function approvalBlockingSummary(budgetOverride) {
|
|
243
|
-
return budgetOverride ? "Budget controls are blocking work until a board decision lands." : "A board decision is blocking work in Paperclip.";
|
|
244
|
-
}
|
|
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
|
-
function genericJudgmentLine(frame, lane) {
|
|
249
|
-
if (lane === "now") {
|
|
250
|
-
if (isBudgetOverride(frame)) return approvalBlockingSummary(true);
|
|
251
|
-
if (frame.mode === "approval") return "A human decision is blocking work right now.";
|
|
252
|
-
if (frame.tone === "critical") return "This surfaced because it can displace the operator now.";
|
|
253
|
-
return "This is the clearest current operator focus.";
|
|
254
|
-
}
|
|
255
|
-
if (isBudgetOverride(frame)) return "Budget review is staged behind the current focus.";
|
|
256
|
-
if (lane === "next") return GENERIC_NEXT_JUDGMENT;
|
|
257
|
-
return "Useful for awareness, but not interrupting.";
|
|
258
|
-
}
|
|
259
|
-
|
|
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";
|
|
412
|
-
var ACCENT_COLOR = "#007ACC";
|
|
413
|
-
var ACCENT_BG = `${ACCENT_COLOR}14`;
|
|
414
|
-
var ACCENT_BORDER = `${ACCENT_COLOR}33`;
|
|
415
|
-
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
|
-
};
|
|
424
|
-
}
|
|
425
|
-
function useAccentColor() {
|
|
426
|
-
const ref = useRef(null);
|
|
427
|
-
useEffect(() => {
|
|
428
|
-
ref.current?.style.setProperty("color", ACCENT_COLOR, "important");
|
|
429
|
-
});
|
|
430
|
-
return ref;
|
|
431
|
-
}
|
|
432
|
-
function Accent({ children, className }) {
|
|
433
|
-
const ref = useAccentColor();
|
|
434
|
-
return /* @__PURE__ */ jsx("span", { ref, className, children });
|
|
435
|
-
}
|
|
436
|
-
function QuietMark({ size = "sm" }) {
|
|
437
|
-
const dimension = size === "md" ? 10 : 8;
|
|
438
|
-
return /* @__PURE__ */ jsx(
|
|
439
|
-
"span",
|
|
440
|
-
{
|
|
441
|
-
"aria-hidden": "true",
|
|
442
|
-
className: "inline-block shrink-0 rounded-full border",
|
|
443
|
-
style: { width: dimension, height: dimension, borderColor: ACCENT_BORDER }
|
|
444
|
-
}
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
function useWideLayout(minWidth = 1024) {
|
|
448
|
-
const [matches, setMatches] = useState(false);
|
|
449
|
-
useEffect(() => {
|
|
450
|
-
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
451
|
-
const query = window.matchMedia(`(min-width: ${minWidth}px)`);
|
|
452
|
-
const update = () => setMatches(query.matches);
|
|
453
|
-
update();
|
|
454
|
-
query.addEventListener("change", update);
|
|
455
|
-
return () => query.removeEventListener("change", update);
|
|
456
|
-
}, [minWidth]);
|
|
457
|
-
return matches;
|
|
458
|
-
}
|
|
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);
|
|
489
|
-
}
|
|
490
|
-
function recommendedMoveValue(frame) {
|
|
491
|
-
return contextValue(frame, ATTENTION_CONTEXT_IDS.recommendedMove);
|
|
492
|
-
}
|
|
493
|
-
function actionOwnerValue(frame) {
|
|
494
|
-
return contextValue(frame, ATTENTION_CONTEXT_IDS.needsActionFrom);
|
|
495
|
-
}
|
|
496
|
-
function blockingTargetValue(frame) {
|
|
497
|
-
return contextValue(frame, ATTENTION_CONTEXT_IDS.blocksTarget);
|
|
498
|
-
}
|
|
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
|
-
] });
|
|
712
|
-
}
|
|
713
|
-
function PageLoadingState({ label }) {
|
|
714
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", "aria-label": label, children: [
|
|
715
|
-
/* @__PURE__ */ jsx("div", { className: "border border-border bg-card px-4 py-3 shadow-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
716
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-44" }),
|
|
717
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-8 w-28" })
|
|
718
|
-
] }) }),
|
|
719
|
-
/* @__PURE__ */ jsxs("div", { className: "border border-border bg-card shadow-sm", children: [
|
|
720
|
-
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-4 py-3 sm:px-6", children: /* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-36" }) }),
|
|
721
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-4 px-4 py-5 sm:px-6", children: [
|
|
722
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-16" }),
|
|
723
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-7 w-2/3" }),
|
|
724
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
725
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-6 w-24" }),
|
|
726
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-6 w-20" }),
|
|
727
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-6 w-16" })
|
|
728
|
-
] }),
|
|
729
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-10 w-56" })
|
|
730
|
-
] })
|
|
731
|
-
] }),
|
|
732
|
-
/* @__PURE__ */ jsxs("div", { className: "border border-border bg-card shadow-sm", children: [
|
|
733
|
-
/* @__PURE__ */ jsx("div", { className: "border-b border-border px-4 py-3 sm:px-6", children: /* @__PURE__ */ jsx(SkeletonLine, { className: "h-3 w-12" }) }),
|
|
734
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 py-4 sm:px-6", children: [
|
|
735
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-full" }),
|
|
736
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-5/6" }),
|
|
737
|
-
/* @__PURE__ */ jsx(SkeletonLine, { className: "h-4 w-2/3" })
|
|
738
|
-
] })
|
|
739
|
-
] })
|
|
740
|
-
] });
|
|
741
|
-
}
|
|
742
|
-
function StatusToast({ message }) {
|
|
743
|
-
return /* @__PURE__ */ jsx(
|
|
744
|
-
"div",
|
|
745
|
-
{
|
|
746
|
-
className: cn(
|
|
747
|
-
"pointer-events-none fixed bottom-6 left-1/2 z-50 -translate-x-1/2 transition-all duration-200",
|
|
748
|
-
message ? "translate-y-0 opacity-100" : "translate-y-2 opacity-0"
|
|
749
|
-
),
|
|
750
|
-
"aria-live": "polite",
|
|
751
|
-
"aria-atomic": "true",
|
|
752
|
-
children: message ? /* @__PURE__ */ jsx("div", { className: "min-w-72 max-w-md rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground shadow-lg", children: message }) : null
|
|
753
|
-
}
|
|
754
|
-
);
|
|
755
|
-
}
|
|
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)));
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
throw lastError ?? new Error("Request failed.");
|
|
796
|
-
}
|
|
797
|
-
function compactTitle(frame) {
|
|
798
|
-
return frame.title.trim().length > 0 ? frame.title : "Untitled frame";
|
|
799
|
-
}
|
|
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;
|
|
811
|
-
}
|
|
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
|
-
}
|
|
830
|
-
}
|
|
831
|
-
setLoading(true);
|
|
832
|
-
void loadApprovals();
|
|
833
|
-
return () => {
|
|
834
|
-
cancelled = true;
|
|
835
|
-
};
|
|
836
|
-
}, [companyId, refreshVersion]);
|
|
837
|
-
return {
|
|
838
|
-
data,
|
|
839
|
-
loading,
|
|
840
|
-
error,
|
|
841
|
-
refresh: () => {
|
|
842
|
-
setRefreshVersion((value) => value + 1);
|
|
843
|
-
}
|
|
844
|
-
};
|
|
845
|
-
}
|
|
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;
|
|
861
|
-
}
|
|
862
|
-
if (lastIndex < text.length) {
|
|
863
|
-
parts.push(text.slice(lastIndex));
|
|
864
|
-
}
|
|
865
|
-
return /* @__PURE__ */ jsx(Fragment, { children: parts });
|
|
866
|
-
}
|
|
867
|
-
function renderTitle(frame) {
|
|
868
|
-
return /* @__PURE__ */ jsx(HighlightedTitle, { text: compactTitle(frame) });
|
|
869
|
-
}
|
|
870
|
-
function useAttentionPolling(companyId, refreshers, intervalMs = 5e3) {
|
|
871
|
-
const refreshersRef = useRef(refreshers);
|
|
872
|
-
refreshersRef.current = refreshers;
|
|
873
|
-
useEffect(() => {
|
|
874
|
-
if (!companyId) return;
|
|
875
|
-
function refreshVisible() {
|
|
876
|
-
if (typeof document !== "undefined" && document.visibilityState === "hidden") return;
|
|
877
|
-
for (const refresh of refreshersRef.current) refresh();
|
|
878
|
-
}
|
|
879
|
-
const timer = window.setInterval(() => {
|
|
880
|
-
refreshVisible();
|
|
881
|
-
}, intervalMs);
|
|
882
|
-
function handleVisibilityChange() {
|
|
883
|
-
if (typeof document !== "undefined" && document.visibilityState === "visible") {
|
|
884
|
-
for (const refresh of refreshersRef.current) refresh();
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
888
|
-
return () => {
|
|
889
|
-
window.clearInterval(timer);
|
|
890
|
-
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
891
|
-
};
|
|
892
|
-
}, [companyId, intervalMs]);
|
|
893
|
-
}
|
|
894
|
-
function useQueueMovement(frames, ttlMs = 3500) {
|
|
895
|
-
const previousPositionsRef = useRef({});
|
|
896
|
-
const timersRef = useRef({});
|
|
897
|
-
const [movementById, setMovementById] = useState({});
|
|
898
|
-
useEffect(() => {
|
|
899
|
-
const nextPositions = Object.fromEntries(frames.map((frame, index) => [frame.interactionId, index]));
|
|
900
|
-
setMovementById((current) => {
|
|
901
|
-
const updates = {};
|
|
902
|
-
for (const frame of frames) {
|
|
903
|
-
const previousIndex = previousPositionsRef.current[frame.interactionId];
|
|
904
|
-
const nextIndex = nextPositions[frame.interactionId];
|
|
905
|
-
if (typeof previousIndex !== "number" || previousIndex === nextIndex) continue;
|
|
906
|
-
updates[frame.interactionId] = nextIndex < previousIndex ? "up" : "down";
|
|
907
|
-
}
|
|
908
|
-
if (Object.keys(updates).length === 0) return current;
|
|
909
|
-
const merged = { ...current, ...updates };
|
|
910
|
-
for (const [interactionId, movement] of Object.entries(updates)) {
|
|
911
|
-
window.clearTimeout(timersRef.current[interactionId]);
|
|
912
|
-
timersRef.current[interactionId] = window.setTimeout(() => {
|
|
913
|
-
setMovementById((active) => {
|
|
914
|
-
if (active[interactionId] !== movement) return active;
|
|
915
|
-
const next = { ...active };
|
|
916
|
-
delete next[interactionId];
|
|
917
|
-
return next;
|
|
918
|
-
});
|
|
919
|
-
delete timersRef.current[interactionId];
|
|
920
|
-
}, ttlMs);
|
|
921
|
-
}
|
|
922
|
-
return merged;
|
|
923
|
-
});
|
|
924
|
-
previousPositionsRef.current = nextPositions;
|
|
925
|
-
return () => {
|
|
926
|
-
for (const timer of Object.values(timersRef.current)) window.clearTimeout(timer);
|
|
927
|
-
};
|
|
928
|
-
}, [frames, ttlMs]);
|
|
929
|
-
return movementById;
|
|
930
|
-
}
|
|
931
|
-
function useAttentionModel(companyId) {
|
|
932
|
-
const displayQuery = usePluginData("attention-display", companyId ? { companyId } : void 0);
|
|
933
|
-
const approvalsQuery = usePendingApprovals(companyId);
|
|
934
|
-
const updates = usePluginStream(
|
|
935
|
-
ATTENTION_UPDATES_STREAM,
|
|
936
|
-
companyId ? { companyId } : void 0
|
|
937
|
-
);
|
|
938
|
-
useAttentionPolling(companyId, [displayQuery.refresh, approvalsQuery.refresh]);
|
|
939
|
-
useEffect(() => {
|
|
940
|
-
if (!companyId || !updates.lastEvent) return;
|
|
941
|
-
displayQuery.refresh();
|
|
942
|
-
approvalsQuery.refresh();
|
|
943
|
-
}, [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,
|
|
950
|
-
review: displayQuery.data?.reviewState ?? null,
|
|
951
|
-
loading: displayQuery.loading || approvalsQuery.loading,
|
|
952
|
-
error: displayQuery.error ?? approvalsQuery.error,
|
|
953
|
-
refresh() {
|
|
954
|
-
displayQuery.refresh();
|
|
955
|
-
approvalsQuery.refresh();
|
|
956
|
-
}
|
|
957
|
-
};
|
|
958
|
-
}
|
|
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
|
-
);
|
|
971
|
-
}
|
|
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
|
|
994
|
-
}
|
|
995
|
-
);
|
|
996
|
-
}
|
|
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
|
-
]
|
|
1012
|
-
}
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
function FrameActions(props) {
|
|
1016
|
-
const actionMode = responseKind(props.frame, props.lane);
|
|
1017
|
-
const isPending = props.pendingId === props.frame.id;
|
|
1018
|
-
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(
|
|
1021
|
-
ActionButton,
|
|
1022
|
-
{
|
|
1023
|
-
label: "Request revision",
|
|
1024
|
-
tone: "secondary",
|
|
1025
|
-
disabled: isPending,
|
|
1026
|
-
onClick: () => void props.onRequestRevision(props.frame)
|
|
1027
|
-
}
|
|
1028
|
-
) : null,
|
|
1029
|
-
/* @__PURE__ */ jsx(
|
|
1030
|
-
ActionButton,
|
|
1031
|
-
{
|
|
1032
|
-
label: isPending ? "Submitting\u2026" : "Approve",
|
|
1033
|
-
tone: "primary",
|
|
1034
|
-
disabled: isPending,
|
|
1035
|
-
onClick: () => void props.onApprove(props.frame)
|
|
1036
|
-
}
|
|
1037
|
-
),
|
|
1038
|
-
/* @__PURE__ */ jsx(
|
|
1039
|
-
ActionButton,
|
|
1040
|
-
{
|
|
1041
|
-
label: "Reject",
|
|
1042
|
-
tone: "danger",
|
|
1043
|
-
disabled: isPending,
|
|
1044
|
-
onClick: () => void props.onReject(props.frame)
|
|
1045
|
-
}
|
|
1046
|
-
)
|
|
1047
|
-
] }) : /* @__PURE__ */ jsx(
|
|
1048
|
-
ActionButton,
|
|
1049
|
-
{
|
|
1050
|
-
label: isPending ? "Saving\u2026" : "Acknowledge",
|
|
1051
|
-
tone: "accent",
|
|
1052
|
-
disabled: isPending,
|
|
1053
|
-
onClick: () => void props.onAcknowledge(props.frame)
|
|
1054
|
-
}
|
|
1055
|
-
) });
|
|
1056
|
-
}
|
|
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
|
-
function NowDetails(props) {
|
|
1244
|
-
const { frame, snapshotUpdatedAt } = props;
|
|
1245
|
-
const href = itemHref(frame, props.companyPrefix);
|
|
1246
|
-
const budgetHref = isBudgetOverride(frame) ? costsHref(props.companyPrefix) : null;
|
|
1247
|
-
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1248
|
-
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: [
|
|
1256
|
-
"Amount requested: ",
|
|
1257
|
-
requestedAmount(frame)
|
|
1258
|
-
] }) : null,
|
|
1259
|
-
budgetReason(frame) ? /* @__PURE__ */ jsx("span", { children: budgetReason(frame) }) : null
|
|
1260
|
-
] }),
|
|
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 })
|
|
1264
|
-
] }) : null,
|
|
1265
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
|
|
1266
|
-
href ? /* @__PURE__ */ jsxs(
|
|
1267
|
-
"a",
|
|
1268
|
-
{
|
|
1269
|
-
href,
|
|
1270
|
-
target: "_blank",
|
|
1271
|
-
rel: "noopener noreferrer",
|
|
1272
|
-
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1273
|
-
children: [
|
|
1274
|
-
primaryLinkLabel(frame),
|
|
1275
|
-
/* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
1276
|
-
]
|
|
1277
|
-
}
|
|
1278
|
-
) : null,
|
|
1279
|
-
activityLink ? /* @__PURE__ */ jsxs(
|
|
1280
|
-
"a",
|
|
1281
|
-
{
|
|
1282
|
-
href: activityLink,
|
|
1283
|
-
target: "_blank",
|
|
1284
|
-
rel: "noopener noreferrer",
|
|
1285
|
-
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1286
|
-
children: [
|
|
1287
|
-
"Open activity",
|
|
1288
|
-
/* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
1289
|
-
]
|
|
1290
|
-
}
|
|
1291
|
-
) : null,
|
|
1292
|
-
budgetHref ? /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
|
|
1293
|
-
"a",
|
|
1294
|
-
{
|
|
1295
|
-
href: budgetHref,
|
|
1296
|
-
target: "_blank",
|
|
1297
|
-
rel: "noopener noreferrer",
|
|
1298
|
-
className: "inline-flex items-center gap-1 text-xs font-medium underline underline-offset-2 hover:opacity-80",
|
|
1299
|
-
style: { color: "inherit" },
|
|
1300
|
-
children: [
|
|
1301
|
-
"Review in Costs",
|
|
1302
|
-
/* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
1303
|
-
]
|
|
1304
|
-
}
|
|
1305
|
-
) }) : null
|
|
1306
|
-
] })
|
|
1307
|
-
] });
|
|
1308
|
-
}
|
|
1309
|
-
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." })
|
|
1313
|
-
] });
|
|
1314
|
-
}
|
|
1315
|
-
function NowActionRail(props) {
|
|
1316
|
-
const actionMode = responseKind(props.frame, props.lane);
|
|
1317
|
-
const isPending = props.pendingId === props.frame.id;
|
|
1318
|
-
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(() => {
|
|
1323
|
-
setCommentOpen(false);
|
|
1324
|
-
setCommentBody("");
|
|
1325
|
-
}, [props.frame.id]);
|
|
1326
|
-
useEffect(() => {
|
|
1327
|
-
if (commentOpen) textareaRef.current?.focus();
|
|
1328
|
-
}, [commentOpen]);
|
|
1329
|
-
async function submitComment() {
|
|
1330
|
-
const nextBody = commentBody.trim();
|
|
1331
|
-
if (!nextBody) return;
|
|
1332
|
-
await props.onComment(props.frame, nextBody);
|
|
1333
|
-
setCommentBody("");
|
|
1334
|
-
setCommentOpen(false);
|
|
1335
|
-
}
|
|
1336
|
-
return /* @__PURE__ */ jsxs(
|
|
1337
|
-
"div",
|
|
1338
|
-
{
|
|
1339
|
-
className: "space-y-3 rounded-md border border-border/60 bg-secondary/20 px-3 py-3",
|
|
1340
|
-
style: props.wide ? { width: 320, flexShrink: 0, position: "sticky", top: 16, alignSelf: "flex-start" } : void 0,
|
|
1341
|
-
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(
|
|
1346
|
-
ActionButton,
|
|
1347
|
-
{
|
|
1348
|
-
label: isPending ? "Submitting\u2026" : "Approve",
|
|
1349
|
-
tone: "primary",
|
|
1350
|
-
disabled: isPending,
|
|
1351
|
-
className: "w-full",
|
|
1352
|
-
onClick: () => void props.onApprove(props.frame)
|
|
1353
|
-
}
|
|
1354
|
-
),
|
|
1355
|
-
/* @__PURE__ */ jsx(
|
|
1356
|
-
ActionButton,
|
|
1357
|
-
{
|
|
1358
|
-
label: "Reject",
|
|
1359
|
-
tone: "danger",
|
|
1360
|
-
disabled: isPending,
|
|
1361
|
-
className: "w-full",
|
|
1362
|
-
onClick: () => void props.onReject(props.frame)
|
|
1363
|
-
}
|
|
1364
|
-
)
|
|
1365
|
-
] }),
|
|
1366
|
-
isBudgetOverride(props.frame) ? /* @__PURE__ */ jsx(
|
|
1367
|
-
ActionButton,
|
|
1368
|
-
{
|
|
1369
|
-
label: "Request revision",
|
|
1370
|
-
tone: "secondary",
|
|
1371
|
-
disabled: isPending,
|
|
1372
|
-
className: "w-full",
|
|
1373
|
-
onClick: () => void props.onRequestRevision(props.frame)
|
|
1374
|
-
}
|
|
1375
|
-
) : null
|
|
1376
|
-
] }) : actionMode === "acknowledge" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1377
|
-
commentEnabled ? /* @__PURE__ */ jsx(
|
|
1378
|
-
ActionButton,
|
|
1379
|
-
{
|
|
1380
|
-
label: "Comment",
|
|
1381
|
-
tone: "accent",
|
|
1382
|
-
disabled: isPending,
|
|
1383
|
-
onClick: () => setCommentOpen(true)
|
|
1384
|
-
}
|
|
1385
|
-
) : null,
|
|
1386
|
-
/* @__PURE__ */ jsx(
|
|
1387
|
-
ActionButton,
|
|
1388
|
-
{
|
|
1389
|
-
label: isPending ? "Saving\u2026" : "Acknowledge",
|
|
1390
|
-
tone: "accent",
|
|
1391
|
-
disabled: isPending,
|
|
1392
|
-
onClick: () => void props.onAcknowledge(props.frame)
|
|
1393
|
-
}
|
|
1394
|
-
)
|
|
1395
|
-
] }) : null,
|
|
1396
|
-
props.itemLink ? /* @__PURE__ */ jsxs(
|
|
1397
|
-
"a",
|
|
1398
|
-
{
|
|
1399
|
-
href: props.itemLink,
|
|
1400
|
-
target: "_blank",
|
|
1401
|
-
rel: "noopener noreferrer",
|
|
1402
|
-
className: "inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",
|
|
1403
|
-
children: [
|
|
1404
|
-
primaryLinkLabel(props.frame),
|
|
1405
|
-
/* @__PURE__ */ jsx(ExternalLinkIcon, {})
|
|
1406
|
-
]
|
|
1407
|
-
}
|
|
1408
|
-
) : 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: [
|
|
1411
|
-
"Commenting on ",
|
|
1412
|
-
compactTitle(props.frame)
|
|
1413
|
-
] }),
|
|
1414
|
-
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: "Posts to the issue thread without leaving Focus." }),
|
|
1415
|
-
/* @__PURE__ */ jsx(
|
|
1416
|
-
"textarea",
|
|
1417
|
-
{
|
|
1418
|
-
ref: textareaRef,
|
|
1419
|
-
value: commentBody,
|
|
1420
|
-
onChange: (event) => setCommentBody(event.target.value),
|
|
1421
|
-
onKeyDown: (event) => {
|
|
1422
|
-
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
1423
|
-
event.preventDefault();
|
|
1424
|
-
void submitComment();
|
|
1425
|
-
}
|
|
1426
|
-
if (event.key === "Escape") {
|
|
1427
|
-
event.preventDefault();
|
|
1428
|
-
setCommentOpen(false);
|
|
1429
|
-
setCommentBody("");
|
|
1430
|
-
}
|
|
1431
|
-
},
|
|
1432
|
-
rows: 4,
|
|
1433
|
-
placeholder: "Add a short operator note back to the issue\u2026",
|
|
1434
|
-
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
|
-
}
|
|
1436
|
-
),
|
|
1437
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
1438
|
-
/* @__PURE__ */ jsx(
|
|
1439
|
-
ActionButton,
|
|
1440
|
-
{
|
|
1441
|
-
label: "Cancel",
|
|
1442
|
-
tone: "secondary",
|
|
1443
|
-
disabled: isPending,
|
|
1444
|
-
onClick: () => {
|
|
1445
|
-
setCommentOpen(false);
|
|
1446
|
-
setCommentBody("");
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
),
|
|
1450
|
-
/* @__PURE__ */ jsx(
|
|
1451
|
-
ActionButton,
|
|
1452
|
-
{
|
|
1453
|
-
label: isPending ? "Posting\u2026" : "Post comment",
|
|
1454
|
-
tone: "accent",
|
|
1455
|
-
disabled: isPending || commentBody.trim().length === 0,
|
|
1456
|
-
onClick: () => void submitComment()
|
|
1457
|
-
}
|
|
1458
|
-
)
|
|
1459
|
-
] })
|
|
1460
|
-
] }) : null
|
|
1461
|
-
]
|
|
1462
|
-
}
|
|
1463
|
-
);
|
|
1464
|
-
}
|
|
1465
|
-
function NowPane(props) {
|
|
1466
|
-
const frame = props.snapshot.now;
|
|
1467
|
-
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
1468
|
-
const wideLayout = useWideLayout(1120);
|
|
1469
|
-
const activeFrameId = frame?.id ?? null;
|
|
1470
|
-
const activeUnread = !!frame && (props.snapshot.review?.unread.now ?? 0) > 0;
|
|
1471
|
-
const itemLink = frame ? itemHref(frame, props.companyPrefix) : null;
|
|
1472
|
-
const recommendedMove = frame ? recommendedMoveValue(frame) : null;
|
|
1473
|
-
const helperLine = frame ? supportingLine(frame, "now") : null;
|
|
1474
|
-
const driverBadge = driverBadgeStyle();
|
|
1475
|
-
useEffect(() => {
|
|
1476
|
-
setDetailsOpen(false);
|
|
1477
|
-
}, [activeFrameId]);
|
|
1478
|
-
return /* @__PURE__ */ jsxs(
|
|
1479
|
-
"section",
|
|
1480
|
-
{
|
|
1481
|
-
className: "border border-border bg-card shadow-sm",
|
|
1482
|
-
style: frame ? { borderLeftWidth: 3, borderLeftColor: ACCENT_COLOR } : void 0,
|
|
1483
|
-
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 })
|
|
1490
|
-
] })
|
|
1491
|
-
] }),
|
|
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: [
|
|
1494
|
-
"now ",
|
|
1495
|
-
props.counts.now > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.now }) : props.counts.now
|
|
1496
|
-
] }),
|
|
1497
|
-
/* @__PURE__ */ jsxs("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
|
|
1498
|
-
"next ",
|
|
1499
|
-
props.counts.next > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.next }) : props.counts.next
|
|
1500
|
-
] }),
|
|
1501
|
-
/* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
|
|
1502
|
-
"ambient ",
|
|
1503
|
-
props.counts.ambient > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.ambient }) : props.counts.ambient
|
|
1504
|
-
] })
|
|
1505
|
-
] })
|
|
1506
|
-
] }),
|
|
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(
|
|
1509
|
-
"div",
|
|
1510
|
-
{
|
|
1511
|
-
className: wideLayout ? "flex items-start gap-6" : "space-y-4",
|
|
1512
|
-
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) })
|
|
1517
|
-
] }),
|
|
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) })
|
|
1522
|
-
] }),
|
|
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
|
|
1527
|
-
] }),
|
|
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(
|
|
1530
|
-
"button",
|
|
1531
|
-
{
|
|
1532
|
-
type: "button",
|
|
1533
|
-
onClick: () => setDetailsOpen(!detailsOpen),
|
|
1534
|
-
"aria-expanded": detailsOpen,
|
|
1535
|
-
className: "flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",
|
|
1536
|
-
style: { color: "inherit" },
|
|
1537
|
-
children: [
|
|
1538
|
-
/* @__PURE__ */ jsx(
|
|
1539
|
-
"svg",
|
|
1540
|
-
{
|
|
1541
|
-
viewBox: "0 0 16 16",
|
|
1542
|
-
className: cn("h-3 w-3 transition-transform", detailsOpen && "rotate-90"),
|
|
1543
|
-
fill: "currentColor",
|
|
1544
|
-
"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" })
|
|
1546
|
-
}
|
|
1547
|
-
),
|
|
1548
|
-
detailsOpen ? "Hide context" : "Show context"
|
|
1549
|
-
]
|
|
1550
|
-
}
|
|
1551
|
-
) }) })
|
|
1552
|
-
] }),
|
|
1553
|
-
/* @__PURE__ */ jsx(
|
|
1554
|
-
NowActionRail,
|
|
1555
|
-
{
|
|
1556
|
-
frame,
|
|
1557
|
-
lane: "now",
|
|
1558
|
-
wide: wideLayout,
|
|
1559
|
-
pendingId: props.pendingId,
|
|
1560
|
-
itemLink,
|
|
1561
|
-
onApprove: props.onApprove,
|
|
1562
|
-
onReject: props.onReject,
|
|
1563
|
-
onRequestRevision: props.onRequestRevision,
|
|
1564
|
-
onAcknowledge: props.onAcknowledge,
|
|
1565
|
-
onComment: props.onComment
|
|
1566
|
-
}
|
|
1567
|
-
)
|
|
1568
|
-
]
|
|
1569
|
-
}
|
|
1570
|
-
),
|
|
1571
|
-
detailsOpen ? /* @__PURE__ */ jsx(
|
|
1572
|
-
NowDetails,
|
|
1573
|
-
{
|
|
1574
|
-
frame,
|
|
1575
|
-
snapshotUpdatedAt: props.snapshot.updatedAt,
|
|
1576
|
-
companyPrefix: props.companyPrefix
|
|
1577
|
-
}
|
|
1578
|
-
) : null
|
|
1579
|
-
] }) })
|
|
1580
|
-
]
|
|
1581
|
-
}
|
|
1582
|
-
);
|
|
1583
|
-
}
|
|
1584
|
-
function NextRow(props) {
|
|
1585
|
-
const { frame } = props;
|
|
1586
|
-
const [expanded, setExpanded] = useState(false);
|
|
1587
|
-
const unread = isFrameUnreadInSnapshot(frame, props.snapshot);
|
|
1588
|
-
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1589
|
-
const itemLink = itemHref(frame, props.companyPrefix);
|
|
1590
|
-
const secondaryText = nextPrimaryText(frame);
|
|
1591
|
-
const driver = driverLabel(frame);
|
|
1592
|
-
const driverBadge = driverBadgeStyle();
|
|
1593
|
-
const detailText = judgmentLine(frame, "next");
|
|
1594
|
-
const showDetailText = detailText.trim().length > 0 && detailText !== secondaryText && detailText !== GENERIC_QUEUED_JUDGMENT;
|
|
1595
|
-
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(
|
|
1598
|
-
"button",
|
|
1599
|
-
{
|
|
1600
|
-
type: "button",
|
|
1601
|
-
onClick: () => setExpanded(!expanded),
|
|
1602
|
-
"aria-expanded": expanded,
|
|
1603
|
-
className: "flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",
|
|
1604
|
-
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
|
|
1610
|
-
] }),
|
|
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(
|
|
1615
|
-
"svg",
|
|
1616
|
-
{
|
|
1617
|
-
viewBox: "0 0 16 16",
|
|
1618
|
-
className: cn("h-3 w-3 shrink-0 text-muted-foreground transition-transform", expanded && "rotate-90"),
|
|
1619
|
-
fill: "currentColor",
|
|
1620
|
-
"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" })
|
|
1622
|
-
}
|
|
1623
|
-
)
|
|
1624
|
-
]
|
|
1625
|
-
}
|
|
1626
|
-
),
|
|
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: [
|
|
1635
|
-
"updated ",
|
|
1636
|
-
formatRelativeTime(frameUpdatedAt(frame, props.snapshotUpdatedAt))
|
|
1637
|
-
] }),
|
|
1638
|
-
itemLink ? /* @__PURE__ */ jsx(
|
|
1639
|
-
"a",
|
|
1640
|
-
{
|
|
1641
|
-
href: itemLink,
|
|
1642
|
-
className: "font-medium underline underline-offset-2 hover:text-foreground",
|
|
1643
|
-
children: primaryLinkLabel(frame)
|
|
1644
|
-
}
|
|
1645
|
-
) : null,
|
|
1646
|
-
activityLink ? /* @__PURE__ */ jsx(
|
|
1647
|
-
"a",
|
|
1648
|
-
{
|
|
1649
|
-
href: activityLink,
|
|
1650
|
-
className: "font-medium underline underline-offset-2 hover:text-foreground",
|
|
1651
|
-
children: "Open activity"
|
|
1652
|
-
}
|
|
1653
|
-
) : null
|
|
1654
|
-
] }),
|
|
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(
|
|
1658
|
-
FrameActions,
|
|
1659
|
-
{
|
|
1660
|
-
frame,
|
|
1661
|
-
lane: "next",
|
|
1662
|
-
pendingId: props.pendingId,
|
|
1663
|
-
onApprove: props.onApprove,
|
|
1664
|
-
onReject: props.onReject,
|
|
1665
|
-
onRequestRevision: props.onRequestRevision,
|
|
1666
|
-
onAcknowledge: props.onAcknowledge
|
|
1667
|
-
}
|
|
1668
|
-
)
|
|
1669
|
-
] })
|
|
1670
|
-
] }) : null
|
|
1671
|
-
] });
|
|
1672
|
-
}
|
|
1673
|
-
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(
|
|
1677
|
-
NextRow,
|
|
1678
|
-
{
|
|
1679
|
-
rank: i + 1,
|
|
1680
|
-
frame: row.frame,
|
|
1681
|
-
movement: props.movement[row.frame.interactionId],
|
|
1682
|
-
snapshot: props.snapshot,
|
|
1683
|
-
snapshotUpdatedAt: props.snapshotUpdatedAt,
|
|
1684
|
-
companyPrefix: props.companyPrefix,
|
|
1685
|
-
pendingId: props.pendingId,
|
|
1686
|
-
onApprove: props.onApprove,
|
|
1687
|
-
onReject: props.onReject,
|
|
1688
|
-
onRequestRevision: props.onRequestRevision,
|
|
1689
|
-
onAcknowledge: props.onAcknowledge,
|
|
1690
|
-
onComment: props.onComment
|
|
1691
|
-
},
|
|
1692
|
-
row.frame.id
|
|
1693
|
-
))
|
|
1694
|
-
] });
|
|
1695
|
-
}
|
|
1696
|
-
function AmbientRow(props) {
|
|
1697
|
-
const { frame, snapshotUpdatedAt } = props;
|
|
1698
|
-
const href = itemHref(frame, props.companyPrefix);
|
|
1699
|
-
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)) })
|
|
1705
|
-
] });
|
|
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 });
|
|
1707
|
-
}
|
|
1708
|
-
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(
|
|
1715
|
-
AmbientRow,
|
|
1716
|
-
{
|
|
1717
|
-
frame: row.frame,
|
|
1718
|
-
snapshot: props.snapshot,
|
|
1719
|
-
snapshotUpdatedAt: props.snapshotUpdatedAt,
|
|
1720
|
-
companyPrefix: props.companyPrefix
|
|
1721
|
-
},
|
|
1722
|
-
row.frame.id
|
|
1723
|
-
))
|
|
1724
|
-
] });
|
|
1725
|
-
}
|
|
1726
|
-
function DashboardWidget(props) {
|
|
1727
|
-
const companyId = props.context.companyId;
|
|
1728
|
-
const brand = currentSurfaceBrand();
|
|
1729
|
-
const href = pluginPagePath(props.context.companyPrefix);
|
|
1730
|
-
const model = useAttentionModel(companyId);
|
|
1731
|
-
const data = model.snapshot;
|
|
1732
|
-
const loading = model.loading;
|
|
1733
|
-
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 });
|
|
1738
|
-
const posture = postureForSnapshot(data);
|
|
1739
|
-
const leadingText = data.now ? recommendedMoveValue(data.now) ?? data.now.title : null;
|
|
1740
|
-
const actionable = actionableLabel(data);
|
|
1741
|
-
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
|
|
1749
|
-
] }) }),
|
|
1750
|
-
/* @__PURE__ */ jsxs("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
|
|
1751
|
-
"now ",
|
|
1752
|
-
data.counts.now,
|
|
1753
|
-
" \xB7 next ",
|
|
1754
|
-
data.counts.next,
|
|
1755
|
-
" \xB7 ambient ",
|
|
1756
|
-
data.counts.ambient
|
|
1757
|
-
] })
|
|
1758
|
-
] }),
|
|
1759
|
-
leadingText ? /* @__PURE__ */ jsx(
|
|
1760
|
-
"div",
|
|
1761
|
-
{
|
|
1762
|
-
className: "mt-2 text-sm font-medium leading-5 text-foreground",
|
|
1763
|
-
style: {
|
|
1764
|
-
display: "-webkit-box",
|
|
1765
|
-
WebkitLineClamp: 2,
|
|
1766
|
-
WebkitBoxOrient: "vertical",
|
|
1767
|
-
overflow: "hidden"
|
|
1768
|
-
},
|
|
1769
|
-
children: leadingText
|
|
1770
|
-
}
|
|
1771
|
-
) : /* @__PURE__ */ jsx("div", { className: "mt-2 text-sm text-muted-foreground", children: "No active interruption right now." })
|
|
1772
|
-
] });
|
|
1773
|
-
}
|
|
1774
|
-
function AttentionSidebarLink({ context }) {
|
|
1775
|
-
const companyId = context.companyId;
|
|
1776
|
-
const brand = currentSurfaceBrand();
|
|
1777
|
-
const href = pluginPagePath(context.companyPrefix);
|
|
1778
|
-
const isActive = typeof window !== "undefined" && window.location.pathname === href;
|
|
1779
|
-
const model = useAttentionModel(companyId);
|
|
1780
|
-
const merged = model.snapshot;
|
|
1781
|
-
const actionable = merged ? actionableCount(merged) : 0;
|
|
1782
|
-
const unread = unreadCount(merged);
|
|
1783
|
-
return /* @__PURE__ */ jsxs(
|
|
1784
|
-
"a",
|
|
1785
|
-
{
|
|
1786
|
-
href,
|
|
1787
|
-
"aria-current": isActive ? "page" : void 0,
|
|
1788
|
-
className: cn(
|
|
1789
|
-
"flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors",
|
|
1790
|
-
isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
|
|
1791
|
-
),
|
|
1792
|
-
children: [
|
|
1793
|
-
/* @__PURE__ */ jsxs("span", { className: "relative shrink-0", "aria-hidden": "true", children: [
|
|
1794
|
-
/* @__PURE__ */ jsxs(
|
|
1795
|
-
"svg",
|
|
1796
|
-
{
|
|
1797
|
-
viewBox: "0 0 24 24",
|
|
1798
|
-
className: "h-4 w-4",
|
|
1799
|
-
fill: "none",
|
|
1800
|
-
stroke: "currentColor",
|
|
1801
|
-
strokeWidth: "1.9",
|
|
1802
|
-
strokeLinecap: "round",
|
|
1803
|
-
strokeLinejoin: "round",
|
|
1804
|
-
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" })
|
|
1812
|
-
]
|
|
1813
|
-
}
|
|
1814
|
-
),
|
|
1815
|
-
unread > 0 ? /* @__PURE__ */ jsx(
|
|
1816
|
-
"span",
|
|
1817
|
-
{
|
|
1818
|
-
className: "absolute -right-1 -top-1 h-2 w-2 rounded-full",
|
|
1819
|
-
style: { backgroundColor: ACCENT_COLOR }
|
|
1820
|
-
}
|
|
1821
|
-
) : null
|
|
1822
|
-
] }),
|
|
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(
|
|
1825
|
-
"span",
|
|
1826
|
-
{
|
|
1827
|
-
className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
|
|
1828
|
-
style: { color: ACCENT_COLOR, borderColor: ACCENT_BORDER, backgroundColor: "transparent" },
|
|
1829
|
-
children: unread
|
|
1830
|
-
}
|
|
1831
|
-
) : null
|
|
1832
|
-
]
|
|
1833
|
-
}
|
|
1834
|
-
);
|
|
1835
|
-
}
|
|
1836
|
-
function AttentionPage(props) {
|
|
1837
|
-
const companyId = props.context.companyId;
|
|
1838
|
-
const brand = currentSurfaceBrand();
|
|
1839
|
-
const model = useAttentionModel(companyId);
|
|
1840
|
-
const snapshot = model.snapshot;
|
|
1841
|
-
const loading = model.loading;
|
|
1842
|
-
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
|
-
);
|
|
1870
|
-
const backgroundErrorMessage = displaySnapshot && error ? `Sync issue: ${error.message}` : null;
|
|
1871
|
-
const posture = useMemo(() => displaySnapshot ? postureForSnapshot(displaySnapshot) : null, [displaySnapshot]);
|
|
1872
|
-
const nextRows = useMemo(
|
|
1873
|
-
() => displaySnapshot ? displaySnapshot.next.map((frame) => ({ frame, lane: "next" })) : [],
|
|
1874
|
-
[displaySnapshot]
|
|
1875
|
-
);
|
|
1876
|
-
const ambientRows = useMemo(
|
|
1877
|
-
() => displaySnapshot ? displaySnapshot.ambient.map((frame) => ({ frame, lane: "ambient" })) : [],
|
|
1878
|
-
[displaySnapshot]
|
|
1879
|
-
);
|
|
1880
|
-
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 });
|
|
2017
|
-
if (!displaySnapshot || !posture) {
|
|
2018
|
-
return /* @__PURE__ */ jsx(
|
|
2019
|
-
MessageCard,
|
|
2020
|
-
{
|
|
2021
|
-
title: `No ${brand.wordmark} State Yet`,
|
|
2022
|
-
body: `No ${brand.key === "focus" ? "focus" : "attention"} state has been captured for this company yet.`
|
|
2023
|
-
}
|
|
2024
|
-
);
|
|
2025
|
-
}
|
|
2026
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2027
|
-
/* @__PURE__ */ jsx(
|
|
2028
|
-
NowPane,
|
|
2029
|
-
{
|
|
2030
|
-
snapshot: displaySnapshot,
|
|
2031
|
-
posture,
|
|
2032
|
-
brand,
|
|
2033
|
-
companyPrefix: props.context.companyPrefix,
|
|
2034
|
-
counts: displaySnapshot.counts,
|
|
2035
|
-
pendingId,
|
|
2036
|
-
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
2037
|
-
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
2038
|
-
onRequestRevision: requestApprovalRevision,
|
|
2039
|
-
onAcknowledge: acknowledgeFrame,
|
|
2040
|
-
onComment: commentOnIssueFrame
|
|
2041
|
-
}
|
|
2042
|
-
),
|
|
2043
|
-
/* @__PURE__ */ jsx(
|
|
2044
|
-
NextLane,
|
|
2045
|
-
{
|
|
2046
|
-
snapshot: displaySnapshot,
|
|
2047
|
-
rows: nextRows,
|
|
2048
|
-
movement: nextMovement,
|
|
2049
|
-
snapshotUpdatedAt: displaySnapshot.updatedAt,
|
|
2050
|
-
companyPrefix: props.context.companyPrefix,
|
|
2051
|
-
pendingId,
|
|
2052
|
-
onApprove: (frame) => submitApprovalDecision(frame, "approve"),
|
|
2053
|
-
onReject: (frame) => submitApprovalDecision(frame, "reject"),
|
|
2054
|
-
onRequestRevision: requestApprovalRevision,
|
|
2055
|
-
onAcknowledge: acknowledgeFrame,
|
|
2056
|
-
onComment: commentOnIssueFrame
|
|
2057
|
-
}
|
|
2058
|
-
),
|
|
2059
|
-
/* @__PURE__ */ jsx(
|
|
2060
|
-
AmbientLane,
|
|
2061
|
-
{
|
|
2062
|
-
snapshot: displaySnapshot,
|
|
2063
|
-
rows: ambientRows,
|
|
2064
|
-
snapshotUpdatedAt: displaySnapshot.updatedAt,
|
|
2065
|
-
companyPrefix: props.context.companyPrefix
|
|
2066
|
-
}
|
|
2067
|
-
),
|
|
2068
|
-
/* @__PURE__ */ jsx(StatusToast, { message: statusOverride ?? backgroundErrorMessage })
|
|
2069
|
-
] });
|
|
2070
|
-
}
|
|
2071
|
-
export {
|
|
2072
|
-
AttentionPage,
|
|
2073
|
-
AttentionSidebarLink,
|
|
2074
|
-
DashboardWidget
|
|
2075
|
-
};
|
|
2076
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import{useEffect as en,useMemo as He,useState as It}from"react";function $(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)?e:null}function I(e){return typeof e=="string"&&e.trim().length>0?e:void 0}function ve(e){return e===null?null:I(e)}function ye(e){if(!Array.isArray(e))return;let t=e.filter(n=>typeof n=="string"&&n.trim().length>0);return t.length>0?t:void 0}function Ct(e){if(!Array.isArray(e))return;let t=e.filter(n=>{if(typeof n!="object"||n===null)return!1;let o=n;return o.kind==="same_issue"||o.kind==="resolves"||o.kind==="supersedes"||o.kind==="repeats"||o.kind==="escalates"});return t.length>0?t:void 0}function E(e){let t=$(e.metadata),n=$(t?.attention),o=$(t?.semantic),i=$(t?.episode),a=$(t?.issueIntelligence),s=$(t?.decision);return{entityType:I(t?.entityType),issueStatus:I(t?.issueStatus),issuePriority:I(t?.issuePriority),approvalStatus:I(t?.approvalStatus),approvalType:I(t?.approvalType),agentStatus:I(t?.agentStatus),pauseReason:ve(t?.pauseReason),liveReconciled:typeof t?.liveReconciled=="boolean"?t.liveReconciled:void 0,activityPath:I(t?.activityPath),attention:n?{rationale:ye(n.rationale)}:void 0,semantic:o?{confidence:o.confidence==="low"||o.confidence==="medium"||o.confidence==="high"?o.confidence:void 0,relationHints:Ct(o.relationHints)}:void 0,episode:i?{size:typeof i.size=="number"?i.size:void 0,state:I(i.state)}:void 0,issueIntelligence:a?{matchedRuleIds:ye(a.matchedRuleIds),owner:ve(a.owner),ownerKind:a.ownerKind==="agent"||a.ownerKind==="human"?a.ownerKind:a.ownerKind===null?null:void 0,blockingTarget:ve(a.blockingTarget)}:void 0,decision:s?{owner:s.owner==="aperture_core"||s.owner==="paperclip_reconciliation"||s.owner==="approval_overlay"||s.owner==="display_merge"?s.owner:void 0,lane:s.lane==="now"||s.lane==="next"||s.lane==="ambient"?s.lane:void 0,sourcePolicy:I(s.sourcePolicy),rationale:ye(s.rationale),promotedFromLane:s.promotedFromLane==="next"||s.promotedFromLane==="ambient"?s.promotedFromLane:void 0}:void 0}}var pn=300*1e3;function R(e){return e.provenance?.factors?.includes("budget stop")??!1}function _(e,t){return e.timing.updatedAt||t}var Q="Important enough to keep visible, but not enough to displace now.",Rt=Q;function Tt(e){return e?"Budget controls are blocking work until a board decision lands.":"A board decision is blocking work in Paperclip."}function Ke(e,t){return t==="now"?R(e)?Tt(!0):e.mode==="approval"?"A human decision is blocking work right now.":e.tone==="critical"?"This surfaced because it can displace the operator now.":"This is the clearest current operator focus.":R(e)?"Budget review is staged behind the current focus.":t==="next"?Rt:"Useful for awareness, but not interrupting."}import{useEffect as We,useRef as Lt,useState as ze}from"react";import{jsx as f,jsxs as k}from"react/jsx-runtime";var A="#007ACC",G=`${A}14`,F=`${A}33`,re={backgroundColor:A};function S(...e){return e.filter(Boolean).join(" ")}function Pt(){let e=Lt(null);return We(()=>{e.current?.style.setProperty("color",A,"important")}),e}function v({children:e,className:t}){let n=Pt();return f("span",{ref:n,className:t,children:e})}function we({size:e="sm"}){let t=e==="md"?10:8;return f("span",{"aria-hidden":"true",className:"inline-block shrink-0 rounded-full border",style:{width:t,height:t,borderColor:F}})}function Qe(e=1024){let[t,n]=ze(!1);return We(()=>{if(typeof window>"u"||typeof window.matchMedia!="function")return;let o=window.matchMedia(`(min-width: ${e}px)`),i=()=>n(o.matches);return i(),o.addEventListener("change",i),()=>o.removeEventListener("change",i)},[e]),t}function oe(e){let t=e.tone??"default";if(!e.visible&&!e.reserveSpace)return null;let n=e.visible?t==="muted"?{backgroundColor:F}:re:{backgroundColor:"transparent"};return f("span",{className:"inline-block h-2 w-2 rounded-full shrink-0",style:n,"aria-label":e.visible?"Unread attention item":void 0})}function V(){return k("svg",{viewBox:"0 0 16 16",className:"h-3 w-3",fill:"none",stroke:"currentColor",strokeWidth:"1.5","aria-hidden":"true",children:[f("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"}),f("path",{d:"M8.5 1.5h6v6"}),f("path",{d:"M14.5 1.5l-7 7"})]})}function b(e){return f("div",{className:S("animate-pulse rounded bg-secondary/80",e.className),"aria-hidden":"true"})}function D(e){return k("div",{className:"border border-border bg-card p-4 shadow-sm",children:[f("div",{className:S("text-sm font-medium",e.accent&&"text-foreground"),children:e.title}),f("div",{className:"mt-1 text-sm text-muted-foreground",children:e.body})]})}function Ge({label:e}){return k("div",{className:"border border-border bg-card px-4 py-3 shadow-sm",children:[k("div",{className:"flex items-start gap-3",children:[f(b,{className:"mt-0.5 h-3 w-3 rounded-full"}),k("div",{className:"flex-1 space-y-2",children:[f(b,{className:"h-3 w-20"}),f(b,{className:"h-4 w-48"})]}),f(b,{className:"h-3 w-24"})]}),f("div",{className:"sr-only",children:e})]})}function Ve({label:e}){return k("div",{className:"space-y-5","aria-label":e,children:[f("div",{className:"border border-border bg-card px-4 py-3 shadow-sm",children:k("div",{className:"flex items-center justify-between gap-3",children:[f(b,{className:"h-4 w-44"}),f(b,{className:"h-8 w-28"})]})}),k("div",{className:"border border-border bg-card shadow-sm",children:[f("div",{className:"border-b border-border px-4 py-3 sm:px-6",children:f(b,{className:"h-4 w-36"})}),k("div",{className:"space-y-4 px-4 py-5 sm:px-6",children:[f(b,{className:"h-3 w-16"}),f(b,{className:"h-7 w-2/3"}),k("div",{className:"flex gap-2",children:[f(b,{className:"h-6 w-24"}),f(b,{className:"h-6 w-20"}),f(b,{className:"h-6 w-16"})]}),f(b,{className:"h-10 w-56"})]})]}),k("div",{className:"border border-border bg-card shadow-sm",children:[f("div",{className:"border-b border-border px-4 py-3 sm:px-6",children:f(b,{className:"h-3 w-12"})}),k("div",{className:"space-y-3 px-4 py-4 sm:px-6",children:[f(b,{className:"h-4 w-full"}),f(b,{className:"h-4 w-5/6"}),f(b,{className:"h-4 w-2/3"})]})]})]})}function Ze({message:e}){return f("div",{className:S("pointer-events-none fixed bottom-6 left-1/2 z-50 -translate-x-1/2 transition-all duration-200",e?"translate-y-0 opacity-100":"translate-y-2 opacity-0"),"aria-live":"polite","aria-atomic":"true",children:e?f("div",{className:"min-w-72 max-w-md rounded-lg border border-border bg-card px-4 py-3 text-sm text-foreground shadow-lg",children:e}):null})}function q(e){return f("span",{className:S("inline-flex items-center justify-center rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap shrink-0",e.className),style:e.style,children:e.children})}function N(e){let[t,n]=ze(!1),o=e.tone==="primary"?"bg-green-700 text-white hover:bg-green-600":e.tone==="accent"?"text-white":e.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";return f("button",{type:"button",onClick:e.onClick,onMouseEnter:()=>n(!0),onMouseLeave:()=>n(!1),disabled:e.disabled,className:S("inline-flex h-8 items-center justify-center gap-2 rounded-md px-3 text-sm font-medium","transition-[color,background-color,border-color,box-shadow,opacity,filter]","focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]","disabled:pointer-events-none disabled:opacity-50",e.disabled&&/…|\.\.\./.test(e.label)&&"animate-pulse",o,e.className),style:e.tone==="accent"?{...re,filter:t&&!e.disabled?"brightness(1.08)":void 0}:void 0,children:e.label})}function Je({movement:e}){return e?k("span",{className:"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium",style:{color:A,backgroundColor:G,borderColor:F},children:[f("span",{"aria-hidden":"true",children:e==="up"?"\u2191":"\u2193"}),e==="up"?"moved up":"moved down"]}):null}import{usePluginData as Et,usePluginStream as Mt}from"@paperclipai/plugin-sdk/ui";import{useEffect as ke,useMemo as _t,useRef as Ae,useState as Dt}from"react";var Ot="attention-updates";function ie(){return{key:"focus",wordmark:"Focus",headingEmptyState:"No focus state yet.",loadingLabel:"Loading Focus\u2026"}}function Se(e){return e?`/${e}/aperture`:"/aperture"}function Z(e){let t=new Date(e).getTime();if(Number.isNaN(t))return"recently";let n=t-Date.now(),o=Math.round(n/6e4),i=new Intl.RelativeTimeFormat(void 0,{numeric:"auto"});if(Math.abs(o)<60)return i.format(o,"minute");let a=Math.round(o/60);if(Math.abs(a)<24)return i.format(a,"hour");let s=Math.round(a/24);return i.format(s,"day")}function j(e){return e.source?.label??"Paperclip"}function Ne(e){let t=e.now;return t?.tone==="critical"||t?.consequence==="high"||e.counts.now+e.counts.next>=3?{glyph:"\u25CF",label:"busy"}:e.counts.now>0||e.counts.next>0||e.counts.ambient>0?{glyph:"\u25D0",label:"elevated"}:{glyph:"\u25CB",label:"calm"}}function Ie(e){return(e.now?1:0)+e.next.length}function Xe(e){let t=Ie(e);return t>0?`${t} action${t===1?"":"s"}`:null}function Ye(e){return e?.review?.unread.total??0}function Bt(e,t,n=15e3){let o=Ae(t);o.current=t,ke(()=>{if(!e)return;function i(){if(!(typeof document<"u"&&document.visibilityState==="hidden"))for(let d of o.current)d()}let a=window.setInterval(()=>{i()},n);function s(){if(typeof document<"u"&&document.visibilityState==="visible")for(let d of o.current)d()}return document.addEventListener("visibilitychange",s),()=>{window.clearInterval(a),document.removeEventListener("visibilitychange",s)}},[e,n])}function et(e,t=3500){let n=Ae({}),o=Ae({}),[i,a]=Dt({});return ke(()=>{let s=Object.fromEntries(e.map((d,m)=>[d.interactionId,m]));return a(d=>{let m={};for(let u of e){let x=n.current[u.interactionId],y=s[u.interactionId];typeof x!="number"||x===y||(m[u.interactionId]=y<x?"up":"down")}if(Object.keys(m).length===0)return d;let p={...d,...m};for(let[u,x]of Object.entries(m))window.clearTimeout(o.current[u]),o.current[u]=window.setTimeout(()=>{a(y=>{if(y[u]!==x)return y;let P={...y};return delete P[u],P}),delete o.current[u]},t);return p}),n.current=s,()=>{for(let d of Object.values(o.current))window.clearTimeout(d)}},[e,t]),i}function ae(e){let t=Et("attention-display",e?{companyId:e}:void 0),n=Mt(Ot,e?{companyId:e}:void 0);return Bt(e,[t.refresh]),ke(()=>{!e||!n.lastEvent||t.refresh()},[e,n.lastEvent?.updatedAt,n.lastEvent?.eventType]),_t(()=>({snapshot:t.data?.snapshot??null,review:t.data?.reviewState??null,loading:t.loading,error:t.error,refresh(){t.refresh()}}),[t])}var T={approvalType:"approval-type",requestedAmount:"requested-amount",budgetReason:"budget-reason",decisionContext:"decision-context",linkedIssues:"linked-issues",needsActionFrom:"needs-action-from",blockedBy:"blocked-by",blocksTarget:"blocks-target",recommendedMove:"recommended-move",issueStatus:"issue-status",issuePriority:"issue-priority",latestComment:"latest-comment",agentStatus:"agent-status",pauseReason:"pause-reason",agentTitle:"agent-title"};function tt(e){try{let t=decodeURIComponent(e);return t.trim().length>0?t:null}catch{return null}}function se(e){if(typeof e!="string")return null;let t=e.indexOf(":");if(t<=0||t>=e.length-1)return null;let n=tt(e.slice(0,t)),o=tt(e.slice(t+1));return!n||!o?null:{kind:n,id:o}}function nt(e){return se(e)?.kind??null}function rt(e){return se(e)?.id??null}function ot(e){return`Blocks ${e} until the review lands.`}function it(e){return`Needs action from ${e}.`}import{Fragment as Ht,jsx as Ce}from"react/jsx-runtime";function J(e,t){let n=e.context?.items?.find(o=>o.id===t);return typeof n?.value=="string"&&n.value.trim().length>0?n.value:void 0}function Re(e){return J(e,T.requestedAmount)}function Te(e){return J(e,T.budgetReason)}function X(e){return J(e,T.recommendedMove)}function Ut(e){return J(e,T.needsActionFrom)}function $t(e){return J(e,T.blocksTarget)}function de(e){return`${e.consequence} impact`}function Fe(e){switch(e){case"in_review":return"review required";case"pending_approval":return"approval required";default:return e.replace(/_/g," ")}}function H(e){if(R(e))return"budget stop";let t=E(e),n=t.issueStatus;if(typeof n=="string"&&n.trim().length>0)return Fe(n);let o=t.pauseReason;if(typeof o=="string"&&o.trim().length>0)return Fe(o);let i=t.agentStatus;return typeof i=="string"&&i.trim().length>0?Fe(i):null}function Le(){return{className:"border-transparent",style:{color:A,backgroundColor:G,borderColor:F}}}function at(e){let t=H(e);return t?`${j(e)} \xB7 ${t}`:j(e)}function O(e,t){return e.provenance?.whyNow?e.provenance.whyNow:Ke(e,t)}function st(e){let t=X(e);if(t)return t;let n=e.summary?.trim();if(n)return n;let o=O(e,"next");return o===Q?null:o}function dt(e,t){let n=Ut(e),o=$t(e);if(o&&t==="now"&&B(e)==="issue"&&H(e)==="review required")return ot(o);if(n&&t==="now"&&B(e)==="issue")return it(n);if(t==="now"){let a=X(e)?.trim(),s=e.summary?.trim(),d=O(e,t).trim();return a&&s&&s!==a&&s!==d?s:null}let i=O(e,t);return i.trim().length>0?i:null}function ce(e){let t=se(e.taskId);return t?.kind==="approval"?t.id:null}function Pe(e){return rt(e.taskId)}function B(e){return nt(e.taskId)}function Y(e,t){let n=B(e),o=Pe(e);if(!n||!o||!t)return null;let i=n==="run"?"runs":n==="approval"?"approvals":n==="issue"?"issues":n==="agent"?"agents":null;return i?`/${t}/${i}/${o}`:null}function ct(e){return e?`/${e}/costs`:null}function Ee(e,t){if(!t)return null;let n=E(e);return n.activityPath?`/${t}/${n.activityPath}`:null}function le(e){switch(B(e)){case"approval":return"Open approval";case"issue":return"Open issue";case"run":return"Open run";case"agent":return"Open agent";default:return"Open in Paperclip"}}function Me(e,t){return ce(e)&&(e.responseSpec?.kind==="approval"||e.mode==="approval")?"approval":t!=="ambient"&&(e.responseSpec?.kind==="acknowledge"||e.mode==="status")?"acknowledge":"none"}function _e(e,t){let n=t.review?.lastSeenAt;return n?_(e,t.updatedAt).localeCompare(n)>0:!0}function M(e){return e.title.trim().length>0?e.title:"Untitled frame"}var qt=/\b([A-Z][A-Z0-9]+-\d+)\b|"([^"]+)"/g;function jt({text:e}){let t=[],n=0,o,i=new RegExp(qt.source,"g");for(;(o=i.exec(e))!==null;){o.index>n&&t.push(e.slice(n,o.index));let a=o[1]??o[2]??o[0];t.push(Ce(v,{children:o[1]?a:`"${a}"`},o.index)),n=i.lastIndex}return n<e.length&&t.push(e.slice(n)),Ce(Ht,{children:t})}function ue(e){return Ce(jt,{text:M(e)})}function me(e){return(e.context?.items??[]).filter(t=>t.id!==T.recommendedMove)}function fe(e){return B(e)==="issue"}function Kt(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"&&t.trim().length>0):[]}function Wt(e){let t=E(e).semantic?.confidence;return t==="low"||t==="medium"||t==="high"?t:null}function zt(e){return E(e).semantic?.relationHints??[]}function Qt(e){switch(e.kind){case"same_issue":return"Part of the same thread";case"resolves":return"Resolves an earlier blocker";case"supersedes":return"Moves the request forward";case"repeats":return"Repeats an earlier ask";case"escalates":return"Raises the urgency"}}function Gt(e){switch(e){case"now":return"This is the most urgent item in the queue right now.";case"next":return"This is staged behind the current top item.";case"ambient":return"This is visible for awareness without needing action yet."}}function Vt(e){let t=E(e).episode,n=t?.size??null,o=t?.state?.replace(/_/g," ")??null;return!n||n<=1?null:o?`Part of a ${o} thread with ${n} related interactions.`:`Part of a thread with ${n} related interactions.`}function Zt(e){let t=E(e).attention?.rationale??[],n=Kt(e.provenance?.factors);return[...new Set([...t.length>0?t:n,...n])]}function De(e){return`${e} confidence`}function Oe(e,t){let n=[...new Set(zt(e).map(Qt))];return{whyNow:e.provenance?.whyNow??e.summary??null,laneReason:Gt(t),signalStrength:Wt(e),signals:Zt(e),relationLabels:n,continuity:Vt(e)}}import{jsx as g,jsxs as L}from"react/jsx-runtime";function lt({frame:e}){let t=me(e);return t.length===0?null:g("div",{className:"grid gap-2 md:grid-cols-2",children:t.map(n=>L("div",{className:S("border border-border bg-secondary/50 px-3 py-2",n.id===T.latestComment&&"md:col-span-2"),children:[g("div",{className:"text-xs font-medium text-muted-foreground",children:n.label}),g("div",{className:"mt-1 text-sm text-foreground",children:n.value??"Available"})]},n.id))})}function Be(e){return e.values.length===0?null:g("div",{className:"flex flex-wrap items-center gap-2",children:e.values.map(t=>g(q,{className:e.accent?"border-transparent":"border-border bg-background/70 text-foreground/80",style:e.accent?{color:A,backgroundColor:G,borderColor:F}:void 0,children:t},t))})}function Ue(e){let t=Oe(e.frame,e.lane),n=[...t.signalStrength?[De(t.signalStrength)]:[],...t.signals.slice(0,2),...t.relationLabels.slice(0,1)],o=e.lane==="next"?"Why next":e.lane==="ambient"?"Why ambient":"Why now",i=t.whyNow??O(e.frame,e.lane),a=e.lane==="now"&&e.preferLaneReason?t.laneReason:i,s=e.lane==="now"?!e.preferLaneReason&&i!==t.laneReason?t.laneReason:null:t.laneReason;return L("div",{className:"space-y-2 border-l-2 pl-4",style:{borderColor:A},children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:o}),g("p",{className:"max-w-3xl text-sm leading-relaxed text-foreground/90",children:a}),s?g("p",{className:"text-xs leading-relaxed text-muted-foreground",children:s}):null,n.length>0?g(Be,{values:n,accent:!0}):null]})}function ut(e){let t=Oe(e.frame,e.lane),n=!e.detailOnly&&t.signalStrength?De(t.signalStrength):null,o=e.detailOnly?t.signals.slice(2):t.signals,i=e.detailOnly?t.relationLabels.slice(1):t.relationLabels,a=e.lane==="now"?"Reasoning":e.lane==="next"?"Why it sits here":"Why it stays quiet";return e.detailOnly&&o.length===0&&i.length===0&&!t.continuity?null:L("div",{className:"space-y-4",children:[e.detailOnly?null:L("div",{className:"grid gap-4 lg:grid-cols-[minmax(0,1fr)_16rem]",children:[L("div",{children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:a}),g("p",{className:"mt-1 text-sm leading-relaxed text-muted-foreground",children:t.laneReason})]}),n?L("div",{children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:"Confidence"}),g("p",{className:"mt-1 text-sm text-foreground/90",children:n})]}):null]}),o.length>0?L("div",{className:"space-y-2",children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:e.detailOnly?"More signals":"Signals"}),g(Be,{values:o})]}):null,i.length>0?L("div",{className:"space-y-2",children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:e.detailOnly?"More thread context":"Thread context"}),g(Be,{values:i,accent:!0})]}):null,t.continuity?L("div",{className:"space-y-2",children:[g("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:"Related activity"}),g("p",{className:"text-sm leading-relaxed text-muted-foreground",children:t.continuity})]}):null]})}function mt(e){return g("div",{className:"space-y-2 border-t border-border/60 pt-3",children:g(Ue,{frame:e.frame,lane:e.lane})})}import{useEffect as ft,useRef as Jt,useState as pt}from"react";import{jsx as K,jsxs as $e}from"react/jsx-runtime";function qe(e){let[t,n]=pt(!1),[o,i]=pt(""),a=Jt(null),s=fe(e.frame),d=e.pendingId===e.frame.id;if(ft(()=>{n(!1),i("")},[e.frame.id]),ft(()=>{t&&s&&a.current?.focus()},[t,s]),!s)return null;async function m(){let p=o.trim();p&&(await e.onComment(e.frame,p),i(""),n(!1))}if(!t){let p=e.triggerLabel??"Leave comment",u=e.triggerTone??"link";return u==="link"?K("button",{type:"button",className:"text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",onClick:()=>{e.onOpen?.(),n(!0)},children:p}):K(N,{label:p,tone:u,className:e.triggerFullWidth?"w-full":void 0,disabled:d,onClick:()=>{e.onOpen?.(),n(!0)}})}return $e("div",{className:e.panelClassName??"w-full space-y-2 rounded-md border border-border bg-secondary/40 p-3",children:[$e("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:["Commenting on ",M(e.frame)]}),K("div",{className:"text-[11px] text-muted-foreground",children:"Posts to the issue thread without leaving Focus."}),K("textarea",{ref:a,value:o,onChange:p=>i(p.target.value),onKeyDown:p=>{(p.metaKey||p.ctrlKey)&&p.key==="Enter"&&(p.preventDefault(),m()),p.key==="Escape"&&(p.preventDefault(),n(!1),i(""))},rows:e.rows??3,placeholder:"Add a short operator note back to the issue\u2026",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]"}),$e("div",{className:"flex items-center justify-end gap-2",children:[K(N,{label:"Cancel",tone:"secondary",disabled:d,onClick:()=>{n(!1),i("")}}),K(N,{label:d?"Posting\u2026":"Post comment",tone:"accent",disabled:d||o.trim().length===0,onClick:()=>{m()}})]})]})}import{usePluginAction as W}from"@paperclipai/plugin-sdk/ui";import{useEffect as ge,useRef as xe,useState as St}from"react";function pe(e,t){return e instanceof Error?e.message:t}function gt(e){return`Acknowledged ${e}.`}function xt(e){return pe(e,"Failed to acknowledge frame.")}function ht(){return"This frame is not backed by a Paperclip issue."}function bt(e){return`Posted a comment on ${e}.`}function vt(e){return pe(e,"Failed to post comment.")}function je(){return"This frame is not backed by a Paperclip approval."}function yt(e,t){return`${t==="approve"?"Approved":"Rejected"} ${e}.`}function wt(e){return pe(e,"Failed to submit approval decision.")}function At(e){return`Requested revision for ${e}.`}function kt(e){return pe(e,"Failed to request approval revision.")}var Xt=2e4,Yt=45e3;function Nt(e){let t=W("acknowledge-frame"),n=W("comment-on-issue"),o=W("engage-focus"),i=W("mark-attention-viewed"),a=W("record-approval-response"),s=W("set-focus-presence"),[d,m]=St(null),[p,u]=St(null),x=xe(null),y=xe(null),P=xe(i),z=xe(s);P.current=i,z.current=s,ge(()=>{if(!p)return;let c=window.setTimeout(()=>{u(null)},4e3);return()=>{window.clearTimeout(c)}},[p]),ge(()=>()=>{x.current&&window.clearTimeout(x.current)},[]),ge(()=>{let c=e.companyId;if(c)return z.current({companyId:c,presence:"present"}).catch(()=>{}),()=>{z.current({companyId:c,presence:"absent"}).catch(()=>{})}},[e.companyId]),ge(()=>{let c=e.companyId,h=e.displaySnapshot?.now;if(!c||!h){y.current=null;return}let w=`${c}:${h.interactionId}`;y.current!==w&&(y.current=w,P.current({companyId:c,taskId:h.taskId,interactionId:h.interactionId,surface:"focus"}).catch(()=>{y.current===w&&(y.current=null)}))},[e.companyId,e.displaySnapshot?.now?.interactionId]);async function he(c,h){let w=e.companyId;if(!w||e.displaySnapshot?.now?.interactionId!==c.interactionId)return;let C=h==="comment_compose"?Yt:Xt;try{await o({companyId:w,taskId:c.taskId,interactionId:c.interactionId,reason:h,durationMs:C}),e.refresh(),x.current&&window.clearTimeout(x.current),x.current=window.setTimeout(()=>{e.refresh(),x.current=null},C+150)}catch{}}async function ee(c){let h=e.companyId;if(h){m(c.id);try{await t({companyId:h,taskId:c.taskId,interactionId:c.interactionId}),u(gt(M(c))),e.refresh()}catch(w){u(xt(w))}finally{m(null)}}}async function te(c,h){let w=e.companyId,C=Pe(c);if(!w||!C||B(c)!=="issue"){u(ht());return}m(c.id);try{await n({companyId:w,taskId:c.taskId,issueId:C,body:h}),u(bt(M(c))),e.refresh()}catch(be){u(vt(be))}finally{m(null)}}async function U(c,h){let w=e.companyId,C=ce(c);if(!w||!C){u(je());return}m(c.id);try{await a({companyId:w,taskId:c.taskId,interactionId:c.interactionId,decision:h}),u(yt(M(c),h)),e.refresh()}catch(be){u(wt(be))}finally{m(null)}}async function ne(c){let h=e.companyId,w=ce(c);if(!h||!w){u(je());return}m(c.id);try{await a({companyId:h,taskId:c.taskId,interactionId:c.interactionId,decision:"request-revision"}),u(At(M(c))),e.refresh()}catch(C){u(kt(C))}finally{m(null)}}return{pendingId:d,statusOverride:p,holdNowFrame:he,acknowledgeFrame:ee,commentOnIssueFrame:te,submitApprovalDecision:U,requestApprovalRevision:ne}}import{Fragment as Ft,jsx as r,jsxs as l}from"react/jsx-runtime";function tn(e){let t=Me(e.frame,e.lane),n=e.pendingId===e.frame.id;return t==="none"?null:r("div",{className:S("flex items-center gap-2",e.compact&&"justify-end"),children:t==="approval"?l(Ft,{children:[R(e.frame)?r(N,{label:"Request revision",tone:"secondary",disabled:n,onClick:()=>{e.onRequestRevision(e.frame)}}):null,r(N,{label:n?"Submitting\u2026":"Approve",tone:"primary",disabled:n,onClick:()=>{e.onApprove(e.frame)}}),r(N,{label:"Reject",tone:"danger",disabled:n,onClick:()=>{e.onReject(e.frame)}})]}):r(N,{label:n?"Saving\u2026":"Acknowledge",tone:"accent",disabled:n,onClick:()=>{e.onAcknowledge(e.frame)}})})}function nn(e){let{frame:t,snapshotUpdatedAt:n}=e,o=Y(t,e.companyPrefix),i=R(t)?ct(e.companyPrefix):null,a=Ee(t,e.companyPrefix),s=me(t).length>0;return l("div",{className:"space-y-3 border-t border-border pt-4",children:[r(ut,{frame:t,lane:"now",detailOnly:!0}),l("div",{className:"flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",children:[r("span",{children:at(t)}),r("span",{children:de(t)}),r("span",{children:Z(_(t,n))}),Re(t)?l("span",{children:["Amount requested: ",Re(t)]}):null,Te(t)?r("span",{children:Te(t)}):null]}),s?l(Ft,{children:[r("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:"Context"}),r(lt,{frame:t})]}):null,l("div",{className:"flex flex-wrap items-center gap-3",children:[o?l("a",{href:o,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",children:[le(t),r(V,{})]}):null,a?l("a",{href:a,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",children:["Open activity",r(V,{})]}):null,i?r(v,{children:l("a",{href:i,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-xs font-medium underline underline-offset-2 hover:opacity-80",style:{color:"inherit"},children:["Review in Costs",r(V,{})]})}):null]})]})}function rn(){return l("div",{className:"flex min-h-20 items-center gap-3",children:[r(we,{size:"md"}),r("div",{className:"text-sm text-muted-foreground",children:"Nothing active right now."})]})}function on(e){let t=Me(e.frame,e.lane),n=e.pendingId===e.frame.id,o=t==="acknowledge"&&fe(e.frame);return l("div",{className:"space-y-3 rounded-md border border-border/60 bg-secondary/20 px-3 py-3",style:e.wide?{width:320,flexShrink:0,position:"sticky",top:16,alignSelf:"flex-start"}:void 0,children:[r("div",{className:"text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:"Actions"}),t==="approval"?l("div",{className:"space-y-2",children:[l("div",{className:"grid grid-cols-2 gap-2",children:[r(N,{label:n?"Submitting\u2026":"Approve",tone:"primary",disabled:n,className:"w-full",onClick:()=>{e.onApprove(e.frame)}}),r(N,{label:"Reject",tone:"danger",disabled:n,className:"w-full",onClick:()=>{e.onReject(e.frame)}})]}),R(e.frame)?r(N,{label:"Request revision",tone:"secondary",disabled:n,className:"w-full",onClick:()=>{e.onRequestRevision(e.frame)}}):null]}):t==="acknowledge"?l("div",{className:"flex flex-wrap items-center gap-2",children:[o?r(qe,{frame:e.frame,pendingId:e.pendingId,onComment:e.onComment,triggerTone:"accent",triggerLabel:"Comment",onOpen:()=>e.onEngageFocus(e.frame,"comment_compose"),rows:4,panelClassName:"w-full space-y-2 rounded-md border border-border bg-background/80 p-3"}):null,r(N,{label:n?"Saving\u2026":"Acknowledge",tone:"accent",disabled:n,onClick:()=>{e.onAcknowledge(e.frame)}})]}):null,e.itemLink?l("a",{href:e.itemLink,target:"_blank",rel:"noopener noreferrer",className:"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground underline underline-offset-2 hover:text-foreground",children:[le(e.frame),r(V,{})]}):null]})}function an(e){let t=e.snapshot.now,[n,o]=It(!1),i=Qe(1120),a=t?.id??null,s=!!t&&(e.snapshot.review?.unread.now??0)>0,d=t?Y(t,e.companyPrefix):null,m=t?X(t):null,p=t?dt(t,"now"):null,u=Le();return en(()=>{o(!1)},[a]),l("section",{className:"border border-border bg-card shadow-sm",style:t?{borderLeftWidth:3,borderLeftColor:A}:void 0,children:[l("div",{className:"flex items-center justify-between gap-3 border-b border-border bg-accent px-4 py-3 sm:px-6",children:[l("div",{className:"flex items-center gap-3",children:[r(v,{className:"text-sm leading-none",children:e.posture.glyph}),l("div",{className:"flex items-end gap-2",children:[r(v,{className:"text-base leading-none font-semibold tracking-[0.04em]",children:e.brand.wordmark}),r("span",{className:"text-[11px] leading-none text-muted-foreground",children:e.posture.label})]})]}),l("div",{className:"flex items-center gap-3 text-xs tabular-nums text-muted-foreground",children:[l("span",{className:e.counts.now>0?"font-semibold":"",children:["now ",e.counts.now>0?r(v,{children:e.counts.now}):e.counts.now]}),l("span",{className:e.counts.next>0?"font-semibold":"",children:["next ",e.counts.next>0?r(v,{children:e.counts.next}):e.counts.next]}),l("span",{className:e.counts.ambient>0?"font-semibold":"",children:["ambient ",e.counts.ambient>0?r(v,{children:e.counts.ambient}):e.counts.ambient]})]})]}),r("div",{className:"px-5 py-5 sm:px-6",children:t?l("div",{className:"space-y-5",children:[l("div",{className:i?"flex items-start gap-6":"space-y-4",children:[l("div",{className:"space-y-4",style:i?{minWidth:0,flex:1}:void 0,children:[l("div",{className:"flex items-center gap-2 text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground",children:[r("span",{children:"Now"}),r("span",{className:"normal-case tracking-normal text-muted-foreground/70",children:j(t)})]}),m?r("div",{className:"max-w-4xl text-3xl font-semibold leading-tight text-foreground",children:m}):t.summary?r("div",{className:"max-w-4xl text-3xl font-semibold leading-tight text-foreground",children:t.summary}):null,l("div",{className:"flex items-start gap-2 text-xl font-medium leading-snug text-foreground/80",children:[r(oe,{visible:s}),r("span",{children:ue(t)})]}),p?r("p",{className:"max-w-3xl text-sm leading-relaxed text-muted-foreground",children:p}):null,l("div",{className:"flex flex-wrap items-center gap-2",children:[r(q,{className:"border-border bg-secondary text-foreground/80",children:de(t)}),H(t)?r(q,{className:u.className,style:u.style,children:H(t)}):null]}),r(Ue,{frame:t,lane:"now",preferLaneReason:!!p}),r("div",{className:"flex items-center gap-4",children:r(v,{children:l("button",{type:"button",onClick:()=>{n||e.onEngageFocus(t,"show_context"),o(!n)},"aria-expanded":n,className:"flex items-center gap-1.5 text-xs font-medium transition-opacity hover:opacity-100",style:{color:"inherit"},children:[r("svg",{viewBox:"0 0 16 16",className:S("h-3 w-3 transition-transform",n&&"rotate-90"),fill:"currentColor","aria-hidden":"true",children:r("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"})}),n?"Hide context":"Show context"]})})})]}),r(on,{frame:t,lane:"now",wide:i,pendingId:e.pendingId,itemLink:d,onEngageFocus:e.onEngageFocus,onApprove:e.onApprove,onReject:e.onReject,onRequestRevision:e.onRequestRevision,onAcknowledge:e.onAcknowledge,onComment:e.onComment})]}),n?r(nn,{frame:t,snapshotUpdatedAt:e.snapshot.updatedAt,companyPrefix:e.companyPrefix}):null]}):r(rn,{})})]})}function sn(e){let{frame:t}=e,[n,o]=It(!1),i=_e(t,e.snapshot),a=Ee(t,e.companyPrefix),s=Y(t,e.companyPrefix),d=st(t),m=H(t),p=Le(),u=O(t,"next"),x=u.trim().length>0&&u!==d&&u!==Q,y=Math.max(.4,1-(e.rank-1)*.14);return l("div",{className:"border-b border-border/80 last:border-b-0",style:n?{borderLeftWidth:2,borderLeftColor:A}:void 0,children:[l("button",{type:"button",onClick:()=>o(!n),"aria-expanded":n,className:"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-accent/50 sm:px-6",children:[r(oe,{visible:i}),r("span",{className:"w-5 shrink-0",style:{opacity:y},children:r(v,{className:"text-xs font-medium tabular-nums",children:String(e.rank).padStart(2,"0")})}),l("div",{className:"min-w-0 flex-1",children:[r("div",{className:"text-sm font-medium leading-5 text-foreground",children:ue(t)}),d?r("div",{className:"mt-0.5 max-w-3xl text-xs leading-5 text-muted-foreground",children:d}):null]}),r(Je,{movement:e.movement}),m?r(q,{className:S("shrink-0",p.className),style:p.style,children:m}):null,r("span",{className:"shrink-0 text-xs text-muted-foreground",style:{opacity:.7},children:Z(_(t,e.snapshotUpdatedAt))}),r("svg",{viewBox:"0 0 16 16",className:S("h-3 w-3 shrink-0 text-muted-foreground transition-transform",n&&"rotate-90"),fill:"currentColor","aria-hidden":"true",children:r("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"})})]}),n?l("div",{className:"space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14",children:[x?r("p",{className:"text-sm leading-relaxed text-muted-foreground",children:u}):null,r(mt,{frame:t,lane:"next"}),l("div",{className:"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground",children:[r("span",{children:de(t)}),m?r(v,{children:m}):null,e.movement?r(v,{children:e.movement==="up"?"rising in queue":"falling in queue"}):null,l("span",{children:["updated ",Z(_(t,e.snapshotUpdatedAt))]}),s?r("a",{href:s,className:"font-medium underline underline-offset-2 hover:text-foreground",children:le(t)}):null,a?r("a",{href:a,className:"font-medium underline underline-offset-2 hover:text-foreground",children:"Open activity"}):null]}),l("div",{className:"flex flex-wrap items-center gap-2 pt-1",children:[r(qe,{frame:t,pendingId:e.pendingId,onComment:e.onComment}),r(tn,{frame:t,lane:"next",pendingId:e.pendingId,onApprove:e.onApprove,onReject:e.onReject,onRequestRevision:e.onRequestRevision,onAcknowledge:e.onAcknowledge})]})]}):null]})}function dn(e){return l("section",{className:"border border-border/80 bg-card",children:[r("div",{className:"border-b border-border/80 px-4 py-2.5 sm:px-6",children:r("h2",{className:"text-xs font-medium uppercase tracking-wider text-muted-foreground",children:"Next"})}),e.rows.length===0?r("div",{className:"px-4 py-4 text-sm text-muted-foreground sm:px-6",style:{opacity:.7},children:"Nothing is staged behind the current focus."}):e.rows.map((t,n)=>r(sn,{rank:n+1,frame:t.frame,movement:e.movement[t.frame.interactionId],snapshot:e.snapshot,snapshotUpdatedAt:e.snapshotUpdatedAt,companyPrefix:e.companyPrefix,pendingId:e.pendingId,onApprove:e.onApprove,onReject:e.onReject,onRequestRevision:e.onRequestRevision,onAcknowledge:e.onAcknowledge,onComment:e.onComment},t.frame.id))]})}function cn(e){let{frame:t,snapshotUpdatedAt:n}=e,o=Y(t,e.companyPrefix),i=_e(t,e.snapshot),a=l("div",{className:"flex items-center gap-3 px-4 py-2.5 sm:px-6",children:[r(oe,{visible:i,tone:"muted",reserveSpace:!0}),r("span",{className:"min-w-0 flex-1 truncate text-[13px] text-muted-foreground",children:ue(t)}),r("span",{className:"shrink-0 text-xs text-muted-foreground/80",children:j(t)}),r("span",{className:"shrink-0 text-xs text-muted-foreground/70",children:Z(_(t,n))})]});return r("div",{style:{borderBottomWidth:1,borderBottomColor:"var(--border)"},className:"last:border-b-0",children:o?r("a",{href:o,className:"block transition-colors hover:bg-accent/40",children:a}):a})}function ln(e){return l("section",{style:{borderWidth:1,borderColor:"var(--border)"},className:"mt-2 bg-card","aria-label":"Ambient attention",children:[r("div",{style:{borderBottomWidth:1,borderBottomColor:"var(--border)"},className:"px-4 py-2.5 sm:px-6",children:r("h2",{className:"text-xs font-medium uppercase tracking-wider text-muted-foreground/80",children:"Ambient"})}),e.rows.length===0?l("div",{className:"flex items-center gap-3 px-4 py-4 text-sm text-muted-foreground sm:px-6",children:[r(we,{}),r("span",{children:"Nothing in peripheral view."})]}):e.rows.map(t=>r(cn,{frame:t.frame,snapshot:e.snapshot,snapshotUpdatedAt:e.snapshotUpdatedAt,companyPrefix:e.companyPrefix},t.frame.id))]})}function xr(e){let t=e.context.companyId,n=ie(),o=Se(e.context.companyPrefix),i=ae(t),a=i.snapshot,s=i.loading,d=i.error;if(!t)return r(D,{title:`Open ${n.wordmark}`,body:`Open a company to see ${n.wordmark}.`});if(s)return r(Ge,{label:n.loadingLabel});if(d)return r(D,{title:"Plugin error",body:d.message});if(!a)return r(D,{title:n.wordmark,body:n.headingEmptyState});let m=Ne(a),p=a.now?X(a.now)??a.now.title:null,u=Xe(a),x=m.label==="busy"?{borderLeftWidth:2,borderLeftColor:A}:m.label==="elevated"?{borderLeftWidth:2,borderLeftColor:F}:void 0;return l("a",{href:o,className:"block border border-border bg-card px-4 py-3 shadow-sm transition-colors hover:bg-accent/40",style:x,children:[l("div",{className:"flex items-start gap-2 text-xs",children:[r(v,{className:"pt-0.5 text-sm",children:m.glyph}),r("div",{className:"min-w-0",children:l("div",{className:"flex items-end gap-2",children:[r(v,{className:"font-semibold tracking-[0.04em]",children:n.wordmark}),r(v,{className:"text-[11px]",children:m.label}),u?r(v,{className:"text-[11px]",children:u}):null]})}),l("span",{className:"ml-auto tabular-nums text-muted-foreground",children:["now ",a.counts.now," \xB7 next ",a.counts.next," \xB7 ambient ",a.counts.ambient]})]}),p?r("div",{className:"mt-2 text-sm font-medium leading-5 text-foreground",style:{display:"-webkit-box",WebkitLineClamp:2,WebkitBoxOrient:"vertical",overflow:"hidden"},children:p}):r("div",{className:"mt-2 text-sm text-muted-foreground",children:"No active interruption right now."})]})}function hr({context:e}){let t=e.companyId,n=ie(),o=Se(e.companyPrefix),i=typeof window<"u"&&window.location.pathname===o,s=ae(t).snapshot,d=s?Ie(s):0,m=Ye(s);return l("a",{href:o,"aria-current":i?"page":void 0,className:S("flex items-center gap-2.5 px-3 py-2 text-[13px] font-medium transition-colors",i?"bg-accent text-foreground":"text-foreground/80 hover:bg-accent/50 hover:text-foreground"),children:[l("span",{className:"relative shrink-0","aria-hidden":"true",children:[l("svg",{viewBox:"0 0 24 24",className:"h-4 w-4",fill:"none",stroke:"currentColor",strokeWidth:"1.9",strokeLinecap:"round",strokeLinejoin:"round",children:[r("circle",{cx:"12",cy:"12",r:"9"}),r("path",{d:"m14.3 8 5.2 9"}),r("path",{d:"M9.7 8h10.4"}),r("path",{d:"m7.4 12 5.2-9"}),r("path",{d:"m9.7 16-5.2-9"}),r("path",{d:"M14.3 16H3.9"}),r("path",{d:"m16.6 12-5.2 9"})]}),m>0?r("span",{className:"absolute -right-1 -top-1 h-2 w-2 rounded-full",style:{backgroundColor:A}}):null]}),r("span",{className:"flex-1 truncate",children:n.wordmark}),d>0?r("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:re,children:d}):m>0?r("span",{className:"inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",style:{color:A,borderColor:F,backgroundColor:"transparent"},children:m}):null]})}function br(e){let t=e.context.companyId,n=ie(),o=ae(t),i=o.snapshot,a=o.loading,s=o.error,d=i,m=d&&s?`Sync issue: ${s.message}`:null,p=He(()=>d?Ne(d):null,[d]),u=He(()=>d?d.next.map(c=>({frame:c,lane:"next"})):[],[d]),x=He(()=>d?d.ambient.map(c=>({frame:c,lane:"ambient"})):[],[d]),y=et(u.map(c=>c.frame)),{pendingId:P,statusOverride:z,holdNowFrame:he,acknowledgeFrame:ee,commentOnIssueFrame:te,submitApprovalDecision:U,requestApprovalRevision:ne}=Nt({companyId:t,displaySnapshot:d,refresh:o.refresh});return t?!d&&a?r(Ve,{label:"Loading attention center"}):!d&&s?r(D,{title:"Plugin error",body:s.message}):!d||!p?r(D,{title:`No ${n.wordmark} State Yet`,body:`No ${n.key==="focus"?"focus":"attention"} state has been captured for this company yet.`}):l("div",{className:"space-y-4",children:[r(an,{snapshot:d,posture:p,brand:n,companyPrefix:e.context.companyPrefix,counts:d.counts,pendingId:P,onEngageFocus:he,onApprove:c=>U(c,"approve"),onReject:c=>U(c,"reject"),onRequestRevision:ne,onAcknowledge:ee,onComment:te}),r(dn,{snapshot:d,rows:u,movement:y,snapshotUpdatedAt:d.updatedAt,companyPrefix:e.context.companyPrefix,pendingId:P,onApprove:c=>U(c,"approve"),onReject:c=>U(c,"reject"),onRequestRevision:ne,onAcknowledge:ee,onComment:te}),r(ln,{snapshot:d,rows:x,snapshotUpdatedAt:d.updatedAt,companyPrefix:e.context.companyPrefix}),r(Ze,{message:z??m})]}):r(D,{title:`Open ${n.wordmark}`,body:`Select a company to open ${n.wordmark}.`})}export{br as AttentionPage,hr as AttentionSidebarLink,xr as DashboardWidget};
|