@knpkv/herdr-work 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +7 -0
- package/dist/errors.js.map +1 -1
- package/dist/model.d.ts +13 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +11 -0
- package/dist/model.js.map +1 -1
- package/dist/service.d.ts +9 -2
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +2 -0
- package/dist/service.js.map +1 -1
- package/dist/store.d.ts +11 -12
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +83 -22
- package/dist/store.js.map +1 -1
- package/dist/view.d.ts +6 -1
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +194 -105
- package/dist/view.js.map +1 -1
- package/dist/work-triage.d.ts +3 -1
- package/dist/work-triage.d.ts.map +1 -1
- package/dist/work-triage.js +3 -6
- package/dist/work-triage.js.map +1 -1
- package/package.json +4 -4
- package/src/styles.css +382 -46
package/dist/view.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button, StateLabel, Text } from "@knpkv/rly/primitives";
|
|
3
|
-
import {
|
|
3
|
+
import { Icon } from "@knpkv/rly/foundations";
|
|
4
|
+
import { DecisionBar, Hero, HeroWord, Region } from "@knpkv/rly/patterns";
|
|
4
5
|
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import { activityShownOf
|
|
6
|
+
import { activityShownOf } from "./model.js";
|
|
6
7
|
import { decodeWorkBoardNavigationGoal, encodeWorkBoardNavigationGoal } from "./navigation.js";
|
|
7
8
|
import { workRequestClockText, workRequestDecidability } from "./request-decision.js";
|
|
8
9
|
import { displayStateOf, observedFor } from "./display-state.js";
|
|
9
|
-
import { workTriage, workTriageGroups, workTriageGroupTitle, workTriageSentence } from "./work-triage.js";
|
|
10
|
+
import { workGoalFinishedAt, workTriage, workTriageGroups, workTriageGroupTitle, workTriageSentence } from "./work-triage.js";
|
|
10
11
|
/** The request clock's wording ("52s", "4m 12s", "11m", "expiring"), shared with hosts that show the same clocks. */
|
|
11
12
|
export { workRequestClockText } from "./request-decision.js";
|
|
12
13
|
const windows = ["now", "day", "week", "month"];
|
|
13
|
-
const stageOrder = ["local", "review", "pull_request", "merged", "deployed"];
|
|
14
14
|
const initialVisibleGoalCount = 10;
|
|
15
15
|
const selectedGoalFragment = "work-selected-goal";
|
|
16
16
|
const goalStates = [
|
|
@@ -67,17 +67,30 @@ const agentStatusLabel = {
|
|
|
67
67
|
gone: "gone"
|
|
68
68
|
};
|
|
69
69
|
const deliveryLabel = {
|
|
70
|
-
local: "
|
|
71
|
-
review: "
|
|
70
|
+
local: "In progress",
|
|
71
|
+
review: "In review",
|
|
72
72
|
pull_request: "Pull request",
|
|
73
73
|
merged: "Merged",
|
|
74
74
|
deployed: "Deployed"
|
|
75
75
|
};
|
|
76
76
|
const reviewPresentation = {
|
|
77
|
-
not_requested: { label: "Not requested", tone: "neutral" },
|
|
78
|
-
requested: { label: "Requested", tone: "caution" },
|
|
79
|
-
changes_requested: { label: "Changes requested", tone: "critical" },
|
|
80
|
-
approved: { label: "Approved", tone: "positive" }
|
|
77
|
+
not_requested: { label: "Not requested", evidence: "no review yet", tone: "neutral" },
|
|
78
|
+
requested: { label: "Requested", evidence: "review requested", tone: "caution" },
|
|
79
|
+
changes_requested: { label: "Changes requested", evidence: "changes requested", tone: "critical" },
|
|
80
|
+
approved: { label: "Approved", evidence: "approved", tone: "positive" }
|
|
81
|
+
};
|
|
82
|
+
const checksPresentation = {
|
|
83
|
+
none: { label: "no checks", tone: "neutral" },
|
|
84
|
+
pending: { label: "checks running", tone: "progress" },
|
|
85
|
+
passing: { label: "checks passing", tone: "positive" },
|
|
86
|
+
failing: { label: "checks failing", tone: "critical" }
|
|
87
|
+
};
|
|
88
|
+
const stateIcon = {
|
|
89
|
+
neutral: "minus",
|
|
90
|
+
positive: "check",
|
|
91
|
+
critical: "alert",
|
|
92
|
+
caution: "clock",
|
|
93
|
+
progress: "loader"
|
|
81
94
|
};
|
|
82
95
|
const requestPresentation = {
|
|
83
96
|
open: { label: "Open", tone: "caution" },
|
|
@@ -89,6 +102,17 @@ const formatTimestamp = (timestamp) => new Intl.DateTimeFormat("en-GB", {
|
|
|
89
102
|
dateStyle: "medium",
|
|
90
103
|
timeStyle: "short"
|
|
91
104
|
}).format(timestamp);
|
|
105
|
+
/** The board's timestamp formatter also supplies compact clocks. */
|
|
106
|
+
const formatClock = (timestamp) => formatTimestamp(timestamp).split(", ").at(-1) ?? formatTimestamp(timestamp);
|
|
107
|
+
const ageOf = (timestamp, asOf) => {
|
|
108
|
+
const minutes = Math.max(0, Math.floor((asOf - timestamp) / 60_000));
|
|
109
|
+
if (minutes < 60)
|
|
110
|
+
return `${minutes}m ago`;
|
|
111
|
+
if (minutes < 1_440)
|
|
112
|
+
return `${Math.floor(minutes / 60)}h ago`;
|
|
113
|
+
const days = Math.floor(minutes / 1_440);
|
|
114
|
+
return `${days} ${days === 1 ? "day" : "days"} ago`;
|
|
115
|
+
};
|
|
92
116
|
const formatSpend = (goal) => goal.spend === null
|
|
93
117
|
? "Not recorded"
|
|
94
118
|
: new Intl.NumberFormat("en", {
|
|
@@ -102,12 +126,6 @@ const blockersFor = (goal) => {
|
|
|
102
126
|
};
|
|
103
127
|
const activityFor = (goal) => goal.activity ?? [];
|
|
104
128
|
const requestsFor = (goal) => goal.requests ?? [];
|
|
105
|
-
const hierarchyLabel = (goal) => {
|
|
106
|
-
const hierarchy = goal.agentHierarchy;
|
|
107
|
-
if (hierarchy === undefined || hierarchy === null)
|
|
108
|
-
return "No agent hierarchy recorded";
|
|
109
|
-
return `${hierarchy.agent.host} / ${hierarchy.agent.name}`;
|
|
110
|
-
};
|
|
111
129
|
/** Renders the persisted approval deep link after its origin is bound at ingress. */
|
|
112
130
|
const exactLink = (target, label) => (_jsx("a", { className: "work-exact-link", href: target.url, children: label }));
|
|
113
131
|
/** Renders the persisted review destination without rewriting its credential-free URL. */
|
|
@@ -118,24 +136,37 @@ const reviewLabel = (review) => {
|
|
|
118
136
|
}
|
|
119
137
|
return (_jsx(StateLabel, { label: reviewPresentation[review.state].label, size: "compact", tone: reviewPresentation[review.state].tone }));
|
|
120
138
|
};
|
|
121
|
-
/**
|
|
122
|
-
const
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
/** PR delivery includes review; local work includes it only when recorded. Abandoned work stops at its last step. */
|
|
140
|
+
const GoalProgress = ({ goal, observed }) => {
|
|
141
|
+
const shown = observed?.displayState ?? goal.state;
|
|
142
|
+
const merged = observed?.pullRequest?.fact.state === "merged" || goal.delivery === "merged";
|
|
143
|
+
const hasPullRequest = merged || observed?.pullRequest != null || goal.delivery === "pull_request";
|
|
144
|
+
const hasReview = hasPullRequest ||
|
|
145
|
+
goal.delivery === "review" ||
|
|
146
|
+
shown === "review" ||
|
|
147
|
+
(goal.review != null && goal.review.state !== "not_requested");
|
|
148
|
+
const finished = !hasPullRequest && (shown === "completed" || shown === "deployed");
|
|
149
|
+
const names = hasReview
|
|
150
|
+
? ["Planned", "In progress", "In review", hasPullRequest ? "Merged" : "Done"]
|
|
151
|
+
: ["Planned", "In progress", "Done"];
|
|
152
|
+
const finalStep = names.length - 1;
|
|
153
|
+
const current = merged || finished ? finalStep : hasReview ? 2 : shown === "planned" ? 0 : 1;
|
|
154
|
+
const abandoned = shown === "abandoned";
|
|
155
|
+
return (_jsxs("section", { "aria-labelledby": "work-delivery-title", className: "work-delivery", children: [_jsx("h3", { className: "work-group-title", id: "work-delivery-title", children: "Delivery" }), _jsx("ol", { "aria-label": `Delivery of ${goal.title}`, className: "work-row-progress", "data-steps": names.length, role: "list", children: names.map((name, index) => (_jsxs("li", { "aria-current": !abandoned && index === current ? "step" : undefined, "aria-label": abandoned && index === current ? `${name}, abandoned here` : undefined, "data-reached": index < current || (!abandoned && index === current), "data-stopped": abandoned && index === current, "data-tone": displayPresentation[shown].tone, children: [_jsx("span", { "aria-hidden": "true", className: "work-step-number", children: index + 1 }), _jsx("span", { className: "work-step-name", children: name }), abandoned && index === current ? (_jsx("span", { className: "work-step-status", children: "Abandoned" })) : !abandoned && index === current && current !== finalStep ? (_jsx(StateLabel, { icon: stateIcon[displayPresentation[shown].tone], label: "Now", size: "compact", tone: displayPresentation[shown].tone })) : (_jsx("span", { className: "work-step-status", children: index < current || (!abandoned && index === current) ? "Done" : "Not yet" }))] }, name))) }), hasPullRequest ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: "No pull request" }))] }));
|
|
156
|
+
};
|
|
157
|
+
/** CI and review come only from an observed PR. Its confirmation time says when those facts were last read. */
|
|
158
|
+
const GoalEvidence = ({ goal, observed }) => {
|
|
159
|
+
const pullRequest = observed?.pullRequest;
|
|
160
|
+
if (pullRequest == null && goal.delivery !== "pull_request" && goal.delivery !== "merged")
|
|
161
|
+
return null;
|
|
162
|
+
const ci = pullRequest == null
|
|
163
|
+
? { label: "checks unknown", tone: "neutral" }
|
|
164
|
+
: checksPresentation[pullRequest.fact.checks];
|
|
165
|
+
const review = pullRequest == null
|
|
166
|
+
? { evidence: "review unknown", tone: "neutral" }
|
|
167
|
+
: reviewPresentation[pullRequest.fact.review];
|
|
168
|
+
return (_jsxs("div", { className: "work-row-evidence", children: [_jsxs("span", { className: "work-evidence-chip", children: [_jsx(StateLabel, { className: "work-ci-fact", icon: stateIcon[ci.tone], label: ci.label, size: "compact", tone: ci.tone }), _jsx("span", { className: "work-row-read", children: pullRequest == null ? (" · Not read") : (_jsxs(_Fragment, { children: [observed?.unknown?.source === "github" ? " · last read " : " · read ", _jsx("time", { dateTime: new Date(pullRequest.confirmedAt).toISOString(), title: formatTimestamp(pullRequest.confirmedAt), children: formatClock(pullRequest.confirmedAt) })] })) })] }), _jsxs("span", { className: "work-evidence-chip", children: [_jsx(StateLabel, { className: "work-review-fact", icon: stateIcon[review.tone], label: review.evidence, size: "compact", tone: review.tone }), _jsx("span", { className: "work-row-read", children: pullRequest == null ? (" · Not read") : (_jsxs(_Fragment, { children: [observed?.unknown?.source === "github" ? " · last read" : " · read", " ", _jsx("time", { dateTime: new Date(pullRequest.confirmedAt).toISOString(), title: formatTimestamp(pullRequest.confirmedAt), children: formatClock(pullRequest.confirmedAt) })] })) })] })] }));
|
|
130
169
|
};
|
|
131
|
-
/**
|
|
132
|
-
* The repository as a row names it: its last path segment ("npm" for a checkout at
|
|
133
|
-
* /home/…/knpkv.dev/npm, or for "knpkv/npm"). The detail keeps the full value.
|
|
134
|
-
*/
|
|
135
|
-
const repositoryName = (repository) => repository
|
|
136
|
-
.split("/")
|
|
137
|
-
.filter((segment) => segment.length > 0)
|
|
138
|
-
.at(-1) ?? repository;
|
|
139
170
|
const rowCaption = (goal, observed, decisions) => {
|
|
140
171
|
const open = requestsFor(goal).filter(({ state }) => state === "open");
|
|
141
172
|
if (open.length > 0) {
|
|
@@ -151,16 +182,14 @@ const rowCaption = (goal, observed, decisions) => {
|
|
|
151
182
|
: `, ${workRequestClockText(soonest.expiresAt, decisions.now)}${soonest.expiresAt > decisions.now ? " left" : ""}`;
|
|
152
183
|
return {
|
|
153
184
|
blocking: false,
|
|
154
|
-
text: open.length === 1
|
|
155
|
-
? `Needs approval: ${open[0]?.summary ?? ""}${clock}`
|
|
156
|
-
: `${open.length} requests need approval${clock}`
|
|
185
|
+
text: open.length === 1 ? `${open[0]?.summary ?? ""}${clock}` : `${open.length} requests need approval${clock}`
|
|
157
186
|
};
|
|
158
187
|
}
|
|
159
188
|
const blockers = blockersFor(goal);
|
|
160
189
|
if (blockers.length > 0)
|
|
161
190
|
return { blocking: true, text: blockers[0]?.summary ?? "Blocked" };
|
|
162
191
|
if (observed?.stale === true && observed.agent !== null) {
|
|
163
|
-
return { blocking: false, text: `Owner gone since ${
|
|
192
|
+
return { blocking: false, text: `Owner gone since ${formatClock(observed.agent.observedAt)}` };
|
|
164
193
|
}
|
|
165
194
|
if (observed?.unknown != null) {
|
|
166
195
|
return {
|
|
@@ -174,24 +203,29 @@ const rowCaption = (goal, observed, decisions) => {
|
|
|
174
203
|
return { blocking: false, text: `${goal.owner.name}, ${deliveryLabel[goal.delivery].toLowerCase()}` };
|
|
175
204
|
};
|
|
176
205
|
/** The caption under the hero: the request waiting longest, or the latest change. */
|
|
177
|
-
const summaryCaption = (summary, tense) => {
|
|
206
|
+
const summaryCaption = (summary, tense, snapshot) => {
|
|
178
207
|
switch (summary._tag) {
|
|
179
208
|
case "Attention":
|
|
180
209
|
return summary.oldestRequest === null
|
|
181
210
|
? undefined
|
|
182
|
-
: `${tense === "present" ? "Waiting longest" : "Waiting longest then"}: ${summary.oldestRequest.request.summary} on ${summary.oldestRequest.goal.title}
|
|
183
|
-
case "Clear":
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
: `${tense === "present" ? "Waiting longest" : "Waiting longest then"}: ${summary.oldestRequest.request.summary} on ${summary.oldestRequest.goal.title}${tense === "past" ? "." : `, requested ${ageOf(summary.oldestRequest.request.requestedAt, snapshot.asOf)}.`}`;
|
|
212
|
+
case "Clear": {
|
|
213
|
+
const rows = workTriage(snapshot).rows;
|
|
214
|
+
const planned = rows.filter((row) => row.group === "planned").length;
|
|
215
|
+
const finished = rows
|
|
216
|
+
.filter((row) => row.group === "done" || row.group === "earlier")
|
|
217
|
+
.map(({ goal }) => ({ goal, at: workGoalFinishedAt(goal, observedFor(snapshot, goal.id)) }))
|
|
218
|
+
.toSorted((left, right) => right.at - left.at)[0];
|
|
219
|
+
return `${summary.moving} ${summary.moving === 1 ? "goal" : "goals"} ${tense === "past" ? "were " : ""}moving, ${planned} planned.${finished === undefined ? "" : ` Last finished: ${finished.goal.title}, ${ageOf(finished.at, snapshot.asOf)}.`}`;
|
|
220
|
+
}
|
|
187
221
|
case "Empty":
|
|
188
|
-
return
|
|
222
|
+
return undefined;
|
|
189
223
|
}
|
|
190
224
|
};
|
|
191
225
|
/**
|
|
192
226
|
* What the timeline shows for one goal, newest first: the owner's recorded activity, plus what the
|
|
193
227
|
* reconciler observed (a pull request, the owner's agent) and any source it could not read. Observed
|
|
194
|
-
* events
|
|
228
|
+
* events keep source detail and provenance in their accessible descriptions beside terse visible titles.
|
|
195
229
|
*/
|
|
196
230
|
const timelineFor = (goal, observed) => {
|
|
197
231
|
const recorded = activityFor(goal).map((entry) => ({
|
|
@@ -199,7 +233,7 @@ const timelineFor = (goal, observed) => {
|
|
|
199
233
|
dateTime: new Date(entry.occurredAt).toISOString(),
|
|
200
234
|
detail: activityKindLabel[entry.kind],
|
|
201
235
|
id: entry.id,
|
|
202
|
-
time:
|
|
236
|
+
time: formatClock(entry.occurredAt),
|
|
203
237
|
title: entry.summary
|
|
204
238
|
}));
|
|
205
239
|
const seen = [];
|
|
@@ -212,7 +246,7 @@ const timelineFor = (goal, observed) => {
|
|
|
212
246
|
detail: `${checksLabel[fact.checks]}, last read ${formatTimestamp(confirmedAt)}`,
|
|
213
247
|
id: `observed-pull-request-${fact.repository}-${fact.pullRequest}`,
|
|
214
248
|
provenance: { kind: "auto", label: "Observed on GitHub" },
|
|
215
|
-
time:
|
|
249
|
+
time: formatClock(at),
|
|
216
250
|
title: `Pull request #${fact.pullRequest} ${pullRequestStateLabel[fact.state]}`
|
|
217
251
|
});
|
|
218
252
|
}
|
|
@@ -224,7 +258,7 @@ const timelineFor = (goal, observed) => {
|
|
|
224
258
|
detail: `${fact.host}, last read ${formatTimestamp(confirmedAt)}`,
|
|
225
259
|
id: `observed-agent-${fact.host}-${fact.agentId}`,
|
|
226
260
|
provenance: { kind: "auto", label: "Observed in herdr" },
|
|
227
|
-
time:
|
|
261
|
+
time: formatClock(observedAt),
|
|
228
262
|
title: `Agent ${agentStatusLabel[fact.status]}`
|
|
229
263
|
});
|
|
230
264
|
}
|
|
@@ -239,13 +273,20 @@ const timelineFor = (goal, observed) => {
|
|
|
239
273
|
kind: "unknown",
|
|
240
274
|
label: lastGoodAt === null ? "Never read" : `Last good read ${formatTimestamp(lastGoodAt)}`
|
|
241
275
|
},
|
|
242
|
-
time:
|
|
276
|
+
time: formatClock(since),
|
|
243
277
|
title: `Couldn't read ${sourceLabel[source]}`
|
|
244
278
|
});
|
|
245
279
|
}
|
|
246
280
|
return [...recorded, ...seen].toSorted((left, right) => Date.parse(right.dateTime) - Date.parse(left.dateTime));
|
|
247
281
|
};
|
|
248
|
-
const requestDecisionFor = (request, decisions) => {
|
|
282
|
+
const requestDecisionFor = (request, decisions, historical) => {
|
|
283
|
+
// A recorded open request is never a live approval target; no current queue, clock or handler reaches this bar.
|
|
284
|
+
if (historical && request.state === "open" && request.approvalTarget !== null) {
|
|
285
|
+
return {
|
|
286
|
+
_tag: "Bar",
|
|
287
|
+
bar: (_jsx(DecisionBar, { onApprove: () => undefined, onReject: () => undefined, state: { _tag: "off", reason: "Decisions are off in the past." }, target: request.summary }))
|
|
288
|
+
};
|
|
289
|
+
}
|
|
249
290
|
const decidability = workRequestDecidability(request, decisions);
|
|
250
291
|
if (decidability._tag === "Elsewhere" || decisions === undefined)
|
|
251
292
|
return { _tag: "Elsewhere" };
|
|
@@ -284,17 +325,21 @@ const requestDecisionFor = (request, decisions) => {
|
|
|
284
325
|
};
|
|
285
326
|
};
|
|
286
327
|
/** One request in a goal's detail: its title once, then the bar, the hub link or the outcome. */
|
|
287
|
-
const RequestItem = ({ decisions, externalLinks, request }) => {
|
|
288
|
-
const decision = requestDecisionFor(request, externalLinks === "enabled" ? decisions : undefined);
|
|
328
|
+
const RequestItem = ({ asOf, decisions, externalLinks, historical, request }) => {
|
|
329
|
+
const decision = requestDecisionFor(request, externalLinks === "enabled" ? decisions : undefined, historical && externalLinks === "enabled");
|
|
289
330
|
const presentation = requestPresentation[request.state];
|
|
290
|
-
return (_jsxs("li", { children: [decision._tag === "Bar" ? null : (_jsxs("span", { className: "work-request-heading", children: [_jsx(Text, { children: request.summary }),
|
|
331
|
+
return (_jsxs("li", { children: [decision._tag === "Bar" ? null : (_jsxs("span", { className: "work-request-heading", children: [_jsx(StateLabel, { className: "work-row-state", icon: request.state === "rejected" ? "close" : stateIcon[presentation.tone], label: presentation.label, size: "compact", tone: presentation.tone }), _jsx(Text, { as: "strong", variant: "meta", children: request.summary }), request.state === "open" ? null : (_jsxs(Text, { tone: "secondary", variant: "meta", children: ["requested ", ageOf(request.requestedAt, asOf)] }))] })), request.state === "open" ? (_jsxs("div", { className: "work-request-meta", children: [decision._tag === "Bar" ? _jsx(StateLabel, { icon: "clock", label: "Open", size: "compact", tone: "caution" }) : null, _jsxs(Text, { tone: "secondary", variant: "meta", children: ["requested ", ageOf(request.requestedAt, asOf)] })] })) : null, decision._tag === "Bar" ? (decision.bar) : decision._tag === "Settled" ? (decision.refusal === null ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: decision.refusal }))) : request.state !== "open" ? null : request.approvalTarget === null ? (_jsx(Text, { tone: "secondary", variant: "meta", children: "No approval link recorded." })) : externalLinks === "disabled" ? (_jsxs(Text, { tone: "secondary", variant: "meta", children: ["Approve this on the hub (", request.approvalTarget.host, ")."] })) : (exactLink(request.approvalTarget, `Approve on ${request.approvalTarget.host}`)), decision._tag === "Elsewhere" ? null : (_jsx("p", { "aria-atomic": "true", className: "work-request-announcement", role: "status", children: decision._tag === "Settled" ? decision.announcement : "" }))] }));
|
|
291
332
|
};
|
|
292
333
|
/** Everything about one goal, in reading order: what it is, where it is, what blocks it, what happened. */
|
|
293
|
-
const GoalDetail = ({ decisions, externalLinks, goal, snapshot }) => {
|
|
334
|
+
const GoalDetail = ({ closeControl, decisions, externalLinks, goal, snapshot }) => {
|
|
294
335
|
const family = familyForGoal(snapshot, goal.id);
|
|
295
336
|
const observed = observedFor(snapshot, goal.id);
|
|
296
337
|
const shown = observed?.displayState ?? goal.state;
|
|
297
338
|
const activity = timelineFor(goal, observed);
|
|
339
|
+
const readDay = new Date(snapshot.observedAt).toDateString();
|
|
340
|
+
const agent = goal.agentHierarchy?.agent;
|
|
341
|
+
const agentName = agent?.name ?? goal.owner.name;
|
|
342
|
+
const agentIdentity = agent === undefined ? agentName : `${agent.host} / ${agentName}`;
|
|
298
343
|
// Only the live window carries the overlay; there "nothing observed" is itself a reading.
|
|
299
344
|
const overlay = snapshot.observed !== undefined;
|
|
300
345
|
// A goal-level approval target (older checkpoints carry one without requests); skipped when a
|
|
@@ -304,16 +349,23 @@ const GoalDetail = ({ decisions, externalLinks, goal, snapshot }) => {
|
|
|
304
349
|
requestsFor(goal).some(({ approvalTarget }) => approvalTarget?.url === goal.approvalTarget?.url)
|
|
305
350
|
? null
|
|
306
351
|
: goal.approvalTarget;
|
|
307
|
-
return (_jsxs("div", { className: "work-detail", children: [
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
352
|
+
return (_jsxs("div", { className: "work-detail", children: [_jsxs("div", { className: "work-detail-heading", children: [_jsx(Text, { as: "h3", variant: "card-title", children: goal.title }), _jsx(StateLabel, { icon: stateIcon[displayPresentation[shown].tone], label: displayPresentation[shown].label, size: "compact", tone: displayPresentation[shown].tone }), shown === goal.state ? null : (_jsxs(Text, { className: "work-fact-note", tone: "secondary", variant: "meta", children: ["Observed; recorded as ", statePresentation[goal.state].label.toLowerCase()] }))] }), observed?.stale === true && observed.agent !== null ? (_jsxs("p", { className: "work-note", "data-tone": "held", children: [_jsx("b", { children: "Owner gone" }), " since ", formatClock(observed.agent.observedAt), ": this unfinished goal has no live agent."] })) : null, _jsx(GoalProgress, { goal: goal, observed: observed }), overlay ? _jsx(GoalEvidence, { goal: goal, observed: observed }) : null, _jsxs("dl", { className: "work-facts", children: [_jsxs("div", { children: [_jsx("dt", { children: "Pull request" }), _jsxs("dd", { children: [observed?.pullRequest == null
|
|
353
|
+
? observed === null && snapshot.observedOmitted !== undefined
|
|
354
|
+
? "Not in this read: live state was trimmed to the most recently updated goals"
|
|
355
|
+
: "None yet"
|
|
356
|
+
: `#${observed.pullRequest.fact.pullRequest} ${pullRequestStateLabel[observed.pullRequest.fact.state]}${observed.stale ? `, ${checksLabel[observed.pullRequest.fact.checks]}` : ""}`, observed?.pullRequest == null ? null : (_jsx(Text, { as: "p", className: "work-fact-note", tone: "secondary", variant: "meta", children: "Observed on GitHub" }))] })] }), goal.review == null ? null : (_jsxs("div", { children: [_jsx("dt", { children: "Review" }), _jsxs("dd", { children: [reviewLabel(goal.review), overlay ? null : ", as recorded", goal.review?.url === null || goal.review?.url === undefined ? null : externalLinks ===
|
|
357
|
+
"disabled" ? null : (_jsxs(_Fragment, { children: [" ", reviewLink(goal.review.url)] })), goal.review?.summary === null || goal.review?.summary === undefined ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: goal.review.summary }))] })] })), _jsxs("div", { children: [_jsx("dt", { children: "Repository" }), _jsx("dd", { children: goal.repository.repository })] }), _jsxs("div", { children: [_jsx("dt", { children: "Branch" }), _jsx("dd", { children: goal.repository.branch })] }), goal.owner.name === agentName ? null : (_jsxs("div", { children: [_jsx("dt", { children: "Owner" }), _jsx("dd", { children: goal.owner.name })] })), _jsxs("div", { children: [_jsx("dt", { children: "Agent" }), _jsxs("dd", { children: [observed?.agent == null
|
|
358
|
+
? `${agentIdentity}, as recorded`
|
|
359
|
+
: `${agentIdentity}, ${agentStatusLabel[observed.agent.fact.status]}${observed.agent.fact.status === "gone" ? ` since ${formatClock(observed.agent.observedAt)}` : ""}`, observed?.agent == null ? null : (_jsx(Text, { as: "p", className: "work-fact-note", tone: "secondary", variant: "meta", children: "Observed in herdr" })), goal.agentHierarchy?.agent.relationship === undefined || goal.agentHierarchy === null
|
|
312
360
|
? null
|
|
313
|
-
: `, ${goal.agentHierarchy.agent.relationship.relation} from ${goal.agentHierarchy.agent.relationship.parentAgentId}`] })] }), _jsxs("div", { children: [_jsx("dt", { children: "
|
|
314
|
-
"disabled" ? null : (_jsxs(_Fragment, { children: [" ", reviewLink(goal.review.url)] })), goal.review?.summary === null || goal.review?.summary === undefined ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: goal.review.summary }))] })] }), _jsxs("div", { children: [_jsx("dt", { children: "Spend" }), _jsx("dd", { children: formatSpend(goal) })] })] }), observed?.stale === true && observed.agent !== null ? (_jsxs("p", { className: "work-note", "data-tone": "held", children: [_jsx("b", { children: "Owner gone" }), " since ", formatTimestamp(observed.agent.observedAt), ": this unfinished goal has no live agent."] })) : null, observed?.unknown == null ? null : (_jsxs("p", { className: "work-note", "data-tone": "unknown", children: [_jsxs("b", { children: ["Couldn't read ", sourceLabel[observed.unknown.source]] }), " since ", formatTimestamp(observed.unknown.since), ":", " ", observed.unknown.reason, ".", " ", observed.unknown.lastGoodAt === null
|
|
361
|
+
: `, ${goal.agentHierarchy.agent.relationship.relation} from ${goal.agentHierarchy.agent.relationship.parentAgentId}`] })] }), goal.spend === null ? null : (_jsxs("div", { children: [_jsx("dt", { children: "Spend" }), _jsx("dd", { children: formatSpend(goal) })] }))] }), goal.detail === goal.title ? null : _jsx(Text, { tone: "secondary", children: goal.detail }), observed?.unknown == null ? null : (_jsxs("p", { className: "work-note", "data-tone": "unknown", children: [_jsxs("b", { children: ["Couldn't read ", sourceLabel[observed.unknown.source]] }), " since ", formatTimestamp(observed.unknown.since), ":", " ", observed.unknown.reason, ".", " ", observed.unknown.lastGoodAt === null
|
|
315
362
|
? "It has never been read, so nothing here comes from it."
|
|
316
|
-
: `Facts from it are as of ${formatTimestamp(observed.unknown.lastGoodAt)}.`] })), blockersFor(goal).map((blocker) => (_jsxs("p", { className: "work-note", "data-tone": "blocked", children: [_jsx("b", { children: "Blocked" }), " since ", formatTimestamp(blocker.since), ": ", blocker.summary] }, `${blocker.since}-${blocker.summary}`))), _jsxs("section", { "aria-labelledby": "work-requests-title", className: "work-detail-section", children: [_jsx("h3", { className: "work-group-title", id: "work-requests-title", children: "Requests" }), requestsFor(goal).length === 0 ? (_jsx(Text, { tone: "secondary", children: goal.requests === undefined ? "No requests recorded." : "No outstanding requests." })) : (_jsx("ul", { className: "work-detail-list", children: requestsFor(goal).map((request) => (_jsx(RequestItem, { decisions: decisions, externalLinks: externalLinks, request: request }, request.id))) }))] }), _jsxs("section", { "aria-labelledby": "work-activity-title", className: "work-detail-section", children: [_jsx("h3", { className: "work-group-title", id: "work-activity-title", children: "Activity" }), activityOmittedFor(snapshot, goal) === 0 ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: activityShownOf(goal.activity?.length ?? 0, activityOmittedFor(snapshot, goal)) })), activity.length === 0 ? (_jsx(Text, { tone: "secondary", children: goal.activity === undefined ? "No activity recorded." : "Activity is clear." })) : (_jsx("ol", { "aria-label": `Activity on ${goal.title}`, className: "work-activity", children: activity.map((event
|
|
363
|
+
: `Facts from it are as of ${formatTimestamp(observed.unknown.lastGoodAt)}.`] })), blockersFor(goal).map((blocker) => (_jsxs("p", { className: "work-note", "data-tone": "blocked", children: [_jsx("b", { children: "Blocked" }), " since ", formatTimestamp(blocker.since), ": ", blocker.summary] }, `${blocker.since}-${blocker.summary}`))), _jsxs("section", { "aria-labelledby": "work-requests-title", className: "work-detail-section", children: [_jsx("h3", { className: "work-group-title", id: "work-requests-title", children: "Requests" }), requestsFor(goal).length === 0 ? (_jsx(Text, { tone: "secondary", children: goal.requests === undefined ? "No requests recorded." : "No outstanding requests." })) : (_jsx("ul", { className: "work-detail-list", children: requestsFor(goal).map((request) => (_jsx(RequestItem, { asOf: snapshot.asOf, decisions: decisions, externalLinks: externalLinks, historical: snapshot.window !== "now", request: request }, request.id))) }))] }), _jsxs("section", { "aria-labelledby": "work-activity-title", className: "work-detail-section", children: [_jsx("h3", { className: "work-group-title", id: "work-activity-title", children: "Activity" }), activityOmittedFor(snapshot, goal) === 0 ? null : (_jsx(Text, { tone: "secondary", variant: "meta", children: activityShownOf(goal.activity?.length ?? 0, activityOmittedFor(snapshot, goal)) })), activity.length === 0 ? (_jsx(Text, { tone: "secondary", children: goal.activity === undefined ? "No activity recorded." : "Activity is clear." })) : (_jsx("ol", { "aria-label": `Activity on ${goal.title}`, className: "work-activity", children: activity.map((event) => {
|
|
364
|
+
const descriptionId = `work-activity-${goal.id}-${event.id}`;
|
|
365
|
+
const eventAt = Date.parse(event.dateTime);
|
|
366
|
+
const date = new Date(eventAt).toDateString() === readDay ? null : formatTimestamp(eventAt);
|
|
367
|
+
return (_jsxs("li", { "aria-describedby": descriptionId, className: "work-activity-row", "data-rly-timeline-event-id": event.id, children: [_jsx("time", { dateTime: event.dateTime, children: event.time }), _jsx("span", { children: event.title }), _jsxs("span", { className: "work-accessible-detail", "data-rly-timeline-provenance": event.provenance?.kind, id: descriptionId, children: [event.detail, event.provenance === undefined ? "" : `. ${event.provenance.label}`, date === null ? "" : `. ${date}`] })] }, event.id));
|
|
368
|
+
}) }))] }), family === null ? null : (_jsxs("section", { "aria-labelledby": "work-family-title", className: "work-detail-section", children: [_jsx("h3", { className: "work-group-title", id: "work-family-title", children: "Superseded history" }), _jsxs("details", { className: "work-family-history", children: [_jsxs("summary", { children: ["Show ", family.superseded.length, " superseded"] }), _jsx("ul", { className: "work-detail-list", children: family.superseded.map((entry) => (_jsxs("li", { children: [_jsxs("span", { className: "work-request-heading", children: [_jsx(Text, { children: entry.title }), _jsx("span", { className: "work-row-state", "data-tone": statePresentation[entry.state].tone, children: statePresentation[entry.state].label })] }), _jsxs(Text, { tone: "secondary", variant: "meta", children: [entry.summary, ", ", formatTimestamp(entry.updatedAt)] }), blockersFor(entry).map((blocker) => (_jsxs(Text, { tone: "secondary", variant: "meta", children: ["Blocked since ", formatTimestamp(blocker.since), ": ", blocker.summary] }, `${blocker.since}-${blocker.summary}`))), entry.review === undefined || entry.review === null ? null : (_jsxs(Text, { tone: "secondary", variant: "meta", children: ["Review ", reviewPresentation[entry.review.state].label.toLowerCase(), entry.review.summary === null ? "" : `: ${entry.review.summary}`] }))] }, entry.id))) })] })] })), _jsxs("div", { className: "work-links", children: [goal.connectTarget === null ? (_jsx(Text, { tone: "secondary", children: "No exact Connect target recorded." })) : externalLinks === "disabled" ? (_jsx(Text, { tone: "secondary", variant: "meta", children: "Connect target recorded." })) : (_jsxs("a", { className: "work-connect-link", href: `${goal.connectTarget.url}&open=stage`, children: [observed?.stale === true ? "Find" : "Open", " ", agentName, " in Connect", _jsx(Icon, { decorative: true, name: "arrow-right", size: "small" })] })), goalApproval === null ? null : externalLinks === "disabled" ? (_jsxs(Text, { tone: "secondary", variant: "meta", children: ["Approval target recorded on ", goalApproval.host, "."] })) : (exactLink(goalApproval, `Open ${goalApproval.host} approval`)), closeControl] })] }));
|
|
317
369
|
};
|
|
318
370
|
const activityKindLabel = {
|
|
319
371
|
note: "Note",
|
|
@@ -326,25 +378,19 @@ const activityKindLabel = {
|
|
|
326
378
|
const snapshotFor = (snapshots, window) => snapshots[window];
|
|
327
379
|
/** How many older activities the snapshot left out of this goal's timeline. */
|
|
328
380
|
const activityOmittedFor = (snapshot, goal) => snapshot.activityOmitted?.[goal.id] ?? 0;
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
*/
|
|
333
|
-
const headerSentences = (window, snapshot) => {
|
|
334
|
-
const when = `${window === "now" ? "Live" : windowLabel[window]}, as of ${formatTimestamp(snapshot.asOf)}.`;
|
|
381
|
+
/** Header read time and live-read limitations. Omitted goal counts belong to the board. */
|
|
382
|
+
const headerSentences = (window, snapshot, host) => {
|
|
383
|
+
const when = `Durable goals${host === undefined ? "" : ` on ${host}`}, read ${formatClock(snapshot.observedAt)}.`;
|
|
335
384
|
const live = window !== "now"
|
|
336
385
|
? []
|
|
337
386
|
: snapshot.observed === undefined
|
|
338
387
|
? [
|
|
339
|
-
"Live state not available: this hub sends no observed facts
|
|
340
|
-
"Update herdr-work on the hub to see them."
|
|
388
|
+
"Live state not available: this hub sends no observed facts, so owner and pull request lines show only what each goal recorded. Update herdr-work on the hub to see them."
|
|
341
389
|
]
|
|
342
390
|
: snapshot.observedOmitted === undefined
|
|
343
391
|
? []
|
|
344
392
|
: [`Live state shown for the most recently updated goals; ${String(snapshot.observedOmitted)} left out.`];
|
|
345
|
-
|
|
346
|
-
const cut = snapshot.goalsOmitted === undefined ? [] : [`${goalsNotShown(snapshot.goalsOmitted)}.`];
|
|
347
|
-
return [when, ...live, ...finished, ...cut];
|
|
393
|
+
return [when, ...live];
|
|
348
394
|
};
|
|
349
395
|
const familyForGoal = (snapshot, goalId) => (snapshot.families ?? []).find((group) => group.canonicalGoalId === goalId) ?? null;
|
|
350
396
|
const familyLabelForGoal = (snapshot, goal) => {
|
|
@@ -358,8 +404,9 @@ const withFragment = (href, fragment) => {
|
|
|
358
404
|
const fragmentIndex = href.indexOf("#");
|
|
359
405
|
return `${fragmentIndex === -1 ? href : href.slice(0, fragmentIndex)}#${fragment}`;
|
|
360
406
|
};
|
|
361
|
-
export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId, initialWindow = "now", navigation, snapshots }) => {
|
|
362
|
-
const [
|
|
407
|
+
export const WorkBoard = ({ decisions, externalLinks = "enabled", host, initialGoalId, initialWindow = "now", navigation, snapshots }) => {
|
|
408
|
+
const [localWindow, setWindow] = useState(initialWindow);
|
|
409
|
+
const window = navigation === undefined ? localWindow : initialWindow;
|
|
363
410
|
const snapshot = snapshotFor(snapshots, window);
|
|
364
411
|
const directInitialGoal = snapshot.goals.find(({ id }) => id === initialGoalId);
|
|
365
412
|
const boardNavigation = directInitialGoal === undefined ? decodeWorkBoardNavigationGoal(initialGoalId ?? null) : null;
|
|
@@ -372,15 +419,24 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
372
419
|
? requestedInitialSelectedGoal
|
|
373
420
|
: undefined;
|
|
374
421
|
const initialSelectedId = initialSelectedGoal?.id ?? null;
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const [
|
|
379
|
-
const [
|
|
422
|
+
const initialDetailsOpen = initialSelectedGoal !== undefined &&
|
|
423
|
+
(boardNavigation?.detailsOpen ?? (initialGoalId !== undefined && initialGoalId !== null));
|
|
424
|
+
const [localSelectedId, setSelectedId] = useState(initialSelectedId);
|
|
425
|
+
const [localDetailsOpen, setDetailsOpen] = useState(initialDetailsOpen);
|
|
426
|
+
const [localStatusFilter, setStatusFilter] = useState(initialStatusFilter);
|
|
427
|
+
const [localVisibleGoalCount, setVisibleGoalCount] = useState(boardNavigation?.visibleGoalCount ?? initialVisibleGoalCount);
|
|
428
|
+
const selectedId = navigation === undefined ? localSelectedId : initialSelectedId;
|
|
429
|
+
const detailsOpen = navigation === undefined ? localDetailsOpen : initialDetailsOpen;
|
|
430
|
+
const statusFilter = navigation === undefined ? localStatusFilter : initialStatusFilter;
|
|
431
|
+
const visibleGoalCount = navigation === undefined ? localVisibleGoalCount : (boardNavigation?.visibleGoalCount ?? initialVisibleGoalCount);
|
|
380
432
|
const detailsRef = useRef(null);
|
|
381
433
|
const selectedLinkRowRef = useRef(null);
|
|
382
434
|
const selectedRowRef = useRef(null);
|
|
435
|
+
const previousDetailsOpen = useRef(detailsOpen);
|
|
383
436
|
const triage = workTriage(snapshot);
|
|
437
|
+
const finishedGoalCount = triage.rows.filter(({ displayState }) => displayState === "completed" || displayState === "deployed" || displayState === "abandoned").length;
|
|
438
|
+
const hasGoalOmissions = (snapshot.goalsOmitted ?? 0) > 0 || (snapshot.finishedOmitted ?? 0) > 0;
|
|
439
|
+
const finishedCount = finishedGoalCount > 0 || (snapshot.finishedOmitted ?? 0) > 0 ? (_jsxs(Text, { className: "work-finished-count", tone: "secondary", variant: "meta", children: [finishedGoalCount, " finished ", finishedGoalCount === 1 ? "goal" : "goals", (snapshot.finishedOmitted ?? 0) > 0 ? ` · ${snapshot.finishedOmitted} older not shown` : null] })) : null;
|
|
384
440
|
// The overlay by goal, built once per snapshot: rows, captions and filters all read it.
|
|
385
441
|
const overlay = new Map((snapshot.observed ?? []).map((entry) => [entry.goalId, entry]));
|
|
386
442
|
const observedOf = (goal) => overlay.get(goal.id) ?? null;
|
|
@@ -388,9 +444,7 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
388
444
|
// A historical window is the state as of its time; say so, never in the present tense.
|
|
389
445
|
const tense = window === "now" ? "present" : "past";
|
|
390
446
|
const sentence = workTriageSentence(triage.summary, tense);
|
|
391
|
-
const heroFact = tense === "
|
|
392
|
-
? sentence
|
|
393
|
-
: `As of ${formatTimestamp(snapshot.asOf)}, ${sentence.charAt(0).toLowerCase()}${sentence.slice(1)}`;
|
|
447
|
+
const heroFact = triage.summary._tag === "Attention" && triage.summary.blocked > 0 ? (_jsxs(_Fragment, { children: [sentence.slice(0, -`${triage.summary.blocked} blocked`.length), _jsx(HeroWord, { tone: "blocked", children: `${triage.summary.blocked} blocked` })] })) : tense === "past" && triage.summary._tag === "Attention" ? (_jsxs(_Fragment, { children: [sentence.split("needed you")[0], _jsx(HeroWord, { tone: "held", children: "needed you" })] })) : triage.summary._tag === "Empty" && tense === "past" ? ("Nothing needed you") : (sentence);
|
|
394
448
|
const groupById = new Map(triage.rows.map((row) => [row.goal.id, row.group]));
|
|
395
449
|
const ordered = triage.rows.map((row) => row.goal);
|
|
396
450
|
// Filters match what each row shows: the observed state where there is one.
|
|
@@ -408,24 +462,42 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
408
462
|
detailsRef.current?.querySelector("h2")?.focus();
|
|
409
463
|
}, [detailsOpen, selectedId]);
|
|
410
464
|
useEffect(() => {
|
|
465
|
+
const closing = previousDetailsOpen.current && !detailsOpen;
|
|
466
|
+
previousDetailsOpen.current = detailsOpen;
|
|
411
467
|
if (navigation !== undefined &&
|
|
412
468
|
!detailsOpen &&
|
|
413
469
|
selectedId !== null &&
|
|
414
|
-
document.location.hash === `#${selectedGoalFragment}`) {
|
|
470
|
+
(closing || document.location.hash === `#${selectedGoalFragment}`)) {
|
|
415
471
|
selectedLinkRowRef.current?.focus();
|
|
416
472
|
}
|
|
417
473
|
}, [detailsOpen, navigation, selectedId]);
|
|
418
474
|
useEffect(() => {
|
|
419
|
-
if (selectedId === null || selectedFilteredGoalIndex !== -1)
|
|
475
|
+
if (navigation !== undefined || selectedId === null || selectedFilteredGoalIndex !== -1)
|
|
420
476
|
return;
|
|
421
477
|
setDetailsOpen(false);
|
|
422
478
|
setSelectedId(null);
|
|
423
479
|
setVisibleGoalCount(initialVisibleGoalCount);
|
|
424
|
-
}, [selectedFilteredGoalIndex, selectedId]);
|
|
480
|
+
}, [navigation, selectedFilteredGoalIndex, selectedId]);
|
|
425
481
|
const goalRow = (goal) => {
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
|
|
482
|
+
const observed = observedOf(goal);
|
|
483
|
+
const shown = shownOf(goal);
|
|
484
|
+
const caption = rowCaption(goal, observed, externalLinks === "enabled" && window === "now" ? decisions : undefined);
|
|
485
|
+
const open = requestsFor(goal).filter((request) => request.state === "open");
|
|
486
|
+
const host = observed?.agent?.fact.host ?? goal.connectTarget?.host ?? goal.agentHierarchy?.agent.host;
|
|
487
|
+
const agentName = goal.agentHierarchy?.agent.name ?? goal.owner.name;
|
|
488
|
+
const pr = observed?.pullRequest?.fact;
|
|
489
|
+
const terminal = shown === "completed" || shown === "deployed" || shown === "abandoned";
|
|
490
|
+
const stage = pr === undefined
|
|
491
|
+
? `Stage: ${deliveryLabel[goal.delivery]}`
|
|
492
|
+
: `PR #${pr.pullRequest} ${pullRequestStateLabel[pr.state]}, ${checksLabel[pr.checks]}${pr.review === "not_requested" ? "" : ` · Review ${reviewPresentation[pr.review].label.toLowerCase()}`}`;
|
|
493
|
+
const metadata = goal.owner.name === "Unassigned"
|
|
494
|
+
? `Unassigned · ${goal.repository.repository}`
|
|
495
|
+
: terminal
|
|
496
|
+
? `${agentName} · ${shown === "abandoned" ? "" : goal.delivery === "merged" || pr?.state === "merged" ? "Merged · " : "Done · "}${displayPresentation[shown].label} ${ageOf(pr?.closedAt ?? goal.updatedAt, snapshot.asOf)}`
|
|
497
|
+
: `${agentName}${host === undefined ? "" : ` on ${host}`} · ${stage}${blockersFor(goal).length === 0 || open.length > 0 ? "" : ` · ${blockersFor(goal)[0]?.summary ?? "Blocked"}`}`;
|
|
498
|
+
const hasCaption = open.length > 0 || observed?.stale === true || observed?.unknown != null;
|
|
499
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: "work-row-title", children: goal.title }), _jsx(StateLabel, { className: "work-row-state", icon: stateIcon[displayPresentation[shown].tone], label: displayPresentation[shown].label, size: "compact", tone: displayPresentation[shown].tone }), _jsxs("span", { className: "work-row-meta", children: [metadata, familyLabelForGoal(snapshot, goal) === null ? null : ` · ${familyLabelForGoal(snapshot, goal)}`] }), hasCaption ? (_jsx("span", { className: "work-row-caption", "data-blocking": caption.blocking, children: open.length === 0 ? (caption.text) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "work-request-line", children: [_jsx(StateLabel, { className: "work-row-state", icon: "clock", label: "Open", size: "compact", tone: "caution" }), " ", _jsx("span", { className: "work-request-title", children: caption.text })] }), " ", _jsx("span", { className: "work-request-age", children: window === "now" ? ageOf(open[0]?.requestedAt ?? snapshot.asOf, snapshot.asOf) : "was open then" })] })) })) : null] }));
|
|
500
|
+
const control = navigation === undefined ? (_jsx("button", { "aria-controls": detailsOpen && selected?.id === goal.id ? "work-goal-details" : undefined, "aria-expanded": detailsOpen && selected?.id === goal.id, "aria-pressed": selected?.id === goal.id, className: "work-board-row", id: selected?.id === goal.id ? selectedGoalFragment : undefined, onClick: () => {
|
|
429
501
|
setDetailsOpen(true);
|
|
430
502
|
setSelectedId(goal.id);
|
|
431
503
|
}, ref: selected?.id === goal.id ? selectedRowRef : undefined, type: "button", children: content })) : (_jsx("a", { "aria-current": selected?.id === goal.id ? "true" : undefined, className: "work-board-row", href: navigation({
|
|
@@ -439,6 +511,7 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
439
511
|
}),
|
|
440
512
|
window
|
|
441
513
|
}), id: selected?.id === goal.id ? selectedGoalFragment : undefined, ref: selected?.id === goal.id ? selectedLinkRowRef : undefined, children: content }));
|
|
514
|
+
return control;
|
|
442
515
|
};
|
|
443
516
|
const timeTravel = (_jsx("div", { "aria-label": "Choose work snapshot", className: "work-time-controls", role: "group", children: windows.map((option) => {
|
|
444
517
|
if (navigation === undefined) {
|
|
@@ -472,12 +545,12 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
472
545
|
}) }));
|
|
473
546
|
const statusFilterControls = (_jsx("div", { "aria-label": "Filter goals by status", className: "work-status-filters", role: "group", children: statusFilters.map((state) => {
|
|
474
547
|
const label = state === "all" ? "All" : statePresentation[state].label;
|
|
475
|
-
return navigation === undefined ? (_jsx("button", { "aria-pressed": statusFilter === state, className: "work-status-filter", onClick: () => {
|
|
548
|
+
return navigation === undefined ? (_jsx("button", { "aria-pressed": statusFilter === state, className: "work-status-filter", onFocus: ({ currentTarget }) => currentTarget.scrollIntoView({ block: "nearest", inline: "nearest" }), onClick: () => {
|
|
476
549
|
setDetailsOpen(false);
|
|
477
550
|
setSelectedId(null);
|
|
478
551
|
setStatusFilter(state);
|
|
479
552
|
setVisibleGoalCount(initialVisibleGoalCount);
|
|
480
|
-
}, type: "button", children: label }, state)) : (_jsx("a", { "aria-current": statusFilter === state ? "page" : undefined, className: "work-status-filter", href: navigation({
|
|
553
|
+
}, type: "button", children: label }, state)) : (_jsx("a", { "aria-current": statusFilter === state ? "page" : undefined, className: "work-status-filter", onFocus: ({ currentTarget }) => currentTarget.scrollIntoView({ block: "nearest", inline: "nearest" }), href: navigation({
|
|
481
554
|
goalId: encodeWorkBoardNavigationGoal({
|
|
482
555
|
detailsOpen: false,
|
|
483
556
|
goalId: null,
|
|
@@ -487,7 +560,35 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
487
560
|
window
|
|
488
561
|
}), children: label }, state));
|
|
489
562
|
}) }));
|
|
490
|
-
|
|
563
|
+
const closeControl = (label) => navigation === undefined ? (_jsx(Button, { onClick: () => {
|
|
564
|
+
setDetailsOpen(false);
|
|
565
|
+
(selectedRowRef.current ?? selectedLinkRowRef.current)?.focus();
|
|
566
|
+
}, className: label === "Close" ? "work-close-heading" : undefined, variant: label === "Close" ? "quiet" : "secondary", children: label })) : (_jsx("a", { className: `work-exact-link work-close-control${label === "Close" ? " work-close-heading" : ""}`, href: withFragment(navigation({
|
|
567
|
+
goalId: encodeWorkBoardNavigationGoal({
|
|
568
|
+
detailsOpen: false,
|
|
569
|
+
goalId: selectedId,
|
|
570
|
+
statusFilter,
|
|
571
|
+
visibleGoalCount
|
|
572
|
+
}),
|
|
573
|
+
window
|
|
574
|
+
}), selectedGoalFragment), children: label }));
|
|
575
|
+
return (_jsxs("section", { className: "work-page", "aria-labelledby": "work-board-title", children: [_jsxs("header", { className: "work-page-intro", children: [_jsx(Text, { as: "h1", id: "work-board-title", variant: "section-title", children: "Work" }), _jsx(Text, { tone: "secondary", variant: "meta", children: headerSentences(window, snapshot, host)[0] })] }), headerSentences(window, snapshot, host)
|
|
576
|
+
.slice(1)
|
|
577
|
+
.map((sentence) => (_jsxs("p", { className: "work-note work-overlay-note", "data-tone": "held", children: [_jsx(Icon, { decorative: true, name: "clock", size: "small" }), _jsx("span", { children: sentence })] }, sentence))), externalLinks === "disabled" ? (_jsx("p", { className: "work-note", children: "Read-only on the local network. Requests are decided on the hub." })) : null, window === "now" ? null : (_jsxs("div", { className: "work-note work-history-note", children: [_jsxs("span", { children: ["Work as it was ", windowLabel[window].toLowerCase(), ", ", formatTimestamp(snapshot.asOf), ". Decisions are off in the past."] }), navigation === undefined ? (_jsx(Button, { onClick: () => {
|
|
578
|
+
setWindow("now");
|
|
579
|
+
setDetailsOpen(false);
|
|
580
|
+
setSelectedId(null);
|
|
581
|
+
setStatusFilter("all");
|
|
582
|
+
setVisibleGoalCount(initialVisibleGoalCount);
|
|
583
|
+
}, variant: "secondary", children: "Back to now" })) : (_jsx("a", { className: "work-exact-link", href: navigation({ goalId: null, window: "now" }), children: "Back to now" }))] })), snapshot.goals.length === 0 && (window === "now" || hasGoalOmissions) ? null : (_jsx(Hero, { caption: summaryCaption(triage.summary, tense, snapshot), fact: heroFact, label: "Work summary", size: "heading" })), _jsxs("div", { className: "work-board-controls", children: [timeTravel, statusFilterControls] }), snapshot.goals.length === 0 ? (_jsx(Region, { actions: (snapshot.goalsOmitted ?? 0) > 0 ? (_jsxs(Text, { "aria-live": "polite", tone: "secondary", variant: "meta", children: ["Showing 0 of ", snapshot.goalsOmitted, " goals"] })) : undefined, className: "work-empty-board", count: snapshot.goalsOmitted ?? 0, title: "Goals", children: _jsxs("div", { className: "work-empty-copy", children: [hasGoalOmissions ? (_jsx(Text, { as: "strong", children: "No goals in this read." })) : window === "now" ? (_jsx(Text, { as: "strong", children: "No goals yet." })) : null, hasGoalOmissions ? null : (_jsx(Text, { tone: "secondary", children: window === "now" ? (_jsxs(_Fragment, { children: ["Delegate work to an agent with", " ", _jsx("code", { children: "fleetctl submit HOST agent.delegate work REPOSITORY PROMPT" }), " (HOST is a name from", " ", _jsx("code", { children: "fleetctl hosts" }), "); its goal appears here once the hub admits it."] })) : ("No goals at this checkpoint.") })), finishedCount] }) })) : (_jsxs("div", { className: "work-board-layout", "data-has-detail": selected !== null && detailsOpen, children: [selected === null || !detailsOpen ? null : (_jsx(Region, { actions: closeControl("Close"), "aria-label": "Goal details", className: "work-inspector", headingId: "work-goal-details-title", id: "work-goal-details", ref: detailsRef, title: "Goal details", children: _jsx(GoalDetail, { closeControl: closeControl("Close details"), decisions: snapshot.window === "now" ? decisions : undefined, externalLinks: externalLinks, goal: selected, snapshot: snapshot }) })), _jsxs(Region, { actions: filteredGoals.length === 0 ? undefined : (_jsxs(Text, { "aria-live": "polite", tone: "secondary", variant: "meta", children: ["Showing ", visibleGoals.length, " of", " ", filteredGoals.length + (statusFilter === "all" ? (snapshot.goalsOmitted ?? 0) : 0), " goals"] })), className: "work-board-list", count: statusFilter === "all"
|
|
584
|
+
? filteredGoals.length + (snapshot.goalsOmitted ?? 0)
|
|
585
|
+
: filteredGoals.length === 0
|
|
586
|
+
? `0 of ${snapshot.goals.length + (snapshot.goalsOmitted ?? 0)}`
|
|
587
|
+
: filteredGoals.length, title: "Goals", children: [filteredGoals.length === 0 ? (_jsxs("div", { className: "work-filter-empty", children: [_jsx(Text, { tone: "secondary", children: "No goals match this status." }), navigation === undefined ? (_jsx(Button, { onClick: () => setStatusFilter("all"), variant: "secondary", children: "Show all" })) : (_jsx("a", { className: "work-exact-link", href: navigation({ goalId: null, window }), children: "Show all" }))] })) : null, visibleGroups.map(({ goals, group }) => (_jsxs("section", { "aria-labelledby": `work-group-${group}`, className: "work-group", children: [_jsxs("h3", { className: "work-group-title", id: `work-group-${group}`, children: [group === "planned"
|
|
588
|
+
? "Planned"
|
|
589
|
+
: group === "needs-you" && tense === "past"
|
|
590
|
+
? "Needed you"
|
|
591
|
+
: workTriageGroupTitle[group], " ", _jsxs("span", { className: "work-group-count", children: ["(", goals.length, ")"] })] }), _jsx("ul", { className: "work-rows", children: goals.map((goal) => (_jsx("li", { children: goalRow(goal) }, goal.id))) })] }, group))), filteredGoals.length > 0 ? finishedCount : null, visibleGoals.length < filteredGoals.length ? (navigation === undefined ? (_jsx(Button, { onClick: () => setVisibleGoalCount((count) => Math.min(count + initialVisibleGoalCount, filteredGoals.length)), children: "Load 10 more" })) : (_jsx("a", { className: "work-load-more-link", href: navigation({
|
|
491
592
|
goalId: encodeWorkBoardNavigationGoal({
|
|
492
593
|
detailsOpen,
|
|
493
594
|
goalId: selectedId,
|
|
@@ -495,18 +596,6 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
495
596
|
visibleGoalCount: Math.min(visibleGoalCount + initialVisibleGoalCount, filteredGoals.length)
|
|
496
597
|
}),
|
|
497
598
|
window
|
|
498
|
-
}), children: "Load 10 more" }))) : null] })
|
|
499
|
-
setDetailsOpen(false);
|
|
500
|
-
const selectedRow = selectedRowRef.current ?? selectedLinkRowRef.current;
|
|
501
|
-
selectedRow?.focus();
|
|
502
|
-
}, variant: "secondary", children: "Close details" })) : (_jsx("a", { className: "work-exact-link", href: withFragment(navigation({
|
|
503
|
-
goalId: encodeWorkBoardNavigationGoal({
|
|
504
|
-
detailsOpen: false,
|
|
505
|
-
goalId: selectedId,
|
|
506
|
-
statusFilter,
|
|
507
|
-
visibleGoalCount
|
|
508
|
-
}),
|
|
509
|
-
window
|
|
510
|
-
}), selectedGoalFragment), children: "Close details" })), "aria-label": "Goal details", className: "work-inspector", headingId: "work-goal-details-title", id: "work-goal-details", ref: detailsRef, title: selected.title, children: _jsx(GoalDetail, { decisions: decisions, externalLinks: externalLinks, goal: selected, snapshot: snapshot }) }))] }))] }));
|
|
599
|
+
}), children: "Load 10 more" }))) : null] })] }))] }));
|
|
511
600
|
};
|
|
512
601
|
//# sourceMappingURL=view.js.map
|