@tomismeta/paperclip-aperture 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/manifest.js +3 -1
- package/dist/ui/index.js +118 -95
- package/dist/worker.js +6309 -4264
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ It is designed as a live attention layer, not an inbox clone:
|
|
|
20
20
|
- `now` shows the single most interrupting item
|
|
21
21
|
- `next` stages the queue behind it
|
|
22
22
|
- `ambient` keeps low-pressure awareness visible without demanding action
|
|
23
|
+
- unread state is secondary to action pressure, so Focus stays judgment-first instead of becoming a second inbox
|
|
23
24
|
|
|
24
25
|
Put differently:
|
|
25
26
|
|
package/dist/manifest.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const manifest = {
|
|
2
2
|
id: "tomismeta.paperclip-aperture",
|
|
3
3
|
apiVersion: 1,
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.4.1",
|
|
5
5
|
displayName: "Paperclip Aperture",
|
|
6
6
|
description: "The live attention layer for Paperclip, powered by Aperture's deterministic attention engine.",
|
|
7
7
|
author: "@tomismeta",
|
|
@@ -13,8 +13,10 @@ const manifest = {
|
|
|
13
13
|
"issue.comments.read",
|
|
14
14
|
"issue.comments.create",
|
|
15
15
|
"issue.documents.read",
|
|
16
|
+
"activity.log.write",
|
|
16
17
|
"plugin.state.read",
|
|
17
18
|
"plugin.state.write",
|
|
19
|
+
"telemetry.track",
|
|
18
20
|
"ui.dashboardWidget.register",
|
|
19
21
|
"ui.page.register",
|
|
20
22
|
"ui.sidebar.register"
|
package/dist/ui/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/ui/index.tsx
|
|
2
2
|
import {
|
|
3
3
|
usePluginAction,
|
|
4
|
-
usePluginData
|
|
4
|
+
usePluginData,
|
|
5
|
+
usePluginStream
|
|
5
6
|
} from "@paperclipai/plugin-sdk/ui";
|
|
6
7
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
7
8
|
|
|
@@ -40,14 +41,14 @@ function frameSortScore(frame, lane) {
|
|
|
40
41
|
const toneWeight = frame.tone === "critical" ? 40 : frame.tone === "focused" ? 25 : 5;
|
|
41
42
|
const consequenceWeight = frame.consequence === "high" ? 30 : frame.consequence === "medium" ? 15 : 0;
|
|
42
43
|
const modeWeight = frame.mode === "approval" ? 12 : frame.mode === "choice" ? 8 : 0;
|
|
43
|
-
const laneWeight = lane === "
|
|
44
|
+
const laneWeight = lane === "now" ? 20 : lane === "next" ? 10 : 0;
|
|
44
45
|
const budgetWeight = isBudgetOverride(frame) ? 10 : 0;
|
|
45
46
|
return toneWeight + consequenceWeight + modeWeight + laneWeight + budgetWeight + frameSemanticWeight(frame);
|
|
46
47
|
}
|
|
47
48
|
function emptyLaneCounts() {
|
|
48
49
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
now: 0,
|
|
51
|
+
next: 0,
|
|
51
52
|
ambient: 0,
|
|
52
53
|
total: 0
|
|
53
54
|
};
|
|
@@ -73,10 +74,10 @@ function isFrameUnread(frame, snapshotUpdatedAt, review) {
|
|
|
73
74
|
}
|
|
74
75
|
function calculateUnreadCounts(snapshot, review) {
|
|
75
76
|
const unread = emptyLaneCounts();
|
|
76
|
-
if (snapshot.
|
|
77
|
-
unread.
|
|
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;
|
|
78
79
|
unread.ambient = snapshot.ambient.filter((frame) => isFrameUnread(frame, snapshot.updatedAt, review)).length;
|
|
79
|
-
unread.total = unread.
|
|
80
|
+
unread.total = unread.now + unread.next + unread.ambient;
|
|
80
81
|
return unread;
|
|
81
82
|
}
|
|
82
83
|
function attachReviewState(snapshot, review) {
|
|
@@ -92,33 +93,33 @@ function mergeStoredFrames(snapshot, companyId, candidates, review, nowIso = (/*
|
|
|
92
93
|
const base = snapshot ?? {
|
|
93
94
|
companyId,
|
|
94
95
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
now: null,
|
|
97
|
+
next: [],
|
|
97
98
|
ambient: [],
|
|
98
99
|
counts: emptyLaneCounts()
|
|
99
100
|
};
|
|
100
101
|
const candidateTaskIds = new Set(candidates.map((candidate) => candidate.frame.taskId));
|
|
101
102
|
const baseEntries = [
|
|
102
|
-
...base.
|
|
103
|
-
...base.
|
|
103
|
+
...base.now ? [{ frame: base.now, lane: "now" }] : [],
|
|
104
|
+
...base.next.map((frame) => ({ frame, lane: "next" })),
|
|
104
105
|
...base.ambient.map((frame) => ({ frame, lane: "ambient" }))
|
|
105
106
|
].filter((candidate) => !candidateTaskIds.has(candidate.frame.taskId));
|
|
106
107
|
const filtered = [...baseEntries, ...candidates].filter((candidate) => !isFrameSuppressed(candidate.frame, base.updatedAt, review));
|
|
107
108
|
const actionable = filtered.filter((candidate) => candidate.lane !== "ambient").sort((left, right) => compareCandidates(left, right, base.updatedAt));
|
|
108
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));
|
|
109
|
-
const
|
|
110
|
-
const
|
|
110
|
+
const now = actionable.shift()?.frame ?? null;
|
|
111
|
+
const next = actionable.map((candidate) => candidate.frame);
|
|
111
112
|
const ambientFrames = ambient.map((candidate) => candidate.frame);
|
|
112
113
|
return attachReviewState({
|
|
113
114
|
...base,
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
now,
|
|
116
|
+
next,
|
|
116
117
|
ambient: ambientFrames,
|
|
117
118
|
counts: {
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
now: now ? 1 : 0,
|
|
120
|
+
next: next.length,
|
|
120
121
|
ambient: ambientFrames.length,
|
|
121
|
-
total: (
|
|
122
|
+
total: (now ? 1 : 0) + next.length + ambientFrames.length
|
|
122
123
|
}
|
|
123
124
|
}, review);
|
|
124
125
|
}
|
|
@@ -162,10 +163,10 @@ function relationHintLabel(hint) {
|
|
|
162
163
|
}
|
|
163
164
|
function laneReason(lane) {
|
|
164
165
|
switch (lane) {
|
|
165
|
-
case "
|
|
166
|
+
case "now":
|
|
166
167
|
return "This is the most urgent item in the queue right now.";
|
|
167
|
-
case "
|
|
168
|
-
return "This is
|
|
168
|
+
case "next":
|
|
169
|
+
return "This is staged behind the current top item.";
|
|
169
170
|
case "ambient":
|
|
170
171
|
return "This is visible for awareness without needing action yet.";
|
|
171
172
|
}
|
|
@@ -237,6 +238,7 @@ function decisionContextItem(value) {
|
|
|
237
238
|
|
|
238
239
|
// src/aperture/attention-language.ts
|
|
239
240
|
var GENERIC_QUEUED_JUDGMENT = "Important enough to keep visible, but not enough to displace now.";
|
|
241
|
+
var GENERIC_NEXT_JUDGMENT = GENERIC_QUEUED_JUDGMENT;
|
|
240
242
|
function approvalBlockingSummary(budgetOverride) {
|
|
241
243
|
return budgetOverride ? "Budget controls are blocking work until a board decision lands." : "A board decision is blocking work in Paperclip.";
|
|
242
244
|
}
|
|
@@ -244,14 +246,14 @@ function approvalBlockingWhyNow(budgetOverride) {
|
|
|
244
246
|
return budgetOverride ? "Budget controls are blocking work until a board decision lands." : "Paperclip is waiting on a human approval before work can continue.";
|
|
245
247
|
}
|
|
246
248
|
function genericJudgmentLine(frame, lane) {
|
|
247
|
-
if (lane === "
|
|
249
|
+
if (lane === "now") {
|
|
248
250
|
if (isBudgetOverride(frame)) return approvalBlockingSummary(true);
|
|
249
251
|
if (frame.mode === "approval") return "A human decision is blocking work right now.";
|
|
250
252
|
if (frame.tone === "critical") return "This surfaced because it can displace the operator now.";
|
|
251
253
|
return "This is the clearest current operator focus.";
|
|
252
254
|
}
|
|
253
255
|
if (isBudgetOverride(frame)) return "Budget review is staged behind the current focus.";
|
|
254
|
-
if (lane === "
|
|
256
|
+
if (lane === "next") return GENERIC_NEXT_JUDGMENT;
|
|
255
257
|
return "Useful for awareness, but not interrupting.";
|
|
256
258
|
}
|
|
257
259
|
|
|
@@ -260,12 +262,12 @@ function createEmptySnapshot(companyId) {
|
|
|
260
262
|
return {
|
|
261
263
|
companyId,
|
|
262
264
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
263
|
-
|
|
264
|
-
|
|
265
|
+
now: null,
|
|
266
|
+
next: [],
|
|
265
267
|
ambient: [],
|
|
266
268
|
counts: {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
now: 0,
|
|
270
|
+
next: 0,
|
|
269
271
|
ambient: 0,
|
|
270
272
|
total: 0
|
|
271
273
|
}
|
|
@@ -357,7 +359,7 @@ function mergeSnapshotWithApprovals(snapshot, companyId, approvals, review) {
|
|
|
357
359
|
const approvalFrames = actionableApprovalRecords(approvals).map(approvalRecordToFrame);
|
|
358
360
|
const base = snapshot ?? createEmptySnapshot(companyId);
|
|
359
361
|
const merged = mergeStoredFrames(base, companyId, [
|
|
360
|
-
...approvalFrames.map((frame, index) => ({ frame, lane: index === 0 ? "
|
|
362
|
+
...approvalFrames.map((frame, index) => ({ frame, lane: index === 0 ? "now" : "next" }))
|
|
361
363
|
], review);
|
|
362
364
|
return merged;
|
|
363
365
|
}
|
|
@@ -411,6 +413,7 @@ var ACCENT_COLOR = "#007ACC";
|
|
|
411
413
|
var ACCENT_BG = `${ACCENT_COLOR}14`;
|
|
412
414
|
var ACCENT_BORDER = `${ACCENT_COLOR}33`;
|
|
413
415
|
var ACCENT_BG_STYLE = { backgroundColor: ACCENT_COLOR };
|
|
416
|
+
var ATTENTION_UPDATES_STREAM = "attention-updates";
|
|
414
417
|
function currentSurfaceBrand() {
|
|
415
418
|
return {
|
|
416
419
|
key: "focus",
|
|
@@ -533,17 +536,21 @@ function requestDescriptor(frame) {
|
|
|
533
536
|
return driver ? `${sourceLabel(frame)} \xB7 ${driver}` : sourceLabel(frame);
|
|
534
537
|
}
|
|
535
538
|
function postureForSnapshot(snapshot) {
|
|
536
|
-
const current = snapshot.
|
|
537
|
-
if (current?.tone === "critical" || current?.consequence === "high" || snapshot.counts.
|
|
539
|
+
const current = snapshot.now;
|
|
540
|
+
if (current?.tone === "critical" || current?.consequence === "high" || snapshot.counts.now + snapshot.counts.next >= 3) {
|
|
538
541
|
return { glyph: "\u25CF", label: "busy" };
|
|
539
542
|
}
|
|
540
|
-
if (snapshot.counts.
|
|
543
|
+
if (snapshot.counts.now > 0 || snapshot.counts.next > 0 || snapshot.counts.ambient > 0) {
|
|
541
544
|
return { glyph: "\u25D0", label: "elevated" };
|
|
542
545
|
}
|
|
543
546
|
return { glyph: "\u25CB", label: "calm" };
|
|
544
547
|
}
|
|
545
548
|
function actionableCount(snapshot) {
|
|
546
|
-
return (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;
|
|
547
554
|
}
|
|
548
555
|
function judgmentLine(frame, lane) {
|
|
549
556
|
if (frame.provenance?.whyNow) return frame.provenance.whyNow;
|
|
@@ -554,19 +561,19 @@ function nextPrimaryText(frame) {
|
|
|
554
561
|
if (recommendedMove) return recommendedMove;
|
|
555
562
|
const summary = frame.summary?.trim();
|
|
556
563
|
if (summary) return summary;
|
|
557
|
-
const fallback = judgmentLine(frame, "
|
|
564
|
+
const fallback = judgmentLine(frame, "next");
|
|
558
565
|
return fallback === GENERIC_QUEUED_JUDGMENT ? null : fallback;
|
|
559
566
|
}
|
|
560
567
|
function supportingLine(frame, lane) {
|
|
561
568
|
const owner = actionOwnerValue(frame);
|
|
562
569
|
const target = blockingTargetValue(frame);
|
|
563
|
-
if (target && lane === "
|
|
570
|
+
if (target && lane === "now" && entityTypeFromFrame(frame) === "issue" && driverLabel(frame) === "review required") {
|
|
564
571
|
return issueBlocksTargetLine(target);
|
|
565
572
|
}
|
|
566
|
-
if (owner && lane === "
|
|
573
|
+
if (owner && lane === "now" && entityTypeFromFrame(frame) === "issue") {
|
|
567
574
|
return issueNeedsActionFromLine(owner);
|
|
568
575
|
}
|
|
569
|
-
if (lane === "
|
|
576
|
+
if (lane === "now") {
|
|
570
577
|
const recommendedMove = recommendedMoveValue(frame)?.trim();
|
|
571
578
|
const summary = frame.summary?.trim();
|
|
572
579
|
const judgment2 = judgmentLine(frame, lane).trim();
|
|
@@ -639,21 +646,21 @@ function applyLocalSuppressions(snapshot, suppressedAtByTaskId) {
|
|
|
639
646
|
if (!suppressedAt) return true;
|
|
640
647
|
return frameUpdatedAt(frame, snapshot.updatedAt).localeCompare(suppressedAt) > 0;
|
|
641
648
|
};
|
|
642
|
-
const
|
|
649
|
+
const next = snapshot.next.filter(keepFrame);
|
|
643
650
|
const ambient = snapshot.ambient.filter(keepFrame);
|
|
644
|
-
const
|
|
645
|
-
const
|
|
646
|
-
const
|
|
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);
|
|
647
654
|
return {
|
|
648
655
|
...snapshot,
|
|
649
|
-
|
|
650
|
-
|
|
656
|
+
now,
|
|
657
|
+
next: nextFrames,
|
|
651
658
|
ambient,
|
|
652
659
|
counts: {
|
|
653
|
-
|
|
654
|
-
|
|
660
|
+
now: now ? 1 : 0,
|
|
661
|
+
next: nextFrames.length,
|
|
655
662
|
ambient: ambient.length,
|
|
656
|
-
total: (
|
|
663
|
+
total: (now ? 1 : 0) + nextFrames.length + ambient.length
|
|
657
664
|
}
|
|
658
665
|
};
|
|
659
666
|
}
|
|
@@ -924,7 +931,16 @@ function useQueueMovement(frames, ttlMs = 3500) {
|
|
|
924
931
|
function useAttentionModel(companyId) {
|
|
925
932
|
const displayQuery = usePluginData("attention-display", companyId ? { companyId } : void 0);
|
|
926
933
|
const approvalsQuery = usePendingApprovals(companyId);
|
|
934
|
+
const updates = usePluginStream(
|
|
935
|
+
ATTENTION_UPDATES_STREAM,
|
|
936
|
+
companyId ? { companyId } : void 0
|
|
937
|
+
);
|
|
927
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]);
|
|
928
944
|
const snapshot = useMemo(
|
|
929
945
|
() => companyId ? mergeSnapshotWithApprovals(displayQuery.data?.snapshot ?? null, companyId, approvalsQuery.data, displayQuery.data?.reviewState ?? null) : null,
|
|
930
946
|
[displayQuery.data, approvalsQuery.data, companyId]
|
|
@@ -1078,10 +1094,10 @@ function InlineExplainability(props) {
|
|
|
1078
1094
|
...explanation.signals.slice(0, 2),
|
|
1079
1095
|
...explanation.relationLabels.slice(0, 1)
|
|
1080
1096
|
];
|
|
1081
|
-
const label = props.lane === "
|
|
1097
|
+
const label = props.lane === "next" ? "Why next" : props.lane === "ambient" ? "Why ambient" : "Why now";
|
|
1082
1098
|
const whyNow = explanation.whyNow ?? judgmentLine(props.frame, props.lane);
|
|
1083
|
-
const primaryLine = props.lane === "
|
|
1084
|
-
const secondaryLine = 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;
|
|
1085
1101
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-l-2 pl-4", style: { borderColor: ACCENT_COLOR }, children: [
|
|
1086
1102
|
/* @__PURE__ */ jsx("div", { className: "text-[11px] font-medium uppercase tracking-[0.16em] text-muted-foreground", children: label }),
|
|
1087
1103
|
/* @__PURE__ */ jsx("p", { className: "max-w-3xl text-sm leading-relaxed text-foreground/90", children: primaryLine }),
|
|
@@ -1094,7 +1110,7 @@ function ExplainabilityPanel(props) {
|
|
|
1094
1110
|
const strength = !props.detailOnly && explanation.signalStrength ? signalStrengthLabel(explanation.signalStrength) : null;
|
|
1095
1111
|
const signalValues = props.detailOnly ? explanation.signals.slice(2) : explanation.signals;
|
|
1096
1112
|
const relationValues = props.detailOnly ? explanation.relationLabels.slice(1) : explanation.relationLabels;
|
|
1097
|
-
const reasoningLabel = props.lane === "
|
|
1113
|
+
const reasoningLabel = props.lane === "now" ? "Reasoning" : props.lane === "next" ? "Why it sits here" : "Why it stays quiet";
|
|
1098
1114
|
if (props.detailOnly && signalValues.length === 0 && relationValues.length === 0 && !explanation.continuity) {
|
|
1099
1115
|
return null;
|
|
1100
1116
|
}
|
|
@@ -1231,7 +1247,7 @@ function NowDetails(props) {
|
|
|
1231
1247
|
const activityLink = activityHref(frame, props.companyPrefix);
|
|
1232
1248
|
const hasContext = visibleContextItems(frame).length > 0;
|
|
1233
1249
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3 border-t border-border pt-4", children: [
|
|
1234
|
-
/* @__PURE__ */ jsx(ExplainabilityPanel, { frame, lane: "
|
|
1250
|
+
/* @__PURE__ */ jsx(ExplainabilityPanel, { frame, lane: "now", detailOnly: true }),
|
|
1235
1251
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1236
1252
|
/* @__PURE__ */ jsx("span", { children: requestDescriptor(frame) }),
|
|
1237
1253
|
/* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
|
|
@@ -1447,14 +1463,14 @@ function NowActionRail(props) {
|
|
|
1447
1463
|
);
|
|
1448
1464
|
}
|
|
1449
1465
|
function NowPane(props) {
|
|
1450
|
-
const frame = props.snapshot.
|
|
1466
|
+
const frame = props.snapshot.now;
|
|
1451
1467
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
1452
1468
|
const wideLayout = useWideLayout(1120);
|
|
1453
1469
|
const activeFrameId = frame?.id ?? null;
|
|
1454
|
-
const activeUnread = !!frame && (props.snapshot.review?.unread.
|
|
1470
|
+
const activeUnread = !!frame && (props.snapshot.review?.unread.now ?? 0) > 0;
|
|
1455
1471
|
const itemLink = frame ? itemHref(frame, props.companyPrefix) : null;
|
|
1456
1472
|
const recommendedMove = frame ? recommendedMoveValue(frame) : null;
|
|
1457
|
-
const helperLine = frame ? supportingLine(frame, "
|
|
1473
|
+
const helperLine = frame ? supportingLine(frame, "now") : null;
|
|
1458
1474
|
const driverBadge = driverBadgeStyle();
|
|
1459
1475
|
useEffect(() => {
|
|
1460
1476
|
setDetailsOpen(false);
|
|
@@ -1474,13 +1490,13 @@ function NowPane(props) {
|
|
|
1474
1490
|
] })
|
|
1475
1491
|
] }),
|
|
1476
1492
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs tabular-nums text-muted-foreground", children: [
|
|
1477
|
-
/* @__PURE__ */ jsxs("span", { className: props.counts.
|
|
1493
|
+
/* @__PURE__ */ jsxs("span", { className: props.counts.now > 0 ? "font-semibold" : "", children: [
|
|
1478
1494
|
"now ",
|
|
1479
|
-
props.counts.
|
|
1495
|
+
props.counts.now > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.now }) : props.counts.now
|
|
1480
1496
|
] }),
|
|
1481
|
-
/* @__PURE__ */ jsxs("span", { className: props.counts.
|
|
1497
|
+
/* @__PURE__ */ jsxs("span", { className: props.counts.next > 0 ? "font-semibold" : "", children: [
|
|
1482
1498
|
"next ",
|
|
1483
|
-
props.counts.
|
|
1499
|
+
props.counts.next > 0 ? /* @__PURE__ */ jsx(Accent, { children: props.counts.next }) : props.counts.next
|
|
1484
1500
|
] }),
|
|
1485
1501
|
/* @__PURE__ */ jsxs("span", { className: props.counts.ambient > 0 ? "font-semibold" : "", children: [
|
|
1486
1502
|
"ambient ",
|
|
@@ -1509,7 +1525,7 @@ function NowPane(props) {
|
|
|
1509
1525
|
/* @__PURE__ */ jsx(Badge, { className: "border-border bg-secondary text-foreground/80", children: impactLabel(frame) }),
|
|
1510
1526
|
driverLabel(frame) ? /* @__PURE__ */ jsx(Badge, { className: driverBadge.className, style: driverBadge.style, children: driverLabel(frame) }) : null
|
|
1511
1527
|
] }),
|
|
1512
|
-
/* @__PURE__ */ jsx(InlineExplainability, { frame, lane: "
|
|
1528
|
+
/* @__PURE__ */ jsx(InlineExplainability, { frame, lane: "now", preferLaneReason: !!helperLine }),
|
|
1513
1529
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ jsx(Accent, { children: /* @__PURE__ */ jsxs(
|
|
1514
1530
|
"button",
|
|
1515
1531
|
{
|
|
@@ -1538,7 +1554,7 @@ function NowPane(props) {
|
|
|
1538
1554
|
NowActionRail,
|
|
1539
1555
|
{
|
|
1540
1556
|
frame,
|
|
1541
|
-
lane: "
|
|
1557
|
+
lane: "now",
|
|
1542
1558
|
wide: wideLayout,
|
|
1543
1559
|
pendingId: props.pendingId,
|
|
1544
1560
|
itemLink,
|
|
@@ -1574,7 +1590,7 @@ function NextRow(props) {
|
|
|
1574
1590
|
const secondaryText = nextPrimaryText(frame);
|
|
1575
1591
|
const driver = driverLabel(frame);
|
|
1576
1592
|
const driverBadge = driverBadgeStyle();
|
|
1577
|
-
const detailText = judgmentLine(frame, "
|
|
1593
|
+
const detailText = judgmentLine(frame, "next");
|
|
1578
1594
|
const showDetailText = detailText.trim().length > 0 && detailText !== secondaryText && detailText !== GENERIC_QUEUED_JUDGMENT;
|
|
1579
1595
|
const rankOpacity = Math.max(0.4, 1 - (props.rank - 1) * 0.14);
|
|
1580
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: [
|
|
@@ -1610,7 +1626,7 @@ function NextRow(props) {
|
|
|
1610
1626
|
),
|
|
1611
1627
|
expanded ? /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-4 pl-12 sm:px-6 sm:pl-14", children: [
|
|
1612
1628
|
showDetailText ? /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed text-muted-foreground", children: detailText }) : null,
|
|
1613
|
-
/* @__PURE__ */ jsx(ExplainabilityStrip, { frame, lane: "
|
|
1629
|
+
/* @__PURE__ */ jsx(ExplainabilityStrip, { frame, lane: "next" }),
|
|
1614
1630
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground", children: [
|
|
1615
1631
|
/* @__PURE__ */ jsx("span", { children: impactLabel(frame) }),
|
|
1616
1632
|
driver ? /* @__PURE__ */ jsx(Accent, { children: driver }) : null,
|
|
@@ -1642,7 +1658,7 @@ function NextRow(props) {
|
|
|
1642
1658
|
FrameActions,
|
|
1643
1659
|
{
|
|
1644
1660
|
frame,
|
|
1645
|
-
lane: "
|
|
1661
|
+
lane: "next",
|
|
1646
1662
|
pendingId: props.pendingId,
|
|
1647
1663
|
onApprove: props.onApprove,
|
|
1648
1664
|
onReject: props.onReject,
|
|
@@ -1720,7 +1736,8 @@ function DashboardWidget(props) {
|
|
|
1720
1736
|
if (error) return /* @__PURE__ */ jsx(MessageCard, { title: "Plugin error", body: error.message });
|
|
1721
1737
|
if (!data) return /* @__PURE__ */ jsx(MessageCard, { title: brand.wordmark, body: brand.headingEmptyState });
|
|
1722
1738
|
const posture = postureForSnapshot(data);
|
|
1723
|
-
const leadingText = data.
|
|
1739
|
+
const leadingText = data.now ? recommendedMoveValue(data.now) ?? data.now.title : null;
|
|
1740
|
+
const actionable = actionableLabel(data);
|
|
1724
1741
|
const borderStyle = posture.label === "busy" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_COLOR } : posture.label === "elevated" ? { borderLeftWidth: 2, borderLeftColor: ACCENT_BORDER } : void 0;
|
|
1725
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: [
|
|
1726
1743
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 text-xs", children: [
|
|
@@ -1728,16 +1745,13 @@ function DashboardWidget(props) {
|
|
|
1728
1745
|
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
|
|
1729
1746
|
/* @__PURE__ */ jsx(Accent, { className: "font-semibold tracking-[0.04em]", children: brand.wordmark }),
|
|
1730
1747
|
/* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: posture.label }),
|
|
1731
|
-
|
|
1732
|
-
"new ",
|
|
1733
|
-
unreadCount(data)
|
|
1734
|
-
] }) : null
|
|
1748
|
+
actionable ? /* @__PURE__ */ jsx(Accent, { className: "text-[11px]", children: actionable }) : null
|
|
1735
1749
|
] }) }),
|
|
1736
1750
|
/* @__PURE__ */ jsxs("span", { className: "ml-auto tabular-nums text-muted-foreground", children: [
|
|
1737
1751
|
"now ",
|
|
1738
|
-
data.counts.
|
|
1752
|
+
data.counts.now,
|
|
1739
1753
|
" \xB7 next ",
|
|
1740
|
-
data.counts.
|
|
1754
|
+
data.counts.next,
|
|
1741
1755
|
" \xB7 ambient ",
|
|
1742
1756
|
data.counts.ambient
|
|
1743
1757
|
] })
|
|
@@ -1776,34 +1790,43 @@ function AttentionSidebarLink({ context }) {
|
|
|
1776
1790
|
isActive ? "bg-accent text-foreground" : "text-foreground/80 hover:bg-accent/50 hover:text-foreground"
|
|
1777
1791
|
),
|
|
1778
1792
|
children: [
|
|
1779
|
-
/* @__PURE__ */
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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
|
+
] }),
|
|
1800
1823
|
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: brand.wordmark }),
|
|
1801
|
-
|
|
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(
|
|
1802
1825
|
"span",
|
|
1803
1826
|
{
|
|
1804
1827
|
className: "inline-flex items-center justify-center rounded-full border px-1.5 py-0.5 text-xs leading-none font-medium",
|
|
1805
1828
|
style: { color: ACCENT_COLOR, borderColor: ACCENT_BORDER, backgroundColor: "transparent" },
|
|
1806
|
-
children:
|
|
1829
|
+
children: unread
|
|
1807
1830
|
}
|
|
1808
1831
|
) : null
|
|
1809
1832
|
]
|
|
@@ -1847,7 +1870,7 @@ function AttentionPage(props) {
|
|
|
1847
1870
|
const backgroundErrorMessage = displaySnapshot && error ? `Sync issue: ${error.message}` : null;
|
|
1848
1871
|
const posture = useMemo(() => displaySnapshot ? postureForSnapshot(displaySnapshot) : null, [displaySnapshot]);
|
|
1849
1872
|
const nextRows = useMemo(
|
|
1850
|
-
() => displaySnapshot ? displaySnapshot.
|
|
1873
|
+
() => displaySnapshot ? displaySnapshot.next.map((frame) => ({ frame, lane: "next" })) : [],
|
|
1851
1874
|
[displaySnapshot]
|
|
1852
1875
|
);
|
|
1853
1876
|
const ambientRows = useMemo(
|
|
@@ -1860,8 +1883,8 @@ function AttentionPage(props) {
|
|
|
1860
1883
|
setLocalSuppressions((current) => {
|
|
1861
1884
|
const nextEntries = Object.entries(current).filter(([taskId, suppressedAt]) => {
|
|
1862
1885
|
const frames = [
|
|
1863
|
-
...snapshot.
|
|
1864
|
-
...snapshot.
|
|
1886
|
+
...snapshot.now ? [snapshot.now] : [],
|
|
1887
|
+
...snapshot.next,
|
|
1865
1888
|
...snapshot.ambient
|
|
1866
1889
|
];
|
|
1867
1890
|
const matching = frames.filter((frame) => frame.taskId === taskId);
|