@davmash/bygone 0.9.2 → 0.9.4

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.
@@ -2452,6 +2452,10 @@ var init_gitHistory = __esm({
2452
2452
  shortCommit: commit.shortCommit,
2453
2453
  summary: commit.summary,
2454
2454
  timestamp: commit.timestamp,
2455
+ author: commit.author,
2456
+ authorEmail: commit.authorEmail,
2457
+ message: commit.message,
2458
+ parents: commit.parents,
2455
2459
  parentSummary: parentMetadata.summary,
2456
2460
  parentTimestamp: parentMetadata.timestamp,
2457
2461
  leftLabel: `${fileName} @ ${parentCommit.slice(0, 7)}`,
@@ -2468,6 +2472,10 @@ var init_gitHistory = __esm({
2468
2472
  shortCommit: entry.shortCommit,
2469
2473
  summary: entry.summary,
2470
2474
  timestamp: entry.timestamp,
2475
+ author: entry.author,
2476
+ authorEmail: entry.authorEmail,
2477
+ message: entry.message,
2478
+ parents: entry.parents,
2471
2479
  parentSummary: entry.parentSummary,
2472
2480
  parentTimestamp: entry.parentTimestamp,
2473
2481
  leftLabel: entry.leftLabel,
@@ -2490,7 +2498,7 @@ var init_gitHistory = __esm({
2490
2498
  "--max-count",
2491
2499
  String(maxCommits),
2492
2500
  "--follow",
2493
- "--format=%H%x09%h%x09%cI%x09%s%x09%P",
2501
+ "--format=%H%x00%h%x00%cI%x00%s%x00%P%x00%an%x00%ae%x00%B%x1e",
2494
2502
  "--",
2495
2503
  relativePath
2496
2504
  ], {
@@ -2559,21 +2567,19 @@ var init_gitHistory = __esm({
2559
2567
  }, /* @__PURE__ */ new Map());
2560
2568
  }
2561
2569
  parseHistoryCommitRecords(logOutput) {
2562
- return logOutput.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).map((line) => {
2563
- const parts = line.split(" ");
2564
- const commit = parts[0];
2565
- const shortCommit = parts[1];
2566
- const timestamp = parts[2];
2567
- const hasParentField = parts.length >= 5;
2568
- const parentField = hasParentField ? parts[parts.length - 1] || "" : "";
2569
- const summaryParts = hasParentField ? parts.slice(3, -1) : parts.slice(3);
2570
- const firstParentCommit = parentField.split(" ").find((candidate) => candidate.length > 0);
2570
+ return logOutput.split("").map((record) => record.replace(/^\n+|\n+$/g, "")).filter(Boolean).map((record) => {
2571
+ const [commit, shortCommit, timestamp, summary, parentField, author, authorEmail, message] = record.split("\0");
2572
+ const parents = parentField.split(" ").filter(Boolean);
2571
2573
  return {
2572
2574
  commit,
2573
2575
  shortCommit,
2574
2576
  timestamp,
2575
- summary: summaryParts.join(" "),
2576
- parentCommit: firstParentCommit
2577
+ summary,
2578
+ author,
2579
+ authorEmail,
2580
+ message: message.trimEnd(),
2581
+ parents,
2582
+ parentCommit: parents[0]
2577
2583
  };
2578
2584
  });
2579
2585
  }
@@ -15989,11 +15995,11 @@ var require_tourHistory = __commonJS({
15989
15995
  return input;
15990
15996
  }
15991
15997
  function listCommitHistory(root, commit) {
15992
- const output = git(root, ["log", "--topo-order", "--format=%x1e%H%x00%P%x00%s%x00%cI%x00", commit, "--"]);
15998
+ const output = git(root, ["log", "--topo-order", "--format=%x1e%H%x00%P%x00%s%x00%cI%x00%an%x00%ae%x00%B%x00", commit, "--"]);
15993
15999
  const entries = [];
15994
16000
  for (const record of output.split("").slice(1)) {
15995
16001
  const fields = record.split("\0");
15996
- const [oid, parents, summary, timestamp] = fields;
16002
+ const [oid, parents, summary, timestamp, author, authorEmail, message] = fields;
15997
16003
  if (!oid) continue;
15998
16004
  entries.push({
15999
16005
  commit: oid,
@@ -16002,7 +16008,10 @@ var require_tourHistory = __commonJS({
16002
16008
  parents: parents.split(" ").filter(Boolean),
16003
16009
  shortCommit: oid.slice(0, 7),
16004
16010
  summary,
16005
- timestamp
16011
+ timestamp,
16012
+ author,
16013
+ authorEmail,
16014
+ message: message.trimEnd()
16006
16015
  });
16007
16016
  }
16008
16017
  return entries;
@@ -16261,7 +16270,7 @@ var require_tourHistory = __commonJS({
16261
16270
  "--topo-order",
16262
16271
  "--follow",
16263
16272
  "-M",
16264
- "--format=%x1e%H%x00%P%x00%s%x00%cI%x00",
16273
+ "--format=%x1e%H%x00%P%x00%s%x00%cI%x00%an%x00%ae%x00%B%x00",
16265
16274
  "--name-status",
16266
16275
  "-z",
16267
16276
  commit,
@@ -16272,8 +16281,8 @@ var require_tourHistory = __commonJS({
16272
16281
  let historicalPath = filePath;
16273
16282
  for (const record of output.split("").slice(1)) {
16274
16283
  const fields = record.split("\0");
16275
- const [oid, parents, summary, timestamp] = fields;
16276
- const changes = parseNameStatusZ2(fields.slice(4).join("\0").replace(/^\0?\n/, ""));
16284
+ const [oid, parents, summary, timestamp, author, authorEmail, message] = fields;
16285
+ const changes = parseNameStatusZ2(fields.slice(7).join("\0").replace(/^\0?\n/, ""));
16277
16286
  const change = changes.find((item) => item.path === historicalPath || item.previousPath === historicalPath) || changes[0];
16278
16287
  if (!change) continue;
16279
16288
  entries.push({
@@ -16283,6 +16292,10 @@ var require_tourHistory = __commonJS({
16283
16292
  shortCommit: oid.slice(0, 7),
16284
16293
  summary,
16285
16294
  timestamp,
16295
+ author,
16296
+ authorEmail,
16297
+ message: message.trimEnd(),
16298
+ parents: parents.split(" ").filter(Boolean),
16286
16299
  path: change.path,
16287
16300
  ...change.previousPath ? { previousPath: change.previousPath } : {}
16288
16301
  });
@@ -16961,6 +16974,12 @@ var require_workspaceHost = __commonJS({
16961
16974
  commit: entry.commit,
16962
16975
  label: `${entry.shortCommit} ${entry.summary}`.trim(),
16963
16976
  meta: entry.timestamp,
16977
+ summary: entry.summary,
16978
+ timestamp: entry.timestamp,
16979
+ author: entry.author,
16980
+ authorEmail: entry.authorEmail,
16981
+ message: entry.message,
16982
+ parents: entry.parents || (entry.parentCommit ? [entry.parentCommit] : []),
16964
16983
  selectionEnabled: index < history.entries.length,
16965
16984
  selected: current.draft.includes(entry.commit),
16966
16985
  active: active === entry.commit,
@@ -17116,7 +17135,7 @@ var require_workspaceHost = __commonJS({
17116
17135
  const workspaceAction = message.type.startsWith("workspace");
17117
17136
  const current = host.getSession();
17118
17137
  const derived = Boolean(current.workspaceView);
17119
- const sharedAction = message.type === "toggleHistorySelection" || message.type === "selectHistoryEntry";
17138
+ const sharedAction = ["toggleHistorySelection", "selectHistoryEntry", "compareHistoryEntry"].includes(message.type);
17120
17139
  const derivedAction = derived && ["refreshSession", "selectHistoryEntry", "historyBack", "historyForward", "openDirectoryEntry", "navigateFile", "multiAddPanel", "multiRemovePanel", "historyToggleStaged", "historyToggleSkipUnchanged"].includes(message.type);
17121
17140
  if (!workspaceAction && !sharedAction && !derivedAction) return false;
17122
17141
  ensure();
@@ -17170,15 +17189,21 @@ var require_workspaceHost = __commonJS({
17170
17189
  } finally {
17171
17190
  if (!accepted) tour?.dispose?.();
17172
17191
  }
17192
+ } else if (message.type === "compareHistoryEntry") {
17193
+ const entry = backend().entries[message.index];
17194
+ if (entry?.parentCommit) {
17195
+ const next = makeSession("compare", [entry.parentCommit, entry.commit], activePath());
17196
+ if (await leave()) await install(makeSession("compare", next.workspaceView.revisions, next.workspaceView.path), "compare");
17197
+ }
17173
17198
  } else if (message.type === "selectHistoryEntry" && state.mode === "compare") {
17174
17199
  const revision = backend().entries[message.index]?.commit;
17175
17200
  const panel = current.multi?.files.find((item) => item.revision === revision);
17176
17201
  if (panel) {
17177
17202
  current.multi.activePanelId = panel.id;
17178
17203
  host.send({ type: "focusHistoryPanel", panelId: panel.id });
17179
- } else {
17180
- state.status = "Select commits with the checkboxes, then choose Compare selected.";
17181
- update();
17204
+ } else if (revision) {
17205
+ const next = makeSession("history", windowAround(backend().entries, message.index, Math.max(2, current.multi?.files.length || 2)), activePath());
17206
+ if (await leave()) await install(makeSession("history", next.workspaceView.revisions, next.workspaceView.path), "history");
17182
17207
  }
17183
17208
  } else if (derivedAction) {
17184
17209
  if (message.type === "refreshSession") {
@@ -17921,7 +17946,11 @@ function createWorkspaceHistory(context, options = {}) {
17921
17946
  parentCommit: entry.parentCommit,
17922
17947
  shortCommit: entry.shortCommit,
17923
17948
  summary: entry.summary,
17924
- timestamp: entry.timestamp
17949
+ timestamp: entry.timestamp,
17950
+ author: entry.author,
17951
+ authorEmail: entry.authorEmail,
17952
+ message: entry.message,
17953
+ parents: entry.parents
17925
17954
  }))
17926
17955
  ];
17927
17956
  const changedCommitsCache = /* @__PURE__ */ new Map();
@@ -18177,7 +18206,7 @@ function readCommitMetadataBatch(repoRoot, selectedRoots, axis) {
18177
18206
  "log",
18178
18207
  "--topo-order",
18179
18208
  "--reverse",
18180
- "--format=%H%x00%h%x00%cI%x00%s%x00%P%x1e",
18209
+ "--format=%H%x00%h%x00%cI%x00%s%x00%P%x00%an%x00%ae%x00%B%x1e",
18181
18210
  ...selectedRoots.slice().sort()
18182
18211
  ], repoRoot, MAX_GIT_METADATA_BYTES);
18183
18212
  const axisSet = new Set(axis);
@@ -18187,7 +18216,7 @@ function readCommitMetadataBatch(repoRoot, selectedRoots, axis) {
18187
18216
  if (!record) {
18188
18217
  continue;
18189
18218
  }
18190
- const [commit = "", shortCommit = "", timestamp = "", summary = "", parentField = ""] = record.split("\0");
18219
+ const [commit = "", shortCommit = "", timestamp = "", summary = "", parentField = "", author = "", authorEmail = "", message = ""] = record.split("\0");
18191
18220
  if (!axisSet.has(commit)) {
18192
18221
  continue;
18193
18222
  }
@@ -18196,6 +18225,10 @@ function readCommitMetadataBatch(repoRoot, selectedRoots, axis) {
18196
18225
  shortCommit: shortCommit || commit.slice(0, 7),
18197
18226
  timestamp,
18198
18227
  summary,
18228
+ author,
18229
+ authorEmail,
18230
+ message: message.trimEnd(),
18231
+ parents: parentField.split(" ").filter(Boolean),
18199
18232
  parentCommit: parentField.split(" ").find(Boolean) ?? null
18200
18233
  });
18201
18234
  }
@@ -18752,7 +18785,7 @@ var require_package = __commonJS({
18752
18785
  displayName: "Bygone",
18753
18786
  desktopName: "com.davidmashburn.bygone.desktop",
18754
18787
  description: "See how change happened. Visual diff and file history for VS Code.",
18755
- version: "0.9.2",
18788
+ version: "0.9.4",
18756
18789
  author: "David Mashburn",
18757
18790
  license: "MIT",
18758
18791
  private: false,
@@ -32452,6 +32485,141 @@ ${error.message}`);
32452
32485
  assert.equal(session2().multi.files.at(-1).editable, true);
32453
32486
  const checkboxes = await evaluate("document.querySelectorAll('#history-rail [role=checkbox]').length");
32454
32487
  assert.ok(checkboxes >= 4, `Expected all commits, got ${checkboxes}`);
32488
+ const hoverElapsed = await evaluate(`new Promise((resolve, reject) => {
32489
+ document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
32490
+ const details = document.querySelector('.commit-hover-details');
32491
+ const started = performance.now();
32492
+ const timeout = setTimeout(() => { observer.disconnect(); reject(new Error('Commit hover did not open')); }, 5000);
32493
+ const observer = new MutationObserver(() => {
32494
+ if (details.hidden) return;
32495
+ observer.disconnect(); clearTimeout(timeout); resolve(performance.now() - started);
32496
+ });
32497
+ observer.observe(details, { attributes: true, attributeFilter: ['hidden'] });
32498
+ document.querySelector('[data-commit-row] .history-rail-entry').dispatchEvent(
32499
+ new MouseEvent('mouseover', { bubbles: true, relatedTarget: document.querySelector('[data-commit-row] [data-rail-menu]') }));
32500
+ })`);
32501
+ assert.ok(hoverElapsed >= 480, `Commit details wait for the shared 500ms hover delay (got ${hoverElapsed}ms)`);
32502
+ const overlays = await evaluate(`(async () => {
32503
+ const row = document.querySelector('[data-commit-row]');
32504
+ const details = document.querySelector('.commit-hover-details');
32505
+ const selected = document.querySelectorAll('[aria-checked=true]').length;
32506
+ const detailStyle = getComputedStyle(details);
32507
+ const hover = { visible: !details.hidden, background: detailStyle.backgroundColor, font: detailStyle.fontFamily };
32508
+ row.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, cancelable: true, clientX: 40, clientY: 200 }));
32509
+ const menu = document.querySelector('.commit-context-menu');
32510
+ const menuStyle = getComputedStyle(menu);
32511
+ const buttonStyle = getComputedStyle(menu.querySelector('button'));
32512
+ const result = { hover, menuVisible: !menu.hidden, background: menuStyle.backgroundColor,
32513
+ font: menuStyle.fontFamily, buttonFont: buttonStyle.fontFamily,
32514
+ singleNavigationTooltip: ['previous-file', 'next-file'].every(id => {
32515
+ const button = document.getElementById(id);
32516
+ return button.hasAttribute('data-tooltip') && !button.hasAttribute('title');
32517
+ }),
32518
+ selectionUnchanged: selected === document.querySelectorAll('[aria-checked=true]').length };
32519
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
32520
+ await new Promise(resolve => setTimeout(resolve, 700));
32521
+ result.dismissed = menu.hidden && details.hidden;
32522
+ return result;
32523
+ })()`);
32524
+ assert.equal(overlays.hover.visible, true);
32525
+ assert.equal(overlays.menuVisible, true);
32526
+ assert.equal(overlays.selectionUnchanged, true);
32527
+ assert.equal(overlays.singleNavigationTooltip, true, "File navigation must not show both managed and native tooltips");
32528
+ assert.equal(overlays.dismissed, true, "Escape must not reopen the tooltip through focus restoration");
32529
+ for (const [surface, background] of Object.entries({ details: overlays.hover.background, menu: overlays.background })) {
32530
+ assert.match(background, /^rgb\(/, `${surface} must be opaque: ${JSON.stringify(overlays)}`);
32531
+ }
32532
+ assert.match(overlays.hover.font, /system-ui/);
32533
+ assert.match(overlays.font, /system-ui/);
32534
+ assert.equal(overlays.buttonFont, overlays.font);
32535
+ const canceledCommitHover = await evaluate(`(async () => {
32536
+ const entry = document.querySelector('[data-commit-row] .history-rail-entry');
32537
+ entry.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: document.body }));
32538
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
32539
+ await new Promise(resolve => setTimeout(resolve, 700));
32540
+ return document.querySelector('.commit-hover-details').hidden;
32541
+ })()`);
32542
+ assert.equal(canceledCommitHover, true, "Escape cancels pending commit hovers as well as visible ones");
32543
+ const tooltipChecks = await evaluate(`(async () => {
32544
+ const tooltip = document.getElementById('bygone-ui-tooltip');
32545
+ const toolbar = document.getElementById('directory-tree-toolbar');
32546
+ const originallyHidden = toolbar.hidden;
32547
+ toolbar.hidden = false;
32548
+ const hover = async (button, dismiss = true) => {
32549
+ document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
32550
+ const started = performance.now();
32551
+ const elapsed = await new Promise((resolve, reject) => {
32552
+ const timeout = setTimeout(() => { observer.disconnect(); reject(new Error('Tooltip did not open for ' + button.id)); }, 5000);
32553
+ const observer = new MutationObserver(() => {
32554
+ if (tooltip.hidden) return;
32555
+ observer.disconnect(); clearTimeout(timeout); resolve(performance.now() - started);
32556
+ });
32557
+ observer.observe(tooltip, { attributes: true, attributeFilter: ['hidden'] });
32558
+ const svg = button.querySelector('svg') || button;
32559
+ svg.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: document.body }));
32560
+ // Moving across the icon must not restart or cancel the timer.
32561
+ (svg.querySelector('path') || svg).dispatchEvent(new MouseEvent('mouseover', { bubbles: true, relatedTarget: svg }));
32562
+ });
32563
+ const style = getComputedStyle(tooltip);
32564
+ const anchor = button.getBoundingClientRect();
32565
+ const bounds = tooltip.getBoundingClientRect();
32566
+ const result = { id: button.id, elapsed, text: tooltip.textContent, nativeTitle: button.hasAttribute('title'),
32567
+ opacity: style.opacity, background: style.backgroundColor, font: style.fontFamily,
32568
+ placedBelow: Math.abs(bounds.top - anchor.bottom - 8) < 1,
32569
+ inViewport: bounds.left >= 8 && bounds.right <= innerWidth - 8,
32570
+ pseudoContent: getComputedStyle(button, '::after').content };
32571
+ if (dismiss) button.dispatchEvent(new MouseEvent('mouseout', { bubbles: true, relatedTarget: document.body }));
32572
+ result.dismissed = tooltip.hidden;
32573
+ return result;
32574
+ };
32575
+ const checks = [];
32576
+ for (const id of ['directory-expand-all', 'directory-collapse-all', 'directory-collapse-unchanged', 'previous-file', 'next-file']) {
32577
+ const button = document.getElementById(id);
32578
+ const originallyHidden = button.hidden;
32579
+ button.hidden = false;
32580
+ checks.push(await hover(button));
32581
+ button.hidden = originallyHidden;
32582
+ }
32583
+ const button = document.getElementById('directory-expand-all');
32584
+ const oldText = button.dataset.tooltip;
32585
+ button.title = 'Updated tooltip';
32586
+ await Promise.resolve();
32587
+ checks.push({ updatedTitle: button.dataset.tooltip === 'Updated tooltip' && !button.hasAttribute('title') });
32588
+ button.title = oldText;
32589
+ document.dispatchEvent(new Event('pointerdown', { bubbles: true }));
32590
+ const modal = document.createElement('dialog');
32591
+ const action = document.createElement('button');
32592
+ action.id = 'modal-tooltip-smoke';
32593
+ action.title = 'Tooltip inside modal';
32594
+ action.textContent = 'Action';
32595
+ modal.appendChild(action);
32596
+ document.body.appendChild(modal);
32597
+ modal.showModal();
32598
+ const modalCheck = await hover(action, false);
32599
+ modalCheck.parentIsModal = tooltip.parentElement === modal;
32600
+ modal.close();
32601
+ await Promise.resolve();
32602
+ modalCheck.modalDismissed = tooltip.hidden && tooltip.parentElement === document.body;
32603
+ modal.remove();
32604
+ checks.push(modalCheck);
32605
+ toolbar.hidden = originallyHidden;
32606
+ return checks;
32607
+ })()`);
32608
+ const modalCheck = tooltipChecks.pop();
32609
+ assert.ok(
32610
+ modalCheck.text === "Tooltip inside modal" && modalCheck.parentIsModal && modalCheck.modalDismissed,
32611
+ "Modal tooltips render in the top layer and close with their dialog"
32612
+ );
32613
+ assert.equal(tooltipChecks.pop().updatedTitle, true, "Dynamic title updates use the managed tooltip");
32614
+ for (const check of tooltipChecks) {
32615
+ assert.ok(check.elapsed >= 480, `${check.id} must wait 500ms: ${JSON.stringify(check)}`);
32616
+ assert.ok(check.text && !check.nativeTitle && check.dismissed, JSON.stringify(check));
32617
+ assert.equal(check.opacity, "1", "Tooltip opacity is independent of disabled buttons");
32618
+ assert.match(check.background, /^rgb\(/);
32619
+ assert.match(check.font, /system-ui/);
32620
+ assert.equal(check.pseudoContent, "none", "No CSS bubble can appear instantly");
32621
+ assert.ok(check.placedBelow && check.inViewport, JSON.stringify(check));
32622
+ }
32455
32623
  await evaluate("document.querySelectorAll('#history-rail [role=checkbox]')[1].click()");
32456
32624
  await waitFor("document.querySelector('[data-action=workspaceApply]').textContent.includes('(1)')");
32457
32625
  await evaluate("document.querySelectorAll('#history-rail [role=checkbox]')[2].click()");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davmash/bygone",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "description": "See how change happened. Visual diff and file history.",
5
5
  "author": "David Mashburn",
6
6
  "license": "MIT",
@@ -30,13 +30,13 @@
30
30
  <div id="change-toolbar" class="change-toolbar header-align-diff" hidden>
31
31
  <div class="change-toolbar-main">
32
32
  <div class="change-toolbar-nav">
33
- <button id="previous-file" class="change-button icon-button" type="button" title="Open previous file" data-tooltip="Open previous file" aria-label="Previous file">
33
+ <button id="previous-file" class="change-button icon-button" type="button" data-tooltip="Open previous file" aria-label="Previous file">
34
34
  <svg viewBox="0 0 24 24" aria-hidden="true">
35
35
  <path d="M17 11l-5-5-5 5"></path>
36
36
  <path d="M17 18l-5-5-5 5" opacity="0.7"></path>
37
37
  </svg>
38
38
  </button>
39
- <button id="next-file" class="change-button change-button-primary icon-button" type="button" title="Open next file" data-tooltip="Open next file" aria-label="Next file">
39
+ <button id="next-file" class="change-button change-button-primary icon-button" type="button" data-tooltip="Open next file" aria-label="Next file">
40
40
  <svg viewBox="0 0 24 24" aria-hidden="true">
41
41
  <path d="M7 13l5 5 5-5"></path>
42
42
  <path d="M7 6l5 5 5-5" opacity="0.7"></path>
@@ -102,14 +102,14 @@
102
102
  <div id="history-left-time" class="history-time"></div>
103
103
  </div>
104
104
  <div class="history-nav">
105
- <button id="history-back" class="history-button icon-button" type="button" title="Open older commit (Alt+Left)" data-tooltip="Open older commit" aria-label="Older commit">
105
+ <button id="history-back" class="history-button icon-button" type="button" data-tooltip="Open older commit (Alt+Left)" aria-label="Older commit">
106
106
  <svg viewBox="0 0 24 24" aria-hidden="true">
107
107
  <path d="M15 18l-6-6 6-6"></path>
108
108
  <path d="M10 18l-6-6 6-6" opacity="0.7"></path>
109
109
  </svg>
110
110
  </button>
111
111
  <div id="history-position" class="history-position" role="status" aria-live="polite" aria-atomic="true"></div>
112
- <button id="history-forward" class="history-button icon-button" type="button" title="Open newer commit (Alt+Right)" data-tooltip="Open newer commit" aria-label="Newer commit">
112
+ <button id="history-forward" class="history-button icon-button" type="button" data-tooltip="Open newer commit (Alt+Right)" aria-label="Newer commit">
113
113
  <svg viewBox="0 0 24 24" aria-hidden="true">
114
114
  <path d="M9 18l6-6-6-6"></path>
115
115
  <path d="M14 18l6-6-6-6" opacity="0.7"></path>
package/web/index.html CHANGED
@@ -177,13 +177,13 @@
177
177
  <div id="change-toolbar" class="change-toolbar header-align-diff" hidden>
178
178
  <div class="change-toolbar-main">
179
179
  <div class="change-toolbar-nav">
180
- <button id="previous-file" class="change-button icon-button" type="button" title="Open previous file" data-tooltip="Open previous file" aria-label="Previous file">
180
+ <button id="previous-file" class="change-button icon-button" type="button" data-tooltip="Open previous file" aria-label="Previous file">
181
181
  <svg viewBox="0 0 24 24" aria-hidden="true">
182
182
  <path d="M17 11l-5-5-5 5"></path>
183
183
  <path d="M17 18l-5-5-5 5"></path>
184
184
  </svg>
185
185
  </button>
186
- <button id="next-file" class="change-button icon-button" type="button" title="Open next file" data-tooltip="Open next file" aria-label="Next file">
186
+ <button id="next-file" class="change-button icon-button" type="button" data-tooltip="Open next file" aria-label="Next file">
187
187
  <svg viewBox="0 0 24 24" aria-hidden="true">
188
188
  <path d="M7 13l5 5 5-5"></path>
189
189
  <path d="M7 6l5 5 5-5"></path>
@@ -249,14 +249,14 @@
249
249
  <div id="history-left-time" class="history-time"></div>
250
250
  </div>
251
251
  <div class="history-nav">
252
- <button id="history-back" class="history-button icon-button" type="button" title="Open older commit" data-tooltip="Open older commit" aria-label="Older commit">
252
+ <button id="history-back" class="history-button icon-button" type="button" data-tooltip="Open older commit" aria-label="Older commit">
253
253
  <svg viewBox="0 0 24 24" aria-hidden="true">
254
254
  <path d="M15 18l-6-6 6-6"></path>
255
255
  <path d="M10 18l-6-6 6-6" opacity="0.7"></path>
256
256
  </svg>
257
257
  </button>
258
258
  <div id="history-position" class="history-position" role="status" aria-live="polite" aria-atomic="true"></div>
259
- <button id="history-forward" class="history-button icon-button" type="button" title="Open newer commit" data-tooltip="Open newer commit" aria-label="Newer commit">
259
+ <button id="history-forward" class="history-button icon-button" type="button" data-tooltip="Open newer commit" aria-label="Newer commit">
260
260
  <svg viewBox="0 0 24 24" aria-hidden="true">
261
261
  <path d="M9 18l6-6-6-6"></path>
262
262
  <path d="M14 18l6-6-6-6" opacity="0.7"></path>
package/web/presenter.css CHANGED
@@ -253,12 +253,18 @@ body.tour-mode #container {
253
253
  }
254
254
 
255
255
  .tour-coverage-item {
256
+ position: relative;
256
257
  display: grid;
257
258
  gap: 5px;
258
259
  padding-left: 8px;
259
260
  border-left: 2px solid var(--tour-accent-bright);
260
261
  }
261
262
 
263
+ .tour-coverage-item:focus-visible {
264
+ outline: 2px solid var(--tour-accent-bright);
265
+ outline-offset: 3px;
266
+ }
267
+
262
268
  .tour-coverage-item[data-metric="assignment"] {
263
269
  border-left-color: #74d6ad;
264
270
  }