prreviewbuddy 0.55.1 → 0.55.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as relativeTime$1, $n as AGENT_IDS, $t as kissDecisionRefusal, A as createPrState, An as readQueue, At as checkCodeFreshness, B as toneOf, Bn as WITHHELD_REASON, C as readEvents, Cn as offersLifecycle, Ct as statedWorkspacePort, D as announceJob, Dn as queueAddMany, Dt as forgeFor, E as telemetryUploadConsent, En as queueAdd, F as markAllRead, Fn as questionsOutstanding, Ft as endedAt, G as isLongDescription, Gn as git, H as withUsageRecorded, Ht as phasesFor, I as markRead, In as reviewerDispositions, It as fail, J as ReviewBeingDeletedError, Jn as clearAgentPreference, Jt as decideKissRecommendation, K as asString, L as pruneNotifications, Ln as processDiscussion, Lt as isTerminal, M as eventName, Mn as isIssueOutstanding, Mt as followedBranch, N as headline, Nn as isQuestionOutstanding, Nt as reviewedShaOf, O as branchOf, On as queueRemove, Ot as remoteHost, P as listNotifications, Pn as issuesOutstanding, Pt as allJobs, Qn as STORE_ROOT, Qt as indexReviewGroups, R as readNotification, Rn as recommendationsOf, Rt as loadJob, Sn as lifecyclePill, St as resolveWorkspacePort, T as recordTelemetryUploadConsent, Tn as sectionOf, Tt as claimRequestUrl, U as EXPLAIN_SIMPLY_PROMPT, Ut as progressSteps, V as unreadCount$1, Vn as kissOutcome, Vt as isLiveJob, W as hasAuthorContent, Wn as isCommitReview, Wt as purposeOf, X as readMarker, Xn as writeAgentPreference, Xt as groupByLineage, Y as MANAGED_ROOT, Yn as readAgentPreference, Yt as followedRefName, Z as removeWorktree, Zt as indexKeptIds, _ as agentEnvOf, _n as LIFECYCLES, _t as feedbackUrl, a as wasBlocked, ar as AgentUnavailableError, b as reanalyseReview, bn as lifecycleAction, bt as MIN_WORKSPACE_PORT, c as RefreshUnavailableError, cn as presentedKissRunOf, ct as readServerRecord, d as refreshPrContext, dn as reviewedCommit, dt as spawnServer, en as kissDecisionsOf, et as activateQueued, f as isUnchanged, gn as workspaceStamps, h as liveUpdateFor, hn as workspaceRevision, ht as BUILD_VERSION, i as liveJobsFor, in as loadWorkspace, ir as AgentCancelledError, it as readIndexToken, j as createPrUpdate, jn as doneVerb, jt as checkReview, k as subjectOf, kn as queueStamp, kt as fillFileUrlTemplate, l as mergeUpdateInto, ln as previousKissResult, m as UpdateAlreadyRunningError, mn as touchWorkspace, mt as writeServerRecord, n as deleteReview, nr as agentFor, o as discardJob, on as mutateWorkspace, ot as clearServerRecord, p as askCheckout, q as extractJsonText, qn as resolveModel, qt as isClaimed, r as lineageIds, rn as lineagePosition, rt as settleQueue, s as FETCH_TIMEOUT_MS, t as deleteLineage, tn as latestKissRun, tt as compactQueue, u as updateReview, un as recentWorkspaces, ut as serverEntryExists, vn as activityLabel, vt as DEFAULT_WORKSPACE_PORT, w as record, wn as parseLifecycle, wt as writePortPreference, x as runJob, xn as lifecycleChip, xt as parseWorkspacePort, y as startKissJob, yn as isLifecycle, yt as MAX_WORKSPACE_PORT, z as removeNotification, zn as describeAuthorship, zt as saveJob } from "./delete_review-B2bqhgtx.js";
1
+ import { $ as extractJsonText, $n as resolveModel, $t as isClaimed, A as createPrState, An as parseLifecycle, At as writePortPreference, B as toneOf, Bn as issuesOutstanding, Bt as allJobs, C as readEvents, Cn as LIFECYCLES, Ct as feedbackUrl, D as announceJob, Dn as lifecycleChip, Dt as parseWorkspacePort, E as telemetryUploadConsent, En as lifecycleAction, Et as MIN_WORKSPACE_PORT, F as markAllRead, Fn as queueStamp, Ft as fillFileUrlTemplate, G as authorPreview, Gn as describeAuthorship, Gt as saveJob, H as withUsageRecorded, Hn as reviewerDispositions, Ht as fail, I as markRead, In as readQueue, It as checkCodeFreshness, J as CODE_BLOCK_CLASS, Jt as phasesFor, K as hasAuthorContent, Kn as WITHHELD_REASON, L as pruneNotifications, Ln as doneVerb, Lt as checkReview, M as eventName, Mn as queueAdd, N as headline, Nn as queueAddMany, Nt as forgeFor, O as branchOf, On as lifecyclePill, Ot as resolveWorkspacePort, P as listNotifications, Pn as queueRemove, Pt as remoteHost, Q as asString, R as readNotification, Rn as isIssueOutstanding, Rt as followedBranch, Sn as workspaceStamps, T as recordTelemetryUploadConsent, Tn as isLifecycle, Tt as MAX_WORKSPACE_PORT, U as EXPLAIN_SIMPLY_PROMPT, Un as processDiscussion, Ut as isTerminal, V as unreadCount$1, Vn as questionsOutstanding, Vt as endedAt, W as UNESTABLISHED_MOTIVATION, Wn as recommendationsOf, Wt as loadJob, X as escapeHtml$1, Xn as isCommitReview, Xt as purposeOf, Y as CODE_COPY_CLASS, Yt as progressSteps, Z as renderInlineText, Zn as git, _ as agentEnvOf, _n as reviewedCommit, _t as spawnServer, a as wasBlocked, an as kissDecisionRefusal, ar as AGENT_IDS, at as relativeTime$1, b as reanalyseReview, bn as touchWorkspace, bt as writeServerRecord, c as RefreshUnavailableError, cr as agentFor, d as refreshPrContext, dr as AgentUnavailableError, en as decideKissRecommendation, er as clearAgentPreference, et as ReviewBeingDeletedError, f as isUnchanged, fn as mutateWorkspace, ft as clearServerRecord, gn as recentWorkspaces, gt as serverEntryExists, h as liveUpdateFor, hn as previousKissResult, i as liveJobsFor, in as indexReviewGroups, ir as STORE_ROOT, j as createPrUpdate, jn as sectionOf, jt as claimRequestUrl, k as subjectOf, kn as offersLifecycle, kt as statedWorkspacePort, l as mergeUpdateInto, ln as lineagePosition, lt as settleQueue, m as UpdateAlreadyRunningError, mn as presentedKissRunOf, mt as readServerRecord, n as deleteReview, nn as groupByLineage, nr as writeAgentPreference, nt as readMarker, o as discardJob, on as kissDecisionsOf, ot as activateQueued, p as askCheckout, q as renderMarkdown, qn as kissOutcome, qt as isLiveJob, r as lineageIds, rn as indexKeptIds, rt as removeWorktree, s as FETCH_TIMEOUT_MS, sn as latestKissRun, st as compactQueue, t as deleteLineage, tn as followedRefName, tr as readAgentPreference, tt as MANAGED_ROOT, u as updateReview, un as loadWorkspace, ur as AgentCancelledError, ut as readIndexToken, w as record, wn as activityLabel, wt as DEFAULT_WORKSPACE_PORT, x as runJob, xn as workspaceRevision, xt as BUILD_VERSION, y as startKissJob, z as removeNotification, zn as isQuestionOutstanding, zt as reviewedShaOf } from "./delete_review-DEvRanb6.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";
@@ -233,14 +233,6 @@ function readingThemeLine(sections, sectionId) {
233
233
  return section ? `The reviewer is currently reading the "${section.title}" theme. Answer with that in view, but do not ignore the rest of the change if it bears on the question.` : null;
234
234
  }
