@youtyan/code-viewer 0.8.7 → 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
@@ -513,6 +513,8 @@ ${lines.join(`
513
513
  ];
514
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";
515
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";
516
518
  function iconSvg(className, paths) {
517
519
  const pathList = Array.isArray(paths) ? paths : [paths];
518
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>";
@@ -522,6 +524,9 @@ ${lines.join(`
522
524
  var GRABBER_16_PATH = "M10 13h4v-2h-4v2Zm-8 0h4v-2H2v2Zm8-6h4V5h-4v2ZM2 7h4V5H2v2Zm8-6v2h4V1h-4ZM2 3h4V1H2v2Z";
523
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";
524
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";
525
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";
526
531
  var DOWNLOAD_16_PATHS = [
527
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",
@@ -778,6 +783,50 @@ ${lines.join(`
778
783
  };
779
784
  }
780
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
+
781
830
  // web-src/core/routes.ts
782
831
  function assertNever(value) {
783
832
  throw new Error(`unhandled route: ${JSON.stringify(value)}`);
@@ -1005,7 +1054,7 @@ ${lines.join(`
1005
1054
  }
1006
1055
  case "journal": {
1007
1056
  const params = new URLSearchParams;
1008
- if (route.tab && route.tab !== "journal")
1057
+ if (route.tab && route.tab !== "tasks")
1009
1058
  params.set("tab", route.tab);
1010
1059
  if (route.date)
1011
1060
  params.set("date", route.date);
@@ -8573,16 +8622,19 @@ ${frontmatter.yaml}
8573
8622
 
8574
8623
  // web-src/views/annotations-ui.ts
8575
8624
  var ANNOTATION_SESSION_PARAM = "annotationSession";
8625
+ var ANNOTATION_PANEL_PARAM = "annotations";
8626
+ var ANNOTATION_ENTRY_PARAM = "annotation";
8576
8627
  function createAnnotationsUi(deps) {
8577
8628
  const { $ } = deps;
8578
8629
  let ANNOTATIONS = { version: 1, sessions: [] };
8579
8630
  let annotationFollow = deps.getAnnotationFollow();
8580
- let activeAnnotationId = null;
8631
+ const initialUrlParams = new URLSearchParams(window.location.search);
8632
+ let activeAnnotationId = initialUrlParams.get(ANNOTATION_ENTRY_PARAM);
8581
8633
  let refreshAnnotationsInFlight = null;
8582
8634
  let inlineAnnotationsMounted = false;
8583
8635
  let databaseAnnotationsMounted = false;
8584
8636
  let annotationPanelDismissed = false;
8585
- let activeSessionId = new URLSearchParams(window.location.search).get(ANNOTATION_SESSION_PARAM);
8637
+ let activeSessionId = initialUrlParams.get(ANNOTATION_SESSION_PARAM);
8586
8638
  const annotationsChangedCallbacks = [];
8587
8639
  function notifyAnnotationsChanged() {
8588
8640
  for (const cb of annotationsChangedCallbacks)
@@ -8639,8 +8691,14 @@ ${frontmatter.yaml}
8639
8691
  if (qhPanel)
8640
8692
  qhPanel.hidden = true;
8641
8693
  document.body.classList.remove("query-history-panel-open");
8694
+ } else if (activeAnnotationId) {
8695
+ activeAnnotationId = null;
8696
+ annotationDetail.hidden = true;
8697
+ updateActiveHighlights();
8698
+ syncInlineAnnotationActive();
8642
8699
  }
8643
8700
  deps.setAnnotationPanelOpenState(open);
8701
+ syncSessionUrl();
8644
8702
  applyInlineAnnotations();
8645
8703
  }
8646
8704
  function annotationLineTarget(entry) {
@@ -8688,13 +8746,33 @@ ${frontmatter.yaml}
8688
8746
  }
8689
8747
  function withSessionParam(rawUrl) {
8690
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);
8691
8753
  if (activeSessionId)
8692
8754
  url.searchParams.set(ANNOTATION_SESSION_PARAM, activeSessionId);
8693
8755
  else
8694
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);
8695
8761
  return url.pathname + url.search;
8696
8762
  }
8697
- 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) {
8698
8776
  const tr = document.createElement("tr");
8699
8777
  tr.className = "gdp-annotation-row";
8700
8778
  tr.dataset.annotationId = entry.id;
@@ -8703,13 +8781,17 @@ ${frontmatter.yaml}
8703
8781
  td.colSpan = colSpan;
8704
8782
  const box = document.createElement("div");
8705
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));
8706
8787
  if (entry.title) {
8707
8788
  const heading2 = document.createElement("strong");
8708
8789
  heading2.className = "gdp-annotation-inline-title";
8709
8790
  heading2.textContent = entry.title;
8710
- box.appendChild(heading2);
8791
+ head.appendChild(heading2);
8711
8792
  }
8712
- box.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8793
+ head.appendChild(createCopyRefButton(entry, "gdp-annotation-inline-copy"));
8794
+ box.appendChild(head);
8713
8795
  const markdown = document.createElement("div");
8714
8796
  markdown.className = "gdp-annotation-inline-body";
8715
8797
  ensureMarkdownHighlighter();
@@ -8719,7 +8801,7 @@ ${frontmatter.yaml}
8719
8801
  tr.appendChild(td);
8720
8802
  return tr;
8721
8803
  }
8722
- function buildDatabaseAnnotationBlock(entry) {
8804
+ function buildDatabaseAnnotationBlock(entry, step) {
8723
8805
  const box = document.createElement("div");
8724
8806
  box.className = "gdp-db-annotation-inline";
8725
8807
  box.dataset.annotationId = entry.id;
@@ -8736,7 +8818,7 @@ ${frontmatter.yaml}
8736
8818
  const location2 = document.createElement("span");
8737
8819
  location2.className = "gdp-db-annotation-inline-location";
8738
8820
  location2.textContent = annotationLocationLabel(entry);
8739
- head.append(title, location2, createCopyRefButton(entry));
8821
+ head.append(createStepChip(entry, step), title, location2, createCopyRefButton(entry));
8740
8822
  const markdown = document.createElement("div");
8741
8823
  markdown.className = "gdp-db-annotation-inline-body";
8742
8824
  ensureMarkdownHighlighter();
@@ -8763,7 +8845,10 @@ ${frontmatter.yaml}
8763
8845
  strip.className = "gdp-db-annotation-strip";
8764
8846
  strip.setAttribute("aria-label", "Datastore annotations");
8765
8847
  for (const entry of matches) {
8766
- strip.appendChild(buildDatabaseAnnotationBlock(entry));
8848
+ strip.appendChild(buildDatabaseAnnotationBlock(entry, {
8849
+ index: session.entries.indexOf(entry),
8850
+ total: session.entries.length
8851
+ }));
8767
8852
  }
8768
8853
  root.prepend(strip);
8769
8854
  databaseAnnotationsMounted = true;
@@ -8838,16 +8923,19 @@ ${frontmatter.yaml}
8838
8923
  if (!session)
8839
8924
  return;
8840
8925
  let mountedInlineRows = false;
8841
- for (const entry of session.entries) {
8926
+ session.entries.forEach((entry, index) => {
8842
8927
  if (entry.target?.kind === "database")
8843
- continue;
8928
+ return;
8844
8929
  const target = inlineAnnotationTargetRow(entry);
8845
8930
  if (!target)
8846
- continue;
8931
+ return;
8847
8932
  let anchor = target;
8848
8933
  while (anchor.nextElementSibling?.classList.contains("gdp-annotation-row"))
8849
8934
  anchor = anchor.nextElementSibling;
8850
- anchor.after(buildInlineAnnotationRow(entry, target.cells.length));
8935
+ anchor.after(buildInlineAnnotationRow(entry, target.cells.length, {
8936
+ index,
8937
+ total: session.entries.length
8938
+ }));
8851
8939
  const sibling = siblingSideRow(target);
8852
8940
  if (sibling) {
8853
8941
  let sibAnchor = sibling;
@@ -8856,7 +8944,7 @@ ${frontmatter.yaml}
8856
8944
  sibAnchor.after(buildInlineSpacerRow(entry, sibling.cells.length));
8857
8945
  }
8858
8946
  mountedInlineRows = true;
8859
- }
8947
+ });
8860
8948
  inlineAnnotationsMounted = mountedInlineRows;
8861
8949
  syncInlineAnnotationWidths();
8862
8950
  }
@@ -8895,14 +8983,21 @@ ${frontmatter.yaml}
8895
8983
  if (activeSessionId === sessionId)
8896
8984
  return;
8897
8985
  activeSessionId = sessionId;
8986
+ activeAnnotationId = null;
8987
+ annotationDetail.hidden = true;
8898
8988
  syncSessionUrl();
8899
8989
  updateActiveHighlights();
8990
+ syncInlineAnnotationActive();
8900
8991
  applyInlineAnnotations();
8901
8992
  notifyAnnotationsChanged();
8902
8993
  }
