@youtyan/code-viewer 0.5.4 → 0.6.0

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
@@ -400,7 +400,8 @@
400
400
  { action: "start-g-sequence", key: "g", scope: "main" },
401
401
  { action: "layout-unified", key: "u" },
402
402
  { action: "layout-split", key: "s" },
403
- { action: "toggle-theme", key: "t" }
403
+ { action: "toggle-theme", key: "t" },
404
+ { action: "open-help", key: "?", shift: true }
404
405
  ];
405
406
  function resolveKeymapAction(event, context) {
406
407
  const key = event.key.toLowerCase();
@@ -9637,14 +9638,10 @@ ${frontmatter.yaml}
9637
9638
  calculatingDiff: "Computing diff…",
9638
9639
  diffError: "Failed to compute diff",
9639
9640
  noChanges: "No changes detected.",
9640
- before: "Before",
9641
9641
  loading: "Loading…",
9642
9642
  loadError: "Failed to load",
9643
9643
  saveNote: "Save",
9644
9644
  noteInputPlaceholder: "Enter a note",
9645
- inserted: "Added",
9646
- updated: "Updated",
9647
- deleted: "Deleted",
9648
9645
  tableCount: (n2) => `${n2} tables`,
9649
9646
  rowCount: (n2) => ` (${n2})`,
9650
9647
  listTitle: (n2) => `Snapshots (${n2})`,
@@ -9926,14 +9923,10 @@ ${frontmatter.yaml}
9926
9923
  calculatingDiff: "差分を計算中...",
9927
9924
  diffError: "差分の計算に失敗しました",
9928
9925
  noChanges: "変更は検出されませんでした。",
9929
- before: "更新前",
9930
9926
  loading: "読み込み中...",
9931
9927
  loadError: "読み込みに失敗しました",
9932
9928
  saveNote: "保存",
9933
9929
  noteInputPlaceholder: "メモを入力",
9934
- inserted: "追加",
9935
- updated: "更新",
9936
- deleted: "削除",
9937
9930
  tableCount: (n2) => `${n2}テーブル`,
9938
9931
  rowCount: (n2) => ` (${n2}件)`,
9939
9932
  listTitle: (n2) => `スナップショット (${n2}件)`,
@@ -14551,10 +14544,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
14551
14544
  const getJson = (path) => trackLoad(fetch(path));
14552
14545
  const changeTypeLabel = (type) => {
14553
14546
  if (type === "inserted")
14554
- return text2().snapshot.inserted;
14555
- if (type === "updated")
14556
- return text2().snapshot.updated;
14557
- return text2().snapshot.deleted;
14547
+ return "+";
14548
+ return "";
14558
14549
  };
14559
14550
  const snapshotLabel = (s2) => {
14560
14551
  const date = new Date(s2.createdAt).toLocaleString();
@@ -15360,11 +15351,19 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15360
15351
  allCols.add(k);
15361
15352
  }
15362
15353
  const columns = [...allCols];
15363
- const table2 = document.createElement("table");
15364
- table2.className = "db-snap-diff-grid";
15354
+ const colHeaderWrap = document.createElement("div");
15355
+ colHeaderWrap.className = "db-snapshot-diff-col-header-wrap";
15356
+ const colHeaderTable = document.createElement("table");
15357
+ colHeaderTable.className = "db-snap-diff-grid";
15358
+ const colHeaderColgroup = document.createElement("colgroup");
15359
+ for (let i2 = 0;i2 < columns.length + 1; i2++) {
15360
+ colHeaderColgroup.appendChild(document.createElement("col"));
15361
+ }
15362
+ colHeaderTable.appendChild(colHeaderColgroup);
15365
15363
  const thead = document.createElement("thead");
15366
15364
  const headRow = document.createElement("tr");
15367
15365
  const thType = document.createElement("th");
15366
+ thType.className = "snap-diff-type-cell";
15368
15367
  thType.textContent = "";
15369
15368
  headRow.appendChild(thType);
15370
15369
  for (const col of columns) {
@@ -15373,16 +15372,25 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15373
15372
  headRow.appendChild(th);
15374
15373
  }
15375
15374
  thead.appendChild(headRow);
15376
- table2.appendChild(thead);
15375
+ colHeaderTable.appendChild(thead);
15376
+ colHeaderWrap.appendChild(colHeaderTable);
15377
+ const rowsViewport = document.createElement("div");
15378
+ rowsViewport.className = "db-snapshot-diff-rows-viewport";
15379
+ const bodyTable = document.createElement("table");
15380
+ bodyTable.className = "db-snap-diff-grid";
15381
+ const bodyColgroup = document.createElement("colgroup");
15382
+ for (let i2 = 0;i2 < columns.length + 1; i2++) {
15383
+ bodyColgroup.appendChild(document.createElement("col"));
15384
+ }
15385
+ bodyTable.appendChild(bodyColgroup);
15377
15386
  const tbody = document.createElement("tbody");
15378
15387
  for (const row of rows) {
15379
15388
  if (row.changeType === "updated" && row.beforeValues && row.afterValues) {
15380
15389
  const trBefore = document.createElement("tr");
15381
- trBefore.className = "snap-diff-del";
15390
+ trBefore.className = "snap-diff-del snap-diff-record-start";
15382
15391
  const tdTypeBefore = document.createElement("td");
15383
15392
  tdTypeBefore.className = "snap-diff-type-cell";
15384
- tdTypeBefore.textContent = text2().snapshot.before;
15385
- tdTypeBefore.rowSpan = 2;
15393
+ tdTypeBefore.textContent = "−";
15386
15394
  trBefore.appendChild(tdTypeBefore);
15387
15395
  for (const col of columns) {
15388
15396
  const td = document.createElement("td");
@@ -15395,7 +15403,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15395
15403
  }
15396
15404
  tbody.appendChild(trBefore);
15397
15405
  const trAfter = document.createElement("tr");
15398
- trAfter.className = "snap-diff-add";
15406
+ trAfter.className = "snap-diff-add snap-diff-record-end";
15407
+ const tdTypeAfter = document.createElement("td");
15408
+ tdTypeAfter.className = "snap-diff-type-cell";
15409
+ tdTypeAfter.textContent = "+";
15410
+ trAfter.appendChild(tdTypeAfter);
15399
15411
  for (const col of columns) {
15400
15412
  const td = document.createElement("td");
15401
15413
  const bs = row.beforeValues[col] == null ? "NULL" : String(row.beforeValues[col]);
@@ -15408,7 +15420,8 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15408
15420
  tbody.appendChild(trAfter);
15409
15421
  } else {
15410
15422
  const tr = document.createElement("tr");
15411
- tr.className = row.changeType === "inserted" ? "snap-diff-add" : "snap-diff-del";
15423
+ const sideClass = row.changeType === "inserted" ? "snap-diff-add" : "snap-diff-del";
15424
+ tr.className = `${sideClass} snap-diff-record-start snap-diff-record-end`;
15412
15425
  const tdType = document.createElement("td");
15413
15426
  tdType.className = "snap-diff-type-cell";
15414
15427
  tdType.textContent = changeTypeLabel(row.changeType);
@@ -15416,6 +15429,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15416
15429
  const values = row.changeType === "inserted" ? row.afterValues : row.beforeValues;
15417
15430
  for (const col of columns) {
15418
15431
  const td = document.createElement("td");
15432
+ td.classList.add("snap-diff-changed-cell");
15419
15433
  const v = values?.[col];
15420
15434
  td.textContent = v == null ? "NULL" : String(v);
15421
15435
  tr.appendChild(td);
@@ -15423,8 +15437,32 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
15423
15437
  tbody.appendChild(tr);
15424
15438
  }
15425
15439
  }
15426
- table2.appendChild(tbody);
15427
- container.appendChild(table2);
15440
+ bodyTable.appendChild(tbody);
15441
+ rowsViewport.appendChild(bodyTable);
15442
+ container.append(colHeaderWrap, rowsViewport);
15443
+ rowsViewport.addEventListener("scroll", () => {
15444
+ colHeaderWrap.scrollLeft = rowsViewport.scrollLeft;
15445
+ });
15446
+ const syncColumnWidths = () => {
15447
+ if (disposed)
15448
+ return;
15449
+ const firstRow = tbody.querySelector("tr");
15450
+ if (!firstRow)
15451
+ return;
15452
+ const cells = firstRow.children;
15453
+ const bodyCols = bodyColgroup.children;
15454
+ const headerCols = colHeaderColgroup.children;
15455
+ for (let i2 = 0;i2 < cells.length; i2++) {
15456
+ const w = cells[i2].offsetWidth;
15457
+ if (bodyCols[i2])
15458
+ bodyCols[i2].style.width = `${w}px`;
15459
+ if (headerCols[i2])
15460
+ headerCols[i2].style.width = `${w}px`;
15461
+ }
15462
+ };
15463
+ requestAnimationFrame(syncColumnWidths);
15464
+ const ro = new ResizeObserver(syncColumnWidths);
15465
+ ro.observe(bodyTable);
15428
15466
  if (total > rows.length) {
15429
15467
  const more = document.createElement("div");
15430
15468
  more.className = "db-snapshot-diff-more";
@@ -20198,6 +20236,20 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20198
20236
  const b2 = Math.max(1, Math.floor(Math.max(start, end)));
20199
20237
  return a2 === b2 ? `@${path}#${a2}` : `@${path}#${a2}-${b2}`;
20200
20238
  }
20239
+ function fileReferenceWithCodeClipboardText(path, start, end, lines, lang) {
20240
+ const ref = fileReferenceClipboardText(path, start, end);
20241
+ if (!ref)
20242
+ return "";
20243
+ if (!lines || lines.length === 0)
20244
+ return ref;
20245
+ const fence2 = (lang || "").trim();
20246
+ return `${ref}
20247
+
20248
+ \`\`\`${fence2}
20249
+ ${lines.join(`
20250
+ `)}
20251
+ \`\`\``;
20252
+ }
20201
20253
 
20202
20254
  // web-src/core/ws-highlight.ts
20203
20255
  function isWhitespaceOnlyInlineHighlight(text2) {
@@ -20228,6 +20280,16 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20228
20280
  function shouldRenderDiffSidebar(listSame, domIntact) {
20229
20281
  return !listSame || !domIntact;
20230
20282
  }
20283
+ function findDirectDiffCardByKey(target, key) {
20284
+ for (const child of Array.from(target.children)) {
20285
+ if (!child.classList.contains("gdp-file-shell"))
20286
+ continue;
20287
+ const card = child;
20288
+ if (card.dataset.key === key)
20289
+ return card;
20290
+ }
20291
+ return null;
20292
+ }
20231
20293
  function createDiffView(deps) {
20232
20294
  const {
20233
20295
  $,
@@ -20443,11 +20505,17 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20443
20505
  let CLIENT_REQ_SEQ = 0;
20444
20506
  const LOAD_QUEUE = [];
20445
20507
  let ACTIVE_LOADS = 0;
20508
+ let LOAD_EPOCH = 0;
20446
20509
  const MAX_PARALLEL = 2;
20447
20510
  let lazyObserver = null;
20448
20511
  let scrollSpyInstalled = false;
20449
20512
  let prevListSignature = "";
20450
20513
  let prevCardSignatures = new Map;
20514
+ function resetLoadScheduling() {
20515
+ LOAD_EPOCH++;
20516
+ LOAD_QUEUE.length = 0;
20517
+ ACTIVE_LOADS = 0;
20518
+ }
20451
20519
  function fileKey(f2) {
20452
20520
  return f2.key || f2.path;
20453
20521
  }
@@ -20497,11 +20565,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20497
20565
  }
20498
20566
  }
20499
20567
  function invalidateLoadedCard(card, file, changedPaths) {
20500
- if (!card.classList.contains("loaded"))
20568
+ if (!card.classList.contains("loaded") && !card.classList.contains("loading"))
20501
20569
  return false;
20502
20570
  if (changedPaths && !changedPaths.has(file.path))
20503
20571
  return false;
20504
- card.classList.remove("loaded", "error");
20572
+ card.dataset.reqId = String(++CLIENT_REQ_SEQ);
20573
+ card.classList.remove("loaded", "loading", "error");
20505
20574
  card.classList.add("pending");
20506
20575
  card._diffData = null;
20507
20576
  const body = card.querySelector(".gdp-shell-body");
@@ -20531,6 +20600,25 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20531
20600
  }
20532
20601
  }
20533
20602
  }
20603
+ function resetReusableCard(card, file) {
20604
+ card.classList.remove("loaded", "loading", "error", "manual-load");
20605
+ card.classList.add("pending");
20606
+ card.replaceChildren();
20607
+ const tmp = createPlaceholder(file);
20608
+ while (tmp.firstChild)
20609
+ card.appendChild(tmp.firstChild);
20610
+ card.dataset.path = file.path;
20611
+ card.dataset.key = fileKey(file);
20612
+ card.dataset.sizeClass = file.size_class || "small";
20613
+ card.dataset.status = file.status || "M";
20614
+ card.dataset.reqId = String(++CLIENT_REQ_SEQ);
20615
+ delete card.dataset.manualRendered;
20616
+ delete card.dataset.manualLoad;
20617
+ delete card.dataset.manualMode;
20618
+ card.style.minHeight = `${file.estimated_height_px || 80}px`;
20619
+ card._diffData = null;
20620
+ card._file = null;
20621
+ }
20534
20622
  function renderShell(meta, changedPaths) {
20535
20623
  const newFiles = meta.files || [];
20536
20624
  const newListSig = computeListSignature(newFiles);
@@ -20559,7 +20647,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20559
20647
  empty.classList.remove("hidden");
20560
20648
  target.replaceChildren();
20561
20649
  }
20562
- LOAD_QUEUE.length = 0;
20650
+ resetLoadScheduling();
20563
20651
  return {
20564
20652
  structureChanged: sidebarNeedsRender,
20565
20653
  invalidatedCards: 0,
@@ -20571,39 +20659,33 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20571
20659
  for (const f2 of newFiles) {
20572
20660
  newCardSigs.set(fileKey(f2), computeCardSignature(f2));
20573
20661
  }
20662
+ const pathsUnknown = !changedPaths;
20663
+ const contentMayNeedReload = !listSame || pathsUnknown || (changedPaths ? newFiles.some((file) => {
20664
+ const key = fileKey(file);
20665
+ return prevCardSignatures.get(key) !== newCardSigs.get(key) || changedPaths.has(file.path);
20666
+ }) : false);
20667
+ if (contentMayNeedReload)
20668
+ resetLoadScheduling();
20574
20669
  let invalidatedCards = 0;
20575
20670
  if (listSame && domIntact) {
20576
- const pathsUnknown = !changedPaths;
20671
+ const pathsUnknown2 = !changedPaths;
20577
20672
  let sidebarNeedsStatsUpdate = false;
20578
20673
  for (const f2 of newFiles) {
20579
20674
  const key = fileKey(f2);
20580
20675
  const oldSig = prevCardSignatures.get(key);
20581
- const newSig = newCardSigs.get(key);
20676
+ const newSig = newCardSigs.get(key) ?? computeCardSignature(f2);
20582
20677
  const sigChanged = oldSig !== newSig;
20583
- const pathHint = pathsUnknown || changedPaths.has(f2.path);
20678
+ const pathHint = pathsUnknown2 || changedPaths.has(f2.path);
20584
20679
  if (!sigChanged && !pathHint) {
20585
20680
  continue;
20586
20681
  }
20587
- const card = target.querySelector(`.gdp-file-shell[data-key="${CSS.escape(key)}"]`);
20682
+ const card = findDirectDiffCardByKey(target, key);
20588
20683
  if (!card)
20589
20684
  continue;
20590
20685
  const sizeChanged = card.dataset.sizeClass !== (f2.size_class || "small");
20591
20686
  const statusChanged = card.dataset.status !== (f2.status || "M");
20592
20687
  if (sizeChanged || statusChanged) {
20593
- card.classList.remove("loaded", "error");
20594
- card.classList.add("pending");
20595
- card.replaceChildren();
20596
- const tmp = createPlaceholder(f2);
20597
- while (tmp.firstChild)
20598
- card.appendChild(tmp.firstChild);
20599
- card.dataset.sizeClass = f2.size_class || "small";
20600
- card.dataset.status = f2.status || "M";
20601
- delete card.dataset.manualRendered;
20602
- delete card.dataset.manualLoad;
20603
- delete card.dataset.manualMode;
20604
- card.style.minHeight = `${f2.estimated_height_px || 80}px`;
20605
- card._diffData = null;
20606
- card._file = null;
20688
+ resetReusableCard(card, f2);
20607
20689
  activatePendingCard(card, f2);
20608
20690
  invalidatedCards++;
20609
20691
  sidebarNeedsStatsUpdate = true;
@@ -20616,6 +20698,9 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20616
20698
  const didInvalidate = sigChanged ? invalidateLoadedCard(card, f2, null) : invalidateLoadedCard(card, f2, changedPaths);
20617
20699
  if (didInvalidate)
20618
20700
  invalidatedCards++;
20701
+ else if ((sigChanged || pathHint) && card.classList.contains("pending")) {
20702
+ activatePendingCard(card, f2);
20703
+ }
20619
20704
  if (sigChanged)
20620
20705
  sidebarNeedsStatsUpdate = true;
20621
20706
  }
@@ -20650,21 +20735,11 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20650
20735
  oldByKey.delete(key);
20651
20736
  const sizeChanged = old.dataset.sizeClass !== (f2.size_class || "small");
20652
20737
  const statusChanged = old.dataset.status !== (f2.status || "M");
20653
- if (sizeChanged || statusChanged) {
20654
- old.classList.remove("loaded", "error");
20655
- old.classList.add("pending");
20656
- old.replaceChildren();
20657
- const tmp = createPlaceholder(f2);
20658
- while (tmp.firstChild)
20659
- old.appendChild(tmp.firstChild);
20660
- old.dataset.sizeClass = f2.size_class || "small";
20661
- old.dataset.status = f2.status || "M";
20662
- delete old.dataset.manualRendered;
20663
- delete old.dataset.manualLoad;
20664
- delete old.dataset.manualMode;
20665
- old.style.minHeight = `${f2.estimated_height_px || 80}px`;
20666
- old._diffData = null;
20667
- old._file = null;
20738
+ const oldSig = prevCardSignatures.get(key);
20739
+ const newSig = newCardSigs.get(key) ?? computeCardSignature(f2);
20740
+ const sigChanged = oldSig !== newSig;
20741
+ if (sizeChanged || statusChanged || sigChanged) {
20742
+ resetReusableCard(old, f2);
20668
20743
  invalidatedCards++;
20669
20744
  } else {
20670
20745
  const stats = old.querySelector(".gdp-shell-header .stats");
@@ -20756,20 +20831,29 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20756
20831
  renderManualLoadPlaceholder(card, file);
20757
20832
  return;
20758
20833
  }
20759
- if (LOAD_QUEUE.find((item) => item.card === card))
20834
+ if (LOAD_QUEUE.find((item) => item.card === card && item.epoch === LOAD_EPOCH))
20760
20835
  return;
20761
- LOAD_QUEUE.push({ file, card, priority: priority || 0 });
20836
+ LOAD_QUEUE.push({
20837
+ file,
20838
+ card,
20839
+ priority: priority || 0,
20840
+ epoch: LOAD_EPOCH
20841
+ });
20762
20842
  LOAD_QUEUE.sort((a2, b2) => b2.priority - a2.priority);
20763
20843
  pumpQueue();
20764
20844
  }
20765
20845
  function pumpQueue() {
20766
20846
  while (ACTIVE_LOADS < MAX_PARALLEL && LOAD_QUEUE.length) {
20767
20847
  const item = LOAD_QUEUE.shift();
20848
+ if (item.epoch !== LOAD_EPOCH)
20849
+ continue;
20768
20850
  if (item.card.classList.contains("loaded") || item.card.classList.contains("loading"))
20769
20851
  continue;
20770
20852
  ACTIVE_LOADS++;
20771
20853
  loadFile(item.file, item.card).finally(() => {
20772
- ACTIVE_LOADS--;
20854
+ if (item.epoch === LOAD_EPOCH) {
20855
+ ACTIVE_LOADS = Math.max(0, ACTIVE_LOADS - 1);
20856
+ }
20773
20857
  pumpQueue();
20774
20858
  });
20775
20859
  }
@@ -21414,7 +21498,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21414
21498
  });
21415
21499
  }
21416
21500
  function clearLoadQueue() {
21417
- LOAD_QUEUE.length = 0;
21501
+ resetLoadScheduling();
21418
21502
  }
21419
21503
  return {
21420
21504
  renderMeta,
@@ -21900,6 +21984,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
21900
21984
  let hasMore = false;
21901
21985
  let loading = false;
21902
21986
  let generation = 0;
21987
+ let selectionGeneration = 0;
21903
21988
  let selectedSha = "";
21904
21989
  let query = "";
21905
21990
  let mode = "history";
@@ -22160,11 +22245,12 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22160
22245
  return page.commits.length > 0;
22161
22246
  }
22162
22247
  async function selectCommit(commit, options = {}) {
22248
+ const selectionGen = ++selectionGeneration;
22163
22249
  const gen = generation;
22164
22250
  selectedSha = commit.sha;
22165
22251
  updateActiveRow();
22166
22252
  await updateCommitInfo(commit);
22167
- if (gen !== generation)
22253
+ if (selectionGen !== selectionGeneration || gen !== generation)
22168
22254
  return;
22169
22255
  if (options.updateUrl !== false) {
22170
22256
  const range = commitDiffRange(commit);
@@ -22181,13 +22267,14 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22181
22267
  deps.setRoute({ screen: "history", ref, commit: commit.sha, range }, true);
22182
22268
  }
22183
22269
  }
22184
- if (gen !== generation)
22270
+ if (selectionGen !== selectionGeneration || gen !== generation)
22185
22271
  return;
22186
22272
  await deps.applyCommitRange(commitDiffRange(commit), pathFilter || undefined);
22187
- if (gen !== generation)
22273
+ if (selectionGen !== selectionGeneration || gen !== generation)
22188
22274
  return;
22189
22275
  }
22190
22276
  async function selectWorktree(options = {}) {
22277
+ const selectionGen = ++selectionGeneration;
22191
22278
  const gen = generation;
22192
22279
  selectedSha = HISTORY_WORKTREE_COMMIT;
22193
22280
  updateActiveRow();
@@ -22207,10 +22294,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22207
22294
  deps.setRoute({ screen: "history", ref, commit: selectedSha, range }, true);
22208
22295
  }
22209
22296
  }
22210
- if (gen !== generation)
22297
+ if (selectionGen !== selectionGeneration || gen !== generation)
22211
22298
  return;
22212
22299
  await deps.applyCommitRange(range, pathFilter || undefined);
22213
- if (gen !== generation)
22300
+ if (selectionGen !== selectionGeneration || gen !== generation)
22214
22301
  return;
22215
22302
  }
