@davmash/bygone 0.8.5 → 0.8.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3811,6 +3811,9 @@ var require_changeTour = __commonJS({
3811
3811
  }
3812
3812
  requireString(value.title, "title");
3813
3813
  requireString(value.generatedAt, "generatedAt");
3814
+ if (value.windowTitle !== void 0) {
3815
+ requireString(value.windowTitle, "windowTitle");
3816
+ }
3814
3817
  if (value.sourceUrl !== void 0) {
3815
3818
  requireString(value.sourceUrl, "sourceUrl");
3816
3819
  }
@@ -4103,8 +4106,9 @@ var require_changeTour = __commonJS({
4103
4106
  if (!isRecord22(value) || value.version !== CHANGE_TOUR_SOURCE_VERSION) {
4104
4107
  throw new Error("Unsupported or missing change-tour source version.");
4105
4108
  }
4106
- requireOnlyKeys(value, ["version", "title", "sourceUrl", "range", "anchors", "connections", "chapters", "coverage"], "source");
4109
+ requireOnlyKeys(value, ["version", "title", "windowTitle", "sourceUrl", "range", "anchors", "connections", "chapters", "coverage"], "source");
4107
4110
  optionalString(value.title, "title");
4111
+ optionalString(value.windowTitle, "windowTitle");
4108
4112
  optionalString(value.sourceUrl, "sourceUrl");
4109
4113
  if (value.range !== void 0) {
4110
4114
  if (!isRecord22(value.range)) throw new Error("range must be an object.");
@@ -4947,7 +4951,7 @@ var require_changeTour = __commonJS({
4947
4951
  packaging: { id: "packaging", title: "Packaging and dependencies", priority: 4 }
4948
4952
  };
4949
4953
  function buildChangeTourManifest(startPath, options = {}) {
4950
- var _a, _b, _c, _d;
4954
+ var _a, _b, _c, _d, _e;
4951
4955
  const range = resolveBranchReviewRange3(startPath, options.headRef, options.baseRef);
4952
4956
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_TOUR_FILE_BYTES;
4953
4957
  const maxLineBytes = options.maxLineBytes ?? DEFAULT_MAX_TOUR_LINE_BYTES;
@@ -5019,7 +5023,8 @@ var require_changeTour = __commonJS({
5019
5023
  const manifest = {
5020
5024
  version: CHANGE_TOUR_MANIFEST_VERSION,
5021
5025
  title: ((_a = options.source) == null ? void 0 : _a.title) || ((_b = options.story) == null ? void 0 : _b.title) || options.title || `${range.headRef} against ${range.baseRef}`,
5022
- sourceUrl: ((_c = options.source) == null ? void 0 : _c.sourceUrl) || ((_d = options.story) == null ? void 0 : _d.sourceUrl) || options.sourceUrl,
5026
+ windowTitle: (_c = options.source) == null ? void 0 : _c.windowTitle,
5027
+ sourceUrl: ((_d = options.source) == null ? void 0 : _d.sourceUrl) || ((_e = options.story) == null ? void 0 : _e.sourceUrl) || options.sourceUrl,
5023
5028
  generatedAt: options.generatedAt || (/* @__PURE__ */ new Date()).toISOString(),
5024
5029
  range: {
5025
5030
  baseRef: range.baseRef,
@@ -5466,6 +5471,140 @@ var require_changeTour = __commonJS({
5466
5471
  }
5467
5472
  });
5468
5473
 
5474
+ // out/windowTitle.js
5475
+ var require_windowTitle = __commonJS({
5476
+ "out/windowTitle.js"(exports2, module2) {
5477
+ "use strict";
5478
+ var __defProp2 = Object.defineProperty;
5479
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
5480
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
5481
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
5482
+ var __export2 = (target, all) => {
5483
+ for (var name in all)
5484
+ __defProp2(target, name, { get: all[name], enumerable: true });
5485
+ };
5486
+ var __copyProps2 = (to, from, except, desc) => {
5487
+ if (from && typeof from === "object" || typeof from === "function") {
5488
+ for (let key of __getOwnPropNames2(from))
5489
+ if (!__hasOwnProp2.call(to, key) && key !== except)
5490
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
5491
+ }
5492
+ return to;
5493
+ };
5494
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
5495
+ var windowTitle_exports2 = {};
5496
+ __export2(windowTitle_exports2, {
5497
+ MAX_WINDOW_TITLE_LENGTH: () => MAX_WINDOW_TITLE_LENGTH2,
5498
+ buildHistoryTitle: () => buildHistoryTitle3,
5499
+ buildHistoryTitleFromViewState: () => buildHistoryTitleFromViewState2,
5500
+ buildMultiPanelTitle: () => buildMultiPanelTitle2,
5501
+ buildStandaloneSessionTitle: () => buildStandaloneSessionTitle3,
5502
+ buildTourWindowTitle: () => buildTourWindowTitle3,
5503
+ buildTwoWayComparisonTitle: () => buildTwoWayComparisonTitle2,
5504
+ formatComparisonPair: () => formatComparisonPair2,
5505
+ truncateTitle: () => truncateTitle2
5506
+ });
5507
+ module2.exports = __toCommonJS2(windowTitle_exports2);
5508
+ var MAX_WINDOW_TITLE_LENGTH2 = 120;
5509
+ function truncateTitle2(text, max = MAX_WINDOW_TITLE_LENGTH2) {
5510
+ if (text.length <= max) {
5511
+ return text;
5512
+ }
5513
+ return `${text.slice(0, max - 1)}\u2026`;
5514
+ }
5515
+ function formatComparisonPair2(left, right) {
5516
+ return `${left} \u2194 ${right}`;
5517
+ }
5518
+ function buildMultiPanelTitle2(input) {
5519
+ const { panels, activePanelId, activePairIndex, panelIds } = input;
5520
+ if (panels.length === 0) {
5521
+ return "Multi-Panel Compare";
5522
+ }
5523
+ if (panels.length === 1) {
5524
+ return truncateTitle2(panels[0].label);
5525
+ }
5526
+ if (panels.length === 2) {
5527
+ return truncateTitle2(formatComparisonPair2(panels[0].label, panels[1].label));
5528
+ }
5529
+ const ids = panelIds ?? panels.map((_panel, index) => String(index));
5530
+ const activeIndex = activePanelId ? ids.findIndex((id) => id === activePanelId) : -1;
5531
+ if (activePairIndex !== null && activePairIndex !== void 0 && activePairIndex >= 0 && activePairIndex < panels.length - 1) {
5532
+ return truncateTitle2(formatComparisonPair2(
5533
+ panels[activePairIndex].label,
5534
+ panels[activePairIndex + 1].label
5535
+ ));
5536
+ }
5537
+ if (activeIndex >= 0) {
5538
+ const derivedPairIndex = activeIndex < panels.length - 1 ? activeIndex : Math.max(0, activeIndex - 1);
5539
+ return truncateTitle2(formatComparisonPair2(
5540
+ panels[derivedPairIndex].label,
5541
+ panels[derivedPairIndex + 1].label
5542
+ ));
5543
+ }
5544
+ return truncateTitle2(formatComparisonPair2(panels[0].label, panels[panels.length - 1].label));
5545
+ }
5546
+ function buildHistoryTitle3(fileName, options) {
5547
+ const parts = [`${fileName} History`];
5548
+ if (options == null ? void 0 : options.shortCommit) {
5549
+ parts.push(options.shortCommit);
5550
+ }
5551
+ if (options == null ? void 0 : options.positionLabel) {
5552
+ parts.push(`(${options.positionLabel})`);
5553
+ }
5554
+ return truncateTitle2(parts.join(" \u2014 "));
5555
+ }
5556
+ function buildHistoryTitleFromViewState2(fileName, history) {
5557
+ const shortCommit = history.rightCommitLabel.split(/\s+/)[0] || void 0;
5558
+ return buildHistoryTitle3(fileName, {
5559
+ shortCommit,
5560
+ positionLabel: history.positionLabel
5561
+ });
5562
+ }
5563
+ function buildTwoWayComparisonTitle2(left, right) {
5564
+ return truncateTitle2(formatComparisonPair2(left, right));
5565
+ }
5566
+ function buildTourWindowTitle3(manifest, appName = "Bygone") {
5567
+ var _a, _b;
5568
+ const label = ((_a = manifest == null ? void 0 : manifest.windowTitle) == null ? void 0 : _a.trim()) || ((_b = manifest == null ? void 0 : manifest.title) == null ? void 0 : _b.trim());
5569
+ return label ? truncateTitle2(label) : `${appName} Tour`;
5570
+ }
5571
+ function buildStandaloneSessionTitle3(session2) {
5572
+ var _a, _b, _c, _d;
5573
+ if (session2.mode === "multi-diff" && session2.multi) {
5574
+ return buildMultiPanelTitle2({
5575
+ panels: session2.multi.files,
5576
+ activePanelId: session2.multi.activePanelId,
5577
+ activePairIndex: session2.multi.activePairIndex,
5578
+ panelIds: session2.multi.files.map((file) => file.id)
5579
+ });
5580
+ }
5581
+ if (session2.mode === "history" && ((_a = session2.history) == null ? void 0 : _a.filePath)) {
5582
+ const entry = (_b = session2.history.entries) == null ? void 0 : _b[session2.history.index ?? 0];
5583
+ return buildHistoryTitle3(
5584
+ session2.history.filePath.split("/").pop() || "File",
5585
+ { shortCommit: entry == null ? void 0 : entry.shortCommit }
5586
+ );
5587
+ }
5588
+ if (session2.mode === "directory-history" && session2.dirHistory) {
5589
+ const name = session2.dirHistory.displayName || session2.dirHistory.viewRelativePath || "Directory";
5590
+ return truncateTitle2(`${name} Directory History`);
5591
+ }
5592
+ if (session2.mode === "directory" && session2.directory) {
5593
+ if ((_c = session2.directory.review) == null ? void 0 : _c.headRef) {
5594
+ return truncateTitle2(`${session2.directory.review.headRef} Branch Change`);
5595
+ }
5596
+ if ((_d = session2.directory.labels) == null ? void 0 : _d.length) {
5597
+ return truncateTitle2(session2.directory.labels.join(" \u2194 "));
5598
+ }
5599
+ }
5600
+ if (session2.left && session2.right) {
5601
+ return buildTwoWayComparisonTitle2(session2.left.label, session2.right.label);
5602
+ }
5603
+ return "Compare";
5604
+ }
5605
+ }
5606
+ });
5607
+
5469
5608
  // node_modules/js-yaml/lib/common.js
5470
5609
  var require_common = __commonJS({
5471
5610
  "node_modules/js-yaml/lib/common.js"(exports2, module2) {
@@ -8594,6 +8733,7 @@ var require_present = __commonJS({
8594
8733
  var path9 = require("path");
8595
8734
  var { spawn: spawn2 } = require("child_process");
8596
8735
  var { buildChangeTourManifest, parseChangeTourStory } = require_changeTour();
8736
+ var { buildTourWindowTitle: buildTourWindowTitle3 } = require_windowTitle();
8597
8737
  var { tokenMatches: tokenMatches2 } = require_commandSpec();
8598
8738
  var { loadTourSource } = require_tourFile();
8599
8739
  var MIME_TYPES = /* @__PURE__ */ new Map([
@@ -8627,8 +8767,25 @@ var require_present = __commonJS({
8627
8767
  process.stdout.write(`Wrote change-tour manifest to ${resolvedOutput}
8628
8768
  `);
8629
8769
  }
8770
+ const tourWindowTitle = buildTourWindowTitle3(manifest, "Bygone");
8771
+ const presenterIndexPath = path9.join(packageRoot2, "web", "index.html");
8772
+ let presenterIndexTemplate;
8630
8773
  const server = createServer((request, response) => {
8631
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
+ }
8632
8789
  if (requestUrl.pathname === "/tour.json") {
8633
8790
  response.writeHead(200, {
8634
8791
  "Content-Type": "application/json; charset=utf-8",
@@ -8637,6 +8794,21 @@ var require_present = __commonJS({
8637
8794
  response.end(serializedManifest);
8638
8795
  return;
8639
8796
  }
8797
+ if (requestUrl.pathname === "/" || requestUrl.pathname === "/index.html") {
8798
+ if (!presenterIndexTemplate) {
8799
+ presenterIndexTemplate = readFileSync5(presenterIndexPath, "utf8");
8800
+ }
8801
+ const html = presenterIndexTemplate.replace(
8802
+ /<title>Bygone Tour<\/title>/,
8803
+ `<title>${escapeHtml(tourWindowTitle)}</title>`
8804
+ );
8805
+ response.writeHead(200, {
8806
+ "Content-Type": "text/html; charset=utf-8",
8807
+ "Cache-Control": "no-store"
8808
+ });
8809
+ response.end(html);
8810
+ return;
8811
+ }
8640
8812
  const targetPath = resolveAssetPath(packageRoot2, requestUrl.pathname);
8641
8813
  if (!targetPath) {
8642
8814
  respond(response, 404, "Not found");
@@ -8707,7 +8879,7 @@ var require_present = __commonJS({
8707
8879
  }
8708
8880
  function resolveAssetPath(packageRoot2, requestPath) {
8709
8881
  if (requestPath === "/" || requestPath === "/index.html") {
8710
- return path9.join(packageRoot2, "web", "index.html");
8882
+ return null;
8711
8883
  }
8712
8884
  if (!requestPath.startsWith("/web/") && !requestPath.startsWith("/media/")) {
8713
8885
  return null;
@@ -8739,9 +8911,24 @@ var require_present = __commonJS({
8739
8911
  }
8740
8912
  response.end(message);
8741
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
+ }
8925
+ function escapeHtml(text) {
8926
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
8927
+ }
8742
8928
  module2.exports = {
8743
8929
  parsePresentArgs,
8744
- startPresentation: startPresentation2
8930
+ startPresentation: startPresentation2,
8931
+ escapeHtml
8745
8932
  };
8746
8933
  }
8747
8934
  });
@@ -8789,6 +8976,231 @@ var require_launchArgs = __commonJS({
8789
8976
  }
8790
8977
  });
8791
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
+
9089
+ // src/windowTitle.ts
9090
+ var windowTitle_exports = {};
9091
+ __export(windowTitle_exports, {
9092
+ MAX_WINDOW_TITLE_LENGTH: () => MAX_WINDOW_TITLE_LENGTH,
9093
+ buildHistoryTitle: () => buildHistoryTitle,
9094
+ buildHistoryTitleFromViewState: () => buildHistoryTitleFromViewState,
9095
+ buildMultiPanelTitle: () => buildMultiPanelTitle,
9096
+ buildStandaloneSessionTitle: () => buildStandaloneSessionTitle,
9097
+ buildTourWindowTitle: () => buildTourWindowTitle,
9098
+ buildTwoWayComparisonTitle: () => buildTwoWayComparisonTitle,
9099
+ formatComparisonPair: () => formatComparisonPair,
9100
+ truncateTitle: () => truncateTitle
9101
+ });
9102
+ function truncateTitle(text, max = MAX_WINDOW_TITLE_LENGTH) {
9103
+ if (text.length <= max) {
9104
+ return text;
9105
+ }
9106
+ return `${text.slice(0, max - 1)}\u2026`;
9107
+ }
9108
+ function formatComparisonPair(left, right) {
9109
+ return `${left} \u2194 ${right}`;
9110
+ }
9111
+ function buildMultiPanelTitle(input) {
9112
+ const { panels, activePanelId, activePairIndex, panelIds } = input;
9113
+ if (panels.length === 0) {
9114
+ return "Multi-Panel Compare";
9115
+ }
9116
+ if (panels.length === 1) {
9117
+ return truncateTitle(panels[0].label);
9118
+ }
9119
+ if (panels.length === 2) {
9120
+ return truncateTitle(formatComparisonPair(panels[0].label, panels[1].label));
9121
+ }
9122
+ const ids = panelIds ?? panels.map((_panel, index) => String(index));
9123
+ const activeIndex = activePanelId ? ids.findIndex((id) => id === activePanelId) : -1;
9124
+ if (activePairIndex !== null && activePairIndex !== void 0 && activePairIndex >= 0 && activePairIndex < panels.length - 1) {
9125
+ return truncateTitle(formatComparisonPair(
9126
+ panels[activePairIndex].label,
9127
+ panels[activePairIndex + 1].label
9128
+ ));
9129
+ }
9130
+ if (activeIndex >= 0) {
9131
+ const derivedPairIndex = activeIndex < panels.length - 1 ? activeIndex : Math.max(0, activeIndex - 1);
9132
+ return truncateTitle(formatComparisonPair(
9133
+ panels[derivedPairIndex].label,
9134
+ panels[derivedPairIndex + 1].label
9135
+ ));
9136
+ }
9137
+ return truncateTitle(formatComparisonPair(panels[0].label, panels[panels.length - 1].label));
9138
+ }
9139
+ function buildHistoryTitle(fileName, options) {
9140
+ const parts = [`${fileName} History`];
9141
+ if (options?.shortCommit) {
9142
+ parts.push(options.shortCommit);
9143
+ }
9144
+ if (options?.positionLabel) {
9145
+ parts.push(`(${options.positionLabel})`);
9146
+ }
9147
+ return truncateTitle(parts.join(" \u2014 "));
9148
+ }
9149
+ function buildHistoryTitleFromViewState(fileName, history) {
9150
+ const shortCommit = history.rightCommitLabel.split(/\s+/)[0] || void 0;
9151
+ return buildHistoryTitle(fileName, {
9152
+ shortCommit,
9153
+ positionLabel: history.positionLabel
9154
+ });
9155
+ }
9156
+ function buildTwoWayComparisonTitle(left, right) {
9157
+ return truncateTitle(formatComparisonPair(left, right));
9158
+ }
9159
+ function buildTourWindowTitle(manifest, appName = "Bygone") {
9160
+ const label = manifest?.windowTitle?.trim() || manifest?.title?.trim();
9161
+ return label ? truncateTitle(label) : `${appName} Tour`;
9162
+ }
9163
+ function buildStandaloneSessionTitle(session2) {
9164
+ if (session2.mode === "multi-diff" && session2.multi) {
9165
+ return buildMultiPanelTitle({
9166
+ panels: session2.multi.files,
9167
+ activePanelId: session2.multi.activePanelId,
9168
+ activePairIndex: session2.multi.activePairIndex,
9169
+ panelIds: session2.multi.files.map((file) => file.id)
9170
+ });
9171
+ }
9172
+ if (session2.mode === "history" && session2.history?.filePath) {
9173
+ const entry = session2.history.entries?.[session2.history.index ?? 0];
9174
+ return buildHistoryTitle(
9175
+ session2.history.filePath.split("/").pop() || "File",
9176
+ { shortCommit: entry?.shortCommit }
9177
+ );
9178
+ }
9179
+ if (session2.mode === "directory-history" && session2.dirHistory) {
9180
+ const name = session2.dirHistory.displayName || session2.dirHistory.viewRelativePath || "Directory";
9181
+ return truncateTitle(`${name} Directory History`);
9182
+ }
9183
+ if (session2.mode === "directory" && session2.directory) {
9184
+ if (session2.directory.review?.headRef) {
9185
+ return truncateTitle(`${session2.directory.review.headRef} Branch Change`);
9186
+ }
9187
+ if (session2.directory.labels?.length) {
9188
+ return truncateTitle(session2.directory.labels.join(" \u2194 "));
9189
+ }
9190
+ }
9191
+ if (session2.left && session2.right) {
9192
+ return buildTwoWayComparisonTitle(session2.left.label, session2.right.label);
9193
+ }
9194
+ return "Compare";
9195
+ }
9196
+ var MAX_WINDOW_TITLE_LENGTH;
9197
+ var init_windowTitle = __esm({
9198
+ "src/windowTitle.ts"() {
9199
+ "use strict";
9200
+ MAX_WINDOW_TITLE_LENGTH = 120;
9201
+ }
9202
+ });
9203
+
8792
9204
  // package.json
8793
9205
  var require_package = __commonJS({
8794
9206
  "package.json"(exports2, module2) {
@@ -8798,7 +9210,7 @@ var require_package = __commonJS({
8798
9210
  displayName: "Bygone",
8799
9211
  desktopName: "com.davidmashburn.bygone.desktop",
8800
9212
  description: "See how change happened. Visual diff and file history for VS Code.",
8801
- version: "0.8.5",
9213
+ version: "0.8.7",
8802
9214
  author: "David Mashburn",
8803
9215
  license: "MIT",
8804
9216
  private: false,
@@ -22047,6 +22459,12 @@ var { tokenMatches, tokensFor } = require_commandSpec();
22047
22459
  var { startPresentation } = require_present();
22048
22460
  var { resolveWorkingDirectory } = require_workingDirectory();
22049
22461
  var { getCliArgsFromArgv, getForwardedLaunchArgs } = require_launchArgs();
22462
+ var { readWindowState, writeWindowState } = require_windowState();
22463
+ var {
22464
+ buildHistoryTitle: buildHistoryTitle2,
22465
+ buildStandaloneSessionTitle: buildStandaloneSessionTitle2,
22466
+ buildTourWindowTitle: buildTourWindowTitle2
22467
+ } = (init_windowTitle(), __toCommonJS(windowTitle_exports));
22050
22468
  var APP_NAME = "Bygone";
22051
22469
  var APP_VERSION = require_package().version;
22052
22470
  var HELP_URL = "https://github.com/davidmashburn/bygone";
@@ -22061,13 +22479,15 @@ var parsedDirectoryHistoryCacheSize = Number.parseInt(process.env.BYGONE_DIR_HIS
22061
22479
  var DIRECTORY_HISTORY_CACHE_SIZE = Number.isFinite(parsedDirectoryHistoryCacheSize) && parsedDirectoryHistoryCacheSize > 0 ? parsedDirectoryHistoryCacheSize : DEFAULT_DIRECTORY_HISTORY_CACHE_SIZE;
22062
22480
  var commandLineToolPath = process.platform === "win32" ? path8.join(process.env.LOCALAPPDATA || os.homedir(), "Microsoft", "WindowsApps", "bygone.cmd") : "/usr/local/bin/bygone";
22063
22481
  var gitHistoryService = new GitHistoryService2();
22064
- var launchArguments = parseLaunchArgs(getCliArgs());
22482
+ var initialCliArgs = getCliArgs();
22483
+ var launchArguments = parseLaunchArgs(initialCliArgs);
22065
22484
  var smokeTestMode = launchArguments.kind === "smoke" || launchArguments.kind === "smoke-multi" || launchArguments.kind === "smoke-directory";
22066
22485
  var captureOutputPath = launchArguments.capturePath ? path8.resolve(launchArguments.capturePath) : null;
22067
22486
  var captureMode = Boolean(captureOutputPath);
22068
22487
  var launchWindowWidth = Number.isFinite(launchArguments.windowWidth) ? launchArguments.windowWidth : 1500;
22069
22488
  var launchWindowHeight = Number.isFinite(launchArguments.windowHeight) ? launchArguments.windowHeight : 960;
22070
22489
  var shouldUseSingleInstanceLock = app.isPackaged && !smokeTestMode && !captureMode;
22490
+ var shouldRestoreWindowState = initialCliArgs.length === 0 && !smokeTestMode && !captureMode;
22071
22491
  app.setName(APP_NAME);
22072
22492
  if (typeof app.setAppUserModelId === "function") {
22073
22493
  app.setAppUserModelId("com.davidmashburn.bygone");
@@ -22101,6 +22521,8 @@ var wordWrapEnabled = false;
22101
22521
  var wordWrapAvailable = false;
22102
22522
  var repositorySearch = null;
22103
22523
  var lastRepositoryReplacement = null;
22524
+ var appIsQuitting = false;
22525
+ var restoringWindowState = false;
22104
22526
  if (!singleInstanceLock) {
22105
22527
  app.quit();
22106
22528
  }
@@ -22117,6 +22539,10 @@ app.on("window-all-closed", () => {
22117
22539
  app.quit();
22118
22540
  }
22119
22541
  });
22542
+ app.on("before-quit", () => {
22543
+ appIsQuitting = true;
22544
+ persistOpenWindowState();
22545
+ });
22120
22546
  app.on("will-quit", () => {
22121
22547
  repositorySearch?.handle?.cancel();
22122
22548
  for (const server of [...activeTourServers]) {
@@ -22266,6 +22692,9 @@ function createMainWindow({ show = !smokeTestMode } = {}) {
22266
22692
  smokeTimeout = void 0;
22267
22693
  captureScheduled = false;
22268
22694
  captureRenderReady = false;
22695
+ if (!appIsQuitting && (process.platform === "darwin" || BrowserWindow.getAllWindows().length > 0)) {
22696
+ persistOpenWindowState();
22697
+ }
22269
22698
  });
22270
22699
  }
22271
22700
  function ensureMainWindow() {
@@ -22275,13 +22704,18 @@ function ensureMainWindow() {
22275
22704
  }
22276
22705
  }
22277
22706
  async function openTourPresentation(args, cwd) {
22707
+ let ownerWindow = null;
22278
22708
  const presentation = await startPresentation(args, cwd, packageRoot, {
22279
22709
  announce: false,
22280
- open: false
22710
+ open: false,
22711
+ onNarrationClaim: () => claimTourNarration(ownerWindow)
22281
22712
  });
22282
22713
  activeTourServers.add(presentation.server);
22283
22714
  try {
22284
- await showTourWindow(presentation.url, presentation.server);
22715
+ ownerWindow = await showTourWindow(presentation.url, presentation.server, presentation.manifest, {
22716
+ args: [...args],
22717
+ cwd: path8.resolve(cwd)
22718
+ });
22285
22719
  } catch (error) {
22286
22720
  closeTourServer(presentation.server);
22287
22721
  throw error;
@@ -22291,7 +22725,7 @@ async function openAuthoredTourDocument(sourcePath) {
22291
22725
  const document2 = discoverAuthoredTourDocument2(sourcePath);
22292
22726
  await openTourPresentation(["--tour", document2.documentPath], document2.repoRoot);
22293
22727
  }
22294
- async function showTourWindow(url, server) {
22728
+ async function showTourWindow(url, server, manifest, launch) {
22295
22729
  const parsedUrl = new URL(url);
22296
22730
  if (parsedUrl.protocol !== "http:" || parsedUrl.hostname !== "127.0.0.1") {
22297
22731
  throw new Error("Tour presenter URL must use the local Bygone server.");
@@ -22303,7 +22737,7 @@ async function showTourWindow(url, server) {
22303
22737
  minWidth: 960,
22304
22738
  minHeight: 640,
22305
22739
  show: false,
22306
- title: `${APP_NAME} Tour`,
22740
+ title: buildTourWindowTitle2(manifest, APP_NAME),
22307
22741
  webPreferences: {
22308
22742
  sandbox: true,
22309
22743
  contextIsolation: true,
@@ -22313,7 +22747,8 @@ async function showTourWindow(url, server) {
22313
22747
  }
22314
22748
  });
22315
22749
  latestTourWindow = tourWindow;
22316
- tourPresentations.set(tourWindow, { server, origin: tourOrigin });
22750
+ tourPresentations.set(tourWindow, { server, origin: tourOrigin, launch });
22751
+ installApplicationMenu();
22317
22752
  tourWindow.webContents.setWindowOpenHandler(({ url: externalUrl }) => {
22318
22753
  if (/^https?:\/\//i.test(externalUrl)) {
22319
22754
  void shell.openExternal(externalUrl);
@@ -22340,20 +22775,55 @@ async function showTourWindow(url, server) {
22340
22775
  if (latestTourWindow === tourWindow) {
22341
22776
  latestTourWindow = getLatestTourWindow();
22342
22777
  }
22778
+ installApplicationMenu();
22779
+ if (!appIsQuitting && (process.platform === "darwin" || BrowserWindow.getAllWindows().length > 0)) {
22780
+ persistOpenWindowState();
22781
+ }
22782
+ });
22783
+ const tourWindowTitle = buildTourWindowTitle2(manifest, APP_NAME);
22784
+ const applyTourWindowTitle = () => {
22785
+ if (!tourWindow.isDestroyed()) {
22786
+ tourWindow.setTitle(tourWindowTitle);
22787
+ }
22788
+ };
22789
+ tourWindow.webContents.on("page-title-updated", (event) => {
22790
+ event.preventDefault();
22791
+ queueMicrotask(applyTourWindowTitle);
22343
22792
  });
22793
+ tourWindow.webContents.on("did-finish-load", applyTourWindowTitle);
22344
22794
  try {
22345
22795
  await tourWindow.loadURL(url);
22346
22796
  } catch (error) {
22347
22797
  tourWindow.destroy();
22348
22798
  throw error;
22349
22799
  }
22800
+ applyTourWindowTitle();
22350
22801
  tourWindow.show();
22351
22802
  tourWindow.focus();
22803
+ persistOpenWindowState();
22352
22804
  return tourWindow;
22353
22805
  }
22354
22806
  function getLatestTourWindow() {
22355
22807
  return [...tourPresentations.keys()].reverse().find((window2) => !window2.isDestroyed());
22356
22808
  }
22809
+ function claimTourNarration(ownerWindow) {
22810
+ if (!ownerWindow || ownerWindow.isDestroyed()) return;
22811
+ for (const tourWindow of tourPresentations.keys()) {
22812
+ if (tourWindow !== ownerWindow && !tourWindow.isDestroyed()) {
22813
+ dispatchTourPresenterCommand(tourWindow, "pauseNarration");
22814
+ }
22815
+ }
22816
+ }
22817
+ function dispatchTourPresenterCommand(tourWindow, command) {
22818
+ if (!tourWindow || tourWindow.isDestroyed()) return;
22819
+ const script = `window.dispatchEvent(new CustomEvent('bygone:tour-command', { detail: ${JSON.stringify(command)} }))`;
22820
+ void tourWindow.webContents.executeJavaScript(script).catch(() => {
22821
+ });
22822
+ }
22823
+ function toggleLatestTourNarration() {
22824
+ const tourWindow = getLatestTourWindow();
22825
+ if (tourWindow) dispatchTourPresenterCommand(tourWindow, "toggleNarration");
22826
+ }
22357
22827
  function closeTourServer(server) {
22358
22828
  if (!server || !activeTourServers.delete(server)) {
22359
22829
  return;
@@ -22668,6 +23138,12 @@ ${HELP_URL}`,
22668
23138
  click: () => {
22669
23139
  void openAuthoredTourDialog().catch((error) => showError(`Could not open Bygone presentation: ${getErrorMessage(error)}`));
22670
23140
  }
23141
+ },
23142
+ { type: "separator" },
23143
+ {
23144
+ label: "Listen to Tour",
23145
+ enabled: Boolean(getLatestTourWindow()),
23146
+ click: toggleLatestTourNarration
22671
23147
  }
22672
23148
  ]
22673
23149
  },
@@ -23026,8 +23502,73 @@ async function openInitialLaunchTarget() {
23026
23502
  await routePendingOpenPaths();
23027
23503
  return;
23028
23504
  }
23505
+ if (shouldRestoreWindowState && await restoreOpenWindowState()) {
23506
+ return;
23507
+ }
23029
23508
  await routeLaunchTarget(launchArguments);
23030
23509
  }
23510
+ async function restoreOpenWindowState() {
23511
+ const savedState = readWindowState(getWindowStatePath());
23512
+ if (!savedState || !savedState.main && savedState.tours.length === 0) {
23513
+ return false;
23514
+ }
23515
+ let restoredAnyWindow = false;
23516
+ restoringWindowState = true;
23517
+ try {
23518
+ if (savedState.main) {
23519
+ ensureMainWindow();
23520
+ try {
23521
+ await restoreMainSession(savedState.main.source);
23522
+ } catch (error) {
23523
+ console.error(`Could not restore the previous Bygone session: ${getErrorMessage(error)}`);
23524
+ await openBlankDiff();
23525
+ }
23526
+ restoredAnyWindow = true;
23527
+ }
23528
+ for (const tour of savedState.tours) {
23529
+ try {
23530
+ await openTourPresentation(tour.args, tour.cwd);
23531
+ restoredAnyWindow = true;
23532
+ } catch (error) {
23533
+ console.error(`Could not restore a previous Bygone tour: ${getErrorMessage(error)}`);
23534
+ }
23535
+ }
23536
+ } finally {
23537
+ restoringWindowState = false;
23538
+ persistOpenWindowState();
23539
+ }
23540
+ return restoredAnyWindow;
23541
+ }
23542
+ async function restoreMainSession(source) {
23543
+ if (source.kind === "blank") {
23544
+ await openBlankDiff();
23545
+ return;
23546
+ }
23547
+ session = buildSessionFromSource(source);
23548
+ historyIncludeStagedPreference = Boolean(source.includeStaged ?? historyIncludeStagedPreference);
23549
+ historySkipUnchangedPreference = Boolean(source.skipUnchanged ?? historySkipUnchangedPreference);
23550
+ clearWatchers();
23551
+ updateWatchers();
23552
+ await sendCurrentSession();
23553
+ }
23554
+ function persistOpenWindowState() {
23555
+ if (restoringWindowState || !app.isReady()) return;
23556
+ const hasMainWindow = Boolean(mainWindow && !mainWindow.isDestroyed());
23557
+ const mainSource = hasMainWindow && (isRefreshableSource(session.source) || session.source?.kind === "blank") ? cloneSessionSource(session.source) : hasMainWindow ? { kind: "blank" } : null;
23558
+ const tours = [...tourPresentations.entries()].filter(([window2, presentation]) => !window2.isDestroyed() && presentation.launch).map(([, presentation]) => presentation.launch);
23559
+ try {
23560
+ writeWindowState(getWindowStatePath(), {
23561
+ version: 1,
23562
+ main: mainSource ? { source: mainSource } : null,
23563
+ tours
23564
+ });
23565
+ } catch (error) {
23566
+ console.error(`Could not save Bygone window state: ${getErrorMessage(error)}`);
23567
+ }
23568
+ }
23569
+ function getWindowStatePath() {
23570
+ return path8.join(app.getPath("userData"), "open-windows.json");
23571
+ }
23031
23572
  async function routeLaunchTarget(launchTarget) {
23032
23573
  if (launchTarget.kind === "unsupported-launch-intent") {
23033
23574
  ensureMainWindow();
@@ -23435,11 +23976,15 @@ async function handleRendererMessage(message) {
23435
23976
  return;
23436
23977
  }
23437
23978
  if (message.type === "multiSetActivePanel" && typeof message.panelId === "string") {
23438
- setActiveMultiPanel(message.panelId);
23979
+ if (setActiveMultiPanel(message.panelId)) {
23980
+ refreshSessionWindowTitle();
23981
+ }
23439
23982
  return;
23440
23983
  }
23441
23984
  if (message.type === "multiSetActivePair" && Number.isInteger(message.pairIndex)) {
23442
- setActiveMultiPair(message.pairIndex);
23985
+ if (setActiveMultiPair(message.pairIndex)) {
23986
+ refreshSessionWindowTitle();
23987
+ }
23443
23988
  return;
23444
23989
  }
23445
23990
  if (message.type === "multiAddPanel" && typeof message.anchorPanelId === "string" && (message.side === "left" || message.side === "right")) {
@@ -23463,7 +24008,7 @@ async function handleRendererMessage(message) {
23463
24008
  if (panel) {
23464
24009
  panel.content = message.content;
23465
24010
  panel.dirty = panel.content !== panel.savedContent;
23466
- updateWindowTitle(session.multi.files.map((file) => file.label).join(" \u2194 ") || "Multi-Panel Compare");
24011
+ refreshSessionWindowTitle();
23467
24012
  }
23468
24013
  return;
23469
24014
  }
@@ -24117,7 +24662,7 @@ async function sendCurrentDirectoryDiff() {
24117
24662
  canMutate: !review && session.source?.kind === "directories",
24118
24663
  review: buildReviewViewState(review)
24119
24664
  });
24120
- updateWindowTitle(review ? `${review.headRef} Branch Change` : session.directory.labels.join(" \u2194 "));
24665
+ refreshSessionWindowTitle();
24121
24666
  if (launchArguments.kind === "smoke-directory" && mainWindow && !mainWindow.isDestroyed()) {
24122
24667
  setTimeout(() => {
24123
24668
  void mainWindow.webContents.executeJavaScript(`document.querySelector('.dir-entry[data-is-dir="false"]')?.click()`);
@@ -24182,7 +24727,7 @@ async function sendCurrentDirectoryHistoryEntry() {
24182
24727
  fileNavigation: buildDirectoryHistoryFileNavigationState(session.dirHistory, entry),
24183
24728
  canReturnToDirectory: true
24184
24729
  });
24185
- updateWindowTitle(`${relativePath} Directory History`);
24730
+ refreshSessionWindowTitle();
24186
24731
  scheduleCaptureIfNeeded();
24187
24732
  return;
24188
24733
  }
@@ -24206,7 +24751,7 @@ async function sendCurrentDirectoryHistoryEntry() {
24206
24751
  history,
24207
24752
  canMutate: true
24208
24753
  });
24209
- updateWindowTitle(`${session.dirHistory.displayName} Directory History`);
24754
+ refreshSessionWindowTitle();
24210
24755
  scheduleCaptureIfNeeded();
24211
24756
  }
24212
24757
  function buildDirectoryHistoryViewState(dirHistory, entry) {
@@ -25377,7 +25922,7 @@ async function sendCurrentMultiDiff() {
25377
25922
  fileNavigation: buildStandaloneFileNavigationState(),
25378
25923
  mutationEnabled: !isDirectoryDrilldown
25379
25924
  });
25380
- updateWindowTitle(panels.map((panel) => panel.label).join(" \u2194 ") || "Multi-Panel Compare");
25925
+ refreshSessionWindowTitle();
25381
25926
  if (mainWindow && !mainWindow.isDestroyed()) {
25382
25927
  setTimeout(() => {
25383
25928
  void mainWindow.webContents.executeJavaScript(`(() => ({
@@ -25495,7 +26040,7 @@ async function sendCurrentDiff() {
25495
26040
  directoryNavigation: buildDirectoryDrilldownNavigationState(),
25496
26041
  canReturnToDirectory: Boolean(session.returnDirectory)
25497
26042
  });
25498
- updateWindowTitle(`${session.left.label} \u2194 ${session.right.label}`);
26043
+ refreshSessionWindowTitle();
25499
26044
  scheduleCaptureIfNeeded();
25500
26045
  return;
25501
26046
  }
@@ -25523,7 +26068,7 @@ async function sendCurrentDiff() {
25523
26068
  comparisonSummary: session.returnDirectory?.comparisonSummary
25524
26069
  };
25525
26070
  postOrQueue(message);
25526
- updateWindowTitle(`${session.left.label} \u2194 ${session.right.label}`);
26071
+ refreshSessionWindowTitle();
25527
26072
  if (mainWindow && !mainWindow.isDestroyed()) {
25528
26073
  setTimeout(() => {
25529
26074
  void mainWindow.webContents.executeJavaScript(`(() => ({
@@ -25611,7 +26156,7 @@ async function sendCurrentHistoryEntry() {
25611
26156
  rail
25612
26157
  }
25613
26158
  });
25614
- updateWindowTitle(`${fileName} History`);
26159
+ refreshSessionWindowTitle();
25615
26160
  if (mainWindow && !mainWindow.isDestroyed()) {
25616
26161
  setTimeout(() => {
25617
26162
  void mainWindow.webContents.executeJavaScript(`(() => ({
@@ -25931,7 +26476,7 @@ async function saveDirtyMultiPanels() {
25931
26476
  }
25932
26477
  updateWatchers();
25933
26478
  await sendCurrentMultiDiff();
25934
- updateWindowTitle(session.multi.files.map((file) => file.label).join(" \u2194 ") || "Multi-Panel Compare");
26479
+ refreshSessionWindowTitle();
25935
26480
  return true;
25936
26481
  }
25937
26482
  async function saveMultiPanel(panel, { dialogTitle, refreshView }) {
@@ -25957,7 +26502,7 @@ async function saveMultiPanel(panel, { dialogTitle, refreshView }) {
25957
26502
  if (refreshView) {
25958
26503
  updateWatchers();
25959
26504
  await sendCurrentMultiDiff();
25960
- updateWindowTitle(session.multi.files.map((file) => file.label).join(" \u2194 ") || "Multi-Panel Compare");
26505
+ refreshSessionWindowTitle();
25961
26506
  }
25962
26507
  return true;
25963
26508
  }
@@ -26071,6 +26616,20 @@ function updateWindowTitle(title) {
26071
26616
  const dirtySuffix = hasUnsavedChanges() ? " \u2022 Unsaved" : "";
26072
26617
  mainWindow.setTitle(`${APP_NAME} \u2014 ${title}${dirtySuffix}`);
26073
26618
  }
26619
+ function refreshSessionWindowTitle() {
26620
+ if (session.mode === "history" && session.history) {
26621
+ const visibleIndices = getVisibleFileHistoryIndices(session.history);
26622
+ const visiblePosition = visibleIndices.indexOf(session.history.index);
26623
+ const entry = session.history.entries[session.history.index];
26624
+ const fileName = path8.basename(session.history.filePath);
26625
+ updateWindowTitle(buildHistoryTitle2(fileName, {
26626
+ shortCommit: entry?.shortCommit,
26627
+ positionLabel: visiblePosition >= 0 ? `${visiblePosition + 1} / ${visibleIndices.length}` : void 0
26628
+ }));
26629
+ return;
26630
+ }
26631
+ updateWindowTitle(buildStandaloneSessionTitle2(session));
26632
+ }
26074
26633
  async function confirmSessionReplacement(action) {
26075
26634
  if (!hasUnsavedChanges()) {
26076
26635
  return true;
@@ -26616,6 +27175,7 @@ async function refreshSession(options = {}) {
26616
27175
  function postOrQueue(message) {
26617
27176
  installApplicationMenu();
26618
27177
  if (message && typeof message === "object" && typeof message.type === "string" && message.type.startsWith("show")) {
27178
+ persistOpenWindowState();
26619
27179
  if (!refreshInProgress) {
26620
27180
  refreshFailure = null;
26621
27181
  }