8903
8994
  function restoreSessionFromUrl() {
8904
- 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);
8905
8999
  renderAnnotationPanel();
9000
+ restoreAnnotationDetailFromState();
8906
9001
  applyInlineAnnotations();
8907
9002
  }
8908
9003
  async function waitForAnnotationDiffTarget(entry) {
@@ -9007,9 +9102,8 @@ ${frontmatter.yaml}
9007
9102
  syncSessionUrl();
9008
9103
  }
9009
9104
  renderAnnotationPanel();
9105
+ restoreAnnotationDetailFromState();
9010
9106
  applyInlineAnnotations();
9011
- if (activeAnnotationId && !findAnnotation(activeAnnotationId))
9012
- hideAnnotationDetail();
9013
9107
  notifyAnnotationsChanged();
9014
9108
  }
9015
9109
  async function postAnnotationAction(payload) {
@@ -9169,6 +9263,10 @@ ${frontmatter.yaml}
9169
9263
  time.className = "annotation-session-time";
9170
9264
  time.textContent = annotationTimeLabel(session.created_at);
9171
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`;
9172
9270
  title.addEventListener("click", () => {
9173
9271
  setActiveSession(session.id === activeSessionId ? null : session.id);
9174
9272
  });
@@ -9204,7 +9302,7 @@ ${frontmatter.yaml}
9204
9302
  return;
9205
9303
  postAnnotationAction({ action: "delete", id: session.id });
9206
9304
  });
9207
- head.append(title, time, rename, del);
9305
+ head.append(title, count, time, rename, del);
9208
9306
  sessionEl.appendChild(head);
9209
9307
  const list2 = document.createElement("ol");
9210
9308
  list2.className = "annotation-entries";
@@ -9263,6 +9361,7 @@ ${frontmatter.yaml}
9263
9361
  function hideAnnotationDetail() {
9264
9362
  activeAnnotationId = null;
9265
9363
  annotationDetail.hidden = true;
9364
+ syncSessionUrl();
9266
9365
  updateActiveHighlights();
9267
9366
  syncInlineAnnotationActive();
9268
9367
  }
@@ -9306,6 +9405,25 @@ ${frontmatter.yaml}
9306
9405
  updateActiveHighlights();
9307
9406
  syncInlineAnnotationActive();
9308
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
+ }
9309
9427
  function focusStandaloneSourceLines(card, entry) {
9310
9428
  if (!entry.line)
9311
9429
  return;
@@ -9505,7 +9623,7 @@ ${frontmatter.yaml}
9505
9623
  if (persist)
9506
9624
  deps.setAnnotationPanelWidth(clamped);
9507
9625
  }
9508
- if (deps.getAnnotationPanelOpen())
9626
+ if (initialUrlParams.get(ANNOTATION_PANEL_PARAM) === "open" || activeAnnotationId || deps.getAnnotationPanelOpen())
9509
9627
  setAnnotationPanelOpen(true);
9510
9628
  applyAnnotationPanelWidth(deps.getAnnotationPanelWidth() ?? ANNOTATION_PANEL_DEFAULT_WIDTH, false);
9511
9629
  updateDatabaseCaptureButton();
@@ -9786,6 +9904,9 @@ ${frontmatter.yaml}
9786
9904
  name.appendChild(deps.createFileBreadcrumb(target.path, target.ref));
9787
9905
  name.appendChild(createFilePathCopyButton(target));
9788
9906
  header.appendChild(name);
9907
+ const repositoryWebLink = deps.createRepositoryWebLink?.(target);
9908
+ if (repositoryWebLink)
9909
+ header.appendChild(repositoryWebLink);
9789
9910
  sticky.appendChild(header);
9790
9911
  const tabsHost = document.createElement("div");
9791
9912
  tabsHost.className = "gdp-file-detail-tabs";
@@ -9988,7 +10109,8 @@ ${frontmatter.yaml}
9988
10109
  currentRange: deps.currentRange,
9989
10110
  setRoute: deps.setRoute,
9990
10111
  setPreferredSourceTab: deps.setPreferredSourceTab,
9991
- createFileBreadcrumb: deps.createFileBreadcrumb
10112
+ createFileBreadcrumb: deps.createFileBreadcrumb,
10113
+ createRepositoryWebLink: deps.createRepositoryWebLink
9992
10114
  }, target, "blame");
9993
10115
  sticky.appendChild(buildLegend());
9994
10116
  wrapper.appendChild(sticky);
@@ -9998,10 +10120,11 @@ ${frontmatter.yaml}
9998
10120
  wrapper.appendChild(body);
9999
10121
  card.appendChild(wrapper);
10000
10122
  mountFileShellCard(deps, target, card);
10123
+ const sourceShikiLang = normalizeSourceShikiLang(deps.inferLang(target.path));
10001
10124
  const [blameResp, srcText, highlighter] = await Promise.all([
10002
10125
  fetchBlame(target, generation),
10003
10126
  fetchSource(target, base2),
10004
- deps.getSyntaxHighlight() ? deps.loadSourceShikiHighlighter() : Promise.resolve(null)
10127
+ deps.getSyntaxHighlight() && sourceShikiLang ? deps.loadSourceShikiHighlighter(sourceShikiLang) : Promise.resolve(null)
10005
10128
  ]);
10006
10129
  if (generation !== activeGeneration)
10007
10130
  return;
@@ -17931,6 +18054,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
17931
18054
  spacer.className = "db-grid-spacer";
17932
18055
  const body = document.createElement("div");
17933
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
+ });
17934
18074
  const filteredEmpty = document.createElement("div");
17935
18075
  filteredEmpty.className = "db-pane-empty";
17936
18076
  filteredEmpty.hidden = true;
@@ -18119,8 +18259,23 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
18119
18259
  }
18120
18260
  }
18121
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
+ }
18122
18277
  function getColWidth(colName) {
18123
- return colWidths.get(colName) ?? DEFAULT_COL_WIDTH;
18278
+ return colWidths.get(colName) ?? defaultColWidth(colName);
18124
18279
  }
18125
18280
  function applyColWidth(colName, index) {
18126
18281
  const w = getColWidth(colName);
@@ -22417,7 +22572,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22417
22572
  });
22418
22573
  chip.addEventListener("dragend", () => clearDragState());
22419
22574
  }
22420
- tabsList.addEventListener("dragover", (e2) => {
22575
+ tabsBar.addEventListener("dragover", (e2) => {
22421
22576
  if (!draggingTabId)
22422
22577
  return;
22423
22578
  const target = e2.target?.closest(".db-tabs-chip");
@@ -22428,7 +22583,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22428
22583
  e2.dataTransfer.dropEffect = "move";
22429
22584
  clearDropTarget();
22430
22585
  });
22431
- tabsList.addEventListener("drop", (e2) => {
22586
+ tabsBar.addEventListener("drop", (e2) => {
22432
22587
  if (!draggingTabId)
22433
22588
  return;
22434
22589
  const target = e2.target?.closest(".db-tabs-chip");
@@ -23072,7 +23227,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23072
23227
  if (!el)
23073
23228
  return;
23074
23229
  el.hidden = !branch;
23075
- el.textContent = branch;
23230
+ const name = el.querySelector(".project-branch-name");
23231
+ if (name)
23232
+ name.textContent = branch;
23076
23233
  el.title = branch ? `Current branch: ${branch}` : "";
23077
23234
  }
23078
23235
  let metaFilesForViewedProgress = [];
@@ -23892,9 +24049,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23892
24049
  const unfold = card.querySelector(".gdp-file-unfold");
23893
24050
  if (unfold)
23894
24051
  unfold.disabled = collapsed;
23895
- const viewFile = card.querySelector(".gdp-view-file");
23896
- if (viewFile)
23897
- viewFile.disabled = collapsed;
24052
+ card.querySelectorAll(".gdp-preview-file, .gdp-view-file").forEach((button2) => {
24053
+ button2.disabled = collapsed;
24054
+ });
23898
24055
  }
23899
24056
  function setViewFileButtonState(button, sourceMode) {
23900
24057
  if (!button)
@@ -24094,6 +24251,27 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
24094
24251
  wrap.appendChild(box);
24095
24252
  }
24096
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
+ }
24097
24275
  if (!header.querySelector(".gdp-view-file")) {
24098
24276
  const viewFile = document.createElement("button");
24099
24277
  viewFile.type = "button";
@@ -26072,6 +26250,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26072
26250
  kind: "paragraph",
26073
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."
26074
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
+ },
26075
26257
  {
26076
26258
  kind: "paragraph",
26077
26259
  text: "In large repositories the sidebar loads folder children on demand. Folders you open are remembered and automatically re-expanded after a reload."
@@ -26163,6 +26345,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26163
26345
  kind: "paragraph",
26164
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."
26165
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
+ },
26166
26352
  {
26167
26353
  kind: "paragraph",
26168
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)."
@@ -26271,6 +26457,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26271
26457
  [
26272
26458
  "Persistent state",
26273
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."
26274
26464
  ]
26275
26465
  ]
26276
26466
  }
@@ -26743,6 +26933,10 @@ code-viewer query agent-help`
26743
26933
  kind: "paragraph",
26744
26934
  text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
26745
26935
  },