235
235
  //#endregion
236
- //#region ../../packages/review-harness/src/domain/utils/html.ts
237
- function escapeHtml$1(value) {
238
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
239
- }
240
- function renderInlineText(value) {
241
- return escapeHtml$1(value).replace(/`([^`\n]+)`/g, "<code>$1</code>");
242
- }
243
- //#endregion
244
236
  //#region ../../packages/review-harness/src/workspace/reviews_index.ts
245
237
  /**
246
238
  * The sections of the list, top to bottom.
@@ -980,139 +972,6 @@ function formatRelativeTime(ts, now = Date.now()) {
980
972
  return `${days} day${days === 1 ? "" : "s"} ago`;
981
973
  }
982
974
  //#endregion
983
- //#region ../../packages/review-harness/src/domain/utils/code_block.ts
984
- var CODE_BLOCK_CLASS = "pr-helper-code-block";
985
- var CODE_COPY_CLASS = "pr-helper-code-copy";
986
- //#endregion
987
- //#region ../../packages/review-harness/src/domain/utils/markdown.ts
988
- var BLOCK_MARKER = "\0";
989
- var INLINE_MARKER = "";
990
- var BLOCK_TOKEN = /\u0000(\d+)\u0000/g;
991
- var BLOCK_TOKEN_LINE = /^\u0000\d+\u0000$/;
992
- var INLINE_TOKEN = /\u0001(\d+)\u0001/g;
993
- var SAFE_HREF = /^(?:https?:\/\/|#)/i;
994
- function renderMarkdown(value) {
995
- const { text, blocks } = extractFencedBlocks(value.replace(/[\u0000\u0001]/g, ""));
996
- return restoreFencedBlocks(renderBlocks(text), blocks);
997
- }
998
- function extractFencedBlocks(value) {
999
- const blocks = [];
1000
- const lines = value.split("\n");
1001
- const out = [];
1002
- let i = 0;
1003
- while (i < lines.length) {
1004
- const opening = lines[i].match(/^\s*(`{3,}|~{3,})\s*[\w+#.-]*\s*$/);
1005
- if (!opening) {
1006
- out.push(lines[i]);
1007
- i += 1;
1008
- continue;
1009
- }
1010
- const closing = new RegExp(`^\\s*${opening[1][0]}{3,}\\s*$`);
1011
- const body = [];
1012
- i += 1;
1013
- while (i < lines.length && !closing.test(lines[i])) {
1014
- body.push(lines[i]);
1015
- i += 1;
1016
- }
1017
- i += 1;
1018
- out.push(`${BLOCK_MARKER}${blocks.length}${BLOCK_MARKER}`);
1019
- blocks.push(body.join("\n"));
1020
- }
1021
- return {
1022
- text: out.join("\n"),
1023
- blocks
1024
- };
1025
- }
1026
- function restoreFencedBlocks(html, blocks) {
1027
- return html.replace(BLOCK_TOKEN, (_match, index) => `<div class="${CODE_BLOCK_CLASS}"><pre><code>${escapeHtml$1(blocks[Number(index)])}</code></pre><button type="button" class="${CODE_COPY_CLASS}" aria-label="Copy code">Copy</button></div>`);
1028
- }
1029
- function renderBlocks(text) {
1030
- const out = [];
1031
- let paragraph = [];
1032
- let quote = [];
1033
- let list = null;
1034
- const flush = () => {
1035
- if (paragraph.length) out.push(`<p>${renderInline(paragraph.join(" "))}</p>`);
1036
- if (quote.length) out.push(`<blockquote>${renderInline(quote.join(" "))}</blockquote>`);
1037
- if (list) out.push(`<${list.tag}>${list.items.map((item) => `<li>${renderInline(item)}</li>`).join("")}</${list.tag}>`);
1038
- paragraph = [];
1039
- quote = [];
1040
- list = null;
1041
- };
1042
- for (const raw of text.split("\n")) {
1043
- const line = raw.trim();
1044
- if (!line) {
1045
- flush();
1046
- continue;
1047
- }
1048
- if (BLOCK_TOKEN_LINE.test(line)) {
1049
- flush();
1050
- out.push(line);
1051
- continue;
1052
- }
1053
- if (/^(?:-{3,}|\*{3,}|_{3,})$/.test(line)) {
1054
- flush();
1055
- out.push("<hr>");
1056
- continue;
1057
- }
1058
- const heading = line.match(/^(#{1,6})\s+(.+)$/);
1059
- if (heading) {
1060
- flush();
1061
- const level = Math.min(heading[1].length, 3) + 3;
1062
- out.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
1063
- continue;
1064
- }
1065
- const bullet = line.match(/^[-*+]\s+(.+)$/);
1066
- if (bullet) {
1067
- if (list?.tag !== "ul") {
1068
- flush();
1069
- list = {
1070
- tag: "ul",
1071
- items: []
1072
- };
1073
- }
1074
- list.items.push(bullet[1]);
1075
- continue;
1076
- }
1077
- const ordered = line.match(/^\d{1,9}[.)]\s+(.+)$/);
1078
- if (ordered) {
1079
- if (list?.tag !== "ol") {
1080
- flush();
1081
- list = {
1082
- tag: "ol",
1083
- items: []
1084
- };
1085
- }
1086
- list.items.push(ordered[1]);
1087
- continue;
1088
- }
1089
- const quoted = line.match(/^>\s?(.*)$/);
1090
- if (quoted) {
1091
- if (!quote.length) flush();
1092
- quote.push(quoted[1]);
1093
- continue;
1094
- }
1095
- if (list && list.items.length) {
1096
- list.items[list.items.length - 1] += ` ${line}`;
1097
- continue;
1098
- }
1099
- if (quote.length) {
1100
- quote[quote.length - 1] += ` ${line}`;
1101
- continue;
1102
- }
1103
- paragraph.push(line);
1104
- }
1105
- flush();
1106
- return out.join("");
1107
- }
1108
- function renderInline(value) {
1109
- const codeSpans = [];
1110
- return escapeHtml$1(value).replace(/`([^`\n]+)`/g, (_match, code) => {
1111
- codeSpans.push(code);
1112
- return `${INLINE_MARKER}${codeSpans.length - 1}${INLINE_MARKER}`;
1113
- }).replace(/(?<!!)\[([^\]\n]+)\]\(([^)\s]+)\)/g, (match, label, href) => SAFE_HREF.test(href) ? `<a href="${href}" target="_blank" rel="noopener noreferrer">${label}</a>` : match).replace(/\*\*([^*\n]+)\*\*/g, "<strong>$1</strong>").replace(/__([^_\n]+)__/g, "<strong>$1</strong>").replace(/(^|[^*\w])\*([^*\n]+)\*/g, "$1<em>$2</em>").replace(/(^|[^_\w])_([^_\n]+)_(?![\w_])/g, "$1<em>$2</em>").replace(INLINE_TOKEN, (_match, index) => `<code>${codeSpans[Number(index)]}</code>`);
1114
- }
1115
- //#endregion
1116
975
  //#region ../../packages/review-harness/src/domain/ui/components/intent_note.ts
1117
976
  /**
1118
977
  * The Overview's unclear motive, kept in step with its Intent question.
@@ -1124,7 +983,7 @@ function renderInline(value) {
1124
983
  * Marking a question answered records that it was answered, never what the answer was, so nothing
1125
984
  * here touches the stored motivation. It only says the analysis is older than the answer.
1126
985
  */
1127
- var INTENT_NOTE_COPY = "This question is marked answered. If the answer was added to the PR description or conversation, run the review again to incorporate it.";
986
+ var INTENT_NOTE_COPY = "This question has been marked answered. Run the review again after adding the answer to the PR description.";
1128
987
  var RUN_REVIEW_AGAIN = "Run review again";
1129
988
  //#endregion
1130
989
  //#region ../../packages/review-harness/src/domain/ui/components/author_context_toggle.ts
@@ -1134,9 +993,13 @@ var RUN_REVIEW_AGAIN = "Run review again";
1134
993
  * Its own module because both hosts call it from their own click handling, and the workspace
1135
994
  * client should not have to bundle the whole tab renderer to do it. The markup it acts on is
1136
995
  * written by `renderAuthorContext` in review_tabs.ts, from the same constants.
996
+ *
997
+ * The preview and the full description are two elements, and one of them is always `hidden`. A
998
+ * closed description is not in the tab order or the accessibility tree, so nothing focusable can
999
+ * sit out of sight inside it.
1137
1000
  */
1138
- var AUTHOR_CONTEXT_COLLAPSED_CLASS = "pr-helper-author-context-collapsed";
1139
- var SHOW_FULL_DESCRIPTION = "Show full description";
1001
+ var AUTHOR_CONTEXT_PREVIEW_ID = "pr-helper-author-context-preview";
1002
+ var SHOW_FULL_DESCRIPTION = "View full PR description";
1140
1003
  var AUTHOR_CONTEXT_SECTION_ID = "pr-helper-author-context";
1141
1004
  //#endregion
1142
1005
  //#region ../../packages/review-harness/src/domain/utils/model_label.ts
@@ -1785,20 +1648,24 @@ function renderOverviewActions(session, options) {
1785
1648
  * template: an empty "Author's context" would read as the author having said nothing, when on a
1786
1649
  * local branch there was never anyone to ask.
1787
1650
  *
1788
- * Long descriptions are clamped rather than cut. The whole text is in the page, checklists and
1789
- * testing notes included, and the button only changes how much of it shows.
1651
+ * Opens on a bounded preview of the first thing the author wrote (`authorPreview`), so it sits
1652
+ * beside the other two narrative sections rather than outweighing them. The whole description is in the page, checklists and testing notes
1653
+ * included, and the button swaps the preview for it. Nothing is cut from either.
1790
1654
  */
1655
+ var GITHUB_MARKDOWN = { hideHtmlComments: true };
1791
1656
  function renderAuthorContext(session) {
1792
1657
  const description = session.metadata.authorDescription;
1793
1658
  if (!hasAuthorContent(description)) return "";
1794
1659
  const id = "pr-helper-author-context-body";
1795
- const long = isLongDescription(description);
1796
- const toggle = long ? `<button type="button" class="pr-helper-author-context-toggle" aria-expanded="false" aria-controls="${id}" data-author-context-toggle>${SHOW_FULL_DESCRIPTION}</button>` : "";
1660
+ const preview = authorPreview(description);
1661
+ const omitted = preview?.codeOmitted ? "<p class=\"pr-helper-author-context-omitted\">Code block included</p>" : "";
1662
+ const opening = preview === null ? "" : `<div class="pr-helper-author-context-body pr-helper-markdown" id="${AUTHOR_CONTEXT_PREVIEW_ID}">${preview.markdown ? renderMarkdown(preview.markdown, GITHUB_MARKDOWN) : ""}${omitted}</div>
1663
+ <button type="button" class="pr-helper-author-context-toggle" aria-expanded="false" aria-controls="${id}" data-author-context-toggle>${SHOW_FULL_DESCRIPTION}</button>`;
1797
1664
  return `
1798
1665
  <section class="pr-helper-author-context" id="${AUTHOR_CONTEXT_SECTION_ID}" aria-labelledby="pr-helper-author-context-heading">
1799
1666
  <h4 class="pr-helper-overview-heading" id="pr-helper-author-context-heading" tabindex="-1">Author's context</h4>
1800
- <div class="pr-helper-author-context-body pr-helper-markdown${long ? ` ${AUTHOR_CONTEXT_COLLAPSED_CLASS}` : ""}" id="${id}">${renderMarkdown(description)}</div>
1801
- ${toggle}
1667
+ <div class="pr-helper-author-context-body pr-helper-markdown" id="${id}"${preview === null ? "" : " hidden"}>${renderMarkdown(description, GITHUB_MARKDOWN)}</div>
1668
+ ${opening}
1802
1669
  </section>
1803
1670
  `;
1804
1671
  }
@@ -1814,9 +1681,9 @@ function renderEvidenceFile(item) {
1814
1681
  return `<span class="pr-helper-why-evidence-unverified"><code>${escapeHtml$1(item.file)}</code> · unverified path</span>`;
1815
1682
  }
1816
1683
  var MOTIVATION_LABELS = {
1817
- stated: "Stated by the author",
1818
- inferred: "Likely motivation",
1819
- unclear: "Motivation unclear"
1684
+ stated: "Author's reason",
1685
+ inferred: "Possible reason",
1686
+ unclear: "Reason unclear"
1820
1687
  };
1821
1688
  var EVIDENCE_LABELS = {
1822
1689
  test: "Test",
@@ -1829,8 +1696,8 @@ var EVIDENCE_LABELS = {
1829
1696
  /**
1830
1697
  * Why the change exists, and how the review knows.
1831
1698
  *
1832
- * Every status shows what supports it: a stated motive its quote, an inferred one its evidence, an
1833
- * unclear one the question that would settle it. `verifyMotivation` guarantees each is there, so a
1699
+ * Every status carries what supports it: a stated motive its quote (one click away), an inferred
1700
+ * one its evidence, an unclear one the question that would settle it. `verifyMotivation` guarantees each is there, so a
1834
1701
  * status rendered without its support would be a stored result this version did not write.
1835
1702
  *
1836
1703
  * Nothing for a result with no motivation, which covers every review made before the field and
@@ -1839,40 +1706,57 @@ var EVIDENCE_LABELS = {
1839
1706
  function renderWhyThisChange(motivation, session, questions) {
1840
1707
  if (!motivation) return "";
1841
1708
  if (motivation.status === "unclear") return renderUnclearMotive(motivation, session, questions);
1709
+ let summary = renderInlineText(motivation.summary);
1842
1710
  let support = "";
1843
1711
  if (motivation.status === "stated" && motivation.sourceQuote) {
1844
1712
  const source = hasAuthorContent(session.metadata.authorDescription) ? `<button type="button" class="pr-helper-why-link" data-author-context-reveal>From Author's context</button>` : "";
1845
- support = `<blockquote class="pr-helper-why-quote">${renderInlineText(motivation.sourceQuote)}</blockquote>${source}`;
1846
- } else if (motivation.status === "inferred" && motivation.evidence?.length) support = `<ul class="pr-helper-why-evidence">${motivation.evidence.map((item) => `<li><span class="pr-helper-why-evidence-kind">${EVIDENCE_LABELS[item.kind]}</span> ${renderInlineText(item.detail)}${renderEvidenceFile(item)}</li>`).join("")}</ul>`;
1713
+ support = `<details class="pr-helper-why-source"><summary>Author's words</summary><blockquote class="pr-helper-why-quote">${renderInlineText(motivation.sourceQuote)}</blockquote>${source}</details>`;
1714
+ } else if (motivation.status === "inferred") {
1715
+ summary = renderInlineText(possibleReason(motivation.summary, hasAuthorContent(session.metadata.authorDescription)));
1716
+ if (motivation.evidence?.length) support = `<ul class="pr-helper-why-evidence">${motivation.evidence.map((item) => `<li><span class="pr-helper-why-evidence-kind">${EVIDENCE_LABELS[item.kind]}</span> ${renderInlineText(item.detail)}${renderEvidenceFile(item)}</li>`).join("")}</ul>`;
1717
+ }
1847
1718
  return `
1848
- <section class="pr-helper-why" aria-labelledby="pr-helper-why-heading">
1719
+ <section class="pr-helper-why pr-helper-why-${motivation.status}" aria-labelledby="pr-helper-why-heading">
1849
1720
  <h4 class="pr-helper-overview-heading" id="pr-helper-why-heading">Why this change?</h4>
1850
1721
  <p class="pr-helper-why-status pr-helper-why-status-${motivation.status}">${MOTIVATION_LABELS[motivation.status]}</p>
1851
- <p class="pr-helper-why-summary">${renderInlineText(motivation.summary)}</p>
1722
+ <p class="pr-helper-why-summary">${summary}</p>
1852
1723
  ${support}
1853
1724
  </section>
1854
1725
  `;
