prreviewbuddy 0.86.7 → 0.87.2
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 +49 -0
- package/dist/{delete_review-CwKZQlMg.js → delete_review-D7TXn5ck.js} +5 -1
- package/dist/main.js +1 -1
- package/dist/server.js +338 -130
- package/package.json +1 -1
- package/static/{chunk-Di41wu5k.js → chunk-BKBTVY9X.js} +1 -1
- package/static/{chunk-huqt-B16.js → chunk-BmIk8Mrb.js} +1 -1
- package/static/{chunk-D9x9bcKf.js → chunk-CMC5F4_t.js} +1 -1
- package/static/{chunk-BUD1RoN4.js → chunk-DDm8oQLU.js} +1 -1
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/runs.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +178 -108
- package/static/workspace.js +47 -47
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as MAX_WORKSPACE_PORT, $i as resolveBase, $n as fillFileUrlTemplate, $r as createWithMembers, $t as markAllRead, A as relativeTime$1, Ai as currentGradeIn, An as CODE_COPY_CLASS, Ar as mutateWorkspace, At as checkArchitecture, B as readIndexToken, Bi as statusLookup, Bn as isWorktreeHeld, Br as workspaceStamps, Bt as REPOSITORY_SCOPE, C as RUN_KINDS, Ci as isExplanationAction, Cn as chatConversationSection, Cr as kissDecisionRefusal, Ct as waitingHistory, D as heldRuns, Di as analysisRefOf, Dn as GITHUB_MARKDOWN, Dr as lineagePosition, Dt as describeRuntimes, E as endRun, Ei as NO_STATUS, En as hasAuthorContent, Er as lineageKey, Et as RUNTIME_WORDS, F as runBatch, Fi as isGradeFor, Fn as isBusy, Fr as reviewedCommit, Ft as storeArchitecture, Gi as objectWithAnyOf, Gr as lifecycleChip, Gt as branchOf, H as clearServerRecordOwnedBy, Hi as followUpFindingId, Hn as removeWorktree, Hr as activityAttributes, Ht as isArchitectureId, I as activateQueued, Ii as isOffered, In as withReviewLock, It as storedScopes, J as stopChild, Ji as kissOutcome, Jn as checkReview, Jr as offersLifecycle, Jt as createArchitectureNotification, K as serverEntryExists, Ki as describeAuthorship, Kn as findExistingReview, Kr as lifecycleItemLabel, Kt as subjectOf, L as compactQueue, Li as promptGrade, Ln as MANAGED_ROOT, Lr as summarise, Lt as trackedFiles, M as extractPullRequestLinks, Mi as findingIn, Mn as renderInlineText, Mr as presentedKissRunOf, Mt as partFiles, N as parsePullRequestLink, Ni as findingStatus, Nr as previousKissResult, Nt as readArchitecture, O as reportRun, Oi as assessmentLookup, On as renderMarkdown, Or as loadWorkspace, Ot as formatDuration, P as unsupportedReason, Pi as gradeScale, Pn as ReviewBeingDeletedError, Pr as recentWorkspaces, Pt as repositoryKey, Q as DEFAULT_WORKSPACE_PORT, Qi as isCommitReview, Qn as forgeResolver, Qr as createCollection, Qt as listNotifications, Ri as reassessIn, Rn as createWorktree, Rr as touchWorkspace, Rt as ownersOf, S as track, Sa as AgentUnavailableError, Si as explanationOf, Sn as EXPLAIN_SIMPLY_PROMPT, Sr as indexReviewGroups, St as feedbackUrl, T as completeRun, Ti as ANALYSIS_ONLY, Tn as authorPreview, Tr as latestKissRun, Tt as recordRuntimeBestEffort, Ui as asString, Un as requestJobStop, Ur as activityLabel, Ut as writeJsonAtomically, Vi as wasReassessed, Vn as readMarker, Vr as LIFECYCLES, Vt as ancestorsOf, W as readServerRecord, Wi as extractJsonText, Wr as isLifecycle, Wt as announceJob, Xi as originRemote, Xn as reviewedShaOf, Xr as sectionOf, Xt as createPrUpdate, Yi as resolveTarget, Yn as followedBranch, Yr as parseLifecycle, Yt as createPrState, Z as writeServerRecord, Zi as DetachedHeadError, Zn as fetchHead, Zr as collectionsStamp, Zt as eventName, _ as runningJobs, _a as detectAgents, _i as reviewerDispositions, _n as storedConversation, _t as recordTelemetryUploadConsent, a as wasBlocked, aa as resolveAgent, ai as updateCollections, an as readStateOf, ar as isTerminal, at as failureOf$1, b as startKissJob, ba as runnableAgents, bi as recommendationsOf, bn as isUnchanged, br as groupByLineage, bt as BUILD_VERSION, c as RefreshUnavailableError, ca as clearAgentPreference, ci as queueRemove, cn as toneOf, cr as saveJob, ct as settleReview, d as mergeUpdateInto, di as doneVerb, dn as withUsageRecorded, dr as isTerminalJobPhase, ea as AmbiguousRefError, ei as deleteCollection, en as markRead, er as RUN_RECORD_RETENTION_MS, et as MIN_WORKSPACE_PORT, fa as STORE_ROOT, fi as isIssueOutstanding, fn as existingPaths, fr as phasesFor, g as liveUpdateFor, ga as agentIdentityOf, gi as resolvedByConversation, gn as saveRefresh, gr as isClaimed, h as UpdateAlreadyRunningError, ha as agentFor$1, hi as questionsOutstanding, hn as prepareConversationWithin, i as liveJobsFor, ia as refExists, ii as setMembership, in as readNotification, ir as fail$1, it as writePortPreference, j as batchIdentity, ji as findingAssessment, jn as escapeHtml$1, jt as loadArchitecture, k as stopRun, ki as closesFinding, kn as CODE_BLOCK_CLASS, kt as ARCHITECTURE_DIR, l as cancelUpdate, la as readAgentPreference, li as queueStamp, ln as unreadAround, lt as reanalyseReview, m as askCheckout, ma as agentById, mi as issuesOutstanding, mn as adoptLegacyBaseline, mr as purposeOf, mt as record, n as deleteReview, na as shorthandOf, ni as readCollections, nn as moveIds, nr as attemptIdOf, nt as resolveWorkspacePort, o as discardJob, oa as resolveModel, oi as queueAdd, on as removeNotification, or as loadJob, ot as observeReview, p as updateReview, pa as AGENT_IDS, pi as isQuestionOutstanding, pn as ASK_REREAD_CEILING_MS, pr as progressSteps, pt as readEvents, q as spawnServer, qi as WITHHELD_REASON, qn as checkCodeFreshness, qr as lifecyclePill, qt as categoryOf, r as lineageIds, ra as git, ri as renameCollection, rn as pruneNotifications, rr as endedAt, rt as statedWorkspacePort, s as FETCH_TIMEOUT_MS, sa as agentIdOf, si as queueAddMany, sn as retireNotification, sr as pruneJobResidue, st as prepareReview, t as deleteLineage, ta as shortNameOf, ti as nameProblemText, tn as markReadForReview, tr as allJobs, tt as parseWorkspacePort, u as cancellableHere, ua as writeAgentPreference, ui as readQueue, un as unreadCount$1, ur as isLiveJob, ut as runJob, v as kissAgentId, va as isAgentId, vi as verdictClosure, vn as unforeseenFailure, vr as decideKissRecommendation, vt as telemetryUploadConsent, w as beginRun, wi as withExplanation, wn as UNESTABLISHED_MOTIVATION, wr as kissDecisionsOf, wt as mapRuntimes, x as isDriving, xa as AgentCancelledError, xi as explanationActionOf, xn as agentEnvOf, xr as indexKeptIds, ya as parkedReason, yi as processDiscussion, yn as refreshPrContext, yr as followedRefName, z as settleQueue, zi as sourcePhrase, zn as holdWorktree, zr as workspaceRevision, zt as QUOTE_LIMITS } from "./delete_review-D7TXn5ck.js";
|
|
2
2
|
import { basename, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -867,6 +867,20 @@ function supersededLabel(because) {
|
|
|
867
867
|
return because === "moved-again" ? "replaced by a later move" : "replaced when the review caught up";
|
|
868
868
|
}
|
|
869
869
|
//#endregion
|
|
870
|
+
//#region ../../packages/review-harness/src/workspace/running_label.ts
|
|
871
|
+
/**
|
|
872
|
+
* The words and the digit for the count beside Runs, which the server renders and the browser
|
|
873
|
+
* repaints. Pure, because the client bundle reads it.
|
|
874
|
+
*/
|
|
875
|
+
/** In words, for a screen reader: "1 run in progress", "3 runs in progress". */
|
|
876
|
+
function runningLabel(running) {
|
|
877
|
+
return running === 0 ? "Nothing running" : `${running} ${running === 1 ? "run" : "runs"} in progress`;
|
|
878
|
+
}
|
|
879
|
+
/** The digit an icon has room for. Past nine it says there are many rather than how many. */
|
|
880
|
+
function runningDigit(running) {
|
|
881
|
+
return running > 9 ? "9+" : String(running);
|
|
882
|
+
}
|
|
883
|
+
//#endregion
|
|
870
884
|
//#region ../../packages/review-harness/src/workspace/server_status.ts
|
|
871
885
|
/** The dot's accessible name, which is the only form of it a screen reader gets. */
|
|
872
886
|
function serverStatusLabel(status) {
|
|
@@ -891,8 +905,8 @@ var SHOW_SIDEBAR = "Show sidebar";
|
|
|
891
905
|
/**
|
|
892
906
|
* The workspace's own places, and the one model every page draws them from.
|
|
893
907
|
*
|
|
894
|
-
*
|
|
895
|
-
* alone. There is no second list: the rail, the narrow header and the review page's rail foot all
|
|
908
|
+
* Drawn in the same place on every page, directly under New review, so they never move: a review
|
|
909
|
+
* page adds its own section below them, and every other workspace page draws these alone. There is no second list: the rail, the narrow header and the review page's rail foot all
|
|
896
910
|
* read `WORKSPACE_PLACES`, so a place added here appears everywhere and a place renamed here is
|
|
897
911
|
* renamed everywhere.
|
|
898
912
|
*/
|
|
@@ -936,6 +950,16 @@ function sidebarAttributes(sidebar) {
|
|
|
936
950
|
return `${sidebar?.compact ? " data-sidebar-compact" : ""}${sidebar?.hidden ? " data-sidebar-hidden" : ""}`;
|
|
937
951
|
}
|
|
938
952
|
/**
|
|
953
|
+
* The count beside Runs. Always rendered, hidden at zero, so a poll that reports a new number can
|
|
954
|
+
* reveal it without the rail being rebuilt. See `client/running_counts.ts`, its one painter.
|
|
955
|
+
*
|
|
956
|
+
* In the Activity accent, as the unread count beside Activity is: both say something is happening
|
|
957
|
+
* that the reader did not just do. The words are its label, because a bare digit names nothing.
|
|
958
|
+
*/
|
|
959
|
+
function runningCount$1(running) {
|
|
960
|
+
return `<span class="prb-rail-count prb-rail-running" data-running-count data-running-style="rail" aria-label="${escapeHtml$1(runningLabel(running))}"${running === 0 ? " hidden" : ""}>${running === 0 ? "" : running}</span>`;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
939
963
|
* The count beside Notifications. Always rendered, hidden at zero, so the page can reveal it when
|
|
940
964
|
* `/revision` or Mark all read reports a new number. See `client/unread_counts.ts`.
|
|
941
965
|
*/
|
|
@@ -978,19 +1002,25 @@ function renderGlobalBar(options) {
|
|
|
978
1002
|
</header>`;
|
|
979
1003
|
}
|
|
980
1004
|
/**
|
|
981
|
-
* The bar
|
|
1005
|
+
* The bar at the top of the content column: what page this is, and the controls that belong to it.
|
|
1006
|
+
* Beside the rail rather than above it, so the rail starts under the strip on every page.
|
|
982
1007
|
*
|
|
983
1008
|
* The dominant bar of the composition on every page, as the review's own bar is on a review page.
|
|
984
1009
|
* That is the point of the split: the strip is utility chrome and this says what you are looking at.
|
|
985
1010
|
*
|
|
986
|
-
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
1011
|
+
* Two regions. The title row is the same on every page, the same height as a review's bar, with
|
|
1012
|
+
* page-level actions (Mark all read) at its right, so the title never moves between pages. The
|
|
1013
|
+
* controls row (search and filters) is drawn only by a page that has controls, which is the one
|
|
1014
|
+
* way the bar grows: a page with nothing to put there gets no empty band to match the others.
|
|
989
1015
|
*/
|
|
990
|
-
function renderContextBar(title,
|
|
1016
|
+
function renderContextBar(title, parts = {}) {
|
|
1017
|
+
const controls = parts.controls ? `
|
|
1018
|
+
<div class="prb-context-secondary">${parts.controls}</div>` : "";
|
|
991
1019
|
return `<div class="prb-context">
|
|
992
|
-
<
|
|
993
|
-
|
|
1020
|
+
<div class="prb-context-primary">
|
|
1021
|
+
<h1 class="prb-page-title">${escapeHtml$1(title)}</h1>
|
|
1022
|
+
${parts.actions ?? ""}
|
|
1023
|
+
</div>${controls}
|
|
994
1024
|
</div>`;
|
|
995
1025
|
}
|
|
996
1026
|
/**
|
|
@@ -1069,9 +1099,21 @@ function renderHistoryControls() {
|
|
|
1069
1099
|
function renderStripReviews(options) {
|
|
1070
1100
|
return `<a class="prb-strip-action" href="/"${options.current === "reviews" ? " aria-current=\"true\"" : ""} aria-label="Reviews" data-strip-reviews>${iconSvg("reviews")}</a>`;
|
|
1071
1101
|
}
|
|
1072
|
-
/**
|
|
1102
|
+
/**
|
|
1103
|
+
* Runs, beside Reviews, in the rail's order. Its own `aria-label`, for the reason Settings has one.
|
|
1104
|
+
*
|
|
1105
|
+
* It carries the count beside Runs as well, as the bell carries the unread one, so the work going
|
|
1106
|
+
* on is visible wherever the rail is not: Compact, Hidden, a narrow window. The digit is for the
|
|
1107
|
+
* eye; the words, its description, are what a screen reader hears after the name.
|
|
1108
|
+
*/
|
|
1073
1109
|
function renderStripRuns(options) {
|
|
1074
|
-
|
|
1110
|
+
const current = options.current === "runs" ? " aria-current=\"true\"" : "";
|
|
1111
|
+
const running = options.running ?? 0;
|
|
1112
|
+
const hidden = running === 0 ? " hidden" : "";
|
|
1113
|
+
return `<a class="prb-strip-action prb-strip-runs" href="/runs"${current} aria-label="Runs" aria-describedby="prb-strip-running" data-strip-runs>${iconSvg("runs")}
|
|
1114
|
+
<span class="prb-strip-count" data-running-count data-running-style="digit" aria-hidden="true"${hidden}>${running === 0 ? "" : runningDigit(running)}</span>
|
|
1115
|
+
<span class="prb-visually-hidden" id="prb-strip-running" data-running-count data-running-style="words">${running === 0 ? "" : escapeHtml$1(runningLabel(running))}</span>
|
|
1116
|
+
</a>`;
|
|
1075
1117
|
}
|
|
1076
1118
|
/**
|
|
1077
1119
|
* Settings, as an icon, beside the bell.
|
|
@@ -1132,12 +1174,13 @@ function renderServerStatus() {
|
|
|
1132
1174
|
function renderNewReviewButton() {
|
|
1133
1175
|
return `<div class="prb-rail-new"><button type="button" class="prb-new-review" data-new-review="rail">${iconSvg("plus")}<span class="prb-new-review-label">New review</span><kbd class="prb-new-review-key" data-new-review-key aria-hidden="true"></kbd></button></div>`;
|
|
1134
1176
|
}
|
|
1135
|
-
/** New review, then the workspace rows
|
|
1177
|
+
/** New review, then the workspace rows: the first group of every rail, review pages' included. */
|
|
1136
1178
|
function renderWorkspaceNav(options) {
|
|
1137
1179
|
return renderNewReviewButton() + WORKSPACE_PLACES.map((item) => `<a class="prb-rail-item prb-rail-link" href="${item.href}"${item.place === options.current ? " aria-current=\"true\"" : ""}>
|
|
1138
1180
|
${railIcon(item.icon)}
|
|
1139
1181
|
<span class="prb-rail-label"><span class="prb-rail-title">${escapeHtml$1(item.label)}</span></span>
|
|
1140
1182
|
${item.place === "notifications" ? unreadCount(options.unread, "rail") : ""}
|
|
1183
|
+
${item.place === "runs" ? runningCount$1(options.running ?? 0) : ""}
|
|
1141
1184
|
</a>`).join("");
|
|
1142
1185
|
}
|
|
1143
1186
|
function renderShutdownDialog() {
|
|
@@ -1170,7 +1213,7 @@ function renderSendFeedback() {
|
|
|
1170
1213
|
*/
|
|
1171
1214
|
function renderWorkspaceRail(options) {
|
|
1172
1215
|
return `<nav class="prb-rail" aria-label="Workspace">
|
|
1173
|
-
<div class="prb-rail-group">${renderWorkspaceNav(options)}</div>
|
|
1216
|
+
<div class="prb-rail-group prb-rail-places">${renderWorkspaceNav(options)}</div>
|
|
1174
1217
|
<div class="prb-rail-group prb-rail-foot">${renderSendFeedback()}${renderSidebarToggle(options.sidebar)}</div>
|
|
1175
1218
|
</nav>`;
|
|
1176
1219
|
}
|
|
@@ -6396,9 +6439,28 @@ function renderWorkspace(view) {
|
|
|
6396
6439
|
}) ? " data-guided-tour=\"offer\"" : ""}${view.tourBusy ? " data-tour-busy" : ""}>
|
|
6397
6440
|
${renderGlobalBar({
|
|
6398
6441
|
unread: view.unreadNotifications ?? 0,
|
|
6442
|
+
running: view.runningRuns ?? 0,
|
|
6399
6443
|
live: Boolean(view.token),
|
|
6400
6444
|
driving: view.driving
|
|
6401
6445
|
})}
|
|
6446
|
+
${renderReviewBody(view, sections, renderReviewHead(view, freshness))}
|
|
6447
|
+
${view.token ? renderPromptDialog() : ""}
|
|
6448
|
+
${offersFeedback(view) ? renderFeedbackDialog() : ""}
|
|
6449
|
+
<script type="module" src="/assets/workspace.js"><\/script>
|
|
6450
|
+
</body>
|
|
6451
|
+
</html>`;
|
|
6452
|
+
}
|
|
6453
|
+
/**
|
|
6454
|
+
* Everything about this review that sits above its panes: the review bar, then whatever banners the
|
|
6455
|
+
* review's state calls for (job status, freshness, the last update's outcome, the lineage notice).
|
|
6456
|
+
*
|
|
6457
|
+
* In the content column, to the right of the rail, rather than across the window. The application
|
|
6458
|
+
* shell is the strip and the rail, and it is the same on every page; this is the review's own
|
|
6459
|
+
* header, and however many banners it holds it pushes down only the review, never the rail. The
|
|
6460
|
+
* workspace pages put their context bar in the same column for the same reason.
|
|
6461
|
+
*/
|
|
6462
|
+
function renderReviewHead(view, freshness) {
|
|
6463
|
+
return `<div class="prb-review-head">
|
|
6402
6464
|
${renderReviewBar(view, freshness)}
|
|
6403
6465
|
${showsProgress(view) || updating(view) ? "" : renderJobState(view.job ?? null, {
|
|
6404
6466
|
dismissed: view.dismissedNotices,
|
|
@@ -6412,12 +6474,7 @@ ${showsProgress(view) || updating(view) ? "" : renderJobState(view.job ?? null,
|
|
|
6412
6474
|
})}</div>
|
|
6413
6475
|
${updating(view) ? "" : renderUpdateOutcome(view.lastUpdate)}
|
|
6414
6476
|
${updating(view) ? "" : renderLineageNotice(view)}
|
|
6415
|
-
|
|
6416
|
-
${view.token ? renderPromptDialog() : ""}
|
|
6417
|
-
${offersFeedback(view) ? renderFeedbackDialog() : ""}
|
|
6418
|
-
<script type="module" src="/assets/workspace.js"><\/script>
|
|
6419
|
-
</body>
|
|
6420
|
-
</html>`;
|
|
6477
|
+
</div>`;
|
|
6421
6478
|
}
|
|
6422
6479
|
/**
|
|
6423
6480
|
* Whether this page's subject is a review that is still being made, or one that stopped.
|
|
@@ -6466,9 +6523,10 @@ function showsProgress(view) {
|
|
|
6466
6523
|
* question box offering to discuss findings nobody has produced is a worse thing to hand somebody
|
|
6467
6524
|
* than no box at all.
|
|
6468
6525
|
*/
|
|
6469
|
-
function renderReviewBody(view, sections) {
|
|
6526
|
+
function renderReviewBody(view, sections, head = "") {
|
|
6470
6527
|
const result = view.session.result;
|
|
6471
6528
|
return `<div class="prb-body"${result ? view.reviewLayout?.ask ? ` data-ask="${view.reviewLayout.ask}"` : "" : ` data-ask="closed"`} data-review-layout="${escapeHtml$1(JSON.stringify(view.reviewLayout ?? {}))}"${view.askWidth ? ` style="--ask-width: ${Math.round(view.askWidth)}px"` : ""}>
|
|
6529
|
+
${head}
|
|
6472
6530
|
${renderRail(view, sections, updating(view))}
|
|
6473
6531
|
<main class="prb-doc">
|
|
6474
6532
|
${updating(view) ? renderUpdatingDoc(view) : renderReviewDoc(view, sections)}
|
|
@@ -6961,46 +7019,33 @@ function renderIdMenu(view, freshness) {
|
|
|
6961
7019
|
</div>`;
|
|
6962
7020
|
}
|
|
6963
7021
|
/**
|
|
6964
|
-
*
|
|
6965
|
-
*
|
|
6966
|
-
* These are in the rail rather than the header because they are not about this review. The header
|
|
6967
|
-
* answers "what am I looking at" — branch, size, where the review stands — and
|
|
6968
|
-
* anything global sitting up there competed with that. Leaving the review is navigation, and it
|
|
6969
|
-
* belongs with the other navigation.
|
|
6970
|
-
*
|
|
6971
|
-
* Stopping the server is separated from the links because it is operational and it ends every open
|
|
6972
|
-
* review, not just this one. The build sits beside it: it is provenance, wanted when something is
|
|
6973
|
-
* wrong, and that is the same corner as the feedback link that carries it.
|
|
7022
|
+
* The foot of the rail: Send feedback and the sidebar's own toggle, anchored at the bottom whichever
|
|
7023
|
+
* review is open and however long its section is.
|
|
6974
7024
|
*
|
|
6975
7025
|
* The same feedback form the extension uses, so replies land in one place rather than two. What
|
|
6976
|
-
* it carries is decided in `version.ts`
|
|
7026
|
+
* it carries is decided in `version.ts` (version, client, OS), and deliberately not the
|
|
6977
7027
|
* repository or its path.
|
|
6978
7028
|
*/
|
|
6979
7029
|
function renderRailFoot(view) {
|
|
6980
7030
|
return `<div class="prb-rail-group prb-rail-foot">
|
|
6981
|
-
${view.token ? renderWorkspaceNav({ unread: view.unreadNotifications ?? 0 }) : ""}
|
|
6982
7031
|
${renderSendFeedback()}
|
|
6983
7032
|
${view.token ? renderSidebarToggle(view.sidebar) : ""}
|
|
6984
7033
|
</div>`;
|
|
6985
7034
|
}
|
|
6986
7035
|
/**
|
|
6987
|
-
* The rail
|
|
7036
|
+
* The rail of a review page: the workspace's places, then a section for this review, then the foot.
|
|
6988
7037
|
*
|
|
6989
|
-
*
|
|
6990
|
-
*
|
|
6991
|
-
*
|
|
6992
|
-
*
|
|
7038
|
+
* The places come first and are the very rows every other page draws (`workspace_nav.ts`), so they
|
|
7039
|
+
* hold one position across the whole workspace. Everything about this review is contextual and sits
|
|
7040
|
+
* below them, under a heading rather than a row of its own: the reader is already on the review, so
|
|
7041
|
+
* a "Review" entry would be a door to the room they are standing in. Its first row is the review's
|
|
7042
|
+
* own page (`renderReviewHome`).
|
|
6993
7043
|
*
|
|
6994
|
-
*
|
|
6995
|
-
*
|
|
6996
|
-
* splitting them would have put a second row under `Review` labelled `Overview` and a second under
|
|
6997
|
-
* `KISS` labelled something invented. Collapsing is a plain button beside the row rather than a
|
|
6998
|
-
* <details>: the row has to stay a link, and a control inside a <summary> is toggled by the
|
|
6999
|
-
* browser as well as by us in some engines.
|
|
7044
|
+
* KISS is in the same section, as a group of its own with its pages under it: it is a second
|
|
7045
|
+
* question asked of this change, and it belongs to the review the way Issues and Files do.
|
|
7000
7046
|
*
|
|
7001
|
-
* The review path keeps a twisty of its own
|
|
7002
|
-
*
|
|
7003
|
-
* separately or folding the path would mean folding away Issues and Questions with it.
|
|
7047
|
+
* The review path keeps a twisty of its own. It is the long part of the section and the short links
|
|
7048
|
+
* under it are the ones a reviewer reaches for repeatedly, so it folds on its own.
|
|
7004
7049
|
*
|
|
7005
7050
|
* Themes are numbered because the guide genuinely is an ordered walkthrough — `order` is the
|
|
7006
7051
|
* model's answer to "what sequence should a reviewer take through this change" — so the number
|
|
@@ -7025,16 +7070,7 @@ function renderRail(view, sections, stale = false) {
|
|
|
7025
7070
|
<span class="prb-attention" data-attention="${escapeHtml$1(section.reviewAttention)}" title="Needs ${escapeHtml$1(section.reviewAttention)} attention"></span>
|
|
7026
7071
|
</a>`;
|
|
7027
7072
|
}).join("");
|
|
7028
|
-
const standard = view.session.result ?
|
|
7029
|
-
id: "prb-rail-review",
|
|
7030
|
-
title: "Review",
|
|
7031
|
-
icon: "review",
|
|
7032
|
-
view: "overview",
|
|
7033
|
-
current: true,
|
|
7034
|
-
badge: null,
|
|
7035
|
-
collapses: "the review",
|
|
7036
|
-
open: railFoldOpen(view, "prb-rail-review"),
|
|
7037
|
-
body: `<button type="button" class="prb-rail-subhead" data-rail-toggle="the review path" ${foldAttributes(railFoldOpen(view, "prb-rail-path"), "the review path")} aria-controls="prb-rail-path">
|
|
7073
|
+
const standard = view.session.result ? `<button type="button" class="prb-rail-subhead" data-rail-toggle="the review path" ${foldAttributes(railFoldOpen(view, "prb-rail-path"), "the review path")} aria-controls="prb-rail-path">
|
|
7038
7074
|
<span class="prb-rail-heading">Review path</span>
|
|
7039
7075
|
<span class="prb-rail-twisty" aria-hidden="true">${TWISTY}</span>
|
|
7040
7076
|
</button>
|
|
@@ -7045,49 +7081,66 @@ function renderRail(view, sections, stale = false) {
|
|
|
7045
7081
|
${railLink("issues", "issues", "Issues", findingsBadge(issuesOutstanding(issues, dispositions)), void 0, void 0, true)}
|
|
7046
7082
|
${railLink("questions", "questions", "Questions", remainingBadge(questionsOutstanding(questions, answered, verdictClosure(view.session).question), "question", "answered"), void 0, void 0, true)}
|
|
7047
7083
|
${railLink("files", "files", "Files", totalBadge(view.changeSet.data.files.length, "files"))}
|
|
7048
|
-
${view.prContext ? railLink("prcontext", "prcontext", "PR context", totalBadge(view.prContext.processed.stats.totalThreads, "PR comment threads")) : railLink("prcontext", "prcontext", "PR context", null, "No pull request was connected to this review")}`
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7084
|
+
${view.prContext ? railLink("prcontext", "prcontext", "PR context", totalBadge(view.prContext.processed.stats.totalThreads, "PR comment threads")) : railLink("prcontext", "prcontext", "PR context", null, "No pull request was connected to this review")}` : renderRunningRail(view);
|
|
7085
|
+
return `<nav class="prb-rail" aria-label="Workspace">
|
|
7086
|
+
${view.token ? `<div class="prb-rail-group prb-rail-places">${renderWorkspaceNav({
|
|
7087
|
+
unread: view.unreadNotifications ?? 0,
|
|
7088
|
+
running: view.runningRuns ?? 0
|
|
7089
|
+
})}</div>` : ""}
|
|
7090
|
+
<section class="prb-rail-context" id="prb-rail-review" aria-labelledby="prb-rail-context-title"${stale ? " inert data-stale" : ""}>
|
|
7091
|
+
<h2 class="prb-rail-heading prb-rail-context-title" id="prb-rail-context-title">Current review</h2>
|
|
7092
|
+
${renderReviewHome(view)}
|
|
7093
|
+
${standard}
|
|
7094
|
+
${renderKissRail(view)}
|
|
7095
|
+
</section>
|
|
7053
7096
|
${renderRailFoot(view)}
|
|
7054
7097
|
</nav>`;
|
|
7055
7098
|
}
|
|
7056
7099
|
/**
|
|
7057
|
-
* The
|
|
7100
|
+
* The review's own first page: Overview once there is a result to present, Progress until then.
|
|
7058
7101
|
*
|
|
7059
|
-
*
|
|
7060
|
-
*
|
|
7061
|
-
*
|
|
7062
|
-
*
|
|
7102
|
+
* Decided by the result, not by whether anything is running. An update keeps the result it is
|
|
7103
|
+
* replacing, so the row stays Overview through it; a first analysis, or a re-run, which clears the
|
|
7104
|
+
* result before it starts, has nothing to present yet, and a failed one keeps Progress so its
|
|
7105
|
+
* failure and its Retry stay one press away. Both are the same row in the same slot, so the label
|
|
7106
|
+
* changing moves nothing. A review with neither (KISS alone) draws none: KISS is its page.
|
|
7063
7107
|
*
|
|
7064
|
-
*
|
|
7065
|
-
*
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7108
|
+
* Current when the server draws it, as the default view is, so the first paint is already right;
|
|
7109
|
+
* the client keeps `aria-current` true from there, as it does for every `data-view-nav`.
|
|
7110
|
+
*/
|
|
7111
|
+
function renderReviewHome(view) {
|
|
7112
|
+
const home = view.session.result ? {
|
|
7113
|
+
view: "overview",
|
|
7114
|
+
label: "Overview"
|
|
7115
|
+
} : showsProgress(view) ? {
|
|
7116
|
+
view: "progress",
|
|
7117
|
+
label: "Progress"
|
|
7118
|
+
} : null;
|
|
7119
|
+
if (!home) return "";
|
|
7120
|
+
return `<a class="prb-rail-item prb-rail-link" href="${escapeHtml$1(hashForView(home.view))}" data-view-nav="${home.view}" data-review-home aria-current="true">
|
|
7121
|
+
${railIcon("review")}
|
|
7122
|
+
<span class="prb-rail-label"><span class="prb-rail-title">${home.label}</span></span>
|
|
7123
|
+
</a>`;
|
|
7124
|
+
}
|
|
7125
|
+
/**
|
|
7126
|
+
* What the review's section carries while the review is still being made.
|
|
7127
|
+
*
|
|
7128
|
+
* What actually exists: the files, once they have been read, under the Progress row. There are
|
|
7129
|
+
* deliberately no Issues or Questions rows waiting to be filled in. A badge is a measured figure,
|
|
7130
|
+
* so a `0` beside Issues would report that an analysis nobody has finished found nothing, and a
|
|
7131
|
+
* row with no badge is a door onto an empty room. The rail says what is there; the Progress pane
|
|
7132
|
+
* is where "and these arrive later" belongs.
|
|
7069
7133
|
*/
|
|
7070
7134
|
function renderRunningRail(view) {
|
|
7071
7135
|
if (!showsProgress(view)) return "";
|
|
7072
7136
|
const files = view.changeSet.data.files.length;
|
|
7073
|
-
return
|
|
7074
|
-
id: "prb-rail-review",
|
|
7075
|
-
title: "Review",
|
|
7076
|
-
icon: "review",
|
|
7077
|
-
view: "progress",
|
|
7078
|
-
current: true,
|
|
7079
|
-
badge: null,
|
|
7080
|
-
collapses: "the review",
|
|
7081
|
-
open: railFoldOpen(view, "prb-rail-review"),
|
|
7082
|
-
body: view.job && changeIsRead(view.job) ? railLink("files", "files", "Files", totalBadge(files, "files")) : ""
|
|
7083
|
-
});
|
|
7137
|
+
return view.job && changeIsRead(view.job) ? railLink("files", "files", "Files", totalBadge(files, "files")) : "";
|
|
7084
7138
|
}
|
|
7085
7139
|
/**
|
|
7086
|
-
* The KISS
|
|
7140
|
+
* The KISS group of the review's section, present before anybody has run it.
|
|
7087
7141
|
*
|
|
7088
|
-
* A
|
|
7089
|
-
*
|
|
7090
|
-
* nesting either under the other would say one is a facet of the other.
|
|
7142
|
+
* A group of its own rather than a row among Issues and Files, because it is a second question
|
|
7143
|
+
* asked of the same change: whether its complexity is justified, beside whether it is correct.
|
|
7091
7144
|
*
|
|
7092
7145
|
* Visible on a change nobody has run it on, which is the point. The entry is what teaches that one
|
|
7093
7146
|
* change can be interrogated through more than one review type, and a reader who never sees it
|
|
@@ -8053,6 +8106,22 @@ function architectureRunsFor(repoPath) {
|
|
|
8053
8106
|
startedAt
|
|
8054
8107
|
})).sort((a, b) => Number(b.scope === "repository") - Number(a.scope === "repository") || a.startedAt - b.startedAt);
|
|
8055
8108
|
}
|
|
8109
|
+
/**
|
|
8110
|
+
* Every repository being mapped now, for the Runs page. `key` is the run's own key, repository and
|
|
8111
|
+
* scope, so two scopes of one repository are two entries and one scope is never listed twice.
|
|
8112
|
+
*/
|
|
8113
|
+
function allArchitectureRuns() {
|
|
8114
|
+
return [...runs.entries()].map(([key, { repoPath, scope, stage, steps, startedAt, record, partName }]) => ({
|
|
8115
|
+
key,
|
|
8116
|
+
repoPath,
|
|
8117
|
+
scope,
|
|
8118
|
+
stage,
|
|
8119
|
+
steps,
|
|
8120
|
+
startedAt,
|
|
8121
|
+
reviewId: record.reviewId,
|
|
8122
|
+
...partName ? { partName } : {}
|
|
8123
|
+
}));
|
|
8124
|
+
}
|
|
8056
8125
|
/** Whether a record that says `running` is really running, or was cut short. */
|
|
8057
8126
|
function isRunLive(repoPath, scope) {
|
|
8058
8127
|
return runs.has(keyOf(repoPath, scope));
|
|
@@ -8116,7 +8185,8 @@ function startArchitectureRun(workspace, scope, deps = {}) {
|
|
|
8116
8185
|
steps: 0,
|
|
8117
8186
|
startedAt,
|
|
8118
8187
|
controller: new AbortController(),
|
|
8119
|
-
record
|
|
8188
|
+
record,
|
|
8189
|
+
...partName ? { partName } : {}
|
|
8120
8190
|
};
|
|
8121
8191
|
runs.set(key, held);
|
|
8122
8192
|
saveRunRecord(repoPath, record);
|
|
@@ -9997,17 +10067,19 @@ function renderReviewsPage(view) {
|
|
|
9997
10067
|
${renderGlobalBar({
|
|
9998
10068
|
current: "reviews",
|
|
9999
10069
|
unread: view.unreadNotifications ?? 0,
|
|
10070
|
+
running: view.runningRuns ?? 0,
|
|
10000
10071
|
driving: view.driving
|
|
10001
10072
|
})}
|
|
10002
|
-
|
|
10073
|
+
<div class="prb-shell">
|
|
10074
|
+
${renderContextBar("Reviews", { controls: ordered.length === 0 ? void 0 : renderControls$1(queue.length, authorOptions(ordered.map(({ latest }) => ({
|
|
10003
10075
|
handle: latest.authorHandle,
|
|
10004
10076
|
name: latest.authorName,
|
|
10005
10077
|
createdAt: latest.createdAt
|
|
10006
|
-
}))), ordered.some((group) => group.latest.prNumber !== void 0), context.refs.length > 0))}
|
|
10007
|
-
<div class="prb-shell">
|
|
10078
|
+
}))), ordered.some((group) => group.latest.prNumber !== void 0), context.refs.length > 0) })}
|
|
10008
10079
|
${renderWorkspaceRail({
|
|
10009
10080
|
current: "reviews",
|
|
10010
10081
|
unread: view.unreadNotifications ?? 0,
|
|
10082
|
+
running: view.runningRuns ?? 0,
|
|
10011
10083
|
sidebar: view.sidebar
|
|
10012
10084
|
})}
|
|
10013
10085
|
<main class="prb-index">
|
|
@@ -11265,13 +11337,15 @@ function renderSettingsPage(view) {
|
|
|
11265
11337
|
${renderGlobalBar({
|
|
11266
11338
|
current: "settings",
|
|
11267
11339
|
unread: view.unreadNotifications ?? 0,
|
|
11340
|
+
running: view.runningRuns ?? 0,
|
|
11268
11341
|
driving: view.driving
|
|
11269
11342
|
})}
|
|
11270
|
-
${renderContextBar("Settings", renderBack())}
|
|
11271
11343
|
<div class="prb-shell">
|
|
11344
|
+
${renderContextBar("Settings")}
|
|
11272
11345
|
${renderWorkspaceRail({
|
|
11273
11346
|
current: "settings",
|
|
11274
11347
|
unread: view.unreadNotifications ?? 0,
|
|
11348
|
+
running: view.runningRuns ?? 0,
|
|
11275
11349
|
sidebar: view.sidebar
|
|
11276
11350
|
})}
|
|
11277
11351
|
<main class="prb-index prb-settings">
|
|
@@ -11301,21 +11375,6 @@ ${renderShutdownDialog()}
|
|
|
11301
11375
|
</html>`;
|
|
11302
11376
|
}
|
|
11303
11377
|
/**
|
|
11304
|
-
* The way back out.
|
|
11305
|
-
*
|
|
11306
|
-
* Settings is reached from two places and only one of them is the index: the other is the rail of a
|
|
11307
|
-
* review somebody was in the middle of reading. A link to `/` is the right answer for the first and
|
|
11308
|
-
* a detour for the second, and the server cannot tell which happened or address the review if it
|
|
11309
|
-
* could — a workspace URL carries that review's own token, and putting one in a link on this page
|
|
11310
|
-
* would hand a page that is not that review a credential that opens it.
|
|
11311
|
-
*
|
|
11312
|
-
* So the server renders the answer that is always true and the browser improves it when it can, out
|
|
11313
|
-
* of a referrer it already has. See `client/settings.ts`.
|
|
11314
|
-
*/
|
|
11315
|
-
function renderBack() {
|
|
11316
|
-
return `<a class="prb-settings-back" href="/" data-settings-back><span aria-hidden="true">‹</span> All reviews</a>`;
|
|
11317
|
-
}
|
|
11318
|
-
/**
|
|
11319
11378
|
* Stopping the server, on the one page where stopping it is a decision rather than a stray click.
|
|
11320
11379
|
*
|
|
11321
11380
|
* It used to sit at the foot of every rail, one press from ending every open review, beside the
|
|
@@ -12044,17 +12103,19 @@ function renderHelpPage(view) {
|
|
|
12044
12103
|
<link rel="stylesheet" href="/assets/sidebar.css">
|
|
12045
12104
|
<link rel="stylesheet" href="/assets/shell.css">
|
|
12046
12105
|
</head>
|
|
12047
|
-
<body class="prb-index-body" data-way-back="${escapeHtml$1(wayBackSentence())}">
|
|
12106
|
+
<body class="prb-index-body" data-way-back="${escapeHtml$1(wayBackSentence())}" data-notifications-since="${Date.now()}">
|
|
12048
12107
|
${renderGlobalBar({
|
|
12049
12108
|
current: "help",
|
|
12050
12109
|
unread: view.unreadNotifications ?? 0,
|
|
12110
|
+
running: view.runningRuns ?? 0,
|
|
12051
12111
|
driving: view.driving
|
|
12052
12112
|
})}
|
|
12053
|
-
${renderContextBar("Help", "")}
|
|
12054
12113
|
<div class="prb-shell">
|
|
12114
|
+
${renderContextBar("Help")}
|
|
12055
12115
|
${renderWorkspaceRail({
|
|
12056
12116
|
current: "help",
|
|
12057
12117
|
unread: view.unreadNotifications ?? 0,
|
|
12118
|
+
running: view.runningRuns ?? 0,
|
|
12058
12119
|
sidebar: view.sidebar
|
|
12059
12120
|
})}
|
|
12060
12121
|
<main class="prb-index prb-help">
|
|
@@ -12093,13 +12154,15 @@ function renderNotificationsPage(view) {
|
|
|
12093
12154
|
${renderGlobalBar({
|
|
12094
12155
|
current: "notifications",
|
|
12095
12156
|
unread: view.unreadNotifications ?? 0,
|
|
12157
|
+
running: view.runningRuns ?? 0,
|
|
12096
12158
|
driving: view.driving
|
|
12097
12159
|
})}
|
|
12098
|
-
${renderContextBar("Activity", view.notifications.length === 0 ? "" : renderReadAll(unread))}
|
|
12099
12160
|
<div class="prb-shell">
|
|
12161
|
+
${renderContextBar("Activity", { actions: view.notifications.length === 0 ? void 0 : renderReadAll(unread) })}
|
|
12100
12162
|
${renderWorkspaceRail({
|
|
12101
12163
|
current: "notifications",
|
|
12102
12164
|
unread: view.unreadNotifications ?? 0,
|
|
12165
|
+
running: view.runningRuns ?? 0,
|
|
12103
12166
|
sidebar: view.sidebar
|
|
12104
12167
|
})}
|
|
12105
12168
|
<main class="prb-index prb-notifications-page">
|
|
@@ -13150,6 +13213,37 @@ async function chooseFolder(deps) {
|
|
|
13150
13213
|
}
|
|
13151
13214
|
//#endregion
|
|
13152
13215
|
//#region ../../packages/review-harness/src/workspace/runs.ts
|
|
13216
|
+
/**
|
|
13217
|
+
* A question or a drawing, while it is still running. A stopped one is over for the reader, and an
|
|
13218
|
+
* answered one is only being saved: neither is executing, so neither is listed or counted.
|
|
13219
|
+
*/
|
|
13220
|
+
function activeAssistantRun(run) {
|
|
13221
|
+
if (run.state !== "running") return null;
|
|
13222
|
+
return {
|
|
13223
|
+
key: `${run.kind}:${run.workspaceId}`,
|
|
13224
|
+
kind: run.kind,
|
|
13225
|
+
...run.kind === "diagram" ? { drawing: run.drawing ?? "map" } : {},
|
|
13226
|
+
reviewId: run.workspaceId,
|
|
13227
|
+
startedAt: run.startedAt
|
|
13228
|
+
};
|
|
13229
|
+
}
|
|
13230
|
+
/**
|
|
13231
|
+
* A repository, or one part of it, being mapped. Its registry holds it only while it runs, so every
|
|
13232
|
+
* one is active. It belongs to the repository, and the review it was started from is where it opens.
|
|
13233
|
+
*/
|
|
13234
|
+
function activeArchitectureRun(run) {
|
|
13235
|
+
return {
|
|
13236
|
+
key: `architecture:${run.key}`,
|
|
13237
|
+
kind: "diagram",
|
|
13238
|
+
drawing: "architecture",
|
|
13239
|
+
reviewId: run.reviewId,
|
|
13240
|
+
repoPath: run.repoPath,
|
|
13241
|
+
scope: run.scope,
|
|
13242
|
+
...run.partName ? { scopeName: run.partName } : {},
|
|
13243
|
+
stage: run.stage,
|
|
13244
|
+
startedAt: run.startedAt
|
|
13245
|
+
};
|
|
13246
|
+
}
|
|
13153
13247
|
function runKindOf(job) {
|
|
13154
13248
|
if (purposeOf(job) === "update") return "update";
|
|
13155
13249
|
if (job.reviewType === "kiss") return "kiss";
|
|
@@ -13176,7 +13270,7 @@ function operationRunKind(operation) {
|
|
|
13176
13270
|
* The history is ordered newest ending first, then by key, a total order, because it is hashed into
|
|
13177
13271
|
* the page's revision and a tie left to the directory would reload the page for nothing.
|
|
13178
13272
|
*/
|
|
13179
|
-
function deriveRuns(jobs, isLive, rows, now) {
|
|
13273
|
+
function deriveRuns(jobs, isLive, rows, now, active = []) {
|
|
13180
13274
|
const inProgress = deriveOperations(jobs, isLive, rows);
|
|
13181
13275
|
const recent = [];
|
|
13182
13276
|
for (const job of jobs) {
|
|
@@ -13236,6 +13330,7 @@ function deriveRuns(jobs, isLive, rows, now) {
|
|
|
13236
13330
|
recent.sort((a, b) => b.endedAt - a.endedAt || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
13237
13331
|
return {
|
|
13238
13332
|
inProgress,
|
|
13333
|
+
active: sortActive(active),
|
|
13239
13334
|
recent
|
|
13240
13335
|
};
|
|
13241
13336
|
}
|
|
@@ -13254,6 +13349,20 @@ function fromAttempt(attempt) {
|
|
|
13254
13349
|
...attempt.agentId ? { agentId: attempt.agentId } : {}
|
|
13255
13350
|
};
|
|
13256
13351
|
}
|
|
13352
|
+
/** Newest first, ending on the key, a total order, because the list is hashed into the revision. */
|
|
13353
|
+
function sortActive(active) {
|
|
13354
|
+
return [...active].sort((a, b) => b.startedAt - a.startedAt || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
|
|
13355
|
+
}
|
|
13356
|
+
/**
|
|
13357
|
+
* How many executions are going on now: every operation but the queued, and every registry run.
|
|
13358
|
+
*
|
|
13359
|
+
* The one number the Runs page's Running figure and the sidebar's count beside Runs both show, so the
|
|
13360
|
+
* count a reader follows and the page it opens cannot disagree. Queued work is waiting, not running.
|
|
13361
|
+
* A row and the job it handed over to are one operation already (`deriveOperations`).
|
|
13362
|
+
*/
|
|
13363
|
+
function runningCount(operations, active) {
|
|
13364
|
+
return operations.filter((operation) => operation.kind !== "queued").length + active.length;
|
|
13365
|
+
}
|
|
13257
13366
|
/**
|
|
13258
13367
|
* The three numbers above the lists. Failed counts every failed attempt the page can list, earlier
|
|
13259
13368
|
* attempts included and removed ones not: the same population as the history, so the count and the
|
|
@@ -13261,7 +13370,7 @@ function fromAttempt(attempt) {
|
|
|
13261
13370
|
*/
|
|
13262
13371
|
function runCounts(runs) {
|
|
13263
13372
|
return {
|
|
13264
|
-
running: runs.inProgress
|
|
13373
|
+
running: runningCount(runs.inProgress, runs.active),
|
|
13265
13374
|
queued: runs.inProgress.filter((operation) => operation.kind === "queued").length,
|
|
13266
13375
|
failed: runs.recent.filter((entry) => entry.status === "failed").length
|
|
13267
13376
|
};
|
|
@@ -13280,6 +13389,7 @@ function runsRevision(runs, cleanup) {
|
|
|
13280
13389
|
if (cleanup && (cleanup.refusals.length > 0 || cleanup.error)) hash.update(`cleanup:${cleanup.at}:${cleanup.error ?? ""}:${cleanup.refusals.join("\0")}\n`);
|
|
13281
13390
|
const byKey = (a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
13282
13391
|
for (const operation of [...runs.inProgress].sort(byKey)) hash.update(`op:${operation.key}:${operation.kind}:${operation.phase ?? ""}:${operation.reviewId ?? ""}\n`);
|
|
13392
|
+
for (const run of [...runs.active].sort(byKey)) hash.update(`act:${run.key}:${run.startedAt}:${run.drawing ?? ""}:${run.stage ?? ""}:${run.reviewId ?? ""}\n`);
|
|
13283
13393
|
for (const entry of [...runs.recent].sort(byKey)) hash.update(`run:${entry.key}:${entry.status}:${entry.reviewDeleted ? "deleted" : ""}\n`);
|
|
13284
13394
|
return hash.digest("hex").slice(0, 16);
|
|
13285
13395
|
}
|
|
@@ -13316,7 +13426,23 @@ function liveOperations(now = Date.now(), jobs = allJobs(), rows = pendingBatchR
|
|
|
13316
13426
|
* unchanged.
|
|
13317
13427
|
*/
|
|
13318
13428
|
function liveRuns(now = Date.now(), jobs = allJobs(), rows = pendingBatchRows()) {
|
|
13319
|
-
return deriveRuns(jobs, (job) => isClaimed(job.id, now), rows, now);
|
|
13429
|
+
return deriveRuns(jobs, (job) => isClaimed(job.id, now), rows, now, liveActiveRuns());
|
|
13430
|
+
}
|
|
13431
|
+
/**
|
|
13432
|
+
* The work this server's in-memory registries hold now: questions, drawings and mapping runs.
|
|
13433
|
+
*
|
|
13434
|
+
* This process's only, like the queue, and for the same reason: they run as children of the
|
|
13435
|
+
* workspace server and nothing else can start one. Not on disk, so nothing is left once they end.
|
|
13436
|
+
*/
|
|
13437
|
+
function liveActiveRuns() {
|
|
13438
|
+
return sortActive([...heldRuns().map(activeAssistantRun).filter((run) => run !== null), ...allArchitectureRuns().map(activeArchitectureRun)]);
|
|
13439
|
+
}
|
|
13440
|
+
/**
|
|
13441
|
+
* How many executions are going on now, as the count beside Runs shows it on every page: the Runs
|
|
13442
|
+
* page's own Running figure, without reading the week of history to get it.
|
|
13443
|
+
*/
|
|
13444
|
+
function runningNow(now = Date.now()) {
|
|
13445
|
+
return runningCount(liveOperations(now), liveActiveRuns());
|
|
13320
13446
|
}
|
|
13321
13447
|
//#endregion
|
|
13322
13448
|
//#region ../../packages/review-harness/src/workspace/reviews_revision.ts
|
|
@@ -13464,7 +13590,9 @@ var RUN_KIND_LABELS = {
|
|
|
13464
13590
|
review: "Initial review",
|
|
13465
13591
|
update: "Update",
|
|
13466
13592
|
"re-run": "Re-run",
|
|
13467
|
-
kiss: "KISS review"
|
|
13593
|
+
kiss: "KISS review",
|
|
13594
|
+
ask: "Buddy AI",
|
|
13595
|
+
diagram: "Diagram"
|
|
13468
13596
|
};
|
|
13469
13597
|
var STATUS_LABELS = {
|
|
13470
13598
|
completed: "Completed",
|
|
@@ -13479,10 +13607,10 @@ var SOURCE_LABELS = {
|
|
|
13479
13607
|
};
|
|
13480
13608
|
function renderRunsPage(view) {
|
|
13481
13609
|
const now = view.now ?? Date.now();
|
|
13482
|
-
const { inProgress, recent } = view.runs;
|
|
13610
|
+
const { inProgress, active, recent } = view.runs;
|
|
13483
13611
|
const counts = runCounts(view.runs);
|
|
13484
13612
|
const shown = recent.slice(0, MAX_RUNS_SENT);
|
|
13485
|
-
const nothing = inProgress.length === 0 && recent.length === 0;
|
|
13613
|
+
const nothing = inProgress.length === 0 && active.length === 0 && recent.length === 0;
|
|
13486
13614
|
const notResponding = recent.some((entry) => entry.status === "not-responding");
|
|
13487
13615
|
return `<!doctype html>
|
|
13488
13616
|
<html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
|
|
@@ -13494,27 +13622,29 @@ function renderRunsPage(view) {
|
|
|
13494
13622
|
<link rel="stylesheet" href="/assets/sidebar.css">
|
|
13495
13623
|
<link rel="stylesheet" href="/assets/shell.css">
|
|
13496
13624
|
</head>
|
|
13497
|
-
<body class="prb-index-body" data-way-back="${escapeHtml$1(wayBackSentence())}"${view.revision === void 0 ? "" : ` data-runs-revision="${escapeHtml$1(view.revision)}"`} data-live="${inProgress.length > 0}">
|
|
13625
|
+
<body class="prb-index-body" data-way-back="${escapeHtml$1(wayBackSentence())}"${view.revision === void 0 ? "" : ` data-runs-revision="${escapeHtml$1(view.revision)}"`} data-live="${inProgress.length > 0 || active.length > 0}">
|
|
13498
13626
|
${renderGlobalBar({
|
|
13499
13627
|
current: "runs",
|
|
13500
13628
|
unread: view.unreadNotifications ?? 0,
|
|
13629
|
+
running: view.runningRuns ?? 0,
|
|
13501
13630
|
driving: view.driving
|
|
13502
13631
|
})}
|
|
13503
|
-
${renderContextBar("Runs", nothing ? "" : renderControls(notResponding))}
|
|
13504
13632
|
<div class="prb-shell">
|
|
13633
|
+
${renderContextBar("Runs", { controls: nothing ? void 0 : renderControls(notResponding) })}
|
|
13505
13634
|
${renderWorkspaceRail({
|
|
13506
13635
|
current: "runs",
|
|
13507
13636
|
unread: view.unreadNotifications ?? 0,
|
|
13637
|
+
running: view.runningRuns ?? 0,
|
|
13508
13638
|
sidebar: view.sidebar
|
|
13509
13639
|
})}
|
|
13510
13640
|
<main class="prb-index prb-runs">
|
|
13511
13641
|
<p class="prb-runs-lede">Monitor review executions and recent activity.</p>
|
|
13512
13642
|
${renderSummary(counts)}
|
|
13513
|
-
${nothing ? `<p class="prb-index-empty" data-runs-empty>Nothing has run in the last seven days. Reviews, updates and re-runs appear here as soon as they start, whether from this workspace, a terminal or an MCP client.</p>` : `${renderInProgress(inProgress, view.reviews, now, runsSectionOpen("in-progress", view.sectionsCollapsed))}
|
|
13643
|
+
${nothing ? `<p class="prb-index-empty" data-runs-empty>Nothing has run in the last seven days. Reviews, updates and re-runs appear here as soon as they start, whether from this workspace, a terminal or an MCP client.</p>` : `${renderInProgress(inProgress, active, view.reviews, now, runsSectionOpen("in-progress", view.sectionsCollapsed))}
|
|
13514
13644
|
${renderRecent(shown, recent.length, view, now, runsSectionOpen("recent", view.sectionsCollapsed))}
|
|
13515
13645
|
<p class="prb-index-none" data-no-matches hidden>No runs match these filters. <button type="button" class="prb-index-clear" data-clear-filters-inline>Clear filters</button></p>`}
|
|
13516
13646
|
${renderCleanup(view.cleanup, now)}
|
|
13517
|
-
<p class="prb-index-note">Runs are kept for seven days, on this machine only. Older ones are never shown, and their records are deleted by the workspace server, which checks every ten minutes while it is running and once when it starts. Removing a run takes it off this page and changes nothing about its review.</p>
|
|
13647
|
+
<p class="prb-index-note">Runs are kept for seven days, on this machine only. Older ones are never shown, and their records are deleted by the workspace server, which checks every ten minutes while it is running and once when it starts. Removing a run takes it off this page and changes nothing about its review. Questions to Buddy AI and diagrams being drawn are listed while they run and are not kept afterwards.</p>
|
|
13518
13648
|
</main>
|
|
13519
13649
|
</div>
|
|
13520
13650
|
<script src="/assets/runs.js" type="module"><\/script>
|
|
@@ -13576,12 +13706,28 @@ function renderControls(notResponding) {
|
|
|
13576
13706
|
* the scope that list has on purpose: work that is running is seen machine-wide, while a request
|
|
13577
13707
|
* still waiting for a slot is held by the server that accepted it and nowhere else.
|
|
13578
13708
|
*/
|
|
13579
|
-
|
|
13709
|
+
/**
|
|
13710
|
+
* Work going on now: review jobs and the rows waiting for them, then what the in-memory registries
|
|
13711
|
+
* hold (questions, drawings, mapping), each entry one execution.
|
|
13712
|
+
*
|
|
13713
|
+
* The two kinds are listed together by when they started, running before queued, so the list reads
|
|
13714
|
+
* as one account of what is happening rather than as two sources the reader has to merge.
|
|
13715
|
+
*/
|
|
13716
|
+
function renderInProgress(operations, active, reviews, now, open) {
|
|
13717
|
+
const entries = [...operations.filter((operation) => operation.kind !== "queued").map((operation) => ({
|
|
13718
|
+
at: operation.startedAt ?? 0,
|
|
13719
|
+
html: renderOperationRun(operation, reviewOf(operation, reviews), now)
|
|
13720
|
+
})), ...active.map((run) => ({
|
|
13721
|
+
at: run.startedAt,
|
|
13722
|
+
html: renderActiveRun(run, reviews, now)
|
|
13723
|
+
}))].sort((a, b) => b.at - a.at);
|
|
13724
|
+
const queued = operations.filter((operation) => operation.kind === "queued").map((operation) => renderOperationRun(operation, reviewOf(operation, reviews), now));
|
|
13725
|
+
const count = operations.length + active.length;
|
|
13580
13726
|
return `<details class="prb-index-section prb-runs-section" data-runs-section="in-progress"${open ? " open" : ""}>
|
|
13581
|
-
<summary class="prb-index-section-head">${RUNS_SECTION_NAMES["in-progress"].title} · <span data-section-count>${
|
|
13582
|
-
${
|
|
13727
|
+
<summary class="prb-index-section-head">${RUNS_SECTION_NAMES["in-progress"].title} · <span data-section-count>${count}</span></summary>
|
|
13728
|
+
${count === 0 ? "<p class=\"prb-runs-idle\" data-runs-idle>Nothing is running.</p>" : ""}
|
|
13583
13729
|
<div class="prb-cards" data-run-list>
|
|
13584
|
-
${
|
|
13730
|
+
${entries.map((entry) => entry.html).join("")}${queued.join("")}
|
|
13585
13731
|
</div>
|
|
13586
13732
|
<p class="prb-runs-scope">Queued shows the reviews added from this workspace. Pull requests waiting in a terminal batch appear once they start.</p>
|
|
13587
13733
|
</details>`;
|
|
@@ -13675,6 +13821,54 @@ function renderOperationRun(operation, review, now) {
|
|
|
13675
13821
|
${own ? `<a class="prb-card-link" href="${escapeHtml$1(reviewHref(own))}">${body}</a>` : `<div class="prb-card-link">${body}</div>`}
|
|
13676
13822
|
</article>`;
|
|
13677
13823
|
}
|
|
13824
|
+
var MAPPING_STEPS = {
|
|
13825
|
+
exploring: "Exploring the code",
|
|
13826
|
+
checking: "Checking the evidence",
|
|
13827
|
+
correcting: "Correcting the map"
|
|
13828
|
+
};
|
|
13829
|
+
/** What a registry run is doing, in the words its own pane uses. */
|
|
13830
|
+
function activeRunLine(run) {
|
|
13831
|
+
if (run.kind === "ask") return "Buddy AI · Answering a question";
|
|
13832
|
+
if (run.drawing === "architecture") return `${run.scope === void 0 || run.scope === "repository" ? "Mapping the architecture" : `Mapping ${run.scopeName ?? "one part"} in depth`}${run.stage ? ` · ${MAPPING_STEPS[run.stage]}` : ""}`;
|
|
13833
|
+
return run.drawing === "implementation" ? "Drawing an implementation diagram" : "Drawing the change map";
|
|
13834
|
+
}
|
|
13835
|
+
/** The view a registry run's work appears in, on its review's page. */
|
|
13836
|
+
var ACTIVE_RUN_VIEWS = {
|
|
13837
|
+
map: "map",
|
|
13838
|
+
implementation: "diagram",
|
|
13839
|
+
architecture: "architecture"
|
|
13840
|
+
};
|
|
13841
|
+
/**
|
|
13842
|
+
* A question, a drawing or a mapping run, while it lasts. A link to the review whose page shows it
|
|
13843
|
+
* and holds its Stop, as every entry in progress is. A mapping run opens the review it was started
|
|
13844
|
+
* from, at the Architecture pane with its part selected (the `part` the pane reads on load). When
|
|
13845
|
+
* that review is no longer here the entry says so and leads nowhere: another review of the same
|
|
13846
|
+
* repository is not the one that asked, and opening it would be opening somebody else's map.
|
|
13847
|
+
*/
|
|
13848
|
+
function renderActiveRun(run, reviews, now) {
|
|
13849
|
+
const review = run.reviewId !== void 0 ? reviews.get(run.reviewId) : void 0;
|
|
13850
|
+
const repo = review ? lastSegment(review.repoPath) : run.repoPath ? lastSegment(run.repoPath) : void 0;
|
|
13851
|
+
const where = review ? {
|
|
13852
|
+
repo,
|
|
13853
|
+
pr: review.prNumber,
|
|
13854
|
+
branch: followedRefName(review.branch, review.headResolvedRef) ?? review.branch
|
|
13855
|
+
} : { repo };
|
|
13856
|
+
const title = review?.title || repo || "Review";
|
|
13857
|
+
const gone = review === void 0 && run.reviewId !== void 0;
|
|
13858
|
+
const body = `<div class="prb-card-head"><h3>${escapeHtml$1(title)}</h3></div>
|
|
13859
|
+
${renderWhere(where)}
|
|
13860
|
+
<p class="prb-card-meta">
|
|
13861
|
+
<span class="prb-card-analysis" data-analysis="running" role="status"><i aria-hidden="true"></i><span class="prb-visually-hidden">Running: </span>${escapeHtml$1(activeRunLine(run))}</span>
|
|
13862
|
+
<span class="prb-run-provenance">${SOURCE_LABELS.workspace}</span>
|
|
13863
|
+
${since("Started", run.startedAt, now)}
|
|
13864
|
+
</p>
|
|
13865
|
+
${gone ? "<p class=\"prb-card-meta\" data-run-review-gone>Started from a review that is no longer here. Open the map from any review of this repository.</p>" : ""}`;
|
|
13866
|
+
const part = run.drawing === "architecture" && run.scope !== void 0 && run.scope !== "repository" ? `&part=${encodeURIComponent(run.scope)}` : "";
|
|
13867
|
+
const view = run.kind === "diagram" ? `#${ACTIVE_RUN_VIEWS[run.drawing ?? "map"]}` : "";
|
|
13868
|
+
return `<article class="prb-card prb-run" data-run data-run-status="in-progress" data-run-kind="${run.kind}" data-key="${escapeHtml$1(run.key)}" data-search-text="${escapeHtml$1(searchOf(`${title} ${run.scopeName ?? ""}`, where))}">
|
|
13869
|
+
${review ? `<a class="prb-card-link" href="${escapeHtml$1(reviewHref(review) + part + view)}">${body}</a>` : `<div class="prb-card-link">${body}</div>`}
|
|
13870
|
+
</article>`;
|
|
13871
|
+
}
|
|
13678
13872
|
/**
|
|
13679
13873
|
* A run that has ended, or stopped answering.
|
|
13680
13874
|
*
|
|
@@ -16160,6 +16354,7 @@ function indexView() {
|
|
|
16160
16354
|
activity,
|
|
16161
16355
|
revision: reviewsRevision(states, operations),
|
|
16162
16356
|
unreadNotifications: unreadCount$1(),
|
|
16357
|
+
runningRuns: runningCount(operations, liveActiveRuns()),
|
|
16163
16358
|
driving: isDriving(),
|
|
16164
16359
|
theme: readPreferences().theme,
|
|
16165
16360
|
sidebar: readPreferences().sidebar,
|
|
@@ -16253,6 +16448,7 @@ async function handleSettings(req, res, params, indexToken) {
|
|
|
16253
16448
|
sendHtml(res, 200, renderSettingsPage({
|
|
16254
16449
|
settings: readSettings(),
|
|
16255
16450
|
unreadNotifications: unreadCount$1(),
|
|
16451
|
+
runningRuns: runningNow(),
|
|
16256
16452
|
driving: isDriving(),
|
|
16257
16453
|
sidebar: readPreferences().sidebar
|
|
16258
16454
|
}), indexCookieHeader(indexToken));
|
|
@@ -16265,6 +16461,7 @@ function handleHelp(req, res, params, indexToken) {
|
|
|
16265
16461
|
if (!indexAuthorised(req, params, indexToken)) return refuseIndex(req, res);
|
|
16266
16462
|
sendHtml(res, 200, renderHelpPage({
|
|
16267
16463
|
unreadNotifications: unreadCount$1(),
|
|
16464
|
+
runningRuns: runningNow(),
|
|
16268
16465
|
driving: isDriving(),
|
|
16269
16466
|
theme: readPreferences().theme,
|
|
16270
16467
|
sidebar: readPreferences().sidebar,
|
|
@@ -16346,7 +16543,8 @@ function handleRecentNotifications(req, res, params, indexToken) {
|
|
|
16346
16543
|
outcome: toneOf(entry)
|
|
16347
16544
|
})),
|
|
16348
16545
|
more,
|
|
16349
|
-
unread: notifications.filter((entry) => entry.readAt === void 0).length
|
|
16546
|
+
unread: notifications.filter((entry) => entry.readAt === void 0).length,
|
|
16547
|
+
running: runningNow()
|
|
16350
16548
|
});
|
|
16351
16549
|
}
|
|
16352
16550
|
/**
|
|
@@ -16361,7 +16559,10 @@ function handleRecentNotifications(req, res, params, indexToken) {
|
|
|
16361
16559
|
function handleSeen(req, res, workspace) {
|
|
16362
16560
|
if (req.method !== "POST") return send(res, 405, { error: "Method not allowed." });
|
|
16363
16561
|
markReadForReview(workspace.id);
|
|
16364
|
-
send(res, 200, {
|
|
16562
|
+
send(res, 200, {
|
|
16563
|
+
unread: unreadCount$1(),
|
|
16564
|
+
running: runningNow()
|
|
16565
|
+
});
|
|
16365
16566
|
}
|
|
16366
16567
|
/**
|
|
16367
16568
|
* What a visible review page polls: how many are unread, and how many of those are about it.
|
|
@@ -16374,7 +16575,8 @@ function handleUnread(req, res, workspace) {
|
|
|
16374
16575
|
const { here, elsewhere } = unreadAround(workspace.id);
|
|
16375
16576
|
send(res, 200, {
|
|
16376
16577
|
unread: here + elsewhere,
|
|
16377
|
-
here
|
|
16578
|
+
here,
|
|
16579
|
+
running: runningNow()
|
|
16378
16580
|
});
|
|
16379
16581
|
}
|
|
16380
16582
|
/**
|
|
@@ -16401,6 +16603,7 @@ async function handleNotifications(req, res, params, indexToken) {
|
|
|
16401
16603
|
notifications,
|
|
16402
16604
|
gone: new Set(notifications.filter((entry) => !loadWorkspace(entry.reviewId)).map((entry) => entry.id)),
|
|
16403
16605
|
unreadNotifications: unreadCount$1(),
|
|
16606
|
+
runningRuns: runningNow(),
|
|
16404
16607
|
driving: isDriving(),
|
|
16405
16608
|
theme: readPreferences().theme,
|
|
16406
16609
|
sidebar: readPreferences().sidebar
|
|
@@ -16422,6 +16625,7 @@ function handleRuns(req, res, params, indexToken) {
|
|
|
16422
16625
|
cleanup,
|
|
16423
16626
|
revision: runsRevision(runs, cleanup),
|
|
16424
16627
|
unreadNotifications: unreadCount$1(),
|
|
16628
|
+
runningRuns: runCounts(runs).running,
|
|
16425
16629
|
driving: isDriving(),
|
|
16426
16630
|
theme: preferences.theme,
|
|
16427
16631
|
sidebar: preferences.sidebar,
|
|
@@ -16439,8 +16643,9 @@ function handleRunsRevision(req, res, indexToken) {
|
|
|
16439
16643
|
const runs = liveRuns();
|
|
16440
16644
|
send(res, 200, {
|
|
16441
16645
|
revision: runsRevision(runs, lastSweep()),
|
|
16442
|
-
live: runs.inProgress.length > 0,
|
|
16443
|
-
unread: unreadCount$1()
|
|
16646
|
+
live: runs.inProgress.length > 0 || runs.active.length > 0,
|
|
16647
|
+
unread: unreadCount$1(),
|
|
16648
|
+
running: runCounts(runs).running
|
|
16444
16649
|
});
|
|
16445
16650
|
}
|
|
16446
16651
|
/** Removing one ended run from the Runs page. See `run_removal.ts` for what that does and does not touch. */
|
|
@@ -16492,6 +16697,7 @@ function handleLiveNotifications(req, res, params, indexToken) {
|
|
|
16492
16697
|
const wanted = new Set((params.get("ids") ?? "").split(",").filter(Boolean).slice(0, 200));
|
|
16493
16698
|
send(res, 200, {
|
|
16494
16699
|
unread: notifications.filter((entry) => entry.readAt === void 0).length,
|
|
16700
|
+
running: runningNow(),
|
|
16495
16701
|
states: Object.fromEntries(notifications.map((entry) => [entry.id, rowStateOf(entry)])),
|
|
16496
16702
|
rows: notifications.filter((entry) => wanted.has(entry.id)).map((entry) => renderNotificationRow(entry, !loadWorkspace(entry.reviewId)))
|
|
16497
16703
|
});
|
|
@@ -16730,7 +16936,8 @@ function handleRevision(req, res, indexToken) {
|
|
|
16730
16936
|
revision: reviewsRevision(states, operations),
|
|
16731
16937
|
live: anythingRunning(states, operations),
|
|
16732
16938
|
driving: isDriving(),
|
|
16733
|
-
unread: unreadCount$1()
|
|
16939
|
+
unread: unreadCount$1(),
|
|
16940
|
+
running: runningCount(operations, liveActiveRuns())
|
|
16734
16941
|
});
|
|
16735
16942
|
}
|
|
16736
16943
|
var REVIEW_ID = /^[0-9a-f-]{36}$/;
|
|
@@ -17271,6 +17478,7 @@ async function sendWorkspacePage(res, workspace) {
|
|
|
17271
17478
|
complete: workspace.completedAt !== void 0,
|
|
17272
17479
|
pinned: workspace.pinned === true,
|
|
17273
17480
|
unreadNotifications: unreadAround(workspace.id).elsewhere,
|
|
17481
|
+
runningRuns: runningNow(),
|
|
17274
17482
|
driving: isDriving(),
|
|
17275
17483
|
prContext: workspace.prContext ? {
|
|
17276
17484
|
prContext: workspace.prContext,
|