26936
+ {
26937
+ kind: "paragraph",
26938
+ text: "リポジトリの remote が GitHub の場合、リポジトリとファイルのヘッダーから現在のパスを GitHub で開けます。ソース行を選択すると、AI 参照のコピー、選択行範囲を GitHub で開く、GitHub URL のコピーを個別に選べます。Markdown / HTML の diff カードには直接 Preview を開く導線も表示されます。"
26939
+ },
26746
26940
  {
26747
26941
  kind: "paragraph",
26748
26942
  text: "大きいリポジトリではサイドバーがフォルダの中身を必要に応じて読み込みます。開いたフォルダは記憶され、次回のリロード時に同じ状態で展開し直されます。"
@@ -26834,6 +27028,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26834
27028
  kind: "paragraph",
26835
27029
  text: ".code-viewer/ の中身はすべて code-viewer が管理します。JSON ファイルは書き込みごとにバリデーションを通して安全に書き換えられるため、未知のキーは破棄され、不正な値は既定値に戻されます。手で編集すると壊れる可能性があり、壊れたファイルは .corrupt サフィックスに改名されて空の既定値で置き換えられます。"
26836
27030
  },
27031
+ {
27032
+ kind: "paragraph",
27033
+ text: "ディレクトリはリポジトリツリーに表示され、テキストファイルは Code ビューで確認できます。リポジトリ検索と diff の対象からは引き続き除外されます。"
27034
+ },
26837
27035
  {
26838
27036
  kind: "paragraph",
26839
27037
  text: "ディレクトリごと削除するのが、このリポジトリの全状態をリセットする推奨手順です。個別ファイルだけを消した場合はその系統だけがリセットされます(例: tabs.json を消すと次回起動時に DB タブがすべて閉じた状態になります)。"
@@ -26942,6 +27140,10 @@ code-viewer annotate add-db --db app.db --tab query \\
26942
27140
  [
26943
27141
  "永続化",
26944
27142
  "パネルの開閉・幅・follow・ミュート・速度は .code-viewer/settings.json に、注釈自体は .code-viewer/annotations.json に保存されます。"
27143
+ ],
27144
+ [
27145
+ "復元できる URL",
27146
+ "パネルの開閉、選択中のセッション、選択中の注釈は URL に反映されるため、リロードや共有リンクでも同じウォークスルー状態を復元できます。"
26945
27147
  ]
26946
27148
  ]
26947
27149
  }
@@ -28224,7 +28426,7 @@ code-viewer query agent-help`
28224
28426
  let mounted = false;
28225
28427
  let lifecycle = 0;
28226
28428
  let data = null;
28227
- let activeTab = "journal";
28429
+ let activeTab = "tasks";
28228
28430
  let selectedDate = todayIsoDate();
28229
28431
  let selectedEntryId = "";
28230
28432
  let creatingEntry = false;
@@ -28397,7 +28599,7 @@ code-viewer query agent-help`
28397
28599
  function applyRoute(route = deps.getRoute()) {
28398
28600
  if (!isJournalRoute(route))
28399
28601
  return;
28400
- activeTab = route.tab || activeTab || "journal";
28602
+ activeTab = route.tab || activeTab || "tasks";
28401
28603
  selectedDate = route.date || selectedDate || todayIsoDate();
28402
28604
  labelFilter = route.label || "";
28403
28605
  selectedTaskId = route.task || "";
@@ -29960,21 +30162,50 @@ code-viewer query agent-help`
29960
30162
  const copyButton = document.createElement("button");
29961
30163
  copyButton.id = "line-ref-pill-copy";
29962
30164
  copyButton.type = "button";
29963
- copyButton.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
29964
30165
  const closeButton = document.createElement("button");
29965
30166
  closeButton.id = "line-ref-pill-close";
29966
30167
  closeButton.type = "button";
29967
30168
  closeButton.title = "選択を解除";
29968
30169
  closeButton.setAttribute("aria-label", "選択を解除");
29969
30170
  closeButton.textContent = "×";
29970
- pill.appendChild(copyButton);
29971
- 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);
29972
30185
  document.body.appendChild(pill);
29973
30186
  let refText = "";
29974
30187
  let currentPath = "";
29975
30188
  let currentStart = 0;
29976
30189
  let currentEnd = 0;
30190
+ let githubUrl = "";
29977
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
+ }
29978
30209
  function selectionLineCount() {
29979
30210
  return currentEnd - currentStart + 1;
29980
30211
  }
@@ -29983,25 +30214,16 @@ code-viewer query agent-help`
29983
30214
  if (count <= 1)
29984
30215
  return "";
29985
30216
  const warn = count >= AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD;
29986
- 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>`;
29987
30218
  }
29988
30219
  function render(state) {
29989
- pill.classList.toggle("copied", state === "copied" || state === "copied-code");
29990
- if (state === "copied") {
29991
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
29992
- return;
29993
- }
29994
- if (state === "copied-code") {
29995
- const count = selectionLineCount();
29996
- const suffix = count > 1 ? ` (${count} lines)` : "";
29997
- copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code${suffix}</span>`;
29998
- return;
29999
- }
30000
- if (state === "failed") {
30001
- copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
30002
- return;
30003
- }
30004
- 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);
30005
30227
  }
30006
30228
  copyButton.addEventListener("click", async (event) => {
30007
30229
  if (!refText)
@@ -30030,6 +30252,28 @@ code-viewer query agent-help`
30030
30252
  render("ready");
30031
30253
  }, 1200);
30032
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
+ });
30033
30277
  closeButton.addEventListener("click", () => {
30034
30278
  deps.onClose();
30035
30279
  });
@@ -30043,6 +30287,8 @@ code-viewer query agent-help`
30043
30287
  currentPath = path;
30044
30288
  currentStart = Math.max(1, Math.floor(Math.min(start, end)));
30045
30289
  currentEnd = Math.max(1, Math.floor(Math.max(start, end)));
30290
+ githubUrl = deps.githubUrlForSelection(currentPath, currentStart, currentEnd) || "";
30291
+ renderGithubActions();
30046
30292
  if (feedbackTimer) {
30047
30293
  clearTimeout(feedbackTimer);
30048
30294
  feedbackTimer = null;
@@ -30060,6 +30306,8 @@ code-viewer query agent-help`
30060
30306
  currentPath = "";
30061
30307
  currentStart = 0;
30062
30308
  currentEnd = 0;
30309
+ githubUrl = "";
30310
+ githubActions.hidden = true;
30063
30311
  pill.hidden = true;
30064
30312
  }
30065
30313
  };
@@ -30529,6 +30777,19 @@ code-viewer query agent-help`
30529
30777
  return trimmed;
30530
30778
  }
30531
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
+
30532
30793
  // web-src/core/file-filter.ts
30533
30794
  function normalizeFileFilterQuery(value) {
30534
30795
  return (value || "").toLowerCase().trim();
@@ -32240,6 +32501,9 @@ code-viewer query agent-help`
32240
32501
  repositoryFallback,
32241
32502
  repositoryRootFallback,
32242
32503
  commitEntryMeta,
32504
+ repositoryWebTarget,
32505
+ openGithubLabel,
32506
+ openRepositoryWebLabel,
32243
32507
  fileBadge
32244
32508
  } = deps;
32245
32509
  let REPO_SORT = {
@@ -32395,6 +32659,13 @@ code-viewer query agent-help`
32395
32659
  });
32396
32660
  return button;
32397
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
+ }
32398
32669
  function showRepoContextMenu(event, entry, ref, onChanged) {
32399
32670
  if (document.querySelector(".gdp-dialog-backdrop"))
32400
32671
  return false;
@@ -32632,6 +32903,9 @@ code-viewer query agent-help`
32632
32903
  pathHeader.appendChild(createCopyPathButton(meta.path));
32633
32904
  pathHeader.appendChild(createOpenPathButton(meta.path || "", "directory", openDirectoryInOsTitle()));
32634
32905
  toolbar.appendChild(pathHeader);
32906
+ const repositoryWebLink = createRepositoryWebLink2(meta.path || "", meta.ref);
32907
+ if (repositoryWebLink)
32908
+ toolbar.appendChild(repositoryWebLink);
32635
32909
  if (canTrashWorktreeRef(meta.ref)) {
32636
32910
  toolbar.appendChild(createNewFolderButton(meta.path || "", () => loadRepo()));
32637
32911
  if (meta.path) {
@@ -33985,6 +34259,7 @@ code-viewer query agent-help`
33985
34259
  renderRepoBlobSidebar,
33986
34260
  placeSidebarToggle,
33987
34261
  createFileBreadcrumb,
34262
+ createRepositoryWebLink: createRepositoryWebLink2,
33988
34263
  createFileDetailMeta,
33989
34264
  createOpenPathButton,
33990
34265
  createMoveToTrashButton,
@@ -34003,7 +34278,6 @@ code-viewer query agent-help`
34003
34278
  const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
34004
34279
  const UNKNOWN_TEXT_SNIFF_BYTES = 8192;
34005
34280
  const SOURCE_LOADING_SLOW_THRESHOLD_MS = 3000;
34006
- let sourceShikiLoadPromise = null;
34007
34281
  let PREFERRED_SOURCE_TAB = null;
34008
34282
  let SOURCE_CURSOR = null;
34009
34283
  let SOURCE_CURSOR_ROWS = [];
@@ -34016,8 +34290,13 @@ code-viewer query agent-help`
34016
34290
  if (sourceRow)
34017
34291
  return sourceRow.getBoundingClientRect().height || 20;
34018
34292
  const preview = document.querySelector("#content .gdp-markdown-preview:not([hidden])");
34019
- const lineHeight = Number.parseFloat(getComputedStyle(preview || document.body).lineHeight);
34020
- 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;
34021
34300
  }
