@knpkv/herdr-work 0.9.5 → 0.11.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 +4 -0
- package/dist/internal/agent-binding-admission.d.ts +0 -3
- package/dist/internal/agent-binding-admission.d.ts.map +1 -1
- package/dist/internal/agent-binding-admission.js +0 -59
- package/dist/internal/agent-binding-admission.js.map +1 -1
- package/dist/model.d.ts +112 -0
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +44 -0
- package/dist/model.js.map +1 -1
- package/dist/projection.d.ts +41 -1
- package/dist/projection.d.ts.map +1 -1
- package/dist/projection.js +148 -17
- package/dist/projection.js.map +1 -1
- package/dist/store.d.ts +1 -6
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +2 -51
- package/dist/store.js.map +1 -1
- package/dist/view.d.ts +3 -1
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js +187 -90
- package/dist/view.js.map +1 -1
- package/dist/work-triage.d.ts +6 -4
- package/dist/work-triage.d.ts.map +1 -1
- package/dist/work-triage.js +6 -9
- package/dist/work-triage.js.map +1 -1
- package/package.json +4 -4
- package/src/styles.css +382 -46
package/dist/view.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { type WorkRequestDecisions } from "./request-decision.js";
|
|
|
4
4
|
export type { WorkRequestAnswer, WorkRequestDecision, WorkRequestDecisions } from "./request-decision.js";
|
|
5
5
|
/** The request clock's wording ("52s", "4m 12s", "11m", "expiring"), shared with hosts that show the same clocks. */
|
|
6
6
|
export { workRequestClockText } from "./request-decision.js";
|
|
7
|
-
export declare const WorkBoard: ({ decisions, externalLinks, initialGoalId, initialWindow, navigation, snapshots }: {
|
|
7
|
+
export declare const WorkBoard: ({ decisions, externalLinks, host, initialGoalId, initialWindow, navigation, snapshots }: {
|
|
8
8
|
readonly snapshots: WorkSnapshots;
|
|
9
9
|
/**
|
|
10
10
|
* Lets the reader decide approval requests in place, with their clock. Omit it (the LAN view, a
|
|
@@ -12,6 +12,8 @@ export declare const WorkBoard: ({ decisions, externalLinks, initialGoalId, init
|
|
|
12
12
|
*/
|
|
13
13
|
readonly decisions?: WorkRequestDecisions;
|
|
14
14
|
readonly externalLinks?: "disabled" | "enabled";
|
|
15
|
+
/** The application supplies its own host; goal owners may be on other hosts. Omit when unknown. */
|
|
16
|
+
readonly host?: string;
|
|
15
17
|
readonly initialGoalId?: string | null;
|
|
16
18
|
readonly initialWindow?: WorkSnapshotWindow;
|
|
17
19
|
readonly navigation?: (selection: {
|
package/dist/view.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"view.d.ts","sourceRoot":"","sources":["../src/view.tsx"],"names":[],"mappings":"AAUA,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAA;AACtE,OAAO,KAAK,EAaV,aAAa,EACb,kBAAkB,EAEnB,MAAM,YAAY,CAAA;AAGnB,OAAO,EAAiD,KAAK,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAYhH,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACzG,qHAAqH;AACrH,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AAy5B5D,eAAO,MAAM,SAAS,4FAQnB;IACD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,oBAAoB,CAAA;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IAC/C,mGAAmG;IACnG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAC3C,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;KAAE,KAAK,MAAM,CAAA;CACrH,KAAG,YAwgBH,CAAA"}
|
package/dist/view.js
CHANGED
|
@@ -1,15 +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";
|
|
6
|
+
import { activityShownOf } from "./model.js";
|
|
5
7
|
import { decodeWorkBoardNavigationGoal, encodeWorkBoardNavigationGoal } from "./navigation.js";
|
|
6
8
|
import { workRequestClockText, workRequestDecidability } from "./request-decision.js";
|
|
7
9
|
import { displayStateOf, observedFor } from "./display-state.js";
|
|
8
|
-
import { workTriage, workTriageGroups, workTriageGroupTitle, workTriageSentence } from "./work-triage.js";
|
|
10
|
+
import { workGoalFinishedAt, workTriage, workTriageGroups, workTriageGroupTitle, workTriageSentence } from "./work-triage.js";
|
|
9
11
|
/** The request clock's wording ("52s", "4m 12s", "11m", "expiring"), shared with hosts that show the same clocks. */
|
|
10
12
|
export { workRequestClockText } from "./request-decision.js";
|
|
11
13
|
const windows = ["now", "day", "week", "month"];
|
|
12
|
-
const stageOrder = ["local", "review", "pull_request", "merged", "deployed"];
|
|
13
14
|
const initialVisibleGoalCount = 10;
|
|
14
15
|
const selectedGoalFragment = "work-selected-goal";
|
|
15
16
|
const goalStates = [
|
|
@@ -66,17 +67,30 @@ const agentStatusLabel = {
|
|
|
66
67
|
gone: "gone"
|
|
67
68
|
};
|
|
68
69
|
const deliveryLabel = {
|
|
69
|
-
local: "
|
|
70
|
-
review: "
|
|
70
|
+
local: "In progress",
|
|
71
|
+
review: "In review",
|
|
71
72
|
pull_request: "Pull request",
|
|
72
73
|
merged: "Merged",
|
|
73
74
|
deployed: "Deployed"
|
|
74
75
|
};
|
|
75
76
|
const reviewPresentation = {
|
|
76
|
-
not_requested: { label: "Not requested", tone: "neutral" },
|
|
77
|
-
requested: { label: "Requested", tone: "caution" },
|
|
78
|
-
changes_requested: { label: "Changes requested", tone: "critical" },
|
|
79
|
-
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"
|
|
80
94
|
};
|
|
81
95
|
const requestPresentation = {
|
|
82
96
|
open: { label: "Open", tone: "caution" },
|
|
@@ -88,6 +102,17 @@ const formatTimestamp = (timestamp) => new Intl.DateTimeFormat("en-GB", {
|
|
|
88
102
|
dateStyle: "medium",
|
|
89
103
|
timeStyle: "short"
|
|
90
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
|
+
};
|
|
91
116
|
const formatSpend = (goal) => goal.spend === null
|
|
92
117
|
? "Not recorded"
|
|
93
118
|
: new Intl.NumberFormat("en", {
|
|
@@ -101,12 +126,6 @@ const blockersFor = (goal) => {
|
|
|
101
126
|
};
|
|
102
127
|
const activityFor = (goal) => goal.activity ?? [];
|
|
103
128
|
const requestsFor = (goal) => goal.requests ?? [];
|
|
104
|
-
const hierarchyLabel = (goal) => {
|
|
105
|
-
const hierarchy = goal.agentHierarchy;
|
|
106
|
-
if (hierarchy === undefined || hierarchy === null)
|
|
107
|
-
return "No agent hierarchy recorded";
|
|
108
|
-
return `${hierarchy.agent.host} / ${hierarchy.agent.name}`;
|
|
109
|
-
};
|
|
110
129
|
/** Renders the persisted approval deep link after its origin is bound at ingress. */
|
|
111
130
|
const exactLink = (target, label) => (_jsx("a", { className: "work-exact-link", href: target.url, children: label }));
|
|
112
131
|
/** Renders the persisted review destination without rewriting its credential-free URL. */
|
|
@@ -117,24 +136,37 @@ const reviewLabel = (review) => {
|
|
|
117
136
|
}
|
|
118
137
|
return (_jsx(StateLabel, { label: reviewPresentation[review.state].label, size: "compact", tone: reviewPresentation[review.state].tone }));
|
|
119
138
|
};
|
|
120
|
-
/**
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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) })] })) })] })] }));
|
|
129
169
|
};
|
|
130
|
-
/**
|
|
131
|
-
* The repository as a row names it: its last path segment ("npm" for a checkout at
|
|
132
|
-
* /home/…/knpkv.dev/npm, or for "knpkv/npm"). The detail keeps the full value.
|
|
133
|
-
*/
|
|
134
|
-
const repositoryName = (repository) => repository
|
|
135
|
-
.split("/")
|
|
136
|
-
.filter((segment) => segment.length > 0)
|
|
137
|
-
.at(-1) ?? repository;
|
|
138
170
|
const rowCaption = (goal, observed, decisions) => {
|
|
139
171
|
const open = requestsFor(goal).filter(({ state }) => state === "open");
|
|
140
172
|
if (open.length > 0) {
|
|
@@ -150,16 +182,14 @@ const rowCaption = (goal, observed, decisions) => {
|
|
|
150
182
|
: `, ${workRequestClockText(soonest.expiresAt, decisions.now)}${soonest.expiresAt > decisions.now ? " left" : ""}`;
|
|
151
183
|
return {
|
|
152
184
|
blocking: false,
|
|
153
|
-
text: open.length === 1
|
|
154
|
-
? `Needs approval: ${open[0]?.summary ?? ""}${clock}`
|
|
155
|
-
: `${open.length} requests need approval${clock}`
|
|
185
|
+
text: open.length === 1 ? `${open[0]?.summary ?? ""}${clock}` : `${open.length} requests need approval${clock}`
|
|
156
186
|
};
|
|
157
187
|
}
|
|
158
188
|
const blockers = blockersFor(goal);
|
|
159
189
|
if (blockers.length > 0)
|
|
160
190
|
return { blocking: true, text: blockers[0]?.summary ?? "Blocked" };
|
|
161
191
|
if (observed?.stale === true && observed.agent !== null) {
|
|
162
|
-
return { blocking: false, text: `Owner gone since ${
|
|
192
|
+
return { blocking: false, text: `Owner gone since ${formatClock(observed.agent.observedAt)}` };
|
|
163
193
|
}
|
|
164
194
|
if (observed?.unknown != null) {
|
|
165
195
|
return {
|
|
@@ -173,24 +203,29 @@ const rowCaption = (goal, observed, decisions) => {
|
|
|
173
203
|
return { blocking: false, text: `${goal.owner.name}, ${deliveryLabel[goal.delivery].toLowerCase()}` };
|
|
174
204
|
};
|
|
175
205
|
/** The caption under the hero: the request waiting longest, or the latest change. */
|
|
176
|
-
const summaryCaption = (summary, tense) => {
|
|
206
|
+
const summaryCaption = (summary, tense, snapshot) => {
|
|
177
207
|
switch (summary._tag) {
|
|
178
208
|
case "Attention":
|
|
179
209
|
return summary.oldestRequest === null
|
|
180
210
|
? undefined
|
|
181
|
-
: `${tense === "present" ? "Waiting longest" : "Waiting longest then"}: ${summary.oldestRequest.request.summary} on ${summary.oldestRequest.goal.title}
|
|
182
|
-
case "Clear":
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
}
|
|
186
221
|
case "Empty":
|
|
187
|
-
return
|
|
222
|
+
return undefined;
|
|
188
223
|
}
|
|
189
224
|
};
|
|
190
225
|
/**
|
|
191
226
|
* What the timeline shows for one goal, newest first: the owner's recorded activity, plus what the
|
|
192
227
|
* reconciler observed (a pull request, the owner's agent) and any source it could not read. Observed
|
|
193
|
-
* events
|
|
228
|
+
* events keep source detail and provenance in their accessible descriptions beside terse visible titles.
|
|
194
229
|
*/
|
|
195
230
|
const timelineFor = (goal, observed) => {
|
|
196
231
|
const recorded = activityFor(goal).map((entry) => ({
|
|
@@ -198,7 +233,7 @@ const timelineFor = (goal, observed) => {
|
|
|
198
233
|
dateTime: new Date(entry.occurredAt).toISOString(),
|
|
199
234
|
detail: activityKindLabel[entry.kind],
|
|
200
235
|
id: entry.id,
|
|
201
|
-
time:
|
|
236
|
+
time: formatClock(entry.occurredAt),
|
|
202
237
|
title: entry.summary
|
|
203
238
|
}));
|
|
204
239
|
const seen = [];
|
|
@@ -211,7 +246,7 @@ const timelineFor = (goal, observed) => {
|
|
|
211
246
|
detail: `${checksLabel[fact.checks]}, last read ${formatTimestamp(confirmedAt)}`,
|
|
212
247
|
id: `observed-pull-request-${fact.repository}-${fact.pullRequest}`,
|
|
213
248
|
provenance: { kind: "auto", label: "Observed on GitHub" },
|
|
214
|
-
time:
|
|
249
|
+
time: formatClock(at),
|
|
215
250
|
title: `Pull request #${fact.pullRequest} ${pullRequestStateLabel[fact.state]}`
|
|
216
251
|
});
|
|
217
252
|
}
|
|
@@ -223,7 +258,7 @@ const timelineFor = (goal, observed) => {
|
|
|
223
258
|
detail: `${fact.host}, last read ${formatTimestamp(confirmedAt)}`,
|
|
224
259
|
id: `observed-agent-${fact.host}-${fact.agentId}`,
|
|
225
260
|
provenance: { kind: "auto", label: "Observed in herdr" },
|
|
226
|
-
time:
|
|
261
|
+
time: formatClock(observedAt),
|
|
227
262
|
title: `Agent ${agentStatusLabel[fact.status]}`
|
|
228
263
|
});
|
|
229
264
|
}
|
|
@@ -238,13 +273,20 @@ const timelineFor = (goal, observed) => {
|
|
|
238
273
|
kind: "unknown",
|
|
239
274
|
label: lastGoodAt === null ? "Never read" : `Last good read ${formatTimestamp(lastGoodAt)}`
|
|
240
275
|
},
|
|
241
|
-
time:
|
|
276
|
+
time: formatClock(since),
|
|
242
277
|
title: `Couldn't read ${sourceLabel[source]}`
|
|
243
278
|
});
|
|
244
279
|
}
|
|
245
280
|
return [...recorded, ...seen].toSorted((left, right) => Date.parse(right.dateTime) - Date.parse(left.dateTime));
|
|
246
281
|
};
|
|
247
|
-
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
|
+
}
|
|
248
290
|
const decidability = workRequestDecidability(request, decisions);
|
|
249
291
|
if (decidability._tag === "Elsewhere" || decisions === undefined)
|
|
250
292
|
return { _tag: "Elsewhere" };
|
|
@@ -283,17 +325,21 @@ const requestDecisionFor = (request, decisions) => {
|
|
|
283
325
|
};
|
|
284
326
|
};
|
|
285
327
|
/** One request in a goal's detail: its title once, then the bar, the hub link or the outcome. */
|
|
286
|
-
const RequestItem = ({ decisions, externalLinks, request }) => {
|
|
287
|
-
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");
|
|
288
330
|
const presentation = requestPresentation[request.state];
|
|
289
|
-
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 : "" }))] }));
|
|
290
332
|
};
|
|
291
333
|
/** Everything about one goal, in reading order: what it is, where it is, what blocks it, what happened. */
|
|
292
|
-
const GoalDetail = ({ decisions, externalLinks, goal, snapshot }) => {
|
|
334
|
+
const GoalDetail = ({ closeControl, decisions, externalLinks, goal, snapshot }) => {
|
|
293
335
|
const family = familyForGoal(snapshot, goal.id);
|
|
294
336
|
const observed = observedFor(snapshot, goal.id);
|
|
295
337
|
const shown = observed?.displayState ?? goal.state;
|
|
296
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}`;
|
|
297
343
|
// Only the live window carries the overlay; there "nothing observed" is itself a reading.
|
|
298
344
|
const overlay = snapshot.observed !== undefined;
|
|
299
345
|
// A goal-level approval target (older checkpoints carry one without requests); skipped when a
|
|
@@ -303,16 +349,23 @@ const GoalDetail = ({ decisions, externalLinks, goal, snapshot }) => {
|
|
|
303
349
|
requestsFor(goal).some(({ approvalTarget }) => approvalTarget?.url === goal.approvalTarget?.url)
|
|
304
350
|
? null
|
|
305
351
|
: goal.approvalTarget;
|
|
306
|
-
return (_jsxs("div", { className: "work-detail", children: [
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
311
360
|
? null
|
|
312
|
-
: `, ${goal.agentHierarchy.agent.relationship.relation} from ${goal.agentHierarchy.agent.relationship.parentAgentId}`] })] }), _jsxs("div", { children: [_jsx("dt", { children: "
|
|
313
|
-
"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
|
|
314
362
|
? "It has never been read, so nothing here comes from it."
|
|
315
|
-
: `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" }),
|
|
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] })] }));
|
|
316
369
|
};
|
|
317
370
|
const activityKindLabel = {
|
|
318
371
|
note: "Note",
|
|
@@ -323,6 +376,22 @@ const activityKindLabel = {
|
|
|
323
376
|
shipment: "Shipment"
|
|
324
377
|
};
|
|
325
378
|
const snapshotFor = (snapshots, window) => snapshots[window];
|
|
379
|
+
/** How many older activities the snapshot left out of this goal's timeline. */
|
|
380
|
+
const activityOmittedFor = (snapshot, goal) => snapshot.activityOmitted?.[goal.id] ?? 0;
|
|
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)}.`;
|
|
384
|
+
const live = window !== "now"
|
|
385
|
+
? []
|
|
386
|
+
: snapshot.observed === undefined
|
|
387
|
+
? [
|
|
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."
|
|
389
|
+
]
|
|
390
|
+
: snapshot.observedOmitted === undefined
|
|
391
|
+
? []
|
|
392
|
+
: [`Live state shown for the most recently updated goals; ${String(snapshot.observedOmitted)} left out.`];
|
|
393
|
+
return [when, ...live];
|
|
394
|
+
};
|
|
326
395
|
const familyForGoal = (snapshot, goalId) => (snapshot.families ?? []).find((group) => group.canonicalGoalId === goalId) ?? null;
|
|
327
396
|
const familyLabelForGoal = (snapshot, goal) => {
|
|
328
397
|
const group = familyForGoal(snapshot, goal.id);
|
|
@@ -335,7 +404,7 @@ const withFragment = (href, fragment) => {
|
|
|
335
404
|
const fragmentIndex = href.indexOf("#");
|
|
336
405
|
return `${fragmentIndex === -1 ? href : href.slice(0, fragmentIndex)}#${fragment}`;
|
|
337
406
|
};
|
|
338
|
-
export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId, initialWindow = "now", navigation, snapshots }) => {
|
|
407
|
+
export const WorkBoard = ({ decisions, externalLinks = "enabled", host, initialGoalId, initialWindow = "now", navigation, snapshots }) => {
|
|
339
408
|
const [window, setWindow] = useState(initialWindow);
|
|
340
409
|
const snapshot = snapshotFor(snapshots, window);
|
|
341
410
|
const directInitialGoal = snapshot.goals.find(({ id }) => id === initialGoalId);
|
|
@@ -358,6 +427,9 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
358
427
|
const selectedLinkRowRef = useRef(null);
|
|
359
428
|
const selectedRowRef = useRef(null);
|
|
360
429
|
const triage = workTriage(snapshot);
|
|
430
|
+
const finishedGoalCount = triage.rows.filter(({ displayState }) => displayState === "completed" || displayState === "deployed" || displayState === "abandoned").length;
|
|
431
|
+
const hasGoalOmissions = (snapshot.goalsOmitted ?? 0) > 0 || (snapshot.finishedOmitted ?? 0) > 0;
|
|
432
|
+
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;
|
|
361
433
|
// The overlay by goal, built once per snapshot: rows, captions and filters all read it.
|
|
362
434
|
const overlay = new Map((snapshot.observed ?? []).map((entry) => [entry.goalId, entry]));
|
|
363
435
|
const observedOf = (goal) => overlay.get(goal.id) ?? null;
|
|
@@ -365,9 +437,7 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
365
437
|
// A historical window is the state as of its time; say so, never in the present tense.
|
|
366
438
|
const tense = window === "now" ? "present" : "past";
|
|
367
439
|
const sentence = workTriageSentence(triage.summary, tense);
|
|
368
|
-
const heroFact = tense === "
|
|
369
|
-
? sentence
|
|
370
|
-
: `As of ${formatTimestamp(snapshot.asOf)}, ${sentence.charAt(0).toLowerCase()}${sentence.slice(1)}`;
|
|
440
|
+
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);
|
|
371
441
|
const groupById = new Map(triage.rows.map((row) => [row.goal.id, row.group]));
|
|
372
442
|
const ordered = triage.rows.map((row) => row.goal);
|
|
373
443
|
// Filters match what each row shows: the observed state where there is one.
|
|
@@ -400,9 +470,25 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
400
470
|
setVisibleGoalCount(initialVisibleGoalCount);
|
|
401
471
|
}, [selectedFilteredGoalIndex, selectedId]);
|
|
402
472
|
const goalRow = (goal) => {
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
|
|
473
|
+
const observed = observedOf(goal);
|
|
474
|
+
const shown = shownOf(goal);
|
|
475
|
+
const caption = rowCaption(goal, observed, externalLinks === "enabled" && window === "now" ? decisions : undefined);
|
|
476
|
+
const open = requestsFor(goal).filter((request) => request.state === "open");
|
|
477
|
+
const host = observed?.agent?.fact.host ?? goal.connectTarget?.host ?? goal.agentHierarchy?.agent.host;
|
|
478
|
+
const agentName = goal.agentHierarchy?.agent.name ?? goal.owner.name;
|
|
479
|
+
const pr = observed?.pullRequest?.fact;
|
|
480
|
+
const terminal = shown === "completed" || shown === "deployed" || shown === "abandoned";
|
|
481
|
+
const stage = pr === undefined
|
|
482
|
+
? `Stage: ${deliveryLabel[goal.delivery]}`
|
|
483
|
+
: `PR #${pr.pullRequest} ${pullRequestStateLabel[pr.state]}, ${checksLabel[pr.checks]}${pr.review === "not_requested" ? "" : ` · Review ${reviewPresentation[pr.review].label.toLowerCase()}`}`;
|
|
484
|
+
const metadata = goal.owner.name === "Unassigned"
|
|
485
|
+
? `Unassigned · ${goal.repository.repository}`
|
|
486
|
+
: terminal
|
|
487
|
+
? `${agentName} · ${shown === "abandoned" ? "" : goal.delivery === "merged" || pr?.state === "merged" ? "Merged · " : "Done · "}${displayPresentation[shown].label} ${ageOf(pr?.closedAt ?? goal.updatedAt, snapshot.asOf)}`
|
|
488
|
+
: `${agentName}${host === undefined ? "" : ` on ${host}`} · ${stage}${blockersFor(goal).length === 0 || open.length > 0 ? "" : ` · ${blockersFor(goal)[0]?.summary ?? "Blocked"}`}`;
|
|
489
|
+
const hasCaption = open.length > 0 || observed?.stale === true || observed?.unknown != null;
|
|
490
|
+
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] }));
|
|
491
|
+
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: () => {
|
|
406
492
|
setDetailsOpen(true);
|
|
407
493
|
setSelectedId(goal.id);
|
|
408
494
|
}, 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({
|
|
@@ -416,6 +502,7 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
416
502
|
}),
|
|
417
503
|
window
|
|
418
504
|
}), id: selected?.id === goal.id ? selectedGoalFragment : undefined, ref: selected?.id === goal.id ? selectedLinkRowRef : undefined, children: content }));
|
|
505
|
+
return control;
|
|
419
506
|
};
|
|
420
507
|
const timeTravel = (_jsx("div", { "aria-label": "Choose work snapshot", className: "work-time-controls", role: "group", children: windows.map((option) => {
|
|
421
508
|
if (navigation === undefined) {
|
|
@@ -449,12 +536,12 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
449
536
|
}) }));
|
|
450
537
|
const statusFilterControls = (_jsx("div", { "aria-label": "Filter goals by status", className: "work-status-filters", role: "group", children: statusFilters.map((state) => {
|
|
451
538
|
const label = state === "all" ? "All" : statePresentation[state].label;
|
|
452
|
-
return navigation === undefined ? (_jsx("button", { "aria-pressed": statusFilter === state, className: "work-status-filter", onClick: () => {
|
|
539
|
+
return navigation === undefined ? (_jsx("button", { "aria-pressed": statusFilter === state, className: "work-status-filter", onFocus: ({ currentTarget }) => currentTarget.scrollIntoView({ block: "nearest", inline: "nearest" }), onClick: () => {
|
|
453
540
|
setDetailsOpen(false);
|
|
454
541
|
setSelectedId(null);
|
|
455
542
|
setStatusFilter(state);
|
|
456
543
|
setVisibleGoalCount(initialVisibleGoalCount);
|
|
457
|
-
}, type: "button", children: label }, state)) : (_jsx("a", { "aria-current": statusFilter === state ? "page" : undefined, className: "work-status-filter", href: navigation({
|
|
544
|
+
}, 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({
|
|
458
545
|
goalId: encodeWorkBoardNavigationGoal({
|
|
459
546
|
detailsOpen: false,
|
|
460
547
|
goalId: null,
|
|
@@ -464,13 +551,35 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
464
551
|
window
|
|
465
552
|
}), children: label }, state));
|
|
466
553
|
}) }));
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
554
|
+
const closeControl = (label) => navigation === undefined ? (_jsx(Button, { onClick: () => {
|
|
555
|
+
setDetailsOpen(false);
|
|
556
|
+
(selectedRowRef.current ?? selectedLinkRowRef.current)?.focus();
|
|
557
|
+
}, 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({
|
|
558
|
+
goalId: encodeWorkBoardNavigationGoal({
|
|
559
|
+
detailsOpen: false,
|
|
560
|
+
goalId: selectedId,
|
|
561
|
+
statusFilter,
|
|
562
|
+
visibleGoalCount
|
|
563
|
+
}),
|
|
564
|
+
window
|
|
565
|
+
}), selectedGoalFragment), children: label }));
|
|
566
|
+
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)
|
|
567
|
+
.slice(1)
|
|
568
|
+
.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: () => {
|
|
569
|
+
setWindow("now");
|
|
570
|
+
setDetailsOpen(false);
|
|
571
|
+
setSelectedId(null);
|
|
572
|
+
setStatusFilter("all");
|
|
573
|
+
setVisibleGoalCount(initialVisibleGoalCount);
|
|
574
|
+
}, 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"
|
|
575
|
+
? filteredGoals.length + (snapshot.goalsOmitted ?? 0)
|
|
576
|
+
: filteredGoals.length === 0
|
|
577
|
+
? `0 of ${snapshot.goals.length + (snapshot.goalsOmitted ?? 0)}`
|
|
578
|
+
: 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"
|
|
579
|
+
? "Planned"
|
|
580
|
+
: group === "needs-you" && tense === "past"
|
|
581
|
+
? "Needed you"
|
|
582
|
+
: 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({
|
|
474
583
|
goalId: encodeWorkBoardNavigationGoal({
|
|
475
584
|
detailsOpen,
|
|
476
585
|
goalId: selectedId,
|
|
@@ -478,18 +587,6 @@ export const WorkBoard = ({ decisions, externalLinks = "enabled", initialGoalId,
|
|
|
478
587
|
visibleGoalCount: Math.min(visibleGoalCount + initialVisibleGoalCount, filteredGoals.length)
|
|
479
588
|
}),
|
|
480
589
|
window
|
|
481
|
-
}), children: "Load 10 more" }))) : null] })
|
|
482
|
-
setDetailsOpen(false);
|
|
483
|
-
const selectedRow = selectedRowRef.current ?? selectedLinkRowRef.current;
|
|
484
|
-
selectedRow?.focus();
|
|
485
|
-
}, variant: "secondary", children: "Close details" })) : (_jsx("a", { className: "work-exact-link", href: withFragment(navigation({
|
|
486
|
-
goalId: encodeWorkBoardNavigationGoal({
|
|
487
|
-
detailsOpen: false,
|
|
488
|
-
goalId: selectedId,
|
|
489
|
-
statusFilter,
|
|
490
|
-
visibleGoalCount
|
|
491
|
-
}),
|
|
492
|
-
window
|
|
493
|
-
}), 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 }) }))] }))] }));
|
|
590
|
+
}), children: "Load 10 more" }))) : null] })] }))] }));
|
|
494
591
|
};
|
|
495
592
|
//# sourceMappingURL=view.js.map
|