22216
22303
  let lookupFailed = false;
@@ -22307,6 +22394,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22307
22394
  hasMore = false;
22308
22395
  loading = false;
22309
22396
  inFlight = null;
22397
+ selectionGeneration++;
22310
22398
  selectedSha = "";
22311
22399
  setBanner("");
22312
22400
  await updateCommitInfo(null);
@@ -22323,6 +22411,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22323
22411
  if (scope2.commit) {
22324
22412
  await resolveDeepLink(scope2.commit);
22325
22413
  } else {
22414
+ selectionGeneration++;
22326
22415
  selectedSha = "";
22327
22416
  updateActiveRow();
22328
22417
  await updateCommitInfo(null);
@@ -22352,6 +22441,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22352
22441
  generation++;
22353
22442
  loading = false;
22354
22443
  inFlight = null;
22444
+ selectionGeneration++;
22355
22445
  selectedSha = "";
22356
22446
  setBanner("");
22357
22447
  setStatusText("");
@@ -22376,6 +22466,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22376
22466
  return;
22377
22467
  query = value;
22378
22468
  generation++;
22469
+ selectionGeneration++;
22379
22470
  commits = [];
22380
22471
  hasMore = false;
22381
22472
  loading = false;
@@ -22538,6 +22629,308 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22538
22629
  };
