prreviewbuddy 0.82.1 → 0.83.3

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 CHANGED
@@ -1,5 +1,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.83.3
4
+
5
+ **Peek at a collapsed sidebar.** Rest the pointer on the collapsed sidebar, or tab into it, and it
6
+ slides out to full width over the page, with every name and section, until you move away. The page
7
+ underneath stays where it was, and the sidebar stays collapsed.
8
+
9
+ ## 0.83.2
10
+
11
+ **Send feedback is only in the sidebar.** A narrow window no longer draws a Send feedback link in
12
+ the top toolbar; it is in the sidebar, expanded or collapsed, and nowhere else.
13
+
14
+ ## 0.83.1
15
+
16
+ **New review is the sidebar's one purple button, and Collapse sidebar moves to the sidebar's foot.**
17
+ New review is filled in purple wherever it is: the full-width button, the square + of the collapsed
18
+ sidebar, and the + in the top toolbar. Collapse sidebar is now the last row of the sidebar, below
19
+ Send feedback, and expands it again from the same place. While the sidebar is hidden, Show sidebar
20
+ in the top toolbar brings it back, and Send feedback is hidden with it.
21
+
22
+ ## 0.83.0
23
+
24
+ **Start a review from New review in the sidebar, and collapse the sidebar to icons.** A New review
25
+ button, with its shortcut beside it, sits above Reviews in the sidebar on every workspace page. It
26
+ opens the same panel as ⌘P (Ctrl+P on other platforms), which works as before; that panel is now
27
+ called New review too. The button at the far left of the top toolbar collapses the sidebar to a
28
+ column of icons, with New review as a + and each name shown on hover, and expands it again. ⌘K H
29
+ hides the sidebar completely; the same toolbar button then brings it back, and a + in the toolbar
30
+ starts a review while it is hidden. Your choice is remembered.
31
+
32
+ ## 0.82.5
33
+
34
+ **Help explains Collections more plainly.** The Collections topic in Workspace Help now says what
35
+ collections are for, how to add reviews, what happens when one is marked Done, and that collapsing
36
+ helps you stay focused. The note on how long reviews in a collection are kept is unchanged.
37
+
38
+ ## 0.82.4
39
+
40
+ **⌘K L C lists your collections, to expand or collapse one.** Each row shows the collection's
41
+ name, its count and whether it is expanded. Move with J and K or the arrows and press Enter, or
42
+ press a row's number. Expanding a collection from here also lets J and K walk into its reviews.
43
+ New collection moves to ⌘K L N.
44
+
45
+ ## 0.82.3
46
+
47
+ **⌘K L C makes a new collection.** It opens the same New collection dialog as the button beside
48
+ Select. After you create or rename a collection, its heading takes focus without the keyboard ring
49
+ the name field had; Tab brings the ring back. While a new collection is being made, its name is
50
+ locked and the dialog cannot be opened again, so a slow answer can never close or change the next
51
+ one you start.
52
+
53
+ ## 0.82.2
54
+
55
+ **Collections sit in the list like any other section.** Each collection is now a section between
56
+ Pinned and Active, with no Collections heading above them, and nothing is drawn for collections
57
+ until you make one. New collection moves up beside Select and opens a small dialog that says what a
58
+ collection is for, with Cancel and Create. A collection whose reviews are all Done now says "No
59
+ active reviews".
60
+
3
61
  ## 0.82.1
4
62
 
5
63
  **Back no longer draws a focus ring you never had.** Going Back or Forward puts focus where you
package/dist/server.js CHANGED
@@ -376,7 +376,7 @@ function collectionEmptyText(counts, filtered) {
376
376
  if (filtered) return "Nothing in this collection matches the filters.";
377
377
  if (counts.members === 0) return "Nothing here yet. Add reviews from a card’s menu, or select several and choose Manage collections.";
378
378
  if (counts.pinned > 0 && counts.completed === 0) return "Its reviews are pinned, so they are listed under Pinned.";
379
- return "Nothing here needs attention.";
379
+ return "No active reviews";
380
380
  }
381
381
  /** Said under a collection whose oldest members are not drawn, so it never looks smaller than it is. */