34022
34301
  function hasVisibleSourceCodeSurface() {
34023
34302
  return Array.from(document.querySelectorAll("#content .gdp-source-virtual-scroller, #content .gdp-source-table")).some((item) => item.offsetParent !== null);
@@ -34058,8 +34337,9 @@ code-viewer query agent-help`
34058
34337
  }
34059
34338
  function visibleSourceLineFallback() {
34060
34339
  const scroller = findMainScrollTarget();
34340
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
34061
34341
  if (scroller)
34062
- return Math.max(1, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) + 1);
34342
+ return Math.max(1, Math.floor(scroller.scrollTop / rowHeight) + 1);
34063
34343
  const rows = $$("#content .gdp-source-table tr[data-line]");
34064
34344
  const contentTop = document.querySelector("#content")?.getBoundingClientRect().top ?? 0;
34065
34345
  const row = rows.find((item) => item.getBoundingClientRect().bottom >= Math.max(0, contentTop));
@@ -34083,8 +34363,9 @@ code-viewer query agent-help`
34083
34363
  function scrollSourceCursorIntoView(cursor, edge = "nearest") {
34084
34364
  const scroller = findMainScrollTarget();
34085
34365
  if (scroller) {
34086
- const top = (cursor.line - 1) * VIRTUAL_SOURCE_ROW_HEIGHT;
34087
- 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;
34088
34369
  const before = scroller.scrollTop;
34089
34370
  if (edge === "center")
34090
34371
  scroller.scrollTop = Math.max(0, top - Math.round(scroller.clientHeight / 2));
@@ -34177,86 +34458,13 @@ code-viewer query agent-help`
34177
34458
  focusMainPanel();
34178
34459
  return true;
34179
34460
  }
34180
- const SOURCE_SHIKI_LANGS = Array.from(new Set([
34181
- "asciidoc",
34182
- "astro",
34183
- "bash",
34184
- "bibtex",
34185
- "c",
34186
- "clojure",
34187
- "cmake",
34188
- "cpp",
34189
- "csharp",
34190
- "css",
34191
- "dart",
34192
- "diff",
34193
- "dockerfile",
34194
- "dotenv",
34195
- "elixir",
34196
- "erb",
34197
- "erlang",
34198
- "fortran",
34199
- "go",
34200
- "gradle",
34201
- "graphql",
34202
- "haml",
34203
- "handlebars",
34204
- "haskell",
34205
- "html",
34206
- "ini",
34207
- "java",
34208
- "javascript",
34209
- "json",
34210
- "jsonc",
34211
- "julia",
34212
- "kotlin",
34213
- "less",
34214
- "liquid",
34215
- "lua",
34216
- "make",
34217
- "markdown",
34218
- "nix",
34219
- "ocaml",
34220
- "pascal",
34221
- "perl",
34222
- "php",
34223
- "powershell",
34224
- "prisma",
34225
- "properties",
34226
- "protobuf",
34227
- "pug",
34228
- "python",
34229
- "r",
34230
- "rst",
34231
- "ruby",
34232
- "rust",
34233
- "sass",
34234
- "scala",
34235
- "scss",
34236
- "sql",
34237
- "swift",
34238
- "terraform",
34239
- "tex",
34240
- "toml",
34241
- "twig",
34242
- "typescript",
34243
- "vim",
34244
- "vue",
34245
- "xml",
34246
- "yaml"
34247
- ]));
34248
- function loadSourceShikiHighlighter() {
34249
- if (!sourceShikiLoadPromise) {
34250
- sourceShikiLoadPromise = import("/shiki.js").then((mod) => {
34251
- const typed = mod;
34252
- const langs = typed.bundledLanguages ? SOURCE_SHIKI_LANGS.filter((lang) => !!typed.bundledLanguages?.[lang]) : SOURCE_SHIKI_LANGS;
34253
- return typed.createHighlighter({
34254
- themes: ["github-light", "github-dark"],
34255
- langs
34256
- });
34257
- }).catch(() => null);
34258
- }
34259
- 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
+ });
34260
34468
  }
34261
34469
  function sourceShikiLines(textValue, lang, highlighter) {
34262
34470
  try {
@@ -34275,6 +34483,57 @@ code-viewer query agent-help`
34275
34483
  return null;
34276
34484
  }
34277
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
+ }
34278
34537
  function setPreferredSourceTab(tab) {
34279
34538
  PREFERRED_SOURCE_TAB = tab;
34280
34539
  }
@@ -34438,24 +34697,14 @@ code-viewer query agent-help`
34438
34697
  card.appendChild(view);
34439
34698
  setSourceCardState(card, "done");
34440
34699
  }
34441
- function renderSourceInternalPath(card, target, kind) {
34700
+ function renderSourceInternalPath(card, target) {
34442
34701
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
34443
34702
  const info = createSourceFileInfo(target, "internal metadata", {
34444
34703
  loadMeta: false
34445
34704
  });
34446
- const extraChildren = [info];
34447
- if (kind === "code-viewer") {
34448
- const link2 = document.createElement("a");
34449
- link2.className = "gdp-btn gdp-btn-sm gdp-source-download";
34450
- link2.href = buildRawFileUrl(target);
34451
- link2.textContent = "Download raw";
34452
- link2.target = "_blank";
34453
- link2.rel = "noreferrer";
34454
- extraChildren.push(link2);
34455
- }
34456
34705
  const view = renderUnsupportedPreview({
34457
- 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.",
34458
- extraChildren
34706
+ message: "Git internal metadata is not previewed from the file viewer.",
34707
+ extraChildren: [info]
34459
34708
  });
34460
34709
  if (body)
34461
34710
  body.replaceWith(view);
@@ -34512,7 +34761,7 @@ code-viewer query agent-help`
34512
34761
  const tabs = document.createElement("div");
34513
34762
  tabs.className = "gdp-source-tabs";
34514
34763
  const { codeButton, previewButton } = appendFileViewTabs(tabs, { currentRange, setRoute, setPreferredSourceTab }, target, active, {
34515
- includeFileTabs: STATE.route.screen === "file",
34764
+ includeFileTabs: STATE.route.screen === "file" && sourceInternalPathKind(target.path) !== "code-viewer",
34516
34765
  previewable: !!options.previewable || active === "preview",
34517
34766
  sourceTabClick: "manual",
34518
34767
  ...textValue != null ? { sourceCopyButton: createSourceCopyButton(textValue) } : {}
@@ -34536,9 +34785,9 @@ code-viewer query agent-help`
34536
34785
  header.textContent = `${target.path} @ ${target.ref}`;
34537
34786
  }
34538
34787
  const lang = inferLang(target.path);
34788
+ const sourceShikiLang = normalizeSourceShikiLang(lang);
34539
34789
  const usesVirtualSource = shouldVirtualizeSource(textValue, lines) && !isVirtualSourceDisabled();
34540
34790
  const hljsRef = STATE.syntaxHighlight && usesVirtualSource ? await loadSyntaxHighlighter() : null;
34541
- const sourceShikiRef = STATE.syntaxHighlight && !usesVirtualSource ? await loadSourceShikiHighlighter() : null;
34542
34791
  if (signal?.aborted)
34543
34792
  return false;
34544
34793
  const previewable = isPreviewableSource(target.path);
@@ -34554,8 +34803,8 @@ code-viewer query agent-help`
34554
34803
  tabsHost.hidden = false;
34555
34804
  tabsHost.replaceChildren(tabs3);
34556
34805
  }
34557
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34558
- syntaxHighlight: STATE.syntaxHighlight,
34806
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34807
+ syntaxHighlight: false,
34559
34808
  signal,
34560
34809
  onNavigateMarkdown: (path, ref) => {
34561
34810
  setRoute({
@@ -34571,9 +34820,19 @@ code-viewer query agent-help`
34571
34820
  if (signal?.aborted)
34572
34821
  return false;
34573
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
+ };
34574
34832
  applyRenderedSourceTab(initialSourceTab, codeButton2, previewButton2, virtualCode, preview, false);
34575
34833
  previewButton2?.addEventListener("click", () => {
34576
34834
  applyRenderedSourceTab("preview", codeButton2, previewButton2, virtualCode, preview);
34835
+ ensurePreviewHighlight();
34577
34836
  });
34578
34837
  codeButton2?.addEventListener("click", () => {
34579
34838
  applyRenderedSourceTab("code", codeButton2, previewButton2, virtualCode, preview);
@@ -34586,6 +34845,8 @@ code-viewer query agent-help`
34586
34845
  body.replaceWith(view);
34587
34846
  else
34588
34847
  card.appendChild(view);
34848
+ if (initialSourceTab === "preview")
34849
+ ensurePreviewHighlight();
34589
34850
  return true;
34590
34851
  }
34591
34852
  const { tabs: tabs2 } = createSourceTabs(target, "code", textValue, {
@@ -34611,8 +34872,6 @@ code-viewer query agent-help`
34611
34872
  table2.className = "gdp-source-table";
34612
34873
  table2.dataset.sourcePane = "code";
34613
34874
  const tbody = document.createElement("tbody");
34614
- const sourceShikiLang = normalizeSourceShikiLang(lang);
34615
- const shikiLines = sourceShikiRef && sourceShikiLang ? sourceShikiLines(textValue, sourceShikiLang, sourceShikiRef) : null;
34616
34875
  for (let index = 0;index < lines.length; index++) {
34617
34876
  if (signal?.aborted)
34618
34877
  return false;
@@ -34627,12 +34886,7 @@ code-viewer query agent-help`
34627
34886
  bindSourceLineNumber(num, card, target, index + 1);
34628
34887
  const code2 = document.createElement("td");
34629
34888
  code2.className = "gdp-source-line-code";
34630
- if (shikiLines && shikiLines[index] != null) {
34631
- code2.innerHTML = shikiLines[index] || " ";
34632
- code2.classList.add("shiki");
34633
- } else {
34634
- code2.textContent = line || " ";
34635
- }
34889
+ code2.textContent = line || " ";
34636
34890
  tr.appendChild(num);
34637
34891
  tr.appendChild(code2);
34638
34892
  tbody.appendChild(tr);
@@ -34649,8 +34903,8 @@ code-viewer query agent-help`
34649
34903
  tabsHost.replaceChildren(tabs);
34650
34904
  }
34651
34905
  if (previewable) {
34652
- const preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await renderMarkdownPreview(textValue, target, {
34653
- syntaxHighlight: STATE.syntaxHighlight,
34906
+ let preview = previewKind === "html" ? renderHtmlPreview(target, textValue) : await (deps.renderMarkdownPreview ?? renderMarkdownPreview)(textValue, target, {
34907
+ syntaxHighlight: false,
34654
34908
  signal,
34655
34909
  onNavigateMarkdown: (path, ref) => {
34656
34910
  setRoute({
@@ -34666,9 +34920,19 @@ code-viewer query agent-help`
34666
34920
  if (signal?.aborted)
34667
34921
  return false;
34668
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
+ };
34669
34932
  applyRenderedSourceTab(initialSourceTab, codeButton, previewButton, table2, preview, false);
34670
34933
  previewButton?.addEventListener("click", () => {
34671
34934
  applyRenderedSourceTab("preview", codeButton, previewButton, table2, preview);
34935
+ ensurePreviewHighlight();
34672
34936
  });
34673
34937
  codeButton?.addEventListener("click", () => {
34674
34938
  applyRenderedSourceTab("code", codeButton, previewButton, table2, preview);
@@ -34683,6 +34947,10 @@ code-viewer query agent-help`
34683
34947
  body.replaceWith(view);
34684
34948
  else
34685
34949
  card.appendChild(view);
34950
+ if (STATE.syntaxHighlight && sourceShikiLang)
34951
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
34952
+ if (initialSourceTab === "preview")
34953
+ ensurePreviewHighlight();
34686
34954
  return true;
34687
34955
  }
34688
34956
  if (header)
@@ -34694,6 +34962,8 @@ code-viewer query agent-help`
34694
34962
  body.replaceWith(view);
34695
34963
  else
34696
34964
  card.appendChild(view);
34965
+ if (STATE.syntaxHighlight && sourceShikiLang)
34966
+ scheduleSourceHighlight(table2, target, textValue, sourceShikiLang, signal);
34697
34967
  return true;
34698
34968
  }
34699
34969
  function shouldVirtualizeSource(textValue, lines) {
@@ -34885,8 +35155,10 @@ code-viewer query agent-help`
34885
35155
  matches = nextMatches;
34886
35156
  active = matches.length ? Math.max(0, Math.min(active, matches.length - 1)) : -1;
34887
35157
  count.textContent = matches.length ? `${active + 1} / ${matches.length}` : "0 / 0";
34888
- if (active >= 0)
34889
- 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
+ }
34890
35162
  renderFn();
34891
35163
  }).catch(() => {
34892
35164
  if (version !== searchVersion)
@@ -34907,7 +35179,8 @@ code-viewer query agent-help`
34907
35179
  return;
34908
35180
  active = (active + direction + matches.length) % matches.length;
34909
35181
  count.textContent = `${active + 1} / ${matches.length}`;
34910
- 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);
34911
35184
  renderFn();
34912
35185
  };
34913
35186
  input2.addEventListener("input", () => {
@@ -35001,7 +35274,7 @@ code-viewer query agent-help`
35001
35274
  scroller.setAttribute("aria-label", `${target.path} source code`);
35002
35275
  const spacer = document.createElement("div");
35003
35276
  spacer.className = "gdp-source-virtual-spacer";
35004
- 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`;
35005
35278
  const windowEl = document.createElement("div");
35006
35279
  windowEl.className = "gdp-source-virtual-window";
35007
35280
  spacer.appendChild(windowEl);
@@ -35010,19 +35283,23 @@ code-viewer query agent-help`
35010
35283
  let raf = 0;
35011
35284
  let renderedStart = -1;
35012
35285
  let renderedEnd = -1;
35286
+ let renderedRowHeight = -1;
35013
35287
  let search = null;
35014
35288
  const render = () => {
35015
35289
  raf = 0;
35290
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35291
+ spacer.style.height = `${Math.max(1, lines.length * rowHeight)}px`;
35016
35292
  const viewportHeight = scroller.clientHeight || window.innerHeight;
35017
35293
  const overscan = 20;
35018
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
35019
- const end = Math.min(lines.length, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
35020
- 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)
35021
35297
  return;
35022
35298
  renderedStart = start;
35023
35299
  renderedEnd = end;
35300
+ renderedRowHeight = rowHeight;
35024
35301
  windowEl.replaceChildren();
35025
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35302
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
35026
35303
  const fragment = document.createDocumentFragment();
35027
35304
  for (let index = start;index < end; index++) {
35028
35305
  const row = document.createElement("div");
@@ -35138,7 +35415,8 @@ code-viewer query agent-help`
35138
35415
  const updateTotals = () => {
35139
35416
  SOURCE_CURSOR_TOTALS.set(sourceCursorKey(target), totalRows);
35140
35417
  summary.textContent = (complete ? totalRows.toLocaleString() : `${lines.size.toLocaleString()}+`) + " lines loaded from " + formatBytes(size) + ". More rows load as you scroll.";
35141
- 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`;
35142
35420
  };
35143
35421
  const loadPage = (line) => {
35144
35422
  if (signal?.aborted || complete && line > totalRows)
@@ -35175,20 +35453,24 @@ code-viewer query agent-help`
35175
35453
  let raf = 0;
35176
35454
  let renderedStart = -1;
35177
35455
  let renderedEnd = -1;
35456
+ let renderedRowHeight = -1;
35178
35457
  let search = null;
35179
35458
  let searchController = null;
35180
35459
  const render = () => {
35181
35460
  raf = 0;
35461
+ const rowHeight = sourceLineScrollAmount() || VIRTUAL_SOURCE_ROW_HEIGHT;
35462
+ spacer.style.height = `${Math.max(1, totalRows * rowHeight)}px`;
35182
35463
  const viewportHeight = scroller.clientHeight || window.innerHeight;
35183
35464
  const overscan = 20;
35184
- const start = Math.max(0, Math.floor(scroller.scrollTop / VIRTUAL_SOURCE_ROW_HEIGHT) - overscan);
35185
- const end = Math.min(totalRows, Math.ceil((scroller.scrollTop + viewportHeight) / VIRTUAL_SOURCE_ROW_HEIGHT) + overscan);
35186
- 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)
35187
35468
  return;
35188
35469
  renderedStart = start;
35189
35470
  renderedEnd = end;
35471
+ renderedRowHeight = rowHeight;
35190
35472
  windowEl.replaceChildren();
35191
- windowEl.style.transform = `translateY(${start * VIRTUAL_SOURCE_ROW_HEIGHT}px)`;
35473
+ windowEl.style.transform = `translateY(${start * rowHeight}px)`;
35192
35474
  const fragment = document.createDocumentFragment();
35193
35475
  for (let index = start;index < end; index++) {
35194
35476
  const lineNumber = index + 1;
@@ -35417,8 +35699,8 @@ code-viewer query agent-help`
35417
35699
  scrollStandaloneSourceLine(mounted, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35418
35700
  return;
35419
35701
  }
35420
- if (mounted && state === "loading")
35421
- return;
35702
+ if (mounted && state === "loading" && mounted._loadPromise)
35703
+ return mounted._loadPromise;
35422
35704
  }
35423
35705
  cancelActiveSourceLoad("navigation");
35424
35706
  const req = ++SOURCE_REQ_SEQ;
@@ -35442,7 +35724,8 @@ code-viewer query agent-help`
35442
35724
  currentRange,
35443
35725
  setRoute,
35444
35726
  setPreferredSourceTab,
35445
- createFileBreadcrumb
35727
+ createFileBreadcrumb,
35728
+ createRepositoryWebLink: createRepositoryWebLink2
35446
35729
  }, target, activeTab, internalKind ? { includeFileTabs: false, previewable: false } : {});
35447
35730
  const pathActions = header.querySelector(".gdp-file-detail-path") || header;
35448
35731
  pathActions.appendChild(createOpenPathButton(target.path, "file-parent", "open parent folder in OS"));
@@ -35453,7 +35736,7 @@ code-viewer query agent-help`
35453
35736
  loadRepo();
35454
35737
  }));
35455
35738
  }
35456
- if (!internalKind) {
35739
+ if (internalKind !== "git") {
35457
35740
  loadRawFileInfo(target).then((meta) => {
35458
35741
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35459
35742
  return;
@@ -35478,94 +35761,103 @@ code-viewer query agent-help`
35478
35761
  wrapper.appendChild(detailBody);
35479
35762
  card.appendChild(wrapper);
35480
35763
  mountFileShellCard({ $, repoFileTargetFromRoute, renderRepoBlobSidebar, placeSidebarToggle }, target, card, repoTarget);
35481
- if (internalKind) {
35482
- renderSourceInternalPath(card, target, internalKind);
35764
+ if (internalKind === "git") {
35765
+ renderSourceInternalPath(card, target);
35483
35766
  return;
35484
35767
  }
35485
35768
  const controller = new AbortController;
35486
35769
  ACTIVE_SOURCE_LOAD = { controller, req, target, card };
35487
35770
  renderSourceLoading(card, target, () => cancelActiveSourceLoad("user"));
35488
- try {
35489
- let displayKind = sourceDisplayKind(target.path);
35490
- let rawInfo = null;
35491
- if (displayKind === "unsupported") {
35492
- rawInfo = await loadRawFileInfo(target);
35493
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35494
- return;
35495
- if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
35496
- 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
+ }
35497
35782
  }
35498
- }
35499
- if (displayKind === "unsupported") {
35500
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35501
- return;
35502
- finishSourceLoad(req);
35503
- renderSourceUnsupported(card, target);
35504
- return;
35505
- }
35506
- if (displayKind === "image" || displayKind === "video" || displayKind === "audio" || displayKind === "pdf") {
35507
- 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);
35508
35788
  return;
35509
- finishSourceLoad(req);
35510
- renderSourceMedia(card, target, displayKind);
35511
- return;
35512
- }
35513
- if (displayKind === "text") {
35514
- const meta = rawInfo ?? await loadRawFileInfo(target);
35515
- 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);
35516
35795
  return;
35517
- if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
35518
- const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35796
+ }
35797
+ if (displayKind === "text") {
35798
+ const meta = rawInfo ?? await loadRawFileInfo(target);
35519
35799
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35520
35800
  return;
35521
- if (!rendered2) {
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 }));
35816
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35817
+ return;
35818
+ if (!response.ok) {
35522
35819
  finishSourceLoad(req);
35523
35820
  renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35524
35821
  return;
35525
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;
35526
35831
  scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35527
35832
  setSourceCardState(card, "done");
35528
35833
  finishSourceLoad(req);
35529
- return;
35530
35834
  }
35531
- const response = await trackLoad(fetch(buildRawFileUrl(target), { signal: controller.signal }));
35835
+ } catch (err) {
35532
35836
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35533
35837
  return;
35534
- if (!response.ok) {
35535
- finishSourceLoad(req);
35536
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35838
+ finishSourceLoad(req);
35839
+ if (isAbortError3(err)) {
35840
+ renderSourceCancelled(card, target);
35537
35841
  return;
35538
35842
  }
35539
- const textValue = await response.text();
35540
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35541
- return;
35542
- const rendered = await renderSourceText(card, target, textValue, controller.signal);
35543
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35544
- return;
35545
- if (!rendered)
35546
- return;
35547
- scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35548
- setSourceCardState(card, "done");
35549
- finishSourceLoad(req);
35550
- }
35551
- } catch (err) {
35552
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35553
- return;
35554
- finishSourceLoad(req);
35555
- if (isAbortError3(err)) {
35556
- renderSourceCancelled(card, target);
35557
- return;
35843
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35558
35844
  }
35559
- renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35560
- }
35845
+ })();
35846
+ const completed = sourceLoad.finally(() => {
35847
+ if (card._loadPromise === completed)
35848
+ delete card._loadPromise;
35849
+ });
35850
+ card._loadPromise = completed;
35851
+ return completed;
35561
35852
  }