22539
22630
  }
22540
22631
 
22632
+ // web-src/views/help-keybindings.ts
22633
+ var HIDDEN_HELP_KEYBINDING_ACTIONS = new Set([
22634
+ "start-g-sequence"
22635
+ ]);
22636
+ var HELP_KEYBINDING_GROUPS = [
22637
+ {
22638
+ title: { en: "Global", ja: "グローバル" },
22639
+ rows: [
22640
+ {
22641
+ selectors: [{ action: "open-file-palette" }],
22642
+ description: {
22643
+ en: "Open file palette",
22644
+ ja: "ファイルパレットを開く"
22645
+ }
22646
+ },
22647
+ {
22648
+ selectors: [{ action: "open-grep-palette" }],
22649
+ description: {
22650
+ en: "Open grep palette",
22651
+ ja: "grep パレットを開く"
22652
+ }
22653
+ },
22654
+ {
22655
+ selectors: [{ action: "focus-file-filter" }],
22656
+ description: {
22657
+ en: "Focus file filter",
22658
+ ja: "ファイルフィルターへフォーカス"
22659
+ }
22660
+ },
22661
+ {
22662
+ selectors: [{ action: "open-help" }],
22663
+ description: {
22664
+ en: "Open this help (keybindings)",
22665
+ ja: "このヘルプ(キーバインド)を開く"
22666
+ }
22667
+ },
22668
+ {
22669
+ selectors: [{ action: "toggle-theme" }],
22670
+ description: { en: "Toggle theme", ja: "テーマ切り替え" }
22671
+ },
22672
+ {
22673
+ selectors: [
22674
+ { action: "annotation-previous" },
22675
+ { action: "annotation-next" }
22676
+ ],
22677
+ description: {
22678
+ en: "Previous / next annotation",
22679
+ ja: "前 / 次の注釈へ移動"
22680
+ }
22681
+ },
22682
+ {
22683
+ selectors: [{ action: "layout-unified" }, { action: "layout-split" }],
22684
+ description: {
22685
+ en: "Switch unified / split layout",
22686
+ ja: "統合 / 分割レイアウトへ切り替え"
22687
+ }
22688
+ },
22689
+ {
22690
+ selectors: [{ action: "cancel-source-load" }],
22691
+ description: {
22692
+ en: "Cancel active source load",
22693
+ ja: "進行中のソース読み込みを中断"
22694
+ }
22695
+ }
22696
+ ]
22697
+ },
22698
+ {
22699
+ title: { en: "Panels", ja: "パネル" },
22700
+ rows: [
22701
+ {
22702
+ selectors: [{ action: "focus-sidebar" }],
22703
+ description: { en: "Focus sidebar", ja: "サイドバーへフォーカス" }
22704
+ },
22705
+ {
22706
+ selectors: [{ action: "focus-main" }],
22707
+ description: {
22708
+ en: "Focus main panel",
22709
+ ja: "メインパネルへフォーカス"
22710
+ }
22711
+ }
22712
+ ]
22713
+ },
22714
+ {
22715
+ title: { en: "Sidebar", ja: "サイドバー" },
22716
+ rows: [
22717
+ {
22718
+ selectors: [{ action: "sidebar-next" }, { action: "sidebar-previous" }],
22719
+ description: {
22720
+ en: "Move selection down / up",
22721
+ ja: "選択を下 / 上へ移動"
22722
+ }
22723
+ },
22724
+ {
22725
+ selectors: [
22726
+ { action: "sidebar-page-down" },
22727
+ { action: "sidebar-page-up" }
22728
+ ],
22729
+ description: {
22730
+ en: "Move selection by half a page",
22731
+ ja: "半ページ分選択を移動"
22732
+ }
22733
+ },
22734
+ {
22735
+ selectors: [
22736
+ { action: "goto-top", pendingG: true },
22737
+ { action: "goto-bottom", shift: true, pendingG: false }
22738
+ ],
22739
+ description: { en: "Move to top / bottom", ja: "先頭 / 末尾へ移動" }
22740
+ },
22741
+ {
22742
+ selectors: [{ action: "open-sidebar-item" }],
22743
+ description: { en: "Open selected item", ja: "選択項目を開く" }
22744
+ },
22745
+ {
22746
+ selectors: [
22747
+ { action: "sidebar-collapse" },
22748
+ { action: "sidebar-expand" }
22749
+ ],
22750
+ description: {
22751
+ en: "Collapse / expand directory",
22752
+ ja: "ディレクトリを閉じる / 開く"
22753
+ }
22754
+ }
22755
+ ]
22756
+ },
22757
+ {
22758
+ title: { en: "Main Panel", ja: "メインパネル" },
22759
+ rows: [
22760
+ {
22761
+ selectors: [
22762
+ { action: "scroll-main-down", scope: "main" },
22763
+ { action: "scroll-main-up", scope: "main" }
22764
+ ],
22765
+ description: {
22766
+ en: "Move code cursor down / up",
22767
+ ja: "コードカーソルを下 / 上へ移動"
22768
+ }
22769
+ },
22770
+ {
22771
+ selectors: [
22772
+ {
22773
+ action: "scroll-main-page-down",
22774
+ scope: "main",
22775
+ key: "d",
22776
+ ctrl: true
22777
+ },
22778
+ {
22779
+ action: "scroll-main-page-up",
22780
+ scope: "main",
22781
+ key: "u",
22782
+ ctrl: true
22783
+ }
22784
+ ],
22785
+ description: {
22786
+ en: "Move code cursor by half a page",
22787
+ ja: "コードカーソルを半ページ分移動"
22788
+ }
22789
+ },
22790
+ {
22791
+ selectors: [
22792
+ { action: "scroll-main-page-down", scope: "main", key: "pagedown" },
22793
+ { action: "scroll-main-page-up", scope: "main", key: "pageup" }
22794
+ ],
22795
+ description: {
22796
+ en: "Move code cursor by a page",
22797
+ ja: "コードカーソルを1ページ分移動"
22798
+ }
22799
+ },
22800
+ {
22801
+ selectors: [
22802
+ {
22803
+ action: "scroll-main-page-down",
22804
+ scope: "main",
22805
+ key: "arrowdown",
22806
+ ctrl: true
22807
+ },
22808
+ {
22809
+ action: "scroll-main-page-up",
22810
+ scope: "main",
22811
+ key: "arrowup",
22812
+ ctrl: true
22813
+ }
22814
+ ],
22815
+ description: {
22816
+ en: "Move code cursor by a page",
22817
+ ja: "コードカーソルを1ページ分移動"
22818
+ }
22819
+ },
22820
+ {
22821
+ selectors: [
22822
+ { action: "goto-top", pendingG: true },
22823
+ { action: "goto-bottom", shift: true, pendingG: false }
22824
+ ],
22825
+ description: {
22826
+ en: "Move code cursor to top / bottom",
22827
+ ja: "コードカーソルを先頭 / 末尾へ移動"
22828
+ }
22829
+ },
22830
+ {
22831
+ selectors: [
22832
+ { action: "tab-preview", scope: "main", pendingG: true },
22833
+ { action: "tab-code", scope: "main", pendingG: true }
22834
+ ],
22835
+ description: {
22836
+ en: "Switch to Preview / Code tab",
22837
+ ja: "Preview / Code タブへ切り替え"
22838
+ }
22839
+ }
22840
+ ]
22841
+ }
22842
+ ];
22843
+ function matchesSelector(binding, selector) {
22844
+ if (binding.action !== selector.action)
22845
+ return false;
22846
+ if (selector.key !== undefined && binding.key !== selector.key)
22847
+ return false;
22848
+ if (selector.scope !== undefined && binding.scope !== selector.scope)
22849
+ return false;
22850
+ if (selector.ctrl !== undefined && !!binding.ctrl !== selector.ctrl)
22851
+ return false;
22852
+ if (selector.meta !== undefined && !!binding.meta !== selector.meta)
22853
+ return false;
22854
+ if (selector.alt !== undefined && !!binding.alt !== selector.alt)
22855
+ return false;
22856
+ if (selector.shift !== undefined && !!binding.shift !== selector.shift)
22857
+ return false;
22858
+ if (selector.pendingG !== undefined && !!binding.pendingG !== selector.pendingG)
22859
+ return false;
22860
+ return true;
22861
+ }
22862
+ function formatKeyName(key, shifted) {
22863
+ const lower = key.toLowerCase();
22864
+ if (lower === "escape")
22865
+ return "Escape";
22866
+ if (lower === "enter")
22867
+ return "Enter";
22868
+ if (lower === "pagedown")
22869
+ return "PageDown";
22870
+ if (lower === "pageup")
22871
+ return "PageUp";
22872
+ if (lower === "arrowdown")
22873
+ return "ArrowDown";
22874
+ if (lower === "arrowup")
22875
+ return "ArrowUp";
22876
+ if (key === "?")
22877
+ return "?";
22878
+ if (shifted && key.length === 1)
22879
+ return key.toUpperCase();
22880
+ return key;
22881
+ }
22882
+ function formatKeyBinding(binding) {
22883
+ const key = formatKeyName(binding.key, !!binding.shift || !!binding.ctrl || !!binding.meta || !!binding.alt);
22884
+ if (binding.pendingG && !binding.ctrl && !binding.meta && !binding.alt && !binding.shift && binding.key.length === 1)
22885
+ return `g${key}`;
22886
+ const parts = [];
22887
+ if (binding.pendingG)
22888
+ parts.push("g");
22889
+ if (binding.ctrl)
22890
+ parts.push("Ctrl");
22891
+ if (binding.meta)
22892
+ parts.push("Meta");
22893
+ if (binding.alt)
22894
+ parts.push("Alt");
22895
+ if (binding.shift && binding.key !== "?")
22896
+ parts.push("Shift");
22897
+ parts.push(key);
22898
+ return parts.join("+");
22899
+ }
22900
+ function addUnique(values, next) {
22901
+ if (!values.includes(next))
22902
+ values.push(next);
22903
+ }
22904
+ function collectRowCoverage(row, bindings) {
22905
+ const coverage = [];
22906
+ const seen = new Set;
22907
+ for (const selector of row.selectors) {
22908
+ for (const binding of bindings) {
22909
+ if (!matchesSelector(binding, selector))
22910
+ continue;
22911
+ const label = formatKeyBinding(binding);
22912
+ const key = `${binding.action}\x00${binding.scope || ""}\x00${label}`;
22913
+ if (seen.has(key))
22914
+ continue;
22915
+ seen.add(key);
22916
+ coverage.push({ action: selector.action, binding, label });
22917
+ }
22918
+ }
22919
+ return coverage;
22920
+ }
22921
+ function buildRow(row, language, bindings) {
22922
+ const labels = [];
22923
+ for (const item of collectRowCoverage(row, bindings))
22924
+ addUnique(labels, item.label);
22925
+ return [labels.join(" / "), row.description[language]];
22926
+ }
22927
+ function buildHelpKeybindingGroups(language, bindings = DEFAULT_KEY_BINDINGS) {
22928
+ return HELP_KEYBINDING_GROUPS.map((group) => ({
22929
+ title: group.title[language],
22930
+ rows: group.rows.map((row) => buildRow(row, language, bindings))
22931
+ }));
22932
+ }
22933
+
22541
22934
  // web-src/views/help-page.ts
