toolroll 0.9.5 โ 0.9.6
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/CHANGELOG.md +11 -0
- package/dist/assignment-presentation.d.ts +13 -2
- package/dist/assignment-presentation.js +22 -9
- package/dist/assignment-ui.d.ts +10 -15
- package/dist/assignment-ui.js +17 -18
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -20
- package/dist/browser-workspace.d.ts +3 -10
- package/dist/chat-acceptance.js +2 -2
- package/dist/chat-quiet.js +20 -19
- package/dist/demo-chat.d.ts +0 -5
- package/dist/demo-chat.js +10 -2
- package/dist/lead-status.d.ts +7 -0
- package/dist/lead-status.js +11 -2
- package/dist/serve.js +64 -66
- package/dist/task-status.d.ts +179 -0
- package/dist/task-status.js +317 -0
- package/dist/telegram-progress.js +62 -100
- package/dist/telegram-status.js +19 -6
- package/dist/work-index.js +25 -9
- package/dist/workspace-ui.d.ts +7 -0
- package/dist/workspace-ui.js +30 -1
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@ import type { Store } from './store.js';
|
|
|
8
8
|
import type { WorkSummaryAccess } from './work-summary.js';
|
|
9
9
|
import type { StatusTone } from './workspace-ui.js';
|
|
10
10
|
import type { AssignmentCard } from './assignment-ui.js';
|
|
11
|
+
import type { TaskStatus } from './task-status.js';
|
|
11
12
|
import type { FirstRunStep, FirstTaskSuggestion } from './first-run.js';
|
|
12
13
|
export type BrowserProject = {
|
|
13
14
|
name: string;
|
|
@@ -365,12 +366,6 @@ export type BrowserProjectsView = {
|
|
|
365
366
|
};
|
|
366
367
|
};
|
|
367
368
|
export type BrowserResultTab = 'summary' | 'changes' | 'checks';
|
|
368
|
-
export type BrowserResultChip = {
|
|
369
|
-
tone: 'success' | 'danger' | 'warning' | 'info' | 'neutral';
|
|
370
|
-
label: string;
|
|
371
|
-
icon: 'check' | 'x' | null;
|
|
372
|
-
title: string | null;
|
|
373
|
-
};
|
|
374
369
|
/** The shared result panel in parts. Each tab's content, the feedback
|
|
375
370
|
* section and the learning card are the server's own HTML; the page script
|
|
376
371
|
* binds to the same data attributes and ids (tabs, drafts, line notes). */
|
|
@@ -378,10 +373,8 @@ export type BrowserResultPanel = {
|
|
|
378
373
|
attributes: Record<string, string>;
|
|
379
374
|
heading: string;
|
|
380
375
|
outcome: string;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
by: string | null;
|
|
384
|
-
} | null;
|
|
376
|
+
/** The shared task status (task-status.ts): headline, sentence and detail rows. */
|
|
377
|
+
status: TaskStatus | null;
|
|
385
378
|
reviewHistory: string | null;
|
|
386
379
|
attention: string[];
|
|
387
380
|
/** Requirements only a person can confirm, in plain words, and the one
|
package/dist/chat-acceptance.js
CHANGED
|
@@ -6,7 +6,7 @@ export function acceptanceStatus(proof, accepted) {
|
|
|
6
6
|
if (accepted)
|
|
7
7
|
return manualReviewOnly(proof) ? "Accepted by a person" : "Accepted with an exception";
|
|
8
8
|
if (manualReviewOnly(proof))
|
|
9
|
-
return "
|
|
9
|
+
return "You check the remaining requirements";
|
|
10
10
|
return proof === null ? "No checks recorded" : ({ verified: "Checks passed", attested: "Checks reported by the agent", short: "Some required material is missing", refuted: "Saved result conflicts with the approved scope" }[proof.verdict] ?? "Some required material is missing");
|
|
11
11
|
}
|
|
12
12
|
export function readAcceptanceEvidence(store, who, root, task, runId, offset = 0) {
|
|
@@ -75,7 +75,7 @@ export function readAcceptanceEvidence(store, who, root, task, runId, offset = 0
|
|
|
75
75
|
accepted: acceptance !== null, acceptance: acceptance === null ? null : { at: acceptance.acceptedAt, note: brief(acceptance.note) },
|
|
76
76
|
reasons: (recorded?.reasons ?? []).map(one => brief(one)), gate,
|
|
77
77
|
criteriaTotal: matrix.length, nextCriterionOffset: offset + page.length < matrix.length ? offset + page.length : null,
|
|
78
|
-
criteria: page.map(row => ({ id: row.id, requirement: brief(row.statement), state: row.state === "manual-review" ? "
|
|
78
|
+
criteria: page.map(row => ({ id: row.id, requirement: brief(row.statement), state: row.state === "manual-review" ? "You check" : row.state,
|
|
79
79
|
details: row.detail.map(one => brief(one)), requiredEvidence: row.requiredEvidence,
|
|
80
80
|
reviewer: row.review == null ? null : { judgement: row.review.judgement, note: brief(row.review.note) },
|
|
81
81
|
evidence: row.answered.map(one => ({ kind: one.kind, reference: brief(one.ref) })),
|
package/dist/chat-quiet.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { assignmentOf } from "./assignment.js";
|
|
8
8
|
import { chatControlHref } from "./chat-controls.js";
|
|
9
9
|
import { telegramProgressCard } from "./telegram-progress.js";
|
|
10
|
+
import { assignmentStatusFacts, headlineEmoji, taskStatusOf } from "./task-status.js";
|
|
10
11
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
11
12
|
import { isLifecycleNotification } from "./store.js";
|
|
12
13
|
/** What always makes a new message, even in quiet mode: a question or approval waiting, a failure or other
|
|
@@ -37,15 +38,16 @@ function cardRun(store, taskRef) {
|
|
|
37
38
|
return store.runsFor(taskRef).find(run => run.role === "builder" && run.contestant === null) ?? null;
|
|
38
39
|
}
|
|
39
40
|
/** Before any attempt starts, the card says where the task stands in the words of its last lifecycle fact. */
|
|
41
|
+
const before = (headline) => ({ icon: headlineEmoji(headline), status: headline });
|
|
40
42
|
const BEFORE_RUN = {
|
|
41
|
-
"task-filed":
|
|
42
|
-
"scope-approved":
|
|
43
|
-
"approval-withdrawn":
|
|
44
|
-
"task-held":
|
|
45
|
-
"task-released":
|
|
46
|
-
"task-queued":
|
|
47
|
-
"task-requeued":
|
|
48
|
-
"task-cancelled":
|
|
43
|
+
"task-filed": before("Queued"),
|
|
44
|
+
"scope-approved": before("Queued"),
|
|
45
|
+
"approval-withdrawn": before("Needs you"),
|
|
46
|
+
"task-held": before("Stopped"),
|
|
47
|
+
"task-released": before("Queued"),
|
|
48
|
+
"task-queued": before("Queued"),
|
|
49
|
+
"task-requeued": before("Queued"),
|
|
50
|
+
"task-cancelled": before("Stopped"),
|
|
49
51
|
};
|
|
50
52
|
function taskLine(store, taskRef, now, root) {
|
|
51
53
|
const ref = store.refById(taskRef);
|
|
@@ -85,10 +87,8 @@ export function quietCardView(store, taskRefs, now, root) {
|
|
|
85
87
|
export function readyPingView(store, run, taskId, project, now, root) {
|
|
86
88
|
const card = telegramProgressCard(store, run, taskId, project, now, root);
|
|
87
89
|
const [title = "", shown = ""] = card.entities.map(one => card.text.slice(one.offset, one.offset + one.length));
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const heading = saved ? "โ
Ready" : shown;
|
|
91
|
-
const next = card.next !== "" ? card.next : saved ? "Next: open the result, then mark it complete or request changes." : "";
|
|
90
|
+
const heading = shown;
|
|
91
|
+
const next = card.next;
|
|
92
92
|
const first = `${heading} ยท ${title}`;
|
|
93
93
|
return { text: next === "" ? first : `${first}\n${next}`, entities: [{ type: "bold", offset: 0, length: heading.length }], link: card.link };
|
|
94
94
|
}
|
|
@@ -120,18 +120,19 @@ export function eveningDigestText(store, account, now, root) {
|
|
|
120
120
|
failed.set(row.taskId, title(row.taskId));
|
|
121
121
|
}
|
|
122
122
|
for (const taskId of new Set(facts.map(row => row.taskId).filter((one) => one !== null))) {
|
|
123
|
-
let
|
|
123
|
+
let headline = null;
|
|
124
124
|
try {
|
|
125
125
|
const project = facts.find(row => row.taskId === taskId).project;
|
|
126
|
-
|
|
126
|
+
const assignment = assignmentOf(store, taskId, now, { principal: "operator", repos: [project] }, root);
|
|
127
|
+
headline = assignment === null ? null : taskStatusOf(assignmentStatusFacts(assignment)).headline;
|
|
127
128
|
}
|
|
128
129
|
catch {
|
|
129
|
-
|
|
130
|
+
headline = null;
|
|
130
131
|
}
|
|
131
|
-
if (
|
|
132
|
-
waits.set(taskId, `${title(taskId)} ยท
|
|
133
|
-
else if (
|
|
134
|
-
|
|
132
|
+
if (headline === "Ready for review" || headline === "Needs you")
|
|
133
|
+
waits.set(taskId, `${title(taskId)} ยท ${headline}`);
|
|
134
|
+
else if (headline === "Failed")
|
|
135
|
+
failed.set(taskId, title(taskId));
|
|
135
136
|
}
|
|
136
137
|
if (finished.size + failed.size + waits.size === 0)
|
|
137
138
|
return null;
|
package/dist/demo-chat.d.ts
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat in `toolroll demo`: the scripted lead's conversation (src/demo.ts),
|
|
3
|
-
* rendered as plain server HTML with native forms. One small script keeps a
|
|
4
|
-
* running build's progress live; without it the page still works by reload.
|
|
5
|
-
*/
|
|
6
1
|
import type { DemoExchange } from "./demo.js";
|
|
7
2
|
/** What a finished exchange's stored result holds, read back from its evidence. */
|
|
8
3
|
export type DemoResultView = {
|
package/dist/demo-chat.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat in `toolroll demo`: the scripted lead's conversation (src/demo.ts),
|
|
3
|
+
* rendered as plain server HTML with native forms. One small script keeps a
|
|
4
|
+
* running build's progress live; without it the page still works by reload.
|
|
5
|
+
*/
|
|
6
|
+
import { headlineOf } from "./task-status.js";
|
|
1
7
|
const SUGGESTIONS = ["Fix the flaky refund test", "Rewrite the empty Payouts page", "Put the new invoice view behind a flag"];
|
|
2
8
|
const esc = (text) => text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3
9
|
const hidden = (csrf) => `<input type="hidden" name="csrf" value="${esc(csrf)}">`;
|
|
@@ -52,9 +58,11 @@ function diffHtml(diff) {
|
|
|
52
58
|
function resultCard(exchange, csrf, result) {
|
|
53
59
|
if (result === null)
|
|
54
60
|
return `<section class="card demo-result"><p class="meta">This result's saved evidence is unavailable.</p></section>`;
|
|
55
|
-
const status = exchange.state === "complete" ? `<span class="badge demo-complete">Complete</span>`
|
|
56
|
-
: exchange.state === "sent-back" ? `<span class="badge">Sent back</span>` : `<span class="badge demo-ready">Ready</span>`;
|
|
57
61
|
const passed = result.checks.status === "passed";
|
|
62
|
+
// The shared headline (task-status.ts): the same words as every real task.
|
|
63
|
+
const headline = headlineOf({ stage: exchange.state === "complete" ? "complete" : exchange.state === "sent-back" ? "building" : "finished",
|
|
64
|
+
checks: { status: passed ? "passed" : "failed", exitCode: null, head: null } });
|
|
65
|
+
const status = `<span class="badge${headline === "Complete" ? " demo-complete" : headline === "Ready for review" ? " demo-ready" : ""}" data-headline="${headline}">${headline}</span>`;
|
|
58
66
|
const summary = `${result.files} file${result.files === 1 ? "" : "s"} changed ยท +${result.additions} โ${result.deletions} ยท ` +
|
|
59
67
|
`<span class="${passed ? "demo-pass" : "demo-fail"}">${esc(result.checks.detail)}</span>`;
|
|
60
68
|
const actions = exchange.state === "ready"
|
package/dist/lead-status.d.ts
CHANGED
|
@@ -69,6 +69,13 @@ export type InstallationStatus = {
|
|
|
69
69
|
command: string;
|
|
70
70
|
since: string;
|
|
71
71
|
}[];
|
|
72
|
+
/** The most urgent tasks with the shared headline and sentence (task-status.ts), as every other surface words them. */
|
|
73
|
+
tasks: {
|
|
74
|
+
task: string;
|
|
75
|
+
title: string;
|
|
76
|
+
headline: string;
|
|
77
|
+
sentence: string;
|
|
78
|
+
}[];
|
|
72
79
|
};
|
|
73
80
|
/** One indexed observation for `task wait`. `watchedRun` is the attempt the
|
|
74
81
|
* caller first saw. Each poll follows the task's newest attempt from there, so
|
package/dist/lead-status.js
CHANGED
|
@@ -4,6 +4,7 @@ import { COMPLETION_ACTION } from "./result-completion.js";
|
|
|
4
4
|
import { windowLabel } from "./provider-limits.js";
|
|
5
5
|
import { openAuthPauses, signInCommand, signInReason } from "./provider-auth.js";
|
|
6
6
|
import { BUILT_IN, checkSuitesOf } from "./store.js";
|
|
7
|
+
import { workIndexPage } from "./work-index.js";
|
|
7
8
|
const maybeNumber = (value) => value === null || value === undefined || !Number.isInteger(Number(value)) ? null : Number(value);
|
|
8
9
|
const CHECK_COLUMNS = "rc.status AS check_status, rc.exit_code AS check_exit_code, rc.suites_json AS check_suites_json";
|
|
9
10
|
function checkOf(row) {
|
|
@@ -253,6 +254,8 @@ export function installationStatus(store, now) {
|
|
|
253
254
|
observedAt: String(row["observed_at"]),
|
|
254
255
|
})),
|
|
255
256
|
signIn: pauses.map(one => ({ provider: one.provider, reason: signInReason(one), command: signInCommand(one), since: one.openedAt })),
|
|
257
|
+
tasks: workIndexPage(store, now, { principal: "operator", repos: null, includeUnplaced: true }, { limit: 8 }).items
|
|
258
|
+
.map(one => ({ task: one.rootId, title: one.title, headline: one.status.label, sentence: one.status.detail })),
|
|
256
259
|
};
|
|
257
260
|
}
|
|
258
261
|
export function renderInstallationStatus(status) {
|
|
@@ -260,7 +263,13 @@ export function renderInstallationStatus(status) {
|
|
|
260
263
|
// A paused provider comes first: it is the one thing a person must do.
|
|
261
264
|
for (const one of status.signIn)
|
|
262
265
|
lines.push(`${one.reason} โ run \`${one.command}\`. Its tasks wait until then.`);
|
|
263
|
-
|
|
266
|
+
// Each task's one headline, the same words as the console and chat cards.
|
|
267
|
+
if (status.tasks.length > 0) {
|
|
268
|
+
lines.push("Tasks:");
|
|
269
|
+
for (const one of status.tasks)
|
|
270
|
+
lines.push(` ${one.headline.padEnd(16)} ${one.title} (${one.task}) โ ${one.sentence}`);
|
|
271
|
+
}
|
|
272
|
+
lines.push(status.running.count === 0 ? "Building: none" : `Building: ${status.running.count} โ ${status.running.tasks.map(one => `${one.task} (#${one.run}, ${one.phase})`).join(", ")}${status.running.count > status.running.tasks.length ? ", โฆ" : ""}`);
|
|
264
273
|
lines.push(status.queued.count === 0 ? "Queued: none" : `Queued: ${status.queued.count} โ ${status.queued.tasks.map(one => `${one.task} (${one.reason})`).join(", ")}${status.queued.count > status.queued.tasks.length ? ", โฆ" : ""}`);
|
|
265
274
|
lines.push(status.waitingForReview.count === 0 ? "Ready for review: none" : `Ready for review: ${status.waitingForReview.count} โ ${status.waitingForReview.results.map(one => `${one.task} (#${one.run})`).join(", ")}${status.waitingForReview.count > status.waitingForReview.results.length ? ", โฆ" : ""}`);
|
|
266
275
|
if (status.releaseCheck === null) {
|
|
@@ -289,7 +298,7 @@ export function renderInstallationStatus(status) {
|
|
|
289
298
|
lines.push(`Plan windows: ${provider} โ ${detail}`);
|
|
290
299
|
}
|
|
291
300
|
}
|
|
292
|
-
return lines.slice(0, 12 + status.signIn.length);
|
|
301
|
+
return lines.slice(0, 12 + status.signIn.length + (status.tasks.length === 0 ? 0 : status.tasks.length + 1));
|
|
293
302
|
}
|
|
294
303
|
export function renderTaskWait(snapshot, outcome = snapshot.outcome) {
|
|
295
304
|
const check = snapshot.check.status === "unknown" ? "checks unknown" : snapshot.check.status === "not-run" ? "checks not run" : `checks ${snapshot.check.status}${snapshot.check.exitCode === null ? "" : ` (exit ${snapshot.check.exitCode})`}`;
|
package/dist/serve.js
CHANGED
|
@@ -64,6 +64,7 @@ import { checkPublishing, completeAndOpenPullRequest, mergeAsPerson, pullRequest
|
|
|
64
64
|
import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
65
65
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
66
66
|
import { assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
67
|
+
import { pullRequestFactOf, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
67
68
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
68
69
|
import { ledgerBody } from "./ledger-view.js";
|
|
69
70
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -152,7 +153,7 @@ import { starterRecipes, savedRecipes, findRecipe, importRecipe, exportRecipe, c
|
|
|
152
153
|
import { recipeFromForm, recipeLibraryHtml, recipeEditorHtml, workflowPreviewHtml, recipeScript, RECIPE_CSS, recipeDefinitionPreviewHtml, recipeRunHtml, recipeAnswersFromForm } from "./recipe-ui.js";
|
|
153
154
|
import { EVIDENCE_CAPS, readVerifiedArtifact, readVerifiedReport, readVerifiedProofForRun, storeEvidence, writeEvidenceFile, scanForSecrets } from "./evidence.js";
|
|
154
155
|
import { GOAL_ASSESSMENT_PENDING, reviewConflict, manualReviewOnly, manualReviewCriterionOf, personCheckWords, plainReasonWords, dispatchStatusToken, passFraction, semanticCoverage, coverageWords, coverageStateWords } from "./proof.js";
|
|
155
|
-
import { WORK_VIEWS, REVIEW_TOKENS, parseWorkView, resultStatusOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
156
|
+
import { WORK_VIEWS, REVIEW_TOKENS, parseWorkView, resultStatusOf, resultHeadlineOf, receiptHeadingOf, receiptPublicationWords, reviewFactsOf, workStatusOf, primaryDestinationOf, needsPerson, dispatchActionLabel, } from "./workspace-ui.js";
|
|
156
157
|
import { PRICED_BUILD_MODELS } from "./pricing.js";
|
|
157
158
|
import { RESULT_TABS, RESULT_REVIEW_SCRIPT, isRevisionFeedback, evidenceResultStatusOf, evidenceShortenedWords, parseResultTab, resultLeadOf, evidenceProblemDetailsOf, evidenceHealthOf, resultFactsAttributes, resultFactsAttributeMap, resultReturnTarget, commentSourceKey, REQUEST_TOKEN, revisionBatchOf, parseRevisionBatch, revisionSourceOf, } from "./result-review.js";
|
|
158
159
|
import { parseReport } from "./scout-report.js";
|
|
@@ -4907,8 +4908,6 @@ export function createDecisionServer(options) {
|
|
|
4907
4908
|
const result = receipt === undefined && task.state === "done" && latest !== null && (latest.outcome === "built" || latest.outcome === "no-change")
|
|
4908
4909
|
? completionReceiptView(store, latest, store.artifactsFor(latest.id), evidenceRoot, reviewFactsFor(latest.id)) : receipt;
|
|
4909
4910
|
const status = workStatusOf(facts, result == null ? undefined : receiptStatusOf(result));
|
|
4910
|
-
if (store.lookupRef(task.id)?.revisionOf !== null && live !== null && status.tone === "live")
|
|
4911
|
-
status.label = "Revising";
|
|
4912
4911
|
const earlierLive = task.family === undefined ? [] : earlierLiveVersions(task.family, now);
|
|
4913
4912
|
if (earlierLive.length > 0 && !status.views.includes("running"))
|
|
4914
4913
|
status.views = [...status.views, "running"];
|
|
@@ -5495,8 +5494,12 @@ export function createDecisionServer(options) {
|
|
|
5495
5494
|
const publishing = publishingOf(store, repo);
|
|
5496
5495
|
const target = publishing.on ? publishing.base : null;
|
|
5497
5496
|
const view = newestPullRequestOf(store, family === null ? [taskId] : family.versions.map(one => one.id));
|
|
5498
|
-
if (view !== null)
|
|
5499
|
-
|
|
5497
|
+
if (view !== null) {
|
|
5498
|
+
const publication = store.publicationForRun(view.runId);
|
|
5499
|
+
const fact = publication === null ? null : { ...pullRequestFactOf({ ...publication, merged: view.state === "merged" }),
|
|
5500
|
+
note: view.state === "merged" ? `${view.detail}${view.mergeCommit === null ? "" : ` Merge commit ${view.mergeCommit.slice(0, 12)}.`}` : null };
|
|
5501
|
+
return { taskId: rootId, view: { ...view, canMerge: view.canMerge && who.role === "approver" && who.via === "cookie" }, offer: null, target, fact };
|
|
5502
|
+
}
|
|
5500
5503
|
if (!publishing.on || who.via !== "cookie" || who.role !== "approver")
|
|
5501
5504
|
return null;
|
|
5502
5505
|
const assignment = assignmentOf(store, taskId, clock(), { principal: "operator", repos: admissionList(), includeUnplaced: visible(null) }, evidenceRoot);
|
|
@@ -11242,7 +11245,8 @@ export function editorFileHref(worktree, path, line) {
|
|
|
11242
11245
|
* chat (v39, extending Priority 2's "one surface, six places" rule from
|
|
11243
11246
|
* the verdict word to the per-criterion state). */
|
|
11244
11247
|
function matrixStateBadge(state) {
|
|
11245
|
-
|
|
11248
|
+
// Red belongs to a Failed headline alone (task-status.ts); an unmet requirement is an amber note.
|
|
11249
|
+
const cls = state === "pass" ? "badge-done" : state === "failed" ? "badge-note" : state === "missing" ? "badge-note" : "badge-manual-review";
|
|
11246
11250
|
const word = state === "pass" ? "pass" : state === "missing" ? "missing" : state === "failed" ? "failed" : "manual review";
|
|
11247
11251
|
return `<span class="badge ${cls}" data-matrix-state="${escape(state)}">${escape(word)}</span>`;
|
|
11248
11252
|
}
|
|
@@ -11328,7 +11332,7 @@ function criterionMatrixHtml(matrix, options = {}) {
|
|
|
11328
11332
|
if (matrix.length === 0)
|
|
11329
11333
|
return "";
|
|
11330
11334
|
const kinds = {
|
|
11331
|
-
check: "Checks", "changed-path": "Changed files", screenshot: "Screenshots", "manual-review": "
|
|
11335
|
+
check: "Checks", "changed-path": "Changed files", screenshot: "Screenshots", "manual-review": "You check",
|
|
11332
11336
|
};
|
|
11333
11337
|
const evidenceLink = (a) => {
|
|
11334
11338
|
const text = `<code>${escape(a.ref)}</code>`;
|
|
@@ -11344,8 +11348,9 @@ function criterionMatrixHtml(matrix, options = {}) {
|
|
|
11344
11348
|
const state = row.state;
|
|
11345
11349
|
const awaitingAssessment = row.assessment?.evidenceState === "pass" && row.review === null;
|
|
11346
11350
|
const confirmed = row.assessment !== undefined && state === "pass";
|
|
11347
|
-
|
|
11348
|
-
const
|
|
11351
|
+
// Plain words (task-status brief): the retired assessment step is not a state a person acts on.
|
|
11352
|
+
const label = awaitingAssessment ? "Met" : confirmed ? "Confirmed" : state === "pass" ? "Met" : state === "manual-review" ? "You check" : state === "missing" ? "Not shown yet" : "Not met";
|
|
11353
|
+
const cls = awaitingAssessment ? "badge-done" : state === "pass" ? "badge-done" : state === "manual-review" ? "badge-manual-review" : "badge-note";
|
|
11349
11354
|
const warnings = [];
|
|
11350
11355
|
// Only replace the known boilerplate. Other recorded failure details
|
|
11351
11356
|
// remain verbatim, so concision cannot hide a different problem.
|
|
@@ -11978,6 +11983,10 @@ ${THEME_DARK}
|
|
|
11978
11983
|
background: color-mix(in srgb, var(--muted) 58%, var(--card));
|
|
11979
11984
|
color: color-mix(in srgb, var(--destructive) 68%, var(--foreground)); border-color: var(--border);
|
|
11980
11985
|
}
|
|
11986
|
+
.badge-note {
|
|
11987
|
+
background: color-mix(in srgb, var(--muted) 58%, var(--card));
|
|
11988
|
+
color: var(--so-warning, #ab6400); border-color: var(--border);
|
|
11989
|
+
}
|
|
11981
11990
|
.badge-overdue {
|
|
11982
11991
|
background: color-mix(in srgb, var(--muted) 58%, var(--card)); color: var(--foreground);
|
|
11983
11992
|
border-color: var(--border);
|
|
@@ -12818,8 +12827,9 @@ ${THEME_DARK}
|
|
|
12818
12827
|
.dispatch-status[data-dispatch-status="proof-refuted"] .dispatch-copy > strong::before,
|
|
12819
12828
|
.dispatch-status[data-dispatch-status="needs-verification"] .dispatch-copy > strong::before {
|
|
12820
12829
|
content: ""; display: inline-block; width: .45rem; height: .45rem; margin-right: .45rem; border-radius: 50%; vertical-align: .08rem;
|
|
12821
|
-
background: var(--
|
|
12830
|
+
background: var(--so-warning, #ab6400);
|
|
12822
12831
|
}
|
|
12832
|
+
.problem.dispatch-status[data-dispatch-status="proof-refuted"] .dispatch-copy > strong::before { background: var(--destructive); }
|
|
12823
12833
|
.dispatch-status[data-dispatch-status="needs-verification"] .dispatch-copy > strong::before { background: var(--muted-foreground); }
|
|
12824
12834
|
/* A review in flight (review fixes): the box reads neutral โ neither the
|
|
12825
12835
|
green of a settled verdict nor the red of a problem โ with the dot in
|
|
@@ -13320,7 +13330,7 @@ ${THEME_DARK}
|
|
|
13320
13330
|
.pull-request-head strong { font-size: 1.0625rem; font-weight: 600; color: var(--foreground); display: inline-flex; align-items: center; gap: .5rem; }
|
|
13321
13331
|
.pull-request-head strong::before { content: ""; width: .5rem; height: .5rem; border-radius: 999px; background: var(--muted-foreground); }
|
|
13322
13332
|
.pull-request--ok .pull-request-head strong::before { background: var(--success, #15803d); }
|
|
13323
|
-
.pull-request--problem .pull-request-head strong::before { background: var(--
|
|
13333
|
+
.pull-request--problem .pull-request-head strong::before { background: var(--so-warning, #ab6400); }
|
|
13324
13334
|
.pull-request-act { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .5rem .75rem; margin: .25rem 0 0; }
|
|
13325
13335
|
.pull-request-act .meta { flex: 1 1 100%; }
|
|
13326
13336
|
.pull-request-act label { display: grid; gap: .25rem; flex: 1 1 12rem; margin: 0; }
|
|
@@ -14513,7 +14523,7 @@ button.pick-file { min-height: 1.75rem; padding: 0 .55rem; font-size: .75rem; }
|
|
|
14513
14523
|
const THEME_CONTROLS_CSS = `.task-repo select{width:100%;min-height:2.75rem;font-size:1rem}.task-repo-add{margin:.35rem .1rem .5rem}.task-repo-add a{display:inline-flex;align-items:center;min-height:2.25rem}details.result-request-open.result-request-form>summary{border:0;background:transparent;padding:.5rem 0;min-height:2.75rem;font-weight:600;display:list-item;list-style:revert}details.result-request-open.result-request-form>summary::-webkit-details-marker{display:revert}form.js-autosave button[type=submit]{display:none}.provider-row{border-bottom:1px solid var(--so-line);padding:.35rem 0}.provider-row:first-of-type{border-top:1px solid var(--so-line)}.provider-head{display:flex;align-items:center;gap:.75rem;margin:.4rem 0 0}.provider-status{display:inline-flex;align-items:center;gap:.4rem;color:var(--so-muted);font-size:.875rem}.provider-status i{width:.5rem;height:.5rem;border-radius:50%;background:var(--so-muted)}.provider-status--ok i{background:var(--so-success)}.provider-status--warn i{background:var(--so-attention)}.provider-status--off i{background:transparent;border:1.5px solid var(--so-muted)}details.provider-manage>summary{cursor:pointer;color:var(--so-accent-text);font-size:.875rem;min-height:2.5rem;display:list-item;padding-block:.5rem}.card.props .row{display:grid;gap:.1rem;margin:0 0 .75rem}.card.props .row>.meta{display:block;font-size:.75rem}.card.props .row>.meta::first-letter{text-transform:uppercase}.card.props .row>.mono{font-family:var(--font-sans);font-size:.875rem}.card.props .row>.mono .seal{font-family:var(--font-mono);font-size:.8125rem}details.evidence-files{margin:1rem 0}details.evidence-files>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:600}details.evidence-files ul{list-style:none;margin:0;padding:0}details.evidence-files li{display:flex;justify-content:space-between;gap:1rem;padding:.5rem 0;border-bottom:1px solid var(--so-line)}.result-action .result-feedback-link{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;text-decoration:none}@media(hover:hover) and (pointer:fine){.result-action .result-feedback-link:hover{background:var(--so-raised)}}.so-sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.verdict{margin:.5rem 0 .75rem}.verdict-chips{display:flex;flex-wrap:wrap;gap:.4rem;list-style:none;padding:0;margin:0}.verdict-chip{display:inline-flex;align-items:center;gap:.3rem;min-height:1.75rem;padding:.2rem .65rem;border-radius:999px;font-size:.8125rem;font-weight:600;background:var(--so-neutral-soft);color:var(--so-neutral-ink)}.verdict-chip svg{width:.9rem;height:.9rem}.verdict-chip--success{background:var(--so-success-soft);color:var(--so-success)}.verdict-chip--danger{background:var(--so-danger-soft);color:var(--so-danger)}.verdict-chip--warning{background:var(--so-warning-soft);color:var(--so-warning)}.verdict-chip--info{background:var(--so-info-soft);color:var(--so-info)}.verdict-by{margin:.4rem 0 0}details.result-request-open{margin:.5rem 0}details.result-request-open>summary{display:inline-flex;align-items:center;min-height:2.5rem;padding:.5rem 1rem;border:1px solid var(--so-input-line);border-radius:.5rem;background:var(--so-paper);color:var(--so-ink);font-weight:600;cursor:pointer;list-style:none}details.result-request-open>summary::-webkit-details-marker{display:none}details.result-request-open[open]>summary{margin-bottom:.75rem}.settings-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));gap:.5rem;margin:0 0 2rem}.settings-tiles a{display:flex;align-items:center;gap:.6rem;min-height:3rem;padding:.65rem .8rem;border:1px solid var(--so-line);border-radius:.625rem;background:var(--so-paper);color:var(--so-ink);text-decoration:none;font-weight:550;font-size:.875rem}@media(hover:hover) and (pointer:fine){.settings-tiles a:hover{border-color:var(--so-input-line);background:var(--so-raised)}}.settings-tiles svg{width:1.1rem;height:1.1rem;flex-shrink:0;color:var(--so-accent-text)}details.settings-more{margin:.25rem 0 1.25rem}details.settings-more>summary{cursor:pointer;min-height:2.75rem;display:list-item;padding-block:.7rem;font-weight:550}details.settings-more>summary .meta{font-weight:400;margin-left:.35rem}.settings-changed{margin-top:-.25rem}.appearance{margin:0 0 28px}.appearance h2{margin:0 0 10px}.theme-switch{display:inline-flex;flex-wrap:nowrap;max-width:100%;gap:4px;padding:4px;margin:0;border:1px solid var(--so-line);border-radius:10px;background:var(--so-raised)}.theme-switch .theme-choice,.so-native-region .theme-switch .theme-choice{flex:1 1 0;width:auto;white-space:nowrap;min-height:40px;padding:8px 16px;border:0;border-radius:7px;background:transparent;color:var(--so-muted);font:inherit;font-weight:550;box-shadow:none;cursor:pointer}@media(hover:hover) and (pointer:fine){.theme-switch .theme-choice:hover{color:var(--so-ink)}}.theme-switch .theme-choice[aria-pressed="true"]{background:var(--so-paper);color:var(--so-ink);box-shadow:0 1px 2px rgb(0 0 0 / .1)}.appearance .meta{margin:8px 0 0}@media(max-width:600px){.theme-switch .theme-choice{min-height:44px}}.update-notes{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;max-height:18rem;overflow:auto}`;
|
|
14514
14524
|
/** The page CSS this module writes itself (not the imported modules'), for the stylesheet contract tests. */
|
|
14515
14525
|
export const PAGE_CSS = STYLE + THEME_CONTROLS_CSS;
|
|
14516
|
-
const WORKSPACE_STYLE = styleAsset(STYLE + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
14526
|
+
const WORKSPACE_STYLE = styleAsset(STYLE + APPROVAL_RULES_CSS + SPEND_CSS + RETENTION_CSS + STORAGE_CSS + UPDATES_CSS + LIMITS_CSS + MONITORING_CSS + INTEGRATIONS_CSS + BACKUP_CSS + EXPORT_CSS + PROJECT_DELETE_CSS + POLICY_CSS + EVIDENCE_PACK_CSS + THEME_CONTROLS_CSS + CODING_CSS + CODING_SHIPPING_CSS + RECIPE_CSS + SKILLS_CSS + TOOLS_CSS + FLOWS_CSS + TEAMMATE_CSS + KITS_CSS + STARTERS_CSS + SSO_CSS + CREDENTIALS_CSS + KNOWLEDGE_CSS + MODELS_CSS + CHAT_POLISH_CSS + TRANSITIONS_CSS + WORKSPACE_MOTION_CSS + ASSIGNMENT_CSS + TASK_STATUS_CSS + LEAD_CONTEXT_CSS + PULL_REQUEST_SETTINGS_CSS + '.learning{min-width:0;overflow-wrap:anywhere}.learning .card{min-width:0}.learning code,.learning blockquote,.learning pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word}.learning button,.learning summary,.learning .button-link{min-height:44px}.learning button{white-space:nowrap}.learning summary{padding:12px 0;cursor:pointer}.learning form{margin:12px 0}.learning select{max-width:100%}.learning blockquote{margin:8px 0}.learning ul{padding-left:20px}');
|
|
14517
14527
|
/**
|
|
14518
14528
|
* The board's liveness: fetch this page's own fragment on a timer and swap
|
|
14519
14529
|
* it in place โ no flicker, no scroll reset, no long-lived stream to manage,
|
|
@@ -15975,7 +15985,7 @@ function chatFleetOverview(snapshot, projects, csrf, interactive) {
|
|
|
15975
15985
|
const dispatch = task.dispatch;
|
|
15976
15986
|
rows.push(`<a class="chat-overview-item decision" href="${taskHref(task.rootId ?? task.id)}" data-dispatch-status="${escape(dispatch.code)}">` +
|
|
15977
15987
|
`<span class="chat-overview-icon">${strokeIcon(`<path d="M12 8v4"/><path d="M12 16h.01"/><circle cx="12" cy="12" r="9"/>`)}</span>` +
|
|
15978
|
-
`<span class="chat-overview-copy"><strong>${escape(task.title)}</strong><span>${escape(projectOf(task.repoIndex))} ยท ${escape(task.id)} ยท ${escape(dispatch
|
|
15988
|
+
`<span class="chat-overview-copy"><strong>${escape(task.title)}</strong><span>${escape(projectOf(task.repoIndex))} ยท ${escape(task.id)} ยท ${escape(dispatchHeadline(dispatch))}</span></span>` +
|
|
15979
15989
|
`<span class="chat-overview-arrow" aria-hidden="true">โ</span></a>`);
|
|
15980
15990
|
}
|
|
15981
15991
|
for (const task of snapshot.tasks.filter(one => one.state === "failed").slice(0, Math.max(0, 3 - rows.length))) {
|
|
@@ -17212,6 +17222,11 @@ function statusActionHref(status, taskId, runId, prUrl, repo) {
|
|
|
17212
17222
|
}
|
|
17213
17223
|
/** The one status line every surface renders: a dot in the tone, the
|
|
17214
17224
|
* label, and the `data-work-status` token tests and CSS key off. */
|
|
17225
|
+
/** A not-yet-finished task's shared headline from its dispatch diagnosis (task-status.ts). */
|
|
17226
|
+
function dispatchHeadline(d) {
|
|
17227
|
+
const read = stageOfDispatch(d);
|
|
17228
|
+
return taskStatusOf({ stage: read.stage, ...(read.need === undefined ? {} : { need: read.need }) }).headline;
|
|
17229
|
+
}
|
|
17215
17230
|
function statusLineHtml(status, extra = "") {
|
|
17216
17231
|
return `<span class="status-line" data-work-status="${escape(status.token)}" data-tone="${escape(status.tone)}"><i class="status-dot" aria-hidden="true"></i><span class="status-label">${escape(status.label)}</span>${extra}</span>`;
|
|
17217
17232
|
}
|
|
@@ -17265,7 +17280,7 @@ function workPage(chrome, data) {
|
|
|
17265
17280
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
17266
17281
|
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}${row.repo === null ? '' : '&project=' + encodeURIComponent(row.repo)}`
|
|
17267
17282
|
: browserWorkActionHref(row);
|
|
17268
|
-
const needsYouDetail = row.status.views.includes('needs-you') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem;
|
|
17283
|
+
const needsYouDetail = (row.status.views.includes('needs-you') || row.status.label === 'Failed') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.label !== 'Ready for review' && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem;
|
|
17269
17284
|
return {
|
|
17270
17285
|
id: row.rootId, title: row.title, href: taskHref(row.rootId),
|
|
17271
17286
|
project: data.multiProject || row.repo === null ? (row.repo === null ? 'Unplaced' : projectName(row.repo)) : null,
|
|
@@ -18331,7 +18346,7 @@ function queueCard(one, csrf, revision, queueRevision, workers, column) {
|
|
|
18331
18346
|
? `<span class="badge">queued</span>`
|
|
18332
18347
|
: `<span class="badge">reserved for ${escape(column)}</span>`;
|
|
18333
18348
|
const chips = ` ${state}` +
|
|
18334
|
-
`${one.dispatch === null || one.dispatch === undefined ? "" : ` <a class="badge" href="${taskHref(one.id)}"
|
|
18349
|
+
`${one.dispatch === null || one.dispatch === undefined ? "" : ` <a class="badge" href="${taskHref(one.id)}" title="${escape(one.dispatch.summary)}">${escape(dispatchHeadline(one.dispatch))}</a>`}`;
|
|
18335
18350
|
const hidden = `<input type="hidden" name="csrf" value="${escape(csrf)}">` +
|
|
18336
18351
|
`<input type="hidden" name="projectRevision" value="${revision}">` +
|
|
18337
18352
|
`<input type="hidden" name="queueRevision" value="${queueRevision}">` +
|
|
@@ -18802,7 +18817,11 @@ function taskBodyParts(data) {
|
|
|
18802
18817
|
// data-work-status is the shared projection's token (package 1) โ
|
|
18803
18818
|
// the same one the Work row, chat, and cockpit carry for this task.
|
|
18804
18819
|
const workToken = resultStatus?.token ?? data.dispatch?.code ?? "unknown";
|
|
18805
|
-
|
|
18820
|
+
// A diagnosis under Task options: the exact technical reason, one tap
|
|
18821
|
+
// away from the shared headline (task-status.ts). Red only when that
|
|
18822
|
+
// headline is Failed.
|
|
18823
|
+
const headline = status.label;
|
|
18824
|
+
const box = (kind, title, detail, code, controls = "") => `<div class="${kind === "problem" && !inReview && headline === "Failed" ? "problem" : "answered"} dispatch-status" id="run-status" data-dispatch-status="${escape(code ?? title.toLowerCase().replace(/[^a-z0-9]+/g, "-"))}" data-work-status="${escape(workToken)}">` +
|
|
18806
18825
|
`<div class="dispatch-copy"><strong>${escape(inReview ? "Recorded checks" : title)}</strong><span class="meta">${detail}</span></div>${controls}</div>`;
|
|
18807
18826
|
if (task.state !== "done") {
|
|
18808
18827
|
const diagnosis = data.dispatch ?? null;
|
|
@@ -19689,7 +19708,13 @@ function taskBodyParts(data) {
|
|
|
19689
19708
|
: ` ยท filed via ${escape(data.filedVia)}`}${data.deliverable === "report" ? ` ยท <span class="badge">scout</span>` : ""}</p>`;
|
|
19690
19709
|
// A pull request that can be merged (or opened) owns the one primary action; the result stays in its section.
|
|
19691
19710
|
const pullRequestActs = data.csrf !== "" && (data.pullRequest?.view?.canMerge === true || (data.pullRequest?.view == null && data.pullRequest?.offer != null));
|
|
19692
|
-
const
|
|
19711
|
+
const pullRequestFact = data.pullRequest?.fact != null && data.pullRequest.view?.runId === data.assignment?.receipt?.runId ? data.pullRequest.fact : undefined;
|
|
19712
|
+
const assignmentOptions = data.assignment == null ? null : { workStatus: status, hideAction: (approveForm !== "" && data.dispatch?.action === "approve-scope") || pullRequestActs, problem: status.tone === "problem",
|
|
19713
|
+
planning: data.dispatch?.code === "running" && data.dispatch.role === "planner", ...(pullRequestFact === undefined ? {} : { pullRequest: pullRequestFact }),
|
|
19714
|
+
diagnostics: [...(status.diagnostics ?? []), ...(status.tone === "problem" && status.detail !== data.assignment.detail ? [status] : [])] };
|
|
19715
|
+
// Say it once: a pull request that couldn't open, or merged, is the status
|
|
19716
|
+
// card's own row (its reason or merge commit under Details), not a second card.
|
|
19717
|
+
const pullRequestCard = data.pullRequest == null || (pullRequestFact !== undefined && (data.pullRequest.view?.state === "failed" || data.pullRequest.view?.state === "merged")) ? "" : pullRequestCardHtml(data.pullRequest, data.csrf);
|
|
19693
19718
|
// The result takes over from the task status as soon as it is ready.
|
|
19694
19719
|
const statusHtml = (data.assignment != null && assignmentOptions !== null ? assignmentSummaryHtml(data.assignment, assignmentOptions) : receiptLeads ? completionReceiptCard(data.completion.receipt, task.id, "task", status) : taskStatusCard(status, task.id, data.dispatch ?? null, liveRunId, approveForm !== "" && data.dispatch?.action === "approve-scope")) + checkProgressHtml(data.checkProgress ?? null);
|
|
19695
19720
|
// The exact-run control (v52), directly under the scheduler's answer:
|
|
@@ -19819,7 +19844,7 @@ function taskBodyParts(data) {
|
|
|
19819
19844
|
approval: approvalHtml,
|
|
19820
19845
|
lead: [
|
|
19821
19846
|
{ key: "history", html: data.history ?? "" }, { key: "control", html: controlHtml }, { key: "problem", html: problemHtml },
|
|
19822
|
-
{ key: "pull-request", html:
|
|
19847
|
+
{ key: "pull-request", html: pullRequestCard },
|
|
19823
19848
|
{ key: "needs-scope", html: needsScopeCard }, { key: "progress", html: progressCard }, { key: "revisions", html: revisionLedgerCard },
|
|
19824
19849
|
{ key: "plan", html: planCard }, { key: "mirror", html: mirrorCard }, { key: "contest", html: contestCard }, { key: "attempt", html: attemptPanel },
|
|
19825
19850
|
].filter(one => one.html !== ""),
|
|
@@ -20071,11 +20096,11 @@ function cockpitStatusOf(view) {
|
|
|
20071
20096
|
// The receipt's own status (package 3): the same projection the chat
|
|
20072
20097
|
// receipt and the run page print, read from the shared detail.
|
|
20073
20098
|
if (view.run === null || view.detail === null)
|
|
20074
|
-
return view.assignment === null ? resultStatusOf(null, null) : assignmentPresentationOf(view.assignment).status;
|
|
20099
|
+
return view.assignment === null ? resultHeadlineOf(resultStatusOf(null, null)) : assignmentPresentationOf(view.assignment).status;
|
|
20075
20100
|
const receiptStatus = receiptStatusOf(view.detail.receipt);
|
|
20076
20101
|
if (view.assignment !== null)
|
|
20077
20102
|
return assignmentStatusOf(assignmentWithEvidence(view.assignment, receiptStatus, view.run.id));
|
|
20078
|
-
return receiptStatus;
|
|
20103
|
+
return resultHeadlineOf(receiptStatus);
|
|
20079
20104
|
}
|
|
20080
20105
|
/** Turn verifier records into one sentence a project owner can act on.
|
|
20081
20106
|
* A command-not-found result means the check could not start, not that the
|
|
@@ -20368,7 +20393,8 @@ function pullRequestCardHtml(pr, csrf) {
|
|
|
20368
20393
|
const view = pr.view;
|
|
20369
20394
|
const url = safePrUrl(view.prUrl);
|
|
20370
20395
|
const link = view.prNumber === null ? "" : url === null ? `<span class="mono">PR #${view.prNumber}</span>` : `<a href="${escape(url)}" class="mono" rel="noreferrer" target="_blank">PR #${view.prNumber}</a>`;
|
|
20371
|
-
|
|
20396
|
+
// A pull request's trouble never undoes the result: amber, never red.
|
|
20397
|
+
const tone = view.state === "ready" || view.state === "merged" ? "ok" : view.state === "failing" || view.state === "failed" ? "problem" : "waiting";
|
|
20372
20398
|
const revision = view.revisionTask === null || view.state !== "failing" ? "" : ` <a href="${taskHref(view.revisionTask)}">Open revision</a>`;
|
|
20373
20399
|
const merged = view.mergeCommit === null ? "" : `<p class="meta">Merge commit <span class="mono">${escape(view.mergeCommit.slice(0, 12))}</span></p>`;
|
|
20374
20400
|
const target = pr.target === null ? "the base branch" : pr.target;
|
|
@@ -21144,7 +21170,8 @@ function completionReceiptCard(view, taskId, place, standing = receiptStatusOf(v
|
|
|
21144
21170
|
lead +
|
|
21145
21171
|
`<p class="receipt-summary">${escape(conciseOutcomeOf(view.summary ?? (view.outcome === "no-change" ? "The agent found that no repository change was needed." : "The build finished without a concise handoff.")))}</p>` +
|
|
21146
21172
|
(headline ? `<div class="receipt-actions"><a class="button-link" href="${escape(resultHref)}" data-open-result data-primary-action>${escape(status.action?.label ?? "Open result")}</a></div>` : "") +
|
|
21147
|
-
|
|
21173
|
+
// A pull request that couldn't open never undoes the result: an amber note, not red.
|
|
21174
|
+
(view.publication?.state === "failed" ? `<p class="receipt-note">${statusIconSvg("note")} Pull request couldn't open. The commit is safe locally.</p>` : "") +
|
|
21148
21175
|
caveats +
|
|
21149
21176
|
coverage +
|
|
21150
21177
|
`<details class="receipt-details"><summary>Result details</summary>` +
|
|
@@ -21195,16 +21222,18 @@ function resultPanelParts(detail, o) {
|
|
|
21195
21222
|
const { run, receipt, proof, terminal, handoff } = detail;
|
|
21196
21223
|
const facts = receipt.facts;
|
|
21197
21224
|
const current = detail.assignment?.receipt?.runId === run.id ? detail.assignment : null;
|
|
21198
|
-
const
|
|
21199
|
-
|
|
21225
|
+
const resultLinks = { result: o.hrefFor("summary"), checks: o.hrefFor("checks"), pullRequest: `${taskHref(detail.rootId ?? detail.taskId)}#merge` };
|
|
21226
|
+
const presentation = current == null ? null : assignmentPresentationOf(current, { additionalAttention: [...facts.evidenceProblems, ...receipt.caveats],
|
|
21227
|
+
...(detail.publication === null ? {} : { pullRequest: pullRequestFactOf({ ...detail.publication, lastCheckState: detail.ciFailing ? "failing" : detail.publication.lastCheckState }) }),
|
|
21228
|
+
evidence: { damaged: facts.evidenceHealth.damaged, missing: facts.evidenceHealth.missing, shortened: facts.evidenceHealth.shortened }, links: resultLinks });
|
|
21229
|
+
const taskStatus = presentation?.taskStatus ?? null;
|
|
21230
|
+
const status = presentation?.status ?? resultHeadlineOf(receiptStatusOf(receipt));
|
|
21200
21231
|
const stored = receiptStatusOf(receipt, null);
|
|
21201
21232
|
const humanReview = manualReviewOnly(proof === null ? null : { ...proof, verdict: proof.verdict ?? "" });
|
|
21202
21233
|
const directAssessment = proof?.matrix.some(row => row.assessment !== undefined) === true;
|
|
21203
21234
|
const awaitingGoalReview = directAssessment && proof?.reasons.length === 1 && proof.reasons[0] === GOAL_ASSESSMENT_PENDING;
|
|
21204
21235
|
const assessmentReasons = directAssessment ? new Set(proof.matrix.flatMap(row => row.review ? [`${row.review.author} ${row.review.judgement === "contradicts" ? "contradicts" : "needs more evidence for"} criterion "${row.id}": ${row.review.note}`] : [])) : new Set();
|
|
21205
21236
|
const physicalReasons = receipt.reasons.filter(reason => !assessmentReasons.has(reason));
|
|
21206
|
-
if (directAssessment && status.token === "verification-needed" && proof?.verdict === "short" && proof.matrix.some(row => row.review?.judgement === "cannot-tell") && physicalReasons.length === 0)
|
|
21207
|
-
status = { ...status, label: "More evidence needed", tone: "attention" };
|
|
21208
21237
|
const runId = run.id;
|
|
21209
21238
|
const shots = proof?.screenshots ?? [];
|
|
21210
21239
|
const shown = shots.filter(one => one.problem === null);
|
|
@@ -21238,10 +21267,11 @@ function resultPanelParts(detail, o) {
|
|
|
21238
21267
|
attention.push(...(handoff?.followUps ?? []).map(one => `Follow-up: ${one}`));
|
|
21239
21268
|
// Publication risks stay in the open (repair 2026-09-14); the routine
|
|
21240
21269
|
// publication fact lives with the build details below.
|
|
21241
|
-
|
|
21270
|
+
// With the shared status, the pull request is its own quiet row (its reason under Details).
|
|
21271
|
+
if (publication !== null && publication.state === "failed" && taskStatus === null) {
|
|
21242
21272
|
attention.push(`Publication failed after ${publication.attempts} attempt${publication.attempts === 1 ? "" : "s"}${publication.lastError === null ? "" : ` โ ${oneLineOf(publication.lastError, 200)}`}. No pull request or merge is recorded.`);
|
|
21243
21273
|
}
|
|
21244
|
-
if (detail.ciFailing && publication !== null)
|
|
21274
|
+
if (detail.ciFailing && publication !== null && taskStatus === null)
|
|
21245
21275
|
attention.push(`CI is failing on PR #${publication.prNumber} at the last check.`);
|
|
21246
21276
|
// Requirements only a person can confirm: plain words and one Accept,
|
|
21247
21277
|
// in neutral ink โ nothing failed. Accepting records the person's
|
|
@@ -21517,11 +21547,13 @@ function resultPanelParts(detail, o) {
|
|
|
21517
21547
|
const html = (`<section class="card result-panel" id="result" data-result-panel data-result-place="${o.place}" data-result-lead="${lead}" data-result-task="${escape(detail.rootId ?? detail.taskId)}" data-result-user="${escape(o.user)}"${resultFactsAttributes(facts)}>` +
|
|
21518
21548
|
(o.back === null ? "" : `<p class="result-back"><a href="${escape(o.back.href)}" data-result-back>โ ${escape(o.back.label)}</a></p>`) +
|
|
21519
21549
|
(detail.history ?? "") +
|
|
21520
|
-
|
|
21550
|
+
// One headline: with the shared status, its words lead (task-status.ts); "Changes saved" would compete with them.
|
|
21551
|
+
(taskStatus !== null && o.headStatus !== false
|
|
21552
|
+
? `<header class="result-head"><div data-headline-tone="${taskStatus.tone}"><h2 class="status-headline" data-work-status="${escape(status.token)}"><i aria-hidden="true"></i>${escape(taskStatus.headline)}</h2><p class="status-sentence">${escape(taskStatus.sentence)}</p></div></header>`
|
|
21553
|
+
: `<header class="result-head"><div><h2>${escape(heading)}</h2></div>${o.headStatus === false ? "" : statusLineHtml(status)}</header>`) +
|
|
21521
21554
|
`<p class="result-summary">${escape(outcome)}</p>` +
|
|
21522
21555
|
(current == null || o.place === "review" && (current.state === "ready-to-check" || current.state === "complete") ? "" :
|
|
21523
|
-
`<div class="verdict" data-current-outcome>${
|
|
21524
|
-
(current.receipt?.checks.status === "failed" || current.receipt?.checks.status === "unavailable" ? `<p class="problem">${escape(current.detail)}</p>` : "") + `</div>`) +
|
|
21556
|
+
`<div class="verdict" data-current-outcome data-headline="${escape(taskStatus.headline)}">${statusDetailsHtml(taskStatus)}${statusWhyHtml(taskStatus)}</div>`) +
|
|
21525
21557
|
attentionHtml +
|
|
21526
21558
|
youCheckHtml +
|
|
21527
21559
|
action +
|
|
@@ -21536,7 +21568,7 @@ function resultPanelParts(detail, o) {
|
|
|
21536
21568
|
const panel = {
|
|
21537
21569
|
attributes: { "data-result-panel": "", "data-result-place": o.place, "data-result-lead": lead, "data-result-task": detail.rootId ?? detail.taskId, "data-result-user": o.user, ...resultFactsAttributeMap(facts) },
|
|
21538
21570
|
heading, outcome,
|
|
21539
|
-
|
|
21571
|
+
status: taskStatus,
|
|
21540
21572
|
reviewHistory: REVIEW_TOKENS.has(status.token) ? status.detail : null,
|
|
21541
21573
|
attention: attention.filter(one => !shortenedMaterialReason(one)).map(plainReasonWords),
|
|
21542
21574
|
youCheck,
|
|
@@ -21551,40 +21583,6 @@ function resultPanelParts(detail, o) {
|
|
|
21551
21583
|
};
|
|
21552
21584
|
return { html, panel };
|
|
21553
21585
|
}
|
|
21554
|
-
const CHIP_ICONS = { check: `<path d="M20 6 9 17l-5-5"/>`, x: `<path d="M18 6 6 18M6 6l12 12"/>` };
|
|
21555
|
-
/** The chips as data: the server list below and the rebuilt result page. */
|
|
21556
|
-
function verdictChipsOf(current, facts) {
|
|
21557
|
-
const chips = [];
|
|
21558
|
-
const checks = current.receipt?.checks.status ?? null;
|
|
21559
|
-
if (checks === "passed")
|
|
21560
|
-
chips.push({ tone: "success", label: "Checks passed", icon: "check", title: null });
|
|
21561
|
-
else if (checks === "failed")
|
|
21562
|
-
chips.push({ tone: "danger", label: "Checks failed", icon: "x", title: null });
|
|
21563
|
-
else if (checks === "unavailable")
|
|
21564
|
-
chips.push({ tone: "warning", label: "Check unavailable", icon: null, title: null });
|
|
21565
|
-
const publication = { none: ["neutral", "Branch only"], intended: ["info", "Publishing"], pushed: ["info", "Pushed"], opened: ["info", "Pull request open"], failed: ["danger", "Publish failed"] };
|
|
21566
|
-
const [tone, label] = publication[facts.publicationState] ?? ["neutral", facts.publicationState];
|
|
21567
|
-
chips.push({ tone, label, icon: null, title: facts.publicationWords });
|
|
21568
|
-
const health = facts.evidenceHealth;
|
|
21569
|
-
if (health.damaged > 0)
|
|
21570
|
-
chips.push({ tone: "danger", label: "Damaged evidence", icon: null, title: null });
|
|
21571
|
-
else if (health.missing > 0)
|
|
21572
|
-
chips.push({ tone: "warning", label: "Missing evidence", icon: null, title: null });
|
|
21573
|
-
else if (health.shortened > 0)
|
|
21574
|
-
chips.push({ tone: "warning", label: "Partial output", icon: null, title: "Some saved output was shortened when stored" });
|
|
21575
|
-
return { chips, by: current.completion === null ? null : current.completion.actor.replace(/^(?:operator|coordinator|lead):/, "") };
|
|
21576
|
-
}
|
|
21577
|
-
/** The verdict at a glance: checks, where the work lives, and whether any
|
|
21578
|
-
* saved output is partial, as chips instead of sentences. The same facts
|
|
21579
|
-
* stay available in words on the Checks tab and in Build details. */
|
|
21580
|
-
function verdictChipsHtml(current, facts) {
|
|
21581
|
-
const { chips, by } = verdictChipsOf(current, facts);
|
|
21582
|
-
const items = chips.map(one => {
|
|
21583
|
-
const label = `${one.icon === null ? "" : strokeIcon(CHIP_ICONS[one.icon])}${one.label}`;
|
|
21584
|
-
return `<li class="verdict-chip verdict-chip--${one.tone}" title="${escape(one.title ?? label)}">${label}</li>`;
|
|
21585
|
-
});
|
|
21586
|
-
return `<ul class="verdict-chips" aria-label="Result at a glance">${items.join("")}</ul>` + (by === null ? "" : `<p class="meta verdict-by">Completed by ${escape(by)}</p>`);
|
|
21587
|
-
}
|
|
21588
21586
|
/** The outcome in one bounded sentence (repair 2026-09-14): the handoff's
|
|
21589
21587
|
* first sentence, cut at a word when it runs past 180 characters. The
|
|
21590
21588
|
* full conclusion stays available behind the Summary's disclosure. */
|