35562
35853
  function scrollStandaloneSourceLine(card, line) {
35563
35854
  if (!line || line < 1)
35564
35855
  return;
35565
35856
  const virtualScroller = card.querySelector(".gdp-source-virtual-scroller");
35566
35857
  if (virtualScroller) {
35567
- const centeredOffset = virtualScroller.clientHeight / 2 - VIRTUAL_SOURCE_ROW_HEIGHT / 2;
35568
- 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));
35569
35861
  virtualScroller.__gdpRenderVirtualSource?.();
35570
35862
  return;
35571
35863
  }
@@ -35689,6 +35981,8 @@ code-viewer query agent-help`
35689
35981
  let PENDING_G_SCOPE = null;
35690
35982
  let PENDING_G_UNTIL = 0;
35691
35983
  let PROJECT_NAME = "";
35984
+ let PROJECT_BRANCH = "";
35985
+ let REPO_WEB_URL = null;
35692
35986
  let APP_SETTINGS = { version: 1 };
35693
35987
  let VIEW_STATE = {
35694
35988
  version: 1,
@@ -35833,11 +36127,14 @@ code-viewer query agent-help`
35833
36127
  }
35834
36128
  }
35835
36129
  function setProjectBranch(branch) {
36130
+ PROJECT_BRANCH = branch;
35836
36131
  const el = document.querySelector("#project-branch");
35837
36132
  if (!el)
35838
36133
  return;
35839
36134
  el.hidden = !branch;
35840
- el.textContent = branch;
36135
+ const name = el.querySelector(".project-branch-name");
36136
+ if (name)
36137
+ name.textContent = branch;
35841
36138
  el.title = branch ? `Current branch: ${branch}` : "";
35842
36139
  }