22542
22935
  var HELP_LANGUAGES = ["en", "ja"];
22543
22936
  var HELP_SECTIONS = [
@@ -22546,6 +22939,7 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22546
22939
  "annotations",
22547
22940
  "database",
22548
22941
  "skills",
22942
+ "mcp",
22549
22943
  "keybindings"
22550
22944
  ];
22551
22945
  var HELP_CONTENT = {
@@ -22633,6 +23027,21 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
22633
23027
  {
22634
23028
  kind: "paragraph",
22635
23029
  text: "Each row reports OK / WARN / ERROR with a remediation hint when relevant. The check groups are Runtime (Node / Bun / NODE_MODULE_VERSION), Package (version + execution origin including npx cache), SQLite driver, Snapshot store, Git, Discovery summary, Docker / Compose (CLI / v2 plugin / daemon / compose config dry-parse / compose ps health per discovered service), and Server. The most common hint is the npx cache fix for better-sqlite3 NODE_MODULE_VERSION mismatch (rm -rf ~/.npm/_npx then re-run with npx -y @youtyan/code-viewer@latest)."
23030
+ },
23031
+ {
23032
+ kind: "paragraph",
23033
+ text: 'From the terminal — and for AI agents or CI — the same report is available without a browser. `code-viewer doctor` prints a status summary; add `--json` for the full DoctorReport (matches the /_doctor endpoint). Exit code is 1 when worstStatus is "error", so it doubles as a CI gate.'
23034
+ },
23035
+ {
23036
+ kind: "command",
23037
+ title: "Doctor summary in the terminal",
23038
+ command: "code-viewer doctor"
23039
+ },
23040
+ {
23041
+ kind: "command",
23042
+ title: "Doctor JSON for agents and CI",
23043
+ command: `code-viewer doctor --json
23044
+ code-viewer doctor --cwd /path/to/repo --port 64160 --json`
22636
23045
  }
22637
23046
  ]