1855
1726
  }
1856
1727
  /**
1728
+ * An inferred reason, said as one: where it came from, then the model's own hedged sentence.
1729
+ *
1730
+ * The prompt asks for "This appears to...", which reads on after a comma once its first letter is
1731
+ * lowered. Anything else keeps its wording and capital after a colon, so a sentence that opens on
1732
+ * a name (`GitHub`, `ErrorSeverity`) is never rewritten. With no description to speak of, nothing
1733
+ * is said about one.
1734
+ */
1735
+ function possibleReason(summary, hasDescription) {
1736
+ const lead = hasDescription ? "The PR description doesn't give a clear reason. " : "";
1737
+ return /^This\s/.test(summary) ? `${lead}Based on the change itself, t${summary.slice(1)}` : `${lead}Based on the change itself: ${summary}`;
1738
+ }
1739
+ /**
1857
1740
  * An unclear motive, and what became of its question.
1858
1741
  *
1859
- * Once the question is answered the stored motivation is still the analysis's, so it is shown as
1860
- * that: unclear when analysed. Only a new analysis that reads the answer may replace it, and the
1861
- * button runs one. Both halves are rendered and one is hidden, so the workspace can switch them
1862
- * when a question is marked answered in place (`syncIntentNote`).
1742
+ * Always our own sentence, never the model's: an unclear motive is the absence of a reason, and
1743
+ * the reader needs only that and what to ask. Once the question is answered the stored motivation
1744
+ * is still the analysis's, so the button runs a new one that can read the answer. Both halves are
1745
+ * rendered and one is hidden, so the workspace can switch them when a question is marked answered
1746
+ * in place (`syncIntentNote`).
1863
1747
  */
