@davmash/bygone 0.9.8 → 0.10.1

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +74 -1
  3. package/cli/commandSpec.js +2 -0
  4. package/cli/completions.js +3 -3
  5. package/cli/present.js +41 -14
  6. package/cli/tour.js +22 -1
  7. package/cli/tourExport.js +147 -0
  8. package/cli/tourFile.js +109 -4
  9. package/cli/tourHistory.js +31 -1
  10. package/completions/_bygone +1 -1
  11. package/completions/bygone +1 -1
  12. package/completions/bygone.fish +1 -1
  13. package/docs/change-tour-format.md +85 -68
  14. package/docs/explore.md +14 -3
  15. package/docs/generating-change-tours.md +84 -46
  16. package/docs/present.md +103 -14
  17. package/docs/product-surface.md +19 -8
  18. package/docs/sharing-tours.md +95 -0
  19. package/docs/visual-walkthrough/README.md +89 -0
  20. package/docs/visual-walkthrough/build-tour.mjs +54 -0
  21. package/docs/visual-walkthrough/capture-desktop.cjs +42 -0
  22. package/docs/visual-walkthrough/capture-desktop.mjs +21 -0
  23. package/docs/visual-walkthrough/captures.md +49 -0
  24. package/docs/visual-walkthrough/create-demo.mjs +57 -0
  25. package/docs/visual-walkthrough/deconstructed-demo.bygone +100 -0
  26. package/docs/visual-walkthrough/images/01-two-files.png +0 -0
  27. package/docs/visual-walkthrough/images/02-many-panels.png +0 -0
  28. package/docs/visual-walkthrough/images/03-directories.png +0 -0
  29. package/docs/visual-walkthrough/images/04-search.png +0 -0
  30. package/docs/visual-walkthrough/images/05-repository-history.png +0 -0
  31. package/docs/visual-walkthrough/images/06-file-history.png +0 -0
  32. package/docs/visual-walkthrough/images/07-branch-review.png +0 -0
  33. package/docs/visual-walkthrough/images/08-tour-reader.png +0 -0
  34. package/docs/visual-walkthrough/images/09-focused-step.png +0 -0
  35. package/docs/visual-walkthrough/images/10-deconstructed.png +0 -0
  36. package/docs/visual-walkthrough/images/11-exploration.png +0 -0
  37. package/docs/visual-walkthrough/images/12-authoring.png +0 -0
  38. package/docs/visual-walkthrough/images/13-export.png +0 -0
  39. package/docs/visual-walkthrough/images/14-vscode.png +0 -0
  40. package/docs/visual-walkthrough/images/15-cli.png +0 -0
  41. package/docs/visual-walkthrough/narrative.md +134 -0
  42. package/docs/visual-walkthrough/outline.md +52 -0
  43. package/docs/visual-walkthrough/walkthrough.bygone +340 -0
  44. package/docs/walkthrough.md +6 -101
  45. package/examples/bygone-history.bygone +11 -44
  46. package/examples/marker-focus.bygone +116 -0
  47. package/examples/navigation-lab.bygone.yaml +401 -336
  48. package/examples/tour-orientation.bygone +349 -0
  49. package/media/diff.worker.js +13 -3
  50. package/media/webview.css +1 -1
  51. package/media/webview.js +394 -319
  52. package/out/changeTour.js +865 -397
  53. package/out/deepLink.js +167 -0
  54. package/out/historyDirectory.js +66 -0
  55. package/out/standalone-main.js +20532 -16832
  56. package/out/tourCoverage.js +850 -386
  57. package/out/tourDocument.js +97 -0
  58. package/package.json +1 -1
  59. package/schemas/change-tour-source.schema.json +91 -64
  60. package/skills/pr-tour-guide/SKILL.md +144 -128
  61. package/skills/pr-tour-guide/references/expectations-and-feedback.md +57 -0
  62. package/standalone/index.html +10 -25
  63. package/web/export-bootstrap.js +62 -0
  64. package/web/export-licenses.txt +78 -0
  65. package/web/export-runtime.json +12 -0
  66. package/web/index.html +54 -47
  67. package/web/presenter.css +332 -178
  68. package/web/web-host.js +239 -137
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/deepLink.ts
21
+ var deepLink_exports = {};
22
+ __export(deepLink_exports, {
23
+ authoredModeTour: () => authoredModeTour,
24
+ parseDeepLink: () => parseDeepLink,
25
+ parseDocumentFocus: () => parseDocumentFocus,
26
+ parseDocumentFragment: () => parseDocumentFragment,
27
+ resolveDocumentFocus: () => resolveDocumentFocus,
28
+ serializeDeepLink: () => serializeDeepLink,
29
+ serializeDocumentFragment: () => serializeDocumentFragment,
30
+ validateRelativePath: () => validateRelativePath
31
+ });
32
+ module.exports = __toCommonJS(deepLink_exports);
33
+ var oidPattern = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/;
34
+ var focusFields = ["part", "chapter", "scene", "step"];
35
+ function required(params, key) {
36
+ const value = params.get(key);
37
+ if (!value || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value)) throw new Error(`Invalid or missing ${key}.`);
38
+ return value;
39
+ }
40
+ function validateRelativePath(value) {
41
+ if (!value || value.startsWith("/") || value.includes("\\") || value.includes("\0") || value.split("/").some((part) => !part || part === "." || part === "..")) {
42
+ throw new Error("Expected a repository-relative path without traversal.");
43
+ }
44
+ return value;
45
+ }
46
+ function validateFields(params, allowed, repeated = []) {
47
+ for (const key of params.keys()) {
48
+ if (!allowed.includes(key)) throw new Error(`Unsupported link field: ${key}.`);
49
+ if (!repeated.includes(key) && params.getAll(key).length !== 1) throw new Error(`Duplicate link field: ${key}.`);
50
+ }
51
+ }
52
+ function validateLocalFileUrl(value) {
53
+ const local = new URL(value);
54
+ if (local.protocol !== "file:" || local.hostname || local.search || local.hash || local.username || local.password || !local.pathname.startsWith("/")) throw new Error("Links require a local file URL.");
55
+ return value;
56
+ }
57
+ function parseDocumentFocus(params) {
58
+ const part = required(params, "part");
59
+ const fields = part === "title" || part === "conclusion" ? [] : part === "chapter" ? ["chapter"] : part === "scene" ? ["scene"] : part === "step" ? ["scene", "step"] : null;
60
+ if (!fields) throw new Error(`Unsupported document part: ${part}.`);
61
+ for (const key of focusFields.filter((key2) => key2 !== "part")) {
62
+ if (params.has(key) && !fields.includes(key)) throw new Error(`${key} is incompatible with ${part}.`);
63
+ }
64
+ return Object.fromEntries([["part", part], ...fields.map((key) => [key, required(params, key)])]);
65
+ }
66
+ function parseDeepLink(value) {
67
+ if (value.length > 16384 || /%(?![\da-f]{2})/i.test(value)) throw new Error("Invalid or oversized Bygone link.");
68
+ const url = new URL(value);
69
+ if (url.protocol !== "bygone:" || url.hostname !== "open" || url.pathname !== "/v1" || url.username || url.password || url.port || url.hash) throw new Error("Unsupported Bygone link address or version.");
70
+ const p = url.searchParams;
71
+ const kind = required(p, "kind");
72
+ const repo = validateLocalFileUrl(required(p, "repo"));
73
+ if (kind === "tour") {
74
+ validateFields(p, ["kind", "repo", "tour", "mode", ...focusFields]);
75
+ const mode = required(p, "mode");
76
+ if (mode !== "historical" && mode !== "deconstructed") throw new Error("Unsupported authored tour mode.");
77
+ const tour = required(p, "tour");
78
+ if (/^[a-z][a-z\d+.-]*:/i.test(tour)) validateLocalFileUrl(tour);
79
+ else validateRelativePath(tour);
80
+ return { kind, repo, tour, mode, focus: parseDocumentFocus(p) };
81
+ }
82
+ if (kind !== "compare") throw new Error("Unsupported Bygone link kind.");
83
+ validateFields(p, ["kind", "repo", "rev", "file", "revision", "line"], ["rev"]);
84
+ const revisions = p.getAll("rev");
85
+ if (revisions.length !== 2 || revisions.some((oid) => !oidPattern.test(oid)) || revisions[0] === revisions[1]) {
86
+ throw new Error("Comparison links require two distinct full commit IDs in order.");
87
+ }
88
+ const file = p.has("file") ? validateRelativePath(required(p, "file")) : void 0;
89
+ const revision = p.has("revision") ? required(p, "revision") : void 0;
90
+ if (revision && (!file || !revisions.includes(revision))) throw new Error("Code focus requires a file and one of the comparison revisions.");
91
+ const line = p.has("line") ? Number(required(p, "line")) : void 0;
92
+ if (line !== void 0 && (!revision || !Number.isSafeInteger(line) || line < 1)) throw new Error("Line focus requires a revision and positive line number.");
93
+ return { kind, repo, revisions, ...file ? { file } : {}, ...revision ? { revision } : {}, ...line !== void 0 ? { line } : {} };
94
+ }
95
+ function serializeDeepLink(link) {
96
+ const url = new URL("bygone://open/v1");
97
+ url.searchParams.set("kind", link.kind);
98
+ url.searchParams.set("repo", link.repo);
99
+ if (link.kind === "tour") {
100
+ url.searchParams.set("tour", link.tour);
101
+ url.searchParams.set("mode", link.mode);
102
+ for (const [key, value] of Object.entries(link.focus)) url.searchParams.set(key, value);
103
+ } else {
104
+ for (const revision of link.revisions) url.searchParams.append("rev", revision);
105
+ for (const key of ["file", "revision", "line"]) if (link[key] !== void 0) url.searchParams.set(key, String(link[key]));
106
+ }
107
+ parseDeepLink(url.href);
108
+ return url.href;
109
+ }
110
+ function authoredModeTour(tour, mode) {
111
+ if (tour.tours) {
112
+ const result = tour.tours[mode];
113
+ if (!result) throw new Error(`This tour has no ${mode} mode.`);
114
+ return result;
115
+ }
116
+ const deconstructed = tour.scenes.some((scene) => scene.kind === "deconstructed-diff");
117
+ if (mode === "deconstructed" && deconstructed) return tour;
118
+ if (mode === "historical" && !deconstructed) return tour;
119
+ if (mode === "historical" && tour.zoom?.final.scenes.some((scene) => scene.kind === "walkthrough")) return tour.zoom.final;
120
+ throw new Error(`This tour has no ${mode} mode.`);
121
+ }
122
+ function resolveDocumentFocus(tour, mode, focus) {
123
+ const selected = authoredModeTour(tour, mode);
124
+ if (focus.part === "title") return { key: "title", sceneIndex: 0, stepIndex: 0 };
125
+ if (focus.part === "conclusion") {
126
+ if (!selected.conclusion) throw new Error("Conclusion is unavailable in this tour mode.");
127
+ return { key: "conclusion", sceneIndex: -1, stepIndex: 0 };
128
+ }
129
+ if (focus.part === "chapter") {
130
+ const chapter = selected.chapters.find((item) => item.id === focus.chapter);
131
+ const sceneIndex2 = selected.scenes.findIndex((scene2) => scene2.id === chapter?.sceneIds[0]);
132
+ if (!chapter || sceneIndex2 < 0) throw new Error(`Chapter is unavailable: ${focus.chapter}.`);
133
+ return { key: chapter.summary?.trim() ? `chapter:${chapter.id}` : `scene:${selected.scenes[sceneIndex2].id}`, sceneIndex: sceneIndex2, stepIndex: 0 };
134
+ }
135
+ const sceneIndex = selected.scenes.findIndex((scene2) => scene2.id === focus.scene);
136
+ if (sceneIndex < 0) throw new Error(`Scene is unavailable: ${focus.scene}.`);
137
+ const scene = selected.scenes[sceneIndex];
138
+ if (focus.part === "scene") return { key: `scene:${scene.id}`, sceneIndex, stepIndex: 0 };
139
+ const stepIndex = "steps" in scene ? scene.steps.findIndex((step) => step.id === focus.step) : -1;
140
+ if (stepIndex < 0) throw new Error(`Step is unavailable: ${focus.step}.`);
141
+ return { key: `step:${scene.id}:${focus.step}`, sceneIndex, stepIndex };
142
+ }
143
+ function serializeDocumentFragment(mode, focus) {
144
+ return `#${new URLSearchParams({ location: "1", mode, ...focus })}`;
145
+ }
146
+ function parseDocumentFragment(hash) {
147
+ if (!hash) return null;
148
+ if (hash.length > 16384 || /%(?![\da-f]{2})/i.test(hash)) throw new Error("Oversized tour location.");
149
+ const p = new URLSearchParams(hash.replace(/^#/, ""));
150
+ validateFields(p, ["location", "mode", ...focusFields]);
151
+ if (p.get("location") !== "1") throw new Error("Unsupported tour location version.");
152
+ const mode = p.get("mode");
153
+ if (mode !== "historical" && mode !== "deconstructed") throw new Error("Unsupported tour location mode.");
154
+ return { mode, focus: parseDocumentFocus(p) };
155
+ }
156
+ // Annotate the CommonJS export names for ESM import in node:
157
+ 0 && (module.exports = {
158
+ authoredModeTour,
159
+ parseDeepLink,
160
+ parseDocumentFocus,
161
+ parseDocumentFragment,
162
+ resolveDocumentFocus,
163
+ serializeDeepLink,
164
+ serializeDocumentFragment,
165
+ validateRelativePath
166
+ });
167
+ //# sourceMappingURL=deepLink.js.map
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/historyDirectory.ts
21
+ var historyDirectory_exports = {};
22
+ __export(historyDirectory_exports, {
23
+ buildHistoryDirectoryEntries: () => buildHistoryDirectoryEntries
24
+ });
25
+ module.exports = __toCommonJS(historyDirectory_exports);
26
+ function buildHistoryDirectoryEntries(paths, inventories) {
27
+ const rows = /* @__PURE__ */ new Map();
28
+ const trees = inventories.map((files) => {
29
+ const tree = new Set(files);
30
+ for (const file of files) {
31
+ const parts = file.split("/");
32
+ for (let depth = 1; depth < parts.length; depth++) tree.add(parts.slice(0, depth).join("/"));
33
+ }
34
+ return tree;
35
+ });
36
+ for (const file of paths) {
37
+ const parts = file.split("/");
38
+ for (let depth = 0; depth < parts.length; depth++) {
39
+ const relativePath = parts.slice(0, depth + 1).join("/");
40
+ if (rows.has(relativePath)) continue;
41
+ const sides = trees.map((tree) => tree.has(relativePath));
42
+ rows.set(relativePath, {
43
+ relativePath,
44
+ displayName: parts[depth],
45
+ depth,
46
+ isDirectory: depth < parts.length - 1,
47
+ sides,
48
+ status: sides.every(Boolean) ? "modified" : sides.length !== 2 ? "partial" : sides[0] ? "left-only" : "right-only"
49
+ });
50
+ }
51
+ }
52
+ return [...rows.values()].sort((a, b) => {
53
+ const left = a.relativePath.split("/");
54
+ const right = b.relativePath.split("/");
55
+ for (let i = 0; i < Math.min(left.length, right.length); i++) {
56
+ if (left[i] === right[i]) continue;
57
+ return Number(i < right.length - 1 || b.isDirectory) - Number(i < left.length - 1 || a.isDirectory) || left[i].localeCompare(right[i]);
58
+ }
59
+ return left.length - right.length;
60
+ });
61
+ }
62
+ // Annotate the CommonJS export names for ESM import in node:
63
+ 0 && (module.exports = {
64
+ buildHistoryDirectoryEntries
65
+ });
66
+ //# sourceMappingURL=historyDirectory.js.map