prreviewbuddy 0.50.1 → 0.52.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 +82 -0
- package/dist/{delete_review-BIXqUnpx.js → delete_review-BiV1aBpx.js} +152 -42
- package/dist/main.js +7 -6
- package/dist/server.js +192 -82
- package/package.json +1 -1
- package/static/{chunk-BeL0x-wt.js → chunk-B5CvMZtt.js} +1 -1
- package/static/chunk-BeqgAIrQ.js +1 -1
- package/static/chunk-CofqJ-Qn.js +1 -0
- package/static/help.js +1 -1
- package/static/notifications.js +1 -1
- package/static/reviews.js +1 -1
- package/static/settings.js +1 -1
- package/static/shell.css +92 -53
- package/static/workspace.js +64 -64
- package/static/chunk-vvGSmhmc.js +0 -1
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as compactQueue, A as createPrState, An as
|
|
1
|
+
import { $ as compactQueue, $t as loadWorkspace, A as createPrState, An as reviewerDispositions, At as followedBranch, B as toneOf, Bn as resolveModel, Bt as phasesFor, C as readEvents, Cn as queueStamp, Ct as claimRequestUrl, D as announceJob, Dn as isQuestionOutstanding, Dt as fillFileUrlTemplate, E as telemetryUploadConsent, En as isIssueOutstanding, Et as remoteHost, F as markAllRead, Ft as isTerminal, G as extractJsonText, Gn as STORE_ROOT, Gt as isClaimed, H as withUsageRecorded, Hn as readAgentPreference, Ht as purposeOf, I as markRead, It as loadJob, J as readMarker, Jt as indexKeptIds, K as ReviewBeingDeletedError, Kn as AGENT_IDS, Kt as followedRefName, L as pruneNotifications, Ln as isCommitReview, Lt as saveJob, M as eventName, Mn as describeAuthorship, Mt as allJobs, N as headline, Nn as WITHHELD_REASON, Nt as endedAt, O as branchOf, On as issuesOutstanding, Ot as checkCodeFreshness, P as listNotifications, Pn as kissOutcome, Pt as fail, Q as activateQueued, Qn as AgentUnavailableError, Qt as lineagePosition, R as readNotification, Rn as git, Sn as queueRemove, St as writePortPreference, T as recordTelemetryUploadConsent, Tn as doneVerb, Tt as forgeFor, U as EXPLAIN_SIMPLY_PROMPT, Un as writeAgentPreference, V as unreadCount$1, Vn as clearAgentPreference, Vt as progressSteps, W as asString, Xt as latestKissRun, Y as removeWorktree, Yn as agentFor, Yt as indexReviewGroups, Z as relativeTime$1, Zn as AgentCancelledError, _ as agentEnvOf, _n as offersLifecycle, _t as MAX_WORKSPACE_PORT, a as wasBlocked, an as reviewedCommit, bn as queueAdd, bt as resolveWorkspacePort, c as RefreshUnavailableError, cn as touchWorkspace, ct as serverEntryExists, d as refreshPrContext, dn as LIFECYCLES, f as isUnchanged, fn as activityLabel, ft as writeServerRecord, gn as lifecyclePill, gt as DEFAULT_WORKSPACE_PORT, h as liveUpdateFor, hn as lifecycleChip, ht as feedbackUrl, i as liveJobsFor, in as recentWorkspaces, it as clearServerRecord, j as createPrUpdate, jn as processDiscussion, jt as reviewedShaOf, k as subjectOf, kn as questionsOutstanding, kt as checkReview, l as mergeUpdateInto, ln as workspaceRevision, lt as spawnServer, m as UpdateAlreadyRunningError, mn as lifecycleAction, n as deleteReview, nt as readIndexToken, o as discardJob, ot as readServerRecord, p as askCheckout, pn as isLifecycle, pt as BUILD_VERSION, q as MANAGED_ROOT, qt as groupByLineage, r as lineageIds, rn as previousKissResult, s as FETCH_TIMEOUT_MS, t as deleteLineage, tn as mutateWorkspace, tt as settleQueue, u as updateReview, un as workspaceStamps, vn as parseLifecycle, vt as MIN_WORKSPACE_PORT, w as record, wn as readQueue, x as runJob, xn as queueAddMany, xt as statedWorkspacePort, y as startKissJob, yn as sectionOf, yt as parseWorkspacePort, z as removeNotification, zt as isLiveJob } from "./delete_review-BiV1aBpx.js";
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
@@ -242,6 +242,47 @@ function renderInlineText(value) {
|
|
|
242
242
|
//#endregion
|
|
243
243
|
//#region ../../packages/review-harness/src/workspace/reviews_index.ts
|
|
244
244
|
/**
|
|
245
|
+
* The sections of the list, top to bottom.
|
|
246
|
+
*
|
|
247
|
+
* Pinned is not a lifecycle state. It is where a card is drawn, on top of a lifecycle it keeps: a
|
|
248
|
+
* pinned review can be Active, Waiting or Done, and unpinning it puts it back in the section that
|
|
249
|
+
* says. That is why it lives here, beside the other placement rules, and not in `LIFECYCLES`.
|
|
250
|
+
*/
|
|
251
|
+
var INDEX_SECTIONS = [
|
|
252
|
+
"pinned",
|
|
253
|
+
"active",
|
|
254
|
+
"waiting",
|
|
255
|
+
"done"
|
|
256
|
+
];
|
|
257
|
+
/**
|
|
258
|
+
* Which section a card is drawn in: Pinned when pinned, and otherwise where its lifecycle says.
|
|
259
|
+
*
|
|
260
|
+
* Pinning controls prominence in the general list and never overrides an ordering the reviewer
|
|
261
|
+
* asked for. Under such a filter a pinned card stays in its lifecycle section, in the order the
|
|
262
|
+
* filter gives, and only its star says it is pinned.
|
|
263
|
+
*/
|
|
264
|
+
function indexSectionOf(pinned, lifecycle, activity, filter) {
|
|
265
|
+
return pinned && !ordersExplicitly(filter) ? "pinned" : sectionOf(lifecycle, activity);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Whether a filter replaces the activity order with one the reviewer set. Queue is the only one:
|
|
269
|
+
* the list is the queue, in its order.
|
|
270
|
+
* `orderIndex` and `indexSectionOf` both ask this, so the ordering and the placement cannot disagree.
|
|
271
|
+
*/
|
|
272
|
+
function ordersExplicitly(filter) {
|
|
273
|
+
return filter === "queue";
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* The lifecycle chip on a card, or absent.
|
|
277
|
+
*
|
|
278
|
+
* In a lifecycle section the heading already names the lifecycle, so the chip only says what moved
|
|
279
|
+
* (`lifecycleChip`). In Pinned nothing names it, so the chip does, in the words the review page's
|
|
280
|
+
* pill uses: the list and the page give one answer about the same review.
|
|
281
|
+
*/
|
|
282
|
+
function cardLifecycleChip(pinned, lifecycle, activity) {
|
|
283
|
+
return pinned ? lifecyclePill(lifecycle, activity)?.text : lifecycleChip(lifecycle, activity);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
245
286
|
* "45 reviews · 13 branches", and "13 reviews" when those are the same thirteen things.
|
|
246
287
|
*
|
|
247
288
|
* The two numbers are different questions and only both worth asking once they differ. A page of
|
|
@@ -269,12 +310,12 @@ function markItemLabel(mark) {
|
|
|
269
310
|
return mark === "complete" ? "Mark reviewed" : "Mark in progress";
|
|
270
311
|
}
|
|
271
312
|
/**
|
|
272
|
-
* The review page's
|
|
273
|
-
*
|
|
274
|
-
*
|
|
313
|
+
* The review page's Pin item, which says what pressing it will do. The words of the index's star,
|
|
314
|
+
* so pinning from either place reads as the same act. Here because the browser relabels the item
|
|
315
|
+
* after the change, for the reason `markItemLabel` is here.
|
|
275
316
|
*/
|
|
276
|
-
function
|
|
277
|
-
return
|
|
317
|
+
function pinItemLabel(pinned) {
|
|
318
|
+
return pinned ? "Unpin review" : "Pin review";
|
|
278
319
|
}
|
|
279
320
|
/**
|
|
280
321
|
* The queue item, on a card and on the review's own page. "review queue" rather than "queue",
|
|
@@ -285,16 +326,6 @@ function queueItemLabel(queued) {
|
|
|
285
326
|
return queued ? "Remove from review queue" : "Add to review queue";
|
|
286
327
|
}
|
|
287
328
|
/**
|
|
288
|
-
* What `Continue reviewing` opens, and whether it exists at all.
|
|
289
|
-
*
|
|
290
|
-
* One rule rather than an expression in the template, because "is there anything to continue" and
|
|
291
|
-
* "what is first" are the same question and must not be able to answer differently -- a button that
|
|
292
|
-
* is present but navigates nowhere is worse than no button.
|
|
293
|
-
*/
|
|
294
|
-
function continueTarget(queue) {
|
|
295
|
-
return queue[0] ?? null;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
329
|
* The Queue pill, which carries a number only once there is one.
|
|
299
330
|
*
|
|
300
331
|
* "Queue 0" is a count of something that is not there, and reads as a fault. Bare "Queue" reads as
|
|
@@ -359,6 +390,11 @@ function anythingRunning(states) {
|
|
|
359
390
|
return false;
|
|
360
391
|
}
|
|
361
392
|
//#endregion
|
|
393
|
+
//#region ../../packages/review-harness/src/workspace/fetch_updates_view.ts
|
|
394
|
+
/** A card's and a review page's item. Named for what it reaches: a remote branch, not the list. */
|
|
395
|
+
var FETCH_REVIEW_LABEL = "Fetch remote updates";
|
|
396
|
+
var FETCH_SELECTED_LABEL = "Fetch selected";
|
|
397
|
+
//#endregion
|
|
362
398
|
//#region ../../packages/review-harness/src/workspace/way_back.ts
|
|
363
399
|
var CLI = {
|
|
364
400
|
command: "prreviewbuddy open",
|
|
@@ -660,7 +696,7 @@ function renderWorkspaceRail(options) {
|
|
|
660
696
|
* with the reason for it.
|
|
661
697
|
*/
|
|
662
698
|
function renderHeaderNav(_options) {
|
|
663
|
-
return `<nav class="prb-header-nav" aria-label="Workspace"><a class="prb-feedback" href="${escapeHtml$1(feedbackUrl())}" target="_blank" rel="noopener noreferrer">Send feedback</a></nav>`;
|
|
699
|
+
return `<nav class="prb-header-nav" aria-label="Workspace"><a class="prb-feedback" href="${escapeHtml$1(feedbackUrl())}" target="_blank" rel="noopener noreferrer"><span class="prb-feedback-icon" aria-hidden="true">${iconSvg("feedback")}</span><span class="prb-feedback-label">Send feedback</span></a></nav>`;
|
|
664
700
|
}
|
|
665
701
|
//#endregion
|
|
666
702
|
//#region ../../packages/review-harness/src/domain/utils/file_path.ts
|
|
@@ -3642,10 +3678,11 @@ ${renderRunControl(view.canRun, "Run KISS review again")}`;
|
|
|
3642
3678
|
function renderResultBody(run, provenance = "inline") {
|
|
3643
3679
|
const result = run.result;
|
|
3644
3680
|
if (!result) return "";
|
|
3645
|
-
|
|
3681
|
+
const outcome = kissOutcome(result, run.audit);
|
|
3682
|
+
return `${outcome === "proportionate" ? `<div class="prb-kiss-outcome" data-review-outcome="empty">
|
|
3646
3683
|
<h4 class="prb-kiss-outcome-title">Appropriately simple</h4>
|
|
3647
3684
|
<p class="prb-kiss-outcome-body">This change appears appropriately simple for the current requirements. No material simplifications were identified.</p>
|
|
3648
|
-
</div>` : ""}
|
|
3685
|
+
</div>` : outcome === "incomplete" ? renderIncomplete(run) : ""}
|
|
3649
3686
|
<div class="prb-kiss-assessment pr-helper-markdown">${renderMarkdown(result.assessment)}</div>
|
|
3650
3687
|
<div class="prb-kiss-summary pr-helper-markdown">${renderMarkdown(result.summary)}</div>
|
|
3651
3688
|
${result.aggregateEstimate ? `<p class="prb-kiss-estimate"><span class="prb-kiss-estimate-label">Rough estimate</span> ${escapeHtml$1(result.aggregateEstimate)}</p>` : ""}
|
|
@@ -3674,15 +3711,38 @@ function renderKissRecommendations(view) {
|
|
|
3674
3711
|
const result = presented?.result;
|
|
3675
3712
|
if (!result) return `${RECS_HEAD}${renderNoRunYet(view)}`;
|
|
3676
3713
|
const earlier = earlierNote(view, presented);
|
|
3677
|
-
|
|
3714
|
+
const outcome = kissOutcome(result, presented.audit);
|
|
3715
|
+
if (outcome === "incomplete") return `${RECS_HEAD}${earlier}${renderIncomplete(presented)}`;
|
|
3716
|
+
if (outcome === "proportionate") return `${RECS_HEAD}${earlier}
|
|
3678
3717
|
<div class="prb-kiss-outcome" data-review-outcome="empty">
|
|
3679
3718
|
<h4 class="prb-kiss-outcome-title">Nothing to simplify</h4>
|
|
3680
3719
|
<p class="prb-kiss-outcome-body">The analysis completed and did not identify a materially simpler way to meet these requirements.</p>
|
|
3681
3720
|
</div>`;
|
|
3682
|
-
return `${RECS_HEAD}${earlier}
|
|
3721
|
+
return `${RECS_HEAD}${earlier}${renderWithheldNote(presented)}
|
|
3683
3722
|
${result.recommendations.map((recommendation) => renderRecommendation(recommendation)).join("")}`;
|
|
3684
3723
|
}
|
|
3685
3724
|
/**
|
|
3725
|
+
* A run whose every recommendation was withheld: incomplete, and said to be.
|
|
3726
|
+
*
|
|
3727
|
+
* Never "appropriately simple" and never "nothing to simplify". The analysis did recommend
|
|
3728
|
+
* something; it just did not return enough of it to show, and the one conclusion that cannot be
|
|
3729
|
+
* drawn from that is that the change is proportionate. Its own outcome value, so a test can tell it
|
|
3730
|
+
* from both a clean answer and a failed run.
|
|
3731
|
+
*/
|
|
3732
|
+
function renderIncomplete(run) {
|
|
3733
|
+
const withheld = run.audit.withheldRecommendations ?? 0;
|
|
3734
|
+
return `<div class="prb-kiss-outcome" data-review-outcome="incomplete">
|
|
3735
|
+
<h4 class="prb-kiss-outcome-title">Recommendations came back incomplete</h4>
|
|
3736
|
+
<p class="prb-kiss-outcome-body">The analysis returned ${withheld === 1 ? "a recommendation" : `${withheld} recommendations`}, but ${withheld === 1 ? "it was" : "each was"} ${escapeHtml$1(WITHHELD_REASON)}, so ${withheld === 1 ? "it cannot" : "none can"} be shown. This is not a finding that the change is appropriately simple. Running KISS again usually produces a complete answer.</p>
|
|
3737
|
+
</div>`;
|
|
3738
|
+
}
|
|
3739
|
+
/** Said on the list itself when some were withheld, since the correction box is on the other pane. */
|
|
3740
|
+
function renderWithheldNote(run) {
|
|
3741
|
+
const withheld = run.audit.withheldRecommendations ?? 0;
|
|
3742
|
+
if (withheld === 0) return "";
|
|
3743
|
+
return `<p class="prb-kiss-note" data-kiss-withheld>${withheld === 1 ? "1 more recommendation was" : `${withheld} more recommendations were`} withheld as incomplete: ${escapeHtml$1(WITHHELD_REASON)}.</p>`;
|
|
3744
|
+
}
|
|
3745
|
+
/**
|
|
3686
3746
|
* Which run these came from, said only when it is not the one the section is headed by.
|
|
3687
3747
|
*
|
|
3688
3748
|
* Silent in the ordinary case, because a permanent "this is the current run" strip is the noise
|
|
@@ -3896,14 +3956,30 @@ function renderAudit(run) {
|
|
|
3896
3956
|
const notes = [];
|
|
3897
3957
|
if (run.audit.unknownPaths.length > 0) notes.push(`${run.audit.unknownPaths.length} cited ${run.audit.unknownPaths.length === 1 ? "path does" : "paths do"} not exist in the reviewed version of the repository, and ${run.audit.unknownPaths.length === 1 ? "it was" : "they were"} withdrawn: ${run.audit.unknownPaths.join(", ")}.`);
|
|
3898
3958
|
if (run.audit.unlocatedRecommendations > 0) notes.push(`${run.audit.unlocatedRecommendations} recommendation${run.audit.unlocatedRecommendations === 1 ? "" : "s"} ${run.audit.unlocatedRecommendations === 1 ? "is" : "are"} shown without a file to open, because nothing ${run.audit.unlocatedRecommendations === 1 ? "it" : "they"} cited exists in the reviewed version of the repository.`);
|
|
3959
|
+
const withheld = run.audit.withheldRecommendations ?? 0;
|
|
3960
|
+
if (withheld > 0 && run.result && kissOutcome(run.result, run.audit) !== "incomplete") notes.push(`${withheld} recommendation${withheld === 1 ? " was" : "s were"} withheld as incomplete: ${WITHHELD_REASON}.`);
|
|
3899
3961
|
if (notes.length === 0) return "";
|
|
3900
3962
|
return `<div class="prb-kiss-audit" role="note">
|
|
3901
3963
|
<p class="prb-kiss-audit-title">What was corrected</p>
|
|
3902
3964
|
${notes.map((note) => `<p>${escapeHtml$1(note)}</p>`).join("")}
|
|
3903
3965
|
</div>`;
|
|
3904
3966
|
}
|
|
3967
|
+
/**
|
|
3968
|
+
* One recommendation, as a decision aid rather than an analysis record.
|
|
3969
|
+
*
|
|
3970
|
+
* The card carries what a reviewer needs to understand and weigh the recommendation: the title,
|
|
3971
|
+
* the case in a sentence or two, the simpler approach, what it gains, the trade-off that could
|
|
3972
|
+
* change the decision, and where to start. Everything that exists to audit or challenge it sits in
|
|
3973
|
+
* one disclosure beneath, closed until asked for.
|
|
3974
|
+
*
|
|
3975
|
+
* Reads both schemas. A record written under schema 1 has no gist, and its `complexity` stands in
|
|
3976
|
+
* as the lede, because it is the only field of that shape that names what the card is about; it is
|
|
3977
|
+
* then not repeated in the disclosure. Stored runs are history and are not rewritten to fit.
|
|
3978
|
+
*/
|
|
3905
3979
|
function renderRecommendation(recommendation) {
|
|
3906
3980
|
const worthiness = recommendation.commentWorthiness;
|
|
3981
|
+
const lede = recommendation.gist || recommendation.complexity || "";
|
|
3982
|
+
const [start, ...rest] = orderedLocations(recommendation.locations);
|
|
3907
3983
|
return `<article class="prb-kiss-rec" data-payoff="${recommendation.payoff}"${worthiness ? ` data-worthiness="${worthiness}"` : ""}>
|
|
3908
3984
|
<header class="prb-kiss-rec-head">
|
|
3909
3985
|
<h4 class="prb-kiss-rec-title">${escapeHtml$1(recommendation.title)}</h4>
|
|
@@ -3912,20 +3988,56 @@ function renderRecommendation(recommendation) {
|
|
|
3912
3988
|
${worthiness ? `<span class="prb-kiss-worthiness" data-worthiness="${worthiness}">${escapeHtml$1(WORTHINESS[worthiness])}</span>` : ""}
|
|
3913
3989
|
</div>
|
|
3914
3990
|
</header>
|
|
3915
|
-
${
|
|
3916
|
-
${field("
|
|
3917
|
-
${
|
|
3918
|
-
${
|
|
3919
|
-
${
|
|
3920
|
-
|
|
3991
|
+
${lede ? `<div class="prb-kiss-gist pr-helper-markdown" data-kiss-gist>${renderMarkdown(lede)}</div>` : ""}
|
|
3992
|
+
${field("Simpler approach", recommendation.simplerDirection)}
|
|
3993
|
+
${renderGains(recommendation)}
|
|
3994
|
+
${recommendation.caveat ? `<p class="prb-kiss-caveat" data-kiss-caveat><span class="prb-kiss-caveat-label">Trade-off</span> ${escapeHtml$1(recommendation.caveat)}</p>` : ""}
|
|
3995
|
+
${start ? `<div class="prb-kiss-locations">
|
|
3996
|
+
<p class="prb-kiss-locations-label">Start here</p>
|
|
3997
|
+
${renderLocationList([start])}
|
|
3998
|
+
</div>` : ""}
|
|
3999
|
+
${renderEvidence(recommendation, rest)}
|
|
3921
4000
|
${recommendation.draftComment ? `<details class="prb-kiss-draft">
|
|
3922
4001
|
<summary class="prb-kiss-draft-title">Draft comment</summary>
|
|
3923
4002
|
<blockquote class="pr-helper-markdown">${renderMarkdown(recommendation.draftComment)}</blockquote>
|
|
3924
4003
|
${renderMessageCopySource(recommendation.draftComment)}
|
|
3925
4004
|
</details>` : ""}
|
|
3926
|
-
${renderLocations(recommendation.locations)}
|
|
3927
4005
|
</article>`;
|
|
3928
4006
|
}
|
|
4007
|
+
/**
|
|
4008
|
+
* What acting on it gains, on one line: the estimate first, then the gains.
|
|
4009
|
+
*
|
|
4010
|
+
* The estimate says it is one in words beside it, so a phrase like "~35–50 lines" cannot be taken
|
|
4011
|
+
* for a measurement, and the gains beside it are not read as estimates too.
|
|
4012
|
+
*/
|
|
4013
|
+
function renderGains(recommendation) {
|
|
4014
|
+
const items = [...recommendation.reductionEstimate ? [`<span class="prb-kiss-gain" data-estimate>${escapeHtml$1(recommendation.reductionEstimate)} <span class="prb-kiss-gain-note">(estimate)</span></span>`] : [], ...(recommendation.gains ?? []).map((gain) => `<span class="prb-kiss-gain">${escapeHtml$1(gain)}</span>`)];
|
|
4015
|
+
if (items.length === 0) return "";
|
|
4016
|
+
return `<p class="prb-kiss-gains" data-kiss-gains>${items.join("<span class=\"prb-kiss-gain-sep\" aria-hidden=\"true\"> · </span>")}</p>`;
|
|
4017
|
+
}
|
|
4018
|
+
/**
|
|
4019
|
+
* The audit trail, in one disclosure: enough to check or challenge the recommendation.
|
|
4020
|
+
*
|
|
4021
|
+
* Closed by default. A reviewer should never have to open it to understand what is being
|
|
4022
|
+
* recommended, only to verify it. The other locations live here too, because the card names the
|
|
4023
|
+
* one place to start and a list of files up front is a decision the reader has not asked to make.
|
|
4024
|
+
*/
|
|
4025
|
+
function renderEvidence(recommendation, rest) {
|
|
4026
|
+
const body = [
|
|
4027
|
+
field("Evidence in this change", recommendation.evidence),
|
|
4028
|
+
field("Why the requirements do not call for it", recommendation.whyNotJustified),
|
|
4029
|
+
field("Trade-offs", recommendation.payoffAndTradeoffs),
|
|
4030
|
+
rest.length > 0 ? `<div class="prb-kiss-locations">
|
|
4031
|
+
<p class="prb-kiss-locations-label">Also relevant</p>
|
|
4032
|
+
${renderLocationList(rest)}
|
|
4033
|
+
</div>` : ""
|
|
4034
|
+
].filter(Boolean);
|
|
4035
|
+
if (body.length === 0) return "";
|
|
4036
|
+
return `<details class="prb-kiss-evidence" data-kiss-evidence>
|
|
4037
|
+
<summary class="prb-kiss-evidence-title">Evidence & trade-offs${escapeHtml$1(rest.length > 0 ? ` · ${rest.length} more ${rest.length === 1 ? "file" : "files"}` : "")}</summary>
|
|
4038
|
+
${body.join("\n ")}
|
|
4039
|
+
</details>`;
|
|
4040
|
+
}
|
|
3929
4041
|
function field(label, value) {
|
|
3930
4042
|
if (!value) return "";
|
|
3931
4043
|
return `<div class="prb-kiss-field">
|
|
@@ -3934,7 +4046,8 @@ function field(label, value) {
|
|
|
3934
4046
|
</div>`;
|
|
3935
4047
|
}
|
|
3936
4048
|
/**
|
|
3937
|
-
* Where
|
|
4049
|
+
* Where an item of complexity worth keeping points, primary first. A recommendation draws its own
|
|
4050
|
+
* "Start here" on the card and keeps the rest in its evidence disclosure; see `renderRecommendation`.
|
|
3938
4051
|
*
|
|
3939
4052
|
* `data-file` and `data-line` and nothing else: the workspace already opens anything carrying
|
|
3940
4053
|
* those, so a KISS location behaves exactly as a standard finding's does and there is no second
|
|
@@ -3970,7 +4083,7 @@ function field(label, value) {
|
|
|
3970
4083
|
*/
|
|
3971
4084
|
function renderLocations(locations) {
|
|
3972
4085
|
if (locations.length === 0) return "";
|
|
3973
|
-
const [first, ...rest] =
|
|
4086
|
+
const [first, ...rest] = orderedLocations(locations);
|
|
3974
4087
|
if (rest.length === 0) return `<div class="prb-kiss-locations">${renderLocationList([first])}</div>`;
|
|
3975
4088
|
return `<div class="prb-kiss-locations">
|
|
3976
4089
|
<p class="prb-kiss-locations-label">Start here</p>
|
|
@@ -3979,6 +4092,10 @@ function renderLocations(locations) {
|
|
|
3979
4092
|
${renderLocationList(rest)}
|
|
3980
4093
|
</div>`;
|
|
3981
4094
|
}
|
|
4095
|
+
/** Primary first, the rest in the order the model gave them. */
|
|
4096
|
+
function orderedLocations(locations) {
|
|
4097
|
+
return [...locations].sort((a, b) => Number(b.primary) - Number(a.primary));
|
|
4098
|
+
}
|
|
3982
4099
|
function renderLocationList(locations) {
|
|
3983
4100
|
return `<ul class="prb-kiss-locations-list">${locations.map((location) => {
|
|
3984
4101
|
const { dir, name } = splitFilePath(location.path);
|
|
@@ -5157,10 +5274,11 @@ function renderIdMenu(view, freshness) {
|
|
|
5157
5274
|
const branchItems = canWrite && latest;
|
|
5158
5275
|
const items = [
|
|
5159
5276
|
canWrite && (freshness || view.remote) ? "<button type=\"button\" class=\"prb-menu-item\" data-fresh-check>Check for updates</button>" : "",
|
|
5277
|
+
branchItems ? `<button type="button" class="prb-menu-item" data-fetch-remote hidden>${escapeHtml$1(FETCH_REVIEW_LABEL)}</button>` : "",
|
|
5160
5278
|
canWrite ? `<button type="button" class="prb-menu-item" data-mark-status="${mark}">${markItemLabel(mark)}</button>` : "",
|
|
5161
5279
|
...branchItems ? LIFECYCLES.map(({ id }) => `<button type="button" class="prb-menu-item" data-lifecycle-set="${id}"${offersLifecycle(id, view.lifecycle, view.activity) ? "" : " hidden"}>${escapeHtml$1(lifecycleAction(id))}</button>`) : [],
|
|
5162
5280
|
branchItems ? `<button type="button" class="prb-menu-item" data-queue-action="${view.queued ? "remove" : "add"}">${queueItemLabel(view.queued === true)}</button>` : "",
|
|
5163
|
-
canWrite ? `<button type="button" class="prb-menu-item" data-
|
|
5281
|
+
canWrite ? `<button type="button" class="prb-menu-item" data-pin-toggle aria-pressed="${view.pinned === true}">${pinItemLabel(view.pinned === true)}</button>` : "",
|
|
5164
5282
|
`<button type="button" class="prb-menu-item" data-copy-id="${safe}">Copy review ID</button>`,
|
|
5165
5283
|
canWrite ? `<button type="button" class="prb-menu-item prb-menu-item-danger" data-delete="${safe}">Delete review</button>` : ""
|
|
5166
5284
|
].filter(Boolean);
|
|
@@ -6836,10 +6954,6 @@ function startWatching(makeDeps = defaultDeps) {
|
|
|
6836
6954
|
};
|
|
6837
6955
|
}
|
|
6838
6956
|
//#endregion
|
|
6839
|
-
//#region ../../packages/review-harness/src/workspace/fetch_updates_view.ts
|
|
6840
|
-
var FETCH_UPDATES_LABEL = "Fetch updates";
|
|
6841
|
-
var FETCH_SELECTED_LABEL = "Fetch selected";
|
|
6842
|
-
//#endregion
|
|
6843
6957
|
//#region ../../packages/review-harness/src/workspace/reviews_page.ts
|
|
6844
6958
|
function renderReviewsPage(view) {
|
|
6845
6959
|
const now = view.now ?? Date.now();
|
|
@@ -6847,20 +6961,19 @@ function renderReviewsPage(view) {
|
|
|
6847
6961
|
at: group.latest.createdAt,
|
|
6848
6962
|
kind: "created"
|
|
6849
6963
|
};
|
|
6850
|
-
const ordered = [...view.groups].sort((a, b) =>
|
|
6964
|
+
const ordered = [...view.groups].sort((a, b) => activityOf(b).at - activityOf(a).at);
|
|
6851
6965
|
const sections = {
|
|
6966
|
+
pinned: [],
|
|
6852
6967
|
active: [],
|
|
6853
6968
|
waiting: [],
|
|
6854
6969
|
done: []
|
|
6855
6970
|
};
|
|
6856
|
-
for (const group of ordered) sections[
|
|
6971
|
+
for (const group of ordered) sections[indexSectionOf(group.latest.pinned === true, group.latest.lifecycle, activityOf(group), "all")].push(group);
|
|
6857
6972
|
const rank = new Map(ordered.map((group, index) => [group, index]));
|
|
6858
6973
|
const renderCards = (groups) => groups.map((group) => renderCard(group, now, activityOf(group), rank.get(group), view.analyses?.get(group.latest.id), positions.get(group.latest.id), group.history.map((review) => view.moved?.get(review.id)).find((kind) => kind !== void 0), prStateOf(group.latest, view.prStates))).join("");
|
|
6859
6974
|
const storedReviews = view.groups.reduce((total, group) => total + group.history.length, 0);
|
|
6860
6975
|
const queue = view.queue ?? [];
|
|
6861
6976
|
const positions = new Map(queue.map((id, position) => [id, position]));
|
|
6862
|
-
const continues = continueTarget(queue);
|
|
6863
|
-
const front = continues === null ? void 0 : findReview(view.groups, continues);
|
|
6864
6977
|
return `<!doctype html>
|
|
6865
6978
|
<html lang="en"${themeAttribute(view.theme)}>
|
|
6866
6979
|
<head>
|
|
@@ -6884,20 +6997,14 @@ ${renderWorkspaceRail({
|
|
|
6884
6997
|
unread: view.unreadNotifications ?? 0
|
|
6885
6998
|
})}
|
|
6886
6999
|
<main class="prb-index">
|
|
6887
|
-
${front === void 0 ? "" : renderContinue(front)}
|
|
6888
7000
|
${ordered.length === 0 ? "" : `<div class="prb-index-summary">
|
|
6889
7001
|
<p class="prb-index-count" data-count>${escapeHtml$1(countLabel(storedReviews, ordered.length))}</p>
|
|
6890
7002
|
<button type="button" class="prb-index-select" data-select-mode aria-pressed="false">Select</button>
|
|
6891
7003
|
</div>`}
|
|
6892
|
-
|
|
6893
|
-
${renderCards(sections.active)}
|
|
6894
|
-
</div>
|
|
6895
|
-
${renderLoadMore("active")}
|
|
7004
|
+
${INDEX_SECTIONS.map((section) => renderSection(section, sections[section], renderCards)).join("\n ")}
|
|
6896
7005
|
${ordered.length === 0 ? `<p class="prb-index-empty">No reviews stored yet. Ask Claude Code to review a change and one will appear here.</p>` : `<p class="prb-index-none" data-no-matches hidden>Nothing matches that.</p>`}
|
|
6897
|
-
${renderSection("waiting", "Waiting", sections.waiting, true, renderCards)}
|
|
6898
|
-
${renderSection("done", "Done", sections.done, false, renderCards)}
|
|
6899
7006
|
${ordered.length === 0 ? "" : renderBulkBar()}
|
|
6900
|
-
<p class="prb-index-note">Reviews are kept for thirty days, on this machine only.
|
|
7007
|
+
<p class="prb-index-note">Reviews are kept for thirty days, on this machine only. Pinned reviews, Done reviews and anything in your review queue are kept for ninety days instead, counting from the last time you opened or marked them. Every review has a fixed address, so a link to one is worth keeping and still opens after a restart. Stopping the server deletes nothing; it only stops anything being served until the next review starts it again.</p>
|
|
6901
7008
|
</main>
|
|
6902
7009
|
</div>
|
|
6903
7010
|
<script src="/assets/reviews.js" type="module"><\/script>
|
|
@@ -6921,13 +7028,11 @@ function renderControls(queued, authors, anyRequest) {
|
|
|
6921
7028
|
<select data-status-filter aria-label="Filter by status">
|
|
6922
7029
|
<option value="all">All</option>
|
|
6923
7030
|
<option value="queue" data-queue-option>${escapeHtml$1(queueOptionLabel(queued))}</option>
|
|
6924
|
-
<option value="saved">Saved</option>
|
|
6925
7031
|
<option value="in-progress">In progress</option>
|
|
6926
7032
|
<option value="complete">Reviewed</option>
|
|
6927
7033
|
</select>
|
|
6928
7034
|
</label>
|
|
6929
7035
|
${anyRequest ? renderPrFilter() : ""}
|
|
6930
|
-
<button type="button" class="prb-index-fetch-button" data-fetch-updates disabled>${escapeHtml$1(FETCH_UPDATES_LABEL)}</button>
|
|
6931
7036
|
</div>`;
|
|
6932
7037
|
}
|
|
6933
7038
|
/**
|
|
@@ -6959,25 +7064,6 @@ function renderAuthorFilter(authors) {
|
|
|
6959
7064
|
</select>`;
|
|
6960
7065
|
}
|
|
6961
7066
|
/**
|
|
6962
|
-
* The way back into the batch, above the list.
|
|
6963
|
-
*
|
|
6964
|
-
* A link rather than a button, and carrying the review's own token, because that is what every
|
|
6965
|
-
* other way into a review on this page is -- the list is the only thing holding every credential.
|
|
6966
|
-
*
|
|
6967
|
-
* It names the front of the queue rather than saying only "Continue reviewing", so somebody
|
|
6968
|
-
* returning after an hour can tell whether it is about to open what they expect before they click.
|
|
6969
|
-
*/
|
|
6970
|
-
function renderContinue(review) {
|
|
6971
|
-
return `<a class="prb-index-continue" href="${escapeHtml$1(`/r/${encodeURIComponent(review.id)}?t=${encodeURIComponent(review.token)}`)}">
|
|
6972
|
-
<span class="prb-index-continue-label">Continue reviewing</span>
|
|
6973
|
-
<span class="prb-index-continue-title">${escapeHtml$1(review.title)}</span>
|
|
6974
|
-
</a>`;
|
|
6975
|
-
}
|
|
6976
|
-
/** The review a queued id refers to. The caller has already guaranteed it is one this page shows. */
|
|
6977
|
-
function findReview(groups, id) {
|
|
6978
|
-
for (const group of groups) for (const review of group.history) if (review.id === id) return review;
|
|
6979
|
-
}
|
|
6980
|
-
/**
|
|
6981
7067
|
* One section's Load more, which the browser shows once that section holds more than it is showing.
|
|
6982
7068
|
*
|
|
6983
7069
|
* One per section rather than one pager for the page, so revealing older Done reviews never costs
|
|
@@ -6988,15 +7074,17 @@ function renderLoadMore(section) {
|
|
|
6988
7074
|
return `<button type="button" class="prb-index-more" data-load-more="${section}" hidden>Load more</button>`;
|
|
6989
7075
|
}
|
|
6990
7076
|
/**
|
|
6991
|
-
*
|
|
7077
|
+
* One section, as a disclosure headed with its name and count.
|
|
6992
7078
|
*
|
|
6993
|
-
*
|
|
6994
|
-
*
|
|
6995
|
-
*
|
|
6996
|
-
* browser moves here after a mark
|
|
7079
|
+
* Every section is headed, Active included. Active used to go without, because the count line
|
|
7080
|
+
* directly above it said what it held; with Pinned drawn first, the count line sits over Pinned and
|
|
7081
|
+
* would be heading the wrong list. Everything starts open but Done, which is the archive. Rendered
|
|
7082
|
+
* empty and hidden rather than left out, so a card the browser moves here after a mark or a pin has
|
|
7083
|
+
* somewhere to go.
|
|
6997
7084
|
*/
|
|
6998
|
-
function renderSection(section,
|
|
6999
|
-
|
|
7085
|
+
function renderSection(section, groups, renderCards) {
|
|
7086
|
+
const title = section === "pinned" ? "Pinned" : LIFECYCLES.find((lifecycle) => lifecycle.id === section).name;
|
|
7087
|
+
return `<details class="prb-index-section" data-section="${section}"${section !== "done" ? " open" : ""}${groups.length === 0 ? " hidden" : ""}>
|
|
7000
7088
|
<summary class="prb-index-section-head">${title} · <span data-section-count>${groups.length}</span></summary>
|
|
7001
7089
|
<div class="prb-cards" data-cards="${section}">
|
|
7002
7090
|
${renderCards(groups)}
|
|
@@ -7023,7 +7111,7 @@ function renderBulkBar() {
|
|
|
7023
7111
|
<p class="prb-bulk-count" data-bulk-count aria-live="polite"></p>
|
|
7024
7112
|
<div class="prb-bulk-actions" data-bulk-actions>
|
|
7025
7113
|
<button type="button" class="prb-bulk-button" data-bulk-select-all>${escapeHtml$1(selectAllLabel(0))}</button>
|
|
7026
|
-
<button type="button" class="prb-bulk-button" data-bulk-fetch
|
|
7114
|
+
<button type="button" class="prb-bulk-button" data-bulk-fetch hidden>${escapeHtml$1(FETCH_SELECTED_LABEL)}</button>
|
|
7027
7115
|
<button type="button" class="prb-bulk-button" data-bulk-queue disabled>Add to review queue</button>
|
|
7028
7116
|
<button type="button" class="prb-bulk-button" data-bulk-mark="complete">Mark reviewed</button>
|
|
7029
7117
|
<button type="button" class="prb-bulk-button" data-bulk-mark="in-progress">Mark in progress</button>
|
|
@@ -7064,12 +7152,12 @@ function renderBulkBar() {
|
|
|
7064
7152
|
function renderCard(group, now, activity, rank, analysis, queued, moved, prState) {
|
|
7065
7153
|
const review = group.latest;
|
|
7066
7154
|
const status = statusOf(review);
|
|
7067
|
-
const chip =
|
|
7155
|
+
const chip = cardLifecycleChip(review.pinned === true, review.lifecycle, activity);
|
|
7068
7156
|
const href = `/r/${encodeURIComponent(review.id)}?t=${encodeURIComponent(review.token)}`;
|
|
7069
7157
|
const followed = review.targetType === "commit" ? `commit ${(review.commitSha ?? review.branch).slice(0, 7)}` : followedRefName(review.branch, review.headResolvedRef) ?? review.branch;
|
|
7070
7158
|
return `<article class="prb-card" data-card data-id="${escapeHtml$1(review.id)}" data-status="${status}" data-pinned="${review.pinned}"${queued === void 0 ? "" : ` data-queue="${queued}"`}${prState === void 0 ? "" : ` data-pr-state="${prState}"`}${review.authorHandle ? ` data-author="${escapeHtml$1(review.authorHandle)}"` : ""} data-lifecycle="${review.lifecycle?.state ?? "active"}"${review.lifecycle ? ` data-lifecycle-at="${review.lifecycle.at}"` : ""} data-activity-at="${activity.at}" data-activity-kind="${activity.kind}" data-rank="${rank}" data-reviews="${group.history.length}" data-lineage="${escapeHtml$1(group.history.map((entry) => entry.id).join(" "))}" data-search-text="${escapeHtml$1(groupSearchText(group))}">
|
|
7071
7159
|
<input type="checkbox" class="prb-card-select" data-select="${escapeHtml$1(review.id)}" aria-label="Select ${escapeHtml$1(followed)}">
|
|
7072
|
-
<button type="button" class="prb-card-pin" data-pin="${escapeHtml$1(review.id)}" aria-pressed="${review.pinned}" aria-label="${review.pinned ? "
|
|
7160
|
+
<button type="button" class="prb-card-pin" data-pin="${escapeHtml$1(review.id)}" aria-pressed="${review.pinned}" aria-label="${review.pinned ? "Unpin" : "Pin"} this review">${review.pinned ? "★" : "☆"}</button>
|
|
7073
7161
|
${renderCardMenu(group, followed, queued !== void 0, status, activity)}
|
|
7074
7162
|
<a class="prb-card-link" href="${escapeHtml$1(href)}">
|
|
7075
7163
|
<div class="prb-card-head">
|
|
@@ -7132,7 +7220,8 @@ function renderCardMenu(group, followed, queued, status, activity) {
|
|
|
7132
7220
|
copy: "Copy review ID",
|
|
7133
7221
|
queue: queued ? "remove" : "add",
|
|
7134
7222
|
mark,
|
|
7135
|
-
lifecycle
|
|
7223
|
+
lifecycle,
|
|
7224
|
+
fetch: true
|
|
7136
7225
|
});
|
|
7137
7226
|
const all = `all ${count} reviews following ${followed}`;
|
|
7138
7227
|
return renderMenu({
|
|
@@ -7144,6 +7233,7 @@ function renderCardMenu(group, followed, queued, status, activity) {
|
|
|
7144
7233
|
queue: queued ? "remove" : "add",
|
|
7145
7234
|
mark,
|
|
7146
7235
|
lifecycle,
|
|
7236
|
+
fetch: true,
|
|
7147
7237
|
copy: "Copy latest review ID"
|
|
7148
7238
|
});
|
|
7149
7239
|
}
|
|
@@ -7162,6 +7252,7 @@ function renderCardMenu(group, followed, queued, status, activity) {
|
|
|
7162
7252
|
*/
|
|
7163
7253
|
function renderMenu(item) {
|
|
7164
7254
|
const id = escapeHtml$1(item.id);
|
|
7255
|
+
const fetchItem = item.fetch ? `<button type="button" class="prb-menu-item" data-fetch-review="${id}" hidden>${escapeHtml$1(FETCH_REVIEW_LABEL)}</button>` : "";
|
|
7165
7256
|
const markItem = item.mark === void 0 ? "" : `<button type="button" class="prb-menu-item" data-mark-status="${item.mark}" data-mark-id="${id}">${markItemLabel(item.mark)}</button>`;
|
|
7166
7257
|
const queueItem = item.queue === void 0 ? "" : `<button type="button" class="prb-menu-item" data-queue-action="${item.queue}" data-queue-id="${id}">${queueItemLabel(item.queue === "remove")}</button>`;
|
|
7167
7258
|
const lifecycleItems = item.lifecycle === void 0 ? "" : LIFECYCLES.map(({ id: target }) => `<button type="button" class="prb-menu-item" data-lifecycle-set="${target}" data-lifecycle-id="${id}"${offersLifecycle(target, item.lifecycle.stored, item.lifecycle.activity) ? "" : " hidden"}>${escapeHtml$1(lifecycleAction(target))}</button>`).join("\n ");
|
|
@@ -7169,6 +7260,7 @@ function renderMenu(item) {
|
|
|
7169
7260
|
<button type="button" class="prb-menu-trigger" data-menu-trigger aria-expanded="false" aria-haspopup="true" aria-label="${escapeHtml$1(item.label)}">⋯</button>
|
|
7170
7261
|
<div class="prb-menu-panel" data-menu-panel hidden>
|
|
7171
7262
|
<div data-menu-items>
|
|
7263
|
+
${fetchItem}
|
|
7172
7264
|
${markItem}
|
|
7173
7265
|
${lifecycleItems}
|
|
7174
7266
|
${queueItem}
|
|
@@ -7927,6 +8019,10 @@ async function fetchTargetIndex(deps = defaultFetchDeps()) {
|
|
|
7927
8019
|
}
|
|
7928
8020
|
return targets;
|
|
7929
8021
|
}
|
|
8022
|
+
/** Whether one review is a fetch target: the review page's question about itself. */
|
|
8023
|
+
async function isFetchTarget(reviewId, deps = defaultFetchDeps()) {
|
|
8024
|
+
return (await fetchTargets(deps, /* @__PURE__ */ new Set([reviewId]))).eligible.has(reviewId);
|
|
8025
|
+
}
|
|
7930
8026
|
var inFlight = /* @__PURE__ */ new Map();
|
|
7931
8027
|
var queue = Promise.resolve();
|
|
7932
8028
|
/**
|
|
@@ -9445,6 +9541,7 @@ async function handle(req, res, options) {
|
|
|
9445
9541
|
if (action === "/job") return send(res, 200, jobReport(workspace));
|
|
9446
9542
|
if (action === "/retry") return handleRetry(res, workspace);
|
|
9447
9543
|
if (action === "/freshness") return handleFreshness(res, workspace, url.searchParams.get("quiet") === "1");
|
|
9544
|
+
if (action === "/fetch") return handleFetchOwn(req, res, workspace);
|
|
9448
9545
|
if (action === "/refresh") return handleRefreshPr(res, workspace);
|
|
9449
9546
|
if (action === "/update") return handleUpdate(res, workspace);
|
|
9450
9547
|
if (action === "/review") return send(res, 200, workspace.session.result ?? {});
|
|
@@ -9519,10 +9616,10 @@ function handleIndex(req, res, params, indexToken) {
|
|
|
9519
9616
|
const cookie = indexCookieHeader(indexToken);
|
|
9520
9617
|
if (onward) return redirect(res, onward, cookie);
|
|
9521
9618
|
const states = analysisStates();
|
|
9522
|
-
const queue = readQueue();
|
|
9523
9619
|
const reviews = recentWorkspaces(Number.MAX_SAFE_INTEGER);
|
|
9524
|
-
const kept = indexKeptIds(reviews, queue);
|
|
9525
9620
|
const everyGroup = groupByLineage(reviews);
|
|
9621
|
+
const queue = settleQueue(everyGroup);
|
|
9622
|
+
const kept = indexKeptIds(reviews, queue);
|
|
9526
9623
|
const activity = reviewActivity(everyGroup);
|
|
9527
9624
|
const groups = indexReviewGroups(MAX_LISTED_BRANCHES, MAX_KEPT_BRANCHES, kept, (group) => activity.get(group.latest.id)?.at ?? group.latest.createdAt, everyGroup);
|
|
9528
9625
|
const rendered = new Set(groups.flatMap((group) => group.history.map((review) => review.id)));
|
|
@@ -9983,6 +10080,19 @@ async function handleFetchUpdates(req, res, indexToken) {
|
|
|
9983
10080
|
send(res, 200, await runFetchUpdates(new Set(ids)));
|
|
9984
10081
|
}
|
|
9985
10082
|
/**
|
|
10083
|
+
* Fetch remote updates, from one review's own page, on its own token.
|
|
10084
|
+
*
|
|
10085
|
+
* The list's fetch scoped to this review: `GET` says whether it applies (the page draws the item
|
|
10086
|
+
* only if so, asked after load so rendering never waits on git), `POST` runs it. The review page's
|
|
10087
|
+
* Check for updates stays read-only; this is the one explicit act on that page that reaches the
|
|
10088
|
+
* network, and it writes remote-tracking refs and nothing else.
|
|
10089
|
+
*/
|
|
10090
|
+
async function handleFetchOwn(req, res, workspace) {
|
|
10091
|
+
if (req.method === "GET") return send(res, 200, { fetchable: await isFetchTarget(workspace.id) });
|
|
10092
|
+
if (req.method !== "POST") return send(res, 405, { error: "Method not allowed." });
|
|
10093
|
+
send(res, 200, await runFetchUpdates(/* @__PURE__ */ new Set([workspace.id])));
|
|
10094
|
+
}
|
|
10095
|
+
/**
|
|
9986
10096
|
* Pin or unpin a review.
|
|
9987
10097
|
*
|
|
9988
10098
|
* Authorised by the index credential rather than the review's own, because pinning is a fact about
|
|
@@ -10296,7 +10406,7 @@ async function sendWorkspacePage(res, workspace) {
|
|
|
10296
10406
|
pendingDiagram: reportRun(workspace.id, "diagram"),
|
|
10297
10407
|
prompts: readPromptBank(),
|
|
10298
10408
|
complete: workspace.completedAt !== void 0,
|
|
10299
|
-
|
|
10409
|
+
pinned: workspace.pinned === true,
|
|
10300
10410
|
unreadNotifications: unreadCount$1(),
|
|
10301
10411
|
driving: isDriving(),
|
|
10302
10412
|
prContext: workspace.prContext ? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prreviewbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.2",
|
|
4
4
|
"description": "Review a branch or pull request in an isolated checkout, from your terminal, with the coding agent you already have installed.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://prreviewbuddy.com",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,t){e.addEventListener(`pageshow`,e=>{e.persisted&&t()})}function t(e){return`${e} more`}function n(e){return e===0?`Nothing unread`:`${e} unread`}function r(e){return e===`restarting`?`Workspace server restarting`:e===`unreachable`?`Workspace server not running`:`Workspace server running`}function i(e,t){let n=e.querySelector(`[data-server-status]`);if(!n)return;n.dataset.serverStatus=t;let i=n.querySelector(`[data-server-status-label]`);i&&(i.textContent=r(t))}function a(e,t){let n=e.querySelector(`[data-server-status]`);!n||n.dataset.serverStatus===`restarting`||i(e,t?`running`:`unreachable`)}var o=6e3,s=5e3;function c(e=document){let t=e.querySelector(`.prb-toasts`);if(t)return t;let n=e.createElement(`div`);return n.className=`prb-toasts`,n.setAttribute(`role`,`status`),n.setAttribute(`aria-live`,`polite`),e.body.append(n),n}function l(e,t,n=window){return u(e,{kind:t.kind,text:t.text},n)}function u(e,t,n){let r=e.ownerDocument,i=r.createElement(`div`);i.className=`prb-toast`,i.dataset.kind=t.kind;let a=r.createElement(`span`);a.className=`prb-toast-text`,a.textContent=t.text;let s=r.createElement(`a`);s.className=`prb-toast-open`,t.href!==void 0&&t.link!==void 0&&(s.href=t.href,s.textContent=t.link);let c=r.createElement(`button`);for(c.type=`button`,c.className=`prb-toast-close`,c.setAttribute(`aria-label`,`Dismiss: ${t.text}`),c.textContent=`×`,i.append(a,...t.href===void 0?[]:[s],c),e.append(i);e.children.length>3;)e.firstElementChild?.remove();let l=n.setTimeout(()=>i.remove(),o),u=()=>n.clearTimeout(l),d=()=>{n.clearTimeout(l),l=n.setTimeout(()=>i.remove(),o)};return i.addEventListener(`mouseenter`,u),i.addEventListener(`mouseleave`,d),i.addEventListener(`focusin`,u),i.addEventListener(`focusout`,d),c.addEventListener(`click`,()=>{n.clearTimeout(l),i.remove()}),i}function d(e,t,n=window){return u(e,{kind:t.tone,text:t.headline,href:`/notifications/open?id=${encodeURIComponent(t.id)}`,link:`Open`},n)}function f(e,n,r=window){return u(e,{kind:`more`,text:t(n),href:`/notifications`,link:`View all`},r)}function p(e=document,t=window){let n=Number(e.body.dataset.notificationsSince);if(!Number.isFinite(n)||n<=0)return;let r=c(e),i=async()=>{try{let i=await t.fetch(`/notifications/recent?after=${n}`);if(a(e,!0),i.status===403)return;if(i.ok){let e=await i.json();typeof e.now==`number`&&(n=e.now);for(let n of[...e.notifications??[]].reverse())d(r,n,t);typeof e.more==`number`&&e.more>0&&f(r,e.more,t)}}catch{a(e,!1)}t.setTimeout(()=>void i(),s)};t.setTimeout(()=>void i(),s)}function m(e,t){if(typeof t==`number`)for(let r of e.querySelectorAll(`[data-unread-count]`))r.dataset.unreadStyle===`rail`&&(r.textContent=t===0?``:n(t)),r.hidden=t===0}function h(e,t){m(e,t.unread),typeof t.driving==`boolean`&&g(e,t.driving)}function g(e,t){let n=e.querySelector(`[data-restart]`),r=e.querySelector(`[data-restart-note]`);n&&(n.disabled=t),r&&(r.hidden=!t)}var _=`prb-server-restarted`;function v(e=document,t=window){let n=null;try{n=t.sessionStorage.getItem(_),t.sessionStorage.removeItem(_)}catch{}n!==null&&l(c(e),{kind:`ready`,text:`Server restarted. Workspace is back online.`},t)}function y(e){v();let t=document.querySelector(`[data-restart]`),n=document.querySelector(`[data-restart-note]`);t&&t.addEventListener(`click`,()=>{t.disabled=!0,b(e,t,n)})}async function b(e,t,n){let r;try{let i=await fetch(e,{method:`POST`});if(i.status===409){g(document,!0);return}if(!i.ok)return void w(t,n,await C(i));let a=await i.json();r=typeof a.startedAt==`number`?a.startedAt:null}catch{w(t,n,`The workspace server could not be reached.`);return}if(document.body.dataset.restarting=`true`,i(document,`restarting`),await x(r)){try{window.sessionStorage.setItem(_,`1`)}catch{}window.location.reload();return}if(delete document.body.dataset.restarting,await S()){i(document,`running`),w(t,n,`The server did not restart, and is still the one that was running.`);return}i(document,`unreachable`),w(t,n,`Server restart failed. Run prreviewbuddy open to start the workspace again.`)}async function x(e){let t=Date.now()+2e4;for(;Date.now()<t;){await new Promise(e=>setTimeout(e,300));try{let t=await fetch(`/health`,{cache:`no-store`});if(!t.ok)continue;let n=await t.json();if(typeof n.startedAt==`number`&&n.startedAt!==e)return!0}catch{}}return!1}async function S(){try{return(await fetch(`/health`,{cache:`no-store`})).ok}catch{return!1}}async function C(e){try{return(await e.json()).error??`The server could not be restarted.`}catch{return`The server could not be restarted.`}}function w(e,t,n){e.disabled=!1,t&&(t.textContent=n,t.hidden=!1)}export{a,
|
|
1
|
+
function e(e,t){e.addEventListener(`pageshow`,e=>{e.persisted&&t()})}function t(e){return`${e} more`}function n(e){return e===0?`Nothing unread`:`${e} unread`}function r(e){return e===`restarting`?`Workspace server restarting`:e===`unreachable`?`Workspace server not running`:`Workspace server running`}function i(e,t){let n=e.querySelector(`[data-server-status]`);if(!n)return;n.dataset.serverStatus=t;let i=n.querySelector(`[data-server-status-label]`);i&&(i.textContent=r(t))}function a(e,t){let n=e.querySelector(`[data-server-status]`);!n||n.dataset.serverStatus===`restarting`||i(e,t?`running`:`unreachable`)}var o=6e3,s=5e3;function c(e=document){let t=e.querySelector(`.prb-toasts`);if(t)return t;let n=e.createElement(`div`);return n.className=`prb-toasts`,n.setAttribute(`role`,`status`),n.setAttribute(`aria-live`,`polite`),e.body.append(n),n}function l(e,t,n=window){return u(e,{kind:t.kind,text:t.text},n)}function u(e,t,n){let r=e.ownerDocument,i=r.createElement(`div`);i.className=`prb-toast`,i.dataset.kind=t.kind;let a=r.createElement(`span`);a.className=`prb-toast-text`,a.textContent=t.text;let s=r.createElement(`a`);s.className=`prb-toast-open`,t.href!==void 0&&t.link!==void 0&&(s.href=t.href,s.textContent=t.link);let c=r.createElement(`button`);for(c.type=`button`,c.className=`prb-toast-close`,c.setAttribute(`aria-label`,`Dismiss: ${t.text}`),c.textContent=`×`,i.append(a,...t.href===void 0?[]:[s],c),e.append(i);e.children.length>3;)e.firstElementChild?.remove();let l=n.setTimeout(()=>i.remove(),o),u=()=>n.clearTimeout(l),d=()=>{n.clearTimeout(l),l=n.setTimeout(()=>i.remove(),o)};return i.addEventListener(`mouseenter`,u),i.addEventListener(`mouseleave`,d),i.addEventListener(`focusin`,u),i.addEventListener(`focusout`,d),c.addEventListener(`click`,()=>{n.clearTimeout(l),i.remove()}),i}function d(e,t,n=window){return u(e,{kind:t.tone,text:t.headline,href:`/notifications/open?id=${encodeURIComponent(t.id)}`,link:`Open`},n)}function f(e,n,r=window){return u(e,{kind:`more`,text:t(n),href:`/notifications`,link:`View all`},r)}function p(e=document,t=window){let n=Number(e.body.dataset.notificationsSince);if(!Number.isFinite(n)||n<=0)return;let r=c(e),i=async()=>{try{let i=await t.fetch(`/notifications/recent?after=${n}`);if(a(e,!0),i.status===403)return;if(i.ok){let e=await i.json();typeof e.now==`number`&&(n=e.now);for(let n of[...e.notifications??[]].reverse())d(r,n,t);typeof e.more==`number`&&e.more>0&&f(r,e.more,t)}}catch{a(e,!1)}t.setTimeout(()=>void i(),s)};t.setTimeout(()=>void i(),s)}function m(e,t){if(typeof t==`number`)for(let r of e.querySelectorAll(`[data-unread-count]`))r.dataset.unreadStyle===`rail`&&(r.textContent=t===0?``:n(t)),r.hidden=t===0}function h(e,t){m(e,t.unread),typeof t.driving==`boolean`&&g(e,t.driving)}function g(e,t){let n=e.querySelector(`[data-restart]`),r=e.querySelector(`[data-restart-note]`);n&&(n.disabled=t),r&&(r.hidden=!t)}var _=`prb-server-restarted`;function v(e=document,t=window){let n=null;try{n=t.sessionStorage.getItem(_),t.sessionStorage.removeItem(_)}catch{}n!==null&&l(c(e),{kind:`ready`,text:`Server restarted. Workspace is back online.`},t)}function y(e){v();let t=document.querySelector(`[data-restart]`),n=document.querySelector(`[data-restart-note]`);t&&t.addEventListener(`click`,()=>{t.disabled=!0,b(e,t,n)})}async function b(e,t,n){let r;try{let i=await fetch(e,{method:`POST`});if(i.status===409){g(document,!0);return}if(!i.ok)return void w(t,n,await C(i));let a=await i.json();r=typeof a.startedAt==`number`?a.startedAt:null}catch{w(t,n,`The workspace server could not be reached.`);return}if(document.body.dataset.restarting=`true`,i(document,`restarting`),await x(r)){try{window.sessionStorage.setItem(_,`1`)}catch{}window.location.reload();return}if(delete document.body.dataset.restarting,await S()){i(document,`running`),w(t,n,`The server did not restart, and is still the one that was running.`);return}i(document,`unreachable`),w(t,n,`Server restart failed. Run prreviewbuddy open to start the workspace again.`)}async function x(e){let t=Date.now()+2e4;for(;Date.now()<t;){await new Promise(e=>setTimeout(e,300));try{let t=await fetch(`/health`,{cache:`no-store`});if(!t.ok)continue;let n=await t.json();if(typeof n.startedAt==`number`&&n.startedAt!==e)return!0}catch{}}return!1}async function S(){try{return(await fetch(`/health`,{cache:`no-store`})).ok}catch{return!1}}async function C(e){try{return(await e.json()).error??`The server could not be restarted.`}catch{return`The server could not be restarted.`}}function w(e,t,n){e.disabled=!1,t&&(t.textContent=n,t.hidden=!1)}export{p as a,i as c,l as i,n as l,h as n,c as o,m as r,a as s,y as t,e as u};
|