35843
36140
  function mergeLocalSettings(patch) {
@@ -36024,7 +36321,14 @@ code-viewer query agent-help`
36024
36321
  return normalizeViewerFontSize(APP_SETTINGS.codeFontSize);
36025
36322
  }
36026
36323
  function applyCodeFontSize(size = savedCodeFontSize()) {
36324
+ const previousLineHeight = Number.parseFloat(getComputedStyle(document.body).getPropertyValue("--code-line-height"));
36027
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
+ });
36028
36332
  }
36029
36333
  function savedSidebarFontSizeSetting() {
36030
36334
  return normalizeViewerFontSize(APP_SETTINGS.sidebarFontSize);
@@ -36057,10 +36361,11 @@ code-viewer query agent-help`
36057
36361
  const settings = await res.json();
36058
36362
  setProjectName(settings.project || "");
36059
36363
  setProjectBranch(settings.branch || "");
36364
+ REPO_WEB_URL = settings.repo_web_url;
36060
36365
  const repoLink = document.querySelector("#repo-web-link");
36061
- if (repoLink && settings.repo_web_url) {
36062
- repoLink.href = settings.repo_web_url;
36063
- repoLink.hidden = false;
36366
+ if (repoLink) {
36367
+ repoLink.href = settings.repo_web_url || "#";
36368
+ repoLink.hidden = !settings.repo_web_url;
36064
36369
  }
36065
36370
  SERVER_SCOPE_OMIT_DIRS_DEFAULT = normalizeScopeOmitDirs(settings.scope.omit_dirs_effective);
36066
36371
  SERVER_SCOPE_EXCLUDE_NAMES_DEFAULT = normalizeScopeExcludeNames(settings.scope.exclude_names_effective);
@@ -36157,7 +36462,24 @@ code-viewer query agent-help`
36157
36462
  const LINE_REF_PILL = createLineRefPill({
36158
36463
  onClose: () => {
36159
36464
  clearLineSelection();
36160
- }
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
36161
36483
  });
36162
36484
  const DIFF_LINE_SELECT = createDiffLineSelect({ pill: LINE_REF_PILL });
36163
36485
  function clearRenderedSourceLineTargets() {
@@ -36308,6 +36630,7 @@ code-viewer query agent-help`
36308
36630
  renderRepoBlobSidebar: (path, ref) => REPO_VIEW.renderRepoBlobSidebar(path, ref),
36309
36631
  placeSidebarToggle,
36310
36632
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36633
+ createRepositoryWebLink: createFileRepositoryWebLink,
36311
36634
  createFileDetailMeta: (target, meta) => REPO_VIEW.createFileDetailMeta(target, meta),
36312
36635
  createOpenPathButton,
36313
36636
  createMoveToTrashButton: (path, onDeleted) => REPO_VIEW.createMoveToTrashButton(path, onDeleted),
@@ -36341,7 +36664,7 @@ code-viewer query agent-help`
36341
36664
  currentRange,
36342
36665
  trackLoad,
36343
36666
  getSyntaxHighlight: () => STATE.syntaxHighlight,
36344
- loadSourceShikiHighlighter: () => SOURCE_VIEW.loadSourceShikiHighlighter(),
36667
+ loadSourceShikiHighlighter: (lang) => SOURCE_VIEW.loadSourceShikiHighlighter(lang),
36345
36668
  sourceShikiLines: (textValue, lang, highlighter) => SOURCE_VIEW.sourceShikiLines(textValue, lang, highlighter),
36346
36669
  inferLang: (path) => SOURCE_VIEW.inferLang(path),
36347
36670
  currentSourceLineTarget: (target) => SOURCE_VIEW.currentSourceLineTarget(target),
@@ -36349,6 +36672,7 @@ code-viewer query agent-help`
36349
36672
  bindSourceLineNumber: (num, card, target, line) => SOURCE_VIEW.bindSourceLineNumber(num, card, target, line),
36350
36673
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
36351
36674
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
36675
+ createRepositoryWebLink: createFileRepositoryWebLink,
36352
36676
  removeStandaloneSource,
36353
36677
  placeSidebarToggle,
36354
36678
  escapeHtml: escapeHtml3,
@@ -36423,6 +36747,14 @@ code-viewer query agent-help`
36423
36747
  const text3 = uiText().repo;
36424
36748
  return submodule ? { label: text3.submoduleLabel, title: text3.submoduleTitle } : { label: text3.gitlinkLabel, title: text3.gitlinkTitle };
36425
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,
36426
36758
  fileBadge: (status) => DIFF_VIEW.fileBadge(status)
36427
36759
  });
36428
36760
  const {
@@ -36465,9 +36797,7 @@ code-viewer query agent-help`
36465
36797
  queryHistory: "query history",
36466
36798
  settings: "viewer settings",
36467
36799
  theme: "toggle theme",
36468
- product: "code viewer",
36469
36800
  copyAiContext: "Copy AI context (Shift+Click to include code)",
36470
- copyAiContextLabel: "AI context",
36471
36801
  copyAiContextCopied: "Copied AI context",
36472
36802
  copyAiContextCopiedWithCode: (lines) => `Copied AI context + code (${lines} line${lines === 1 ? "" : "s"})`,
36473
36803
  copyAiContextFailed: "Copy failed",
@@ -36480,7 +36810,12 @@ code-viewer query agent-help`
36480
36810
  cancelRequestsActiveTitle: (count) => `cancel ${count} in-flight request${count === 1 ? "" : "s"}`,
36481
36811
  cancelRequestsInactiveTitle: "no in-flight requests",
36482
36812
  brandHome: "Repository home",
36483
- 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"
36484
36819
  },
36485
36820
  topbar: {
36486
36821
  resetRange: "reset to HEAD .. worktree",
@@ -36491,15 +36826,13 @@ code-viewer query agent-help`
36491
36826
  ignoreWs: "ignore whitespace changes (-w)",
36492
36827
  ignoreWsLabel: "ws",
36493
36828
  syntaxLoading: "loading...",
36494
- syntaxOn: "syntax on",
36495
- syntaxOff: "syntax off",
36829
+ syntax: "syntax",
36496
36830
  syntaxOnTitle: "syntax highlighting on",
36497
36831
  syntaxLoadingTitle: "loading syntax highlighter",
36498
36832
  syntaxErrorTitle: "failed to load syntax highlighter",
36499
36833
  syntaxOffTitle: "syntax highlighting off",
36500
36834
  hideTests: "hide test files (test|spec)",
36501
36835
  hideTestsLabel: "no test",
36502
- autoUpdate: "auto",
36503
36836
  autoUpdateOnTitle: "auto update on file change",
36504
36837
  autoUpdateOffTitle: "auto update off — manual reload"
36505
36838
  },
@@ -36586,7 +36919,9 @@ code-viewer query agent-help`
36586
36919
  gitlinkLabel: "gitlink",
36587
36920
  gitlinkTitle: "Git commit entry is not directly browsable at this ref",
36588
36921
  submoduleLabel: "submodule",
36589
- 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"
36590
36925
  },
36591
36926
  history: {
36592
36927
  title: "Commits",
@@ -36703,7 +37038,7 @@ code-viewer query agent-help`
36703
37038
  display: "Display",
36704
37039
  language: "Language",
36705
37040
  fileListFontSize: "UI font size",
36706
- fileListFontSizeHelp: "Applies to the file sidebar and datastore UI.",
37041
+ fileListFontSizeHelp: "Applies to all UI except code content.",
36707
37042
  codeFontSize: "Code font size",
36708
37043
  sizeSmall: "Small",
36709
37044
  sizeRegular: "Regular",
@@ -36755,9 +37090,7 @@ code-viewer query agent-help`
36755
37090
  queryHistory: "クエリ履歴",
36756
37091
  settings: "ビューア設定",
36757
37092
  theme: "テーマ切り替え",
36758
- product: "code viewer",
36759
37093
  copyAiContext: "AI 用コンテキストをコピー(Shift+Click でコードも添付)",
36760
- copyAiContextLabel: "AI文脈",
36761
37094
  copyAiContextCopied: "コピーしました",
36762
37095
  copyAiContextCopiedWithCode: (lines) => `コピーしました(コード付き・${lines}行)`,
36763
37096
  copyAiContextFailed: "コピーに失敗しました",
@@ -36770,7 +37103,12 @@ code-viewer query agent-help`
36770
37103
  cancelRequestsActiveTitle: (count) => `実行中のリクエストを${count}件キャンセル`,
36771
37104
  cancelRequestsInactiveTitle: "実行中のリクエストはありません",
36772
37105
  brandHome: "リポジトリホーム",
36773
- menuViews: "ビュー切り替え"
37106
+ menuViews: "ビュー切り替え",
37107
+ repoWebLink: "リポジトリのウェブページを開く",
37108
+ copyLineReference: "AI参照をコピー",
37109
+ selectedLineCount: (count) => `${count}行`,
37110
+ githubSelectionOpen: "選択行をGitHubで開く",
37111
+ githubSelectionCopy: "GitHubリンクをコピー"
36774
37112
  },
