@youtyan/code-viewer 0.8.6 → 0.8.8

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/web/app.js CHANGED
@@ -227,6 +227,8 @@ ${lines.join(`
227
227
  function shouldAutoLoadForRoute(route, options = {}) {
228
228
  if (route.screen === "history")
229
229
  return options.historyWorktreeSelected === true;
230
+ if (route.screen === "diff")
231
+ return !route.range.from || route.range.from === "worktree" || !route.range.to || route.range.to === "worktree";
230
232
  if (route.screen === "database" || route.screen === "journal" || route.screen === "help" || route.screen === "unknown")
231
233
  return false;
232
234
  return true;
@@ -511,6 +513,8 @@ ${lines.join(`
511
513
  ];
512
514
  var X_16_PATH = "M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z";
513
515
  var SYNC_16_PATH = "M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z";
516
+ var ARROW_RIGHT_16_PATH = "M8.22 2.97a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l2.97-2.97H3.75a.75.75 0 0 1 0-1.5h7.44L8.22 4.03a.75.75 0 0 1 0-1.06Z";
517
+ var UNDO_16_PATH = "M1.22 6.28a.749.749 0 0 1 0-1.06l3.5-3.5a.749.749 0 1 1 1.06 1.06L3.561 5h7.188l.001.007L10.749 5c.058 0 .116.007.171.019A4.501 4.501 0 0 1 10.5 14H8.796a.75.75 0 0 1 0-1.5H10.5a3 3 0 1 0 0-6H3.561L5.78 8.72a.749.749 0 1 1-1.06 1.06l-3.5-3.5Z";
514
518
  function iconSvg(className, paths) {
515
519
  const pathList = Array.isArray(paths) ? paths : [paths];
516
520
  return '<svg class="octicon ' + className + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">' + pathList.map((path) => `<path fill="currentColor" d="${path}"></path>`).join("") + "</svg>";
@@ -520,6 +524,9 @@ ${lines.join(`
520
524
  var GRABBER_16_PATH = "M10 13h4v-2h-4v2Zm-8 0h4v-2H2v2Zm8-6h4V5h-4v2ZM2 7h4V5H2v2Zm8-6v2h4V1h-4ZM2 3h4V1H2v2Z";
521
525
  var PULSE_16_PATH = "M6 2c.306 0 .582.187.696.471L10 10.731l1.304-3.26A.751.751 0 0 1 12 7h3.25a.75.75 0 0 1 0 1.5h-2.742l-1.812 4.528a.751.751 0 0 1-1.392 0L6 4.77 4.696 8.03A.75.75 0 0 1 4 8.5H.75a.75.75 0 0 1 0-1.5h2.742l1.812-4.529A.751.751 0 0 1 6 2Z";
522
526
  var MOON_16_PATH = "M9.598 1.591a.749.749 0 0 1 .785-.175 7.001 7.001 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786Zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.499 5.499 0 1 0 7.678-7.678Z";
527
+ var QUESTION_16_PATH = "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z";
528
+ var MARK_GITHUB_16_PATH = "M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656";
529
+ var BOOK_16_PATH = "M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z";
523
530
  var SEARCH_16_PATH = "M10.68 11.74a6 6 0 0 1-7.917-8.984 6 6 0 0 1 8.997 7.905l3.05 3.05a.75.75 0 1 1-1.06 1.06l-3.07-3.03ZM11.5 7a4.499 4.499 0 1 1-8.997 0A4.499 4.499 0 0 1 11.5 7Z";
524
531
  var DOWNLOAD_16_PATHS = [
525
532
  "M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z",
@@ -776,6 +783,50 @@ ${lines.join(`
776
783
  };
777
784
  }
778
785
 
786
+ // web-src/core/repository-web-url.ts
787
+ function encodePath(value) {
788
+ return value.split("/").filter(Boolean).map((part) => encodeURIComponent(part)).join("/");
789
+ }
790
+ function resolvedRef(ref, fallbackRef) {
791
+ if (!ref || ref === "worktree" || ref === "HEAD") {
792
+ return fallbackRef || "HEAD";
793
+ }
794
+ return ref;
795
+ }
796
+ function buildRepositoryWebTarget(repoWebUrl, options) {
797
+ if (!repoWebUrl)
798
+ return null;
799
+ let remote;
800
+ try {
801
+ remote = new URL(repoWebUrl);
802
+ } catch {
803
+ return null;
804
+ }
805
+ if (remote.protocol !== "http:" && remote.protocol !== "https:")
806
+ return null;
807
+ remote.search = "";
808
+ remote.hash = "";
809
+ remote.pathname = remote.pathname.replace(/\/+$/, "");
810
+ const base = remote.toString().replace(/\/$/, "");
811
+ if (remote.hostname.toLowerCase() !== "github.com") {
812
+ return { url: base, provider: "web" };
813
+ }
814
+ const ref = encodePath(resolvedRef(options.ref, options.fallbackRef));
815
+ const path = encodePath(options.path || "");
816
+ if (!ref || options.kind === "blob" && !path) {
817
+ return { url: base, provider: "github" };
818
+ }
819
+ const pathSuffix = path ? `/${path}` : "";
820
+ const target = `${base}/${options.kind}/${ref}${pathSuffix}`;
821
+ if (options.kind !== "blob" || !Number.isFinite(options.start) || !Number.isFinite(options.end)) {
822
+ return { url: target, provider: "github" };
823
+ }
824
+ const start = Math.max(1, Math.floor(Math.min(options.start, options.end)));
825
+ const end = Math.max(1, Math.floor(Math.max(options.start, options.end)));
826
+ const lineHash = start === end ? `#L${start}` : `#L${start}-L${end}`;
827
+ return { url: `${target}${lineHash}`, provider: "github" };
828
+ }
829
+
779
830
  // web-src/core/routes.ts
780
831
  function assertNever(value) {
781
832
  throw new Error(`unhandled route: ${JSON.stringify(value)}`);
@@ -1003,7 +1054,7 @@ ${lines.join(`
1003
1054
  }
1004
1055
  case "journal": {
1005
1056
  const params = new URLSearchParams;
1006
- if (route.tab && route.tab !== "journal")
1057
+ if (route.tab && route.tab !== "tasks")
1007
1058
  params.set("tab", route.tab);
1008
1059
  if (route.date)
1009
1060
  params.set("date", route.date);
@@ -8571,16 +8622,19 @@ ${frontmatter.yaml}
8571
8622
 
8572
8623
  // web-src/views/annotations-ui.ts
8573
8624
  var ANNOTATION_SESSION_PARAM = "annotationSession";
8625
+ var ANNOTATION_PANEL_PARAM = "annotations";
8626
+ var ANNOTATION_ENTRY_PARAM = "annotation";
8574
8627
  function createAnnotationsUi(deps) {
8575
8628
  const { $ } = deps;
8576
8629
  let ANNOTATIONS = { version: 1, sessions: [] };
8577
8630
  let annotationFollow = deps.getAnnotationFollow();
8578
- let activeAnnotationId = null;
8631
+ const initialUrlParams = new URLSearchParams(window.location.search);
8632
+ let activeAnnotationId = initialUrlParams.get(ANNOTATION_ENTRY_PARAM);
8579
8633
  let refreshAnnotationsInFlight = null;
8580
8634
  let inlineAnnotationsMounted = false;
8581
8635
  let databaseAnnotationsMounted = false;
8582
8636
  let annotationPanelDismissed = false;
8583
- let activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
8637
+ let activeSessionId = initialUrlParams.get(ANNOTATION_SESSION_PARAM);
8584
8638
  const annotationsChangedCallbacks = [];
8585
8639
  function notifyAnnotationsChanged() {
8586
8640
  for (const cb of annotationsChangedCallbacks)
@@ -8637,8 +8691,14 @@ ${frontmatter.yaml}
8637
8691
  if (qhPanel)
8638
8692
  qhPanel.hidden = true;
8639
8693
  document.body.classList.remove("query-history-panel-open");
8694
+ } else if (activeAnnotationId) {
8695
+ activeAnnotationId = null;
8696
+ annotationDetail.hidden = true;
8697
+ updateActiveHighlights();
8698
+ syncInlineAnnotationActive();
8640
8699
  }
8641
8700
  deps.setAnnotationPanelOpenState(open);
8701
+ syncSessionUrl();
8642
8702
  applyInlineAnnotations();
8643
8703
  }
8644
8704
  function annotationLineTarget(entry) {
@@ -8686,13 +8746,33 @@ ${frontmatter.yaml}
8686
8746
  }
8687
8747
  function withSessionParam(rawUrl) {
8688
8748
  const url = new URL(rawUrl, window.location.origin);
8749
+ if (!annotationPanel.hidden)
8750
+ url.searchParams.set(ANNOTATION_PANEL_PARAM, "open");
8751
+ else
8752
+ url.searchParams.delete(ANNOTATION_PANEL_PARAM);
8689
8753
  if (activeSessionId)
8690
8754
  url.searchParams.set(ANNOTATION_SESSION_PARAM, activeSessionId);
8691
8755
  else
8692
8756
  url.searchParams.delete(ANNOTATION_SESSION_PARAM);
8757
+ if (activeAnnotationId)
8758
+ url.searchParams.set(ANNOTATION_ENTRY_PARAM, activeAnnotationId);
8759
+ else
8760
+ url.searchParams.delete(ANNOTATION_ENTRY_PARAM);
8693
8761
  return url.pathname + url.search;
8694
8762
  }
8695
- function buildInlineAnnotationRow(entry, colSpan) {
8763
+ function createStepChip(entry, step) {
8764
+ const chip = document.createElement("button");
8765
+ chip.type = "button";
8766
+ chip.className = "gdp-annotation-step";
8767
+ chip.textContent = `${step.index + 1}/${step.total}`;
8768
+ chip.title = `open step ${step.index + 1} of ${step.total}`;
8769
+ chip.setAttribute("aria-label", `open annotation step ${step.index + 1} of ${step.total}`);
8770
+ chip.addEventListener("click", () => {
8771
+ openAnnotationEntry(entry.id);
8772
+ });
8773
+ return chip;
8774
+ }
8775
+ function buildInlineAnnotationRow(entry, colSpan, step) {
8696
8776
  const tr = document.createElement("tr");
8697
8777
  tr.className = "gdp-annotation-row";
8698
8778
  tr.dataset.annotationId = entry.id;
@@ -8701,13 +8781,17 @@ ${frontmatter.yaml}
8701
8781
  td.colSpan = colSpan;
8702
8782
  const box = document.createElement("div");
8703
8783
  box.className = "gdp-annotation-inline";
8784
+ const head = document.createElement("div");
8785
+ head.className = "gdp-annotation-inline-head";
8786
+ head.appendChild(createStepChip(entry, step));
8704
8787
  if (entry.title) {
8705
8788
  const heading2 = document.createElement("strong");
8706
8789
  heading2.className = "gdp-annotation-inline-title";
8707
8790
  heading2.textContent = entry.title;
8708
- box.appendChild(heading2);
8791
+ head.appendChild(heading2);
8709
8792
  }
8710
- box.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8793
+ head.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8794
+ box.appendChild(head);
8711
8795
  const markdown = document.createElement("div");
8712
8796
  markdown.className = "gdp-annotation-inline-body";
8713
8797
  ensureMarkdownHighlighter();
@@ -8717,7 +8801,7 @@ ${frontmatter.yaml}
8717
8801
  tr.appendChild(td);
8718
8802
  return tr;
8719
8803
  }
8720
- function buildDatabaseAnnotationBlock(entry) {
8804
+ function buildDatabaseAnnotationBlock(entry, step) {
8721
8805
  const box = document.createElement("div");
8722
8806
  box.className = "gdp-db-annotation-inline";
8723
8807
  box.dataset.annotationId = entry.id;
@@ -8734,7 +8818,7 @@ ${frontmatter.yaml}
8734
8818
  const location2 = document.createElement("span");
8735
8819
  location2.className = "gdp-db-annotation-inline-location";
8736
8820
  location2.textContent = annotationLocationLabel(entry);
8737
- head.append(title, location2, createCopyRefButton(entry));
8821
+ head.append(createStepChip(entry, step), title, location2, createCopyRefButton(entry));
8738
8822
  const markdown = document.createElement("div");
8739
8823
  markdown.className = "gdp-db-annotation-inline-body";
8740
8824
  ensureMarkdownHighlighter();
@@ -8761,7 +8845,10 @@ ${frontmatter.yaml}
8761
8845
  strip.className = "gdp-db-annotation-strip";
8762
8846
  strip.setAttribute("aria-label", "Datastore annotations");
8763
8847
  for (const entry of matches) {
8764
- strip.appendChild(buildDatabaseAnnotationBlock(entry));
8848
+ strip.appendChild(buildDatabaseAnnotationBlock(entry, {
8849
+ index: session.entries.indexOf(entry),
8850
+ total: session.entries.length
8851
+ }));
8765
8852
  }
8766
8853
  root.prepend(strip);
8767
8854
  databaseAnnotationsMounted = true;
@@ -8836,16 +8923,19 @@ ${frontmatter.yaml}
8836
8923
  if (!session)
8837
8924
  return;
8838
8925
  let mountedInlineRows = false;
8839
- for (const entry of session.entries) {
8926
+ session.entries.forEach((entry, index) => {
8840
8927
  if (entry.target?.kind === "database")
8841
- continue;
8928
+ return;
8842
8929
  const target = inlineAnnotationTargetRow(entry);
8843
8930
  if (!target)
8844
- continue;
8931
+ return;
8845
8932
  let anchor = target;
8846
8933
  while (anchor.nextElementSibling?.classList.contains("gdp-annotation-row"))
8847
8934
  anchor = anchor.nextElementSibling;
8848
- anchor.after(buildInlineAnnotationRow(entry, target.cells.length));
8935
+ anchor.after(buildInlineAnnotationRow(entry, target.cells.length, {
8936
+ index,
8937
+ total: session.entries.length
8938
+ }));
8849
8939
  const sibling = siblingSideRow(target);
8850
8940
  if (sibling) {
8851
8941
  let sibAnchor = sibling;
@@ -8854,7 +8944,7 @@ ${frontmatter.yaml}
8854
8944
  sibAnchor.after(buildInlineSpacerRow(entry, sibling.cells.length));
8855
8945
  }
8856
8946
  mountedInlineRows = true;
8857
- }
8947
+ });
8858
8948
  inlineAnnotationsMounted = mountedInlineRows;
8859
8949
  syncInlineAnnotationWidths();
8860
8950
  }
@@ -8893,14 +8983,21 @@ ${frontmatter.yaml}
8893
8983
  if (activeSessionId === sessionId)
8894
8984
  return;
8895
8985
  activeSessionId = sessionId;
8986
+ activeAnnotationId = null;
8987
+ annotationDetail.hidden = true;
8896
8988
  syncSessionUrl();
8897
8989
  updateActiveHighlights();
8990
+ syncInlineAnnotationActive();
8898
8991
  applyInlineAnnotations();
8899
8992
  notifyAnnotationsChanged();
8900
8993
  }
8901
8994
  function restoreSessionFromUrl() {
8902
- activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
8995
+ const params = new URLSearchParams(window.location.search);
8996
+ activeSessionId = params.get(ANNOTATION_SESSION_PARAM);
8997
+ activeAnnotationId = params.get(ANNOTATION_ENTRY_PARAM);
8998
+ setAnnotationPanelOpen(params.get(ANNOTATION_PANEL_PARAM) === "open" || !!activeAnnotationId);
8903
8999
  renderAnnotationPanel();
9000
+ restoreAnnotationDetailFromState();
8904
9001
  applyInlineAnnotations();
8905
9002
  }
8906
9003
  async function waitForAnnotationDiffTarget(entry) {
@@ -9005,9 +9102,8 @@ ${frontmatter.yaml}
9005
9102
  syncSessionUrl();
9006
9103
  }
9007
9104
  renderAnnotationPanel();
9105
+ restoreAnnotationDetailFromState();
9008
9106
  applyInlineAnnotations();
9009
- if (activeAnnotationId && !findAnnotation(activeAnnotationId))
9010
- hideAnnotationDetail();
9011
9107
  notifyAnnotationsChanged();
9012
9108
  }
9013
9109
  async function postAnnotationAction(payload) {
@@ -9167,6 +9263,10 @@ ${frontmatter.yaml}
9167
9263
  time.className = "annotation-session-time";
9168
9264
  time.textContent = annotationTimeLabel(session.created_at);
9169
9265
  time.title = session.created_at;
9266
+ const count = document.createElement("span");
9267
+ count.className = "annotation-session-count";
9268
+ count.textContent = String(session.entries.length);
9269
+ count.title = `${session.entries.length} steps`;
9170
9270
  title.addEventListener("click", () => {
9171
9271
  setActiveSession(session.id === activeSessionId ? null : session.id);
9172
9272
  });
@@ -9202,7 +9302,7 @@ ${frontmatter.yaml}
9202
9302
  return;
9203
9303
  postAnnotationAction({ action: "delete", id: session.id });
9204
9304
  });
9205
- head.append(title, time, rename, del);
9305
+ head.append(title, count, time, rename, del);
9206
9306
  sessionEl.appendChild(head);
9207
9307
  const list2 = document.createElement("ol");
9208
9308
  list2.className = "annotation-entries";
@@ -9261,6 +9361,7 @@ ${frontmatter.yaml}
9261
9361
  function hideAnnotationDetail() {
9262
9362
  activeAnnotationId = null;
9263
9363
  annotationDetail.hidden = true;
9364
+ syncSessionUrl();
9264
9365
  updateActiveHighlights();
9265
9366
  syncInlineAnnotationActive();
9266
9367
  }
@@ -9304,6 +9405,25 @@ ${frontmatter.yaml}
9304
9405
  updateActiveHighlights();
9305
9406
  syncInlineAnnotationActive();
9306
9407
  }
9408
+ function restoreAnnotationDetailFromState() {
9409
+ if (!activeAnnotationId) {
9410
+ annotationDetail.hidden = true;
9411
+ updateActiveHighlights();
9412
+ syncInlineAnnotationActive();
9413
+ return;
9414
+ }
9415
+ const found = findAnnotation(activeAnnotationId);
9416
+ if (!found) {
9417
+ activeAnnotationId = null;
9418
+ annotationDetail.hidden = true;
9419
+ syncSessionUrl();
9420
+ updateActiveHighlights();
9421
+ syncInlineAnnotationActive();
9422
+ return;
9423
+ }
9424
+ activeSessionId = found.session.id;
9425
+ showAnnotationDetail(found.session, found.entry, found.index);
9426
+ }
9307
9427
  function focusStandaloneSourceLines(card, entry) {
9308
9428
  if (!entry.line)
9309
9429
  return;
@@ -9503,7 +9623,7 @@ ${frontmatter.yaml}
9503
9623
  if (persist)
9504
9624
  deps.setAnnotationPanelWidth(clamped);
9505
9625
  }
9506
- if (deps.getAnnotationPanelOpen())
9626
+ if (initialUrlParams.get(ANNOTATION_PANEL_PARAM) === "open" || activeAnnotationId || deps.getAnnotationPanelOpen())
9507
9627
  setAnnotationPanelOpen(true);
9508
9628
  applyAnnotationPanelWidth(deps.getAnnotationPanelWidth() ?? ANNOTATION_PANEL_DEFAULT_WIDTH, false);
9509
9629
  updateDatabaseCaptureButton();
@@ -9784,6 +9904,9 @@ ${frontmatter.yaml}
9784
9904
  name.appendChild(deps.createFileBreadcrumb(target.path, target.ref));
9785
9905
  name.appendChild(createFilePathCopyButton(target));
9786
9906
  header.appendChild(name);
9907
+ const repositoryWebLink = deps.createRepositoryWebLink?.(target);
9908
+ if (repositoryWebLink)
9909
+ header.appendChild(repositoryWebLink);
9787
9910
  sticky.appendChild(header);
9788
9911
  const tabsHost = document.createElement("div");
9789
9912
  tabsHost.className = "gdp-file-detail-tabs";
@@ -9791,14 +9914,18 @@ ${frontmatter.yaml}
9791
9914
  sticky.appendChild(tabsHost);
9792
9915
  return { sticky, header, tabsHost };
9793
9916
  }
9794
- function mountFileShellCard(deps, target, card, repoTarget = deps.repoFileTargetFromRoute()) {
9917
+ function mountFileShellCard(deps, target, card, repoTarget = deps.repoFileTargetFromRoute(), options = {}) {
9795
9918
  const root = deps.$("#diff");
9796
9919
  if (repoTarget) {
9797
9920
  const layout = document.createElement("div");
9798
9921
  layout.className = "gdp-repo-blob-layout";
9799
9922
  layout.appendChild(card);
9800
9923
  root.prepend(layout);
9801
- Promise.resolve(deps.renderRepoBlobSidebar(target.path, repoTarget)).catch(() => {}).finally(() => deps.placeSidebarToggle());
9924
+ if (options.loadSidebar !== false) {
9925
+ Promise.resolve(deps.renderRepoBlobSidebar(target.path, repoTarget)).catch((error2) => {
9926
+ console.error("[code-viewer] failed to load repository sidebar", error2);
9927
+ }).finally(() => deps.placeSidebarToggle());
9928
+ }
9802
9929
  } else {
9803
9930
  root.prepend(card);
9804
9931
  }
@@ -9982,7 +10109,8 @@ ${frontmatter.yaml}
9982
10109
  currentRange: deps.currentRange,
9983
10110
  setRoute: deps.setRoute,
9984
10111
  setPreferredSourceTab: deps.setPreferredSourceTab,
9985
- createFileBreadcrumb: deps.createFileBreadcrumb
10112
+ createFileBreadcrumb: deps.createFileBreadcrumb,
10113
+ createRepositoryWebLink: deps.createRepositoryWebLink
9986
10114
  }, target, "blame");
9987
10115
  sticky.appendChild(buildLegend());
9988
10116
  wrapper.appendChild(sticky);
@@ -9992,10 +10120,11 @@ ${frontmatter.yaml}
9992
10120
  wrapper.appendChild(body);
9993
10121
  card.appendChild(wrapper);
9994
10122
  mountFileShellCard(deps, target, card);
10123
+ const sourceShikiLang = normalizeSourceShikiLang(deps.inferLang(target.path));
9995
10124
  const [blameResp, srcText, highlighter] = await Promise.all([
9996
10125
  fetchBlame(target, generation),
9997
10126
  fetchSource(target, base2),
9998
- deps.getSyntaxHighlight() ? deps.loadSourceShikiHighlighter() : Promise.resolve(null)
10127
+ deps.getSyntaxHighlight() && sourceShikiLang ? deps.loadSourceShikiHighlighter(sourceShikiLang) : Promise.resolve(null)
9999
10128
  ]);
10000
10129
  if (generation !== activeGeneration)
10001
10130
  return;
@@ -17925,6 +18054,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17925
18054
  spacer.className = "db-grid-spacer";
17926
18055
  const body = document.createElement("div");
17927
18056
  body.className = "db-grid-body";
18057
+ body.addEventListener("mouseover", (e2) => {
18058
+ const cell = e2.target?.closest(".db-grid-cell");
18059
+ if (!cell || !body.contains(cell))
18060
+ return;
18061
+ if (cell.dataset.gridTooltip) {
18062
+ delete cell.dataset.gridTooltip;
18063
+ cell.removeAttribute("title");
18064
+ }
18065
+ if (cell.title)
18066
+ return;
18067
+ if (cell.querySelector("input, textarea, select"))
18068
+ return;
18069
+ if (cell.scrollWidth > cell.clientWidth + 1) {
18070
+ cell.title = cell.textContent ?? "";
18071
+ cell.dataset.gridTooltip = "1";
18072
+ }
18073
+ });
17928
18074
  const filteredEmpty = document.createElement("div");
17929
18075
  filteredEmpty.className = "db-pane-empty";
17930
18076
  filteredEmpty.hidden = true;
@@ -18113,8 +18259,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18113
18259
  }
18114
18260
  }
18115
18261
  }
18262
+ function defaultColWidth(colName) {
18263
+ const type = (columns.find((c2) => c2.name === colName)?.type ?? "").toLowerCase();
18264
+ let w = DEFAULT_COL_WIDTH;
18265
+ if (/timestamp|datetime/.test(type))
18266
+ w = 210;
18267
+ else if (/^(date|time|year)/.test(type))
18268
+ w = 120;
18269
+ else if (/bool|tinyint\(1\)/.test(type))
18270
+ w = 80;
18271
+ else if (/int|decimal|numeric|float|double|real/.test(type))
18272
+ w = 110;
18273
+ else if (/text|json|xml|blob/.test(type))
18274
+ w = 260;
18275
+ return Math.max(w, Math.min(240, colName.length * 7.5 + 34));
18276
+ }
18116
18277
  function getColWidth(colName) {
18117
- return colWidths.get(colName) ?? DEFAULT_COL_WIDTH;
18278
+ return colWidths.get(colName) ?? defaultColWidth(colName);
18118
18279
  }
18119
18280
  function applyColWidth(colName, index) {
18120
18281
  const w = getColWidth(colName);
@@ -22411,7 +22572,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22411
22572
  });
22412
22573
  chip.addEventListener("dragend", () => clearDragState());
22413
22574
  }
22414
- tabsList.addEventListener("dragover", (e2) => {
22575
+ tabsBar.addEventListener("dragover", (e2) => {
22415
22576
  if (!draggingTabId)
22416
22577
  return;
22417
22578
  const target = e2.target?.closest(".db-tabs-chip");
@@ -22422,7 +22583,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22422
22583
  e2.dataTransfer.dropEffect = "move";
22423
22584
  clearDropTarget();
22424
22585
  });
22425
- tabsList.addEventListener("drop", (e2) => {
22586
+ tabsBar.addEventListener("drop", (e2) => {
22426
22587
  if (!draggingTabId)
22427
22588
  return;
22428
22589
  const target = e2.target?.closest(".db-tabs-chip");
@@ -23066,7 +23227,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23066
23227
  if (!el)
23067
23228
  return;
23068
23229
  el.hidden = !branch;
23069
- el.textContent = branch;
23230
+ const name = el.querySelector(".project-branch-name");
23231
+ if (name)
23232
+ name.textContent = branch;
23070
23233
  el.title = branch ? `Current branch: ${branch}` : "";
23071
23234
  }
23072
23235
  let metaFilesForViewedProgress = [];
@@ -23773,7 +23936,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23773
23936
  if (fresh && card.isConnected)
23774
23937
  enqueueLoad(fresh, card, 0);
23775
23938
  };
23776
- return trackLoad(fetch(url).then(async (r2) => {
23939
+ const request = trackLoad(fetch(url).then(async (r2) => {
23777
23940
  if (!r2.ok)
23778
23941
  throw new Error(await r2.text());
23779
23942
  return r2.json();
@@ -23793,9 +23956,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23793
23956
  if (String(myReq) !== card.dataset.reqId)
23794
23957
  return;
23795
23958
  renderFile(file, data, card);
23796
- }).catch(() => {
23959
+ }).catch((error2) => {
23797
23960
  if (String(myReq) !== card.dataset.reqId)
23798
23961
  return;
23962
+ console.error("[code-viewer] failed to load diff", error2);
23799
23963
  card.classList.remove("loading");
23800
23964
  card.classList.add("error");
23801
23965
  card.style.minHeight = "";
@@ -23812,13 +23976,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23812
23976
  enqueueLoad(file, card, 1);
23813
23977
  });
23814
23978
  });
23979
+ const completed = request.finally(() => {
23980
+ if (card._loadPromise === completed)
23981
+ delete card._loadPromise;
23982
+ });
23983
+ card._loadPromise = completed;
23984
+ return completed;
23815
23985
  }
23816
23986
  async function loadDiffFile(path) {
23817
- const card = document.querySelector(diffCardSelector(path));
23987
+ const root = getDiffRoot?.() || document;
23988
+ const card = root.querySelector(diffCardSelector(path));
23818
23989
  if (!card || card.classList.contains("gdp-standalone-source"))
23819
23990
  return false;
23820
23991
  if (card.classList.contains("loaded"))
23821
23992
  return true;
23993
+ if (card.classList.contains("loading") && card._loadPromise) {
23994
+ await card._loadPromise;
23995
+ return card.classList.contains("loaded");
23996
+ }
23822
23997
  const file = card._file || STATE.files.find((x) => x.path === path);
23823
23998
  if (!file)
23824
23999
  return false;
@@ -23874,9 +24049,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23874
24049
  const unfold = card.querySelector(".gdp-file-unfold");
23875
24050
  if (unfold)
23876
24051
  unfold.disabled = collapsed;
23877
- const viewFile = card.querySelector(".gdp-view-file");
23878
- if (viewFile)
23879
- viewFile.disabled = collapsed;
24052
+ card.querySelectorAll(".gdp-preview-file, .gdp-view-file").forEach((button2) => {
24053
+ button2.disabled = collapsed;
24054
+ });
23880
24055
  }
23881
24056
  function setViewFileButtonState(button, sourceMode) {
23882
24057
  if (!button)
@@ -24076,6 +24251,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
24076
24251
  wrap.appendChild(box);
24077
24252
  }
24078
24253
  header.appendChild(wrap);
24254
+ if (!header.querySelector(".gdp-preview-file") && isPreviewableSource(fileSourceTarget(file).path)) {
24255
+ const previewFile = document.createElement("button");
24256
+ previewFile.type = "button";
24257
+ previewFile.className = "gdp-preview-file gdp-btn gdp-btn-sm";
24258
+ previewFile.textContent = "Preview";
24259
+ previewFile.title = "Preview rendered file";
24260
+ previewFile.addEventListener("click", (e2) => {
24261
+ e2.stopPropagation();
24262
+ const target = fileSourceTarget(file);
24263
+ setRoute({
24264
+ screen: "file",
24265
+ path: target.path,
24266
+ ref: target.ref,
24267
+ view: "blob",
24268
+ preview: true,
24269
+ range: currentRange()
24270
+ });
24271
+ applySourceRouteToShell();
24272
+ });
24273
+ header.appendChild(previewFile);
24274
+ }
24079
24275
  if (!header.querySelector(".gdp-view-file")) {
24080
24276
  const viewFile = document.createElement("button");
24081
24277
  viewFile.type = "button";
@@ -25609,7 +25805,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
25609
25805
  (el.closest(".gdp-repo-blob-layout") || el).remove();
25610
25806
  });
25611
25807
  }
25612
- function renderFileHistoryShell(deps, route) {
25808
+ function renderFileHistoryShell(deps, route, options = {}) {
25613
25809
  removeFileHistoryShell();
25614
25810
  const target = { path: route.path, ref: route.ref };
25615
25811
  const card = document.createElement("article");
@@ -25645,7 +25841,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
25645
25841
  body.appendChild(embed);
25646
25842
  wrapper.appendChild(body);
25647
25843
  card.appendChild(wrapper);
25648
- mountFileShellCard(deps, target, card, target.ref);
25844
+ mountFileShellCard(deps, target, card, target.ref, options);
25649
25845
  return {
25650
25846
  ...panelDom,
25651
25847
  commitInfo,
@@ -26054,6 +26250,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26054
26250
  kind: "paragraph",
26055
26251
  text: "A file detail page exposes up to four tabs — Preview, Code, Blame, History. For text files Code is the default and ?preview=1 opts in to the Markdown / HTML preview; media files (images, video, audio, PDF) show a Preview tab only, with no Code tab. Blame and History have their own canonical URLs (view=blame, view=history) so deep links and the browser back/forward stay in sync, and they keep the Repository sidebar visible."
26056
26252
  },
26253
+ {
26254
+ kind: "paragraph",
26255
+ text: "When the repository remote is on GitHub, repository and file headers can open the current path there. Selecting source lines also exposes separate actions to copy the AI reference, open the exact GitHub line range, or copy its URL. Markdown and HTML diff cards include a direct Preview shortcut."
26256
+ },
26057
26257
  {
26058
26258
  kind: "paragraph",
26059
26259
  text: "In large repositories the sidebar loads folder children on demand. Folders you open are remembered and automatically re-expanded after a reload."
@@ -26145,6 +26345,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26145
26345
  kind: "paragraph",
26146
26346
  text: "Everything in .code-viewer/ is owned by code-viewer. JSON files are rewritten safely with validation on every change, so unknown keys are dropped and invalid values are reset to defaults. Hand-edit at your own risk — a corrupt file is renamed with a .corrupt suffix and replaced with an empty default."
26147
26347
  },
26348
+ {
26349
+ kind: "paragraph",
26350
+ text: "The directory appears in the repository tree and its text files can be inspected in the Code view. It remains excluded from repository searches and diffs."
26351
+ },
26148
26352
  {
26149
26353
  kind: "paragraph",
26150
26354
  text: "Deleting the whole directory is the supported way to reset all per-repository state. Removing a single file resets only that subsystem (for example, deleting tabs.json closes all DB tabs on the next load)."
@@ -26253,6 +26457,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26253
26457
  [
26254
26458
  "Persistent state",
26255
26459
  "Open/closed, width, follow, mute, and rate are stored under .code-viewer/settings.json; annotations themselves live in .code-viewer/annotations.json."
26460
+ ],
26461
+ [
26462
+ "Restorable URL",
26463
+ "The panel-open state, selected session, and selected annotation are kept in the URL, so reloads and shared links restore the same walkthrough context."
26256
26464
  ]
26257
26465
  ]
26258
26466
  }
@@ -26725,6 +26933,10 @@ code-viewer query agent-help`
26725
26933
  kind: "paragraph",
26726
26934
  text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
26727
26935
  },
26936
+ {
26937
+ kind: "paragraph",
26938
+ text: "リポジトリの remote が GitHub の場合、リポジトリとファイルのヘッダーから現在のパスを GitHub で開けます。ソース行を選択すると、AI 参照のコピー、選択行範囲を GitHub で開く、GitHub URL のコピーを個別に選べます。Markdown / HTML の diff カードには直接 Preview を開く導線も表示されます。"
26939
+ },
26728
26940
  {
26729
26941
  kind: "paragraph",
26730
26942
  text: "大きいリポジトリではサイドバーがフォルダの中身を必要に応じて読み込みます。開いたフォルダは記憶され、次回のリロード時に同じ状態で展開し直されます。"
@@ -26816,6 +27028,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26816
27028
  kind: "paragraph",
26817
27029
  text: ".code-viewer/ の中身はすべて code-viewer が管理します。JSON ファイルは書き込みごとにバリデーションを通して安全に書き換えられるため、未知のキーは破棄され、不正な値は既定値に戻されます。手で編集すると壊れる可能性があり、壊れたファイルは .corrupt サフィックスに改名されて空の既定値で置き換えられます。"
26818
27030
  },
27031
+ {
27032
+ kind: "paragraph",
27033
+ text: "ディレクトリはリポジトリツリーに表示され、テキストファイルは Code ビューで確認できます。リポジトリ検索と diff の対象からは引き続き除外されます。"
27034
+ },
26819
27035
  {
26820
27036
  kind: "paragraph",
26821
27037
  text: "ディレクトリごと削除するのが、このリポジトリの全状態をリセットする推奨手順です。個別ファイルだけを消した場合はその系統だけがリセットされます(例: tabs.json を消すと次回起動時に DB タブがすべて閉じた状態になります)。"
@@ -26924,6 +27140,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26924
27140
  [
26925
27141
  "永続化",
26926
27142
  "パネルの開閉・幅・follow・ミュート・速度は .code-viewer/settings.json に、注釈自体は .code-viewer/annotations.json に保存されます。"
27143
+ ],
27144
+ [
27145
+ "復元できる URL",
27146
+ "パネルの開閉、選択中のセッション、選択中の注釈は URL に反映されるため、リロードや共有リンクでも同じウォークスルー状態を復元できます。"
26927
27147
  ]
26928
27148
  ]
26929
27149
  }
@@ -28206,7 +28426,7 @@ code-viewer query agent-help`
28206
28426
  let mounted = false;
28207
28427
  let lifecycle = 0;
28208
28428
  let data = null;
28209
- let activeTab = "journal";
28429
+ let activeTab = "tasks";
28210
28430
  let selectedDate = todayIsoDate();
28211
28431
  let selectedEntryId = "";
28212
28432
  let creatingEntry = false;
@@ -28379,7 +28599,7 @@ code-viewer query agent-help`
28379
28599
  function applyRoute(route = deps.getRoute()) {
28380
28600
  if (!isJournalRoute(route))
28381
28601
  return;
28382
- activeTab = route.tab || activeTab || "journal";
28602
+ activeTab = route.tab || activeTab || "tasks";
28383
28603
  selectedDate = route.date || selectedDate || todayIsoDate();
28384
28604
  labelFilter = route.label || "";
28385
28605
  selectedTaskId = route.task || "";
@@ -29942,21 +30162,50 @@ code-viewer query agent-help`
29942
30162
  const copyButton = document.createElement("button");
29943
30163
  copyButton.id = "line-ref-pill-copy";
29944
30164
  copyButton.type = "button";
29945
- copyButton.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
29946
30165
  const closeButton = document.createElement("button");
29947
30166
  closeButton.id = "line-ref-pill-close";
29948
30167
  closeButton.type = "button";
29949
30168
  closeButton.title = "選択を解除";
29950
30169
  closeButton.setAttribute("aria-label", "選択を解除");
29951
30170
  closeButton.textContent = "×";
29952
- pill.appendChild(copyButton);
29953
- pill.appendChild(closeButton);
30171
+ const githubActions = document.createElement("div");
30172
+ githubActions.id = "line-ref-pill-github-actions";
30173
+ githubActions.hidden = true;
30174
+ const githubOpen = document.createElement("a");
30175
+ githubOpen.id = "line-ref-pill-github-open";
30176
+ githubOpen.target = "_blank";
30177
+ githubOpen.rel = "noopener";
30178
+ githubOpen.innerHTML = iconSvg("octicon-mark-github", MARK_GITHUB_16_PATH) + '<span class="lrp-github-open-label"></span>' + iconSvg("octicon-link-external", OPEN_EXTERNAL_16_PATH);
30179
+ const githubCopy = document.createElement("button");
30180
+ githubCopy.id = "line-ref-pill-github-copy";
30181
+ githubCopy.type = "button";
30182
+ githubCopy.innerHTML = `<span class="lrp-github-copy-icon">${iconSvg("octicon-copy", COPY_16_PATHS)}</span>` + '<span class="lrp-github-copy-label"></span>';
30183
+ githubActions.append(githubOpen, githubCopy);
30184
+ pill.append(copyButton, githubActions, closeButton);
29954
30185
  document.body.appendChild(pill);
29955
30186
  let refText = "";
29956
30187
  let currentPath = "";
29957
30188
  let currentStart = 0;
29958
30189
  let currentEnd = 0;
30190
+ let githubUrl = "";
29959
30191
  let feedbackTimer = null;
30192
+ let githubFeedbackTimer = null;
30193
+ function renderGithubActions() {
30194
+ const openTitle = deps.githubOpenTitle();
30195
+ const copyTitle = deps.githubCopyTitle();
30196
+ githubOpen.title = openTitle;
30197
+ githubOpen.setAttribute("aria-label", openTitle);
30198
+ githubCopy.title = copyTitle;
30199
+ githubCopy.setAttribute("aria-label", copyTitle);
30200
+ const openLabel = githubOpen.querySelector(".lrp-github-open-label");
30201
+ if (openLabel)
30202
+ openLabel.textContent = openTitle;
30203
+ const copyLabel = githubCopy.querySelector(".lrp-github-copy-label");
30204
+ if (copyLabel)
30205
+ copyLabel.textContent = copyTitle;
30206
+ githubOpen.href = githubUrl || "#";
30207
+ githubActions.hidden = !githubUrl;
30208
+ }
29960
30209
  function selectionLineCount() {
29961
30210
  return currentEnd - currentStart + 1;
29962
30211
  }
@@ -29965,25 +30214,16 @@ code-viewer query agent-help`
29965
30214
  if (count <= 1)
29966
30215
  return "";
29967
30216
  const warn = count >= AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD;
29968
- return `<span class="lrp-count${warn ? " lrp-count-warn" : ""}">${count} lines</span>`;
30217
+ return `<span class="lrp-count${warn ? " lrp-count-warn" : ""}">${escapeHtml2(deps.lineCountLabel(count))}</span>`;
29969
30218
  }
29970
30219
  function render(state) {
29971
- pill.classList.toggle("copied", state === "copied" || state === "copied-code");
29972
- if (state === "copied") {
29973
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
29974
- return;
29975
- }
29976
- if (state === "copied-code") {
29977
- const count = selectionLineCount();
29978
- const suffix = count > 1 ? ` (${count} lines)` : "";
29979
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code${suffix}</span>`;
29980
- return;
29981
- }
29982
- if (state === "failed") {
29983
- copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
29984
- return;
29985
- }
29986
- copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>` + countBadgeHtml();
30220
+ copyButton.classList.toggle("copied", state === "copied" || state === "copied-code");
30221
+ copyButton.classList.toggle("failed", state === "failed");
30222
+ const icon = state === "copied" || state === "copied-code" ? CHECK_ICON : COPY_ICON;
30223
+ const label = deps.copyReferenceLabel();
30224
+ copyButton.innerHTML = `${icon}<span class="lrp-label">${escapeHtml2(label)}</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>` + countBadgeHtml();
30225
+ copyButton.title = label;
30226
+ copyButton.setAttribute("aria-label", label);
29987
30227
  }
29988
30228
  copyButton.addEventListener("click", async (event) => {
29989
30229
  if (!refText)
@@ -30012,6 +30252,28 @@ code-viewer query agent-help`
30012
30252
  render("ready");
30013
30253
  }, 1200);
30014
30254
  });
30255
+ githubCopy.addEventListener("click", async () => {
30256
+ if (!githubUrl)
30257
+ return;
30258
+ try {
30259
+ await navigator.clipboard.writeText(githubUrl);
30260
+ githubCopy.classList.add("copied");
30261
+ const icon = githubCopy.querySelector(".lrp-github-copy-icon");
30262
+ if (icon)
30263
+ icon.innerHTML = CHECK_ICON;
30264
+ } catch {
30265
+ githubCopy.classList.add("failed");
30266
+ }
30267
+ if (githubFeedbackTimer)
30268
+ clearTimeout(githubFeedbackTimer);
30269
+ githubFeedbackTimer = setTimeout(() => {
30270
+ githubFeedbackTimer = null;
30271
+ githubCopy.classList.remove("copied", "failed");
30272
+ const icon = githubCopy.querySelector(".lrp-github-copy-icon");
30273
+ if (icon)
30274
+ icon.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
30275
+ }, 1200);
30276
+ });
30015
30277
  closeButton.addEventListener("click", () => {
30016
30278
  deps.onClose();
30017
30279
  });
@@ -30025,6 +30287,8 @@ code-viewer query agent-help`
30025
30287
  currentPath = path;
30026
30288
  currentStart = Math.max(1, Math.floor(Math.min(start, end)));
30027
30289
  currentEnd = Math.max(1, Math.floor(Math.max(start, end)));
30290
+ githubUrl = deps.githubUrlForSelection(currentPath, currentStart, currentEnd) || "";
30291
+ renderGithubActions();
30028
30292
  if (feedbackTimer) {
30029
30293
  clearTimeout(feedbackTimer);
30030
30294
  feedbackTimer = null;
@@ -30042,6 +30306,8 @@ code-viewer query agent-help`
30042
30306
  currentPath = "";
30043
30307
  currentStart = 0;
30044
30308
  currentEnd = 0;
30309
+ githubUrl = "";
30310
+ githubActions.hidden = true;
30045
30311
  pill.hidden = true;
30046
30312
  }
30047
30313
  };
@@ -30161,14 +30427,34 @@ code-viewer query agent-help`
30161
30427
  if (!popBody || !popSearch)
30162
30428
  return;
30163
30429
  let popTarget = null;
30430
+ let refsLoaded = false;
30431
+ let refsLoading = false;
30432
+ let refsError = false;
30433
+ let refsInFlight = null;
30164
30434
  function fetchRefs() {
30165
- return fetch("/_refs").then((r2) => r2.json()).then((refs) => {
30435
+ if (refsLoaded)
30436
+ return Promise.resolve();
30437
+ if (refsInFlight)
30438
+ return refsInFlight;
30439
+ refsLoading = true;
30440
+ refsError = false;
30441
+ const request = fetch("/_refs").then((r2) => r2.json()).then((refs) => {
30166
30442
  Object.assign(REFS, refs);
30167
- }).catch(() => {
30168
- return;
30443
+ refsLoaded = true;
30444
+ if (!popover.hidden && popTab !== "commits")
30445
+ buildPopBody(popSearch.value);
30446
+ }).catch((error2) => {
30447
+ refsError = true;
30448
+ console.error("[code-viewer] failed to load refs", error2);
30449
+ }).finally(() => {
30450
+ refsLoading = false;
30451
+ refsInFlight = null;
30452
+ if (!popover.hidden && popTab !== "commits")
30453
+ buildPopBody(popSearch.value);
30169
30454
  });
30455
+ refsInFlight = request;
30456
+ return request;
30170
30457
  }
30171
- fetchRefs();
30172
30458
  let popTab = "commits";
30173
30459
  const COMMIT_PAGE_SIZE = 50;
30174
30460
  let commitSearchTimer = null;
@@ -30309,6 +30595,10 @@ code-viewer query agent-help`
30309
30595
  } else if (commitHasMore) {
30310
30596
  html.push('<div class="rp-empty">scroll for more commits...</div>');
30311
30597
  }
30598
+ } else if (refsLoading) {
30599
+ html.push('<div class="rp-empty">loading refs...</div>');
30600
+ } else if (refsError) {
30601
+ html.push('<div class="rp-empty">failed to load refs</div>');
30312
30602
  } else if (popTab === "branches") {
30313
30603
  const branches = (REFS.branches || []).filter((b2) => m(b2.name));
30314
30604
  if (!branches.length) {
@@ -30443,6 +30733,14 @@ code-viewer query agent-help`
30443
30733
  });
30444
30734
  if (popTab === "commits")
30445
30735
  scheduleCommitSearch(popSearch.value);
30736
+ else {
30737
+ if (commitSearchTimer) {
30738
+ clearTimeout(commitSearchTimer);
30739
+ commitSearchTimer = null;
30740
+ }
30741
+ commitSearchLoading = false;
30742
+ fetchRefs();
30743
+ }
30446
30744
  buildPopBody(popSearch.value);
30447
30745
  });
30448
30746
  });
@@ -30479,6 +30777,19 @@ code-viewer query agent-help`
30479
30777
  return trimmed;
30480
30778
  }
30481
30779
 
30780
+ // web-src/views/repository-web-link.ts
30781
+ function createRepositoryWebLink(target, label) {
30782
+ const link2 = document.createElement("a");
30783
+ link2.className = "gdp-btn gdp-btn-sm gdp-repo-web-link";
30784
+ link2.href = target.url;
30785
+ link2.target = "_blank";
30786
+ link2.rel = "noopener";
30787
+ link2.title = label;
30788
+ link2.setAttribute("aria-label", label);
30789
+ link2.innerHTML = `${iconSvg(target.provider === "github" ? "octicon-mark-github" : "octicon-link-external", target.provider === "github" ? MARK_GITHUB_16_PATH : OPEN_EXTERNAL_16_PATH)}<span>${label}</span>`;
30790
+ return link2;
30791
+ }
30792
+
30482
30793
  // web-src/core/file-filter.ts
30483
30794
  function normalizeFileFilterQuery(value) {
30484
30795
  return (value || "").toLowerCase().trim();
@@ -32190,6 +32501,9 @@ code-viewer query agent-help`
32190
32501
  repositoryFallback,
32191
32502
  repositoryRootFallback,
32192
32503
  commitEntryMeta,
32504
+ repositoryWebTarget,
32505
+ openGithubLabel,
32506
+ openRepositoryWebLabel,
32193
32507
  fileBadge
32194
32508
  } = deps;
32195
32509
  let REPO_SORT = {
@@ -32345,6 +32659,13 @@ code-viewer query agent-help`
32345
32659
  });
32346
32660
  return button;
32347
32661
  }
32662
+ function createRepositoryWebLink2(path, ref) {
32663
+ const target = repositoryWebTarget(path, ref);
32664
+ if (!target)
32665
+ return null;
32666
+ const label = target.provider === "github" ? openGithubLabel() : openRepositoryWebLabel();
32667
+ return createRepositoryWebLink(target, label);
32668
+ }
32348
32669
  function showRepoContextMenu(event, entry, ref, onChanged) {
32349
32670
  if (document.querySelector(".gdp-dialog-backdrop"))
32350
32671
  return false;
@@ -32582,6 +32903,9 @@ code-viewer query agent-help`
32582
32903
  pathHeader.appendChild(createCopyPathButton(meta.path));
32583
32904
  pathHeader.appendChild(createOpenPathButton(meta.path || "", "directory", openDirectoryInOsTitle()));
32584
32905
  toolbar.appendChild(pathHeader);
32906
+ const repositoryWebLink = createRepositoryWebLink2(meta.path || "", meta.ref);
32907
+ if (repositoryWebLink)
32908
+ toolbar.appendChild(repositoryWebLink);
32585
32909
  if (canTrashWorktreeRef(meta.ref)) {
32586
32910
  toolbar.appendChild(createNewFolderButton(meta.path || "", () => loadRepo()));
32587
32911
  if (meta.path) {
@@ -33935,6 +34259,7 @@ code-viewer query agent-help`
33935
34259
  renderRepoBlobSidebar,
33936
34260
  placeSidebarToggle,
33937
34261
  createFileBreadcrumb,
34262
+ createRepositoryWebLink: createRepositoryWebLink2,
33938
34263
  createFileDetailMeta,
33939
34264
  createOpenPathButton,
33940
34265
  createMoveToTrashButton,
@@ -33953,7 +34278,6 @@ code-viewer query agent-help`
33953
34278
  const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
33954
34279
  const UNKNOWN_TEXT_SNIFF_BYTES = 8192;
33955
34280
  const SOURCE_LOADING_SLOW_THRESHOLD_MS = 3000;
33956
- let sourceShikiLoadPromise = null;
33957
34281
  let PREFERRED_SOURCE_TAB = null;
33958
34282
  let SOURCE_CURSOR = null;
33959
34283
  let SOURCE_CURSOR_ROWS = [];
@@ -33966,8 +34290,13 @@ code-viewer query agent-help`
33966
34290
  if (sourceRow)
33967
34291
  return sourceRow.getBoundingClientRect().height || 20;
33968
34292
  const preview = document.querySelector("#content .gdp-markdown-preview:not([hidden])");
33969
- const lineHeight = Number.parseFloat(getComputedStyle(preview || document.body).lineHeight);
33970
- return Number.isFinite(lineHeight) && lineHeight > 0 ? lineHeight : 20;
34293
+ if (preview) {
34294
+ const previewLineHeight = Number.parseFloat(getComputedStyle(preview).lineHeight);
34295
+ if (Number.isFinite(previewLineHeight) && previewLineHeight > 0)
34296
+ return previewLineHeight;
34297
+ }
34298
+ const configuredLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
34299
+ return Number.isFinite(configuredLineHeight) && configuredLineHeight > 0 ? configuredLineHeight : VIRTUAL_SOURCE_ROW_HEIGHT;
33971
34300
  }
33972
34301
  function hasVisibleSourceCodeSurface() {
33973
34302
  return Array.from(document.querySelectorAll("#content .gdp-source-virtual-scroller, #content .gdp-source-table")).some((item) => item.offsetParent !== null);
@@ -34008,8 +34337,9 @@ code-viewer query agent-help`
34008
34337
  }
34009
34338
  function visibleSourceLineFallback() {
34010
34339
  const scroller = findMainScrollTarget();
34340
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
34011
34341
  if (scroller)
34012
- return Math.max(1, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) + 1);
34342
+ return Math.max(1, Math.floor(scroller.scrollTop / rowHeight) + 1);
34013
34343
  const rows = $$("#content .gdp-source-table tr[data-line]");
34014
34344
  const contentTop = document.querySelector("#content")?.getBoundingClientRect().top ?? 0;
34015
34345
  const row = rows.find((item) => item.getBoundingClientRect().bottom >= Math.max(0, contentTop));
@@ -34033,8 +34363,9 @@ code-viewer query agent-help`
34033
34363
  function scrollSourceCursorIntoView(cursor, edge = "nearest") {
34034
34364
  const scroller = findMainScrollTarget();
34035
34365
  if (scroller) {
34036
- const top = (cursor.line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT;
34037
- const bottom = top + VIRTUAL_SOURCE_ROW_HEIGHT;
34366
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
34367
+ const top = (cursor.line - 1) * rowHeight;
34368
+ const bottom = top + rowHeight;
34038
34369
  const before = scroller.scrollTop;
34039
34370
  if (edge === "center")
34040
34371
  scroller.scrollTop = Math.max(0, top - Math.round(scroller.clientHeight / 2));
@@ -34127,86 +34458,13 @@ code-viewer query agent-help`
34127
34458
  focusMainPanel();
34128
34459
  return true;
34129
34460
  }
34130
- const SOURCE_SHIKI_LANGS = Array.from(new Set([
34131
- "asciidoc",
34132
- "astro",
34133
- "bash",
34134
- "bibtex",
34135
- "c",
34136
- "clojure",
34137
- "cmake",
34138
- "cpp",
34139
- "csharp",
34140
- "css",
34141
- "dart",
34142
- "diff",
34143
- "dockerfile",
34144
- "dotenv",
34145
- "elixir",
34146
- "erb",
34147
- "erlang",
34148
- "fortran",
34149
- "go",
34150
- "gradle",
34151
- "graphql",
34152
- "haml",
34153
- "handlebars",
34154
- "haskell",
34155
- "html",
34156
- "ini",
34157
- "java",
34158
- "javascript",
34159
- "json",
34160
- "jsonc",
34161
- "julia",
34162
- "kotlin",
34163
- "less",
34164
- "liquid",
34165
- "lua",
34166
- "make",
34167
- "markdown",
34168
- "nix",
34169
- "ocaml",
34170
- "pascal",
34171
- "perl",
34172
- "php",
34173
- "powershell",
34174
- "prisma",
34175
- "properties",
34176
- "protobuf",
34177
- "pug",
34178
- "python",
34179
- "r",
34180
- "rst",
34181
- "ruby",
34182
- "rust",
34183
- "sass",
34184
- "scala",
34185
- "scss",
34186
- "sql",
34187
- "swift",
34188
- "terraform",
34189
- "tex",
34190
- "toml",
34191
- "twig",
34192
- "typescript",
34193
- "vim",
34194
- "vue",
34195
- "xml",
34196
- "yaml"
34197
- ]));
34198
- function loadSourceShikiHighlighter() {
34199
- if (!sourceShikiLoadPromise) {
34200
- sourceShikiLoadPromise = import("/shiki.js").then((mod) => {
34201
- const typed = mod;
34202
- const langs = typed.bundledLanguages ? SOURCE_SHIKI_LANGS.filter((lang) => !!typed.bundledLanguages?.[lang]) : SOURCE_SHIKI_LANGS;
34203
- return typed.createHighlighter({
34204
- themes: ["github-light", "github-dark"],
34205
- langs
34206
- });
34207
- }).catch(() => null);
34208
- }
34209
- return sourceShikiLoadPromise;
34461
+ function loadSourceShikiHighlighter(lang) {
34462
+ if (deps.loadSourceHighlighter)
34463
+ return deps.loadSourceHighlighter(lang);
34464
+ return loadShikiHighlighter({
34465
+ themes: ["github-light", "github-dark"],
34466
+ langs: [lang]
34467
+ });
34210
34468
  }
34211
34469
  function sourceShikiLines(textValue, lang, highlighter) {
34212
34470
  try {
@@ -34225,6 +34483,57 @@ code-viewer query agent-help`
34225
34483
  return null;
34226
34484
  }
34227
34485
  }
34486
+ function scheduleSourceHighlight(table2, target, textValue, lang, signal) {
34487
+ setTimeout(() => {
34488
+ if (signal?.aborted || !table2.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34489
+ return;
34490
+ loadSourceShikiHighlighter(lang).then((highlighter) => {
34491
+ if (!highlighter || signal?.aborted || !table2.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34492
+ return;
34493
+ const highlightedLines = sourceShikiLines(textValue, lang, highlighter);
34494
+ if (!highlightedLines || signal?.aborted || !table2.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34495
+ return;
34496
+ const cells = table2.querySelectorAll(".gdp-source-line-code");
34497
+ cells.forEach((cell, index) => {
34498
+ if (highlightedLines[index] == null)
34499
+ return;
34500
+ cell.innerHTML = highlightedLines[index] || " ";
34501
+ cell.classList.add("shiki");
34502
+ });
34503
+ }).catch((err) => {
34504
+ console.error("Failed to apply source syntax highlighting", err);
34505
+ });
34506
+ }, 0);
34507
+ }
34508
+ function scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, onReplace) {
34509
+ setTimeout(() => {
34510
+ if (signal?.aborted || !preview.isConnected || !STATE.syntaxHighlight || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34511
+ return;
34512
+ (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34513
+ syntaxHighlight: true,
34514
+ signal,
34515
+ onNavigateMarkdown: (path, ref) => {
34516
+ setRoute({
34517
+ screen: "file",
34518
+ path,
34519
+ ref,
34520
+ view: "blob",
34521
+ range: currentRange()
34522
+ });
34523
+ renderStandaloneSource({ path, ref });
34524
+ }
34525
+ }).then((next) => {
34526
+ if (signal?.aborted || !preview.isConnected || !sourceTargetsEqual(sourceTargetFromRoute(), target))
34527
+ return;
34528
+ next.dataset.sourcePane = "preview";
34529
+ next.hidden = preview.hidden;
34530
+ preview.replaceWith(next);
34531
+ onReplace(next);
34532
+ }).catch((err) => {
34533
+ console.error("Failed to apply preview syntax highlighting", err);
34534
+ });
34535
+ }, 0);
34536
+ }
34228
34537
  function setPreferredSourceTab(tab) {
34229
34538
  PREFERRED_SOURCE_TAB = tab;
34230
34539
  }
@@ -34388,24 +34697,14 @@ code-viewer query agent-help`
34388
34697
  card.appendChild(view);
34389
34698
  setSourceCardState(card, "done");
34390
34699
  }
34391
- function renderSourceInternalPath(card, target, kind) {
34700
+ function renderSourceInternalPath(card, target) {
34392
34701
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
34393
34702
  const info = createSourceFileInfo(target, "internal metadata", {
34394
34703
  loadMeta: false
34395
34704
  });
34396
- const extraChildren = [info];
34397
- if (kind === "code-viewer") {
34398
- const link2 = document.createElement("a");
34399
- link2.className = "gdp-btn gdp-btn-sm gdp-source-download";
34400
- link2.href = buildRawFileUrl(target);
34401
- link2.textContent = "Download raw";
34402
- link2.target = "_blank";
34403
- link2.rel = "noreferrer";
34404
- extraChildren.push(link2);
34405
- }
34406
34705
  const view = renderUnsupportedPreview({
34407
- message: kind === "code-viewer" ? "This path is managed by code-viewer and is not previewed from the file viewer." : "Git internal metadata is not previewed from the file viewer.",
34408
- extraChildren
34706
+ message: "Git internal metadata is not previewed from the file viewer.",
34707
+ extraChildren: [info]
34409
34708
  });
34410
34709
  if (body)
34411
34710
  body.replaceWith(view);
@@ -34462,7 +34761,7 @@ code-viewer query agent-help`
34462
34761
  const tabs = document.createElement("div");
34463
34762
  tabs.className = "gdp-source-tabs";
34464
34763
  const { codeButton, previewButton } = appendFileViewTabs(tabs, { currentRange, setRoute, setPreferredSourceTab }, target, active, {
34465
- includeFileTabs: STATE.route.screen === "file",
34764
+ includeFileTabs: STATE.route.screen === "file" && sourceInternalPathKind(target.path) !== "code-viewer",
34466
34765
  previewable: !!options.previewable || active === "preview",
34467
34766
  sourceTabClick: "manual",
34468
34767
  ...textValue != null ? { sourceCopyButton: createSourceCopyButton(textValue) } : {}
@@ -34486,9 +34785,9 @@ code-viewer query agent-help`
34486
34785
  header.textContent = `${target.path} @ ${target.ref}`;
34487
34786
  }
34488
34787
  const lang = inferLang(target.path);
34788
+ const sourceShikiLang = normalizeSourceShikiLang(lang);
34489
34789
  const usesVirtualSource = shouldVirtualizeSource(textValue, lines) && !isVirtualSourceDisabled();
34490
34790
  const hljsRef = STATE.syntaxHighlight && usesVirtualSource ? await loadSyntaxHighlighter() : null;
34491
- const sourceShikiRef = STATE.syntaxHighlight && !usesVirtualSource ? await loadSourceShikiHighlighter() : null;
34492
34791
  if (signal?.aborted)
34493
34792
  return false;
34494
34793
  const previewable = isPreviewableSource(target.path);
@@ -34504,8 +34803,8 @@ code-viewer query agent-help`
34504
34803
  tabsHost.hidden = false;
34505
34804
  tabsHost.replaceChildren(tabs3);
34506
34805
  }
34507
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34508
- syntaxHighlight: STATE.syntaxHighlight,
34806
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34807
+ syntaxHighlight: false,
34509
34808
  signal,
34510
34809
  onNavigateMarkdown: (path, ref) => {
34511
34810
  setRoute({
@@ -34521,9 +34820,19 @@ code-viewer query agent-help`
34521
34820
  if (signal?.aborted)
34522
34821
  return false;
34523
34822
  preview.dataset.sourcePane = "preview";
34823
+ let previewHighlightScheduled = false;
34824
+ const ensurePreviewHighlight = () => {
34825
+ if (previewKind === "html" || !STATE.syntaxHighlight || previewHighlightScheduled)
34826
+ return;
34827
+ previewHighlightScheduled = true;
34828
+ scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, (next) => {
34829
+ preview = next;
34830
+ });
34831
+ };
34524
34832
  applyRenderedSourceTab(initialSourceTab, codeButton2, previewButton2, virtualCode, preview, false);
34525
34833
  previewButton2?.addEventListener("click", () => {
34526
34834
  applyRenderedSourceTab("preview", codeButton2, previewButton2, virtualCode, preview);
34835
+ ensurePreviewHighlight();
34527
34836
  });
34528
34837
  codeButton2?.addEventListener("click", () => {
34529
34838
  applyRenderedSourceTab("code", codeButton2, previewButton2, virtualCode, preview);
@@ -34536,6 +34845,8 @@ code-viewer query agent-help`
34536
34845
  body.replaceWith(view);
34537
34846
  else
34538
34847
  card.appendChild(view);
34848
+ if (initialSourceTab === "preview")
34849
+ ensurePreviewHighlight();
34539
34850
  return true;
34540
34851
  }
34541
34852
  const { tabs: tabs2 } = createSourceTabs(target, "code", textValue, {
@@ -34561,8 +34872,6 @@ code-viewer query agent-help`
34561
34872
  table2.className = "gdp-source-table";
34562
34873
  table2.dataset.sourcePane = "code";
34563
34874
  const tbody = document.createElement("tbody");
34564
- const sourceShikiLang = normalizeSourceShikiLang(lang);
34565
- const shikiLines = sourceShikiRef && sourceShikiLang ? sourceShikiLines(textValue, sourceShikiLang, sourceShikiRef) : null;
34566
34875
  for (let index = 0;index < lines.length; index++) {
34567
34876
  if (signal?.aborted)
34568
34877
  return false;
@@ -34577,12 +34886,7 @@ code-viewer query agent-help`
34577
34886
  bindSourceLineNumber(num, card, target, index + 1);
34578
34887
  const code2 = document.createElement("td");
34579
34888
  code2.className = "gdp-source-line-code";
34580
- if (shikiLines && shikiLines[index] != null) {
34581
- code2.innerHTML = shikiLines[index] || " ";
34582
- code2.classList.add("shiki");
34583
- } else {
34584
- code2.textContent = line || " ";
34585
- }
34889
+ code2.textContent = line || " ";
34586
34890
  tr.appendChild(num);
34587
34891
  tr.appendChild(code2);
34588
34892
  tbody.appendChild(tr);
@@ -34599,8 +34903,8 @@ code-viewer query agent-help`
34599
34903
  tabsHost.replaceChildren(tabs);
34600
34904
  }
34601
34905
  if (previewable) {
34602
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34603
- syntaxHighlight: STATE.syntaxHighlight,
34906
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34907
+ syntaxHighlight: false,
34604
34908
  signal,
34605
34909
  onNavigateMarkdown: (path, ref) => {
34606
34910
  setRoute({
@@ -34616,9 +34920,19 @@ code-viewer query agent-help`
34616
34920
  if (signal?.aborted)
34617
34921
  return false;
34618
34922
  preview.dataset.sourcePane = "preview";
34923
+ let previewHighlightScheduled = false;
34924
+ const ensurePreviewHighlight = () => {
34925
+ if (previewKind === "html" || !STATE.syntaxHighlight || previewHighlightScheduled)
34926
+ return;
34927
+ previewHighlightScheduled = true;
34928
+ scheduleMarkdownPreviewHighlight(preview, target, textValue, signal, (next) => {
34929
+ preview = next;
34930
+ });
34931
+ };
34619
34932
  applyRenderedSourceTab(initialSourceTab, codeButton, previewButton, table2, preview, false);
34620
34933
  previewButton?.addEventListener("click", () => {
34621
34934
  applyRenderedSourceTab("preview", codeButton, previewButton, table2, preview);
34935
+ ensurePreviewHighlight();
34622
34936
  });
34623
34937
  codeButton?.addEventListener("click", () => {
34624
34938
  applyRenderedSourceTab("code", codeButton, previewButton, table2, preview);
@@ -34633,6 +34947,10 @@ code-viewer query agent-help`
34633
34947
  body.replaceWith(view);
34634
34948
  else
34635
34949
  card.appendChild(view);
34950
+ if (STATE.syntaxHighlight && sourceShikiLang)
34951
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
34952
+ if (initialSourceTab === "preview")
34953
+ ensurePreviewHighlight();
34636
34954
  return true;
34637
34955
  }
34638
34956
  if (header)
@@ -34644,6 +34962,8 @@ code-viewer query agent-help`
34644
34962
  body.replaceWith(view);
34645
34963
  else
34646
34964
  card.appendChild(view);
34965
+ if (STATE.syntaxHighlight && sourceShikiLang)
34966
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
34647
34967
  return true;
34648
34968
  }
34649
34969
  function shouldVirtualizeSource(textValue, lines) {
@@ -34835,8 +35155,10 @@ code-viewer query agent-help`
34835
35155
  matches = nextMatches;
34836
35156
  active = matches.length ? Math.max(0, Math.min(active, matches.length - 1)) : -1;
34837
35157
  count.textContent = matches.length ? `${active + 1} / ${matches.length}` : "0 / 0";
34838
- if (active >= 0)
34839
- scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
35158
+ if (active >= 0) {
35159
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35160
+ scroller.scrollTop = Math.max(0, (matches[active].line - 1) * rowHeight - rowHeight * 3);
35161
+ }
34840
35162
  renderFn();
34841
35163
  }).catch(() => {
34842
35164
  if (version !== searchVersion)
@@ -34857,7 +35179,8 @@ code-viewer query agent-help`
34857
35179
  return;
34858
35180
  active = (active + direction + matches.length) % matches.length;
34859
35181
  count.textContent = `${active + 1} / ${matches.length}`;
34860
- scroller.scrollTop = Math.max(0, (matches[active].line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - VIRTUAL_SOURCE_ROW_HEIGHT * 3);
35182
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35183
+ scroller.scrollTop = Math.max(0, (matches[active].line - 1) * rowHeight - rowHeight * 3);
34861
35184
  renderFn();
34862
35185
  };
34863
35186
  input2.addEventListener("input", () => {
@@ -34951,7 +35274,7 @@ code-viewer query agent-help`
34951
35274
  scroller.setAttribute("aria-label", `${target.path} source code`);
34952
35275
  const spacer = document.createElement("div");
34953
35276
  spacer.className = "gdp-source-virtual-spacer";
34954
- spacer.style.height = `${Math.max(1, lines.length * VIRTUAL_SOURCE_ROW_HEIGHT)}px`;
35277
+ spacer.style.height = `${Math.max(1, lines.length * (sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT))}px`;
34955
35278
  const windowEl = document.createElement("div");
34956
35279
  windowEl.className = "gdp-source-virtual-window";
34957
35280
  spacer.appendChild(windowEl);
@@ -34960,19 +35283,23 @@ code-viewer query agent-help`
34960
35283
  let raf = 0;
34961
35284
  let renderedStart = -1;
34962
35285
  let renderedEnd = -1;
35286
+ let renderedRowHeight = -1;
34963
35287
  let search = null;
34964
35288
  const render = () => {
34965
35289
  raf = 0;
35290
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35291
+ spacer.style.height = `${Math.max(1, lines.length * rowHeight)}px`;
34966
35292
  const viewportHeight = scroller.clientHeight || window.innerHeight;
34967
35293
  const overscan = 20;
34968
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
34969
- const end = Math.min(lines.length, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
34970
- if (start === renderedStart && end === renderedEnd)
35294
+ const start = Math.max(0, Math.floor(scroller.scrollTop / rowHeight) - overscan);
35295
+ const end = Math.min(lines.length, Math.ceil((scroller.scrollTop + viewportHeight) / rowHeight) + overscan);
35296
+ if (start === renderedStart && end === renderedEnd && rowHeight === renderedRowHeight)
34971
35297
  return;
34972
35298
  renderedStart = start;
34973
35299
  renderedEnd = end;
35300
+ renderedRowHeight = rowHeight;
34974
35301
  windowEl.replaceChildren();
34975
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35302
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
34976
35303
  const fragment = document.createDocumentFragment();
34977
35304
  for (let index = start;index < end; index++) {
34978
35305
  const row = document.createElement("div");
@@ -35088,7 +35415,8 @@ code-viewer query agent-help`
35088
35415
  const updateTotals = () => {
35089
35416
  SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), totalRows);
35090
35417
  summary.textContent = (complete ? totalRows.toLocaleString() : `${lines.size.toLocaleString()}+`) + " lines loaded from " + formatBytes(size) + ". More rows load as you scroll.";
35091
- spacer.style.height = `${Math.max(1, totalRows * VIRTUAL_SOURCE_ROW_HEIGHT)}px`;
35418
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35419
+ spacer.style.height = `${Math.max(1, totalRows * rowHeight)}px`;
35092
35420
  };
35093
35421
  const loadPage = (line) => {
35094
35422
  if (signal?.aborted || complete && line > totalRows)
@@ -35125,20 +35453,24 @@ code-viewer query agent-help`
35125
35453
  let raf = 0;
35126
35454
  let renderedStart = -1;
35127
35455
  let renderedEnd = -1;
35456
+ let renderedRowHeight = -1;
35128
35457
  let search = null;
35129
35458
  let searchController = null;
35130
35459
  const render = () => {
35131
35460
  raf = 0;
35461
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35462
+ spacer.style.height = `${Math.max(1, totalRows * rowHeight)}px`;
35132
35463
  const viewportHeight = scroller.clientHeight || window.innerHeight;
35133
35464
  const overscan = 20;
35134
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
35135
- const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
35136
- if (start === renderedStart && end === renderedEnd)
35465
+ const start = Math.max(0, Math.floor(scroller.scrollTop / rowHeight) - overscan);
35466
+ const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / rowHeight) + overscan);
35467
+ if (start === renderedStart && end === renderedEnd && rowHeight === renderedRowHeight)
35137
35468
  return;
35138
35469
  renderedStart = start;
35139
35470
  renderedEnd = end;
35471
+ renderedRowHeight = rowHeight;
35140
35472
  windowEl.replaceChildren();
35141
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35473
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
35142
35474
  const fragment = document.createDocumentFragment();
35143
35475
  for (let index = start;index < end; index++) {
35144
35476
  const lineNumber = index + 1;
@@ -35367,8 +35699,8 @@ code-viewer query agent-help`
35367
35699
  scrollStandaloneSourceLine(mounted, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35368
35700
  return;
35369
35701
  }
35370
- if (mounted && state === "loading")
35371
- return;
35702
+ if (mounted && state === "loading" && mounted._loadPromise)
35703
+ return mounted._loadPromise;
35372
35704
  }
35373
35705
  cancelActiveSourceLoad("navigation");
35374
35706
  const req = ++SOURCE_REQ_SEQ;
@@ -35392,7 +35724,8 @@ code-viewer query agent-help`
35392
35724
  currentRange,
35393
35725
  setRoute,
35394
35726
  setPreferredSourceTab,
35395
- createFileBreadcrumb
35727
+ createFileBreadcrumb,
35728
+ createRepositoryWebLink: createRepositoryWebLink2
35396
35729
  }, target, activeTab, internalKind ? { includeFileTabs: false, previewable: false } : {});
35397
35730
  const pathActions = header.querySelector(".gdp-file-detail-path") || header;
35398
35731
  pathActions.appendChild(createOpenPathButton(target.path, "file-parent", "open parent folder in OS"));
@@ -35403,7 +35736,7 @@ code-viewer query agent-help`
35403
35736
  loadRepo();
35404
35737
  }));
35405
35738
  }
35406
- if (!internalKind) {
35739
+ if (internalKind !== "git") {
35407
35740
  loadRawFileInfo(target).then((meta) => {
35408
35741
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35409
35742
  return;
@@ -35428,94 +35761,103 @@ code-viewer query agent-help`
35428
35761
  wrapper.appendChild(detailBody);
35429
35762
  card.appendChild(wrapper);
35430
35763
  mountFileShellCard({ $, repoFileTargetFromRoute, renderRepoBlobSidebar, placeSidebarToggle }, target, card, repoTarget);
35431
- if (internalKind) {
35432
- renderSourceInternalPath(card, target, internalKind);
35764
+ if (internalKind === "git") {
35765
+ renderSourceInternalPath(card, target);
35433
35766
  return;
35434
35767
  }
35435
35768
  const controller = new AbortController;
35436
35769
  ACTIVE_SOURCE_LOAD = { controller, req, target, card };
35437
35770
  renderSourceLoading(card, target, () => cancelActiveSourceLoad("user"));
35438
- try {
35439
- let displayKind = sourceDisplayKind(target.path);
35440
- let rawInfo = null;
35441
- if (displayKind === "unsupported") {
35442
- rawInfo = await loadRawFileInfo(target);
35443
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35444
- return;
35445
- if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
35446
- displayKind = "text";
35771
+ const sourceLoad = (async () => {
35772
+ try {
35773
+ let displayKind = sourceDisplayKind(target.path);
35774
+ let rawInfo = null;
35775
+ if (displayKind === "unsupported") {
35776
+ rawInfo = await loadRawFileInfo(target);
35777
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35778
+ return;
35779
+ if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
35780
+ displayKind = "text";
35781
+ }
35447
35782
  }
35448
- }
35449
- if (displayKind === "unsupported") {
35450
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35451
- return;
35452
- finishSourceLoad(req);
35453
- renderSourceUnsupported(card, target);
35454
- return;
35455
- }
35456
- if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
35457
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35783
+ if (displayKind === "unsupported") {
35784
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35785
+ return;
35786
+ finishSourceLoad(req);
35787
+ renderSourceUnsupported(card, target);
35458
35788
  return;
35459
- finishSourceLoad(req);
35460
- renderSourceMedia(card, target, displayKind);
35461
- return;
35462
- }
35463
- if (displayKind === "text") {
35464
- const meta = rawInfo ?? await loadRawFileInfo(target);
35465
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35789
+ }
35790
+ if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
35791
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35792
+ return;
35793
+ finishSourceLoad(req);
35794
+ renderSourceMedia(card, target, displayKind);
35466
35795
  return;
35467
- if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
35468
- const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35796
+ }
35797
+ if (displayKind === "text") {
35798
+ const meta = rawInfo ?? await loadRawFileInfo(target);
35799
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35800
+ return;
35801
+ if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
35802
+ const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35803
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35804
+ return;
35805
+ if (!rendered2) {
35806
+ finishSourceLoad(req);
35807
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35808
+ return;
35809
+ }
35810
+ scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35811
+ setSourceCardState(card, "done");
35812
+ finishSourceLoad(req);
35813
+ return;
35814
+ }
35815
+ const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
35469
35816
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35470
35817
  return;
35471
- if (!rendered2) {
35818
+ if (!response.ok) {
35472
35819
  finishSourceLoad(req);
35473
35820
  renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35474
35821
  return;
35475
35822
  }
35823
+ const textValue = await response.text();
35824
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35825
+ return;
35826
+ const rendered = await renderSourceText(card, target, textValue, controller.signal);
35827
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35828
+ return;
35829
+ if (!rendered)
35830
+ return;
35476
35831
  scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35477
35832
  setSourceCardState(card, "done");
35478
35833
  finishSourceLoad(req);
35479
- return;
35480
35834
  }
35481
- const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
35835
+ } catch (err) {
35482
35836
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35483
35837
  return;
35484
- if (!response.ok) {
35485
- finishSourceLoad(req);
35486
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35838
+ finishSourceLoad(req);
35839
+ if (isAbortError3(err)) {
35840
+ renderSourceCancelled(card, target);
35487
35841
  return;
35488
35842
  }
35489
- const textValue = await response.text();
35490
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35491
- return;
35492
- const rendered = await renderSourceText(card, target, textValue, controller.signal);
35493
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35494
- return;
35495
- if (!rendered)
35496
- return;
35497
- scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35498
- setSourceCardState(card, "done");
35499
- finishSourceLoad(req);
35843
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35500
35844
  }
35501
- } catch (err) {
35502
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35503
- return;
35504
- finishSourceLoad(req);
35505
- if (isAbortError3(err)) {
35506
- renderSourceCancelled(card, target);
35507
- return;
35508
- }
35509
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35510
- }
35845
+ })();
35846
+ const completed = sourceLoad.finally(() => {
35847
+ if (card._loadPromise === completed)
35848
+ delete card._loadPromise;
35849
+ });
35850
+ card._loadPromise = completed;
35851
+ return completed;
35511
35852
  }
35512
35853
  function scrollStandaloneSourceLine(card, line) {
35513
35854
  if (!line || line < 1)
35514
35855
  return;
35515
35856
  const virtualScroller = card.querySelector(".gdp-source-virtual-scroller");
35516
35857
  if (virtualScroller) {
35517
- const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
35518
- virtualScroller.scrollTop = Math.max(0, (line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT - Math.max(0, centeredOffset));
35858
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35859
+ const centeredOffset = virtualScroller.clientHeight / 2 - rowHeight / 2;
35860
+ virtualScroller.scrollTop = Math.max(0, (line - 1) * rowHeight - Math.max(0, centeredOffset));
35519
35861
  virtualScroller.__gdpRenderVirtualSource?.();
35520
35862
  return;
35521
35863
  }
@@ -35639,6 +35981,8 @@ code-viewer query agent-help`
35639
35981
  let PENDING_G_SCOPE = null;
35640
35982
  let PENDING_G_UNTIL = 0;
35641
35983
  let PROJECT_NAME = "";
35984
+ let PROJECT_BRANCH = "";
35985
+ let REPO_WEB_URL = null;
35642
35986
  let APP_SETTINGS = { version: 1 };
35643
35987
  let VIEW_STATE = {
35644
35988
  version: 1,
@@ -35783,11 +36127,14 @@ code-viewer query agent-help`
35783
36127
  }
35784
36128
  }
35785
36129
  function setProjectBranch(branch) {
36130
+ PROJECT_BRANCH = branch;
35786
36131
  const el = document.querySelector("#project-branch");
35787
36132
  if (!el)
35788
36133
  return;
35789
36134
  el.hidden = !branch;
35790
- el.textContent = branch;
36135
+ const name = el.querySelector(".project-branch-name");
36136
+ if (name)
36137
+ name.textContent = branch;
35791
36138
  el.title = branch ? `Current branch: ${branch}` : "";
35792
36139
  }
35793
36140
  function mergeLocalSettings(patch) {
@@ -35974,7 +36321,14 @@ code-viewer query agent-help`
35974
36321
  return normalizeViewerFontSize(APP_SETTINGS.codeFontSize);
35975
36322
  }
35976
36323
  function applyCodeFontSize(size = savedCodeFontSize()) {
36324
+ const previousLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
35977
36325
  document.body.dataset.codeFontSize = size;
36326
+ const nextLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
36327
+ document.querySelectorAll(".gdp-source-virtual-scroller").forEach((scroller) => {
36328
+ if (Number.isFinite(previousLineHeight) && previousLineHeight > 0 && Number.isFinite(nextLineHeight) && nextLineHeight > 0)
36329
+ scroller.scrollTop *= nextLineHeight / previousLineHeight;
36330
+ scroller.__gdpRenderVirtualSource?.();
36331
+ });
35978
36332
  }
35979
36333
  function savedSidebarFontSizeSetting() {
35980
36334
  return normalizeViewerFontSize(APP_SETTINGS.sidebarFontSize);
@@ -36007,10 +36361,11 @@ code-viewer query agent-help`
36007
36361
  const settings = await res.json();
36008
36362
  setProjectName(settings.project || "");
36009
36363
  setProjectBranch(settings.branch || "");
36364
+ REPO_WEB_URL = settings.repo_web_url;
36010
36365
  const repoLink = document.querySelector("#repo-web-link");
36011
- if (repoLink && settings.repo_web_url) {
36012
- repoLink.href = settings.repo_web_url;
36013
- repoLink.hidden = false;
36366
+ if (repoLink) {
36367
+ repoLink.href = settings.repo_web_url || "#";
36368
+ repoLink.hidden = !settings.repo_web_url;
36014
36369
  }
36015
36370
  SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
36016
36371
  SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
@@ -36107,7 +36462,24 @@ code-viewer query agent-help`
36107
36462
  const LINE_REF_PILL = createLineRefPill({
36108
36463
  onClose: () => {
36109
36464
  clearLineSelection();
36110
- }
36465
+ },
36466
+ githubUrlForSelection: (path, start, end) => {
36467
+ const route = STATE.route;
36468
+ const ref = route.screen === "file" ? route.commit || route.ref : route.screen === "diff" ? route.range.to : "worktree";
36469
+ const target = buildRepositoryWebTarget(REPO_WEB_URL, {
36470
+ ref,
36471
+ fallbackRef: PROJECT_BRANCH,
36472
+ path,
36473
+ kind: "blob",
36474
+ start,
36475
+ end
36476
+ });
36477
+ return target?.provider === "github" ? target.url : null;
36478
+ },
36479
+ copyReferenceLabel: () => uiText().global.copyLineReference,
36480
+ lineCountLabel: (count) => uiText().global.selectedLineCount(count),
36481
+ githubOpenTitle: () => uiText().global.githubSelectionOpen,
36482
+ githubCopyTitle: () => uiText().global.githubSelectionCopy
36111
36483
  });
36112
36484
  const DIFF_LINE_SELECT = createDiffLineSelect({ pill: LINE_REF_PILL });
36113
36485
  function clearRenderedSourceLineTargets() {
@@ -36258,6 +36630,7 @@ code-viewer query agent-help`
36258
36630
  renderRepoBlobSidebar: (path, ref) => REPO_VIEW.renderRepoBlobSidebar(path, ref),
36259
36631
  placeSidebarToggle,
36260
36632
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36633
+ createRepositoryWebLink: createFileRepositoryWebLink,
36261
36634
  createFileDetailMeta: (target, meta) => REPO_VIEW.createFileDetailMeta(target, meta),
36262
36635
  createOpenPathButton,
36263
36636
  createMoveToTrashButton: (path, onDeleted) => REPO_VIEW.createMoveToTrashButton(path, onDeleted),
@@ -36291,7 +36664,7 @@ code-viewer query agent-help`
36291
36664
  currentRange,
36292
36665
  trackLoad,
36293
36666
  getSyntaxHighlight: () => STATE.syntaxHighlight,
36294
- loadSourceShikiHighlighter: () => SOURCE_VIEW.loadSourceShikiHighlighter(),
36667
+ loadSourceShikiHighlighter: (lang) => SOURCE_VIEW.loadSourceShikiHighlighter(lang),
36295
36668
  sourceShikiLines: (textValue, lang, highlighter) => SOURCE_VIEW.sourceShikiLines(textValue, lang, highlighter),
36296
36669
  inferLang: (path) => SOURCE_VIEW.inferLang(path),
36297
36670
  currentSourceLineTarget: (target) => SOURCE_VIEW.currentSourceLineTarget(target),
@@ -36299,6 +36672,7 @@ code-viewer query agent-help`
36299
36672
  bindSourceLineNumber: (num, card, target, line) => SOURCE_VIEW.bindSourceLineNumber(num, card, target, line),
36300
36673
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
36301
36674
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36675
+ createRepositoryWebLink: createFileRepositoryWebLink,
36302
36676
  removeStandaloneSource,
36303
36677
  placeSidebarToggle,
36304
36678
  escapeHtml: escapeHtml3,
@@ -36373,6 +36747,14 @@ code-viewer query agent-help`
36373
36747
  const text3 = uiText().repo;
36374
36748
  return submodule ? { label: text3.submoduleLabel, title: text3.submoduleTitle } : { label: text3.gitlinkLabel, title: text3.gitlinkTitle };
36375
36749
  },
36750
+ repositoryWebTarget: (path, ref) => buildRepositoryWebTarget(REPO_WEB_URL, {
36751
+ ref,
36752
+ fallbackRef: PROJECT_BRANCH,
36753
+ path,
36754
+ kind: "tree"
36755
+ }),
36756
+ openGithubLabel: () => uiText().repo.openGithub,
36757
+ openRepositoryWebLabel: () => uiText().repo.openRepositoryWeb,
36376
36758
  fileBadge: (status) => DIFF_VIEW.fileBadge(status)
36377
36759
  });
36378
36760
  const {
@@ -36415,9 +36797,7 @@ code-viewer query agent-help`
36415
36797
  queryHistory: "query history",
36416
36798
  settings: "viewer settings",
36417
36799
  theme: "toggle theme",
36418
- product: "code viewer",
36419
36800
  copyAiContext: "Copy AI context (Shift+Click to include code)",
36420
- copyAiContextLabel: "AI context",
36421
36801
  copyAiContextCopied: "Copied AI context",
36422
36802
  copyAiContextCopiedWithCode: (lines) => `Copied AI context + code (${lines} line${lines === 1 ? "" : "s"})`,
36423
36803
  copyAiContextFailed: "Copy failed",
@@ -36430,7 +36810,12 @@ code-viewer query agent-help`
36430
36810
  cancelRequestsActiveTitle: (count) => `cancel ${count} in-flight request${count === 1 ? "" : "s"}`,
36431
36811
  cancelRequestsInactiveTitle: "no in-flight requests",
36432
36812
  brandHome: "Repository home",
36433
- menuViews: "Views"
36813
+ menuViews: "Views",
36814
+ repoWebLink: "open repository web page",
36815
+ copyLineReference: "Copy AI reference",
36816
+ selectedLineCount: (count) => `${count} line${count === 1 ? "" : "s"}`,
36817
+ githubSelectionOpen: "Open selected lines on GitHub",
36818
+ githubSelectionCopy: "Copy GitHub link"
36434
36819
  },
36435
36820
  topbar: {
36436
36821
  resetRange: "reset to HEAD .. worktree",
@@ -36441,15 +36826,13 @@ code-viewer query agent-help`
36441
36826
  ignoreWs: "ignore whitespace changes (-w)",
36442
36827
  ignoreWsLabel: "ws",
36443
36828
  syntaxLoading: "loading...",
36444
- syntaxOn: "syntax on",
36445
- syntaxOff: "syntax off",
36829
+ syntax: "syntax",
36446
36830
  syntaxOnTitle: "syntax highlighting on",
36447
36831
  syntaxLoadingTitle: "loading syntax highlighter",
36448
36832
  syntaxErrorTitle: "failed to load syntax highlighter",
36449
36833
  syntaxOffTitle: "syntax highlighting off",
36450
36834
  hideTests: "hide test files (test|spec)",
36451
36835
  hideTestsLabel: "no test",
36452
- autoUpdate: "auto",
36453
36836
  autoUpdateOnTitle: "auto update on file change",
36454
36837
  autoUpdateOffTitle: "auto update off — manual reload"
36455
36838
  },
@@ -36536,7 +36919,9 @@ code-viewer query agent-help`
36536
36919
  gitlinkLabel: "gitlink",
36537
36920
  gitlinkTitle: "Git commit entry is not directly browsable at this ref",
36538
36921
  submoduleLabel: "submodule",
36539
- submoduleTitle: "Git submodule pinned to a commit"
36922
+ submoduleTitle: "Git submodule pinned to a commit",
36923
+ openGithub: "Open on GitHub",
36924
+ openRepositoryWeb: "Open repository web page"
36540
36925
  },
36541
36926
  history: {
36542
36927
  title: "Commits",
@@ -36653,7 +37038,7 @@ code-viewer query agent-help`
36653
37038
  display: "Display",
36654
37039
  language: "Language",
36655
37040
  fileListFontSize: "UI font size",
36656
- fileListFontSizeHelp: "Applies to the file sidebar and datastore UI.",
37041
+ fileListFontSizeHelp: "Applies to all UI except code content.",
36657
37042
  codeFontSize: "Code font size",
36658
37043
  sizeSmall: "Small",
36659
37044
  sizeRegular: "Regular",
@@ -36705,9 +37090,7 @@ code-viewer query agent-help`
36705
37090
  queryHistory: "クエリ履歴",
36706
37091
  settings: "ビューア設定",
36707
37092
  theme: "テーマ切り替え",
36708
- product: "code viewer",
36709
37093
  copyAiContext: "AI 用コンテキストをコピー(Shift+Click でコードも添付)",
36710
- copyAiContextLabel: "AI文脈",
36711
37094
  copyAiContextCopied: "コピーしました",
36712
37095
  copyAiContextCopiedWithCode: (lines) => `コピーしました(コード付き・${lines}行)`,
36713
37096
  copyAiContextFailed: "コピーに失敗しました",
@@ -36720,7 +37103,12 @@ code-viewer query agent-help`
36720
37103
  cancelRequestsActiveTitle: (count) => `実行中のリクエストを${count}件キャンセル`,
36721
37104
  cancelRequestsInactiveTitle: "実行中のリクエストはありません",
36722
37105
  brandHome: "リポジトリホーム",
36723
- menuViews: "ビュー切り替え"
37106
+ menuViews: "ビュー切り替え",
37107
+ repoWebLink: "リポジトリのウェブページを開く",
37108
+ copyLineReference: "AI参照をコピー",
37109
+ selectedLineCount: (count) => `${count}行`,
37110
+ githubSelectionOpen: "選択行をGitHubで開く",
37111
+ githubSelectionCopy: "GitHubリンクをコピー"
36724
37112
  },
36725
37113
  topbar: {
36726
37114
  resetRange: "HEAD .. worktree に戻す",
@@ -36731,15 +37119,13 @@ code-viewer query agent-help`
36731
37119
  ignoreWs: "空白差分を無視 (-w)",
36732
37120
  ignoreWsLabel: "空白",
36733
37121
  syntaxLoading: "読み込み中...",
36734
- syntaxOn: "構文あり",
36735
- syntaxOff: "構文なし",
37122
+ syntax: "構文",
36736
37123
  syntaxOnTitle: "シンタックスハイライト有効",
36737
37124
  syntaxLoadingTitle: "シンタックスハイライトを読み込み中",
36738
37125
  syntaxErrorTitle: "シンタックスハイライトの読み込みに失敗",
36739
37126
  syntaxOffTitle: "シンタックスハイライト無効",
36740
37127
  hideTests: "test/spec ファイルを隠す",
36741
37128
  hideTestsLabel: "テスト非表示",
36742
- autoUpdate: "自動",
36743
37129
  autoUpdateOnTitle: "ファイル変更時に自動更新",
36744
37130
  autoUpdateOffTitle: "自動更新オフ — 手動で再読み込み"
36745
37131
  },
@@ -36826,7 +37212,9 @@ code-viewer query agent-help`
36826
37212
  gitlinkLabel: "固定コミット",
36827
37213
  gitlinkTitle: "特定のコミットに固定された参照です。この ref では直接開けません。",
36828
37214
  submoduleLabel: "サブモジュール",
36829
- submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。"
37215
+ submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。",
37216
+ openGithub: "GitHubで開く",
37217
+ openRepositoryWeb: "リポジトリのウェブページを開く"
36830
37218
  },
36831
37219
  history: {
36832
37220
  title: "コミット",
@@ -36943,7 +37331,7 @@ code-viewer query agent-help`
36943
37331
  display: "表示",
36944
37332
  language: "言語",
36945
37333
  fileListFontSize: "UIの文字サイズ",
36946
- fileListFontSizeHelp: "ファイル一覧とデータストア画面に適用されます。",
37334
+ fileListFontSizeHelp: "コード本文を除くUI全体に適用されます。",
36947
37335
  codeFontSize: "コード表示の文字サイズ",
36948
37336
  sizeSmall: "小",
36949
37337
  sizeRegular: "標準",
@@ -37009,8 +37397,16 @@ code-viewer query agent-help`
37009
37397
  if (route && text3.nav[route])
37010
37398
  link2.textContent = text3.nav[route];
37011
37399
  });
37012
- setElementText(".global-help-link[data-route='help']", text3.nav.help);
37013
- setElementText(".product-label", text3.global.product);
37400
+ const helpLink = document.querySelector(".global-icon-link[data-route='help']");
37401
+ if (helpLink) {
37402
+ helpLink.title = text3.nav.help;
37403
+ helpLink.setAttribute("aria-label", text3.nav.help);
37404
+ }
37405
+ const repoWebLink = document.querySelector("#repo-web-link");
37406
+ if (repoWebLink) {
37407
+ repoWebLink.title = text3.global.repoWebLink;
37408
+ repoWebLink.setAttribute("aria-label", text3.global.repoWebLink);
37409
+ }
37014
37410
  document.querySelector(".brand")?.setAttribute("aria-label", text3.global.brandHome);
37015
37411
  document.querySelector(".app-menu")?.setAttribute("aria-label", text3.global.menuViews);
37016
37412
  const annotationsToggle = document.querySelector("#annotations-toggle");
@@ -37045,9 +37441,6 @@ code-viewer query agent-help`
37045
37441
  if (copyAiContext) {
37046
37442
  copyAiContext.title = text3.global.copyAiContext;
37047
37443
  copyAiContext.setAttribute("aria-label", text3.global.copyAiContext);
37048
- const copyAiContextLabel = copyAiContext.querySelector(".ai-context-label");
37049
- if (copyAiContextLabel)
37050
- copyAiContextLabel.textContent = text3.global.copyAiContextLabel;
37051
37444
  }
37052
37445
  const refReset = document.querySelector("#ref-reset");
37053
37446
  if (refReset) {
@@ -37264,7 +37657,7 @@ code-viewer query agent-help`
37264
37657
  const text3 = uiText();
37265
37658
  btn.classList.toggle("active", STATE.syntaxHighlight);
37266
37659
  btn.classList.toggle("loading", state === "loading");
37267
- btn.textContent = state === "loading" ? text3.topbar.syntaxLoading : STATE.syntaxHighlight ? text3.topbar.syntaxOn : text3.topbar.syntaxOff;
37660
+ btn.textContent = state === "loading" ? text3.topbar.syntaxLoading : text3.topbar.syntax;
37268
37661
  btn.setAttribute("aria-pressed", STATE.syntaxHighlight ? "true" : "false");
37269
37662
  btn.title = STATE.syntaxHighlight ? text3.topbar.syntaxOnTitle : state === "loading" ? text3.topbar.syntaxLoadingTitle : state === "error" ? text3.topbar.syntaxErrorTitle : text3.topbar.syntaxOffTitle;
37270
37663
  }
@@ -37649,8 +38042,9 @@ code-viewer query agent-help`
37649
38042
  setRoute,
37650
38043
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
37651
38044
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
38045
+ createRepositoryWebLink: createFileRepositoryWebLink,
37652
38046
  emptyText: () => uiText().diff
37653
- }, historyRoute);
38047
+ }, historyRoute, { loadSidebar: false });
37654
38048
  activeFileHistoryDiffHost = mount.diffHost;
37655
38049
  activeFileHistoryEmptyHost = mount.emptyHost;
37656
38050
  return mount;
@@ -37705,7 +38099,17 @@ code-viewer query agent-help`
37705
38099
  parkRangeForHistory();
37706
38100
  setPageMode();
37707
38101
  const mount = renderFileHistoryShell2(route);
37708
- HISTORY_VIEW.enterHistory({ mount });
38102
+ HISTORY_VIEW.enterHistory({ mount }).then(async () => {
38103
+ const currentRoute = STATE.route;
38104
+ if (currentRoute.screen !== "file" || currentRoute.view !== "history" || currentRoute.path !== route.path || currentRoute.ref !== route.ref)
38105
+ return;
38106
+ await loadDiffFile(route.path);
38107
+ const routeAfterDiff = STATE.route;
38108
+ if (routeAfterDiff.screen !== "file" || routeAfterDiff.view !== "history" || routeAfterDiff.path !== route.path || routeAfterDiff.ref !== route.ref)
38109
+ return;
38110
+ await REPO_VIEW.renderRepoBlobSidebar(route.path, route.ref);
38111
+ placeSidebarToggle();
38112
+ });
37709
38113
  return true;
37710
38114
  }
37711
38115
  return false;
@@ -37814,7 +38218,7 @@ code-viewer query agent-help`
37814
38218
  applyHideTests();
37815
38219
  }
37816
38220
  function syncHeaderMenu() {
37817
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
38221
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
37818
38222
  const fileRouteOwner = STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history") ? "repo" : "diff";
37819
38223
  const active = link2.dataset.route === STATE.route.screen || STATE.route.screen === "file" && link2.dataset.route === fileRouteOwner;
37820
38224
  link2.classList.toggle("active", active);
@@ -37939,6 +38343,18 @@ code-viewer query agent-help`
37939
38343
  });
37940
38344
  return button;
37941
38345
  }
38346
+ function createFileRepositoryWebLink(target) {
38347
+ const webTarget = buildRepositoryWebTarget(REPO_WEB_URL, {
38348
+ ref: target.ref,
38349
+ fallbackRef: PROJECT_BRANCH,
38350
+ path: target.path,
38351
+ kind: "blob"
38352
+ });
38353
+ if (!webTarget)
38354
+ return null;
38355
+ const label = webTarget.provider === "github" ? uiText().repo.openGithub : uiText().repo.openRepositoryWeb;
38356
+ return createRepositoryWebLink(webTarget, label);
38357
+ }
37942
38358
  window.addEventListener("scroll", () => enqueueInitialLoads(), {
37943
38359
  passive: true
37944
38360
  });
@@ -38048,14 +38464,34 @@ code-viewer query agent-help`
38048
38464
  if (cancelRequestsIcon) {
38049
38465
  cancelRequestsIcon.innerHTML = iconSvg("octicon-x", X_16_PATH);
38050
38466
  }
38467
+ const repoWebLinkIcon = document.querySelector("#repo-web-link .goi-icon");
38468
+ if (repoWebLinkIcon) {
38469
+ repoWebLinkIcon.innerHTML = iconSvg("octicon-mark-github", MARK_GITHUB_16_PATH);
38470
+ }
38471
+ const quickHelpIcon = document.querySelector("#quick-help-btn .goi-icon");
38472
+ if (quickHelpIcon) {
38473
+ quickHelpIcon.innerHTML = iconSvg("octicon-question", QUESTION_16_PATH);
38474
+ }
38475
+ const helpLinkIcon = document.querySelector(".global-icon-link[data-route='help'] .goi-icon");
38476
+ if (helpLinkIcon) {
38477
+ helpLinkIcon.innerHTML = iconSvg("octicon-book", BOOK_16_PATH);
38478
+ }
38479
+ const branchIcon = document.querySelector("#project-branch .goi-icon");
38480
+ if (branchIcon) {
38481
+ branchIcon.innerHTML = iconSvg("octicon-git-branch", GIT_BRANCH_16_PATH);
38482
+ }
38051
38483
  }
38052
38484
  function setRefActionIcons() {
38053
38485
  const refReset = document.querySelector("#ref-reset");
38054
38486
  if (refReset)
38055
- refReset.innerHTML = iconSvg("octicon-x", X_16_PATH);
38487
+ refReset.innerHTML = iconSvg("octicon-undo", UNDO_16_PATH);
38056
38488
  const reload = document.querySelector("#reload-prom");
38057
38489
  if (reload)
38058
38490
  reload.innerHTML = iconSvg("octicon-sync", SYNC_16_PATH);
38491
+ const refDots = document.querySelector(".ref-dots .goi-icon");
38492
+ if (refDots) {
38493
+ refDots.innerHTML = iconSvg("octicon-arrow-right", ARROW_RIGHT_16_PATH);
38494
+ }
38059
38495
  }
38060
38496
  applySidebarFontSize();
38061
38497
  applyCodeFontSize();
@@ -39076,7 +39512,7 @@ code-viewer query agent-help`
39076
39512
  }
39077
39513
  window.addEventListener("popstate", applyRouteFromLocation);
39078
39514
  window.addEventListener("pagehide", () => flushViewStatePatch(true));
39079
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
39515
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
39080
39516
  if (link2.target === "_blank")
39081
39517
  return;
39082
39518
  link2.addEventListener("click", (e2) => {
@@ -39311,9 +39747,6 @@ code-viewer query agent-help`
39311
39747
  btn.title = autoUpdateTitle;
39312
39748
  btn.setAttribute("aria-label", autoUpdateTitle);
39313
39749
  btn.setAttribute("aria-pressed", STATE.autoUpdate ? "true" : "false");
39314
- const label = btn.querySelector(".auto-update-label");
39315
- if (label)
39316
- label.textContent = text3.topbar.autoUpdate;
39317
39750
  }
39318
39751
  function setAutoUpdate(on) {
39319
39752
  STATE.autoUpdate = on;