382
382
  function omittedText(omitted) {
@@ -803,7 +803,10 @@ var PATHS = {
803
803
  closed: "<circle cx=\"12\" cy=\"12\" r=\"9\"/><path d=\"m5.6 5.6 12.8 12.8\"/>",
804
804
  reopened: "<circle cx=\"12\" cy=\"12\" r=\"9\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/>",
805
805
  warning: "<path d=\"M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z\"/><path d=\"M12 9v4M12 17h.01\"/>",
806
- forward: "<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>"
806
+ forward: "<path d=\"M5 12h14\"/><path d=\"m12 5 7 7-7 7\"/>",
807
+ plus: "<path d=\"M12 5v14M5 12h14\"/>",
808
+ "sidebar-collapse": "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M9 4v16\"/><path d=\"m16 9-3 3 3 3\"/>",
809
+ "sidebar-expand": "<rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><path d=\"M9 4v16\"/><path d=\"m13 9 3 3-3 3\"/>"
807
810
  };
808
811
  /** The icon slot for one rail row. Hidden from assistive technology: the label beside it says it. */
809
812
  function railIcon(icon) {
@@ -863,6 +866,18 @@ function serverStatusLabel(status) {
863
866
  return "Workspace server running";
864
867
  }
865
868
  //#endregion
869
+ //#region ../../packages/review-harness/src/workspace/sidebar_state.ts
870
+ /**
871
+ * What the sidebar's own toggle, at its foot, will do when pressed, which is what it is called.
872
+ * Only Expanded and Compact: while the sidebar is hidden that toggle is not drawn, and the strip's
873
+ * restore control, which only ever says Show sidebar, stands in for it.
874
+ */
875
+ function sidebarToggleLabel(sidebar) {
876
+ return sidebar?.compact ? "Expand sidebar" : "Collapse sidebar";
877
+ }
878
+ /** The strip's restore control, drawn only while the sidebar is hidden. */
879
+ var SHOW_SIDEBAR = "Show sidebar";
880
+ //#endregion
866
881
  //#region ../../packages/review-harness/src/workspace/workspace_nav.ts
867
882
  /**
868
883
  * The workspace's own places, and the one model every page draws them from.
@@ -905,6 +920,13 @@ var WORKSPACE_PLACES = [
905
920
  }
906
921
  ];
907
922
  /**
923
+ * The sidebar's state, on `<html>` beside the theme, so the page is drawn that way from its first
924
+ * paint. The stylesheet reads it; `client/sidebar.ts` changes it.
925
+ */
926
+ function sidebarAttributes(sidebar) {
927
+ return `${sidebar?.compact ? " data-sidebar-compact" : ""}${sidebar?.hidden ? " data-sidebar-hidden" : ""}`;
928
+ }
929
+ /**
908
930
  * The count beside Notifications. Always rendered, hidden at zero, so the page can reveal it when
909
931
  * `/revision` or Mark all read reports a new number. See `client/unread_counts.ts`.
910
932
  */
@@ -934,7 +956,8 @@ function renderGlobalBar(options) {
934
956
  const live = options.live ?? true;
935
957
  return `<header class="prb-strip">
936
958
  <span class="prb-wordmark"><b>pr</b>review<b>buddy</b></span>
937
- ${live ? renderHeaderNav(options) : ""}
959
+ ${live ? renderSidebarRestore() : ""}
960
+ ${live ? renderNewReview() : ""}
938
961
  ${live ? renderHistoryControls() : ""}
939
962
  ${live ? renderStripReviews(options) : ""}
940
963
  ${live ? renderStripRuns(options) : ""}
@@ -980,6 +1003,37 @@ function renderNotificationsBell(options) {
980
1003
  </a>`;
981
1004
  }
982
1005
  /**
1006
+ * Show sidebar: the way back from Hidden, and drawn only then (see `shell.css`). Among the strip's
1007
+ * controls rather than beside the wordmark, so it reads as a control and not as part of the brand.
1008
+ * Named in words and in the strip's own tooltip, never a `title`.
1009
+ */
1010
+ function renderSidebarRestore() {
1011
+ return `<button type="button" class="prb-strip-action prb-sidebar-restore" data-sidebar-toggle="strip" aria-label="${SHOW_SIDEBAR}" data-tip="${SHOW_SIDEBAR}">${iconSvg("sidebar-expand")}</button>`;
1012
+ }
1013
+ /**
1014
+ * The sidebar's own toggle, the last row of its foot: Expanded and Compact, back and forth.
1015
+ *
1016
+ * In the same place in both, so it never moves under the pointer that pressed it. Hidden is not a
1017
+ * third press: it is its own act (⌘K H), and the strip's Show sidebar brings it back at the width it
1018
+ * had. The row's words say what pressing it will do, and in Compact they are its tooltip, as every
1019
+ * row's are. Both drawings are rendered and the stylesheet shows the one for the state, so nothing
1020
+ * needs repainting but the words. `client/sidebar.ts` keeps those true.
1021
+ */
1022
+ function renderSidebarToggle(sidebar) {
1023
+ return `<button type="button" class="prb-rail-item prb-rail-link prb-sidebar-toggle" data-sidebar-toggle="rail" aria-expanded="${!sidebar?.compact}">
1024
+ <span class="prb-rail-icon" aria-hidden="true"><span class="prb-sidebar-collapse-icon">${iconSvg("sidebar-collapse")}</span><span class="prb-sidebar-expand-icon">${iconSvg("sidebar-expand")}</span></span>
1025
+ <span class="prb-rail-label"><span class="prb-rail-title">${sidebarToggleLabel(sidebar)}</span></span>
1026
+ </button>`;
1027
+ }
1028
+ /**
1029
+ * New review, in the strip: the square stand-in for the rail's labelled button, shown only while
1030
+ * the rail is hidden (a narrow window, a review's focus mode). See `renderNewReviewButton`, and
1031
+ * `shell.css` for the one rule that decides which of the two is showing.
1032
+ */
1033
+ function renderNewReview() {
1034
+ return `<button type="button" class="prb-strip-action prb-strip-new-review" aria-label="New review" data-new-review="strip">${iconSvg("plus")}</button>`;
1035
+ }
1036
+ /**
983
1037
  * Back and Forward through this tab's workspace history, ahead of the places.
984
1038
  *
985
1039
  * Workspace-wide, so here in the strip rather than in a review's own bar: they move between views,
@@ -1058,9 +1112,20 @@ function renderServerStatus() {
1058
1112
  <span class="prb-visually-hidden" data-server-status-label>${escapeHtml$1(serverStatusLabel("running"))}</span>
1059
1113
  </span>`;
1060
1114
  }
1061
- /** The workspace rows, for a rail group. */
1115
+ /**
1116
+ * New review, at the head of the workspace rows: starting a review is the product's primary action,
1117
+ * so it is a labelled button rather than an icon to interpret, set apart from the places below it.
1118
+ *
1119
+ * What it does is wired by the command layer (`client/command_layer.ts`), which runs the very
1120
+ * command ⌘P runs, so the two cannot open different things. The shortcut beside the label is
1121
+ * painted there too, because only the browser knows the platform.
1122
+ */
1123
+ function renderNewReviewButton() {
1124
+ return `<div class="prb-rail-new"><button type="button" class="prb-new-review" data-new-review="rail">${iconSvg("plus")}<span class="prb-new-review-label">New review</span><kbd class="prb-new-review-key" data-new-review-key aria-hidden="true"></kbd></button></div>`;
1125
+ }
1126
+ /** New review, then the workspace rows, for a rail group. */
1062
1127
  function renderWorkspaceNav(options) {
1063
- return WORKSPACE_PLACES.map((item) => `<a class="prb-rail-item prb-rail-link" href="${item.href}"${item.place === options.current ? " aria-current=\"true\"" : ""}>
1128
+ return renderNewReviewButton() + WORKSPACE_PLACES.map((item) => `<a class="prb-rail-item prb-rail-link" href="${item.href}"${item.place === options.current ? " aria-current=\"true\"" : ""}>
1064
1129
  ${railIcon(item.icon)}
1065
1130
  <span class="prb-rail-label"><span class="prb-rail-title">${escapeHtml$1(item.label)}</span></span>
1066
1131
  ${item.place === "notifications" ? unreadCount(options.unread, "rail") : ""}
@@ -1097,20 +1162,9 @@ function renderSendFeedback() {
1097
1162
  function renderWorkspaceRail(options) {
1098
1163
  return `<nav class="prb-rail" aria-label="Workspace">
1099
1164
  <div class="prb-rail-group">${renderWorkspaceNav(options)}</div>
1100
- <div class="prb-rail-group prb-rail-foot">${renderSendFeedback()}</div>
1165
+ <div class="prb-rail-group prb-rail-foot">${renderSendFeedback()}${renderSidebarToggle(options.sidebar)}</div>
1101
1166
  </nav>`;
1102
1167
  }
1103
- /**
1104
- * What the rail carries that the strip does not, shown only while a narrow window hides the rail.
1105
- *
1106
- * Which is now the feedback form alone. Every workspace place is an icon in this same strip at
1107
- * every width, so a text link beside one of them would be the same destination twice; before the
1108
- * Reviews icon existed this is where Reviews had to go, and the filter that put it here is gone
1109
- * with the reason for it.
1110
- */
1111
- function renderHeaderNav(_options) {
1112
- return `<nav class="prb-header-nav" aria-label="Workspace"><a class="prb-feedback" href="${escapeHtml$1(feedbackUrl())}" target="_blank" rel="noopener noreferrer"><span class="prb-feedback-icon" aria-hidden="true">${iconSvg("feedback")}</span><span class="prb-feedback-label">Send feedback</span></a></nav>`;
1113
- }
1114
1168
  //#endregion
1115
1169
  //#region ../../packages/review-harness/src/domain/utils/file_path.ts
1116
1170
  /**
@@ -6088,7 +6142,7 @@ function renderWorkspace(view) {
6088
6142
  */
6089
6143
  const freshness = result ? view.freshness : void 0;
6090
6144
  return `<!doctype html>
6091
- <html lang="en"${themeAttribute(view.theme)}>
6145
+ <html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
6092
6146
  <head>
6093
6147
  <meta charset="utf-8">
6094
6148
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -6681,6 +6735,7 @@ function renderRailFoot(view) {
6681
6735
  return `<div class="prb-rail-group prb-rail-foot">
6682
6736
  ${view.token ? renderWorkspaceNav({ unread: view.unreadNotifications ?? 0 }) : ""}
6683
6737
  ${renderSendFeedback()}
6738
+ ${view.token ? renderSidebarToggle(view.sidebar) : ""}
6684
6739
  </div>`;
6685
6740
  }
6686
6741
  /**
@@ -9676,7 +9731,7 @@ function renderReviewsPage(view) {
9676
9731
  const storedReviews = view.groups.reduce((total, group) => total + group.history.length, 0);
9677
9732
  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);
9678
9733
  return `<!doctype html>
9679
- <html lang="en"${themeAttribute(view.theme)}>
9734
+ <html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
9680
9735
  <head>
9681
9736
  <meta charset="utf-8">
9682
9737
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -9699,12 +9754,16 @@ ${renderContextBar("Reviews", ordered.length === 0 ? "" : renderControls$1(queue
9699
9754
  <div class="prb-shell">
9700
9755
  ${renderWorkspaceRail({
9701
9756
  current: "reviews",
9702
- unread: view.unreadNotifications ?? 0
9757
+ unread: view.unreadNotifications ?? 0,
9758
+ sidebar: view.sidebar
9703
9759
  })}
9704
9760
  <main class="prb-index">
9705
9761
  ${ordered.length === 0 ? "" : `<div class="prb-index-summary">
9706
9762
  <p class="prb-index-count" data-count>${escapeHtml$1(countLabel(storedReviews, ordered.length))}</p>
9707
- <button type="button" class="prb-index-select" data-select-mode aria-pressed="false">Select</button>
9763
+ <div class="prb-index-summary-actions">
9764
+ <button type="button" class="prb-index-select" data-collection-new><span aria-hidden="true">+</span> New collection</button>
9765
+ <button type="button" class="prb-index-select" data-select-mode aria-pressed="false">Select</button>
9766
+ </div>
9708
9767
  </div>`}
9709
9768
  ${renderInProgress$1(operations, ordered, positions, view.prStates, now, sectionOpen("in-progress", view.sectionsCollapsed), memberOf)}
9710
9769
  ${renderSectionAt("pinned")}
@@ -9713,6 +9772,7 @@ ${renderWorkspaceRail({
9713
9772
  ${ordered.length === 0 ? `<p class="prb-index-empty">No reviews stored yet. Ask Claude Code to review a change and one will appear here.</p>` : `<p class="prb-index-none" data-no-matches hidden>Nothing matches that.</p>`}
9714
9773
  ${ordered.length === 0 ? "" : renderBulkBar()}
9715
9774
  ${ordered.length === 0 ? "" : renderCollectionsDialog()}
9775
+ ${ordered.length === 0 ? "" : renderNewCollectionDialog()}
9716
9776
  <p class="prb-index-note">Reviews are kept for thirty days, on this machine only. Pinned, Done and queued reviews, and reviews in a collection, are kept for ninety days instead, counting from the last time you opened or marked them. Every review has a fixed address, so a link to one is worth keeping and still opens after a restart. Stopping the server deletes nothing; it only stops anything being served until the next review starts it again.</p>
9717
9777
  </main>
9718
9778
  </div>
@@ -9849,14 +9909,9 @@ function collectionsArea(context, collapsed) {
9849
9909
  completed: renderCards(completed, { collection: collection.id })
9850
9910
  });
9851
9911
  });
9852
- return `<section class="prb-collections" data-collections-area aria-label="Collections">
9912
+ return `<section class="prb-collections" data-collections-area aria-label="Collections"${collections.length === 0 ? " hidden" : ""}>
9853
9913
  <script type="application/json" data-collections-registry>${registry}<\/script>
9854
- <div class="prb-collections-head">
9855
- <h2 class="prb-collections-title">Collections</h2>
9856
- ${collections.length === 0 ? "<p class=\"prb-collections-hint\">Group reviews that belong together, such as one refactor or one release.</p>" : ""}
9857
- </div>
9858
9914
  ${blocks.join("\n ")}
9859
- ${renderNewCollection()}
9860
9915
  </section>`;
9861
9916
  }
9862
9917
  function renderCollection(collection, counts, omitted, collapsed, cards) {
@@ -9918,17 +9973,25 @@ function renderCollectionMenu(collection) {
9918
9973
  </div>
9919
9974
  </div>`;
9920
9975
  }
9921
- /** New collection, which opens a name field in place. */
9922
- function renderNewCollection() {
9923
- return `<div class="prb-collection-new">
9924
- <button type="button" class="prb-index-more prb-collection-new-button" data-collection-new><span aria-hidden="true">+</span> New collection</button>
9925
- <form class="prb-collection-new-form" data-collection-new-form hidden>
9926
- <input type="text" class="prb-collection-name" data-collection-name-input placeholder="Collection name" aria-label="New collection name" maxlength="60" autocomplete="off" spellcheck="false">
9927
- <button type="submit" class="prb-bulk-button">Create</button>
9928
- <button type="button" class="prb-bulk-button" data-collection-cancel>Cancel</button>
9929
- <p class="prb-collection-error" data-collection-error hidden role="alert"></p>
9930
- </form>
9931
- </div>`;
9976
+ /**
9977
+ * New collection, opened from beside Select. The description lives here, where somebody is deciding
9978
+ * what to make, rather than on the list for good.
9979
+ */
9980
+ function renderNewCollectionDialog() {
9981
+ return `<dialog class="prb-dialog prb-collection-new-dialog" data-collection-new-dialog aria-labelledby="prb-collection-new-title">
9982
+ <form method="dialog" data-collection-new-form>
9983
+ <h2 id="prb-collection-new-title">New collection</h2>
9984
+ <p>Group reviews that belong together, such as one refactor or one release.</p>
9985
+ <label class="prb-field"><span>Collection name</span>
9986
+ <input type="text" data-collection-name-input placeholder="e.g. Refactor" maxlength="60" autocomplete="off" spellcheck="false" required>
9987
+ </label>
9988
+ <p class="prb-collection-error" data-collection-error hidden role="alert"></p>
9989
+ <div class="prb-dialog-actions">
9990
+ <button type="button" data-collection-cancel>Cancel</button>
9991
+ <button type="submit" class="prb-dialog-primary" data-collection-create disabled>Create</button>
9992
+ </div>
9993
+ </form>
9994
+ </dialog>`;
9932
9995
  }
9933
9996
  /**
9934
9997
  * Manage collections, for one card or for a selection. Built once; the browser fills the list from
@@ -10610,7 +10673,7 @@ function readPreferences() {
10610
10673
  try {
10611
10674
  const parsed = JSON.parse(readFileSync(PREFERENCES_PATH, "utf8"));
10612
10675
  if (!parsed || typeof parsed !== "object") return {};
10613
- const { askWidth, dismissedNotices, promptsCollapsed, changeMapRegister, theme, reviewSections, runsSections, reviewLayout } = parsed;
10676
+ const { askWidth, dismissedNotices, promptsCollapsed, changeMapRegister, theme, reviewSections, runsSections, reviewLayout, sidebar } = parsed;
10614
10677
  return {
10615
10678
  ...typeof askWidth === "number" && Number.isFinite(askWidth) ? { askWidth: clampAskWidth(askWidth) } : {},
10616
10679
  ...Array.isArray(dismissedNotices) ? { dismissedNotices: dismissedNotices.filter((key) => typeof key === "string" && NOTICE_KEY.test(key)) } : {},
@@ -10619,7 +10682,8 @@ function readPreferences() {
10619
10682
  ...isTheme(theme) ? { theme } : {},
10620
10683
  ...readReviewSections(reviewSections),
10621
10684
  ...readRunsSections(runsSections),
10622
- ...readLayout(reviewLayout)
10685
+ ...readLayout(reviewLayout),
10686
+ ...readSidebar(sidebar)
10623
10687
  };
10624
10688
  } catch {
10625
10689
  return {};
@@ -10652,6 +10716,35 @@ function writeReviewLayout(patch) {
10652
10716
  writeFileSync(PREFERENCES_PATH, `${JSON.stringify(next, null, 2)}\n`);
10653
10717
  } catch {}
10654
10718
  }
10719
+ /** Only the two booleans, and only when one is set; an empty object is no preference. */
10720
+ function readSidebar(value) {
10721
+ if (!value || typeof value !== "object") return {};
10722
+ const { compact, hidden } = value;
10723
+ const sidebar = {
10724
+ ...typeof compact === "boolean" ? { compact } : {},
10725
+ ...typeof hidden === "boolean" ? { hidden } : {}
10726
+ };
10727
+ return Object.keys(sidebar).length === 0 ? {} : { sidebar };
10728
+ }
10729
+ /** The sidebar fields a request may set: a boolean or nothing, each. */
10730
+ function readSidebarPatch(value) {
10731
+ const { sidebar } = readSidebar(value);
10732
+ return sidebar ?? null;
10733
+ }
10734
+ /** Remember a change to the sidebar, merged into what is stored. Read-modify-write, like every other field here. */
10735
+ function writeSidebar(patch) {
10736
+ try {
10737
+ mkdirSync(STORE_ROOT, { recursive: true });
10738
+ const current = readPreferences();
10739
+ writeFileSync(PREFERENCES_PATH, `${JSON.stringify({
10740
+ ...current,
10741
+ sidebar: {
10742
+ ...current.sidebar,
10743
+ ...patch
10744
+ }
10745
+ }, null, 2)}\n`);
10746
+ } catch {}
10747
+ }
10655
10748
  /** Remember whether "Your prompts" is folded. Read-modify-write, like every other field here. */
10656
10749
  function writePromptsCollapsed(collapsed) {
10657
10750
  try {
@@ -10872,7 +10965,7 @@ function renderSettingsPage(view) {
10872
10965
  const { settings } = view;
10873
10966
  const address = `http://localhost:${settings.port.configured}`;
10874
10967
  return `<!doctype html>
10875
- <html lang="en"${themeAttribute(settings.theme)}>
10968
+ <html lang="en"${themeAttribute(settings.theme)}${sidebarAttributes(view.sidebar)}>
10876
10969
  <head>
10877
10970
  <meta charset="utf-8">
10878
10971
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -10891,7 +10984,8 @@ ${renderContextBar("Settings", renderBack())}
10891
10984
  <div class="prb-shell">
10892
10985
  ${renderWorkspaceRail({
10893
10986
  current: "settings",
10894
- unread: view.unreadNotifications ?? 0
10987
+ unread: view.unreadNotifications ?? 0,
10988
+ sidebar: view.sidebar
10895
10989
  })}
10896
10990
  <main class="prb-index prb-settings">
10897
10991
  <div class="prb-page-panel">
@@ -11392,8 +11486,10 @@ var TOPICS = [
11392
11486
  <li><code>prreviewbuddy review --pr &lt;url&gt;</code> for a pull request</li>
11393
11487
  <li><code>prreviewbuddy open</code> to list the reviews you have and open this workspace</li>
11394
11488
  </ul>
11395
- <p>Or press <kbd class="prb-kbd">⌘P</kbd> (<kbd class="prb-kbd">Ctrl+P</kbd> elsewhere) to add a
11396
- review from here. The panel follows the reviews it started while it stays open, and closing it
11489
+ <p>Or click <b>New review</b> at the top of the sidebar, or press <kbd class="prb-kbd">⌘P</kbd>
11490
+ (<kbd class="prb-kbd">Ctrl+P</kbd> on other platforms), to start a review from anywhere in the
11491
+ workspace. With the sidebar collapsed, New review is the <b>+</b> at its top, and with the
11492
+ sidebar hidden, the <b>+</b> in the top toolbar. The panel follows the reviews it started while it stays open, and closing it
11397
11493
  stops nothing. <code>prreviewbuddy help</code> lists every command, and
11398
11494
  <code>prreviewbuddy review --help</code> every option a review takes.</p>
11399
11495
  <p>There is no account and no API key. PR Review Buddy uses the coding agent installed on your
@@ -11491,18 +11587,13 @@ feature: E</pre>
11491
11587
  {
11492
11588
  id: "collections",
11493
11589
  title: "Collections",
11494
- body: `<p>A collection groups reviews that belong together, such as the pull requests of one
11495
- refactor or one release. It says what belongs together; whether each review still needs your
11496
- attention is still Active, Waiting or Done, so adding or removing a review never changes its
11497
- state.</p>
11498
- <p>Make one with New collection on the Reviews page, or from Manage collections in a card's menu.
11499
- To add several reviews at once, choose Select, tick them, and choose Manage collections. A review
11500
- can be in more than one collection. Ticking a collection adds every selected review to it and
11501
- leaves their other collections alone.</p>
11502
- <p>Each collection lists its reviews that are not Done. Marking one Done moves it under Show
11503
- completed, in every collection it is in, and Move to active brings it back. A review in a
11504
- collection is listed there rather than under Active, Waiting or Done. A pinned review stays under
11505
- Pinned. The Collections filter shows chosen collections, or Uncollected for reviews in none.</p>
11590
+ body: `<p>Collections help you organise related reviews, such as a refactor, release or feature.</p>
11591
+ <p>Create a collection from the Reviews page, then add reviews individually or select several and
11592
+ use <b>Manage collections</b>. A review can belong to multiple collections.</p>
11593
+ <p>Collections show reviews that still need attention. When you mark a review <b>Done</b>, it moves
11594
+ under <b>Show completed</b> within its collection, without losing its membership.</p>
11595
+ <p>You can collapse collections to stay focused, and expand them when you're ready to continue.
11596
+ Removing a review from all collections returns it to the normal review sections.</p>
11506
11597
  <p>Reviews in a collection are kept for ninety days, like pinned and Done reviews, counted from
11507
11598
  when you last opened or marked them. Adding one to a collection doesn't restart that count.
11508
11599
  Deleting a collection keeps its reviews.</p>`
@@ -11592,7 +11683,7 @@ function renderContents() {
11592
11683
  }
11593
11684
  function renderHelpPage(view) {
11594
11685
  return `<!doctype html>
11595
- <html lang="en"${themeAttribute(view.theme)}>
11686
+ <html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
11596
11687
  <head>
11597
11688
  <meta charset="utf-8">
11598
11689
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -11611,7 +11702,8 @@ ${renderContextBar("Help", "")}
11611
11702
  <div class="prb-shell">
11612
11703
  ${renderWorkspaceRail({
11613
11704
  current: "help",
11614
- unread: view.unreadNotifications ?? 0
11705
+ unread: view.unreadNotifications ?? 0,
11706
+ sidebar: view.sidebar
11615
11707
  })}
11616
11708
  <main class="prb-index prb-help">
11617
11709
  <div class="prb-page-panel">
@@ -11633,7 +11725,7 @@ ${renderWorkspaceRail({
11633
11725
  function renderNotificationsPage(view) {
11634
11726
  const unread = view.notifications.filter((notification) => notification.readAt === void 0).length;
11635
11727
  return `<!doctype html>
11636
- <html lang="en"${themeAttribute(view.theme)}>
11728
+ <html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
11637
11729
  <head>
11638
11730
  <meta charset="utf-8">
11639
11731
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -11652,7 +11744,8 @@ ${renderContextBar("Activity", view.notifications.length === 0 ? "" : renderRead
11652
11744
  <div class="prb-shell">
11653
11745
  ${renderWorkspaceRail({
11654
11746
  current: "notifications",
11655
- unread: view.unreadNotifications ?? 0
11747
+ unread: view.unreadNotifications ?? 0,
11748
+ sidebar: view.sidebar
11656
11749
  })}
11657
11750
  <main class="prb-index prb-notifications-page">
11658
11751
  ${view.notifications.length === 0 ? `<p class="prb-index-empty" data-notifications-empty>Nothing yet. When a review finishes or fails, or a branch or pull request moves, it appears here.</p>` : `<ol class="prb-notifications" data-notifications>
@@ -13037,7 +13130,7 @@ function renderRunsPage(view) {
13037
13130
  const nothing = inProgress.length === 0 && recent.length === 0;
13038
13131
  const notResponding = recent.some((entry) => entry.status === "not-responding");
13039
13132
  return `<!doctype html>
13040
- <html lang="en"${themeAttribute(view.theme)}>
13133
+ <html lang="en"${themeAttribute(view.theme)}${sidebarAttributes(view.sidebar)}>
13041
13134
  <head>
13042
13135
  <meta charset="utf-8">
13043
13136
  <meta name="viewport" content="width=device-width, initial-scale=1">
@@ -13056,7 +13149,8 @@ ${renderContextBar("Runs", nothing ? "" : renderControls(notResponding))}
13056
13149
  <div class="prb-shell">
13057
13150
  ${renderWorkspaceRail({
13058
13151
  current: "runs",
13059
- unread: view.unreadNotifications ?? 0
13152
+ unread: view.unreadNotifications ?? 0,
13153
+ sidebar: view.sidebar
13060
13154
  })}
13061
13155
  <main class="prb-index prb-runs">
13062
13156
  <p class="prb-runs-lede">Monitor review executions and recent activity.</p>
@@ -14992,6 +15086,7 @@ async function handle(req, res, options) {
14992
15086
  if (url.pathname === "/notifications/live") return handleLiveNotifications(req, res, url.searchParams, options.indexToken);
14993
15087
  if (url.pathname === "/pin") return handlePin(req, res, options.indexToken);
14994
15088
  if (url.pathname === "/sections") return handleSections(req, res, options.indexToken);
15089
+ if (url.pathname === "/sidebar") return handleSidebar(req, res, options.indexToken);
14995
15090
  if (url.pathname === "/queue") return handleQueue(req, res, options.indexToken);
14996
15091
  if (url.pathname === "/collections") return handleCollections(req, res, options.indexToken);
14997
15092
  if (url.pathname === "/collections/members") return handleCollectionMembers(req, res, options.indexToken);
@@ -15141,6 +15236,7 @@ function indexView() {
15141
15236
  unreadNotifications: unreadCount$1(),
15142
15237
  driving: isDriving(),
15143
15238
  theme: readPreferences().theme,
15239
+ sidebar: readPreferences().sidebar,
15144
15240
  sectionsCollapsed: readPreferences().reviewSections
15145
15241
  };
15146
15242
  }
@@ -15231,7 +15327,8 @@ async function handleSettings(req, res, params, indexToken) {
15231
15327
  sendHtml(res, 200, renderSettingsPage({
15232
15328
  settings: readSettings(),
15233
15329
  unreadNotifications: unreadCount$1(),
15234
- driving: isDriving()
15330
+ driving: isDriving(),
15331
+ sidebar: readPreferences().sidebar
15235
15332
  }), indexCookieHeader(indexToken));
15236
15333
  }
15237
15334
  /**
@@ -15243,7 +15340,8 @@ function handleHelp(req, res, params, indexToken) {
15243
15340
  sendHtml(res, 200, renderHelpPage({
15244
15341
  unreadNotifications: unreadCount$1(),
15245
15342
  driving: isDriving(),
15246
- theme: readPreferences().theme
15343
+ theme: readPreferences().theme,
15344
+ sidebar: readPreferences().sidebar
15247
15345
  }), indexCookieHeader(indexToken));
15248
15346
  }
15249
15347
  /**
@@ -15358,7 +15456,8 @@ async function handleNotifications(req, res, params, indexToken) {
15358
15456
  gone: new Set(notifications.filter((entry) => !loadWorkspace(entry.reviewId)).map((entry) => entry.id)),
15359
15457
  unreadNotifications: unreadCount$1(),
15360
15458
  driving: isDriving(),
15361
- theme: readPreferences().theme
15459
+ theme: readPreferences().theme,
15460
+ sidebar: readPreferences().sidebar
15362
15461
  }), indexCookieHeader(indexToken));
15363
15462
  }
15364
15463
  /**
@@ -15379,6 +15478,7 @@ function handleRuns(req, res, params, indexToken) {
15379
15478
  unreadNotifications: unreadCount$1(),
15380
15479
  driving: isDriving(),
15381
15480
  theme: preferences.theme,
15481
+ sidebar: preferences.sidebar,
15382
15482
  sectionsCollapsed: preferences.runsSections
15383
15483
  }), indexCookieHeader(indexToken));
15384
15484
  }
@@ -15792,6 +15892,18 @@ async function handleRunsSections(req, res, indexToken) {
15792
15892
  writeRunsSectionCollapsed(body.section, body.collapsed);
15793
15893
  send(res, 200, { ok: true });
15794
15894
  }
15895
+ /**
15896
+ * The sidebar, from a workspace page that is not a review, with the same credential as `/sections`.
15897
+ * A review page sends the same field to its own `/prefs`, since it may hold no index cookie.
15898
+ */
15899
+ async function handleSidebar(req, res, indexToken) {
15900
+ if (req.method !== "POST") return send(res, 405, { error: "Use POST." });
15901
+ if (!indexToken || !secretMatches(indexToken, indexCookie(req))) return send(res, 403, { error: "Not authorised." });
15902
+ const sidebar = readSidebarPatch((await readJson(req))?.sidebar);
15903
+ if (!sidebar) return send(res, 400, { error: "Not a sidebar setting." });
15904
+ writeSidebar(sidebar);
15905
+ send(res, 200, { ok: true });
15906
+ }
15795
15907
  async function handleSections(req, res, indexToken) {
15796
15908
  if (req.method !== "POST") return send(res, 405, { error: "Use POST." });
15797
15909
  if (!indexToken || !secretMatches(indexToken, indexCookie(req))) return send(res, 403, { error: "Not authorised." });
@@ -16198,6 +16310,7 @@ async function sendWorkspacePage(res, workspace) {
16198
16310
  repoPath: workspace.repoPath,
16199
16311
  askWidth: readPreferences().askWidth,
16200
16312
  theme: readPreferences().theme,
16313
+ sidebar: readPreferences().sidebar,
16201
16314
  dismissedNotices: readPreferences().dismissedNotices,
16202
16315
  promptsCollapsed: readPreferences().promptsCollapsed === true,
16203
16316
  reviewLayout: readPreferences().reviewLayout ?? {},
@@ -17001,6 +17114,8 @@ async function handlePrefs(req, res) {
17001
17114
  if (typeof askWidth === "number") writeAskWidth(askWidth);
17002
17115
  else if (askWidth === null) clearAskWidth();
17003
17116
  if (typeof body?.promptsCollapsed === "boolean") writePromptsCollapsed(body.promptsCollapsed);
17117
+ const sidebar = readSidebarPatch(body?.sidebar);
17118
+ if (sidebar) writeSidebar(sidebar);
17004
17119
  const layout = readReviewLayoutPatch(body?.reviewLayout);
17005
17120
  if (layout) writeReviewLayout(layout);
17006
17121
  if (body?.changeMapRegister === "simple" || body?.changeMapRegister === "technical") writeChangeMapRegister(body.changeMapRegister);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prreviewbuddy",
3
- "version": "0.82.1",
3
+ "version": "0.83.3",
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",
@@ -1 +1 @@
1
- import{dt as e}from"./chunk-CQNkZ9TG.js";var t=`button:not([disabled])`;function n(e){return e.querySelector(`[data-menu-panel]`)}function r(e){return e.querySelector(`[data-menu-trigger]`)}function i(e){let t=n(e);return t?[...t.querySelectorAll(`input:not([disabled]), textarea:not([disabled]), button:not([disabled])`)].filter(e=>!e.closest(`[hidden]`)):[]}function a(e){let r=n(e);return r?[...r.querySelectorAll(t)].filter(e=>!e.closest(`[hidden]`)):[]}var o=`data-menu-placement`;function s(e){let t=e.ownerDocument.defaultView,n=0,r=t?.innerHeight??1/0;for(let i=e.parentElement;i&&t;i=i.parentElement){let e=t.getComputedStyle(i).overflowY;if(e===`visible`||e===``)continue;let a=i.getBoundingClientRect();n=Math.max(n,a.top),r=Math.min(r,a.bottom)}return{top:n,bottom:r}}var c=8,l=120;function u(e){let t=n(e),i=r(e);if(!t||!i)return;t.removeAttribute(o),t.style.removeProperty(`max-height`),t.style.removeProperty(`overflow-y`),t.style.removeProperty(`box-sizing`),t.scrollTop=0;let a=t.getBoundingClientRect(),u=i.getBoundingClientRect(),d=s(e),f=Math.max(0,a.top-u.bottom),p=d.bottom-u.bottom-f-c,m=u.top-d.top-f-c,h=a.height,g=h<=p?`below`:h<=m||m>p?`above`:`below`;g===`above`&&t.setAttribute(o,`above`);let _=g===`above`?m:p;h<=_||(t.style.boxSizing=`border-box`,t.style.maxHeight=`${Math.max(Math.floor(_),l)}px`,t.style.overflowY=`auto`)}function d(e){return!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey?e.key===`j`?`ArrowDown`:e.key===`k`?`ArrowUp`:e.key:e.key}function f(t,o={}){let s=null,c=null,l=o.within?`${o.within} [data-menu]`:`[data-menu]`,f=e=>e&&(!o.within||e.closest(o.within))?e:null,p=(e,t={})=>{let i=n(e);if(!i||i.hidden)return;i.hidden=!0;let a=r(e);a?.setAttribute(`aria-expanded`,`false`);let l=s===e?c:null;s===e&&(s=null,c=null),o.onClose?.(e),t.returnFocus&&(l??(()=>a?.focus()))()},m=()=>{for(let e of t.querySelectorAll(l))p(e)},h=(e,t,i=null)=>{let l=n(e);l&&(m(),c=i,o.onOpen?.(e),l.hidden=!1,u(e),r(e)?.setAttribute(`aria-expanded`,`true`),s=e,t&&a(e)[0]?.focus())};return t.addEventListener(`click`,e=>{let t=e.target,r=f(t?.closest?.(`[data-menu]`)??null);if(!r){m();return}if(t?.closest(`[data-menu-trigger]`)){let t=e.detail===0;n(r)?.hidden?h(r,t):p(r);return}s&&s!==r&&p(s)}),t.addEventListener(`keydown`,t=>{if(s&&!s.isConnected&&(s=null),!s)return;let n=s;if(t.key===`Escape`){t.preventDefault(),p(n,{returnFocus:!0});return}if(!n.contains(t.target))return;if(t.key===`Tab`){if(n.hasAttribute(`data-menu-form`)){let e=i(n),r=e.indexOf(t.target);if(e.length>0){t.preventDefault(),e[(r+(t.shiftKey?-1:1)+e.length)%e.length].focus();return}}p(n);return}if(e(t.target))return;let r=a(n);if(r.length===0)return;let o=r.indexOf(t.target),c=d(t),l=c===`ArrowDown`?r[(o+1)%r.length]:c===`ArrowUp`?r[(o-1+r.length)%r.length]:c===`Home`?r[0]:c===`End`?r[r.length-1]:null;l&&(t.preventDefault(),l.focus(),l.scrollIntoView({block:`nearest`}))}),{open:(e,t={})=>h(e,t.focusFirst===!0,t.returnFocus??null),close:p,closeAll:m}}function p(e){return e<3e4?1e3:e<12e4?3e3:e<6e5?8e3:15e3}export{f as n,p as t};
1
+ import{dt as e}from"./chunk-DwdwtB9r.js";var t=`button:not([disabled])`;function n(e){return e.querySelector(`[data-menu-panel]`)}function r(e){return e.querySelector(`[data-menu-trigger]`)}function i(e){let t=n(e);return t?[...t.querySelectorAll(`input:not([disabled]), textarea:not([disabled]), button:not([disabled])`)].filter(e=>!e.closest(`[hidden]`)):[]}function a(e){let r=n(e);return r?[...r.querySelectorAll(t)].filter(e=>!e.closest(`[hidden]`)):[]}var o=`data-menu-placement`;function s(e){let t=e.ownerDocument.defaultView,n=0,r=t?.innerHeight??1/0;for(let i=e.parentElement;i&&t;i=i.parentElement){let e=t.getComputedStyle(i).overflowY;if(e===`visible`||e===``)continue;let a=i.getBoundingClientRect();n=Math.max(n,a.top),r=Math.min(r,a.bottom)}return{top:n,bottom:r}}var c=8,l=120;function u(e){let t=n(e),i=r(e);if(!t||!i)return;t.removeAttribute(o),t.style.removeProperty(`max-height`),t.style.removeProperty(`overflow-y`),t.style.removeProperty(`box-sizing`),t.scrollTop=0;let a=t.getBoundingClientRect(),u=i.getBoundingClientRect(),d=s(e),f=Math.max(0,a.top-u.bottom),p=d.bottom-u.bottom-f-c,m=u.top-d.top-f-c,h=a.height,g=h<=p?`below`:h<=m||m>p?`above`:`below`;g===`above`&&t.setAttribute(o,`above`);let _=g===`above`?m:p;h<=_||(t.style.boxSizing=`border-box`,t.style.maxHeight=`${Math.max(Math.floor(_),l)}px`,t.style.overflowY=`auto`)}function d(e){return!e.metaKey&&!e.ctrlKey&&!e.altKey&&!e.shiftKey?e.key===`j`?`ArrowDown`:e.key===`k`?`ArrowUp`:e.key:e.key}function f(t,o={}){let s=null,c=null,l=o.within?`${o.within} [data-menu]`:`[data-menu]`,f=e=>e&&(!o.within||e.closest(o.within))?e:null,p=(e,t={})=>{let i=n(e);if(!i||i.hidden)return;i.hidden=!0;let a=r(e);a?.setAttribute(`aria-expanded`,`false`);let l=s===e?c:null;s===e&&(s=null,c=null),o.onClose?.(e),t.returnFocus&&(l??(()=>a?.focus()))()},m=()=>{for(let e of t.querySelectorAll(l))p(e)},h=(e,t,i=null)=>{let l=n(e);l&&(m(),c=i,o.onOpen?.(e),l.hidden=!1,u(e),r(e)?.setAttribute(`aria-expanded`,`true`),s=e,t&&a(e)[0]?.focus())};return t.addEventListener(`click`,e=>{let t=e.target,r=f(t?.closest?.(`[data-menu]`)??null);if(!r){m();return}if(t?.closest(`[data-menu-trigger]`)){let t=e.detail===0;n(r)?.hidden?h(r,t):p(r);return}s&&s!==r&&p(s)}),t.addEventListener(`keydown`,t=>{if(s&&!s.isConnected&&(s=null),!s)return;let n=s;if(t.key===`Escape`){t.preventDefault(),p(n,{returnFocus:!0});return}if(!n.contains(t.target))return;if(t.key===`Tab`){if(n.hasAttribute(`data-menu-form`)){let e=i(n),r=e.indexOf(t.target);if(e.length>0){t.preventDefault(),e[(r+(t.shiftKey?-1:1)+e.length)%e.length].focus();return}}p(n);return}if(e(t.target))return;let r=a(n);if(r.length===0)return;let o=r.indexOf(t.target),c=d(t),l=c===`ArrowDown`?r[(o+1)%r.length]:c===`ArrowUp`?r[(o-1+r.length)%r.length]:c===`Home`?r[0]:c===`End`?r[r.length-1]:null;l&&(t.preventDefault(),l.focus(),l.scrollIntoView({block:`nearest`}))}),{open:(e,t={})=>h(e,t.focusFirst===!0,t.returnFocus??null),close:p,closeAll:m}}function p(e){return e<3e4?1e3:e<12e4?3e3:e<6e5?8e3:15e3}export{f as n,p as t};
@@ -1 +1 @@
1
- import{F as e}from"./chunk-CQNkZ9TG.js";function t(e=document){for(let t of e.querySelectorAll(`.prb-index-status`))t.addEventListener(`click`,e=>{let n=t.querySelector(`select`);if(!(!n||e.target instanceof Node&&n.contains(e.target))){e.preventDefault(),n.focus();try{n.showPicker()}catch{}}})}function n(t,n){r(t,t=>e(t.dataset.section)?t.dataset.section:void 0,n)}function r(e,t,n){let r=Promise.resolve();for(let i of e){let e=t(i);if(e===void 0)continue;let a=i.open;i.addEventListener(`toggle`,()=>{if(i.open===a)return;a=i.open;let t=!i.open;r=r.then(()=>n(e,t)).catch(()=>{})})}}export{n,t as r,r as t};
1
+ import{F as e}from"./chunk-DwdwtB9r.js";function t(e=document){for(let t of e.querySelectorAll(`.prb-index-status`))t.addEventListener(`click`,e=>{let n=t.querySelector(`select`);if(!(!n||e.target instanceof Node&&n.contains(e.target))){e.preventDefault(),n.focus();try{n.showPicker()}catch{}}})}function n(t,n){r(t,t=>e(t.dataset.section)?t.dataset.section:void 0,n)}function r(e,t,n){let r=Promise.resolve();for(let i of e){let e=t(i);if(e===void 0)continue;let a=i.open;i.addEventListener(`toggle`,()=>{if(i.open===a)return;a=i.open;let t=!i.open;r=r.then(()=>n(e,t)).catch(()=>{})})}}export{n,t as r,r as t};
@@ -0,0 +1 @@
1
+ async function e(e,t){try{await navigator.clipboard.writeText(t);let n=e.textContent;e.textContent=`Copied`,window.setTimeout(()=>{e.textContent=n},1200)}catch{let n=document.createElement(`input`);n.type=`text`,n.readOnly=!0,n.value=t,n.className=`prb-menu-id`,n.setAttribute(`data-id-field`,``),e.after(n),n.select()}}function t(e,t){let n=t.toLowerCase().split(/\s+/).filter(Boolean);return n.length===0?[...e]:e.filter(e=>{let t=`${e.name}\n${e.prompt}`.toLowerCase();return n.every(e=>t.includes(e))})}function n(e){return e<0||e>=10?null:e===9?`0`:String(e+1)}function r(e){return/^[0-9]$/.test(e)?e===`0`?9:Number(e)-1:null}function i(e){return{title:`Choose a prompt`,mount(i,a){let o=i.ownerDocument,s=e.prompts(),c=o.createElement(`input`);c.type=`search`,c.className=`prb-palette-input`,c.placeholder=`Filter prompts`,c.setAttribute(`aria-label`,`Filter prompts`),c.spellcheck=!1;let l=o.createElement(`p`);l.className=`prb-palette-summary`,l.setAttribute(`aria-live`,`polite`);let u=o.createElement(`div`);u.className=`prb-palette-results`,u.setAttribute(`role`,`group`),u.setAttribute(`aria-label`,`Prompts`);let d=[],f=()=>[...u.querySelectorAll(`[data-prompt-choice]`)],p=()=>{let e=o.activeElement===c;for(let[t,n]of f().entries())n.toggleAttribute(`data-enter-target`,e&&t===0)},m=()=>{d=t(s,c.value),u.replaceChildren(...d.map((e,t)=>{let r=o.createElement(`button`);r.type=`button`,r.className=`prb-palette-result prb-palette-prompt`,r.dataset.promptChoice=e.id;let i=o.createElement(`kbd`);i.className=`prb-kbd prb-palette-num`;let a=n(t);a?i.textContent=a:i.setAttribute(`aria-hidden`,`true`);let s=o.createElement(`span`);s.className=`prb-palette-prompt-text`;let c=o.createElement(`span`);c.className=`prb-palette-result-title`,c.textContent=e.name;let l=o.createElement(`span`);return l.className=`prb-palette-prompt-said`,l.textContent=e.prompt,s.append(c,l),r.append(i,s),r})),l.textContent=s.length===0?`No saved prompts yet. Add one under Your prompts in Buddy AI.`:d.length===0?`No prompts match.`:d.length===s.length?`${s.length} ${s.length===1?`prompt`:`prompts`}`:`${d.length} of ${s.length} prompts`,p()},h=t=>{!t||!d.some(e=>e.id===t)||(a(),e.choose(t))},g=e=>{c.value=e===`Backspace`?c.value.slice(0,-1):c.value+e,c.focus(),c.dispatchEvent(new Event(`input`,{bubbles:!0}))};c.addEventListener(`input`,m),c.addEventListener(`focus`,p),c.addEventListener(`blur`,p),u.addEventListener(`click`,e=>{h(e.target.closest(`[data-prompt-choice]`)?.dataset.promptChoice)}),i.addEventListener(`keydown`,e=>{if(e.isComposing||e.metaKey||e.ctrlKey||e.altKey)return;let t=f(),n=e.target===c,i=t.indexOf(e.target),a=r(e.key);if(a!==null)return e.preventDefault(),h(t[a]?.dataset.promptChoice);if(e.key===`Enter`)return e.preventDefault(),h((n?t[0]:t[i])?.dataset.promptChoice);let o=e.key===`ArrowDown`||!n&&e.key===`j`,s=e.key===`ArrowUp`||!n&&e.key===`k`;if(o||s){let n=o?i<0?t[0]:t[i+1]:i<=0?c:t[i-1];e.preventDefault(),n?.focus();return}!n&&(e.key.length===1||e.key===`Backspace`)&&(e.preventDefault(),g(e.key))}),i.append(c,l,u),m(),c.focus()}}}var a=`Fetch selected`;function o(e){return`${e} ${e===1?`repository`:`repositories`}`}function s(e){return`Fetching ${o(e)}…`}function c(e){return e.fetched===0?`Could not fetch ${o(e.repositories)}`:e.fetched===e.repositories?`Fetched ${o(e.repositories)}`:`Fetched ${e.fetched} of ${o(e.repositories)}`}function l(e,t){let n=new Set,r=new Set;for(let i of e)for(let e of i.lineage){let i=t[e];i!==void 0&&(n.add(e),r.add(i))}return{ids:[...n],repositories:r.size}}function u(e,t){return l(t,e).repositories>0}export{s as a,n as c,c as i,e as l,u as n,i as o,l as r,r as s,a as t};