1864
1748
  function renderUnclearMotive(motivation, session, questions) {
1865
1749
  const intent = questions.find((q) => q.category === "intent");
1866
1750
  const answered = intent ? !isQuestionOutstanding(intent, new Set(session.answeredQuestionIds ?? [])) : false;
1867
1751
  const hide = (yes) => yes ? " hidden" : "";
1868
1752
  return `
1869
- <section class="pr-helper-why" aria-labelledby="pr-helper-why-heading"${intent ? ` data-intent-question="${escapeHtml$1(intent.id)}"` : ""}>
1753
+ <section class="pr-helper-why pr-helper-why-unclear" aria-labelledby="pr-helper-why-heading"${intent ? ` data-intent-question="${escapeHtml$1(intent.id)}"` : ""}>
1870
1754
  <h4 class="pr-helper-overview-heading" id="pr-helper-why-heading">Why this change?</h4>
1871
1755
  <p class="pr-helper-why-status pr-helper-why-status-unclear" data-intent-open${hide(answered)}>${MOTIVATION_LABELS.unclear}</p>
1872
- <p class="pr-helper-why-status pr-helper-why-status-unclear" data-intent-answered${hide(!answered)}>Motivation was unclear when analysed</p>
1873
- <p class="pr-helper-why-summary">${renderInlineText(motivation.summary)}</p>
1874
- ${motivation.authorQuestion ? `<p class="pr-helper-why-question">${renderInlineText(motivation.authorQuestion)}</p>` : ""}
1875
- <button type="button" class="pr-helper-why-link" data-overview-nav="questions" data-intent-open${hide(answered)}>Asked in Questions, under Intent</button>
1756
+ <p class="pr-helper-why-status pr-helper-why-status-answered" data-intent-answered${hide(!answered)}>Answer available</p>
1757
+ <p class="pr-helper-why-summary" data-intent-open${hide(answered)}>${UNESTABLISHED_MOTIVATION}</p>
1758
+ ${motivation.authorQuestion ? `<button type="button" class="pr-helper-why-ask" data-overview-nav="questions" data-intent-open${hide(answered)}>Ask the author</button>
1759
+ <p class="pr-helper-why-question">\u201c${renderInlineText(motivation.authorQuestion)}\u201d</p>` : ""}
1876
1760
  <div class="pr-helper-why-answered" data-intent-answered${hide(!answered)}>
1877
1761
  <p class="pr-helper-why-answered-note">${INTENT_NOTE_COPY}</p>
1878
1762
  <button type="button" class="pr-helper-why-rerun" data-run-review-again>${RUN_REVIEW_AGAIN}</button>
@@ -1923,7 +1807,7 @@ function renderFileLocation(file, className, line) {
1923
1807
  const suffix = line ? `:${line}` : "";
1924
1808
  const dirLine = dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : "";
1925
1809
  const lineAttr = line ? ` data-line="${line}"` : "";
1926
- return `<span class="${className}" data-file="${escapeHtml$1(file)}"${lineAttr} title="${escapeHtml$1(file + suffix)}">${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name + suffix)}</span></span>`;
1810
+ return `<span class="${className}" data-file="${escapeHtml$1(file)}"${lineAttr}>${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name + suffix)}</span></span>`;
1927
1811
  }
1928
1812
  /**
1929
1813
  * The files a finding rests on that this change does not touch.
@@ -1945,7 +1829,7 @@ function renderRelatedFiles(files) {
1945
1829
  return files.map((file) => {
1946
1830
  const { dir, name } = splitFilePath(file);
1947
1831
  const dirLine = dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : "";
1948
- return `<span class="prb-file-related" data-file="${escapeHtml$1(file)}" title="${escapeHtml$1(file)}">Related file: ${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name)}</span> · not changed</span>`;
1832
+ return `<span class="prb-file-related" data-file="${escapeHtml$1(file)}">Related file: ${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name)}</span> · not changed</span>`;
1949
1833
  }).join("");
1950
1834
  }
1951
1835
  function discussionDetails(session) {
@@ -1970,7 +1854,8 @@ function plural$1(count, word) {
1970
1854
  function renderExistingDiscussion(ref) {
1971
1855
  if (!ref) return "";
1972
1856
  const state = ref.status === "resolved" ? "thread since marked resolved" : "thread still unresolved";
1973
- const label = `<strong class="pr-helper-issue-discussion-lead">${escapeHtml$1(ref.relation === "related" ? "Related discussion" : "Already raised")}</strong>${escapeHtml$1(` by @${ref.raisedBy} · ${state}`)}`;
1857
+ const lead = ref.relation === "related" ? "Related discussion" : "Already raised";
1858
+ const label = `<strong class="pr-helper-issue-discussion-lead">${escapeHtml$1(lead)}</strong>${escapeHtml$1(` by @${ref.raisedBy} · ${state}`)}`;
1974
1859
  const body = ref.url ? `<a class="pr-helper-issue-discussion-link" href="${escapeHtml$1(ref.url)}" data-discussion-url="${escapeHtml$1(ref.url)}" rel="noopener noreferrer">${label}</a>` : label;
1975
1860
  return `<p class="pr-helper-issue-discussion pr-helper-issue-discussion-${escapeHtml$1(ref.status)}">${body}</p>`;
1976
1861
  }
@@ -2098,7 +1983,7 @@ function renderGuideSectionCard(section, counts, activeGuidedSectionId, status,
2098
1983
  const focus = f.focus ? `<div class="pr-helper-guide-file-focus">${renderInlineText(f.focus)}</div>` : "";
2099
1984
  const { dir, name } = splitFilePath(f.path);
2100
1985
  const dirLine = dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : "";
2101
- return `<li class="pr-helper-guide-file" data-file="${escapeHtml$1(f.path)}" data-guide-section="${escapeHtml$1(section.id)}" title="${escapeHtml$1(f.path)}">${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name)}</span>${focus}</li>`;
1986
+ return `<li class="pr-helper-guide-file" data-file="${escapeHtml$1(f.path)}" data-guide-section="${escapeHtml$1(section.id)}">${dirLine}<span class="pr-helper-path-name">${escapeHtml$1(name)}</span>${focus}</li>`;
2102
1987
  }).join("") || "<li class=\"pr-helper-review-empty\">No files listed.</li>";
2103
1988
  const checklist = section.checklist.map((item) => `<li>${renderInlineText(item)}</li>`).join("") || "<li>None</li>";
2104
1989
  const [minMinutes, maxMinutes] = estimateMinutesForWeight(section.reviewWeight);
@@ -2647,13 +2532,17 @@ function renderQuestionsTab(session, investigatingId, options) {
2647
2532
  /**
2648
2533
  * The panel's contents are plain sentences, escaped here, because every caller is passing facts
2649
2534
  * rather than markup and one of them is a model's own words about a file it cited.
2535
+ *
2536
+ * `corrections` is the exception: what the harness changed in the model's output, as markup the
2537
+ * caller has already escaped (a removed path is set as code). It lives here, behind the control,
2538
+ * because it describes how the result was made rather than the result.
2650
2539
  */
2651
- function renderAnalysisDetails(items) {
2652
- if (items.length === 0) return "";
2540
+ function renderAnalysisDetails(items, corrections = []) {
2541
+ if (items.length === 0 && corrections.length === 0) return "";
2653
2542
  return `<details class="prb-doc-provenance" data-analysis-details>
