@youtyan/code-viewer 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/app.js CHANGED
@@ -9672,6 +9672,10 @@ ${frontmatter.yaml}
9672
9672
  }
9673
9673
 
9674
9674
  // web-src/views/file-shell.ts
9675
+ function isMediaPreviewOnlySource(path) {
9676
+ const kind = sourceDisplayKind(path);
9677
+ return kind === "image" || kind === "video" || kind === "audio" || kind === "pdf";
9678
+ }
9675
9679
  function isBlobOrBlameFileRoute(route) {
9676
9680
  return route.screen === "file" && (route.view === "blob" || route.view === "blame");
9677
9681
  }
@@ -9724,14 +9728,18 @@ ${frontmatter.yaml}
9724
9728
  }
9725
9729
  function appendFileViewTabs(tabs, deps, target, activeTab, options = {}) {
9726
9730
  const includeFileTabs = options.includeFileTabs !== false;
9727
- const showPreview = options.previewable ?? (isPreviewableSource(target.path) || activeTab === "preview");
9731
+ const mediaOnly = options.previewable === undefined && isMediaPreviewOnlySource(target.path);
9732
+ const showPreview = mediaOnly || (options.previewable ?? (isPreviewableSource(target.path) || activeTab === "preview"));
9728
9733
  let previewButton = null;
9729
9734
  if (showPreview) {
9730
- previewButton = createBlobSourceTabButton(deps, target, "preview", activeTab === "preview", options);
9735
+ previewButton = createBlobSourceTabButton(deps, target, "preview", activeTab === "preview" || mediaOnly && activeTab === "code", options);
9731
9736
  tabs.appendChild(previewButton);
9732
9737
  }
9733
- const codeButton = createBlobSourceTabButton(deps, target, "code", activeTab === "code", options);
9734
- tabs.appendChild(codeButton);
9738
+ let codeButton = null;
9739
+ if (!mediaOnly) {
9740
+ codeButton = createBlobSourceTabButton(deps, target, "code", activeTab === "code", options);
9741
+ tabs.appendChild(codeButton);
9742
+ }
9735
9743
  if (includeFileTabs) {
9736
9744
  tabs.appendChild(createFileViewTabButton(deps, target, "blame", "Blame", activeTab === "blame"));
9737
9745
  tabs.appendChild(createFileViewTabButton(deps, target, "history", "History", activeTab === "history"));
@@ -22945,15 +22953,16 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22945
22953
  function shouldRenderDiffSidebar(listSame, domIntact) {
22946
22954
  return !listSame || !domIntact;
22947
22955
  }
22948
- function findDirectDiffCardByKey(target, key) {
22956
+ function collectDiffCardsByKey(target) {
22957
+ const cards = new Map;
22949
22958
  for (const child of Array.from(target.children)) {
22950
22959
  if (!child.classList.contains("gdp-file-shell"))
22951
22960
  continue;
22952
22961
  const card = child;
22953
- if (card.dataset.key === key)
22954
- return card;
22962
+ if (card.dataset.key)
22963
+ cards.set(card.dataset.key, card);
22955
22964
  }
22956
- return null;
22965
+ return cards;
22957
22966
  }
22958
22967
  function createDiffView(deps) {
22959
22968
  const {
@@ -23354,21 +23363,16 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23354
23363
  enqueueLoad(file, card, 0);
23355
23364
  }
23356
23365
  }
23357
- function invalidateLoadedCard(card, file, changedPaths) {
23366
+ function invalidateLoadedCard(card, file, changedPaths, measuredHeight) {
23358
23367
  if (!card.classList.contains("loaded") && !card.classList.contains("loading"))
23359
23368
  return false;
23360
23369
  if (changedPaths && !changedPaths.has(file.path))
23361
23370
  return false;
23362
23371
  card.dataset.reqId = String(++CLIENT_REQ_SEQ);
23372
+ card.style.minHeight = `${measuredHeight ?? card.offsetHeight}px`;
23363
23373
  card.classList.remove("loaded", "loading", "error");
23364
23374
  card.classList.add("pending");
23365
23375
  card._diffData = null;
23366
- const body = card.querySelector(".gdp-shell-body");
23367
- if (body)
23368
- body.innerHTML = "";
23369
- const head = card.querySelector(".gdp-shell-header");
23370
- if (head)
23371
- head.style.display = "";
23372
23376
  const indicator = card.querySelector(".loading-indicator");
23373
23377
  if (indicator)
23374
23378
  indicator.hidden = false;
@@ -23459,6 +23463,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23459
23463
  let invalidatedCards = 0;
23460
23464
  if (listSame && domIntact) {
23461
23465
  const pathsUnknown2 = !changedPaths;
23466
+ const cardsByKey = collectDiffCardsByKey(target);
23467
+ const measuredHeights = new Map;
23468
+ for (const f2 of newFiles) {
23469
+ const key = fileKey(f2);
23470
+ const card = cardsByKey.get(key);
23471
+ if (card && (card.classList.contains("loaded") || card.classList.contains("loading")))
23472
+ measuredHeights.set(key, card.offsetHeight);
23473
+ }
23462
23474
  let sidebarNeedsStatsUpdate = false;
23463
23475
  for (const f2 of newFiles) {
23464
23476
  const key = fileKey(f2);
@@ -23469,7 +23481,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23469
23481
  if (!sigChanged && !pathHint) {
23470
23482
  continue;
23471
23483
  }
23472
- const card = findDirectDiffCardByKey(target, key);
23484
+ const card = cardsByKey.get(key);
23473
23485
  if (!card)
23474
23486
  continue;
23475
23487
  const sizeChanged = card.dataset.sizeClass !== (f2.size_class || "small");
@@ -23485,7 +23497,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23485
23497
  stats.innerHTML = '<span class="a">+' + (f2.additions || 0) + "</span>" + '<span class="d">−' + (f2.deletions || 0) + "</span>";
23486
23498
  }
23487
23499
  card._file = f2;
23488
- const didInvalidate = sigChanged ? invalidateLoadedCard(card, f2, null) : invalidateLoadedCard(card, f2, changedPaths);
23500
+ const didInvalidate = sigChanged ? invalidateLoadedCard(card, f2, null, measuredHeights.get(key)) : invalidateLoadedCard(card, f2, changedPaths, measuredHeights.get(key));
23489
23501
  if (didInvalidate)
23490
23502
  invalidatedCards++;
23491
23503
  else if ((sigChanged || pathHint) && card.classList.contains("pending")) {
@@ -23513,11 +23525,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23513
23525
  }
23514
23526
  if (sidebarNeedsRender && canUpdateSidebar)
23515
23527
  renderSidebar(newFiles);
23516
- const oldByKey = new Map;
23517
- target.querySelectorAll(".gdp-file-shell").forEach((c2) => {
23518
- if (c2.dataset.key)
23519
- oldByKey.set(c2.dataset.key, c2);
23520
- });
23528
+ const oldByKey = collectDiffCardsByKey(target);
23521
23529
  const ordered = [];
23522
23530
  for (const f2 of newFiles) {
23523
23531
  const key = fileKey(f2);
@@ -23790,6 +23798,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23790
23798
  return;
23791
23799
  card.classList.remove("loading");
23792
23800
  card.classList.add("error");
23801
+ card.style.minHeight = "";
23793
23802
  const body = card.querySelector(".gdp-shell-body");
23794
23803
  if (!body)
23795
23804
  return;
@@ -23850,6 +23859,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23850
23859
  }
23851
23860
  function setFileCollapsed(card, collapsed) {
23852
23861
  card.classList.toggle("gdp-file-collapsed", collapsed);
23862
+ if (collapsed)
23863
+ card.style.minHeight = "";
23853
23864
  card.querySelectorAll(".d2h-files-diff, .d2h-file-diff, .gdp-source-viewer, .gdp-media").forEach((body) => {
23854
23865
  body.classList.toggle("d2h-d-none", collapsed);
23855
23866
  });
@@ -26041,7 +26052,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26041
26052
  },
26042
26053
  {
26043
26054
  kind: "paragraph",
26044
- text: "A file detail page exposes four tabs — Preview, Code, Blame, History. Code is the default and ?preview=1 opts in to the Markdown / media preview. 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."
26055
+ 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."
26045
26056
  },
26046
26057
  {
26047
26058
  kind: "paragraph",
@@ -26712,7 +26723,7 @@ code-viewer query agent-help`
26712
26723
  },
26713
26724
  {
26714
26725
  kind: "paragraph",
26715
- text: "ファイル詳細ページには Preview / Code / Blame / History 4 つのタブがあります。Code がデフォルトで、?preview=1 を付けると Markdown / メディアプレビューに切り替わります。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
26726
+ text: "ファイル詳細ページには最大 4 つのタブ (Preview / Code / Blame / History) があります。テキストファイルは Code がデフォルトで、?preview=1 を付けると Markdown / HTML プレビューに切り替わります。画像・動画・音声・PDF などのメディアファイルは Preview タブのみ表示され、Code タブは表示されません。Blame と History はそれぞれ専用 URL (view=blame, view=history) を持つため、ディープリンクとブラウザの戻る / 進むが同期し、いずれも Repository サイドバーは表示されたままです。"
26716
26727
  },
26717
26728
  {
26718
26729
  kind: "paragraph",
@@ -30565,6 +30576,7 @@ code-viewer query agent-help`
30565
30576
  let SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
30566
30577
  const SIDEBAR_LAZY_LOADED_DIRS = new Set;
30567
30578
  const SIDEBAR_LAZY_LOADING_DIRS = new Map;
30579
+ const SIDEBAR_LAZY_DIR_SIGNATURES = new Map;
30568
30580
  function savedSidebarFontSize() {
30569
30581
  return normalizeViewerFontSize(getSidebarFontSize());
30570
30582
  }
@@ -30930,37 +30942,40 @@ code-viewer query agent-help`
30930
30942
  if (SIDEBAR_TREE_ROOT)
30931
30943
  buildSidebarTreeRows(SIDEBAR_TREE_ROOT);
30932
30944
  }
30933
- function ensureVirtualSidebarDirLoaded(dir) {
30934
- if (!shouldLazyLoadSidebarDir(dir))
30935
- return Promise.resolve();
30936
- const existing = SIDEBAR_LAZY_LOADING_DIRS.get(dir.path);
30937
- if (existing)
30938
- return existing;
30945
+ function fetchSidebarDirEntries(dir) {
30939
30946
  const params = new URLSearchParams;
30940
30947
  params.set("ref", getRepoSidebarRef() || "worktree");
30941
30948
  params.set("path", dir.path);
30942
30949
  appendScopeParams(params);
30943
- const load = trackLoad(fetch(`/_tree?${params.toString()}`).then((response) => {
30950
+ return trackLoad(fetch(`/_tree?${params.toString()}`).then((response) => {
30944
30951
  if (!response.ok)
30945
30952
  throw new Error("failed to load repository tree");
30946
30953
  return response.json();
30947
- })).then((meta) => {
30948
- const entries = meta.entries.map((entry, index) => ({
30949
- order: dir.minOrder + (index + 1) / 1e5,
30950
- path: entry.path,
30951
- display_path: entry.path,
30952
- type: meta.ref === "worktree" && entry.type === "commit" && !entry.submodule ? "tree" : entry.type,
30953
- submodule: entry.submodule,
30954
- children_omitted: entry.children_omitted,
30955
- children_omitted_reason: entry.children_omitted_reason,
30956
- is_symlink: entry.is_symlink,
30957
- symlink_target: entry.symlink_target,
30958
- symlink_target_type: entry.symlink_target_type,
30959
- resolved_path: entry.resolved_path,
30960
- status: entry.status
30961
- }));
30954
+ })).then((meta) => meta.entries.map((entry, index) => ({
30955
+ order: dir.minOrder + (index + 1) / 1e5,
30956
+ path: entry.path,
30957
+ display_path: entry.path,
30958
+ type: meta.ref === "worktree" && entry.type === "commit" && !entry.submodule ? "tree" : entry.type,
30959
+ submodule: entry.submodule,
30960
+ children_omitted: entry.children_omitted,
30961
+ children_omitted_reason: entry.children_omitted_reason,
30962
+ is_symlink: entry.is_symlink,
30963
+ symlink_target: entry.symlink_target,
30964
+ symlink_target_type: entry.symlink_target_type,
30965
+ resolved_path: entry.resolved_path,
30966
+ status: entry.status
30967
+ })));
30968
+ }
30969
+ function ensureVirtualSidebarDirLoaded(dir) {
30970
+ if (!shouldLazyLoadSidebarDir(dir))
30971
+ return Promise.resolve();
30972
+ const existing = SIDEBAR_LAZY_LOADING_DIRS.get(dir.path);
30973
+ if (existing)
30974
+ return existing;
30975
+ const load = fetchSidebarDirEntries(dir).then((entries) => {
30962
30976
  mergeSidebarTreeEntries(entries);
30963
30977
  SIDEBAR_LAZY_LOADED_DIRS.add(dir.path);
30978
+ SIDEBAR_LAZY_DIR_SIGNATURES.set(dir.path, sidebarItemsSignature(entries));
30964
30979
  }).finally(() => {
30965
30980
  SIDEBAR_LAZY_LOADING_DIRS.delete(dir.path);
30966
30981
  });
@@ -31363,6 +31378,102 @@ code-viewer query agent-help`
31363
31378
  };
31364
31379
  visit(root);
31365
31380
  }
31381
+ function sidebarItemsSignature(items) {
31382
+ return items.map((f2) => [
31383
+ f2.path,
31384
+ f2.type || "blob",
31385
+ f2.status || "",
31386
+ f2.children_omitted ? 1 : 0,
31387
+ f2.children_omitted_reason || "",
31388
+ f2.is_symlink ? 1 : 0,
31389
+ f2.symlink_target || "",
31390
+ f2.resolved_path || "",
31391
+ f2.submodule ? 1 : 0
31392
+ ].join("\x00")).join(`
31393
+ `);
31394
+ }
31395
+ function findSidebarTreeDir(node, path) {
31396
+ if (node.path === path)
31397
+ return node;
31398
+ for (const child of Object.values(node.dirs)) {
31399
+ if (path === child.path || path.startsWith(`${child.path}/`))
31400
+ return findSidebarTreeDir(child, path);
31401
+ }
31402
+ return null;
31403
+ }
31404
+ function graftLoadedSidebarDirs(oldRoot, newRoot) {
31405
+ const scope = newRoot.path ? `${newRoot.path}/` : "";
31406
+ const paths = [...SIDEBAR_LAZY_LOADED_DIRS].filter((p2) => p2 !== newRoot.path && (!scope || p2.startsWith(scope))).sort((a2, b2) => a2.length - b2.length);
31407
+ for (const path of paths) {
31408
+ const oldNode = findSidebarTreeDir(oldRoot, path);
31409
+ const newNode = findSidebarTreeDir(newRoot, path);
31410
+ if (!oldNode || !newNode || newNode.children_omitted) {
31411
+ SIDEBAR_LAZY_LOADED_DIRS.delete(path);
31412
+ SIDEBAR_LAZY_DIR_SIGNATURES.delete(path);
31413
+ continue;
31414
+ }
31415
+ if (sidebarTreeNodeHasChildren(newNode))
31416
+ continue;
31417
+ newNode.dirs = oldNode.dirs;
31418
+ newNode.files = oldNode.files;
31419
+ }
31420
+ }
31421
+ function applySidebarDirEntries(dir, entries) {
31422
+ const signature = sidebarItemsSignature(entries);
31423
+ if (SIDEBAR_LAZY_DIR_SIGNATURES.get(dir.path) === signature)
31424
+ return false;
31425
+ SIDEBAR_LAZY_DIR_SIGNATURES.set(dir.path, signature);
31426
+ const oldSubtree = { ...dir };
31427
+ dir.dirs = {};
31428
+ dir.files = [];
31429
+ for (const entry of entries)
31430
+ upsertSidebarTreeEntry(entry, entry.order ?? 0);
31431
+ graftLoadedSidebarDirs(oldSubtree, dir);
31432
+ return true;
31433
+ }
31434
+ function rebuildVirtualSidebarRows() {
31435
+ if (!SIDEBAR_TREE_ROOT)
31436
+ return;
31437
+ SIDEBAR_TREE_ITEMS_CACHE = new WeakMap;
31438
+ buildSidebarTreeRows(SIDEBAR_TREE_ROOT);
31439
+ rerenderVirtualSidebar();
31440
+ if (STATE.activeFile)
31441
+ markActive(STATE.activeFile);
31442
+ applyFilter();
31443
+ }
31444
+ async function refreshRepoSidebarTree(rootFiles) {
31445
+ if (!isRepositorySidebarMode() || !isVirtualSidebarActive())
31446
+ return;
31447
+ if (!SIDEBAR_TREE_ROOT)
31448
+ return;
31449
+ if (sidebarItemsSignature(rootFiles) !== sidebarItemsSignature(SIDEBAR_FILES)) {
31450
+ const newRoot = buildTree(rootFiles);
31451
+ graftLoadedSidebarDirs(SIDEBAR_TREE_ROOT, newRoot);
31452
+ SIDEBAR_FILES = rootFiles;
31453
+ SIDEBAR_TREE_ROOT = newRoot;
31454
+ rebuildVirtualSidebarRows();
31455
+ }
31456
+ const root = SIDEBAR_TREE_ROOT;
31457
+ const refreshed = await Promise.all([...SIDEBAR_LAZY_LOADED_DIRS].map(async (path) => {
31458
+ const node = findSidebarTreeDir(root, path);
31459
+ if (!node)
31460
+ return null;
31461
+ const entries = await fetchSidebarDirEntries(node).catch(() => null);
31462
+ return entries ? { path, entries } : null;
31463
+ }));
31464
+ if (SIDEBAR_TREE_ROOT !== root)
31465
+ return;
31466
+ let childrenChanged = false;
31467
+ for (const result of refreshed) {
31468
+ if (!result)
31469
+ continue;
31470
+ const node = findSidebarTreeDir(root, result.path);
31471
+ if (node && applySidebarDirEntries(node, result.entries))
31472
+ childrenChanged = true;
31473
+ }
31474
+ if (childrenChanged)
31475
+ rebuildVirtualSidebarRows();
31476
+ }
31366
31477
  function renderFlat(files, ul, onFileClick) {
31367
31478
  files.forEach((f2, i2) => {
31368
31479
  const li = document.createElement("li");
@@ -31426,6 +31537,7 @@ code-viewer query agent-help`
31426
31537
  SIDEBAR_ROW_BY_PATH = new Map;
31427
31538
  SIDEBAR_LAZY_LOADED_DIRS.clear();
31428
31539
  SIDEBAR_LAZY_LOADING_DIRS.clear();
31540
+ SIDEBAR_LAZY_DIR_SIGNATURES.clear();
31429
31541
  if (!onFileClick)
31430
31542
  STATE.files = files;
31431
31543
  SIDEBAR_FILES = files;
@@ -31546,7 +31658,8 @@ code-viewer query agent-help`
31546
31658
  setActiveSidebarItem(sidebarItemByPath(path));
31547
31659
  if ($("#filelist").classList.contains("tree-virtual")) {
31548
31660
  renderVirtualSidebarWindow();
31549
- scrollVirtualSidebarPathIntoView(path);
31661
+ if (options.reveal)
31662
+ scrollVirtualSidebarPathIntoView(path);
31550
31663
  return;
31551
31664
  }
31552
31665
  if (options.reveal) {
@@ -31972,6 +32085,7 @@ code-viewer query agent-help`
31972
32085
  }
31973
32086
  return {
31974
32087
  renderSidebar,
32088
+ refreshRepoSidebarTree,
31975
32089
  applyFilter,
31976
32090
  scheduleApplyFilter,
31977
32091
  flushSidebarFilter,
@@ -32041,6 +32155,7 @@ code-viewer query agent-help`
32041
32155
  markActive,
32042
32156
  applyFilter,
32043
32157
  renderSidebar,
32158
+ refreshRepoSidebarTree,
32044
32159
  rerenderVirtualSidebar,
32045
32160
  ensureVirtualSidebarDirLoaded,
32046
32161
  scrollVirtualSidebarPathIntoView,
@@ -32436,18 +32551,26 @@ code-viewer query agent-help`
32436
32551
  });
32437
32552
  return nav;
32438
32553
  }
32554
+ let REPO_RENDER_SIGNATURE = "";
32555
+ let REPO_RENDER_SEQ = 0;
32556
+ let REPO_RENDER_LOCATION = "";
32439
32557
  async function renderRepo(meta) {
32558
+ const myRender = ++REPO_RENDER_SEQ;
32440
32559
  setProjectName(meta.project || "");
32441
32560
  setPageMode();
32442
32561
  removeStandaloneSource();
32443
32562
  $("#empty").classList.add("hidden");
32444
- $("#diff").replaceChildren();
32445
32563
  if (!isRepoSidebarReusable(meta.ref))
32446
32564
  $("#totals").textContent = "";
32447
32565
  STATE.files = [];
32448
32566
  clearLoadQueue();
32449
32567
  renderRepoBlobSidebar(meta.path || "", meta.ref);
32450
32568
  const target = $("#diff");
32569
+ const signature = JSON.stringify(meta);
32570
+ if (signature === REPO_RENDER_SIGNATURE && target.querySelector(":scope > .gdp-repo-shell")) {
32571
+ placeSidebarToggle();
32572
+ return;
32573
+ }
32451
32574
  const shell = document.createElement("section");
32452
32575
  shell.className = "gdp-repo-shell";
32453
32576
  const toolbar = document.createElement("div");
@@ -32606,9 +32729,33 @@ code-viewer query agent-help`
32606
32729
  readme.appendChild(wrapper);
32607
32730
  shell.appendChild(readme);
32608
32731
  }
32609
- target.appendChild(shell);
32732
+ if (myRender !== REPO_RENDER_SEQ || !isActiveRepoRoute(meta.ref || "worktree", meta.path || ""))
32733
+ return;
32734
+ REPO_RENDER_SIGNATURE = signature;
32735
+ const location2 = `${meta.ref || "worktree"}\x00${meta.path || ""}`;
32736
+ const sameLocation = location2 === REPO_RENDER_LOCATION;
32737
+ REPO_RENDER_LOCATION = location2;
32738
+ const savedScroll = window.scrollY;
32739
+ target.replaceChildren(shell);
32740
+ window.scrollTo(0, sameLocation ? savedScroll : 0);
32610
32741
  placeSidebarToggle();
32611
32742
  }
32743
+ function repoTreeEntriesToSidebarItems(entries, ref) {
32744
+ return entries.map((entry, index) => ({
32745
+ order: index + 1,
32746
+ path: entry.path,
32747
+ display_path: entry.path,
32748
+ type: canBrowseRepoEntry(entry, ref) ? "tree" : entry.type,
32749
+ submodule: entry.submodule,
32750
+ children_omitted: entry.children_omitted,
32751
+ children_omitted_reason: entry.children_omitted_reason,
32752
+ is_symlink: entry.is_symlink,
32753
+ symlink_target: entry.symlink_target,
32754
+ symlink_target_type: entry.symlink_target_type,
32755
+ resolved_path: entry.resolved_path,
32756
+ status: entry.status
32757
+ }));
32758
+ }
32612
32759
  function renderRepoBlobSidebar(currentPath, ref) {
32613
32760
  syncRepoTargetInput(ref);
32614
32761
  const normalizedRef = ref || "worktree";
@@ -32639,20 +32786,7 @@ code-viewer query agent-help`
32639
32786
  })).then(async (meta) => {
32640
32787
  if (!isActiveRepoTreeRef(normalizedRef))
32641
32788
  return;
32642
- const files = meta.entries.map((entry, index) => ({
32643
- order: index + 1,
32644
- path: entry.path,
32645
- display_path: entry.path,
32646
- type: canBrowseRepoEntry(entry, normalizedRef) ? "tree" : entry.type,
32647
- submodule: entry.submodule,
32648
- children_omitted: entry.children_omitted,
32649
- children_omitted_reason: entry.children_omitted_reason,
32650
- is_symlink: entry.is_symlink,
32651
- symlink_target: entry.symlink_target,
32652
- symlink_target_type: entry.symlink_target_type,
32653
- resolved_path: entry.resolved_path,
32654
- status: entry.status
32655
- }));
32789
+ const files = repoTreeEntriesToSidebarItems(meta.entries, normalizedRef);
32656
32790
  setRepoSidebarRef(normalizedRef);
32657
32791
  renderSidebar(files, (file) => {
32658
32792
  if (file.type === "tree") {
@@ -32700,15 +32834,17 @@ code-viewer query agent-help`
32700
32834
  rerenderVirtualSidebar();
32701
32835
  }
32702
32836
  async function activateRepoSidebarPath(currentPath) {
32837
+ const reveal = getSidebarVirtualActivePath() !== currentPath;
32703
32838
  await loadRepoSidebarAncestors(currentPath);
32704
- markActive(currentPath, { reveal: true });
32839
+ markActive(currentPath, { reveal });
32705
32840
  applyFilter();
32706
32841
  const row = getSidebarRowByPath(currentPath);
32707
32842
  if (row?.kind === "dir" && row.dir && shouldLazyLoadSidebarDir(row.dir))
32708
32843
  ensureVirtualSidebarDirLoaded(row.dir).then(() => {
32709
32844
  if (getSidebarVirtualActivePath() === currentPath) {
32710
32845
  rerenderVirtualSidebar();
32711
- scrollVirtualSidebarPathIntoView(currentPath);
32846
+ if (reveal)
32847
+ scrollVirtualSidebarPathIntoView(currentPath);
32712
32848
  }
32713
32849
  });
32714
32850
  }
@@ -32959,6 +33095,42 @@ code-viewer query agent-help`
32959
33095
  }
32960
33096
  let REPO_SIDEBAR_LOAD_REF = null;
32961
33097
  let REPO_SIDEBAR_LOAD = null;
33098
+ let REPO_SIDEBAR_REFRESHING = false;
33099
+ let REPO_SIDEBAR_REFRESH_QUEUED = false;
33100
+ async function refreshRepoSidebar() {
33101
+ const ref = activeRepoTreeRef();
33102
+ if (ref == null)
33103
+ return;
33104
+ if (!isRepoSidebarReusable(ref)) {
33105
+ invalidateRepoSidebar();
33106
+ await renderRepoBlobSidebar(STATE.route.screen === "repo" ? STATE.route.path || "" : "", ref);
33107
+ return;
33108
+ }
33109
+ if (REPO_SIDEBAR_REFRESHING) {
33110
+ REPO_SIDEBAR_REFRESH_QUEUED = true;
33111
+ return;
33112
+ }
33113
+ REPO_SIDEBAR_REFRESHING = true;
33114
+ try {
33115
+ const params = new URLSearchParams;
33116
+ params.set("ref", ref);
33117
+ appendScopeParams(params);
33118
+ const meta = await trackLoad(fetch(`/_tree?${params.toString()}`).then((r2) => {
33119
+ if (!r2.ok)
33120
+ throw new Error("failed to load repository tree");
33121
+ return r2.json();
33122
+ }));
33123
+ if (!isActiveRepoTreeRef(ref) || !isRepoSidebarReusable(ref))
33124
+ return;
33125
+ await refreshRepoSidebarTree(repoTreeEntriesToSidebarItems(meta.entries, ref));
33126
+ } catch {} finally {
33127
+ REPO_SIDEBAR_REFRESHING = false;
33128
+ if (REPO_SIDEBAR_REFRESH_QUEUED) {
33129
+ REPO_SIDEBAR_REFRESH_QUEUED = false;
33130
+ refreshRepoSidebar();
33131
+ }
33132
+ }
33133
+ }
32962
33134
  return {
32963
33135
  loadRepo,
32964
33136
  createFileDetailMeta,
@@ -32972,6 +33144,7 @@ code-viewer query agent-help`
32972
33144
  handleSidebarContextMenu,
32973
33145
  fileEntryIcon,
32974
33146
  invalidateRepoSidebar,
33147
+ refreshRepoSidebar,
32975
33148
  showTrashError
32976
33149
  };
32977
33150
  }
@@ -33918,6 +34091,8 @@ code-viewer query agent-help`
33918
34091
  });
33919
34092
  }
33920
34093
  function applyRenderedSourceTab(tab, codeButton, previewButton, codePane, previewPane, updateRoute = true) {
34094
+ if (!codeButton)
34095
+ return false;
33921
34096
  if (tab === "preview" && (!previewButton || !previewPane))
33922
34097
  return false;
33923
34098
  codeButton.classList.toggle("active", tab === "code");
@@ -34167,6 +34342,7 @@ code-viewer query agent-help`
34167
34342
  body.replaceWith(view);
34168
34343
  else
34169
34344
  card.appendChild(view);
34345
+ setSourceCardState(card, "error");
34170
34346
  }
34171
34347
  function renderSourceCancelled(card, target) {
34172
34348
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
@@ -34191,6 +34367,7 @@ code-viewer query agent-help`
34191
34367
  body.replaceWith(view);
34192
34368
  else
34193
34369
  card.appendChild(view);
34370
+ setSourceCardState(card, "cancelled");
34194
34371
  }
34195
34372
  function renderSourceUnsupported(card, target) {
34196
34373
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
@@ -34209,6 +34386,7 @@ code-viewer query agent-help`
34209
34386
  body.replaceWith(view);
34210
34387
  else
34211
34388
  card.appendChild(view);
34389
+ setSourceCardState(card, "done");
34212
34390
  }
34213
34391
  function renderSourceInternalPath(card, target, kind) {
34214
34392
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
@@ -34233,6 +34411,7 @@ code-viewer query agent-help`
34233
34411
  body.replaceWith(view);
34234
34412
  else
34235
34413
  card.appendChild(view);
34414
+ setSourceCardState(card, "done");
34236
34415
  }
34237
34416
  function renderHtmlPreview(target, html) {
34238
34417
  return renderHtmlPreviewFrame(`${target.path} preview`, html);
@@ -34346,7 +34525,7 @@ code-viewer query agent-help`
34346
34525
  previewButton2?.addEventListener("click", () => {
34347
34526
  applyRenderedSourceTab("preview", codeButton2, previewButton2, virtualCode, preview);
34348
34527
  });
34349
- codeButton2.addEventListener("click", () => {
34528
+ codeButton2?.addEventListener("click", () => {
34350
34529
  applyRenderedSourceTab("code", codeButton2, previewButton2, virtualCode, preview);
34351
34530
  });
34352
34531
  if (header)
@@ -34441,7 +34620,7 @@ code-viewer query agent-help`
34441
34620
  previewButton?.addEventListener("click", () => {
34442
34621
  applyRenderedSourceTab("preview", codeButton, previewButton, table2, preview);
34443
34622
  });
34444
- codeButton.addEventListener("click", () => {
34623
+ codeButton?.addEventListener("click", () => {
34445
34624
  applyRenderedSourceTab("code", codeButton, previewButton, table2, preview);
34446
34625
  });
34447
34626
  if (header)
@@ -34761,7 +34940,7 @@ code-viewer query agent-help`
34761
34940
  e2.preventDefault();
34762
34941
  const url = new URL(full.href, window.location.origin);
34763
34942
  setRoute(parseRoute(url.pathname, url.search, currentRange()), true);
34764
- renderStandaloneSource(target);
34943
+ renderStandaloneSource(target, { refresh: true });
34765
34944
  });
34766
34945
  actions.append(copy, full);
34767
34946
  info.append(badge, summary, actions);
@@ -34877,7 +35056,7 @@ code-viewer query agent-help`
34877
35056
  e2.preventDefault();
34878
35057
  const url = new URL(full.href, window.location.origin);
34879
35058
  setRoute(parseRoute(url.pathname, url.search, currentRange()), true);
34880
- renderStandaloneSource(target);
35059
+ renderStandaloneSource(target, { refresh: true });
34881
35060
  });
34882
35061
  actions.append(raw, full);
34883
35062
  info.append(badge, summary, actions);
@@ -35143,6 +35322,7 @@ code-viewer query agent-help`
35143
35322
  body.replaceWith(view);
35144
35323
  else
35145
35324
  card.appendChild(view);
35325
+ setSourceCardState(card, "done");
35146
35326
  }
35147
35327
  function _renderSourceBinary(card, target) {
35148
35328
  const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
@@ -35166,7 +35346,30 @@ code-viewer query agent-help`
35166
35346
  else
35167
35347
  card.appendChild(view);
35168
35348
  }
35169
- async function renderStandaloneSource(target) {
35349
+ function mountedStandaloneSourceCard(target) {
35350
+ const card = document.querySelector(".gdp-standalone-source");
35351
+ if (!card)
35352
+ return null;
35353
+ if (card.dataset.path !== target.path)
35354
+ return null;
35355
+ if (card.dataset.sourceRef !== (target.ref || "worktree"))
35356
+ return null;
35357
+ return card;
35358
+ }
35359
+ function setSourceCardState(card, state) {
35360
+ card.dataset.sourceState = state;
35361
+ }
35362
+ async function renderStandaloneSource(target, options = {}) {
35363
+ if (!options.refresh) {
35364
+ const mounted = mountedStandaloneSourceCard(target);
35365
+ const state = mounted?.dataset.sourceState;
35366
+ if (mounted && state === "done") {
35367
+ scrollStandaloneSourceLine(mounted, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35368
+ return;
35369
+ }
35370
+ if (mounted && state === "loading")
35371
+ return;
35372
+ }
35170
35373
  cancelActiveSourceLoad("navigation");
35171
35374
  const req = ++SOURCE_REQ_SEQ;
35172
35375
  const repoTarget = repoFileTargetFromRoute();
@@ -35178,10 +35381,13 @@ code-viewer query agent-help`
35178
35381
  const card = document.createElement("article");
35179
35382
  card.className = "gdp-file-shell loaded gdp-standalone-source gdp-source-mode";
35180
35383
  card.dataset.path = target.path;
35384
+ card.dataset.sourceRef = target.ref || "worktree";
35385
+ setSourceCardState(card, "loading");
35181
35386
  const internalKind = sourceInternalPathKind(target.path);
35182
35387
  const wrapper = document.createElement("div");
35183
35388
  wrapper.className = "gdp-file-detail-wrapper";
35184
- const activeTab = !internalKind && STATE.route.screen === "file" && STATE.route.preview ? "preview" : "code";
35389
+ const mediaOnly = !internalKind && isMediaPreviewOnlySource(target.path);
35390
+ const activeTab = mediaOnly || !internalKind && STATE.route.screen === "file" && STATE.route.preview ? "preview" : "code";
35185
35391
  const { sticky, header } = createFileShellSticky({
35186
35392
  currentRange,
35187
35393
  setRoute,
@@ -35262,9 +35468,13 @@ code-viewer query agent-help`
35262
35468
  const rendered2 = await renderPagedSourceText(card, target, meta.size, controller.signal);
35263
35469
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
35264
35470
  return;
35265
- if (!rendered2)
35471
+ if (!rendered2) {
35472
+ finishSourceLoad(req);
35473
+ renderSourceError(card, target, `Cannot load ${target.path} at ${target.ref}`);
35266
35474
  return;
35475
+ }
35267
35476
  scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35477
+ setSourceCardState(card, "done");
35268
35478
  finishSourceLoad(req);
35269
35479
  return;
35270
35480
  }
@@ -35285,6 +35495,7 @@ code-viewer query agent-help`
35285
35495
  if (!rendered)
35286
35496
  return;
35287
35497
  scrollStandaloneSourceLine(card, lineTargetStart(STATE.route.screen === "file" ? STATE.route.line : undefined));
35498
+ setSourceCardState(card, "done");
35288
35499
  finishSourceLoad(req);
35289
35500
  }
35290
35501
  } catch (err) {
@@ -35312,7 +35523,7 @@ code-viewer query agent-help`
35312
35523
  if (row)
35313
35524
  row.scrollIntoView({ block: "center" });
35314
35525
  }
35315
- function applySourceRouteToShell() {
35526
+ function applySourceRouteToShell(options = {}) {
35316
35527
  const target = sourceTargetFromRoute();
35317
35528
  setPageMode();
35318
35529
  if (!target) {
@@ -35322,7 +35533,7 @@ code-viewer query agent-help`
35322
35533
  });
35323
35534
  return;
35324
35535
  }
35325
- renderStandaloneSource(target);
35536
+ renderStandaloneSource(target, options);
35326
35537
  }
35327
35538
  function inferLang(path) {
35328
35539
  const name = sourceFileName(path);
@@ -35989,6 +36200,7 @@ code-viewer query agent-help`
35989
36200
  });
35990
36201
  const {
35991
36202
  renderSidebar,
36203
+ refreshRepoSidebarTree,
35992
36204
  applyFilter,
35993
36205
  scheduleApplyFilter,
35994
36206
  flushSidebarFilter,
@@ -36105,6 +36317,7 @@ code-viewer query agent-help`
36105
36317
  markActive,
36106
36318
  applyFilter,
36107
36319
  renderSidebar,
36320
+ refreshRepoSidebarTree,
36108
36321
  rerenderVirtualSidebar,
36109
36322
  ensureVirtualSidebarDirLoaded,
36110
36323
  scrollVirtualSidebarPathIntoView,
@@ -36169,6 +36382,7 @@ code-viewer query agent-help`
36169
36382
  closeRepoContextMenu,
36170
36383
  handleSidebarContextMenu,
36171
36384
  invalidateRepoSidebar,
36385
+ refreshRepoSidebar,
36172
36386
  showTrashError
36173
36387
  } = REPO_VIEW;
36174
36388
  const SEARCH_PALETTE = createSearchPalette({
@@ -37468,12 +37682,12 @@ code-viewer query agent-help`
37468
37682
  history.replaceState(historyStateForRoute(STATE.route), "", url + window.location.hash);
37469
37683
  }
37470
37684
  }
37471
- function dispatchFileRoute(route) {
37685
+ function dispatchFileRoute(route, options = {}) {
37472
37686
  if (route.view === "blob") {
37473
37687
  setStatus("live");
37474
37688
  removeFileHistoryShell2();
37475
37689
  BLAME_VIEW.removeBlamePage();
37476
- applySourceRouteToShell();
37690
+ applySourceRouteToShell(options);
37477
37691
  return true;
37478
37692
  }
37479
37693
  if (route.view === "blame") {
@@ -38481,7 +38695,7 @@ code-viewer query agent-help`
38481
38695
  setStatus("live");
38482
38696
  return Promise.resolve(null);
38483
38697
  }
38484
- if (STATE.route.screen === "file" && !(isFileHistoryRoute(STATE.route) && activeHistoryPathFilter) && dispatchFileRoute(STATE.route)) {
38698
+ if (STATE.route.screen === "file" && !(isFileHistoryRoute(STATE.route) && activeHistoryPathFilter) && dispatchFileRoute(STATE.route, { refresh: true })) {
38485
38699
  return Promise.resolve({
38486
38700
  structureChanged: false,
38487
38701
  invalidatedCards: 0,
@@ -39174,7 +39388,7 @@ code-viewer query agent-help`
39174
39388
  if (!shouldAutoLoadCurrentRoute(route))
39175
39389
  return;
39176
39390
  if (isBlobOrBlameFileRoute(route)) {
39177
- dispatchFileRoute(route);
39391
+ dispatchFileRoute(route, { refresh: true });
39178
39392
  return;
39179
39393
  }
39180
39394
  doSseLoad(paths);
@@ -39214,12 +39428,12 @@ code-viewer query agent-help`
39214
39428
  const viewingPath = route.path;
39215
39429
  if (paths && viewingPath && !paths.has(viewingPath))
39216
39430
  return;
39217
- dispatchFileRoute(route);
39431
+ dispatchFileRoute(route, { refresh: true });
39218
39432
  return;
39219
39433
  }
39220
39434
  if (route.screen === "repo") {
39221
- invalidateRepoSidebar();
39222
39435
  loadRepo();
39436
+ refreshRepoSidebar();
39223
39437
  return;
39224
39438
  }
39225
39439
  if (route.screen !== "diff" && route.screen !== "history") {