@youtyan/code-viewer 0.8.6 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,6 +157,12 @@ view's row component, so line numbers, drag-selection of `line=` ranges,
157
157
  syntax highlighting and the Viewer Settings code font size all match the
158
158
  Code tab.
159
159
 
160
+ When the repository remote is hosted on GitHub, repository and file headers
161
+ include an **Open on GitHub** action. Selecting source lines opens a compact
162
+ action bar for copying the AI reference, opening the exact line range on
163
+ GitHub, or copying that GitHub URL. Markdown and HTML diff cards also expose a
164
+ direct **Preview** shortcut beside the regular file view action.
165
+
160
166
  Very large text files use a virtualized source viewer. Only visible rows are
161
167
  rendered, and the page includes controls to copy the full file or reopen it in
162
168
  the full non-virtual view.
@@ -213,9 +219,11 @@ root:
213
219
  - `db-snapshots.sqlite` — datastore snapshots and diff blobs.
214
220
  - `datastore-connections.json` — saved non-secret datastore connection settings.
215
221
 
216
- The `.code-viewer` directory is tool-internal: it never shows up in the file
217
- tree, searches, or diffs. Add it to `.gitignore` if you do not want to share
218
- its contents through git.
222
+ The `.code-viewer` directory is tool-managed. It appears in the repository
223
+ tree, and its text files can be inspected in the Code view, but it remains
224
+ excluded from repository searches and diffs. Treat the files as diagnostic
225
+ state rather than hand-edited configuration. Add `.code-viewer/` to
226
+ `.gitignore` if you do not want to share its contents through git.
219
227
 
220
228
  ## Datastore Viewer
221
229
 
@@ -803,6 +811,8 @@ state is saved per project).
803
811
 
804
812
  A copy button on each annotation produces a paste-ready prompt block that
805
813
  references the annotation by URL for sharing back into the originating agent.
814
+ The panel-open state, selected session, and selected annotation are also kept
815
+ in the URL, so a reload or shared link restores the same walkthrough context.
806
816
 
807
817
  The `annotate` subcommand talks to the running server for the repository
808
818
  (discovered via `~/.cache/code-viewer/servers/`); start one with
@@ -196,7 +196,7 @@ function buildRoute(route) {
196
196
  }
197
197
  case "journal": {
198
198
  const params = new URLSearchParams;
199
- if (route.tab && route.tab !== "journal")
199
+ if (route.tab && route.tab !== "tasks")
200
200
  params.set("tab", route.tab);
201
201
  if (route.date)
202
202
  params.set("date", route.date);
@@ -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.8",
4
4
  "description": "Local browser-based code and git diff viewer",
5
5
  "type": "module",
6
6
  "bin": {