2654
2543
  <summary class="prb-doc-provenance-summary">Analysis details</summary>
2655
2544
  <div class="prb-doc-provenance-panel">
2656
- <ul>${items.map((item) => `<li>${escapeHtml$1(item)}</li>`).join("")}</ul>
2545
+ ${items.length > 0 ? `<ul>${items.map((item) => `<li>${escapeHtml$1(item)}</li>`).join("")}</ul>` : ""}${corrections.length > 0 ? `<p class="prb-doc-provenance-heading">What the harness corrected</p><ul data-corrections>${corrections.map((note) => `<li>${note}</li>`).join("")}</ul>` : ""}
2657
2546
  </div>
2658
2547
  </details>`;
2659
2548
  }
@@ -3603,7 +3492,8 @@ function renderAttribution(attribution, processed) {
3603
3492
  if (!attribution || processed.all.length === 0) return "";
3604
3493
  const { findings, matched } = attribution;
3605
3494
  if (findings === 0) return "";
3606
- return `<p class="prb-pr-attribution" data-matched="${matched}">${escapeHtml$1(matched === 0 ? `None of the ${findings} finding${findings === 1 ? "" : "s"} in this review matched a thread here. The comparison was made; nothing lined up.` : `${matched} of ${findings} finding${findings === 1 ? "" : "s"} in this review ${matched === 1 ? "is" : "are"} matched to a thread here. Each one says so on the Issues view.`)}</p>`;
3495
+ const text = matched === 0 ? `None of the ${findings} finding${findings === 1 ? "" : "s"} in this review matched a thread here. The comparison was made; nothing lined up.` : `${matched} of ${findings} finding${findings === 1 ? "" : "s"} in this review ${matched === 1 ? "is" : "are"} matched to a thread here. Each one says so on the Issues view.`;
3496
+ return `<p class="prb-pr-attribution" data-matched="${matched}">${escapeHtml$1(text)}</p>`;
3607
3497
  }
3608
3498
  /**
3609
3499
  * Said plainly, because the alternative is a reviewer assuming their findings were checked
@@ -3688,10 +3578,11 @@ function renderDecisions(processed) {
3688
3578
  *
3689
3579
  * `renderMarkdown` escapes before it parses, so nothing in a comment can become markup this page
3690
3580
  * did not emit, and its href allow-list means a link is only ever a link. It was written for model
3691
- * output injected into github.com, which is a harsher setting than this one.
3581
+ * output injected into github.com, which is a harsher setting than this one. HTML comments are
3582
+ * hidden, as GitHub hides them.
3692
3583
  */
3693
3584
  function renderBody(body) {
3694
- return `<div class="prb-pr-comment-body prb-pr-md">${renderMarkdown(body)}</div>`;
3585
+ return `<div class="prb-pr-comment-body prb-pr-md">${renderMarkdown(body, { hideHtmlComments: true })}</div>`;
3695
3586
  }
3696
3587
  function renderBucket(title, note, threads) {
3697
3588
  if (threads.length === 0) return "";
@@ -4435,8 +4326,9 @@ function renderEvidence(recommendation, rest) {
4435
4326
  </div>` : ""