36775
37113
  topbar: {
36776
37114
  resetRange: "HEAD .. worktree に戻す",
@@ -36781,15 +37119,13 @@ code-viewer query agent-help`
36781
37119
  ignoreWs: "空白差分を無視 (-w)",
36782
37120
  ignoreWsLabel: "空白",
36783
37121
  syntaxLoading: "読み込み中...",
36784
- syntaxOn: "構文あり",
36785
- syntaxOff: "構文なし",
37122
+ syntax: "構文",
36786
37123
  syntaxOnTitle: "シンタックスハイライト有効",
36787
37124
  syntaxLoadingTitle: "シンタックスハイライトを読み込み中",
36788
37125
  syntaxErrorTitle: "シンタックスハイライトの読み込みに失敗",
36789
37126
  syntaxOffTitle: "シンタックスハイライト無効",
36790
37127
  hideTests: "test/spec ファイルを隠す",
36791
37128
  hideTestsLabel: "テスト非表示",
36792
- autoUpdate: "自動",
36793
37129
  autoUpdateOnTitle: "ファイル変更時に自動更新",
36794
37130
  autoUpdateOffTitle: "自動更新オフ — 手動で再読み込み"
36795
37131
  },
@@ -36876,7 +37212,9 @@ code-viewer query agent-help`
36876
37212
  gitlinkLabel: "固定コミット",
36877
37213
  gitlinkTitle: "特定のコミットに固定された参照です。この ref では直接開けません。",
36878
37214
  submoduleLabel: "サブモジュール",
36879
- submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。"
37215
+ submoduleTitle: "Git サブモジュール: 特定のコミットに固定されています。直接は開けません。",
37216
+ openGithub: "GitHubで開く",
37217
+ openRepositoryWeb: "リポジトリのウェブページを開く"
36880
37218
  },
