prreviewbuddy 0.90.0 → 0.91.1
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 +26 -0
- package/dist/main.js +9 -2
- package/dist/{mcp_tools-CSj5vcSE.js → mcp_tools-BR7_XprF.js} +136 -9
- package/dist/server.js +221 -46
- package/package.json +1 -1
- package/static/sidebar.css +175 -2
- package/static/workspace.js +41 -41
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $a as
|
|
1
|
+
import { $a as runnableAgents, $i as explanationOf, $n as authorPreview, $r as kissDecisionsOf, $t as recordRuntimeBestEffort, A as settleQueue, Ai as createWithMembers, An as moveIds, Ar as attemptIdOf, At as UpdateAlreadyRunningError, B as humanizeModelId, Ba as agentIdOf, Bi as queueStamp, Bn as existingPaths, Br as phasesFor, C as parsePullRequestLink, Ca as objectWithAnyOf, Ci as lifecyclePillAction, Cn as createPrState, Cr as followedBranch, Ct as RefreshUnavailableError, D as activateQueued, Da as resolveTarget, Di as sectionOf$1, Dn as markAllRead, Dr as fillFileUrlTemplate, E as relativeTime$1, Ea as kissOutcome, Ei as parseLifecycle, En as listNotifications, Er as forgeResolver, Et as mergeUpdateInto, Fa as shorthandOf, Fi as setMembership, Fn as retireNotification, Fr as pruneJobResidue, Ft as prepareReview, G as reviewTag, Ga as STORE_ROOT, Gi as issuesOutstanding, Gn as storedConversation, Gr as isClaimed, Gt as recordTelemetryUploadConsent, H as findingRef, Ha as readAgentPreference, Hi as doneVerb, Hn as adoptLegacyBaseline, Hr as purposeOf, Ht as record, Ia as git, Ii as updateCollections, In as toneOf, Ir as saveJob, It as settleReview, Ja as agentFor$1, Ji as reviewerDispositions, Jn as isUnchanged, Jr as followedRefName, Jt as BUILD_VERSION, K as reviewTagsIn, Ka as AGENT_IDS, Ki as questionsOutstanding, Kn as unforeseenFailure, Kt as telemetryUploadConsent, L as modelProvenanceOf, La as refExists, Li as queueAdd, Ln as unreadAround, Lt as reanalyseReview, Ma as resolveBase, Mi as nameProblemText, Mn as readNotification, Mr as fail$1, Mt as runningJobs, N as cliEntry, Na as AmbiguousRefError, Ni as readCollections, Nn as readStateOf, Nr as isTerminal, Nt as failureOf$1, O as compactQueue, Oa as originRemote, Oi as collectionsStamp, On as markRead, Or as RUN_RECORD_RETENTION_MS, Ot as updateReview, Pa as shortNameOf, Pi as renameCollection, Pn as removeNotification, Pr as loadJob, Pt as observeReview, Q as markAskRunning, Qa as parkedReason, Qi as explanationActionOf, Qn as UNESTABLISHED_MOTIVATION, Qr as kissDecisionRefusal, Qt as mapRuntimes, R as provenanceDetailLines, Ra as resolveAgent, Ri as queueAddMany, Rn as unreadCount$1, Rr as isLiveJob, Rt as runJob, S as extractPullRequestLinks, Sa as extractJsonText, Si as lifecycleName$1, Sn as createArchitectureNotification, Sr as checkReview, St as FETCH_TIMEOUT_MS, T as runBatch, Ta as WITHHELD_REASON, Ti as offersLifecycle, Tn as eventName, Tr as fetchHead, Tt as cancellableHere, U as findingRefs, Ua as writeAgentPreference, Ui as isIssueOutstanding, Un as prepareConversationWithin, V as findingAtRef, Va as clearAgentPreference, Vi as readQueue, Vn as ASK_REREAD_CEILING_MS, Vr as progressSteps, Vt as readEvents, W as refOf, Wi as isQuestionOutstanding, Wn as saveRefresh, X as isRequestId, Xa as detectAgents, Xi as processDiscussion, Xn as EXPLAIN_SIMPLY_PROMPT, Xr as indexKeptIds, Xt as feedbackUrl, Ya as agentIdentityOf, Yi as verdictClosure, Yn as agentEnvOf, Yr as groupByLineage, Za as isAgentId, Zi as recommendationsOf, Zn as chatConversationSection, Zr as indexReviewGroups, Zt as waitingHistory, _ as endRun, _a as statusLookup, _i as activityAttributes, _n as writeJsonAtomically, _r as requestJobStop, _t as parseWorkspacePort, a as lineageIds, aa as assessmentLookup, ai as mutateWorkspace, an as loadArchitecture, ar as escapeHtml$1, b as stopRun, ba as SEVERITIES, bi as lifecycleChip, bn as subjectOf, br as findExistingReview, bt as writePortPreference, c as discardJob, ca as findingAssessment, ci as previousKissResult, cn as repositoryKey, cr as ReviewBeingDeletedError, ct as serverEntryExists, d as startKissJob, da as gradeScale, dn as trackedFiles, dr as MANAGED_ROOT, ea as isExplanationAction, ei as latestKissRun, en as RUNTIME_WORDS, eo as versionFrom, er as hasAuthorContent, f as isDriving, fa as isGradeFor, fi as summarise, fn as ownersOf, fr as createWorktree, g as completeRun, ga as sourcePhrase, gi as LIFECYCLES, gn as isArchitectureId, gr as removeWorktree, gt as MIN_WORKSPACE_PORT, h as beginRun, ha as reassessIn, hi as workspaceStamps, hn as ancestorsOf, hr as readMarker, ht as MAX_WORKSPACE_PORT, i as deleteReview, ia as analysisRefOf, in as checkArchitecture, ir as CODE_COPY_CLASS, ja as isCommitReview, ji as deleteCollection, jn as pruneNotifications, jr as endedAt, jt as liveUpdateFor, ka as DetachedHeadError, ki as createCollection, kn as markReadForReview, kr as allJobs, kt as askCheckout, l as kissAgentId, la as findingIn, li as recentWorkspaces, ln as storeArchitecture, lr as isBusy, lt as spawnServer, m as RUN_KINDS, ma as promptGrade, mi as workspaceRevision, mn as REPOSITORY_SCOPE, mr as isWorktreeHeld, mt as DEFAULT_WORKSPACE_PORT, n as MCP_TOOL_NAMES, na as ANALYSIS_ONLY, ni as lineagePosition, nn as formatDuration, no as AgentCancelledError, nr as renderMarkdown, o as liveJobsFor, oa as closesFinding, on as partFiles, or as renderInlineText, ot as readServerRecord, p as track, pa as isOffered, pi as touchWorkspace, pn as QUOTE_LIMITS, pr as holdWorktree, pt as writeServerRecord, q as endAskRequest, qa as agentById, qi as resolvedByConversation, qn as refreshPrContext, qr as decideKissRecommendation, r as deleteLineage, ra as NO_STATUS, ri as loadWorkspace, rn as ARCHITECTURE_DIR, ro as AgentUnavailableError, rr as CODE_BLOCK_CLASS, rt as clearServerRecordOwnedBy, s as wasBlocked, sa as currentGradeIn, si as presentedKissRunOf, sn as readArchitecture, ta as withExplanation, ti as lineageKey, tn as describeRuntimes, tr as GITHUB_MARKDOWN, tt as readIndexToken, ua as findingStatus, ui as reviewedCommit, un as storedScopes, ur as withReviewLock, ut as stopChild, v as heldRuns, va as wasReassessed, vi as activityLabel, vn as announceJob, vt as resolveWorkspacePort, w as unsupportedReason, wa as describeAuthorship, wi as lifecyclePillLabel, wn as createPrUpdate, wr as reviewedShaOf, wt as cancelUpdate, x as batchIdentity, xa as asString, xi as lifecycleItemLabel, xn as categoryOf, xr as checkCodeFreshness, y as reportRun, ya as followUpFindingId, yi as isLifecycle, yn as branchOf, yt as statedWorkspacePort, z as agentDisplayName, za as resolveModel, zi as queueRemove, zn as withUsageRecorded, zr as isTerminalJobPhase } from "./mcp_tools-BR7_XprF.js";
|
|
2
2
|
import { basename, delimiter, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
3
|
import { execFile, spawn } from "node:child_process";
|
|
4
4
|
import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -182,6 +182,7 @@ function composeReview(session) {
|
|
|
182
182
|
schemaVersion: 1,
|
|
183
183
|
summary: primary.summary,
|
|
184
184
|
...primary.motivation ? { motivation: primary.motivation } : {},
|
|
185
|
+
...primary.orientation ? { orientation: primary.orientation } : {},
|
|
185
186
|
recommendation: primary.recommendation,
|
|
186
187
|
nextSteps: primary.nextSteps,
|
|
187
188
|
overallRisk,
|
|
@@ -569,7 +570,7 @@ function isCollectionId(value) {
|
|
|
569
570
|
* filter gives, and only its star says it is pinned.
|
|
570
571
|
*/
|
|
571
572
|
function indexSectionOf(pinned, lifecycle, activity, filter) {
|
|
572
|
-
return pinned && !ordersExplicitly(filter) ? "pinned" : sectionOf(lifecycle, activity);
|
|
573
|
+
return pinned && !ordersExplicitly(filter) ? "pinned" : sectionOf$1(lifecycle, activity);
|
|
573
574
|
}
|
|
574
575
|
/**
|
|
575
576
|
* Where a review's own card is drawn, or absent when it is drawn only inside its collections.
|
|
@@ -595,7 +596,7 @@ function homeSection(pinned, lifecycle, activity, collected, filter) {
|
|
|
595
596
|
*/
|
|
596
597
|
function collectionPlacement(pinned, lifecycle, activity, filter) {
|
|
597
598
|
if (pinned || ordersExplicitly(filter)) return void 0;
|
|
598
|
-
return sectionOf(lifecycle, activity) === "done" ? "completed" : "active";
|
|
599
|
+
return sectionOf$1(lifecycle, activity) === "done" ? "completed" : "active";
|
|
599
600
|
}
|
|
600
601
|
/** A collection heading's count: what still needs attention, and the members drawn in Pinned. */
|
|
601
602
|
function collectionCountLabel(active, pinned) {
|
|
@@ -902,7 +903,7 @@ function svg(paths) {
|
|
|
902
903
|
* Buddy is asked about can never disagree. That is also why no severity, theme or status appears:
|
|
903
904
|
* those sort the findings for the reviewer and are not part of what the author is told.
|
|
904
905
|
*/
|
|
905
|
-
function count(n, noun) {
|
|
906
|
+
function count$1(n, noun) {
|
|
906
907
|
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
907
908
|
}
|
|
908
909
|
/**
|
|
@@ -919,8 +920,8 @@ function feedbackSelectionParts(issues, questions) {
|
|
|
919
920
|
const total = issues + questions;
|
|
920
921
|
return {
|
|
921
922
|
total: total === 0 ? "Nothing chosen" : `${total} chosen`,
|
|
922
|
-
issues: issues ? count(issues, "issue") : "",
|
|
923
|
-
questions: questions ? count(questions, "question") : ""
|
|
923
|
+
issues: issues ? count$1(issues, "issue") : "",
|
|
924
|
+
questions: questions ? count$1(questions, "question") : ""
|
|
924
925
|
};
|
|
925
926
|
}
|
|
926
927
|
/** "5 chosen · 2 issues · 3 questions", "1 chosen · 1 question", "Nothing chosen". */
|
|
@@ -1522,6 +1523,99 @@ function splitFilePath(path) {
|
|
|
1522
1523
|
};
|
|
1523
1524
|
}
|
|
1524
1525
|
//#endregion
|
|
1526
|
+
//#region ../../packages/review-harness/src/domain/ui/components/impact_groups.ts
|
|
1527
|
+
/** The pane's folds: each section, and the closed drawer under them. */
|
|
1528
|
+
var ISSUE_FOLDS = [
|
|
1529
|
+
"failure",
|
|
1530
|
+
"risk",
|
|
1531
|
+
"intent",
|
|
1532
|
+
"maintenance",
|
|
1533
|
+
"other",
|
|
1534
|
+
"closed"
|
|
1535
|
+
];
|
|
1536
|
+
/** Display order: what is wrong, what may be, what only the reviewer can judge, what costs later. */
|
|
1537
|
+
var ISSUE_SECTIONS = [
|
|
1538
|
+
"failure",
|
|
1539
|
+
"risk",
|
|
1540
|
+
"intent",
|
|
1541
|
+
"maintenance",
|
|
1542
|
+
"other"
|
|
1543
|
+
];
|
|
1544
|
+
var ISSUE_SECTION_TITLES = {
|
|
1545
|
+
failure: "Failures",
|
|
1546
|
+
risk: "Possible risks",
|
|
1547
|
+
intent: "Intent to confirm",
|
|
1548
|
+
maintenance: "Maintenance",
|
|
1549
|
+
other: "Other issues"
|
|
1550
|
+
};
|
|
1551
|
+
function sectionOf(issue) {
|
|
1552
|
+
return issue.consequence?.group ?? "other";
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Issues by section, each section by severity as the reviewer now holds it, ties in the order the
|
|
1556
|
+
* analysis gave them. Severity orders within a section and never moves an issue between sections.
|
|
1557
|
+
*/
|
|
1558
|
+
function groupIssues(issues, gradeOf) {
|
|
1559
|
+
const rank = (issue) => {
|
|
1560
|
+
const index = SEVERITIES.indexOf(gradeOf(issue) ?? "");
|
|
1561
|
+
return index < 0 ? SEVERITIES.length : index;
|
|
1562
|
+
};
|
|
1563
|
+
return ISSUE_SECTIONS.map((section) => ({
|
|
1564
|
+
section,
|
|
1565
|
+
issues: issues.map((issue, index) => ({
|
|
1566
|
+
issue,
|
|
1567
|
+
index
|
|
1568
|
+
})).filter(({ issue }) => sectionOf(issue) === section).sort((a, b) => rank(a.issue) - rank(b.issue) || a.index - b.index).map(({ issue }) => issue)
|
|
1569
|
+
})).filter((group) => group.issues.length > 0);
|
|
1570
|
+
}
|
|
1571
|
+
function tallyEntries(entries) {
|
|
1572
|
+
const tally = {
|
|
1573
|
+
bySection: {},
|
|
1574
|
+
verify: 0,
|
|
1575
|
+
decide: 0,
|
|
1576
|
+
step: 0
|
|
1577
|
+
};
|
|
1578
|
+
for (const entry of entries) {
|
|
1579
|
+
tally.bySection[entry.section] = (tally.bySection[entry.section] ?? 0) + 1;
|
|
1580
|
+
if (entry.next) tally[entry.next] += 1;
|
|
1581
|
+
}
|
|
1582
|
+
return tally;
|
|
1583
|
+
}
|
|
1584
|
+
function tallyIssues(issues) {
|
|
1585
|
+
return tallyEntries(issues.map((issue) => {
|
|
1586
|
+
const step = issue.consequence?.next;
|
|
1587
|
+
return {
|
|
1588
|
+
section: sectionOf(issue),
|
|
1589
|
+
...step ? { next: step.kind ?? "step" } : {}
|
|
1590
|
+
};
|
|
1591
|
+
}));
|
|
1592
|
+
}
|
|
1593
|
+
var count = (n, one, many) => `${n} ${n === 1 ? one : many}`;
|
|
1594
|
+
/**
|
|
1595
|
+
* One factual line over the sections: how many issues are outstanding, and how many of them name
|
|
1596
|
+
* a step, by the kind the analysis gave it. The per-section counts are the nav's, so they are not
|
|
1597
|
+
* repeated here. Always "issues": Questions keep their own tab and their own count.
|
|
1598
|
+
*/
|
|
1599
|
+
function issuesAttentionLine(tally) {
|
|
1600
|
+
const total = Object.values(tally.bySection).reduce((sum, n) => sum + (n ?? 0), 0);
|
|
1601
|
+
if (total === 0) return "No issues are outstanding from this analysis.";
|
|
1602
|
+
const steps = [
|
|
1603
|
+
tally.verify ? count(tally.verify, "verification step", "verification steps") : "",
|
|
1604
|
+
tally.decide ? count(tally.decide, "decision", "decisions") : "",
|
|
1605
|
+
tally.step ? count(tally.step, "next step", "next steps") : ""
|
|
1606
|
+
].filter(Boolean);
|
|
1607
|
+
return [count(total, "outstanding issue", "outstanding issues"), ...steps].join(" · ");
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Buttons to each section with its count. Buttons, not `#` links: the hash routes the review page.
|
|
1611
|
+
* Drawn only when there are two sections to choose between; a single one is already in view.
|
|
1612
|
+
*/
|
|
1613
|
+
function renderImpactNavItems(tally) {
|
|
1614
|
+
const present = ISSUE_SECTIONS.filter((section) => (tally.bySection[section] ?? 0) > 0);
|
|
1615
|
+
if (present.length < 2) return "";
|
|
1616
|
+
return present.map((section) => `<button type="button" class="pr-helper-issue-nav-item" data-issue-nav="${section}">${escapeHtml$1(ISSUE_SECTION_TITLES[section])}<span class="pr-helper-issue-nav-count">${tally.bySection[section]}</span></button>`).join("");
|
|
1617
|
+
}
|
|
1618
|
+
//#endregion
|
|
1525
1619
|
//#region ../../packages/review-harness/src/domain/ai_review/review_outcome.ts
|
|
1526
1620
|
/**
|
|
1527
1621
|
* The one reading of a session, for every surface.
|
|
@@ -3292,16 +3386,6 @@ function renderInvestigationNote(note) {
|
|
|
3292
3386
|
return `<p class="pr-helper-investigation-note">${renderInlineText(note)}</p>`;
|
|
3293
3387
|
}
|
|
3294
3388
|
/**
|
|
3295
|
-
* The bar above the two groups, worded the way the Questions tab words its own.
|
|
3296
|
-
*
|
|
3297
|
-
* Only drawn once a reconciliation has closed something. On a first analysis every finding is
|
|
3298
|
-
* open, "15 of 15 still open" is a sentence with no information in it, and a review that has
|
|
3299
|
-
* never been updated should not grow a progress bar to tell the reviewer they have made none.
|
|
3300
|
-
*/
|
|
3301
|
-
function findingsProgressBody(total, open, verb = "resolved") {
|
|
3302
|
-
return open === 0 ? `<span class="pr-helper-questions-progress-check" aria-hidden="true">\u2713</span><span class="pr-helper-questions-progress-count">All ${total} issues ${verb}</span>` : `<span class="pr-helper-questions-progress-count">${open} of ${total} still open</span>`;
|
|
3303
|
-
}
|
|
3304
|
-
/**
|
|
3305
3389
|
* Work that is done, kept and closed.
|
|
3306
3390
|
*
|
|
3307
3391
|
* Collapsed rather than dropped: the reviewer asked whether these were fixed, and "they are gone
|
|
@@ -3309,8 +3393,8 @@ function findingsProgressBody(total, open, verb = "resolved") {
|
|
|
3309
3393
|
* ten resolved findings above five open ones is the layout that made the count feel wrong in the
|
|
3310
3394
|
* first place.
|
|
3311
3395
|
*/
|
|
3312
|
-
function renderClosedFindings(summary, cards, kind, nothingOpen, waiting) {
|
|
3313
|
-
return `<details class="pr-helper-closed-findings" data-closed-findings="${kind}"${nothingOpen ? " data-nothing-open" : ""}>
|
|
3396
|
+
function renderClosedFindings(summary, cards, kind, nothingOpen, waiting, open = false) {
|
|
3397
|
+
return `<details class="pr-helper-closed-findings" data-closed-findings="${kind}"${nothingOpen ? " data-nothing-open" : ""}${open ? " open" : ""}>
|
|
3314
3398
|
<summary class="pr-helper-closed-findings-summary" data-closed-findings-summary>${escapeHtml$1(summary)}${renderWaitingSuggestions(waiting)}</summary>
|
|
3315
3399
|
<div class="pr-helper-closed-findings-list">${cards}</div>
|
|
3316
3400
|
</details>`;
|
|
@@ -3346,13 +3430,37 @@ function renderIssuesTab(session, investigatingId, options) {
|
|
|
3346
3430
|
const isDismissed = dispositions.isDismissed(issue);
|
|
3347
3431
|
const stale = dispositions.isStale(issue);
|
|
3348
3432
|
const assessment = assess("issue", issue);
|
|
3433
|
+
const impact = issue.consequence;
|
|
3434
|
+
const fixed = issue.reconciliationStatus === "resolved" ? issue.resolvedBehaviour?.trim() : void 0;
|
|
3435
|
+
const framed = Boolean(impact || fixed);
|
|
3436
|
+
const headline = fixed ?? impact?.title ?? issue.title;
|
|
3437
|
+
const explanation = renderCardExplanation(explanationOf(session, "issue", issue.id), options);
|
|
3438
|
+
const technical = `<p class="pr-helper-issue-desc">${renderInlineText(issue.description)}</p>${framed ? "" : explanation}
|
|
3439
|
+
${framed ? "" : renderReconciliationNote(issue.reconciliationNote)}
|
|
3440
|
+
${renderStatusHistory(issue.statusHistory, now)}
|
|
3441
|
+
${renderInvestigationNote(issue.investigationNote)}
|
|
3442
|
+
${renderExistingDiscussion(issue.existingDiscussion)}
|
|
3443
|
+
${renderFileLocation(issue.file, "pr-helper-issue-loc", issue.line)}
|
|
3444
|
+
${renderRelatedFiles(issue.relatedFiles)}
|
|
3445
|
+
${renderInvestigationProvenance("issue", issue.id, records, now)}`;
|
|
3446
|
+
const body = framed ? `${fixed ? "" : renderConsequence(impact)}${explanation}
|
|
3447
|
+
${renderReconciliationNote(issue.reconciliationNote)}
|
|
3448
|
+
<details class="pr-helper-issue-technical">
|
|
3449
|
+
<summary class="pr-helper-issue-technical-summary">Technical details</summary>
|
|
3450
|
+
<div class="pr-helper-issue-technical-body">
|
|
3451
|
+
${fixed && impact ? renderOriginalConsequence(impact) : ""}
|
|
3452
|
+
<p class="pr-helper-issue-technical-title">${renderInlineText(issue.title)}</p>
|
|
3453
|
+
${technical}
|
|
3454
|
+
</div>
|
|
3455
|
+
</details>` : technical;
|
|
3456
|
+
const next = impact?.next ? ` data-issue-next="${impact.next.kind ?? "step"}"` : "";
|
|
3349
3457
|
return `
|
|
3350
|
-
<div class="pr-helper-issue-card ${cardGradeClass("issue", assessment.current)}${issue.reconciliationStatus ? ` pr-helper-issue-${issue.reconciliationStatus}` : ""}${issue.investigationImpact ? ` pr-helper-issue-investigation-${issue.investigationImpact}` : ""}${isDismissed && !stale ? " pr-helper-issue-card-dismissed" : ""}" data-finding-card="${escapeHtml$1(issue.id)}">
|
|
3458
|
+
<div class="pr-helper-issue-card ${cardGradeClass("issue", assessment.current)}${issue.reconciliationStatus ? ` pr-helper-issue-${issue.reconciliationStatus}` : ""}${issue.investigationImpact ? ` pr-helper-issue-investigation-${issue.investigationImpact}` : ""}${isDismissed && !stale ? " pr-helper-issue-card-dismissed" : ""}" data-finding-card="${escapeHtml$1(issue.id)}" data-issue-section="${sectionOf(issue)}"${next}>
|
|
3351
3459
|
<div class="pr-helper-issue-header">
|
|
3352
3460
|
<div class="pr-helper-issue-title-row">
|
|
3353
3461
|
${options?.feedbackSelect ? renderFeedbackPick("issue", issue.title, issueFeedbackMarkdown(issue), ref) : ""}
|
|
3354
3462
|
${renderFindingRef(ref)}
|
|
3355
|
-
<p class="pr-helper-issue-title">${renderInlineText(
|
|
3463
|
+
<p class="pr-helper-issue-title">${renderInlineText(headline)}</p>
|
|
3356
3464
|
${renderFindingCopySource(issueMarkdown(issue, assessment), "issue")}
|
|
3357
3465
|
${options?.askBuddy ? renderAskBuddyMenu("issue") : ""}
|
|
3358
3466
|
</div>
|
|
@@ -3367,14 +3475,7 @@ function renderIssuesTab(session, investigatingId, options) {
|
|
|
3367
3475
|
</div>
|
|
3368
3476
|
${renderStaleDismissal(stale)}
|
|
3369
3477
|
${renderAssessmentRegion("issue", assessment, grading, statusOf("issue", issue.id))}
|
|
3370
|
-
|
|
3371
|
-
${renderReconciliationNote(issue.reconciliationNote)}
|
|
3372
|
-
${renderStatusHistory(issue.statusHistory, now)}
|
|
3373
|
-
${renderInvestigationNote(issue.investigationNote)}
|
|
3374
|
-
${renderExistingDiscussion(issue.existingDiscussion)}
|
|
3375
|
-
${renderFileLocation(issue.file, "pr-helper-issue-loc", issue.line)}
|
|
3376
|
-
${renderRelatedFiles(issue.relatedFiles)}
|
|
3377
|
-
${renderInvestigationProvenance("issue", issue.id, records, now)}
|
|
3478
|
+
${body}
|
|
3378
3479
|
<div class="pr-helper-issue-actions">
|
|
3379
3480
|
${renderInvestigationActions("issue", issue.id, records, investigatingId)}
|
|
3380
3481
|
${renderDismissToggle(issue, isDismissed, stale, dismissals.get(issue.id), dispositions.isClosedByVerdict(issue))}
|
|
@@ -3383,11 +3484,56 @@ function renderIssuesTab(session, investigatingId, options) {
|
|
|
3383
3484
|
`;
|
|
3384
3485
|
};
|
|
3385
3486
|
const counts = issuesOutstanding(result.issues, dispositions);
|
|
3386
|
-
|
|
3387
|
-
const open = result.issues.filter((issue) => isIssueOutstanding(issue, dispositions)).map(card).join("");
|
|
3487
|
+
const outstanding = result.issues.filter((issue) => isIssueOutstanding(issue, dispositions));
|
|
3388
3488
|
const closedIssues = result.issues.filter((issue) => !isIssueOutstanding(issue, dispositions));
|
|
3389
|
-
const
|
|
3390
|
-
|
|
3489
|
+
const folds = options?.issueFolds ?? {};
|
|
3490
|
+
const tally = tallyIssues(outstanding);
|
|
3491
|
+
const grouped = new Map(groupIssues(outstanding, (issue) => assess("issue", issue).current).map((group) => [group.section, group.issues]));
|
|
3492
|
+
const present = new Set(result.issues.map(sectionOf));
|
|
3493
|
+
const headless = present.size === 1 && present.has("other");
|
|
3494
|
+
const groups = ISSUE_SECTIONS.filter((section) => present.has(section)).map((section) => {
|
|
3495
|
+
const issues = grouped.get(section) ?? [];
|
|
3496
|
+
if (headless) return `<div class="pr-helper-issue-group" data-impact-group="${section}"${issues.length ? "" : " hidden"}>
|
|
3497
|
+
<div class="pr-helper-issue-group-list">${issues.map(card).join("")}</div>
|
|
3498
|
+
</div>`;
|
|
3499
|
+
return `<details class="pr-helper-issue-group" data-impact-group="${section}"${folds[section] === false ? "" : " open"}${issues.length ? "" : " hidden"}>
|
|
3500
|
+
<summary class="pr-helper-issue-group-summary"><h4 class="pr-helper-issue-group-title">${escapeHtml$1(ISSUE_SECTION_TITLES[section])}</h4><span class="pr-helper-issue-group-count" data-issue-group-count>${issues.length}</span></summary>
|
|
3501
|
+
<div class="pr-helper-issue-group-list">${issues.map(card).join("")}</div>
|
|
3502
|
+
</details>`;
|
|
3503
|
+
}).join("");
|
|
3504
|
+
const nav = renderImpactNavItems(tally);
|
|
3505
|
+
const orientation = result.orientation?.trim();
|
|
3506
|
+
const head = `<div class="pr-helper-issues-orientation">
|
|
3507
|
+
${orientation ? `<p class="pr-helper-issues-orientation-text">${renderInlineText(orientation)}</p>` : ""}
|
|
3508
|
+
<p class="pr-helper-issues-attention" role="status" data-issues-attention>${escapeHtml$1(issuesAttentionLine(tally))}</p>
|
|
3509
|
+
<nav class="pr-helper-issue-nav" aria-label="Issues by kind" data-issue-nav-list${nav ? "" : " hidden"}>${nav}</nav>
|
|
3510
|
+
</div>`;
|
|
3511
|
+
if (closedIssues.length === 0) return `${head}${groups}`;
|
|
3512
|
+
return `${head}${groups}${renderClosedFindings(closedFindingsSummary(counts), closedIssues.map(card).join(""), "issues", counts.open === 0, closedIssues.filter((issue) => assess("issue", issue).pending || statusOf("issue", issue.id).pending).length, folds.closed === true)}`;
|
|
3513
|
+
}
|
|
3514
|
+
/** What the issue means, read first: the scenario and the step that would settle it. */
|
|
3515
|
+
function renderConsequence(impact) {
|
|
3516
|
+
if (!impact) return "";
|
|
3517
|
+
return `<div class="pr-helper-issue-impact">
|
|
3518
|
+
<p class="pr-helper-issue-scenario">${renderInlineText(impact.scenario)}</p>
|
|
3519
|
+
${renderConsequenceStep(impact.next)}
|
|
3520
|
+
</div>`;
|
|
3521
|
+
}
|
|
3522
|
+
var STEP_LABELS = {
|
|
3523
|
+
verify: "To verify:",
|
|
3524
|
+
decide: "To decide:"
|
|
3525
|
+
};
|
|
3526
|
+
function renderConsequenceStep(step) {
|
|
3527
|
+
if (!step) return "";
|
|
3528
|
+
return `<p class="pr-helper-issue-next"><span class="pr-helper-issue-next-label">${step.kind ? STEP_LABELS[step.kind] : "Next step:"}</span> ${renderInlineText(step.text)}</p>`;
|
|
3529
|
+
}
|
|
3530
|
+
/** A fixed issue's original consequence, kept with the technical account once the fix heads the card. */
|
|
3531
|
+
function renderOriginalConsequence(impact) {
|
|
3532
|
+
return `<div class="pr-helper-issue-original">
|
|
3533
|
+
<h5>Before the fix</h5>
|
|
3534
|
+
<p class="pr-helper-issue-original-title">${renderInlineText(impact.title)}</p>
|
|
3535
|
+
<p class="pr-helper-issue-scenario">${renderInlineText(impact.scenario)}</p>
|
|
3536
|
+
</div>`;
|
|
3391
3537
|
}
|
|
3392
3538
|
/**
|
|
3393
3539
|
* What to call the closed pile, by what is actually in it.
|
|
@@ -6783,7 +6929,8 @@ function renderStandardViews(view, sections) {
|
|
|
6783
6929
|
askBuddy: true,
|
|
6784
6930
|
feedbackSelect: true,
|
|
6785
6931
|
reassess: true,
|
|
6786
|
-
findingRefs: true
|
|
6932
|
+
findingRefs: true,
|
|
6933
|
+
...view.reviewLayout?.issueGroups ? { issueFolds: view.reviewLayout.issueGroups } : {}
|
|
6787
6934
|
})}
|
|
6788
6935
|
</section>
|
|
6789
6936
|
<section class="prb-view" data-view="questions" hidden>
|
|
@@ -10136,7 +10283,7 @@ function renderReviewsPage(view) {
|
|
|
10136
10283
|
const collected = (memberOf.get(group.key) ?? []).length > 0;
|
|
10137
10284
|
const home = homeSection(group.latest.pinned === true, group.latest.lifecycle, activityOf(group), collected, "all");
|
|
10138
10285
|
if (home === void 0) collectedHome.add(group);
|
|
10139
|
-
sections[home ?? sectionOf(group.latest.lifecycle, activityOf(group))].push(group);
|
|
10286
|
+
sections[home ?? sectionOf$1(group.latest.lifecycle, activityOf(group))].push(group);
|
|
10140
10287
|
}
|
|
10141
10288
|
const storedReviews = view.groups.reduce((total, group) => total + group.history.length, 0);
|
|
10142
10289
|
const renderSectionAt = (section) => renderSection(section, sections[section], (groups) => renderCards(groups, "home", (group) => collectedHome.has(group)), sectionOpen(section, view.sectionsCollapsed), sections[section].filter((group) => !inProgressOnly.has(group.latest.id) && !collectedHome.has(group)).length);
|
|
@@ -11000,10 +11147,11 @@ function isRunsSection(value) {
|
|
|
11000
11147
|
/**
|
|
11001
11148
|
* How the reviewer last laid out a review page, kept so the next review opens the same way.
|
|
11002
11149
|
*
|
|
11003
|
-
*
|
|
11150
|
+
* Four things, each the layout as it last stood rather than a log of what was pressed: whether
|
|
11004
11151
|
* Buddy AI was open or closed, whether a surface was expanded (Buddy AI itself, or the document,
|
|
11005
|
-
* whichever pane that is on the next review),
|
|
11006
|
-
* nobody has changed has no key, so the page's own
|
|
11152
|
+
* whichever pane that is on the next review), which parts of the rail were folded, and which
|
|
11153
|
+
* sections of the Issues pane were. A field nobody has changed has no key, so the page's own
|
|
11154
|
+
* defaults still decide for a new reviewer.
|
|
11007
11155
|
*
|
|
11008
11156
|
* Pure, so the page and the server read it with the same rules. Stored in `ui.json`; see
|
|
11009
11157
|
* `preferences.ts`.
|
|
@@ -11014,35 +11162,41 @@ var RAIL_FOLDS = [
|
|
|
11014
11162
|
"prb-rail-path",
|
|
11015
11163
|
"prb-rail-kiss"
|
|
11016
11164
|
];
|
|
11017
|
-
function
|
|
11165
|
+
function readFolds(value, known) {
|
|
11018
11166
|
if (!value || typeof value !== "object") return void 0;
|
|
11019
|
-
const
|
|
11020
|
-
for (const fold of
|
|
11167
|
+
const folds = {};
|
|
11168
|
+
for (const fold of known) {
|
|
11021
11169
|
const open = value[fold];
|
|
11022
|
-
if (typeof open === "boolean")
|
|
11170
|
+
if (typeof open === "boolean") folds[fold] = open;
|
|
11023
11171
|
}
|
|
11024
|
-
return Object.keys(
|
|
11172
|
+
return Object.keys(folds).length > 0 ? folds : void 0;
|
|
11025
11173
|
}
|
|
11174
|
+
var readRail = (value) => readFolds(value, RAIL_FOLDS);
|
|
11175
|
+
var readIssueGroups = (value) => readFolds(value, ISSUE_FOLDS);
|
|
11026
11176
|
/** A stored layout, keeping only what this page knows how to draw. Anything else reads as unset. */
|
|
11027
11177
|
function readReviewLayout(value) {
|
|
11028
11178
|
if (!value || typeof value !== "object") return {};
|
|
11029
|
-
const { ask, expanded, rail } = value;
|
|
11179
|
+
const { ask, expanded, rail, issueGroups } = value;
|
|
11030
11180
|
const folds = readRail(rail);
|
|
11181
|
+
const groups = readIssueGroups(issueGroups);
|
|
11031
11182
|
return {
|
|
11032
11183
|
...ask === "open" || ask === "closed" ? { ask } : {},
|
|
11033
11184
|
...expanded === "buddy" || expanded === "document" ? { expanded } : {},
|
|
11034
|
-
...folds ? { rail: folds } : {}
|
|
11185
|
+
...folds ? { rail: folds } : {},
|
|
11186
|
+
...groups ? { issueGroups: groups } : {}
|
|
11035
11187
|
};
|
|
11036
11188
|
}
|
|
11037
11189
|
/** A patch from the page, or null when it holds nothing this page could have sent. */
|
|
11038
11190
|
function readReviewLayoutPatch(value) {
|
|
11039
11191
|
if (!value || typeof value !== "object") return null;
|
|
11040
|
-
const { ask, expanded, rail } = value;
|
|
11192
|
+
const { ask, expanded, rail, issueGroups } = value;
|
|
11041
11193
|
const folds = readRail(rail);
|
|
11194
|
+
const groups = readIssueGroups(issueGroups);
|
|
11042
11195
|
const patch = {
|
|
11043
11196
|
...ask === "open" || ask === "closed" ? { ask } : {},
|
|
11044
11197
|
...expanded === "buddy" || expanded === "document" || expanded === null ? { expanded } : {},
|
|
11045
|
-
...folds ? { rail: folds } : {}
|
|
11198
|
+
...folds ? { rail: folds } : {},
|
|
11199
|
+
...groups ? { issueGroups: groups } : {}
|
|
11046
11200
|
};
|
|
11047
11201
|
return Object.keys(patch).length > 0 ? patch : null;
|
|
11048
11202
|
}
|
|
@@ -11056,6 +11210,10 @@ function applyReviewLayoutPatch(layout, patch) {
|
|
|
11056
11210
|
...patch.rail ? { rail: {
|
|
11057
11211
|
...layout.rail,
|
|
11058
11212
|
...patch.rail
|
|
11213
|
+
} } : {},
|
|
11214
|
+
...patch.issueGroups ? { issueGroups: {
|
|
11215
|
+
...layout.issueGroups,
|
|
11216
|
+
...patch.issueGroups
|
|
11059
11217
|
} } : {}
|
|
11060
11218
|
};
|
|
11061
11219
|
}
|
|
@@ -12522,6 +12680,23 @@ feature: E</pre>
|
|
|
12522
12680
|
reads to answer your questions about the change, and what Update review starts from. After
|
|
12523
12681
|
that it is removed. The review itself stays, and a later question makes a new checkout for
|
|
12524
12682
|
itself.</p>`
|
|
12683
|
+
},
|
|
12684
|
+
{
|
|
12685
|
+
id: "reading-issues",
|
|
12686
|
+
title: "Reading issues",
|
|
12687
|
+
body: `<p>Issues opens with a sentence on what the change touches and how many issues are
|
|
12688
|
+
outstanding. Each issue is grouped by the decision it asks of you: <b>Failures</b> the code shows
|
|
12689
|
+
going wrong under a stated condition, <b>Possible risks</b> still to be verified, <b>Intent to
|
|
12690
|
+
confirm</b> where the behaviour is right only if it was meant, and <b>Maintenance</b> for what it
|
|
12691
|
+
costs later. Within each group the most severe come first. Severity and confidence are shown on
|
|
12692
|
+
each card, separately from its group.</p>
|
|
12693
|
+
<p>A card leads with what the issue means and when it happens, then the step that would settle
|
|
12694
|
+
it, marked <b>To verify</b> or <b>To decide</b>. The technical account, where it is in the code and
|
|
12695
|
+
its history are under <b>Technical details</b> on the card. Folding a group is remembered for the
|
|
12696
|
+
next review. Issues from reviews made before this have no grouping and are under <b>Other
|
|
12697
|
+
issues</b>, as they were.</p>
|
|
12698
|
+
<p>Resolved and dismissed issues are in the closed list at the bottom. One the new commits fixed
|
|
12699
|
+
leads with what the code now does, when the update could see it.</p>`
|
|
12525
12700
|
},
|
|
12526
12701
|
{
|
|
12527
12702
|
id: "updating-and-reanalysing",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prreviewbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.91.1",
|
|
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/sidebar.css
CHANGED
|
@@ -1521,8 +1521,7 @@
|
|
|
1521
1521
|
Deliberately lighter than the Guide's progress box (no bar, no "next up" line): the Guide is
|
|
1522
1522
|
a workflow you walk in order, so it earns a full dashboard, whereas this is a flat list where
|
|
1523
1523
|
the only useful fact is how many are still open. */
|
|
1524
|
-
.pr-helper-questions-progress
|
|
1525
|
-
.pr-helper-findings-progress {
|
|
1524
|
+
.pr-helper-questions-progress {
|
|
1526
1525
|
display: flex;
|
|
1527
1526
|
align-items: center;
|
|
1528
1527
|
gap: 8px;
|
|
@@ -1590,6 +1589,180 @@
|
|
|
1590
1589
|
.pr-helper-closed-findings-list .pr-helper-issue-card:hover,
|
|
1591
1590
|
.pr-helper-closed-findings-list .pr-helper-question-card:hover { opacity: 1; }
|
|
1592
1591
|
|
|
1592
|
+
/* ── Issues by kind ──
|
|
1593
|
+
The pane opens on what the change touches and how much needs attention, then one section per
|
|
1594
|
+
kind of decision. A section is a disclosure so a reader can fold away what they have dealt
|
|
1595
|
+
with; its heading keeps the Questions tab's group style so the two tabs read as one product. */
|
|
1596
|
+
.pr-helper-issues-orientation {
|
|
1597
|
+
margin-bottom: 16px;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.pr-helper-issues-orientation-text {
|
|
1601
|
+
font-size: 13px;
|
|
1602
|
+
line-height: 1.5;
|
|
1603
|
+
margin: 0 0 4px;
|
|
1604
|
+
overflow-wrap: anywhere;
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.pr-helper-issues-attention {
|
|
1608
|
+
font-size: 12px;
|
|
1609
|
+
font-weight: 600;
|
|
1610
|
+
color: var(--color-fg-muted, #57606a);
|
|
1611
|
+
margin: 0;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.pr-helper-issue-nav {
|
|
1615
|
+
display: flex;
|
|
1616
|
+
flex-wrap: wrap;
|
|
1617
|
+
gap: 6px;
|
|
1618
|
+
margin-top: 10px;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.pr-helper-issue-nav[hidden] { display: none; }
|
|
1622
|
+
|
|
1623
|
+
.pr-helper-issue-nav-item {
|
|
1624
|
+
display: inline-flex;
|
|
1625
|
+
align-items: center;
|
|
1626
|
+
gap: 6px;
|
|
1627
|
+
font: inherit;
|
|
1628
|
+
font-size: 12px;
|
|
1629
|
+
font-weight: 600;
|
|
1630
|
+
padding: 3px 10px;
|
|
1631
|
+
border: 1px solid var(--color-border-default, #d0d7de);
|
|
1632
|
+
border-radius: 100px;
|
|
1633
|
+
background: transparent;
|
|
1634
|
+
color: var(--color-fg-default, #1f2328);
|
|
1635
|
+
cursor: pointer;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.pr-helper-issue-nav-item:hover { border-color: #9333ea; }
|
|
1639
|
+
.pr-helper-issue-nav-item:focus-visible { outline: 2px solid #9333ea; outline-offset: 2px; }
|
|
1640
|
+
|
|
1641
|
+
.pr-helper-issue-nav-count {
|
|
1642
|
+
color: var(--color-fg-muted, #57606a);
|
|
1643
|
+
font-variant-numeric: tabular-nums;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.pr-helper-issue-group { margin-bottom: 8px; }
|
|
1647
|
+
.pr-helper-issue-group[hidden] { display: none; }
|
|
1648
|
+
|
|
1649
|
+
.pr-helper-issue-group-summary {
|
|
1650
|
+
display: flex;
|
|
1651
|
+
align-items: center;
|
|
1652
|
+
gap: 6px;
|
|
1653
|
+
cursor: pointer;
|
|
1654
|
+
list-style: none;
|
|
1655
|
+
padding: 4px 0;
|
|
1656
|
+
margin-bottom: 6px;
|
|
1657
|
+
color: #9333ea;
|
|
1658
|
+
scroll-margin-top: 12px;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
.pr-helper-issue-group-summary::-webkit-details-marker { display: none; }
|
|
1662
|
+
|
|
1663
|
+
.pr-helper-issue-group-summary::before {
|
|
1664
|
+
content: '';
|
|
1665
|
+
width: 0;
|
|
1666
|
+
height: 0;
|
|
1667
|
+
border-left: 5px solid currentColor;
|
|
1668
|
+
border-top: 4px solid transparent;
|
|
1669
|
+
border-bottom: 4px solid transparent;
|
|
1670
|
+
transition: transform 0.15s ease;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
.pr-helper-issue-group[open] > .pr-helper-issue-group-summary::before { transform: rotate(90deg); }
|
|
1674
|
+
.pr-helper-issue-group-summary:focus-visible { outline: 2px solid #9333ea; outline-offset: 2px; border-radius: 2px; }
|
|
1675
|
+
|
|
1676
|
+
.pr-helper-issue-group-title {
|
|
1677
|
+
font-size: 11px;
|
|
1678
|
+
font-weight: 700;
|
|
1679
|
+
text-transform: uppercase;
|
|
1680
|
+
letter-spacing: 0.04em;
|
|
1681
|
+
margin: 0;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.pr-helper-issue-group-count {
|
|
1685
|
+
font-size: 11px;
|
|
1686
|
+
font-weight: 600;
|
|
1687
|
+
color: var(--color-fg-muted, #57606a);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/* The consequence, read first. The title row is shared with every card; these sit under it. */
|
|
1691
|
+
.pr-helper-issue-card .pr-helper-issue-title { overflow-wrap: anywhere; }
|
|
1692
|
+
|
|
1693
|
+
.pr-helper-issue-impact { margin: 0 0 8px; }
|
|
1694
|
+
|
|
1695
|
+
.pr-helper-issue-scenario {
|
|
1696
|
+
font-size: 13px;
|
|
1697
|
+
line-height: 1.5;
|
|
1698
|
+
margin: 0 0 6px;
|
|
1699
|
+
text-align: left;
|
|
1700
|
+
overflow-wrap: anywhere;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.pr-helper-issue-next {
|
|
1704
|
+
font-size: 13px;
|
|
1705
|
+
line-height: 1.5;
|
|
1706
|
+
margin: 0;
|
|
1707
|
+
overflow-wrap: anywhere;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
.pr-helper-issue-next-label { font-weight: 600; }
|
|
1711
|
+
|
|
1712
|
+
/* The technical account, one click away and never open on arrival. Same chevron as the closed
|
|
1713
|
+
drawer, smaller and muted, because it belongs to one card rather than to the pane. */
|
|
1714
|
+
.pr-helper-issue-technical { margin-top: 8px; }
|
|
1715
|
+
|
|
1716
|
+
.pr-helper-issue-technical-summary {
|
|
1717
|
+
display: inline-flex;
|
|
1718
|
+
align-items: center;
|
|
1719
|
+
gap: 6px;
|
|
1720
|
+
cursor: pointer;
|
|
1721
|
+
list-style: none;
|
|
1722
|
+
font-size: 12px;
|
|
1723
|
+
font-weight: 600;
|
|
1724
|
+
color: var(--color-fg-muted, #57606a);
|
|
1725
|
+
padding: 2px 0;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.pr-helper-issue-technical-summary::-webkit-details-marker { display: none; }
|
|
1729
|
+
|
|
1730
|
+
.pr-helper-issue-technical-summary::before {
|
|
1731
|
+
content: '';
|
|
1732
|
+
width: 0;
|
|
1733
|
+
height: 0;
|
|
1734
|
+
border-left: 5px solid currentColor;
|
|
1735
|
+
border-top: 4px solid transparent;
|
|
1736
|
+
border-bottom: 4px solid transparent;
|
|
1737
|
+
transition: transform 0.15s ease;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.pr-helper-issue-technical[open] > .pr-helper-issue-technical-summary::before { transform: rotate(90deg); }
|
|
1741
|
+
.pr-helper-issue-technical-summary:hover { color: var(--color-fg-default, #1f2328); }
|
|
1742
|
+
.pr-helper-issue-technical-summary:focus-visible { outline: 2px solid #9333ea; outline-offset: 2px; border-radius: 2px; }
|
|
1743
|
+
|
|
1744
|
+
.pr-helper-issue-technical-body {
|
|
1745
|
+
margin-top: 8px;
|
|
1746
|
+
padding-left: 11px;
|
|
1747
|
+
border-left: 2px solid var(--color-border-muted, #d8dee4);
|
|
1748
|
+
overflow-wrap: anywhere;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
.pr-helper-issue-technical-title {
|
|
1752
|
+
font-size: 13px;
|
|
1753
|
+
font-weight: 600;
|
|
1754
|
+
margin: 0 0 4px;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.pr-helper-issue-original { margin-bottom: 10px; }
|
|
1758
|
+
.pr-helper-issue-original h5 { margin: 0 0 4px; }
|
|
1759
|
+
|
|
1760
|
+
.pr-helper-issue-original-title {
|
|
1761
|
+
font-size: 13px;
|
|
1762
|
+
font-weight: 600;
|
|
1763
|
+
margin: 0 0 4px;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1593
1766
|
.pr-helper-question-actions {
|
|
1594
1767
|
display: flex;
|
|
1595
1768
|
align-items: center;
|