4436
4327
  ].filter(Boolean);
4437
4328
  if (body.length === 0) return "";
4329
+ const files = rest.length > 0 ? ` · ${rest.length} more ${rest.length === 1 ? "file" : "files"}` : "";
4438
4330
  return `<details class="prb-kiss-evidence" data-kiss-evidence>
4439
- <summary class="prb-kiss-evidence-title">Evidence &amp; trade-offs${escapeHtml$1(rest.length > 0 ? ` · ${rest.length} more ${rest.length === 1 ? "file" : "files"}` : "")}</summary>
4331
+ <summary class="prb-kiss-evidence-title">Evidence &amp; trade-offs${escapeHtml$1(files)}</summary>
4440
4332
  ${body.join("\n ")}
4441
4333
  </details>`;
4442
4334
  }
@@ -4502,7 +4394,7 @@ function renderLocationList(locations) {
4502
4394
  return `<ul class="prb-kiss-locations-list">${locations.map((location) => {
4503
4395
  const { dir, name } = splitFilePath(location.path);
4504
4396
  const suffix = location.line ? `:${location.line}` : "";
4505
- return `<li><button type="button" class="prb-kiss-location" data-file="${escapeHtml$1(location.path)}"${location.line ? ` data-line="${location.line}"` : ""}${location.primary ? " data-primary" : ""} aria-expanded="false" title="${escapeHtml$1(location.path + suffix)}">
4397
+ return `<li><button type="button" class="prb-kiss-location" data-file="${escapeHtml$1(location.path)}"${location.line ? ` data-line="${location.line}"` : ""}${location.primary ? " data-primary" : ""} aria-expanded="false">
4506
4398
  ${dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : ""}<span class="prb-kiss-location-row"><span class="pr-helper-path-name">${escapeHtml$1(name + suffix)}</span>${location.unchanged ? "<span class=\"prb-kiss-location-unchanged\">not changed</span>" : ""}</span>
4507
4399
  </button></li>`;
4508
4400
  }).join("")}</ul>`;
@@ -5296,7 +5188,6 @@ function renderReviewDoc(view, sections) {
5296
5188
  const result = session.result;
5297
5189
  const progress = showsProgress(view);
5298
5190
  return `<div class="pr-helper-sidebar prb-pane">
5299
- ${result ? renderDisclosure(view) : ""}
5300
5191
  ${result ? renderStandardViews(view, sections) : ""}