22638
23047
  }
@@ -22948,7 +23357,7 @@ code-viewer query clear --db app.db`
22948
23357
  {
22949
23358
  kind: "command",
22950
23359
  title: "Search across all tables",
22951
- command: 'code-viewer query search --db app.db --term "john@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
23360
+ command: 'code-viewer query search --db app.db --term "sample@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
22952
23361
  },
22953
23362
  {
22954
23363
  kind: "command",
@@ -22965,16 +23374,12 @@ code-viewer query snapshot delete --id snap-abc123`
22965
23374
  {
22966
23375
  kind: "command",
22967
23376
  title: "Diff two snapshots",
22968
- command: `code-viewer query diff create --before snap-abc123 --after snap-def456 \\
22969
- --note "User registration test"
22970
- code-viewer query diff list --db app.db --json
22971
- code-viewer query diff delete --id diff-xyz789`
23377
+ command: "code-viewer query diff tables --before snap-abc123 --after snap-def456 --json"
22972
23378
  },
22973
23379
  {
22974
23380
  kind: "command",
22975
23381
  title: "Inspect a diff",
22976
- command: `code-viewer query diff tables --id diff-xyz789
22977
- code-viewer query diff rows --id diff-xyz789 --table users --type inserted --limit 50`
23382
+ command: "code-viewer query diff rows --before snap-abc123 --after snap-def456 --table users --limit 50"
22978
23383
  },
22979
23384
  {
22980
23385
  kind: "command",
@@ -23060,63 +23465,120 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23060
23465
  }
23061
23466
  ]
23062
23467
  },
