prreviewbuddy 0.83.3 → 0.85.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/dist/server.js +141 -12
- package/package.json +1 -1
- package/static/help.js +1 -1
- package/static/shell.css +185 -37
- package/static/sidebar.css +21 -23
- package/static/workspace.js +80 -80
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.85.0
|
|
4
|
+
|
|
5
|
+
**Replay the guided tour from any review.** A review's ⋯ menu now has Replay guided tour, which
|
|
6
|
+
runs the tour on the review you are reading. Replay in Help opens the review you opened last, or,
|
|
7
|
+
if that one is being analysed or updated, your newest review the tour can start on. It never picks
|
|
8
|
+
a review that is busy while another would do. When the tour cannot start, you are told why rather
|
|
9
|
+
than left on a page where nothing happens.
|
|
10
|
+
|
|
11
|
+
## 0.84.2
|
|
12
|
+
|
|
13
|
+
**Replay guided tour sits below its sentence** on Help's Welcome, rather than beside it.
|
|
14
|
+
|
|
15
|
+
## 0.84.1
|
|
16
|
+
|
|
17
|
+
**Replay guided tour is readable.** The button on Help's Welcome showed its label in the link
|
|
18
|
+
colour on its own purple, so the words could not be seen. They are white now.
|
|
19
|
+
|
|
20
|
+
## 0.84.0
|
|
21
|
+
|
|
22
|
+
**A guided tour of your first review.** The first time you open a completed review, a short tour
|
|
23
|
+
walks you through it: the story behind the change, where to focus, the suggested path, diagrams,
|
|
24
|
+
issues, questions, the files, Buddy AI, and the review status that records your judgement. Each
|
|
25
|
+
stop lights up the part of the page it describes. Skip it, or press Escape, and it does not come
|
|
26
|
+
back by itself. Replay it from Help's Welcome at any time. The tour changes nothing about your
|
|
27
|
+
review or your layout: whatever it opens to show you, it puts back.
|
|
28
|
+
|
|
29
|
+
## 0.83.4
|
|
30
|
+
|
|
31
|
+
**Help opens on Welcome, and its index stays beside you.** Help now starts with a short welcome:
|
|
32
|
+
why PR Review Buddy exists, and the three principles behind it. The list of topics moves from the
|
|
33
|
+
top of the page to a column beside them that stays in view as you scroll, and marks the topic you
|
|
34
|
+
are reading. In a narrow window it goes back above the topics.
|
|
35
|
+
|
|
36
|
+
**No underlined links.** Links across the workspace are told apart by colour, not underlines, at
|
|
37
|
+
rest or on hover.
|
|
38
|
+
|
|
3
39
|
## 0.83.3
|
|
4
40
|
|
|
5
41
|
**Peek at a collapsed sidebar.** Rest the pointer on the collapsed sidebar, or tab into it, and it
|
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as MIN_WORKSPACE_PORT, $i as resolveModel, $n as RUN_RECORD_RETENTION_MS, $r as deleteCollection, $t as markRead, A as batchIdentity, Ai as isGradeFor, An as escapeHtml$1, At as loadArchitecture, Bi as WITHHELD_REASON, Bn as readMarker, Br as LIFECYCLES, Bt as ancestorsOf, C as RUN_KINDS, Ci as ANALYSIS_ONLY, Cn as UNESTABLISHED_MOTIVATION, Cr as kissDecisionsOf, Ct as mapRuntimes, D as reportRun, Di as findingAssessment, Dn as renderMarkdown, Dr as loadWorkspace, Dt as formatDuration, E as endRun, Ei as currentGradeIn, En as GITHUB_MARKDOWN, Er as lineagePosition, Et as describeRuntimes, F as activateQueued, Fi as followUpFindingId, Fn as withReviewLock, Ft as storedScopes, G as serverEntryExists, Gi as isCommitReview, Gn as findExistingReview, Gr as lifecycleItemLabel, Gt as subjectOf, Hi as resolveTarget, Hn as requestJobStop, Hr as activityLabel, Ht as writeJsonAtomically, I as compactQueue, Ii as asString, In as MANAGED_ROOT, Ir as summarise, It as trackedFiles, Ji as shortNameOf, Jn as followedBranch, Jr as parseLifecycle, Jt as createPrState, K as spawnServer, Ki as resolveBase, Kn as checkCodeFreshness, Kr as lifecyclePill, Kt as categoryOf, Li as extractJsonText, Ln as createWorktree, Lr as touchWorkspace, Lt as ownersOf, M as parsePullRequestLink, Mi as reassessIn, Mr as previousKissResult, Mt as readArchitecture, N as unsupportedReason, Ni as sourcePhrase, Nn as ReviewBeingDeletedError, Nr as recentWorkspaces, Nt as repositoryKey, O as stopRun, Oi as findingIn, On as CODE_BLOCK_CLASS, Ot as ARCHITECTURE_DIR, P as runBatch, Pi as wasReassessed, Pn as isBusy, Pr as reviewedCommit, Pt as storeArchitecture, Q as MAX_WORKSPACE_PORT, Qi as resolveAgent, Qn as fillFileUrlTemplate, Qr as createWithMembers, Qt as markAllRead, R as settleQueue, Ri as objectWithAnyOf, Rn as holdWorktree, Rr as workspaceRevision, Rt as QUOTE_LIMITS, S as track, Si as withExplanation, Sn as chatConversationSection, Sr as kissDecisionRefusal, St as waitingHistory, T as completeRun, Ti as assessmentLookup, Tn as hasAuthorContent, Tr as lineageKey, Tt as RUNTIME_WORDS, U as readServerRecord, Ui as originRemote, Ur as isLifecycle, Ut as announceJob, V as clearServerRecordOwnedBy, Vi as kissOutcome, Vn as removeWorktree, Vr as activityAttributes, Vt as isArchitectureId, Wi as DetachedHeadError, Wr as lifecycleChip, Wt as branchOf, X as writeServerRecord, Xi as git, Xn as fetchHead, Xr as collectionsStamp, Xt as eventName, Yi as shorthandOf, Yn as reviewedShaOf, Yr as sectionOf, Yt as createPrUpdate, Z as DEFAULT_WORKSPACE_PORT, Zi as refExists, Zn as forgeResolver, Zr as createCollection, Zt as listNotifications, _i as processDiscussion, _n as unforeseenFailure, _r as decideKissRecommendation, _t as telemetryUploadConsent, a as wasBlocked, aa as STORE_ROOT, ai as queueAdd, an as removeNotification, ar as loadJob, at as observeReview, b as startKissJob, bi as explanationOf, bn as agentEnvOf, br as indexKeptIds, c as RefreshUnavailableError, ca as agentFor$1, ci as queueStamp, cn as unreadAround, ct as reanalyseReview, d as mergeUpdateInto, da as isAgentId, di as isIssueOutstanding, dn as existingPaths, dr as phasesFor, ea as agentIdOf, ei as nameProblemText, en as markReadForReview, er as allJobs, et as parseWorkspacePort, fa as parkedReason, fi as isQuestionOutstanding, fn as ASK_REREAD_CEILING_MS, fr as progressSteps, ft as readEvents, g as liveUpdateFor, gi as reviewerDispositions, gn as storedConversation, gt as recordTelemetryUploadConsent, h as UpdateAlreadyRunningError, ha as AgentUnavailableError, hi as resolvedByConversation, hn as saveRefresh, hr as isClaimed, i as liveJobsFor, ii as updateCollections, in as readStateOf, ir as isTerminal, it as failureOf$1, j as extractPullRequestLinks, ji as promptGrade, jn as renderInlineText, jr as presentedKissRunOf, jt as partFiles, k as relativeTime$1, ki as gradeScale, kn as CODE_COPY_CLASS, kr as mutateWorkspace, kt as checkArchitecture, l as cancelUpdate, la as agentIdentityOf, li as readQueue, ln as unreadCount$1, lr as isLiveJob, lt as runJob, m as askCheckout, ma as AgentCancelledError, mi as questionsOutstanding, mn as prepareConversationWithin, n as deleteReview, na as readAgentPreference, ni as renameCollection, nn as pruneNotifications, nr as endedAt, nt as statedWorkspacePort, o as discardJob, oa as AGENT_IDS, oi as queueAddMany, on as retireNotification, or as pruneJobResidue, ot as prepareReview, p as updateReview, pa as runnableAgents, pi as issuesOutstanding, pn as adoptLegacyBaseline, pr as purposeOf, pt as record, q as stopChild, qi as AmbiguousRefError, qn as checkReview, qr as offersLifecycle, qt as createArchitectureNotification, r as lineageIds, ra as writeAgentPreference, ri as setMembership, rn as readNotification, rr as fail$1, rt as writePortPreference, s as FETCH_TIMEOUT_MS, sa as agentById, si as queueRemove, sn as toneOf, sr as saveJob, st as settleReview, t as deleteLineage, ta as clearAgentPreference, ti as readCollections, tn as moveIds, tr as attemptIdOf, tt as resolveWorkspacePort, u as cancellableHere, ua as detectAgents, ui as doneVerb, un as withUsageRecorded, ur as isTerminalJobPhase, v as kissAgentId, vi as recommendationsOf, vn as refreshPrContext, vr as followedRefName, w as beginRun, wi as analysisRefOf, wn as authorPreview, wr as latestKissRun, wt as recordRuntimeBestEffort, x as isDriving, xi as isExplanationAction, xn as EXPLAIN_SIMPLY_PROMPT, xr as indexReviewGroups, xt as feedbackUrl, yi as explanationActionOf, yn as isUnchanged, yr as groupByLineage, yt as BUILD_VERSION, z as readIndexToken, zi as describeAuthorship, zn as isWorktreeHeld, zr as workspaceStamps, zt as REPOSITORY_SCOPE } from "./delete_review-BED4mPxB.js";
|
|
1
|
+
import { $ as MIN_WORKSPACE_PORT, $i as resolveModel, $n as RUN_RECORD_RETENTION_MS, $r as deleteCollection, $t as markRead, A as batchIdentity, Ai as isGradeFor, An as escapeHtml$1, At as loadArchitecture, Bi as WITHHELD_REASON, Bn as readMarker, Br as LIFECYCLES, Bt as ancestorsOf, C as RUN_KINDS, Ci as ANALYSIS_ONLY, Cn as UNESTABLISHED_MOTIVATION, Cr as kissDecisionsOf, Ct as mapRuntimes, D as reportRun, Di as findingAssessment, Dn as renderMarkdown, Dr as loadWorkspace, Dt as formatDuration, E as endRun, Ei as currentGradeIn, En as GITHUB_MARKDOWN, Er as lineagePosition, Et as describeRuntimes, F as activateQueued, Fi as followUpFindingId, Fn as withReviewLock, Ft as storedScopes, G as serverEntryExists, Gi as isCommitReview, Gn as findExistingReview, Gr as lifecycleItemLabel, Gt as subjectOf, Hi as resolveTarget, Hn as requestJobStop, Hr as activityLabel, Ht as writeJsonAtomically, I as compactQueue, Ii as asString, In as MANAGED_ROOT, Ir as summarise, It as trackedFiles, Ji as shortNameOf, Jn as followedBranch, Jr as parseLifecycle, Jt as createPrState, K as spawnServer, Ki as resolveBase, Kn as checkCodeFreshness, Kr as lifecyclePill, Kt as categoryOf, Li as extractJsonText, Ln as createWorktree, Lr as touchWorkspace, Lt as ownersOf, M as parsePullRequestLink, Mi as reassessIn, Mr as previousKissResult, Mt as readArchitecture, N as unsupportedReason, Ni as sourcePhrase, Nn as ReviewBeingDeletedError, Nr as recentWorkspaces, Nt as repositoryKey, O as stopRun, Oi as findingIn, On as CODE_BLOCK_CLASS, Ot as ARCHITECTURE_DIR, P as runBatch, Pi as wasReassessed, Pn as isBusy, Pr as reviewedCommit, Pt as storeArchitecture, Q as MAX_WORKSPACE_PORT, Qi as resolveAgent, Qn as fillFileUrlTemplate, Qr as createWithMembers, Qt as markAllRead, R as settleQueue, Ri as objectWithAnyOf, Rn as holdWorktree, Rr as workspaceRevision, Rt as QUOTE_LIMITS, S as track, Si as withExplanation, Sn as chatConversationSection, Sr as kissDecisionRefusal, St as waitingHistory, T as completeRun, Ti as assessmentLookup, Tn as hasAuthorContent, Tr as lineageKey, Tt as RUNTIME_WORDS, U as readServerRecord, Ui as originRemote, Ur as isLifecycle, Ut as announceJob, V as clearServerRecordOwnedBy, Vi as kissOutcome, Vn as removeWorktree, Vr as activityAttributes, Vt as isArchitectureId, Wi as DetachedHeadError, Wr as lifecycleChip, Wt as branchOf, X as writeServerRecord, Xi as git, Xn as fetchHead, Xr as collectionsStamp, Xt as eventName, Yi as shorthandOf, Yn as reviewedShaOf, Yr as sectionOf, Yt as createPrUpdate, Z as DEFAULT_WORKSPACE_PORT, Zi as refExists, Zn as forgeResolver, Zr as createCollection, Zt as listNotifications, _ as runningJobs, _i as processDiscussion, _n as unforeseenFailure, _r as decideKissRecommendation, _t as telemetryUploadConsent, a as wasBlocked, aa as STORE_ROOT, ai as queueAdd, an as removeNotification, ar as loadJob, at as observeReview, b as startKissJob, bi as explanationOf, bn as agentEnvOf, br as indexKeptIds, c as RefreshUnavailableError, ca as agentFor$1, ci as queueStamp, cn as unreadAround, ct as reanalyseReview, d as mergeUpdateInto, da as isAgentId, di as isIssueOutstanding, dn as existingPaths, dr as phasesFor, ea as agentIdOf, ei as nameProblemText, en as markReadForReview, er as allJobs, et as parseWorkspacePort, fa as parkedReason, fi as isQuestionOutstanding, fn as ASK_REREAD_CEILING_MS, fr as progressSteps, ft as readEvents, g as liveUpdateFor, gi as reviewerDispositions, gn as storedConversation, gt as recordTelemetryUploadConsent, h as UpdateAlreadyRunningError, ha as AgentUnavailableError, hi as resolvedByConversation, hn as saveRefresh, hr as isClaimed, i as liveJobsFor, ii as updateCollections, in as readStateOf, ir as isTerminal, it as failureOf$1, j as extractPullRequestLinks, ji as promptGrade, jn as renderInlineText, jr as presentedKissRunOf, jt as partFiles, k as relativeTime$1, ki as gradeScale, kn as CODE_COPY_CLASS, kr as mutateWorkspace, kt as checkArchitecture, l as cancelUpdate, la as agentIdentityOf, li as readQueue, ln as unreadCount$1, lr as isLiveJob, lt as runJob, m as askCheckout, ma as AgentCancelledError, mi as questionsOutstanding, mn as prepareConversationWithin, n as deleteReview, na as readAgentPreference, ni as renameCollection, nn as pruneNotifications, nr as endedAt, nt as statedWorkspacePort, o as discardJob, oa as AGENT_IDS, oi as queueAddMany, on as retireNotification, or as pruneJobResidue, ot as prepareReview, p as updateReview, pa as runnableAgents, pi as issuesOutstanding, pn as adoptLegacyBaseline, pr as purposeOf, pt as record, q as stopChild, qi as AmbiguousRefError, qn as checkReview, qr as offersLifecycle, qt as createArchitectureNotification, r as lineageIds, ra as writeAgentPreference, ri as setMembership, rn as readNotification, rr as fail$1, rt as writePortPreference, s as FETCH_TIMEOUT_MS, sa as agentById, si as queueRemove, sn as toneOf, sr as saveJob, st as settleReview, t as deleteLineage, ta as clearAgentPreference, ti as readCollections, tn as moveIds, tr as attemptIdOf, tt as resolveWorkspacePort, u as cancellableHere, ua as detectAgents, ui as doneVerb, un as withUsageRecorded, ur as isTerminalJobPhase, v as kissAgentId, vi as recommendationsOf, vn as refreshPrContext, vr as followedRefName, w as beginRun, wi as analysisRefOf, wn as authorPreview, wr as latestKissRun, wt as recordRuntimeBestEffort, x as isDriving, xi as isExplanationAction, xn as EXPLAIN_SIMPLY_PROMPT, xr as indexReviewGroups, xt as feedbackUrl, yi as explanationActionOf, yn as isUnchanged, yr as groupByLineage, yt as BUILD_VERSION, z as readIndexToken, zi as describeAuthorship, zn as isWorktreeHeld, zr as workspaceStamps, zt as REPOSITORY_SCOPE } from "./delete_review-BED4mPxB.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";
|
|
@@ -2562,11 +2562,11 @@ function renderOverviewTab(session, iconUrl = "", options) {
|
|
|
2562
2562
|
return `
|
|
2563
2563
|
${options?.authorContext ? renderAuthorContext(session, options.descriptionLink) : renderDescriptionLink(session, options?.descriptionLink)}
|
|
2564
2564
|
${renderWhyThisChange(result.motivation, session, result.questions, options)}
|
|
2565
|
-
<section class="pr-helper-whats-changing" aria-labelledby="pr-helper-whats-changing-heading">
|
|
2565
|
+
<section class="pr-helper-whats-changing" data-tour="whats-changing" aria-labelledby="pr-helper-whats-changing-heading">
|
|
2566
2566
|
<h4 class="pr-helper-overview-heading" id="pr-helper-whats-changing-heading">What's changing</h4>
|
|
2567
2567
|
<div class="pr-helper-review-summary pr-helper-markdown">${renderMarkdown(result.summary)}</div>
|
|
2568
2568
|
</section>
|
|
2569
|
-
<div class="pr-helper-risk-headline">
|
|
2569
|
+
<div class="pr-helper-risk-headline" data-tour="risk">
|
|
2570
2570
|
<div class="pr-helper-risk-headline-top">
|
|
2571
2571
|
<span class="pr-helper-risk-dot pr-helper-risk-dot-${result.overallRisk}"></span>
|
|
2572
2572
|
<span class="pr-helper-risk-headline-label">${escapeHtml$1(titleCase(result.overallRisk))} Risk</span>
|
|
@@ -2575,7 +2575,7 @@ function renderOverviewTab(session, iconUrl = "", options) {
|
|
|
2575
2575
|
${renderProvisionalReason(session)}
|
|
2576
2576
|
${result.recommendation ? `<div class="pr-helper-risk-headline-takeaway pr-helper-markdown">${renderMarkdown(result.recommendation)}</div>` : ""}
|
|
2577
2577
|
</div>
|
|
2578
|
-
<div class="pr-helper-overview-stats">
|
|
2578
|
+
<div class="pr-helper-overview-stats" data-tour="stats">
|
|
2579
2579
|
${renderOverviewStat(metrics.sections, "Themes", "guide")}
|
|
2580
2580
|
${renderIssuesStat(issuesOutstanding(result.issues, reviewerDispositions(session)))}
|
|
2581
2581
|
${renderRemainingStat(questionsOutstanding(result.questions, new Set(session.answeredQuestionIds ?? [])), "Questions", "questions", "answered")}
|
|
@@ -2698,7 +2698,7 @@ function renderSuggestedPath(sections, heading) {
|
|
|
2698
2698
|
if (sections.length === 0) return "";
|
|
2699
2699
|
const items = sections.map((s) => `<li>${escapeHtml$1(s.title)}</li>`).join("");
|
|
2700
2700
|
return `
|
|
2701
|
-
<div class="pr-helper-suggested-path">
|
|
2701
|
+
<div class="pr-helper-suggested-path" data-tour="suggested-path">
|
|
2702
2702
|
<h4 class="pr-helper-suggested-path-title">${escapeHtml$1(heading)}</h4>
|
|
2703
2703
|
<ol class="pr-helper-suggested-path-list">${items}</ol>
|
|
2704
2704
|
<p class="pr-helper-suggested-path-time">Estimated review time ≈${estimateTotalMinutes(sections)} minutes</p>
|
|
@@ -3555,6 +3555,42 @@ function renderDrawnFrom(diagram, reviewedSha) {
|
|
|
3555
3555
|
return `<p class="prb-diagram-stale">Drawn from <code>${escapeHtml$1(diagram.commitSha.slice(0, 7))}</code>, before this review was brought up to date. Regenerate to draw it from the commit the review now describes.</p>`;
|
|
3556
3556
|
}
|
|
3557
3557
|
//#endregion
|
|
3558
|
+
//#region ../../packages/review-harness/src/workspace/guided_tour.ts
|
|
3559
|
+
function isGuidedTourState(value) {
|
|
3560
|
+
return value === "completed" || value === "dismissed";
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3563
|
+
* Whether a review page opens the tour by itself: a live page (one with a server behind it) over a
|
|
3564
|
+
* standard result that no update is replacing, for a reviewer who has not already finished or
|
|
3565
|
+
* skipped it. Running reviews never qualify, so the tour cannot interrupt one.
|
|
3566
|
+
*/
|
|
3567
|
+
function offersGuidedTour(page) {
|
|
3568
|
+
return page.live && page.hasResult && !page.updating && page.state === void 0;
|
|
3569
|
+
}
|
|
3570
|
+
/** The hash Replay opens a review at. Not a place the page knows, so the page drops it once read. */
|
|
3571
|
+
var TOUR_REPLAY_HASH = "#tour";
|
|
3572
|
+
/**
|
|
3573
|
+
* Whether the tour can be shown on a review: its analysis finished, and nothing is analysing or
|
|
3574
|
+
* updating it now. Whether the reviewer has marked it Reviewed has nothing to do with it.
|
|
3575
|
+
*/
|
|
3576
|
+
function tourEligible(review, busy) {
|
|
3577
|
+
return review.hasStandardReview && !busy.has(review.id);
|
|
3578
|
+
}
|
|
3579
|
+
/**
|
|
3580
|
+
* The review Replay from Help opens: the one last opened, when the tour can be shown on it, and
|
|
3581
|
+
* otherwise the newest it can be shown on. Never a busy review, so none is chosen only when no
|
|
3582
|
+
* review qualifies. Replay from a review's own menu does not ask: it uses the review on screen.
|
|
3583
|
+
*/
|
|
3584
|
+
function chooseTourReview(reviews, busy) {
|
|
3585
|
+
const last = reviews.filter((review) => review.lastViewedAt !== void 0).reduce((best, review) => !best || review.lastViewedAt > best.lastViewedAt ? review : best, null);
|
|
3586
|
+
if (last && tourEligible(last, busy)) return last;
|
|
3587
|
+
return reviews.filter((review) => tourEligible(review, busy)).reduce((best, review) => !best || review.createdAt > best.createdAt ? review : best, null);
|
|
3588
|
+
}
|
|
3589
|
+
function tourUnavailable(reviews, busy) {
|
|
3590
|
+
if (chooseTourReview(reviews, busy)) return null;
|
|
3591
|
+
return reviews.some((review) => review.hasStandardReview) ? "busy" : "none";
|
|
3592
|
+
}
|
|
3593
|
+
//#endregion
|
|
3558
3594
|
//#region ../../packages/review-harness/src/domain/change_map/types.ts
|
|
3559
3595
|
var MAP_ROLES = [
|
|
3560
3596
|
"intent",
|
|
@@ -6151,7 +6187,12 @@ function renderWorkspace(view) {
|
|
|
6151
6187
|
<link rel="stylesheet" href="/assets/sidebar.css">
|
|
6152
6188
|
<link rel="stylesheet" href="/assets/shell.css">
|
|
6153
6189
|
</head>
|
|
6154
|
-
<body data-api-base="${escapeHtml$1(view.apiBase)}"${view.token ? ` data-token="${escapeHtml$1(view.token)}" data-notifications-since="${Date.now()}"` : ""}${view.editor ? ` data-editor-label="${escapeHtml$1(view.editor.label)}" data-editor-template="${escapeHtml$1(view.editor.template)}"` : ""}${view.repoPath ? ` data-repo-path="${escapeHtml$1(view.repoPath)}"` : ""}${view.revision === void 0 ? "" : ` data-workspace-revision="${escapeHtml$1(String(view.revision))}"`} data-session-id="${escapeHtml$1(view.session.id)}" data-explain-prompt="${escapeHtml$1(EXPLAIN_SIMPLY_PROMPT)}" data-way-back="${escapeHtml$1(wayBackSentence())}" data-default-view="${session.result ? "overview" : showsProgress(view) ? "progress" : "kiss"}"
|
|
6190
|
+
<body data-api-base="${escapeHtml$1(view.apiBase)}"${view.token ? ` data-token="${escapeHtml$1(view.token)}" data-notifications-since="${Date.now()}"` : ""}${view.editor ? ` data-editor-label="${escapeHtml$1(view.editor.label)}" data-editor-template="${escapeHtml$1(view.editor.template)}"` : ""}${view.repoPath ? ` data-repo-path="${escapeHtml$1(view.repoPath)}"` : ""}${view.revision === void 0 ? "" : ` data-workspace-revision="${escapeHtml$1(String(view.revision))}"`} data-session-id="${escapeHtml$1(view.session.id)}" data-explain-prompt="${escapeHtml$1(EXPLAIN_SIMPLY_PROMPT)}" data-way-back="${escapeHtml$1(wayBackSentence())}" data-default-view="${session.result ? "overview" : showsProgress(view) ? "progress" : "kiss"}"${offersGuidedTour({
|
|
6191
|
+
live: view.token !== void 0,
|
|
6192
|
+
hasResult: Boolean(session.result),
|
|
6193
|
+
updating: updating(view),
|
|
6194
|
+
state: view.guidedTour
|
|
6195
|
+
}) ? " data-guided-tour=\"offer\"" : ""}${view.tourBusy ? " data-tour-busy" : ""}>
|
|
6155
6196
|
${renderGlobalBar({
|
|
6156
6197
|
unread: view.unreadNotifications ?? 0,
|
|
6157
6198
|
live: Boolean(view.token),
|
|
@@ -6689,6 +6730,7 @@ function renderIdMenu(view, freshness) {
|
|
|
6689
6730
|
branchItems ? `<button type="button" class="prb-menu-item" data-queue-action="${view.queued ? "remove" : "add"}">${queueItemLabel(view.queued === true)}</button>` : "",
|
|
6690
6731
|
canWrite ? `<button type="button" class="prb-menu-item" data-pin-toggle aria-pressed="${view.pinned === true}">${pinItemLabel(view.pinned === true)}</button>` : "",
|
|
6691
6732
|
`<button type="button" class="prb-menu-item" data-copy-id="${safe}">Copy review ID</button>`,
|
|
6733
|
+
canWrite && view.session.result ? "<button type=\"button\" class=\"prb-menu-item\" data-tour-replay>Replay guided tour</button>" : "",
|
|
6692
6734
|
canWrite ? `<button type="button" class="prb-menu-item prb-menu-item-danger" data-delete="${safe}">Delete review</button>` : ""
|
|
6693
6735
|
].filter(Boolean);
|
|
6694
6736
|
return `<div class="prb-menu" data-menu data-review-menu${[
|
|
@@ -10673,7 +10715,7 @@ function readPreferences() {
|
|
|
10673
10715
|
try {
|
|
10674
10716
|
const parsed = JSON.parse(readFileSync(PREFERENCES_PATH, "utf8"));
|
|
10675
10717
|
if (!parsed || typeof parsed !== "object") return {};
|
|
10676
|
-
const { askWidth, dismissedNotices, promptsCollapsed, changeMapRegister, theme, reviewSections, runsSections, reviewLayout, sidebar } = parsed;
|
|
10718
|
+
const { askWidth, dismissedNotices, promptsCollapsed, changeMapRegister, theme, reviewSections, runsSections, reviewLayout, sidebar, guidedTour } = parsed;
|
|
10677
10719
|
return {
|
|
10678
10720
|
...typeof askWidth === "number" && Number.isFinite(askWidth) ? { askWidth: clampAskWidth(askWidth) } : {},
|
|
10679
10721
|
...Array.isArray(dismissedNotices) ? { dismissedNotices: dismissedNotices.filter((key) => typeof key === "string" && NOTICE_KEY.test(key)) } : {},
|
|
@@ -10683,7 +10725,8 @@ function readPreferences() {
|
|
|
10683
10725
|
...readReviewSections(reviewSections),
|
|
10684
10726
|
...readRunsSections(runsSections),
|
|
10685
10727
|
...readLayout(reviewLayout),
|
|
10686
|
-
...readSidebar(sidebar)
|
|
10728
|
+
...readSidebar(sidebar),
|
|
10729
|
+
...isGuidedTourState(guidedTour) ? { guidedTour } : {}
|
|
10687
10730
|
};
|
|
10688
10731
|
} catch {
|
|
10689
10732
|
return {};
|
|
@@ -10745,6 +10788,21 @@ function writeSidebar(patch) {
|
|
|
10745
10788
|
}, null, 2)}\n`);
|
|
10746
10789
|
} catch {}
|
|
10747
10790
|
}
|
|
10791
|
+
/**
|
|
10792
|
+
* Remember how the guided tour was first answered. The first answer stands: a replay from Help, or
|
|
10793
|
+
* a second window finishing a tour another already skipped, is not a new answer to the offer.
|
|
10794
|
+
*/
|
|
10795
|
+
function writeGuidedTour(state) {
|
|
10796
|
+
try {
|
|
10797
|
+
const current = readPreferences();
|
|
10798
|
+
if (current.guidedTour) return;
|
|
10799
|
+
mkdirSync(STORE_ROOT, { recursive: true });
|
|
10800
|
+
writeFileSync(PREFERENCES_PATH, `${JSON.stringify({
|
|
10801
|
+
...current,
|
|
10802
|
+
guidedTour: state
|
|
10803
|
+
}, null, 2)}\n`);
|
|
10804
|
+
} catch {}
|
|
10805
|
+
}
|
|
10748
10806
|
/** Remember whether "Your prompts" is folded. Read-modify-write, like every other field here. */
|
|
10749
10807
|
function writePromptsCollapsed(collapsed) {
|
|
10750
10808
|
try {
|
|
@@ -11474,6 +11532,30 @@ var AGENT_PRODUCTS = ((names) => names.length <= 1 ? names[0] ?? "" : `${names.s
|
|
|
11474
11532
|
* says what to type and points here for what it means. The diagrams are text on purpose.
|
|
11475
11533
|
*/
|
|
11476
11534
|
var TOPICS = [
|
|
11535
|
+
{
|
|
11536
|
+
id: "welcome",
|
|
11537
|
+
title: "Welcome",
|
|
11538
|
+
heading: `<p class="prb-help-eyebrow">Welcome to PR Review Buddy</p>
|
|
11539
|
+
<h2 class="prb-help-headline">Understand before you approve.</h2>`,
|
|
11540
|
+
body: `<p>Code review isn't just about finding bugs. It's about understanding what changed, why it
|
|
11541
|
+
changed, and whether those changes make sense.</p>
|
|
11542
|
+
<p>AI can generate code faster than ever. But someone still needs to understand it, challenge its
|
|
11543
|
+
assumptions, and take responsibility for approving it.</p>
|
|
11544
|
+
<p>That's why we built PR Review Buddy.</p>
|
|
11545
|
+
<p>PR Review Buddy helps you make sense of changes before you approve them. It brings together
|
|
11546
|
+
code, context, conversations and AI-assisted analysis so you can focus on what matters: making
|
|
11547
|
+
informed engineering decisions.</p>
|
|
11548
|
+
<p>The goal isn't to replace the reviewer. It's to make the reviewer better informed.</p>
|
|
11549
|
+
<h3 class="prb-help-eyebrow">Our principles</h3>
|
|
11550
|
+
<dl class="prb-help-principles">
|
|
11551
|
+
<div><dt>Understanding over automation</dt>
|
|
11552
|
+
<dd>AI should help you reason about changes, not make decisions on your behalf.</dd></div>
|
|
11553
|
+
<div><dt>Evidence over assertions</dt>
|
|
11554
|
+
<dd>Findings should be grounded in the code and context, so you can investigate and challenge them.</dd></div>
|
|
11555
|
+
<div><dt>You remain in control</dt>
|
|
11556
|
+
<dd>Choose your AI agent, explore the analysis, and make the final judgement yourself.</dd></div>
|
|
11557
|
+
</dl>`
|
|
11558
|
+
},
|
|
11477
11559
|
{
|
|
11478
11560
|
id: "getting-started",
|
|
11479
11561
|
title: "Getting started",
|
|
@@ -11675,7 +11757,27 @@ feature: E</pre>
|
|
|
11675
11757
|
<div data-shortcut-table></div>`
|
|
11676
11758
|
}
|
|
11677
11759
|
];
|
|
11678
|
-
/**
|
|
11760
|
+
/**
|
|
11761
|
+
* Every topic by name beside the topics, so one in the middle is found by reading a list, not a
|
|
11762
|
+
* page. First in the document so a keyboard or a screen reader reaches it before the prose; the
|
|
11763
|
+
* stylesheet places it. `help_index.ts` marks the topic being read.
|
|
11764
|
+
*/
|
|
11765
|
+
/**
|
|
11766
|
+
* Where the tour lives once it has been seen. `/tour` picks the review when Replay is pressed (see
|
|
11767
|
+
* `chooseTourReview`); with none to pick, Help says why rather than offering a button that would
|
|
11768
|
+
* open a review the tour cannot start on.
|
|
11769
|
+
*/
|
|
11770
|
+
function renderTourAction(tour, failed) {
|
|
11771
|
+
const lead = failed ? "<strong>The guided tour couldn't start.</strong> " : "";
|
|
11772
|
+
const role = failed ? " role=\"status\"" : "";
|
|
11773
|
+
if (tour === "available") return `<p class="prb-help-tour">The guided tour walks through a completed review, one part at a time,
|
|
11774
|
+
starting with the one you opened last. A review's ⋯ menu replays it on that review.</p>
|
|
11775
|
+
<p><a class="prb-help-tour-action" href="/tour">Replay guided tour</a></p>`;
|
|
11776
|
+
if (tour === "busy") return `<p class="prb-help-tour"${role}>${lead}The guided tour walks through a completed review, and every
|
|
11777
|
+
one you have is being analysed or updated right now. Replay it from here once that finishes.</p>`;
|
|
11778
|
+
return `<p class="prb-help-tour"${role}>${lead}The guided tour walks through a completed review, and there
|
|
11779
|
+
isn't one yet. Start a review from <a href="/">Reviews</a>, then replay the tour from here once it has finished.</p>`;
|
|
11780
|
+
}
|
|
11679
11781
|
function renderContents() {
|
|
11680
11782
|
return `<nav class="prb-help-contents" aria-label="Help topics">
|
|
11681
11783
|
<ul>${TOPICS.map((topic) => `<li><a href="#${topic.id}">${escapeHtml$1(topic.title)}</a></li>`).join("")}</ul>
|
|
@@ -11706,13 +11808,16 @@ ${renderWorkspaceRail({
|
|
|
11706
11808
|
sidebar: view.sidebar
|
|
11707
11809
|
})}
|
|
11708
11810
|
<main class="prb-index prb-help">
|
|
11709
|
-
<div class="prb-
|
|
11811
|
+
<div class="prb-help-layout">
|
|
11710
11812
|
${renderContents()}
|
|
11813
|
+
<div class="prb-page-panel">
|
|
11711
11814
|
${TOPICS.map((topic) => `<section class="prb-help-topic" id="${topic.id}">
|
|
11712
|
-
|
|
11815
|
+
${topic.heading ?? `<h2>${escapeHtml$1(topic.title)}</h2>`}
|
|
11713
11816
|
${topic.body}
|
|
11817
|
+
${topic.id === "welcome" ? renderTourAction(view.tour, view.tourFailed === true) : ""}
|
|
11714
11818
|
</section>`).join("")}
|
|
11715
11819
|
</div>
|
|
11820
|
+
</div>
|
|
11716
11821
|
<p class="prb-help-version">PR Review Buddy v${escapeHtml$1(BUILD_VERSION)}</p>
|
|
11717
11822
|
</main>
|
|
11718
11823
|
</div>
|
|
@@ -15081,6 +15186,7 @@ async function handle(req, res, options) {
|
|
|
15081
15186
|
if (url.pathname === "/runs/remove") return void handleRunRemoval(req, res, options.indexToken);
|
|
15082
15187
|
if (url.pathname === "/runs/sections") return void handleRunsSections(req, res, options.indexToken);
|
|
15083
15188
|
if (url.pathname === "/help") return void handleHelp(req, res, url.searchParams, options.indexToken);
|
|
15189
|
+
if (url.pathname === "/tour") return void handleTour(req, res, url.searchParams, options.indexToken);
|
|
15084
15190
|
if (url.pathname === "/notifications/open") return handleNotificationOpen(req, res, url.searchParams, options.indexToken);
|
|
15085
15191
|
if (url.pathname === "/notifications/recent") return handleRecentNotifications(req, res, url.searchParams, options.indexToken);
|
|
15086
15192
|
if (url.pathname === "/notifications/live") return handleLiveNotifications(req, res, url.searchParams, options.indexToken);
|
|
@@ -15341,10 +15447,30 @@ function handleHelp(req, res, params, indexToken) {
|
|
|
15341
15447
|
unreadNotifications: unreadCount$1(),
|
|
15342
15448
|
driving: isDriving(),
|
|
15343
15449
|
theme: readPreferences().theme,
|
|
15344
|
-
sidebar: readPreferences().sidebar
|
|
15450
|
+
sidebar: readPreferences().sidebar,
|
|
15451
|
+
tour: tourUnavailable(recentWorkspaces(Number.MAX_SAFE_INTEGER), busyReviewIds()) ?? "available",
|
|
15452
|
+
tourFailed: params.get("tour") === "unavailable"
|
|
15345
15453
|
}), indexCookieHeader(indexToken));
|
|
15346
15454
|
}
|
|
15347
15455
|
/**
|
|
15456
|
+
* Reviews something is analysing or updating right now. The guided tour is not shown on these: what
|
|
15457
|
+
* it would point at is about to be replaced. An assistant answering, or a diagram being drawn, is
|
|
15458
|
+
* not one of these: the review under it stays as it is.
|
|
15459
|
+
*/
|
|
15460
|
+
function busyReviewIds() {
|
|
15461
|
+
return new Set(runningJobs().map((running) => running.job).filter((job) => purposeOf(job) !== "ask").map((job) => job.workspaceId));
|
|
15462
|
+
}
|
|
15463
|
+
/**
|
|
15464
|
+
* Replay guided tour, from Help. Chosen when it is pressed rather than when Help was drawn, so a
|
|
15465
|
+
* review that started updating in between is not opened to a tour that cannot start. With nothing to
|
|
15466
|
+
* open, back to Help's Welcome, which says why.
|
|
15467
|
+
*/
|
|
15468
|
+
function handleTour(req, res, params, indexToken) {
|
|
15469
|
+
if (!indexAuthorised(req, params, indexToken)) return refuseIndex(req, res);
|
|
15470
|
+
const review = chooseTourReview(recentWorkspaces(Number.MAX_SAFE_INTEGER), busyReviewIds());
|
|
15471
|
+
redirect(res, review ? `/r/${encodeURIComponent(review.id)}?t=${encodeURIComponent(review.token)}${TOUR_REPLAY_HASH}` : "/help?tour=unavailable#welcome");
|
|
15472
|
+
}
|
|
15473
|
+
/**
|
|
15348
15474
|
* Whether the caller is a browser navigating, rather than something reading the API.
|
|
15349
15475
|
*
|
|
15350
15476
|
* `Accept` is the only signal available and browsers are consistent about it: a navigation asks
|
|
@@ -16314,6 +16440,8 @@ async function sendWorkspacePage(res, workspace) {
|
|
|
16314
16440
|
dismissedNotices: readPreferences().dismissedNotices,
|
|
16315
16441
|
promptsCollapsed: readPreferences().promptsCollapsed === true,
|
|
16316
16442
|
reviewLayout: readPreferences().reviewLayout ?? {},
|
|
16443
|
+
guidedTour: readPreferences().guidedTour,
|
|
16444
|
+
tourBusy: busyReviewIds().has(workspace.id),
|
|
16317
16445
|
changeMapRegister: readPreferences().changeMapRegister ?? "simple",
|
|
16318
16446
|
architecture: await architecturePane(workspace),
|
|
16319
16447
|
transcript: workspace.transcript,
|
|
@@ -17119,6 +17247,7 @@ async function handlePrefs(req, res) {
|
|
|
17119
17247
|
const layout = readReviewLayoutPatch(body?.reviewLayout);
|
|
17120
17248
|
if (layout) writeReviewLayout(layout);
|
|
17121
17249
|
if (body?.changeMapRegister === "simple" || body?.changeMapRegister === "technical") writeChangeMapRegister(body.changeMapRegister);
|
|
17250
|
+
if (isGuidedTourState(body?.guidedTour)) writeGuidedTour(body.guidedTour);
|
|
17122
17251
|
if (typeof body?.dismissNotice === "string" && !dismissNotice(body.dismissNotice)) return send(res, 400, { error: "Not a notice this page can dismiss." });
|
|
17123
17252
|
send(res, 200, { ok: true });
|
|
17124
17253
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prreviewbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Understand code changes before you approve them. Local branch and pull request reviews with Claude Code or Codex.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://prreviewbuddy.com",
|
package/static/help.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{at as e,d as t,f as n,ft as r,p as i,rt as a,t as o}from"./chunk-DwdwtB9r.js";a(),t(`global`,{}),document.querySelector(`[data-shortcut-table]`)?.replaceChildren(n(document,i(e(navigator)))),o(`/restart`),r(window,()=>window.location.reload());
|
|
1
|
+
import{at as e,d as t,f as n,ft as r,p as i,rt as a,t as o}from"./chunk-DwdwtB9r.js";function s(e,t){if(e.length===0)return-1;if(t)return e.length-1;let n=0;return e.forEach((e,t)=>{e<=120&&(n=t)}),n}var c=new Set([`ArrowDown`,`ArrowUp`,`PageDown`,`PageUp`,`Home`,`End`,` `]);function l(e,t){let n=[...e.querySelectorAll(`.prb-help-contents a[href^="#"]`)],r=n.map(t=>e.getElementById(decodeURIComponent(t.hash.slice(1)))),i=e.defaultView;if(n.length===0||!i)return;let a=r.findIndex(e=>e!==null&&`#${e.id}`===i.location.hash),o=e=>{n.forEach((t,n)=>{n===e?t.setAttribute(`aria-current`,`location`):t.removeAttribute(`aria-current`)})},l=()=>{if(a>=0)return o(a);let n=t.scrollHeight>t.clientHeight?t:e.scrollingElement??e.documentElement,i=Math.max(0,n===t?t.getBoundingClientRect().top:0),c=r.map(e=>e?e.getBoundingClientRect().top-i:1/0),l=n.scrollTop>0&&n.scrollTop+n.clientHeight>=n.scrollHeight-2;o(s(c,l))},u=e=>{let t=r.findIndex(t=>t!==null&&`#${t.id}`===e);t<0||(a=t,o(t))},d=()=>{a<0||(a=-1,l())};e.addEventListener(`click`,e=>{let t=e.target?.closest?.(`a[href^="#"]`);t&&u(t.hash)}),i.addEventListener(`hashchange`,()=>u(i.location.hash)),t.addEventListener(`wheel`,d,{passive:!0}),t.addEventListener(`touchmove`,d,{passive:!0}),t.addEventListener(`pointerdown`,d),e.addEventListener(`keydown`,e=>{c.has(e.key)&&d()}),e.addEventListener(`scroll`,l,{capture:!0,passive:!0}),i.addEventListener(`resize`,l),l()}a(),t(`global`,{}),document.querySelector(`[data-shortcut-table]`)?.replaceChildren(n(document,i(e(navigator)))),o(`/restart`);var u=document.querySelector(`main.prb-help`);u&&l(document,u),r(window,()=>window.location.reload());
|