@davmash/bygone 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.
@@ -8772,6 +8772,20 @@ var require_present = __commonJS({
8772
8772
  let presenterIndexTemplate;
8773
8773
  const server = createServer((request, response) => {
8774
8774
  const requestUrl = new URL(request.url || "/", "http://127.0.0.1");
8775
+ if (requestUrl.pathname === "/narration/claim") {
8776
+ if (request.method !== "POST") {
8777
+ respond(response, 405, "Method not allowed");
8778
+ return;
8779
+ }
8780
+ if (!isSameOriginLoopbackRequest(request)) {
8781
+ respond(response, 403, "Forbidden");
8782
+ return;
8783
+ }
8784
+ options.onNarrationClaim?.();
8785
+ response.writeHead(204, { "Cache-Control": "no-store" });
8786
+ response.end();
8787
+ return;
8788
+ }
8775
8789
  if (requestUrl.pathname === "/tour.json") {
8776
8790
  response.writeHead(200, {
8777
8791
  "Content-Type": "application/json; charset=utf-8",
@@ -8897,6 +8911,17 @@ var require_present = __commonJS({
8897
8911
  }
8898
8912
  response.end(message);
8899
8913
  }
8914
+ function isSameOriginLoopbackRequest(request) {
8915
+ const origin = request.headers.origin;
8916
+ const host = request.headers.host;
8917
+ if (typeof origin !== "string" || typeof host !== "string") return false;
8918
+ try {
8919
+ const parsed = new URL(origin);
8920
+ return parsed.protocol === "http:" && parsed.hostname === "127.0.0.1" && parsed.host === host;
8921
+ } catch {
8922
+ return false;
8923
+ }
8924
+ }
8900
8925
  function escapeHtml(text) {
8901
8926
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
8902
8927
  }
@@ -8951,6 +8976,116 @@ var require_launchArgs = __commonJS({
8951
8976
  }
8952
8977
  });
8953
8978
 
8979
+ // standalone/windowState.js
8980
+ var require_windowState = __commonJS({
8981
+ "standalone/windowState.js"(exports2, module2) {
8982
+ "use strict";
8983
+ var fs8 = require("fs");
8984
+ var path9 = require("path");
8985
+ var WINDOW_STATE_VERSION = 1;
8986
+ function readWindowState2(filePath) {
8987
+ try {
8988
+ return normalizeWindowState(JSON.parse(fs8.readFileSync(filePath, "utf8")));
8989
+ } catch {
8990
+ return null;
8991
+ }
8992
+ }
8993
+ function writeWindowState2(filePath, state) {
8994
+ const normalized = normalizeWindowState(state);
8995
+ if (!normalized) {
8996
+ throw new Error("Cannot save invalid Bygone window state.");
8997
+ }
8998
+ fs8.mkdirSync(path9.dirname(filePath), { recursive: true });
8999
+ const temporaryPath = `${filePath}.${process.pid}.tmp`;
9000
+ try {
9001
+ fs8.writeFileSync(temporaryPath, `${JSON.stringify(normalized, null, 2)}
9002
+ `, "utf8");
9003
+ fs8.renameSync(temporaryPath, filePath);
9004
+ } finally {
9005
+ try {
9006
+ fs8.unlinkSync(temporaryPath);
9007
+ } catch {
9008
+ }
9009
+ }
9010
+ }
9011
+ function normalizeWindowState(value) {
9012
+ if (!value || typeof value !== "object" || value.version !== WINDOW_STATE_VERSION) {
9013
+ return null;
9014
+ }
9015
+ const mainSource = value.main === null ? null : normalizeSessionSource(value.main?.source);
9016
+ if (value.main !== null && !mainSource) {
9017
+ return null;
9018
+ }
9019
+ if (!Array.isArray(value.tours)) {
9020
+ return null;
9021
+ }
9022
+ const tours = value.tours.map(normalizeTourLaunch).filter(Boolean);
9023
+ if (tours.length !== value.tours.length) {
9024
+ return null;
9025
+ }
9026
+ return {
9027
+ version: WINDOW_STATE_VERSION,
9028
+ main: mainSource ? { source: mainSource } : null,
9029
+ tours
9030
+ };
9031
+ }
9032
+ function normalizeSessionSource(source) {
9033
+ if (!source || typeof source !== "object" || typeof source.kind !== "string") {
9034
+ return null;
9035
+ }
9036
+ if (source.kind === "blank") {
9037
+ return { kind: "blank" };
9038
+ }
9039
+ if (source.kind === "files" || source.kind === "directories") {
9040
+ if (!isStringArray(source.paths) || source.paths.length === 0) return null;
9041
+ return {
9042
+ kind: source.kind,
9043
+ paths: [...source.paths],
9044
+ ...source.kind === "directories" && isStringArray(source.labels) ? { labels: [...source.labels] } : {}
9045
+ };
9046
+ }
9047
+ if (source.kind === "file-history" || source.kind === "directory-history") {
9048
+ if (typeof source.path !== "string" || source.path.length === 0) return null;
9049
+ return {
9050
+ kind: source.kind,
9051
+ path: source.path,
9052
+ includeStaged: Boolean(source.includeStaged),
9053
+ skipUnchanged: Boolean(source.skipUnchanged)
9054
+ };
9055
+ }
9056
+ if (source.kind === "git-refs") {
9057
+ if (typeof source.repoRoot !== "string" || !isStringArray(source.refs) || source.refs.length < 2) return null;
9058
+ return { kind: source.kind, repoRoot: source.repoRoot, refs: [...source.refs] };
9059
+ }
9060
+ if (source.kind === "branch-review") {
9061
+ if (typeof source.repoRoot !== "string" || typeof source.headRef !== "string") return null;
9062
+ return {
9063
+ kind: source.kind,
9064
+ repoRoot: source.repoRoot,
9065
+ headRef: source.headRef,
9066
+ ...typeof source.baseRef === "string" ? { baseRef: source.baseRef } : {}
9067
+ };
9068
+ }
9069
+ return null;
9070
+ }
9071
+ function normalizeTourLaunch(value) {
9072
+ if (!value || typeof value !== "object" || !isStringArray(value.args) || typeof value.cwd !== "string") {
9073
+ return null;
9074
+ }
9075
+ return { args: [...value.args], cwd: value.cwd };
9076
+ }
9077
+ function isStringArray(value) {
9078
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
9079
+ }
9080
+ module2.exports = {
9081
+ WINDOW_STATE_VERSION,
9082
+ normalizeWindowState,
9083
+ readWindowState: readWindowState2,
9084
+ writeWindowState: writeWindowState2
9085
+ };
9086
+ }
9087
+ });
9088
+
8954
9089
  // src/windowTitle.ts
8955
9090
  var windowTitle_exports = {};
8956
9091
  __export(windowTitle_exports, {
@@ -9075,7 +9210,7 @@ var require_package = __commonJS({
9075
9210
  displayName: "Bygone",
9076
9211
  desktopName: "com.davidmashburn.bygone.desktop",
9077
9212
  description: "See how change happened. Visual diff and file history for VS Code.",
9078
- version: "0.8.6",
9213
+ version: "0.8.8",
9079
9214
  author: "David Mashburn",
9080
9215
  license: "MIT",
9081
9216
  private: false,
@@ -9263,7 +9398,7 @@ var require_package = __commonJS({
9263
9398
  "icons:generate": "node ./scripts/generate-icons.mjs",
9264
9399
  "package:vsix": "node ./scripts/check-vsix-contents.mjs && vsce package",
9265
9400
  "standalone:start": "npm run compile && node ./scripts/run-electron.mjs ./out/standalone-main.js",
9266
- "standalone:smoke": "npm run compile && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test-multi && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test-directory",
9401
+ "standalone:smoke": "npm run compile && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test-multi && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test-directory && node ./scripts/run-electron.mjs ./out/standalone-main.js --smoke-test-directory-history",
9267
9402
  "walkthrough:refresh": "node ./scripts/refresh-walkthrough-screenshots.mjs",
9268
9403
  "web:start": "npm run compile && node ./scripts/web-server.mjs",
9269
9404
  "package:desktop": "npm run package:desktop:mac",
@@ -22324,6 +22459,7 @@ var { tokenMatches, tokensFor } = require_commandSpec();
22324
22459
  var { startPresentation } = require_present();
22325
22460
  var { resolveWorkingDirectory } = require_workingDirectory();
22326
22461
  var { getCliArgsFromArgv, getForwardedLaunchArgs } = require_launchArgs();
22462
+ var { readWindowState, writeWindowState } = require_windowState();
22327
22463
  var {
22328
22464
  buildHistoryTitle: buildHistoryTitle2,
22329
22465
  buildStandaloneSessionTitle: buildStandaloneSessionTitle2,
@@ -22343,13 +22479,15 @@ var parsedDirectoryHistoryCacheSize = Number.parseInt(process.env.BYGONE_DIR_HIS
22343
22479
  var DIRECTORY_HISTORY_CACHE_SIZE = Number.isFinite(parsedDirectoryHistoryCacheSize) && parsedDirectoryHistoryCacheSize > 0 ? parsedDirectoryHistoryCacheSize : DEFAULT_DIRECTORY_HISTORY_CACHE_SIZE;
22344
22480
  var commandLineToolPath = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || os.homedir(), "Microsoft", "WindowsApps", "bygone.cmd") : "/usr/local/bin/bygone";
22345
22481
  var gitHistoryService = new GitHistoryService2();
22346
- var launchArguments = parseLaunchArgs(getCliArgs());
22347
- var smokeTestMode = launchArguments.kind === "smoke" || launchArguments.kind === "smoke-multi" || launchArguments.kind === "smoke-directory";
22482
+ var initialCliArgs = getCliArgs();
22483
+ var launchArguments = parseLaunchArgs(initialCliArgs);
22484
+ var smokeTestMode = launchArguments.kind === "smoke" || launchArguments.kind === "smoke-multi" || launchArguments.kind === "smoke-directory" || launchArguments.kind === "smoke-directory-history";
22348
22485
  var captureOutputPath = launchArguments.capturePath ? path8.resolve(launchArguments.capturePath) : null;
22349
22486
  var captureMode = Boolean(captureOutputPath);
22350
22487
  var launchWindowWidth = Number.isFinite(launchArguments.windowWidth) ? launchArguments.windowWidth : 1500;
22351
22488
  var launchWindowHeight = Number.isFinite(launchArguments.windowHeight) ? launchArguments.windowHeight : 960;
22352
22489
  var shouldUseSingleInstanceLock = app.isPackaged && !smokeTestMode && !captureMode;
22490
+ var shouldRestoreWindowState = initialCliArgs.length === 0 && !smokeTestMode && !captureMode;
22353
22491
  app.setName(APP_NAME);
22354
22492
  if (typeof app.setAppUserModelId === "function") {
22355
22493
  app.setAppUserModelId("com.davidmashburn.bygone");
@@ -22365,6 +22503,7 @@ var closingForSave = false;
22365
22503
  var fileWatchers = [];
22366
22504
  var session = createEmptySession();
22367
22505
  var smokeTimeout;
22506
+ var directoryHistorySmokeDrilldownStarted = false;
22368
22507
  var pendingOpenPaths = [];
22369
22508
  var historyIncludeStagedPreference = false;
22370
22509
  var historySkipUnchangedPreference = false;
@@ -22383,6 +22522,8 @@ var wordWrapEnabled = false;
22383
22522
  var wordWrapAvailable = false;
22384
22523
  var repositorySearch = null;
22385
22524
  var lastRepositoryReplacement = null;
22525
+ var appIsQuitting = false;
22526
+ var restoringWindowState = false;
22386
22527
  if (!singleInstanceLock) {
22387
22528
  app.quit();
22388
22529
  }
@@ -22399,6 +22540,10 @@ app.on("window-all-closed", () => {
22399
22540
  app.quit();
22400
22541
  }
22401
22542
  });
22543
+ app.on("before-quit", () => {
22544
+ appIsQuitting = true;
22545
+ persistOpenWindowState();
22546
+ });
22402
22547
  app.on("will-quit", () => {
22403
22548
  repositorySearch?.handle?.cancel();
22404
22549
  for (const server of [...activeTourServers]) {
@@ -22548,6 +22693,9 @@ function createMainWindow({ show = !smokeTestMode } = {}) {
22548
22693
  smokeTimeout = void 0;
22549
22694
  captureScheduled = false;
22550
22695
  captureRenderReady = false;
22696
+ if (!appIsQuitting && (process.platform === "darwin" || BrowserWindow.getAllWindows().length > 0)) {
22697
+ persistOpenWindowState();
22698
+ }
22551
22699
  });
22552
22700
  }
22553
22701
  function ensureMainWindow() {
@@ -22557,13 +22705,18 @@ function ensureMainWindow() {
22557
22705
  }
22558
22706
  }
22559
22707
  async function openTourPresentation(args, cwd) {
22708
+ let ownerWindow = null;
22560
22709
  const presentation = await startPresentation(args, cwd, packageRoot, {
22561
22710
  announce: false,
22562
- open: false
22711
+ open: false,
22712
+ onNarrationClaim: () => claimTourNarration(ownerWindow)
22563
22713
  });
22564
22714
  activeTourServers.add(presentation.server);
22565
22715
  try {
22566
- await showTourWindow(presentation.url, presentation.server, presentation.manifest);
22716
+ ownerWindow = await showTourWindow(presentation.url, presentation.server, presentation.manifest, {
22717
+ args: [...args],
22718
+ cwd: path8.resolve(cwd)
22719
+ });
22567
22720
  } catch (error) {
22568
22721
  closeTourServer(presentation.server);
22569
22722
  throw error;
@@ -22573,7 +22726,7 @@ async function openAuthoredTourDocument(sourcePath) {
22573
22726
  const document2 = discoverAuthoredTourDocument2(sourcePath);
22574
22727
  await openTourPresentation(["--tour", document2.documentPath], document2.repoRoot);
22575
22728
  }
22576
- async function showTourWindow(url, server, manifest) {
22729
+ async function showTourWindow(url, server, manifest, launch) {
22577
22730
  const parsedUrl = new URL(url);
22578
22731
  if (parsedUrl.protocol !== "http:" || parsedUrl.hostname !== "127.0.0.1") {
22579
22732
  throw new Error("Tour presenter URL must use the local Bygone server.");
@@ -22595,7 +22748,8 @@ async function showTourWindow(url, server, manifest) {
22595
22748
  }
22596
22749
  });
22597
22750
  latestTourWindow = tourWindow;
22598
- tourPresentations.set(tourWindow, { server, origin: tourOrigin });
22751
+ tourPresentations.set(tourWindow, { server, origin: tourOrigin, launch });
22752
+ installApplicationMenu();
22599
22753
  tourWindow.webContents.setWindowOpenHandler(({ url: externalUrl }) => {
22600
22754
  if (/^https?:\/\//i.test(externalUrl)) {
22601
22755
  void shell.openExternal(externalUrl);
@@ -22622,6 +22776,10 @@ async function showTourWindow(url, server, manifest) {
22622
22776
  if (latestTourWindow === tourWindow) {
22623
22777
  latestTourWindow = getLatestTourWindow();
22624
22778
  }
22779
+ installApplicationMenu();
22780
+ if (!appIsQuitting && (process.platform === "darwin" || BrowserWindow.getAllWindows().length > 0)) {
22781
+ persistOpenWindowState();
22782
+ }
22625
22783
  });
22626
22784
  const tourWindowTitle = buildTourWindowTitle2(manifest, APP_NAME);
22627
22785
  const applyTourWindowTitle = () => {
@@ -22643,11 +22801,30 @@ async function showTourWindow(url, server, manifest) {
22643
22801
  applyTourWindowTitle();
22644
22802
  tourWindow.show();
22645
22803
  tourWindow.focus();
22804
+ persistOpenWindowState();
22646
22805
  return tourWindow;
22647
22806
  }
22648
22807
  function getLatestTourWindow() {
22649
22808
  return [...tourPresentations.keys()].reverse().find((window2) => !window2.isDestroyed());
22650
22809
  }
22810
+ function claimTourNarration(ownerWindow) {
22811
+ if (!ownerWindow || ownerWindow.isDestroyed()) return;
22812
+ for (const tourWindow of tourPresentations.keys()) {
22813
+ if (tourWindow !== ownerWindow && !tourWindow.isDestroyed()) {
22814
+ dispatchTourPresenterCommand(tourWindow, "pauseNarration");
22815
+ }
22816
+ }
22817
+ }
22818
+ function dispatchTourPresenterCommand(tourWindow, command) {
22819
+ if (!tourWindow || tourWindow.isDestroyed()) return;
22820
+ const script = `window.dispatchEvent(new CustomEvent('bygone:tour-command', { detail: ${JSON.stringify(command)} }))`;
22821
+ void tourWindow.webContents.executeJavaScript(script).catch(() => {
22822
+ });
22823
+ }
22824
+ function toggleLatestTourNarration() {
22825
+ const tourWindow = getLatestTourWindow();
22826
+ if (tourWindow) dispatchTourPresenterCommand(tourWindow, "toggleNarration");
22827
+ }
22651
22828
  function closeTourServer(server) {
22652
22829
  if (!server || !activeTourServers.delete(server)) {
22653
22830
  return;
@@ -22962,6 +23139,12 @@ ${HELP_URL}`,
22962
23139
  click: () => {
22963
23140
  void openAuthoredTourDialog().catch((error) => showError(`Could not open Bygone presentation: ${getErrorMessage(error)}`));
22964
23141
  }
23142
+ },
23143
+ { type: "separator" },
23144
+ {
23145
+ label: "Listen to Tour",
23146
+ enabled: Boolean(getLatestTourWindow()),
23147
+ click: toggleLatestTourNarration
22965
23148
  }
22966
23149
  ]
22967
23150
  },
@@ -23320,8 +23503,73 @@ async function openInitialLaunchTarget() {
23320
23503
  await routePendingOpenPaths();
23321
23504
  return;
23322
23505
  }
23506
+ if (shouldRestoreWindowState && await restoreOpenWindowState()) {
23507
+ return;
23508
+ }
23323
23509
  await routeLaunchTarget(launchArguments);
23324
23510
  }
23511
+ async function restoreOpenWindowState() {
23512
+ const savedState = readWindowState(getWindowStatePath());
23513
+ if (!savedState || !savedState.main && savedState.tours.length === 0) {
23514
+ return false;
23515
+ }
23516
+ let restoredAnyWindow = false;
23517
+ restoringWindowState = true;
23518
+ try {
23519
+ if (savedState.main) {
23520
+ ensureMainWindow();
23521
+ try {
23522
+ await restoreMainSession(savedState.main.source);
23523
+ } catch (error) {
23524
+ console.error(`Could not restore the previous Bygone session: ${getErrorMessage(error)}`);
23525
+ await openBlankDiff();
23526
+ }
23527
+ restoredAnyWindow = true;
23528
+ }
23529
+ for (const tour of savedState.tours) {
23530
+ try {
23531
+ await openTourPresentation(tour.args, tour.cwd);
23532
+ restoredAnyWindow = true;
23533
+ } catch (error) {
23534
+ console.error(`Could not restore a previous Bygone tour: ${getErrorMessage(error)}`);
23535
+ }
23536
+ }
23537
+ } finally {
23538
+ restoringWindowState = false;
23539
+ persistOpenWindowState();
23540
+ }
23541
+ return restoredAnyWindow;
23542
+ }
23543
+ async function restoreMainSession(source) {
23544
+ if (source.kind === "blank") {
23545
+ await openBlankDiff();
23546
+ return;
23547
+ }
23548
+ session = buildSessionFromSource(source);
23549
+ historyIncludeStagedPreference = Boolean(source.includeStaged ?? historyIncludeStagedPreference);
23550
+ historySkipUnchangedPreference = Boolean(source.skipUnchanged ?? historySkipUnchangedPreference);
23551
+ clearWatchers();
23552
+ updateWatchers();
23553
+ await sendCurrentSession();
23554
+ }
23555
+ function persistOpenWindowState() {
23556
+ if (restoringWindowState || !app.isReady()) return;
23557
+ const hasMainWindow = Boolean(mainWindow && !mainWindow.isDestroyed());
23558
+ const mainSource = hasMainWindow && (isRefreshableSource(session.source) || session.source?.kind === "blank") ? cloneSessionSource(session.source) : hasMainWindow ? { kind: "blank" } : null;
23559
+ const tours = [...tourPresentations.entries()].filter(([window2, presentation]) => !window2.isDestroyed() && presentation.launch).map(([, presentation]) => presentation.launch);
23560
+ try {
23561
+ writeWindowState(getWindowStatePath(), {
23562
+ version: 1,
23563
+ main: mainSource ? { source: mainSource } : null,
23564
+ tours
23565
+ });
23566
+ } catch (error) {
23567
+ console.error(`Could not save Bygone window state: ${getErrorMessage(error)}`);
23568
+ }
23569
+ }
23570
+ function getWindowStatePath() {
23571
+ return path8.join(app.getPath("userData"), "open-windows.json");
23572
+ }
23325
23573
  async function routeLaunchTarget(launchTarget) {
23326
23574
  if (launchTarget.kind === "unsupported-launch-intent") {
23327
23575
  ensureMainWindow();
@@ -23397,6 +23645,10 @@ async function routeLaunchTarget(launchTarget) {
23397
23645
  }
23398
23646
  if (launchTarget.kind === "smoke-directory") {
23399
23647
  await compareDirectoryTestFiles();
23648
+ return;
23649
+ }
23650
+ if (launchTarget.kind === "smoke-directory-history") {
23651
+ await compareDirectoryHistoryTestFiles();
23400
23652
  }
23401
23653
  }
23402
23654
  function sourceForLaunchTarget(launchTarget) {
@@ -23535,6 +23787,9 @@ function parseLaunchArgs(args) {
23535
23787
  if (filteredArgs[0] === "--smoke-test-directory") {
23536
23788
  return { kind: "smoke-directory", capturePath, windowWidth, windowHeight };
23537
23789
  }
23790
+ if (filteredArgs[0] === "--smoke-test-directory-history") {
23791
+ return { kind: "smoke-directory-history", capturePath, windowWidth, windowHeight };
23792
+ }
23538
23793
  if (filteredArgs.length >= 1 && !filteredArgs[0].startsWith("--")) {
23539
23794
  const resolvedPaths = filteredArgs.map((candidate) => resolveLaunchPath(candidate, cwd));
23540
23795
  const tourSelection = classifyAuthoredTourPaths2(resolvedPaths);
@@ -24481,6 +24736,9 @@ async function sendCurrentDirectoryHistoryEntry() {
24481
24736
  canReturnToDirectory: true
24482
24737
  });
24483
24738
  refreshSessionWindowTitle();
24739
+ if (launchArguments.kind === "smoke-directory-history") {
24740
+ pollDirectoryHistoryDrilldownSmoke();
24741
+ }
24484
24742
  scheduleCaptureIfNeeded();
24485
24743
  return;
24486
24744
  }
@@ -24505,6 +24763,12 @@ async function sendCurrentDirectoryHistoryEntry() {
24505
24763
  canMutate: true
24506
24764
  });
24507
24765
  refreshSessionWindowTitle();
24766
+ if (launchArguments.kind === "smoke-directory-history" && !directoryHistorySmokeDrilldownStarted && mainWindow && !mainWindow.isDestroyed()) {
24767
+ directoryHistorySmokeDrilldownStarted = true;
24768
+ setTimeout(() => {
24769
+ void mainWindow.webContents.executeJavaScript(`document.querySelector('.dir-entry[data-is-dir="false"]')?.click()`);
24770
+ }, 250);
24771
+ }
24508
24772
  scheduleCaptureIfNeeded();
24509
24773
  }
24510
24774
  function buildDirectoryHistoryViewState(dirHistory, entry) {
@@ -25682,6 +25946,10 @@ async function sendCurrentMultiDiff() {
25682
25946
  fileInfo: document.getElementById('file-info')?.textContent,
25683
25947
  panelCount: document.querySelectorAll('.multi-pane').length,
25684
25948
  gutterCount: document.querySelectorAll('.multi-gutter').length,
25949
+ changePosition: document.getElementById('change-position')?.textContent,
25950
+ panelPositions: [...document.querySelectorAll('.multi-pane-position')].map((element) => element.textContent),
25951
+ decoratedLineCount: document.querySelectorAll('.bygone-paired-line, .bygone-one-sided-line').length,
25952
+ diffWorkerUrl: window.__BYGONE_HOST__?.diffWorkerUrl,
25685
25953
  activeDiffCount: document.querySelectorAll('.bygone-active-diff').length,
25686
25954
  adjacentEdgeCount: document.querySelectorAll('.bygone-paired-edge-left, .bygone-one-sided-edge-left').length,
25687
25955
  inlineAdjacentEdgeCount: document.querySelectorAll('.view-line span.bygone-paired-edge-left, .view-line span.bygone-one-sided-edge-left').length
@@ -25780,6 +26048,44 @@ async function compareDirectoryTestFiles() {
25780
26048
  fs7.writeFileSync(path8.join(right, "b.txt"), "right b\n", "utf8");
25781
26049
  await openDirectories([left, right], { source: { kind: "synthetic" } });
25782
26050
  }
26051
+ async function compareDirectoryHistoryTestFiles() {
26052
+ const root = fs7.mkdtempSync(path8.join(os.tmpdir(), "bygone-directory-history-smoke-"));
26053
+ const filePath = path8.join(root, "history-smoke.js");
26054
+ runGit(["init"], root);
26055
+ runGit(["config", "user.name", "Bygone Smoke Test"], root);
26056
+ runGit(["config", "user.email", "bygone-smoke@example.invalid"], root);
26057
+ fs7.writeFileSync(filePath, "const alpha = 1;\nconst stable = true;\n", "utf8");
26058
+ runGit(["add", "history-smoke.js"], root);
26059
+ runGit(["commit", "-m", "initial"], root);
26060
+ fs7.writeFileSync(filePath, "const alpha = 2;\nconst stable = false;\n", "utf8");
26061
+ runGit(["add", "history-smoke.js"], root);
26062
+ runGit(["commit", "-m", "change history smoke fixture"], root);
26063
+ await openDirectoryHistory(root, false, { skipConfirm: true, source: { kind: "synthetic" } });
26064
+ }
26065
+ function pollDirectoryHistoryDrilldownSmoke() {
26066
+ if (!mainWindow || mainWindow.isDestroyed()) {
26067
+ return;
26068
+ }
26069
+ void mainWindow.webContents.executeJavaScript(`(() => ({
26070
+ directoryHistoryDrilldown: true,
26071
+ fileInfo: document.getElementById('file-info')?.textContent,
26072
+ changePosition: document.getElementById('change-position')?.textContent,
26073
+ panelPositions: [...document.querySelectorAll('.multi-pane-position')].map((element) => element.textContent),
26074
+ decoratedLineCount: document.querySelectorAll('.bygone-paired-line, .bygone-one-sided-line').length,
26075
+ diffWorkerUrl: window.__BYGONE_HOST__?.diffWorkerUrl
26076
+ }))()`).then((snapshot) => {
26077
+ const ready = /^1 \/ [1-9]\d*$/.test(snapshot.changePosition) && snapshot.panelPositions.some((position) => /^1 \/ [1-9]\d*$/.test(position)) && snapshot.decoratedLineCount > 0;
26078
+ if (ready) {
26079
+ finalizeSmokeTest(snapshot);
26080
+ return;
26081
+ }
26082
+ setTimeout(pollDirectoryHistoryDrilldownSmoke, 100);
26083
+ }).catch((error) => {
26084
+ console.error(`Bygone directory history smoke test failed: ${getErrorMessage(error)}`);
26085
+ process.exitCode = 1;
26086
+ app.exit(1);
26087
+ });
26088
+ }
25783
26089
  async function sendCurrentDiff() {
25784
26090
  if (session.mode !== "diff") {
25785
26091
  return;
@@ -26928,6 +27234,7 @@ async function refreshSession(options = {}) {
26928
27234
  function postOrQueue(message) {
26929
27235
  installApplicationMenu();
26930
27236
  if (message && typeof message === "object" && typeof message.type === "string" && message.type.startsWith("show")) {
27237
+ persistOpenWindowState();
26931
27238
  if (!refreshInProgress) {
26932
27239
  refreshFailure = null;
26933
27240
  }
@@ -27150,7 +27457,7 @@ function shellQuote(value) {
27150
27457
  function finalizeSmokeTest(snapshot) {
27151
27458
  clearTimeout(smokeTimeout);
27152
27459
  smokeTimeout = void 0;
27153
- const passed = Boolean(snapshot && (snapshot.fileInfo === "Comparing test-file-1.js and test-file-2.js" && snapshot.file1 === "test-file-1.js" && snapshot.file2 === "test-file-2.js" && /^1 \/ \d+$/.test(snapshot.changePosition) && snapshot.pairedLineCount > 0 && snapshot.oneSidedLineCount > 0 && snapshot.inlineHighlightCount > 0 && snapshot.activeDiffCount === 0 && isVisibleBackground(snapshot.pairedLineBackground) && isVisibleBackground(snapshot.inlineHighlightBackground) && snapshot.pairedLineBackground !== snapshot.inlineHighlightBackground || snapshot.fileInfo === `Comparing ${snapshot.panelCount} files` && snapshot.panelCount >= 2 && snapshot.gutterCount === snapshot.panelCount - 1 && snapshot.activeDiffCount === 0 && snapshot.adjacentEdgeCount === 0 && snapshot.inlineAdjacentEdgeCount === 0 || snapshot.directoryRailVisible === true && snapshot.directoryRailItemCount === 2 && snapshot.directoryReturnVisible === true && snapshot.directorySidebarToggleVisible === true && snapshot.directorySidebarToggleWorked === true && snapshot.nextFileEnabled === true));
27460
+ const passed = Boolean(snapshot && (snapshot.fileInfo === "Comparing test-file-1.js and test-file-2.js" && snapshot.file1 === "test-file-1.js" && snapshot.file2 === "test-file-2.js" && /^1 \/ \d+$/.test(snapshot.changePosition) && snapshot.pairedLineCount > 0 && snapshot.oneSidedLineCount > 0 && snapshot.inlineHighlightCount > 0 && snapshot.activeDiffCount === 0 && isVisibleBackground(snapshot.pairedLineBackground) && isVisibleBackground(snapshot.inlineHighlightBackground) && snapshot.pairedLineBackground !== snapshot.inlineHighlightBackground || snapshot.fileInfo === `Comparing ${snapshot.panelCount} files` && snapshot.panelCount >= 2 && snapshot.gutterCount === snapshot.panelCount - 1 && /^1 \/ [1-9]\d*$/.test(snapshot.changePosition) && snapshot.panelPositions.some((position) => /^1 \/ [1-9]\d*$/.test(position)) && snapshot.decoratedLineCount > 0 && /\/media\/diff\.worker\.js$/.test(snapshot.diffWorkerUrl) && snapshot.activeDiffCount === 0 && snapshot.adjacentEdgeCount === 0 && snapshot.inlineAdjacentEdgeCount === 0 || snapshot.directoryRailVisible === true && snapshot.directoryRailItemCount === 2 && snapshot.directoryReturnVisible === true && snapshot.directorySidebarToggleVisible === true && snapshot.directorySidebarToggleWorked === true && snapshot.nextFileEnabled === true || snapshot.directoryHistoryDrilldown === true && snapshot.fileInfo === "Comparing 2 files" && /^1 \/ [1-9]\d*$/.test(snapshot.changePosition) && snapshot.panelPositions.some((position) => /^1 \/ [1-9]\d*$/.test(position)) && snapshot.decoratedLineCount > 0 && /\/media\/diff\.worker\.js$/.test(snapshot.diffWorkerUrl)));
27154
27461
  if (!passed) {
27155
27462
  console.error(`Bygone smoke test failed: unexpected diff DOM snapshot ${JSON.stringify(snapshot)}`);
27156
27463
  process.exitCode = 1;
@@ -3,6 +3,7 @@
3
3
  // standalone/preload.js
4
4
  var { contextBridge, ipcRenderer, webUtils } = require("electron");
5
5
  var editorWorkerUrl = new URL("../media/editor.worker.js", window.location.href).toString();
6
+ var diffWorkerUrl = new URL("../media/diff.worker.js", window.location.href).toString();
6
7
  ipcRenderer.on("bygone:host-message", (_event, payload) => {
7
8
  window.postMessage({
8
9
  __bygoneHostMessage: payload
@@ -11,6 +12,7 @@ ipcRenderer.on("bygone:host-message", (_event, payload) => {
11
12
  contextBridge.exposeInMainWorld("__BYGONE_HOST__", {
12
13
  environment: "standalone",
13
14
  editorWorkerUrl,
15
+ diffWorkerUrl,
14
16
  getPathForFile(file) {
15
17
  return webUtils.getPathForFile(file);
16
18
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davmash/bygone",
3
- "version": "0.8.6",
3
+ "version": "0.8.8",
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" aria-label="Previous file">
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">
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" aria-label="Next file">
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">
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>
@@ -72,8 +72,15 @@
72
72
  </svg>
73
73
  </button>
74
74
  </div>
75
+ <div class="change-toolbar-actions">
75
76
  <button id="toggle-word-wrap" class="change-button icon-button" type="button" title="Enable line wrapping (Alt+Z)" aria-label="Enable line wrapping" aria-pressed="false" hidden>
76
- <svg viewBox="0 0 24 24" aria-hidden="true">
77
+ <svg class="word-wrap-icon-off" viewBox="0 0 24 24" aria-hidden="true">
78
+ <path d="M4 6h13a3 3 0 0 1 0 6H9"></path>
79
+ <path d="M12 9l-3 3 3 3"></path>
80
+ <path d="M4 18h8"></path>
81
+ <path d="M5 4l14 16"></path>
82
+ </svg>
83
+ <svg class="word-wrap-icon-on" viewBox="0 0 24 24" aria-hidden="true">
77
84
  <path d="M4 6h13a3 3 0 0 1 0 6H9"></path>
78
85
  <path d="M12 9l-3 3 3 3"></path>
79
86
  <path d="M4 18h8"></path>
@@ -85,6 +92,7 @@
85
92
  <path d="M20 5v6h-6"></path>
86
93
  </svg>
87
94
  </button>
95
+ </div>
88
96
  </div>
89
97
  <div class="change-hint">F7 / Shift+F7 · Cmd/Ctrl+Shift+Up/Down to jump.</div>
90
98
  </div>
@@ -94,14 +102,14 @@
94
102
  <div id="history-left-time" class="history-time"></div>
95
103
  </div>
96
104
  <div class="history-nav">
97
- <button id="history-back" class="history-button icon-button" type="button" title="Open older commit (Alt+Left)" aria-label="Older commit">
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">
98
106
  <svg viewBox="0 0 24 24" aria-hidden="true">
99
107
  <path d="M15 18l-6-6 6-6"></path>
100
108
  <path d="M10 18l-6-6 6-6" opacity="0.7"></path>
101
109
  </svg>
102
110
  </button>
103
111
  <div id="history-position" class="history-position" role="status" aria-live="polite" aria-atomic="true"></div>
104
- <button id="history-forward" class="history-button icon-button" type="button" title="Open newer commit (Alt+Right)" aria-label="Newer commit">
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">
105
113
  <svg viewBox="0 0 24 24" aria-hidden="true">
106
114
  <path d="M9 18l6-6-6-6"></path>
107
115
  <path d="M14 18l6-6-6-6" opacity="0.7"></path>