23063
- keybindings: {
23064
- nav: "Keybindings",
23065
- title: "Keyboard Shortcuts",
23066
- intro: "Use these shortcuts to move between panels and navigate files without leaving the keyboard.",
23468
+ mcp: {
23469
+ nav: "MCP Server",
23470
+ title: "MCP Server",
23471
+ intro: "While code-viewer is running, the same server also exposes a local, read-only MCP endpoint at /_mcp so AI agents can call status, file, search, and datastore tools directly over JSON-RPC instead of spawning code-viewer CLI subprocesses.",
23067
23472
  groups: [
23068
23473
  {
23069
- title: "Global",
23474
+ title: "Connect to the endpoint",
23070
23475
  blocks: [
23071
23476
  {
23072
- kind: "table",
23073
- rows: [
23074
- ["Ctrl+K", "Open file palette"],
23075
- ["Ctrl+G", "Open grep palette"],
23076
- ["/", "Focus file filter"],
23077
- ["t", "Toggle theme"],
23078
- ["[ / ]", "Previous / next annotation"]
23079
- ]
23080
- }
23081
- ]
23082
- },
23083
- {
23084
- title: "Panels",
23085
- blocks: [
23477
+ kind: "paragraph",
23478
+ text: "The endpoint speaks JSON-RPC 2.0 over the Streamable HTTP transport (initialize, ping, tools/list, tools/call). It accepts POST requests with an application/json body only, and is guarded by the same localhost / same-origin check as every other route."
23479
+ },
23086
23480
  {
23087
- kind: "table",
23088
- rows: [
23089
- ["Ctrl+H", "Focus sidebar"],
23090
- ["Ctrl+L", "Focus main panel"]
23091
- ]
23481
+ kind: "command",
23482
+ title: "Endpoint URL (port from the printed startup URL)",
23483
+ command: "http://127.0.0.1:<port>/_mcp"
23092
23484
  }
23093
23485
  ]
23094
23486
  },
23095
23487
  {
23096
- title: "Sidebar",
23488
+ title: "Available tools",
23097
23489
  blocks: [
23098
23490
  {
23099
23491
  kind: "table",
23100
23492
  rows: [
23101
- ["j / k", "Move selection down / up"],
23102
- ["Ctrl+D / Ctrl+U", "Move selection by half a page"],
23103
- ["gg / Shift+G", "Move to top / bottom"],
23104
- ["Enter", "Open selected item"],
23105
- ["h / l", "Collapse / expand directory"]
23493
+ [
23494
+ "code_viewer_agent_help",
23495
+ "Index of every AI-facing CLI subcommand."
23496
+ ],
23497
+ [
23498
+ "code_viewer_status",
23499
+ "Branch, remote, changed files, and recent commits."
23500
+ ],
23501
+ [
23502
+ "code_viewer_file_show",
23503
+ "Read a file (optionally a line range) at any ref."
23504
+ ],
23505
+ [
23506
+ "code_viewer_file_blame",
23507
+ "Per-line blame (sha / author / time / summary)."
23508
+ ],
23509
+ [
23510
+ "code_viewer_file_history",
23511
+ "Commit history for one path (follows renames)."
23512
+ ],
23513
+ [
23514
+ "code_viewer_file_diff",
23515
+ "Unified diff for one path (preview-capped by default)."
23516
+ ],
23517
+ [
23518
+ "code_viewer_search_files",
23519
+ "Rank repository paths by fuzzy or glob match."
23520
+ ],
23521
+ [
23522
+ "code_viewer_search_code",
23523
+ "Grep the repository (rg / git grep / fallback)."
23524
+ ],
23525
+ [
23526
+ "code_viewer_datastore_sources",
23527
+ "Discover read-only datastore source ids."
23528
+ ],
23529
+ [
23530
+ "code_viewer_datastore_schemas",
23531
+ "List schemas for one SQL datastore."
23532
+ ],
23533
+ [
23534
+ "code_viewer_datastore_schema",
23535
+ "Inspect tables, indexes, FKs, and columns."
23536
+ ],
23537
+ [
23538
+ "code_viewer_datastore_columns",
23539
+ "Inspect columns for one SQL table."
23540
+ ],
23541
+ [
23542
+ "code_viewer_datastore_ddl",
23543
+ "Inspect the CREATE statement and triggers."
23544
+ ],
23545
+ [
23546
+ "code_viewer_datastore_query",
23547
+ "Run a read-only SELECT / PRAGMA / EXPLAIN / WITH."
23548
+ ],
23549
+ [
23550
+ "code_viewer_datastore_history",
23551
+ "Inspect saved query history."
23552
+ ]
23106
23553
  ]
23107
23554
  }
23108
23555
  ]
23109
- },
23556
+ }
23557
+ ]
23558
+ },
23559
+ keybindings: {
23560
+ nav: "Keybindings",
23561
+ title: "Keyboard Shortcuts",
23562
+ intro: "Use these shortcuts to move between panels and navigate files without leaving the keyboard.",
23563
+ groups: [
23110
23564
  {
23111
- title: "Main Panel",
23565
+ title: "Line selection",
23112
23566
  blocks: [
23113
23567
  {
23114
23568
  kind: "table",
23115
23569
  rows: [
23116
- ["j / k", "Move code cursor down / up"],
23117
- ["Ctrl+D / Ctrl+U", "Move code cursor by half a page"],
23118
- ["gg / Shift+G", "Move code cursor to top / bottom"],
23119
- ["gp / gc", "Switch to Preview / Code tab"]
23570
+ [
23571
+ "Drag on line numbers",
23572
+ "Highlight a range; the floating copy pill prepares @path#L1-L9"
23573
+ ],
23574
+ [
23575
+ "Click the pill",
23576
+ "Copy @path#L1-L9 to the clipboard for pasting into Claude Code / Codex"
23577
+ ],
23578
+ [
23579
+ "Shift+Click the pill",
23580
+ "Copy @path#L1-L9 plus a fenced code block of the selected lines — paste straight to an AI without re-fetching the file"
23581
+ ]
23120
23582
  ]
23121
23583
  }
23122
23584
  ]
@@ -23209,6 +23671,21 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23209
23671
  {
23210
23672
  kind: "paragraph",
23211
23673
  text: "各項目は OK / WARN / ERROR で表示され、必要に応じて対処手順のヒントが付きます。診断グループは Runtime (Node / Bun / NODE_MODULE_VERSION)、Package (バージョン + 実行元: npx cache / global / local / bunx)、SQLite driver、Snapshot store、Git、Discovery summary、Docker / Compose (CLI / v2 plugin / daemon / compose config dry parse / compose ps による各サービスのヘルス)、Server (待ち受けポート) の 8 つ。よくあるヒントは npx キャッシュ起因の better-sqlite3 NODE_MODULE_VERSION 不一致で、rm -rf ~/.npm/_npx の後に npx -y @youtyan/code-viewer@latest を再実行する手順を表示します。"
23674
+ },
23675
+ {
23676
+ kind: "paragraph",
23677
+ text: 'ターミナルからは `code-viewer doctor` で同じレポートを取得できます。AI エージェントや CI からは `--json` で /_doctor と同じ DoctorReport を受け取れます。worstStatus が "error" なら exit code 1 を返すので CI ガードに直接使えます。'
23678
+ },
23679
+ {
23680
+ kind: "command",
23681
+ title: "ターミナル用 doctor サマリー",
23682
+ command: "code-viewer doctor"
23683
+ },
23684
+ {
23685
+ kind: "command",
23686
+ title: "AI / CI 用 doctor JSON",
23687
+ command: `code-viewer doctor --json
23688
+ code-viewer doctor --cwd /path/to/repo --port 64160 --json`
23212
23689
  }
23213
23690
  ]
23214
23691
  }
@@ -23524,7 +24001,7 @@ code-viewer query clear --db app.db`
23524
24001
  {
23525
24002
  kind: "command",
23526
24003
  title: "テーブル横断の全文検索",
23527
- command: 'code-viewer query search --db app.db --term "john@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
24004
+ command: 'code-viewer query search --db app.db --term "sample@example.com" \\\n --tables users,orders --include-non-text --max-hits 20'
23528
24005
  },
23529
24006
  {
23530
24007
  kind: "command",
@@ -23541,16 +24018,12 @@ code-viewer query snapshot delete --id snap-abc123`
23541
24018
  {
23542
24019
  kind: "command",
23543
24020
  title: "2 つのスナップショットを diff",
23544
- command: `code-viewer query diff create --before snap-abc123 --after snap-def456 \\
23545
- --note "ユーザー登録テストの差分"
23546
- code-viewer query diff list --db app.db --json
23547
- code-viewer query diff delete --id diff-xyz789`
24021
+ command: "code-viewer query diff tables --before snap-abc123 --after snap-def456 --json"
23548
24022
  },
23549
24023
  {
23550
24024
  kind: "command",
23551
24025
  title: "diff の中身を確認",
23552
- command: `code-viewer query diff tables --id diff-xyz789
23553
- code-viewer query diff rows --id diff-xyz789 --table users --type inserted --limit 50`
24026
+ command: "code-viewer query diff rows --before snap-abc123 --after snap-def456 --table users --limit 50"
23554
24027
  },
23555
24028
  {
23556
24029
  kind: "command",
@@ -23636,63 +24109,117 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23636
24109
  }
23637
24110
  ]
23638
24111
  },