5301
5192
  ${progress ? `<section class="prb-view" data-view="progress" data-progress-pane data-job-phase="${escapeHtml$1(view.job.phase)}" role="status" aria-live="polite">${renderProgressView(view.job, view.changeSet.data.files.length)}</section>` : ""}
5302
5193
  ${renderFilesView(view, sections)}
@@ -5379,7 +5270,7 @@ function renderImplementationEmpty() {
5379
5270
  function renderStandardViews(view, sections) {
5380
5271
  const { session } = view;
5381
5272
  const result = session.result;
5382
- return `<section class="prb-view" data-view="overview">${renderDocHead("Review overview", `${renderAnalysisDetails(analysisDetailItems(session))}${renderFocusToggle("overview", "Overview")}`)}${renderOverviewTab(session, "", {
5273
+ return `<section class="prb-view" data-view="overview">${renderDocHead("Review overview", `${renderAnalysisDetails(analysisDetailItems(session), correctionNotes(view))}${renderFocusToggle("overview", "Overview")}`)}${renderOverviewTab(session, "", {
5383
5274
  diagrams: true,
5384
5275
  detailsInHeader: true
5385
5276
  })}</section>
@@ -5438,7 +5329,8 @@ function renderStandardViews(view, sections) {
5438
5329
  */
5439
5330
  function renderAttributionAge(view) {
5440
5331
  if (!view.prContext) return "";
5441
- return `<p class="prb-doc-note">Any &ldquo;already raised&rdquo; note below describes the pull request as it stood ${escapeHtml$1(relativeTime$1(view.prContext.prContext.discussion.fetchedAt))}, when its discussion was fetched. That includes whether each thread was still open. If someone has replied or resolved a thread since then, this page has no way of knowing. Use <button type="button" class="prb-doc-note-link" data-view-nav="prcontext">Re-check the conversation</button> on the PR context view to bring it up to date.</p>`;
5332
+ const imported = relativeTime$1(view.prContext.prContext.discussion.fetchedAt);
5333
+ return `<p class="prb-doc-note">Any &ldquo;already raised&rdquo; note below describes the pull request as it stood ${escapeHtml$1(imported)}, when its discussion was fetched. That includes whether each thread was still open. If someone has replied or resolved a thread since then, this page has no way of knowing. Use <button type="button" class="prb-doc-note-link" data-view-nav="prcontext">Re-check the conversation</button> on the PR context view to bring it up to date.</p>`;
5442
5334
  }
5443
5335
  /**
5444
5336
  * The link out to this file on the pull request.
@@ -5993,14 +5885,17 @@ function railLink(view, icon, label, badge, note, alsoCurrentOn) {
5993
5885
  </button>`;
5994
5886
  }
5995
5887
  /**
5996
- * What the harness corrected, stated above the review rather than buried in a details element.
5997
- * A guide quietly repaired is a guide trusted more than it deserves.
5888
+ * What the harness corrected, as notes for Analysis details on the Overview.
5889
+ *
5890
+ * Behind the control rather than above the review: the Overview explains the result, and these
5891
+ * explain how it was made. Each correction's effect on the result is already in the result itself
5892
+ * (a removed reference is gone, a refused motive shows as unclear), so the reader acting on the
5893
+ * review loses nothing by not meeting the mechanism first.
5998
5894
  *
5999
5895
  * Corrections only. There is deliberately nothing here about how much of the change travelled
6000
- * inline: that is provenance, it belongs in Analysis details on the Overview, and putting it in a
6001
- * box under this heading would restate a limit that no longer exists as though it did.
5896
+ * inline: that is provenance, which Analysis details already states as a fact of its own.
6002
5897
  */
6003
- function renderDisclosure(view) {
5898
+ function correctionNotes(view) {
6004
5899
  const { audit } = view;
6005
5900
  const notes = [];
6006
5901
  if (audit.unknownPaths.length > 0) {
@@ -6012,26 +5907,28 @@ function renderDisclosure(view) {
6012
5907
  } else if (audit.relocatedFindings > 0) notes.push(`${audit.relocatedFindings} finding${audit.relocatedFindings === 1 ? "" : "s"} pointed at a file that is not in this change. The finding is kept; its location is not.`);
6013
5908
  const motivation = motivationNote(audit.motivationCorrection);
6014
5909
  if (motivation) notes.push(motivation);
6015
- if (notes.length === 0) return "";
6016
- return `<aside class="prb-disclosure">
6017
- <strong>What the harness corrected</strong>
6018
- <ul>${notes.map((note) => `<li>${note}</li>`).join("")}</ul>
6019
- </aside>`;
5910
+ return notes;
6020
5911
  }
6021
5912
  /**
6022
5913
  * A refused motivation, described by what kind of claim it was and never by its wording: that
6023
- * wording is exactly what the check decided the reader should not be shown.
5914
+ * wording, and any rejected quote, is exactly what the check decided the reader should not see.
6024
5915
  *
6025
- * An unclear motive with no question is not mentioned. Withholding it costs the reader nothing,
6026
- * since it said nothing they could act on.
5916
+ * An unclear motive that asked nothing is not mentioned: it claimed nothing, and the question it
5917
+ * now carries is in plain view under Intent.
6027
5918
  */
6028
5919
  function motivationNote(correction) {
6029
5920
  if (!correction || correction.reason === "no-question") return null;
6030
- return `The review gave ${{
6031
- "missing-quote": "that the author had stated why this change was made, without quoting where",
6032
- "unverified-quote": "that the author had stated why this change was made, quoting words that are not in the description",
6033
- "no-evidence": "a likely reason for this change, without evidence for it"
6034
- }[correction.reason]}, ${correction.outcome === "downgraded" ? "so the reason is shown as unclear, with its question to the author under Intent" : "so no reason is shown"}.`;
5921
+ const claim = {
5922
+ "missing-quote": "The analysis attributed a reason for this change to the author, but did not quote where the PR description gives it.",
5923
+ "unverified-quote": "The analysis attributed a reason for this change to the author, but the words it quoted could not be confirmed as the author's own in the PR description.",
5924
+ "no-evidence": "The analysis suggested a likely reason for this change, but gave no evidence for it."
5925
+ }[correction.reason];
5926
+ const outcome = {
5927
+ downgraded: "The reason is shown as unclear, with a question for the author.",
5928
+ withheld: "No reason is shown."
5929
+ }[correction.outcome];
5930
+ const evidence = correction.discardedEvidence?.length ? ` Evidence it gave, not used: ${correction.discardedEvidence.map((item) => `${escapeHtml$1(item.kind)}: ${escapeHtml$1(item.detail)}`).join("; ")}.` : "";
5931
+ return [claim, outcome].filter(Boolean).join(" ") + evidence;
6035
5932
  }
6036
5933
  /**
6037
5934
  * Every changed file, split by whether the guide sends the reviewer there.
@@ -6115,7 +6012,7 @@ function referencedRow(view, file, titles, refs) {
6115
6012
  const counts = fileCounts(refs);
6116
6013
  const named = file.sectionIds.map((id) => titles.get(id)).filter((title) => Boolean(title));
6117
6014
  const by = named.length > 0 ? `<span class="prb-file-referenced-by">Referenced by: ${escapeHtml$1(named.join(", "))}</span>` : "";
6118
- return `<div class="prb-file prb-file-expandable prb-file-referenced" data-file="${escapeHtml$1(file.path)}" role="button" tabindex="0" aria-expanded="false" data-search="${escapeHtml$1(file.path.toLowerCase())}" title="${escapeHtml$1(file.path)}">
6015
+ return `<div class="prb-file prb-file-expandable prb-file-referenced" data-file="${escapeHtml$1(file.path)}" role="button" tabindex="0" aria-expanded="false" data-search="${escapeHtml$1(file.path.toLowerCase())}">
6119
6016
  <span class="prb-file-path">
6120
6017
  ${dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : ""}
6121
6018
  <span class="pr-helper-path-name">${escapeHtml$1(name)}</span>
@@ -6181,7 +6078,7 @@ function fileRow(view, file, refs) {
6181
6078
  const link = readable ? null : prFileLink(view, file.name);
6182
6079
  const openPr = link ? `<a class="prb-file-open prb-file-pr" href="${escapeHtml$1(link.href)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml$1(link.label)}">Pull request</a>` : "";
6183
6080
  const attributes = readable ? ` data-file="${escapeHtml$1(file.name)}" role="button" tabindex="0" aria-expanded="false"` : "";
6184
- return `<div class="prb-file${readable ? " prb-file-expandable" : ""}"${attributes} data-search="${escapeHtml$1(file.name.toLowerCase())}" title="${escapeHtml$1(file.name)}">
6081
+ return `<div class="prb-file${readable ? " prb-file-expandable" : ""}"${attributes} data-search="${escapeHtml$1(file.name.toLowerCase())}">
6185
6082
  <span class="prb-file-status" data-status="${escapeHtml$1(file.status)}">${escapeHtml$1(file.status)}</span>
6186
6083
  <span class="prb-file-path">
6187
6084
  ${dir ? `<span class="pr-helper-path-dir">${escapeHtml$1(dir)}</span>` : ""}
@@ -7778,7 +7675,8 @@ function renderHistoryRow(review, number, latest, now) {
7778
7675
  function renderAnalysisState(analysis) {
7779
7676
  if (analysis === void 0) return "";
7780
7677
  if (analysis.phase === "failed") return analysis.stopped ? "<span class=\"prb-card-analysis\" data-analysis=\"stopped\">Analysis stopped</span>" : "<span class=\"prb-card-analysis\" data-analysis=\"failed\">Analysis failed</span>";
7781
- return `<span class="prb-card-analysis" data-analysis="running"><i></i>${escapeHtml$1(phasesFor({ purpose: analysis.purpose })[analysis.phase].doing)}</span>`;
7678
+ const names = phasesFor({ purpose: analysis.purpose });
7679
+ return `<span class="prb-card-analysis" data-analysis="running"><i></i>${escapeHtml$1(names[analysis.phase].doing)}</span>`;
7782
7680
  }
7783
7681
  /**
7784
7682
  * What this review still has open.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.55.1",
3
+ "version": "0.55.6",
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",
package/static/shell.css CHANGED
@@ -728,7 +728,6 @@ a.prb-author:hover .prb-author-name { color: var(--text-primary); text-decoratio
728
728
  color: var(--pane-ink-muted);
729
729
  line-height: var(--lh-body);
730
730
  }
731
- .prb-pr-empty { max-width: 62ch; }
732
731
  .prb-pr-provenance {
733
732
  display: flex;
734
733
  flex-wrap: wrap;
@@ -1273,26 +1272,16 @@ a.prb-rail-item { text-decoration: none; }
1273
1272
  .prb-doc-provenance-panel ul { margin: 0; padding-left: 16px; }
1274
1273
  .prb-doc-provenance-panel li { margin-bottom: 5px; }
1275
1274
  .prb-doc-provenance-panel li:last-child { margin-bottom: 0; }
1275
+ .prb-doc-provenance-heading {
1276
+ margin: 12px 0 6px;
1277
+ font-weight: 600;
1278
+ color: var(--pane-ink);
1279
+ }
1280
+ .prb-doc-provenance-heading:first-child { margin-top: 0; }
1276
1281
 
1277
1282
  /* A pane is present in the DOM at all times so switching costs no rebuild; only one is shown. */
1278
1283
  .prb-view[hidden] { display: none; }
1279
1284
 
1280
- /* ── Disclosure strip ──
1281
- Anything the harness corrected or could not confirm. Amber, above the review, never buried:
1282
- a guide quietly repaired is a guide trusted more than it deserves. */
1283
- .prb-disclosure {
1284
- margin: 0 0 20px;
1285
- padding: 12px 14px;
1286
- border-radius: 8px;
1287
- border: 1px solid rgba(154, 103, 0, 0.25);
1288
- background: rgba(240, 178, 50, 0.12);
1289
- color: var(--pane-warning);
1290
- font-size: var(--t-meta);
1291
- line-height: var(--lh-body);
1292
- }
1293
- .prb-disclosure strong { font-weight: 600; }
1294
- .prb-disclosure ul { margin: 6px 0 0; padding-left: 18px; }
1295
-
1296
1285
  /* ── File list ── */
1297
1286
  .prb-file-group { margin-bottom: 26px; }
1298
1287
  .prb-file-group h4 {
@@ -3890,7 +3879,6 @@ a.prb-rail-item { text-decoration: none; }
3890
3879
  Says what the view is for and offers the one action that spends a run. Not a dead end, and not a
3891
3880
  button that pretends to be free. */
3892
3881
  .prb-map-empty {
3893
- max-width: 560px;
3894
3882
  margin: 8px 0 20px;
3895
3883
  padding: 18px 20px;
3896
3884
  border: 1px dashed rgba(15, 23, 42, 0.2);