36881
37219
  history: {
36882
37220
  title: "コミット",
@@ -36993,7 +37331,7 @@ code-viewer query agent-help`
36993
37331
  display: "表示",
36994
37332
  language: "言語",
36995
37333
  fileListFontSize: "UIの文字サイズ",
36996
- fileListFontSizeHelp: "ファイル一覧とデータストア画面に適用されます。",
37334
+ fileListFontSizeHelp: "コード本文を除くUI全体に適用されます。",
36997
37335
  codeFontSize: "コード表示の文字サイズ",
36998
37336
  sizeSmall: "小",
36999
37337
  sizeRegular: "標準",
@@ -37059,8 +37397,16 @@ code-viewer query agent-help`
37059
37397
  if (route && text3.nav[route])
37060
37398
  link2.textContent = text3.nav[route];
37061
37399
  });
37062
- setElementText(".global-help-link[data-route='help']", text3.nav.help);
37063
- 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
+ }
37064
37410
  document.querySelector(".brand")?.setAttribute("aria-label", text3.global.brandHome);
37065
37411
  document.querySelector(".app-menu")?.setAttribute("aria-label", text3.global.menuViews);
37066
37412
  const annotationsToggle = document.querySelector("#annotations-toggle");
@@ -37095,9 +37441,6 @@ code-viewer query agent-help`
37095
37441
  if (copyAiContext) {
37096
37442
  copyAiContext.title = text3.global.copyAiContext;
37097
37443
  copyAiContext.setAttribute("aria-label", text3.global.copyAiContext);
37098
- const copyAiContextLabel = copyAiContext.querySelector(".ai-context-label");
37099
- if (copyAiContextLabel)
37100
- copyAiContextLabel.textContent = text3.global.copyAiContextLabel;
37101
37444
  }
37102
37445
  const refReset = document.querySelector("#ref-reset");
37103
37446
  if (refReset) {
@@ -37314,7 +37657,7 @@ code-viewer query agent-help`
37314
37657
  const text3 = uiText();
37315
37658
  btn.classList.toggle("active", STATE.syntaxHighlight);
37316
37659
  btn.classList.toggle("loading", state === "loading");
37317
- 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;
37318
37661
  btn.setAttribute("aria-pressed", STATE.syntaxHighlight ? "true" : "false");
37319
37662
  btn.title = STATE.syntaxHighlight ? text3.topbar.syntaxOnTitle : state === "loading" ? text3.topbar.syntaxLoadingTitle : state === "error" ? text3.topbar.syntaxErrorTitle : text3.topbar.syntaxOffTitle;
37320
37663
  }
@@ -37699,6 +38042,7 @@ code-viewer query agent-help`
37699
38042
  setRoute,
37700
38043
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
37701
38044
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
38045
+ createRepositoryWebLink: createFileRepositoryWebLink,
37702
38046
  emptyText: () => uiText().diff
37703
38047
  }, historyRoute, { loadSidebar: false });
37704
38048
  activeFileHistoryDiffHost = mount.diffHost;
@@ -37874,7 +38218,7 @@ code-viewer query agent-help`
37874
38218
  applyHideTests();
37875
38219
  }
37876
38220
  function syncHeaderMenu() {
37877
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
38221
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
37878
38222
  const fileRouteOwner = STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history") ? "repo" : "diff";
37879
38223
  const active = link2.dataset.route === STATE.route.screen || STATE.route.screen === "file" && link2.dataset.route === fileRouteOwner;
37880
38224
  link2.classList.toggle("active", active);
@@ -37999,6 +38343,18 @@ code-viewer query agent-help`
37999
38343
  });
38000
38344
  return button;
38001
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
+ }
38002
38358
  window.addEventListener("scroll", () => enqueueInitialLoads(), {
38003
38359
  passive: true
38004
38360
  });
@@ -38108,14 +38464,34 @@ code-viewer query agent-help`
38108
38464
  if (cancelRequestsIcon) {
38109
38465
  cancelRequestsIcon.innerHTML = iconSvg("octicon-x", X_16_PATH);
38110
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
+ }
38111
38483
  }
38112
38484
  function setRefActionIcons() {
38113
38485
  const refReset = document.querySelector("#ref-reset");
38114
38486
  if (refReset)
38115
- refReset.innerHTML = iconSvg("octicon-x", X_16_PATH);
38487
+ refReset.innerHTML = iconSvg("octicon-undo", UNDO_16_PATH);
38116
38488
  const reload = document.querySelector("#reload-prom");
38117
38489
  if (reload)
38118
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
+ }
38119
38495
  }
38120
38496
  applySidebarFontSize();
38121
38497
  applyCodeFontSize();
@@ -39136,7 +39512,7 @@ code-viewer query agent-help`
39136
39512
  }
39137
39513
  window.addEventListener("popstate", applyRouteFromLocation);
39138
39514
  window.addEventListener("pagehide", () => flushViewStatePatch(true));
39139
- document.querySelectorAll(".app-menu-item, .global-help-link").forEach((link2) => {
39515
+ document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
39140
39516
  if (link2.target === "_blank")
39141
39517
  return;
39142
39518
  link2.addEventListener("click", (e2) => {
@@ -39371,9 +39747,6 @@ code-viewer query agent-help`
39371
39747
  btn.title = autoUpdateTitle;
39372
39748
  btn.setAttribute("aria-label", autoUpdateTitle);
39373
39749
  btn.setAttribute("aria-pressed", STATE.autoUpdate ? "true" : "false");
39374
- const label = btn.querySelector(".auto-update-label");
39375
- if (label)
39376
- label.textContent = text3.topbar.autoUpdate;
39377
39750
  }
39378
39751
  function setAutoUpdate(on) {
39379
39752
  STATE.autoUpdate = on;