23639
- keybindings: {
23640
- nav: "キーバインド",
23641
- title: "キーバインド",
23642
- intro: "キーボードだけでパネル移動、ファイル選択、スクロールを行うためのショートカットです。",
24112
+ mcp: {
24113
+ nav: "MCPサーバー",
24114
+ title: "MCPサーバー",
24115
+ intro: "code-viewer を起動している間、同じサーバーがローカルの read-only な MCP エンドポイント (/_mcp) も公開します。AI エージェントは code-viewer の CLI をサブプロセスとして起動する代わりに、status / file / search / datastore の各ツールを JSON-RPC 経由で直接呼び出せます。",
23643
24116
  groups: [
23644
24117
  {
23645
- title: "グローバル",
24118
+ title: "エンドポイントへの接続",
23646
24119
  blocks: [
23647
24120
  {
23648
- kind: "table",
23649
- rows: [
23650
- ["Ctrl+K", "ファイルパレットを開く"],
23651
- ["Ctrl+G", "grep パレットを開く"],
23652
- ["/", "ファイルフィルターへフォーカス"],
23653
- ["t", "テーマ切り替え"],
23654
- ["[ / ]", "前 / 次の注釈へ移動"]
23655
- ]
23656
- }
23657
- ]
23658
- },
23659
- {
23660
- title: "パネル",
23661
- blocks: [
24121
+ kind: "paragraph",
24122
+ text: "エンドポイントは JSON-RPC 2.0 の Streamable HTTP トランスポート (initialize / ping / tools/list / tools/call) で応答します。受け付けるのは application/json ボディの POST リクエストのみで、他のルートと同じ localhost / 同一オリジンチェックで保護されています。"
24123
+ },
23662
24124
  {
23663
- kind: "table",
23664
- rows: [
23665
- ["Ctrl+H", "サイドバーへフォーカス"],
23666
- ["Ctrl+L", "メインパネルへフォーカス"]
23667
- ]
24125
+ kind: "command",
24126
+ title: "エンドポイント URL (port は起動時に表示される URL と同じ)",
24127
+ command: "http://127.0.0.1:<port>/_mcp"
23668
24128
  }
23669
24129
  ]
23670
24130
  },
23671
24131
  {
23672
- title: "サイドバー",
24132
+ title: "利用できるツール",
23673
24133
  blocks: [
23674
24134
  {
23675
24135
  kind: "table",
23676
24136
  rows: [
23677
- ["j / k", "選択を下 / 上へ移動"],
23678
- ["Ctrl+D / Ctrl+U", "半ページ分選択を移動"],
23679
- ["gg / Shift+G", "先頭 / 末尾へ移動"],
23680
- ["Enter", "選択項目を開く"],
23681
- ["h / l", "ディレクトリを閉じる / 開く"]
24137
+ [
24138
+ "code_viewer_agent_help",
24139
+ "AI 向け CLI サブコマンドの一覧索引。"
24140
+ ],
24141
+ [
24142
+ "code_viewer_status",
24143
+ "ブランチ、remote、変更ファイル、直近のコミット。"
24144
+ ],
24145
+ [
24146
+ "code_viewer_file_show",
24147
+ "任意の ref でファイル(または行範囲)を読む。"
24148
+ ],
24149
+ [
24150
+ "code_viewer_file_blame",
24151
+ "行単位の blame (sha / author / time / summary)。"
24152
+ ],
24153
+ [
24154
+ "code_viewer_file_history",
24155
+ "1ファイルのコミット履歴(リネーム追跡あり)。"
24156
+ ],
24157
+ [
24158
+ "code_viewer_file_diff",
24159
+ "1ファイルの unified diff(既定はプレビュー上限あり)。"
24160
+ ],
24161
+ [
24162
+ "code_viewer_search_files",
24163
+ "ファジー / glob マッチでリポジトリのパスを順位付け。"
24164
+ ],
24165
+ [
24166
+ "code_viewer_search_code",
24167
+ "リポジトリを grep する (rg / git grep / フォールバック)。"
24168
+ ],
24169
+ [
24170
+ "code_viewer_datastore_sources",
24171
+ "read-only なデータストアの source id を発見。"
24172
+ ],
24173
+ [
24174
+ "code_viewer_datastore_schemas",
24175
+ "SQL データストア1件のスキーマ一覧。"
24176
+ ],
24177
+ [
24178
+ "code_viewer_datastore_schema",
24179
+ "テーブル、インデックス、外部キー、カラムを調査。"
24180
+ ],
24181
+ [
24182
+ "code_viewer_datastore_columns",
24183
+ "SQL テーブル1件のカラムを調査。"
24184
+ ],
24185
+ ["code_viewer_datastore_ddl", "CREATE 文とトリガーを調査。"],
24186
+ [
24187
+ "code_viewer_datastore_query",
24188
+ "read-only な SELECT / PRAGMA / EXPLAIN / WITH を実行。"
24189
+ ],
24190
+ [
24191
+ "code_viewer_datastore_history",
24192
+ "保存済みのクエリ履歴を調査。"
24193
+ ]
23682
24194
  ]
23683
24195
  }
23684
24196
  ]
23685
- },
24197
+ }
24198
+ ]
24199
+ },
24200
+ keybindings: {
24201
+ nav: "キーバインド",
24202
+ title: "キーバインド",
24203
+ intro: "キーボードだけでパネル移動、ファイル選択、スクロールを行うためのショートカットです。",
24204
+ groups: [
23686
24205
  {
23687
- title: "メインパネル",
24206
+ title: "行選択",
23688
24207
  blocks: [
23689
24208
  {
23690
24209
  kind: "table",
23691
24210
  rows: [
23692
- ["j / k", "コードカーソルを下 / 上へ移動"],
23693
- ["Ctrl+D / Ctrl+U", "コードカーソルを半ページ分移動"],
23694
- ["gg / Shift+G", "コードカーソルを先頭 / 末尾へ移動"],
23695
- ["gp / gc", "Preview / Code タブへ切り替え"]
24211
+ [
24212
+ "行番号でドラッグ",
24213
+ "範囲をハイライトし、フロートする Copy ピルが @path#L1-L9 を用意"
24214
+ ],
24215
+ [
24216
+ "ピルをクリック",
24217
+ "@path#L1-L9 をコピー(Claude Code / Codex への貼り付け用)"
24218
+ ],
24219
+ [
24220
+ "Shift+ピルをクリック",
24221
+ "@path#L1-L9 と選択行の実コード(フェンス付き)をまとめてコピー — AI へ直接貼れて、ファイル再取得不要"
24222
+ ]
23696
24223
  ]
23697
24224
  }
23698
24225
  ]
@@ -23708,6 +24235,19 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23708
24235
  function helpSectionFromRoute(route) {
23709
24236
  return route.screen === "help" && HELP_SECTIONS.includes(route.section) ? route.section : "overview";
23710
24237
  }
24238
+ function openHelpKeybindings(deps) {
24239
+ const route = deps.getRoute();
24240
+ deps.cancelActiveSourceLoad("navigation");
24241
+ deps.setRoute({
24242
+ screen: "help",
24243
+ lang: route.screen === "help" ? helpLanguageFromRoute(route) : deps.getLanguage(),
24244
+ section: "keybindings",
24245
+ range: deps.currentRange()
24246
+ });
24247
+ deps.setPageMode();
24248
+ deps.renderHelpPage();
24249
+ deps.setStatus("live");
24250
+ }
23711
24251
  function renderHelpCommand(block2) {
23712
24252
  const wrap = document.createElement("div");
23713
24253
  wrap.className = "gdp-help-command";
@@ -23776,6 +24316,13 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23776
24316
  const section = helpSectionFromRoute(deps.getRoute());
23777
24317
  const content = HELP_CONTENT[lang];
23778
24318
  const sectionContent = content.sections[section];
24319
+ const sectionGroups = section === "keybindings" ? [
24320
+ ...buildHelpKeybindingGroups(lang).map((group) => ({
24321
+ title: group.title,
24322
+ blocks: [{ kind: "table", rows: group.rows }]
24323
+ })),
24324
+ ...sectionContent.groups
24325
+ ] : sectionContent.groups;
23779
24326
  const shell = document.createElement("section");
23780
24327
  shell.className = "gdp-help-shell";
23781
24328
  const header = document.createElement("header");
@@ -23825,7 +24372,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
23825
24372
  const intro = document.createElement("p");
23826
24373
  intro.textContent = sectionContent.intro;
23827
24374
  article.append(h2, intro);
23828
- sectionContent.groups.forEach((group) => {
24375
+ sectionGroups.forEach((group) => {
23829
24376
  const groupSection = document.createElement("section");
23830
24377
  groupSection.className = "gdp-help-group";
23831
24378
  const groupTitle = document.createElement("h3");
@@ -24233,39 +24780,109 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
24233
24780
  return { setupHunkExpand };
24234
24781
  }
24235
24782
 
24783
+ // web-src/core/html-escape.ts
24784
+ function escapeHtml2(value) {
24785
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
24786
+ }
24787
+
24236
24788
  // web-src/views/line-ref-pill.ts
24237
24789
  var COPY_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" fill="currentColor">' + '<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>' + '<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>' + "</svg>";
24238
24790
  var CHECK_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" fill="currentColor">' + '<path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"/>' + "</svg>";
24239
- function escapeHtml2(value) {
24240
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
24791
+ function langFromPath(path) {
24792
+ const m = path.match(/\.([^./]+)$/);
24793
+ if (!m)
24794
+ return "";
24795
+ return EXT_TO_LANG[m[1].toLowerCase()] || "";
24796
+ }
24797
+ function readRenderedLines(path, start, end) {
24798
+ const card = document.querySelector(`.gdp-file-shell[data-path="${CSS.escape(path)}"]`);
24799
+ if (!card)
24800
+ return [];
24801
+ const out = [];
24802
+ for (let line = start;line <= end; line++) {
24803
+ const sourceRow = card.querySelector(`.gdp-source-table tr[data-line="${String(line)}"]`);
24804
+ if (sourceRow) {
24805
+ const code2 = sourceRow.querySelector(".gdp-source-line-code");
24806
+ out.push(code2?.textContent ?? "");
24807
+ }
24808
+ }
24809
+ if (out.length > 0 && out.some((value) => value.length > 0))
24810
+ return out;
24811
+ const diffRows = card.querySelectorAll("table.d2h-diff-table tr");
24812
+ if (diffRows.length === 0)
24813
+ return [];
24814
+ const collected = new Map;
24815
+ diffRows.forEach((row) => {
24816
+ const sideCell = row.querySelector("td.d2h-code-linenumber, td.d2h-code-side-linenumber");
24817
+ if (!sideCell)
24818
+ return;
24819
+ const side = sideCell.closest(".d2h-file-side-diff");
24820
+ const wrapper = sideCell.closest(".d2h-file-wrapper");
24821
+ if (side && wrapper) {
24822
+ const sides = wrapper.querySelectorAll(".d2h-file-side-diff");
24823
+ if (sides.length >= 2 && side !== sides[1])
24824
+ return;
24825
+ }
24826
+ const numText = sideCell.querySelector(".line-num2")?.textContent ?? sideCell.textContent ?? "";
24827
+ const line = Number(numText.trim());
24828
+ if (!Number.isInteger(line) || line < start || line > end)
24829
+ return;
24830
+ const codeCell = row.querySelector(".d2h-code-line-ctn");
24831
+ if (codeCell)
24832
+ collected.set(line, codeCell.textContent ?? "");
24833
+ });
24834
+ if (collected.size === 0)
24835
+ return [];
24836
+ const ordered = [];
24837
+ for (let line = start;line <= end; line++) {
24838
+ ordered.push(collected.get(line) ?? "");
24839
+ }
24840
+ return ordered;
24241
24841
  }
24242
24842
  function createLineRefPill() {
24243
24843
  const pill = document.createElement("button");
24244
24844
  pill.id = "line-ref-pill";
24245
24845
  pill.type = "button";
24246
- pill.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)";
24846
+ pill.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
24247
24847
  pill.hidden = true;
24248
24848
  document.body.appendChild(pill);
24249
24849
  let refText = "";
24850
+ let currentPath = "";
24851
+ let currentStart = 0;
24852
+ let currentEnd = 0;
24250
24853
  let feedbackTimer = null;
24251
24854
  function render(state) {
24252
- pill.classList.toggle("copied", state === "copied");
24855
+ pill.classList.toggle("copied", state === "copied" || state === "copied-code");
24253
24856
  if (state === "copied") {
24254
24857
  pill.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
24255
24858
  return;
24256
24859
  }
24860
+ if (state === "copied-code") {
24861
+ pill.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code</span>`;
24862
+ return;
24863
+ }
24257
24864
  if (state === "failed") {
24258
24865
  pill.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
24259
24866
  return;
24260
24867
  }
24261
24868
  pill.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>`;
24262
24869
  }
24263
- pill.addEventListener("click", async () => {
24870
+ pill.addEventListener("click", async (event) => {
24264
24871
  if (!refText)
24265
24872
  return;
24873
+ const withCode = event.shiftKey;
24874
+ let payload = refText;
24875
+ let copiedCode = false;
24876
+ if (withCode && currentPath) {
24877
+ const lines = readRenderedLines(currentPath, currentStart, currentEnd);
24878
+ if (lines.length > 0) {
24879
+ payload = fileReferenceWithCodeClipboardText(currentPath, currentStart, currentEnd, lines, langFromPath(currentPath));
24880
+ copiedCode = true;
24881
+ }
24882
+ }
24266
24883
  try {
24267
- await navigator.clipboard.writeText(refText);
24268
- render("copied");
24884
+ await navigator.clipboard.writeText(payload);
24885
+ render(copiedCode ? "copied-code" : "copied");
24269
24886
  } catch {
24270
24887
  render("failed");
24271
24888
  }
@@ -24284,6 +24901,9 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
24284
24901
  return;
24285
24902
  const changed = next !== refText;
24286
24903
  refText = next;
24904
+ currentPath = path;
24905
+ currentStart = Math.max(1, Math.floor(Math.min(start, end)));
24906
+ currentEnd = Math.max(1, Math.floor(Math.max(start, end)));
24287
24907
  if (feedbackTimer) {
24288
24908
  clearTimeout(feedbackTimer);
24289
24909
  feedbackTimer = null;
@@ -24298,6 +24918,9 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
24298
24918
  },
24299
24919
  hide() {
24300
24920
  refText = "";
24921
+ currentPath = "";
24922
+ currentStart = 0;
24923
+ currentEnd = 0;
24301
24924
  pill.hidden = true;
24302
24925
  }
24303
24926
  };
@@ -24563,6 +25186,11 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
24563
25186
  wireRefSelectorInput(refToInput, () => deps.setRange(refFromInput.value, refToInput.value));
24564
25187
  wireRefSelectorInput(deps.$("#repo-target"), (ref) => {
24565
25188
  const route = deps.getRoute();
25189
+ if (route.screen === "repo") {
25190
+ deps.setRoute({ ...route, ref, range: deps.currentRange() });
25191
+ deps.loadRepo();
25192
+ return;
25193
+ }
24566
25194
  if (route.screen !== "file")
24567
25195
  return;
24568
25196
  const nextRoute = {
@@ -25526,6 +26154,13 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
25526
26154
  function renderSidebar(files, onFileClick) {
25527
26155
  const ul = $("#filelist");
25528
26156
  const repoSidebar = !!onFileClick;
26157
+ if (!repoSidebar) {
26158
+ const repoTarget = document.querySelector("#repo-target-wrap");
26159
+ if (repoTarget) {
26160
+ repoTarget.hidden = true;
26161
+ repoTarget.style.display = "none";
26162
+ }
26163
+ }
25529
26164
  const treeMode = STATE.sbView === "tree" || repoSidebar;
25530
26165
  ul.innerHTML = "";
25531
26166
  ul.classList.toggle("tree", treeMode);
@@ -26174,8 +26809,10 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
26174
26809
  const wrap = document.querySelector("#repo-target-wrap");
26175
26810
  if (!input || !wrap)
26176
26811
  return;
26177
- input.value = ref || "worktree";
26178
- wrap.hidden = activeRepoTreeRef() == null;
26812
+ const activeRef = activeRepoTreeRef();
26813
+ input.value = activeRef || ref || "worktree";
26814
+ wrap.hidden = activeRef == null;
26815
+ wrap.style.display = activeRef == null ? "none" : "";
26179
26816
  syncSidebarHeaderHeight();
26180
26817
  }
26181
26818
  function activeRepoTreeRef() {
@@ -29961,8 +30598,8 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
29961
30598
  view: "view",
29962
30599
  tree: "tree",
29963
30600
  flat: "flat",
29964
- filter: "Filter files...",
29965
- filterTitle: "Filter files. Use /pattern/ for regex. Cmd/Ctrl+K focuses this field.",
30601
+ filter: "Filter files… / ⌘K",
30602
+ filterTitle: "Filter files. Use /pattern/ for regex. Press / to focus this field, Cmd/Ctrl+K for the full-file palette, Ctrl+G for grep, ? for help.",
29966
30603
  hide: "hide sidebar"
29967
30604
  },
29968
30605
  history: {
@@ -30067,8 +30704,8 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
30067
30704
  view: "表示",
30068
30705
  tree: "ツリー",
30069
30706
  flat: "一覧",
30070
- filter: "ファイルを絞り込み...",
30071
- filterTitle: "ファイルを絞り込みます。正規表現は /pattern/。Cmd/Ctrl+K でフォーカス。",
30707
+ filter: "ファイル絞り込み… / ⌘K",
30708
+ filterTitle: "ファイルを絞り込みます。/pattern/ は正規表現。/ でこの欄にフォーカス、Cmd/Ctrl+K で全ファイルパレット、Ctrl+G で grep、? でヘルプ。",
30072
30709
  hide: "サイドバーを隠す"
30073
30710
  },
30074
30711
  history: {
@@ -30804,13 +31441,21 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
30804
31441
  function setPageMode() {
30805
31442
  const historyPanelRoute = STATE.route.screen === "history";
30806
31443
  const fileHistoryRoute = isFileHistoryRoute(STATE.route);
31444
+ const fileRepoBlobRoute = STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history");
31445
+ const repoSidebarRoute = STATE.route.screen === "repo" || fileRepoBlobRoute;
30807
31446
  document.body.classList.toggle("gdp-file-detail-page", STATE.route.screen === "file");
30808
- document.body.classList.toggle("gdp-repo-blob-page", STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history"));
31447
+ document.body.classList.toggle("gdp-repo-blob-page", fileRepoBlobRoute);
30809
31448
  document.body.classList.toggle("gdp-repo-page", STATE.route.screen === "repo");
31449
+ document.body.classList.toggle("gdp-diff-page", STATE.route.screen === "diff");
30810
31450
  document.body.classList.toggle("gdp-help-page", STATE.route.screen === "help");
30811
31451
  document.body.classList.toggle("gdp-history-page", historyPanelRoute);
30812
31452
  document.body.classList.toggle("gdp-file-history-page", fileHistoryRoute);
30813
31453
  document.body.classList.toggle("gdp-database-page", STATE.route.screen === "database");
31454
+ const repoTargetWrap = document.querySelector("#repo-target-wrap");
31455
+ if (!repoSidebarRoute && repoTargetWrap) {
31456
+ repoTargetWrap.hidden = true;
31457
+ repoTargetWrap.style.display = "none";
31458
+ }
30814
31459
  placeSidebarToggle();
30815
31460
  syncSidebarHeaderHeight();
30816
31461
  const historyPanel = $("#history-panel");
@@ -31405,6 +32050,19 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
31405
32050
  $("#theme").click();
31406
32051
  return true;
31407
32052
  }
32053
+ if (action === "open-help") {
32054
+ openHelpKeybindings({
32055
+ getRoute: () => STATE.route,
32056
+ getLanguage: () => STATE.language,
32057
+ currentRange,
32058
+ setRoute,
32059
+ setPageMode,
32060
+ renderHelpPage,
32061
+ setStatus,
32062
+ cancelActiveSourceLoad
32063
+ });
32064
+ return true;
32065
+ }
31408
32066
  return false;
31409
32067
  }
31410
32068
  document.addEventListener("keydown", handleVirtualSourcePagingKeydown, {
@@ -31575,6 +32233,8 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
31575
32233
  getRoute: () => STATE.route,
31576
32234
  setRoute,
31577
32235
  applyCommitRange: (range, pathFilter) => {
32236
+ cancelInFlightRequests();
32237
+ DIFF_VIEW.clearLoadQueue();
31578
32238
  STATE.from = range.from;
31579
32239
  STATE.to = range.to;
31580
32240
  activeHistoryPathFilter = pathFilter || null;
@@ -31706,6 +32366,7 @@ code-viewer query diff rows --id diff-xyz789 --table users --type inserted --lim
31706
32366
  currentRange,
31707
32367
  setRange,
31708
32368
  setRoute,
32369
+ loadRepo,
31709
32370
  renderStandaloneSource,
31710
32371
  getFrom: () => STATE.from,
31711
32372
  getTo: () => STATE.to,