@youtyan/code-viewer 0.8.6 → 0.8.7

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.
@@ -1449,18 +1449,13 @@ function resolveGitArgs(args) {
1449
1449
  return [commandForExternal("git"), ...args.slice(1)];
1450
1450
  }
1451
1451
  function gitFailureMessage(res, fallback) {
1452
- if (isCommandNotFoundResult("git", res))
1453
- return commandNotFoundDetail("git");
1454
- return res.stderr?.trim() || fallback;
1452
+ const message = isCommandNotFoundResult("git", res) ? commandNotFoundDetail("git") : res.stderr?.trim() || fallback;
1453
+ console.error(`[code-viewer] ${fallback} (git exit ${res.code}): ${message}`);
1454
+ return message;
1455
1455
  }
1456
1456
  function gitFailureResult(res, fallback) {
1457
- if (!isCommandNotFoundResult("git", res)) {
1458
- return { error: fallback };
1459
- }
1460
- return {
1461
- error: commandNotFoundDetail("git"),
1462
- status: 503
1463
- };
1457
+ const error = gitFailureMessage(res, fallback);
1458
+ return isCommandNotFoundResult("git", res) ? { error, status: 503 } : { error };
1464
1459
  }
1465
1460
  function runGitRefLookup(args, cwd) {
1466
1461
  const res = run(args, cwd);
@@ -2534,10 +2529,19 @@ async function untrackedMetaAsync(cwd) {
2534
2529
  return results.filter((meta) => meta !== null);
2535
2530
  }
2536
2531
  async function fileMetaResultAsync(args, cwd, includeUntracked = false) {
2537
- const ns = await nameStatusResultAsync(args, cwd);
2532
+ let ns;
2533
+ let nm;
2534
+ if (args.includes("--")) {
2535
+ [ns, nm] = await Promise.all([
2536
+ nameStatusResultAsync(args, cwd),
2537
+ numstatZResultAsync(args, cwd)
2538
+ ]);
2539
+ } else {
2540
+ ns = await nameStatusResultAsync(args, cwd);
2541
+ nm = await numstatZResultAsync(args, cwd);
2542
+ }
2538
2543
  if (ns.error)
2539
2544
  return { files: [], error: ns.error };
2540
- const nm = await numstatZResultAsync(args, cwd);
2541
2545
  if (nm.error)
2542
2546
  return { files: [], error: nm.error };
2543
2547
  const byPath = new Map(nm.files.map((file) => [file.path, file]));
@@ -15054,6 +15058,7 @@ function startWorktreeUpdateWatch(options) {
15054
15058
  const initialScanAsync = options.initialScanMode === "async" || (!options.watch || options.watch === nodeWatch) && !options.readdirSync;
15055
15059
  const initialScanQueue = [];
15056
15060
  let initialScanTimer = null;
15061
+ let processingInitialScan = false;
15057
15062
  const pendingPathInspections = new Map;
15058
15063
  let pathInspectionTimer = null;
15059
15064
  let timer = null;
@@ -15141,8 +15146,14 @@ function startWorktreeUpdateWatch(options) {
15141
15146
  return;
15142
15147
  }
15143
15148
  const next = initialScanQueue.shift();
15144
- if (next)
15145
- watchDirectory(next, true);
15149
+ if (next) {
15150
+ processingInitialScan = true;
15151
+ try {
15152
+ watchDirectory(next, true);
15153
+ } finally {
15154
+ processingInitialScan = false;
15155
+ }
15156
+ }
15146
15157
  if (watchers.size >= maxWatchedDirectories) {
15147
15158
  reportWatchLimit();
15148
15159
  initialScanQueue.length = 0;
@@ -15160,7 +15171,7 @@ function startWorktreeUpdateWatch(options) {
15160
15171
  if (children.length > remaining)
15161
15172
  reportWatchLimit();
15162
15173
  initialScanQueue.push(...children.slice(0, remaining));
15163
- if (!initialScanTimer)
15174
+ if (!initialScanTimer && !processingInitialScan)
15164
15175
  initialScanTimer = setTimer(processInitialScanQueue, 5000);
15165
15176
  };
15166
15177
  const processChangedPath = (changed, fullChangedPath) => {
@@ -24819,7 +24830,7 @@ function buildQuery(params) {
24819
24830
  const s = q.toString();
24820
24831
  return s ? `?${s}` : "";
24821
24832
  }
24822
- function fileToMeta(file, range, extraQs) {
24833
+ function fileToMeta(file, range, extraQs, responseGeneration) {
24823
24834
  const sizeClass = classify(file);
24824
24835
  const q = {
24825
24836
  path: file.path,
@@ -24827,6 +24838,7 @@ function fileToMeta(file, range, extraQs) {
24827
24838
  status: file.status,
24828
24839
  from: range.from,
24829
24840
  to: range.to,
24841
+ generation: responseGeneration,
24830
24842
  ...extraQs
24831
24843
  };
24832
24844
  if (file.untracked)
@@ -24865,7 +24877,7 @@ async function computePayload(extras, range, pathFilter = "", responseGeneration
24865
24877
  };
24866
24878
  }
24867
24879
  const { args, refs } = buildRangeArgs(range);
24868
- const fullArgs = [...extras, ...args];
24880
+ const fullArgs = pathFilter ? [...extras, ...args, "--", pathFilter] : [...extras, ...args];
24869
24881
  const metaResult = await fileMetaResultAsync(fullArgs, cwd, false);
24870
24882
  const files = metaResult.files;
24871
24883
  if (!metaResult.error && includeUntracked(range, refs)) {
@@ -24883,7 +24895,7 @@ async function computePayload(extras, range, pathFilter = "", responseGeneration
24883
24895
  if (e === "--ignore-blank-lines")
24884
24896
  extraQs.ignore_blank = "1";
24885
24897
  }
24886
- const meta = filteredFiles.map((file) => fileToMeta(file, range, extraQs));
24898
+ const meta = filteredFiles.map((file) => fileToMeta(file, range, extraQs, responseGeneration));
24887
24899
  const totals = meta.reduce((acc, file) => {
24888
24900
  acc.additions += file.additions || 0;
24889
24901
  acc.deletions += file.deletions || 0;
@@ -24902,7 +24914,7 @@ async function computePayload(extras, range, pathFilter = "", responseGeneration
24902
24914
  };
24903
24915
  }
24904
24916
  async function handleDiffJson(url) {
24905
- const responseGeneration = generation;
24917
+ let responseGeneration = generation;
24906
24918
  const extras = [];
24907
24919
  if (url.searchParams.get("ignore_ws") === "1")
24908
24920
  extras.push("-w");
@@ -24928,9 +24940,15 @@ async function handleDiffJson(url) {
24928
24940
  const requestSequence = ++diffMetaRequestSequence;
24929
24941
  latestDiffMetaRequest.set(key, requestSequence);
24930
24942
  try {
24931
- const payload = await computePayload(extras, range, path, responseGeneration);
24932
- if (latestDiffMetaRequest.get(key) !== requestSequence || responseGeneration !== generation)
24943
+ let payload = await computePayload(extras, range, path, responseGeneration);
24944
+ if (latestDiffMetaRequest.get(key) !== requestSequence)
24933
24945
  return json2(payload);
24946
+ if (responseGeneration !== generation) {
24947
+ responseGeneration = generation;
24948
+ payload = await computePayload(extras, range, path, responseGeneration);
24949
+ if (latestDiffMetaRequest.get(key) !== requestSequence || responseGeneration !== generation)
24950
+ return json2(payload);
24951
+ }
24934
24952
  const sig = JSON.stringify({ ...payload, generation: undefined });
24935
24953
  if (noCache && (!cached || cached.sig !== sig)) {
24936
24954
  generation++;
@@ -25399,7 +25417,7 @@ async function handleFileBlame(url) {
25399
25417
  return json2({ ...result, base, ref, generation: responseGeneration });
25400
25418
  }
25401
25419
  async function handleFileDiff(url) {
25402
- const responseGeneration = generation;
25420
+ const serverGenerationAtRequest = generation;
25403
25421
  const path = url.searchParams.get("path") || "";
25404
25422
  if (!safePath(path))
25405
25423
  return text("invalid path", 400);
@@ -25413,6 +25431,9 @@ async function handleFileDiff(url) {
25413
25431
  from: url.searchParams.get("from") || "",
25414
25432
  to: url.searchParams.get("to") || ""
25415
25433
  };
25434
+ const requestedGeneration = Number(url.searchParams.get("generation"));
25435
+ const usesWorktree = !range.from || range.from === "worktree" || !range.to || range.to === "worktree";
25436
+ const responseGeneration = !usesWorktree && Number.isSafeInteger(requestedGeneration) && requestedGeneration > 0 ? requestedGeneration : serverGenerationAtRequest;
25416
25437
  if (isSameWorktreeRange(range)) {
25417
25438
  return json2({
25418
25439
  path,
@@ -25466,7 +25487,7 @@ async function handleFileDiff(url) {
25466
25487
  errStatus = res.status ?? 500;
25467
25488
  }
25468
25489
  }
25469
- if (!errText && responseGeneration === generation)
25490
+ if (!errText && serverGenerationAtRequest === generation)
25470
25491
  setTimedCacheEntry(fileCache, cacheKey, { diffText });
25471
25492
  }
25472
25493
  if (errStatus)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@youtyan/code-viewer",
3
- "version": "0.8.6",
3
+ "version": "0.8.7",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {
package/web/app.js CHANGED
@@ -227,6 +227,8 @@ ${lines.join(`
227
227
  function shouldAutoLoadForRoute(route, options = {}) {
228
228
  if (route.screen === "history")
229
229
  return options.historyWorktreeSelected === true;
230
+ if (route.screen === "diff")
231
+ return !route.range.from || route.range.from === "worktree" || !route.range.to || route.range.to === "worktree";
230
232
  if (route.screen === "database" || route.screen === "journal" || route.screen === "help" || route.screen === "unknown")
231
233
  return false;
232
234
  return true;
@@ -9791,14 +9793,18 @@ ${frontmatter.yaml}
9791
9793
  sticky.appendChild(tabsHost);
9792
9794
  return { sticky, header, tabsHost };
9793
9795
  }
9794
- function mountFileShellCard(deps, target, card, repoTarget = deps.repoFileTargetFromRoute()) {
9796
+ function mountFileShellCard(deps, target, card, repoTarget = deps.repoFileTargetFromRoute(), options = {}) {
9795
9797
  const root = deps.$("#diff");
9796
9798
  if (repoTarget) {
9797
9799
  const layout = document.createElement("div");
9798
9800
  layout.className = "gdp-repo-blob-layout";
9799
9801
  layout.appendChild(card);
9800
9802
  root.prepend(layout);
9801
- Promise.resolve(deps.renderRepoBlobSidebar(target.path, repoTarget)).catch(() => {}).finally(() => deps.placeSidebarToggle());
9803
+ if (options.loadSidebar !== false) {
9804
+ Promise.resolve(deps.renderRepoBlobSidebar(target.path, repoTarget)).catch((error2) => {
9805
+ console.error("[code-viewer] failed to load repository sidebar", error2);
9806
+ }).finally(() => deps.placeSidebarToggle());
9807
+ }
9802
9808
  } else {
9803
9809
  root.prepend(card);
9804
9810
  }
@@ -23773,7 +23779,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23773
23779
  if (fresh && card.isConnected)
23774
23780
  enqueueLoad(fresh, card, 0);
23775
23781
  };
23776
- return trackLoad(fetch(url).then(async (r2) => {
23782
+ const request = trackLoad(fetch(url).then(async (r2) => {
23777
23783
  if (!r2.ok)
23778
23784
  throw new Error(await r2.text());
23779
23785
  return r2.json();
@@ -23793,9 +23799,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23793
23799
  if (String(myReq) !== card.dataset.reqId)
23794
23800
  return;
23795
23801
  renderFile(file, data, card);
23796
- }).catch(() => {
23802
+ }).catch((error2) => {
23797
23803
  if (String(myReq) !== card.dataset.reqId)
23798
23804
  return;
23805
+ console.error("[code-viewer] failed to load diff", error2);
23799
23806
  card.classList.remove("loading");
23800
23807
  card.classList.add("error");
23801
23808
  card.style.minHeight = "";
@@ -23812,13 +23819,24 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
23812
23819
  enqueueLoad(file, card, 1);
23813
23820
  });
23814
23821
  });
23822
+ const completed = request.finally(() => {
23823
+ if (card._loadPromise === completed)
23824
+ delete card._loadPromise;
23825
+ });
23826
+ card._loadPromise = completed;
23827
+ return completed;
23815
23828
  }
23816
23829
  async function loadDiffFile(path) {
23817
- const card = document.querySelector(diffCardSelector(path));
23830
+ const root = getDiffRoot?.() || document;
23831
+ const card = root.querySelector(diffCardSelector(path));
23818
23832
  if (!card || card.classList.contains("gdp-standalone-source"))
23819
23833
  return false;
23820
23834
  if (card.classList.contains("loaded"))
23821
23835
  return true;
23836
+ if (card.classList.contains("loading") && card._loadPromise) {
23837
+ await card._loadPromise;
23838
+ return card.classList.contains("loaded");
23839
+ }
23822
23840
  const file = card._file || STATE.files.find((x) => x.path === path);
23823
23841
  if (!file)
23824
23842
  return false;
@@ -25609,7 +25627,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
25609
25627
  (el.closest(".gdp-repo-blob-layout") || el).remove();
25610
25628
  });
25611
25629
  }
25612
- function renderFileHistoryShell(deps, route) {
25630
+ function renderFileHistoryShell(deps, route, options = {}) {
25613
25631
  removeFileHistoryShell();
25614
25632
  const target = { path: route.path, ref: route.ref };
25615
25633
  const card = document.createElement("article");
@@ -25645,7 +25663,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
25645
25663
  body.appendChild(embed);
25646
25664
  wrapper.appendChild(body);
25647
25665
  card.appendChild(wrapper);
25648
- mountFileShellCard(deps, target, card, target.ref);
25666
+ mountFileShellCard(deps, target, card, target.ref, options);
25649
25667
  return {
25650
25668
  ...panelDom,
25651
25669
  commitInfo,
@@ -30161,14 +30179,34 @@ code-viewer query agent-help`
30161
30179
  if (!popBody || !popSearch)
30162
30180
  return;
30163
30181
  let popTarget = null;
30182
+ let refsLoaded = false;
30183
+ let refsLoading = false;
30184
+ let refsError = false;
30185
+ let refsInFlight = null;
30164
30186
  function fetchRefs() {
30165
- return fetch("/_refs").then((r2) => r2.json()).then((refs) => {
30187
+ if (refsLoaded)
30188
+ return Promise.resolve();
30189
+ if (refsInFlight)
30190
+ return refsInFlight;
30191
+ refsLoading = true;
30192
+ refsError = false;
30193
+ const request = fetch("/_refs").then((r2) => r2.json()).then((refs) => {
30166
30194
  Object.assign(REFS, refs);
30167
- }).catch(() => {
30168
- return;
30195
+ refsLoaded = true;
30196
+ if (!popover.hidden && popTab !== "commits")
30197
+ buildPopBody(popSearch.value);
30198
+ }).catch((error2) => {
30199
+ refsError = true;
30200
+ console.error("[code-viewer] failed to load refs", error2);
30201
+ }).finally(() => {
30202
+ refsLoading = false;
30203
+ refsInFlight = null;
30204
+ if (!popover.hidden && popTab !== "commits")
30205
+ buildPopBody(popSearch.value);
30169
30206
  });
30207
+ refsInFlight = request;
30208
+ return request;
30170
30209
  }
30171
- fetchRefs();
30172
30210
  let popTab = "commits";
30173
30211
  const COMMIT_PAGE_SIZE = 50;
30174
30212
  let commitSearchTimer = null;
@@ -30309,6 +30347,10 @@ code-viewer query agent-help`
30309
30347
  } else if (commitHasMore) {
30310
30348
  html.push('<div class="rp-empty">scroll for more commits...</div>');
30311
30349
  }
30350
+ } else if (refsLoading) {
30351
+ html.push('<div class="rp-empty">loading refs...</div>');
30352
+ } else if (refsError) {
30353
+ html.push('<div class="rp-empty">failed to load refs</div>');
30312
30354
  } else if (popTab === "branches") {
30313
30355
  const branches = (REFS.branches || []).filter((b2) => m(b2.name));
30314
30356
  if (!branches.length) {
@@ -30443,6 +30485,14 @@ code-viewer query agent-help`
30443
30485
  });
30444
30486
  if (popTab === "commits")
30445
30487
  scheduleCommitSearch(popSearch.value);
30488
+ else {
30489
+ if (commitSearchTimer) {
30490
+ clearTimeout(commitSearchTimer);
30491
+ commitSearchTimer = null;
30492
+ }
30493
+ commitSearchLoading = false;
30494
+ fetchRefs();
30495
+ }
30446
30496
  buildPopBody(popSearch.value);
30447
30497
  });
30448
30498
  });
@@ -37650,7 +37700,7 @@ code-viewer query agent-help`
37650
37700
  setPreferredSourceTab: (tab) => SOURCE_VIEW.setPreferredSourceTab(tab),
37651
37701
  createFileBreadcrumb: (path, ref) => DIFF_VIEW.createFileBreadcrumb(path, ref),
37652
37702
  emptyText: () => uiText().diff
37653
- }, historyRoute);
37703
+ }, historyRoute, { loadSidebar: false });
37654
37704
  activeFileHistoryDiffHost = mount.diffHost;
37655
37705
  activeFileHistoryEmptyHost = mount.emptyHost;
37656
37706
  return mount;
@@ -37705,7 +37755,17 @@ code-viewer query agent-help`
37705
37755
  parkRangeForHistory();
37706
37756
  setPageMode();
37707
37757
  const mount = renderFileHistoryShell2(route);
37708
- HISTORY_VIEW.enterHistory({ mount });
37758
+ HISTORY_VIEW.enterHistory({ mount }).then(async () => {
37759
+ const currentRoute = STATE.route;
37760
+ if (currentRoute.screen !== "file" || currentRoute.view !== "history" || currentRoute.path !== route.path || currentRoute.ref !== route.ref)
37761
+ return;
37762
+ await loadDiffFile(route.path);
37763
+ const routeAfterDiff = STATE.route;
37764
+ if (routeAfterDiff.screen !== "file" || routeAfterDiff.view !== "history" || routeAfterDiff.path !== route.path || routeAfterDiff.ref !== route.ref)
37765
+ return;
37766
+ await REPO_VIEW.renderRepoBlobSidebar(route.path, route.ref);
37767
+ placeSidebarToggle();
37768
+ });
37709
37769
  return true;
37710
37770
  }
37711
37771
  return false;