@youtyan/code-viewer 0.6.0 → 0.6.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.
package/web/app.js CHANGED
@@ -21,6 +21,122 @@
21
21
  throw Error('Dynamic require of "' + x + '" is not supported');
22
22
  });
23
23
 
24
+ // web-src/core/file-path-copy.ts
25
+ function filePathClipboardText(path) {
26
+ return path || "";
27
+ }
28
+ function fileNameClipboardText(path) {
29
+ if (!path)
30
+ return "";
31
+ const parts = path.split("/").filter(Boolean);
32
+ return parts[parts.length - 1] || "";
33
+ }
34
+ function fileReferenceClipboardText(path, start, end) {
35
+ if (!path)
36
+ return "";
37
+ const a = Math.max(1, Math.floor(Math.min(start, end)));
38
+ const b = Math.max(1, Math.floor(Math.max(start, end)));
39
+ return a === b ? `@${path}#${a}` : `@${path}#${a}-${b}`;
40
+ }
41
+ function fileReferenceWithCodeClipboardText(path, start, end, lines, lang) {
42
+ const ref = fileReferenceClipboardText(path, start, end);
43
+ if (!ref)
44
+ return "";
45
+ if (!lines || lines.length === 0)
46
+ return ref;
47
+ const fence = (lang || "").trim();
48
+ return `${ref}
49
+
50
+ \`\`\`${fence}
51
+ ${lines.join(`
52
+ `)}
53
+ \`\`\``;
54
+ }
55
+
56
+ // web-src/core/ai-context-copy.ts
57
+ var AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD = 300;
58
+ function lineRange(line) {
59
+ return typeof line === "number" ? { start: line, end: line } : { start: line.start, end: line.end };
60
+ }
61
+ function resolveSelectionTarget(route) {
62
+ const path = route.screen === "file" || route.screen === "diff" ? route.path : undefined;
63
+ const line = route.screen === "file" || route.screen === "diff" ? route.line : undefined;
64
+ if (!path || !line)
65
+ return null;
66
+ return { path, ...lineRange(line) };
67
+ }
68
+ function activePath(route) {
69
+ if (route.screen === "file")
70
+ return route.path;
71
+ if (route.screen === "repo")
72
+ return route.path || undefined;
73
+ if (route.screen === "diff")
74
+ return route.path;
75
+ return;
76
+ }
77
+ function commitOrRefSuffix(route) {
78
+ const commit = "commit" in route ? route.commit : undefined;
79
+ if (commit)
80
+ return ` (commit: ${commit})`;
81
+ const ref = "ref" in route ? route.ref : undefined;
82
+ if (ref && ref !== "worktree" && ref !== "HEAD")
83
+ return ` (ref: ${ref})`;
84
+ return "";
85
+ }
86
+ function referenceLine(route, code) {
87
+ const target = resolveSelectionTarget(route);
88
+ const path = target?.path ?? activePath(route);
89
+ if (!path)
90
+ return "";
91
+ const suffix = commitOrRefSuffix(route);
92
+ if (!target)
93
+ return `@${path}${suffix}`;
94
+ const ref = fileReferenceClipboardText(target.path, target.start, target.end);
95
+ const withSuffix = `${ref}${suffix}`;
96
+ const lines = code?.lines ?? [];
97
+ if (lines.length === 0)
98
+ return withSuffix;
99
+ const lang = (code?.lang || "").trim();
100
+ return `${withSuffix}
101
+
102
+ \`\`\`${lang}
103
+ ${lines.join(`
104
+ `)}
105
+ \`\`\``;
106
+ }
107
+ function historyLine(route) {
108
+ if (!route.commit)
109
+ return "";
110
+ const ref = route.ref && route.ref !== "worktree" && route.ref !== "HEAD" ? ` (ref: ${route.ref})` : "";
111
+ return `commit: ${route.commit}${ref}`;
112
+ }
113
+ function databaseLine(route) {
114
+ const parts = [];
115
+ if (route.db)
116
+ parts.push(`db=${route.db}`);
117
+ if (route.schema)
118
+ parts.push(`schema=${route.schema}`);
119
+ if (route.table)
120
+ parts.push(`table=${route.table}`);
121
+ if (route.tab)
122
+ parts.push(`tab=${route.tab}`);
123
+ if (route.diffBefore && route.diffAfter) {
124
+ parts.push(`snapshot=${route.diffBefore}..${route.diffAfter}`);
125
+ }
126
+ return parts.length > 0 ? `database: ${parts.join(", ")}` : "";
127
+ }
128
+ function aiContextClipboardText(snapshot) {
129
+ const { route } = snapshot;
130
+ if (route.screen === "database")
131
+ return databaseLine(route);
132
+ if (route.screen === "history")
133
+ return historyLine(route);
134
+ if (route.screen === "diff" && !route.path) {
135
+ return `Diff: ${snapshot.diffFrom}..${snapshot.diffTo}`;
136
+ }
137
+ return referenceLine(route, snapshot.selectionCode);
138
+ }
139
+
24
140
  // web-src/core/catch-up.ts
25
141
  function shouldAutoLoadForRoute(route, options = {}) {
26
142
  if (route.screen === "history")
@@ -290,6 +406,9 @@
290
406
  return '<svg class="octicon ' + className + '" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" aria-hidden="true">' + pathList.map((path) => `<path fill="currentColor" d="${path}"></path>`).join("") + "</svg>";
291
407
  }
292
408
  var PENCIL_16_PATH = "M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.609Zm.176 4.823L9.75 4.81l-6.286 6.287a.253.253 0 0 0-.064.108l-.558 1.953 1.953-.558a.253.253 0 0 0 .108-.064Zm1.238-3.763a.25.25 0 0 0-.354 0L10.811 3.75l1.439 1.44 1.263-1.263a.25.25 0 0 0 0-.354Z";
409
+ var COMMENT_DISCUSSION_16_PATH = "M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z";
410
+ var PULSE_16_PATH = "M6 2c.306 0 .582.187.696.471L10 10.731l1.304-3.26A.751.751 0 0 1 12 7h3.25a.75.75 0 0 1 0 1.5h-2.742l-1.812 4.528a.751.751 0 0 1-1.392 0L6 4.77 4.696 8.03A.75.75 0 0 1 4 8.5H.75a.75.75 0 0 1 0-1.5h2.742l1.812-4.529A.751.751 0 0 1 6 2Z";
411
+ var MOON_16_PATH = "M9.598 1.591a.749.749 0 0 1 .785-.175 7.001 7.001 0 1 1-8.967 8.967.75.75 0 0 1 .961-.96 5.5 5.5 0 0 0 7.046-7.046.75.75 0 0 1 .175-.786Zm1.616 1.945a7 7 0 0 1-7.678 7.678 5.499 5.499 0 1 0 7.678-7.678Z";
293
412
 
294
413
  // web-src/core/keyboard.ts
295
414
  function isImeComposing(event) {
@@ -401,7 +520,9 @@
401
520
  { action: "layout-unified", key: "u" },
402
521
  { action: "layout-split", key: "s" },
403
522
  { action: "toggle-theme", key: "t" },
404
- { action: "open-help", key: "?", shift: true }
523
+ { action: "open-help", key: "?", shift: true },
524
+ { action: "copy-ai-context", key: "y" },
525
+ { action: "copy-ai-context-with-code", key: "y", shift: true }
405
526
  ];
406
527
  function resolveKeymapAction(event, context) {
407
528
  const key = event.key.toLowerCase();
@@ -792,6 +913,486 @@
792
913
  return qs ? `${base}?${qs}` : base;
793
914
  }
794
915
 
916
+ // web-src/views/media-embed.ts
917
+ var MEDIA_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico|mp4|webm|mov|mp3|wav|ogg|flac|m4a|aac|opus)(\?.*)?$/i;
918
+ var IMAGE_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i;
919
+ var VIDEO_RE = /\.(mp4|webm|mov)$/i;
920
+ var AUDIO_RE = /\.(mp3|wav|ogg|flac|m4a|aac|opus)$/i;
921
+ function isMedia(p) {
922
+ return MEDIA_RE.test(p);
923
+ }
924
+ function isImage(p) {
925
+ return IMAGE_RE.test(p);
926
+ }
927
+ function isVideo(p) {
928
+ return VIDEO_RE.test(p);
929
+ }
930
+ function isAudio(p) {
931
+ return AUDIO_RE.test(p);
932
+ }
933
+ function fileURL(path, ref) {
934
+ return `/_file?path=${encodeURIComponent(path)}&ref=${ref}`;
935
+ }
936
+ function mediaTag(path, ref) {
937
+ const url = fileURL(path, ref);
938
+ if (isVideo(path)) {
939
+ return `<video src="${url}" controls preload="metadata"></video>`;
940
+ }
941
+ if (isAudio(path)) {
942
+ return `<audio src="${url}" controls preload="metadata"></audio>`;
943
+ }
944
+ return `<img src="${url}" alt="" loading="lazy">`;
945
+ }
946
+ function enhanceMediaCard(file, card) {
947
+ const path = file.path;
948
+ if (!file.media_kind && !isMedia(path))
949
+ return;
950
+ const wrapper = card.querySelector(".d2h-file-wrapper");
951
+ if (!wrapper)
952
+ return;
953
+ const body = wrapper.querySelector(".d2h-files-diff") || wrapper.querySelector(".d2h-file-diff");
954
+ if (!body)
955
+ return;
956
+ const container = document.createElement("div");
957
+ container.className = "gdp-media";
958
+ let leftHTML;
959
+ let rightHTML;
960
+ if (file.status === "A") {
961
+ leftHTML = '<div class="media-empty">Not in HEAD</div>';
962
+ rightHTML = mediaTag(path, "worktree");
963
+ } else if (file.status === "D") {
964
+ leftHTML = mediaTag(path, "HEAD");
965
+ rightHTML = '<div class="media-empty">Deleted</div>';
966
+ } else {
967
+ leftHTML = mediaTag(path, "HEAD");
968
+ rightHTML = mediaTag(path, "worktree");
969
+ }
970
+ container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML + "</div>" + '<div class="media-side"><div class="media-label add">After</div>' + rightHTML + "</div>";
971
+ body.replaceWith(container);
972
+ }
973
+
974
+ // web-src/core/source-meta.ts
975
+ var SOURCE_SHIKI_LANG_ALIASES = {
976
+ makefile: "make",
977
+ objectivec: "c",
978
+ "objective-c": "c",
979
+ "objective-cpp": "cpp",
980
+ starlark: "python"
981
+ };
982
+ function normalizeSourceShikiLang(lang) {
983
+ if (!lang)
984
+ return null;
985
+ return SOURCE_SHIKI_LANG_ALIASES[lang] || lang;
986
+ }
987
+ function isPreviewableSource(path) {
988
+ return /\.(md|markdown|mdown|mkdn|mdx|html|htm)$/i.test(path);
989
+ }
990
+ function sourceInternalPathKind(path) {
991
+ for (const part of path.split(/[\\/]+/)) {
992
+ const lower = part.toLowerCase();
993
+ if (lower === ".code-viewer")
994
+ return "code-viewer";
995
+ if (lower === ".git")
996
+ return "git";
997
+ }
998
+ return null;
999
+ }
1000
+ function sourcePreviewKind(path) {
1001
+ if (/\.(md|markdown|mdown|mkdn|mdx)$/i.test(path))
1002
+ return "markdown";
1003
+ if (/\.(html|htm)$/i.test(path))
1004
+ return "html";
1005
+ return null;
1006
+ }
1007
+ var EXT_TO_LANG = {
1008
+ js: "javascript",
1009
+ mjs: "javascript",
1010
+ cjs: "javascript",
1011
+ ts: "typescript",
1012
+ tsx: "typescript",
1013
+ jsx: "javascript",
1014
+ py: "python",
1015
+ rb: "ruby",
1016
+ go: "go",
1017
+ rs: "rust",
1018
+ java: "java",
1019
+ kt: "kotlin",
1020
+ swift: "swift",
1021
+ c: "c",
1022
+ h: "c",
1023
+ cc: "cpp",
1024
+ cpp: "cpp",
1025
+ hpp: "cpp",
1026
+ cs: "csharp",
1027
+ php: "php",
1028
+ lua: "lua",
1029
+ sh: "bash",
1030
+ bash: "bash",
1031
+ zsh: "bash",
1032
+ fish: "bash",
1033
+ sql: "sql",
1034
+ json: "json",
1035
+ yaml: "yaml",
1036
+ yml: "yaml",
1037
+ toml: "toml",
1038
+ tf: "terraform",
1039
+ tfvars: "terraform",
1040
+ hcl: "terraform",
1041
+ xml: "xml",
1042
+ html: "xml",
1043
+ vue: "xml",
1044
+ css: "css",
1045
+ scss: "scss",
1046
+ md: "markdown",
1047
+ dockerfile: "dockerfile",
1048
+ proto: "protobuf",
1049
+ gradle: "gradle",
1050
+ properties: "properties",
1051
+ patch: "diff",
1052
+ diff: "diff",
1053
+ nix: "nix",
1054
+ cue: "cue",
1055
+ rego: "rego",
1056
+ bicep: "bicep",
1057
+ bazel: "starlark",
1058
+ bzl: "starlark",
1059
+ cmake: "cmake",
1060
+ groovy: "groovy",
1061
+ dart: "dart",
1062
+ scala: "scala",
1063
+ clj: "clojure",
1064
+ cljs: "clojure",
1065
+ cljc: "clojure",
1066
+ edn: "clojure",
1067
+ ex: "elixir",
1068
+ exs: "elixir",
1069
+ erl: "erlang",
1070
+ hrl: "erlang",
1071
+ hs: "haskell",
1072
+ lhs: "haskell",
1073
+ ml: "ocaml",
1074
+ mli: "ocaml",
1075
+ jl: "julia",
1076
+ r: "r",
1077
+ rmd: "r",
1078
+ pl: "perl",
1079
+ pm: "perl",
1080
+ tcl: "tcl",
1081
+ vim: "vim",
1082
+ f: "fortran",
1083
+ f90: "fortran",
1084
+ m: "objective-c",
1085
+ mm: "objective-cpp",
1086
+ tex: "tex",
1087
+ bib: "bibtex",
1088
+ rst: "rst"
1089
+ };
1090
+ var TEXT_SOURCE_EXTENSIONS = new Set([
1091
+ ...Object.keys(EXT_TO_LANG),
1092
+ "txt",
1093
+ "md",
1094
+ "markdown",
1095
+ "mdown",
1096
+ "mkdn",
1097
+ "mdx",
1098
+ "json",
1099
+ "jsonc",
1100
+ "csv",
1101
+ "tsv",
1102
+ "yaml",
1103
+ "yml",
1104
+ "toml",
1105
+ "hcl",
1106
+ "tf",
1107
+ "tfvars",
1108
+ "tfstate",
1109
+ "xml",
1110
+ "html",
1111
+ "htm",
1112
+ "css",
1113
+ "scss",
1114
+ "sass",
1115
+ "less",
1116
+ "js",
1117
+ "jsx",
1118
+ "mjs",
1119
+ "cjs",
1120
+ "ts",
1121
+ "tsx",
1122
+ "mts",
1123
+ "cts",
1124
+ "vue",
1125
+ "svelte",
1126
+ "astro",
1127
+ "rs",
1128
+ "go",
1129
+ "py",
1130
+ "rb",
1131
+ "php",
1132
+ "java",
1133
+ "kt",
1134
+ "kts",
1135
+ "c",
1136
+ "cc",
1137
+ "cpp",
1138
+ "cxx",
1139
+ "h",
1140
+ "hpp",
1141
+ "cs",
1142
+ "swift",
1143
+ "sh",
1144
+ "bash",
1145
+ "zsh",
1146
+ "fish",
1147
+ "ps1",
1148
+ "sql",
1149
+ "graphql",
1150
+ "graphqls",
1151
+ "gql",
1152
+ "ini",
1153
+ "conf",
1154
+ "env",
1155
+ "properties",
1156
+ "rules",
1157
+ "rule",
1158
+ "prompt",
1159
+ "prompts",
1160
+ "instructions",
1161
+ "gitignore",
1162
+ "dockerignore",
1163
+ "editorconfig",
1164
+ "lock",
1165
+ "log",
1166
+ "patch",
1167
+ "diff",
1168
+ "sum",
1169
+ "mk",
1170
+ "proto",
1171
+ "thrift",
1172
+ "prisma",
1173
+ "gradle",
1174
+ "cmake",
1175
+ "nix",
1176
+ "cue",
1177
+ "rego",
1178
+ "bicep",
1179
+ "bazel",
1180
+ "bzl",
1181
+ "dart",
1182
+ "scala",
1183
+ "clj",
1184
+ "cljs",
1185
+ "cljc",
1186
+ "edn",
1187
+ "ex",
1188
+ "exs",
1189
+ "erl",
1190
+ "hrl",
1191
+ "hs",
1192
+ "lhs",
1193
+ "ml",
1194
+ "mli",
1195
+ "jl",
1196
+ "r",
1197
+ "rmd",
1198
+ "pl",
1199
+ "pm",
1200
+ "tcl",
1201
+ "vim",
1202
+ "groovy",
1203
+ "f",
1204
+ "f90",
1205
+ "m",
1206
+ "mm",
1207
+ "pas",
1208
+ "tex",
1209
+ "bib",
1210
+ "rst",
1211
+ "adoc",
1212
+ "org",
1213
+ "ipynb",
1214
+ "ejs",
1215
+ "hbs",
1216
+ "mustache",
1217
+ "liquid",
1218
+ "pug"
1219
+ ]);
1220
+ var TEXT_SOURCE_FILENAMES = new Set([
1221
+ "readme",
1222
+ "license",
1223
+ "copying",
1224
+ "authors",
1225
+ "contributors",
1226
+ "notice",
1227
+ "changelog",
1228
+ "todo",
1229
+ "manifest",
1230
+ "version",
1231
+ "codeowners",
1232
+ "go.mod",
1233
+ "build.bazel",
1234
+ "workspace.bazel",
1235
+ "module.bazel",
1236
+ "gemfile",
1237
+ "rakefile",
1238
+ "procfile",
1239
+ "brewfile",
1240
+ "gnumakefile",
1241
+ "bsdmakefile",
1242
+ ".gitattributes",
1243
+ ".gitmodules",
1244
+ ".npmrc",
1245
+ ".nvmrc",
1246
+ ".yarnrc",
1247
+ ".prettierrc",
1248
+ ".eslintrc",
1249
+ ".babelrc",
1250
+ ".stylelintrc"
1251
+ ]);
1252
+ var FILENAME_TO_LANG = {
1253
+ dockerfile: "dockerfile",
1254
+ makefile: "makefile",
1255
+ gnumakefile: "makefile",
1256
+ bsdmakefile: "makefile",
1257
+ "go.mod": "go",
1258
+ "build.bazel": "starlark",
1259
+ "workspace.bazel": "starlark",
1260
+ "module.bazel": "starlark"
1261
+ };
1262
+ function sourceFileName(path) {
1263
+ return (path.split("/").pop() || path).toLowerCase();
1264
+ }
1265
+ function sourceFileExtension(name) {
1266
+ const index = name.lastIndexOf(".");
1267
+ return index >= 0 ? name.slice(index + 1) : "";
1268
+ }
1269
+ function isDockerfileName(name) {
1270
+ return /^dockerfile(?:[.-].+)?$/i.test(name);
1271
+ }
1272
+ function isMakefileName(name) {
1273
+ return /^makefile(?:[.-].+)?$/i.test(name);
1274
+ }
1275
+ function isDotenvName(name) {
1276
+ return /^(?:\.?env|.*\.env)(?:[.-].+)?$/i.test(name);
1277
+ }
1278
+ function sourceDisplayKind(path) {
1279
+ if (isVideo(path))
1280
+ return "video";
1281
+ if (isAudio(path))
1282
+ return "audio";
1283
+ if (isImage(path))
1284
+ return "image";
1285
+ if (/\.pdf$/i.test(path))
1286
+ return "pdf";
1287
+ const name = sourceFileName(path);
1288
+ const ext = sourceFileExtension(name);
1289
+ if (TEXT_SOURCE_EXTENSIONS.has(ext))
1290
+ return "text";
1291
+ if (TEXT_SOURCE_FILENAMES.has(name))
1292
+ return "text";
1293
+ if (isDotenvName(name))
1294
+ return "text";
1295
+ if (isDockerfileName(name) || isMakefileName(name))
1296
+ return "text";
1297
+ return "unsupported";
1298
+ }
1299
+ function isLikelyTextBytes(bytes) {
1300
+ if (bytes.length === 0)
1301
+ return true;
1302
+ if (bytes.includes(0))
1303
+ return false;
1304
+ let text = "";
1305
+ try {
1306
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1307
+ } catch {
1308
+ return false;
1309
+ }
1310
+ if (!text)
1311
+ return true;
1312
+ let controlCount = 0;
1313
+ for (const char of text) {
1314
+ const code = char.charCodeAt(0);
1315
+ const allowed = code === 9 || code === 10 || code === 12 || code === 13 || code >= 32;
1316
+ if (!allowed)
1317
+ controlCount++;
1318
+ }
1319
+ return controlCount / text.length <= 0.02;
1320
+ }
1321
+ function formatBytes(bytes) {
1322
+ if (!Number.isFinite(bytes) || bytes < 0)
1323
+ return "";
1324
+ const units = ["B", "KB", "MB", "GB"];
1325
+ let value = bytes;
1326
+ let unit = 0;
1327
+ while (value >= 1024 && unit < units.length - 1) {
1328
+ value /= 1024;
1329
+ unit++;
1330
+ }
1331
+ return (unit === 0 ? String(value) : value.toFixed(value >= 10 ? 1 : 2).replace(/\.0+$/, "")) + " " + units[unit];
1332
+ }
1333
+ function formatFileDate(value) {
1334
+ if (!value)
1335
+ return "";
1336
+ const date = new Date(value);
1337
+ if (Number.isNaN(date.getTime()))
1338
+ return "";
1339
+ return date.toLocaleString(undefined, {
1340
+ year: "numeric",
1341
+ month: "short",
1342
+ day: "numeric",
1343
+ hour: "2-digit",
1344
+ minute: "2-digit"
1345
+ });
1346
+ }
1347
+ function humanFileKind(path, mime, fallback) {
1348
+ const ext = (path.split(".").pop() || "").toLowerCase();
1349
+ if (ext === "png")
1350
+ return "PNG image";
1351
+ if (ext === "jpg" || ext === "jpeg")
1352
+ return "JPEG image";
1353
+ if (ext === "gif")
1354
+ return "GIF image";
1355
+ if (ext === "webp")
1356
+ return "WebP image";
1357
+ if (ext === "svg")
1358
+ return "SVG image";
1359
+ if (ext === "pdf")
1360
+ return "PDF document";
1361
+ if (ext === "zip")
1362
+ return "ZIP archive";
1363
+ if (ext === "mp4")
1364
+ return "MP4 video";
1365
+ if (ext === "webm")
1366
+ return "WebM video";
1367
+ if (ext === "mp3")
1368
+ return "MP3 audio";
1369
+ if (ext === "wav")
1370
+ return "WAV audio";
1371
+ if (ext === "ogg")
1372
+ return "Ogg audio";
1373
+ if (ext === "flac")
1374
+ return "FLAC audio";
1375
+ if (ext === "m4a")
1376
+ return "M4A audio";
1377
+ if (ext === "aac")
1378
+ return "AAC audio";
1379
+ if (ext === "opus")
1380
+ return "Opus audio";
1381
+ if (ext === "mid" || ext === "midi")
1382
+ return "MIDI file";
1383
+ if (mime?.startsWith("image/"))
1384
+ return "Image";
1385
+ if (mime?.startsWith("video/"))
1386
+ return "Video";
1387
+ if (mime?.startsWith("audio/"))
1388
+ return "Audio";
1389
+ if (mime === "application/pdf")
1390
+ return "PDF document";
1391
+ if (fallback === "unsupported file")
1392
+ return "Binary file";
1393
+ return fallback.charAt(0).toUpperCase() + fallback.slice(1);
1394
+ }
1395
+
795
1396
  // web-src/core/annotation-player-core.ts
796
1397
  function createAnnotationPlayerCore(deps) {
797
1398
  let status = "idle";
@@ -7619,6 +8220,7 @@ ${frontmatter.yaml}
7619
8220
  document.body.classList.remove("query-history-panel-open");
7620
8221
  }
7621
8222
  deps.setAnnotationPanelOpenState(open);
8223
+ applyInlineAnnotations();
7622
8224
  }
7623
8225
  function annotationLineTarget(entry) {
7624
8226
  if (!entry.line)
@@ -7783,6 +8385,10 @@ ${frontmatter.yaml}
7783
8385
  row.remove();
7784
8386
  });
7785
8387
  const session = ANNOTATIONS.sessions.find((s2) => s2.id === activeSessionId);
8388
+ if (!annotationPanel.hidden) {
8389
+ applyDatabaseAnnotations(undefined);
8390
+ return;
8391
+ }
7786
8392
  applyDatabaseAnnotations(session);
7787
8393
  if (!session)
7788
8394
  return;
@@ -7964,7 +8570,7 @@ ${frontmatter.yaml}
7964
8570
  } catch {}
7965
8571
  await refreshAnnotations();
7966
8572
  }
7967
- function sessionTimeLabel(createdAt) {
8573
+ function annotationTimeLabel(createdAt) {
7968
8574
  const date = new Date(createdAt);
7969
8575
  if (Number.isNaN(date.getTime()))
7970
8576
  return "";
@@ -7993,6 +8599,9 @@ ${frontmatter.yaml}
7993
8599
  }
7994
8600
  function openDatabaseCaptureForm(target) {
7995
8601
  $("#annotation-detail-session").textContent = activeSessionId || "Datastore annotations";
8602
+ const detailTime = $("#annotation-detail-time");
8603
+ detailTime.textContent = "";
8604
+ detailTime.title = "";
7996
8605
  $("#annotation-detail-step").textContent = "new";
7997
8606
  const location2 = $("#annotation-detail-location");
7998
8607
  location2.textContent = databaseAnnotationTitle(target);
@@ -8103,7 +8712,7 @@ ${frontmatter.yaml}
8103
8712
  title.title = session.created_at;
8104
8713
  const time = document.createElement("span");
8105
8714
  time.className = "annotation-session-time";
8106
- time.textContent = sessionTimeLabel(session.created_at);
8715
+ time.textContent = annotationTimeLabel(session.created_at);
8107
8716
  time.title = session.created_at;
8108
8717
  title.addEventListener("click", () => {
8109
8718
  setActiveSession(session.id === activeSessionId ? null : session.id);
@@ -8158,6 +8767,14 @@ ${frontmatter.yaml}
8158
8767
  summary.className = "annotation-entry-summary";
8159
8768
  summary.textContent = annotationEntrySummary(entry);
8160
8769
  open.append(location2, summary);
8770
+ const entryTimeLabel = annotationTimeLabel(entry.created_at);
8771
+ if (entryTimeLabel) {
8772
+ const time2 = document.createElement("span");
8773
+ time2.className = "annotation-entry-time";
8774
+ time2.textContent = entryTimeLabel;
8775
+ time2.title = entry.created_at;
8776
+ open.appendChild(time2);
8777
+ }
8161
8778
  open.addEventListener("click", () => {
8162
8779
  openAnnotationEntry(entry.id);
8163
8780
  });
@@ -8197,6 +8814,9 @@ ${frontmatter.yaml}
8197
8814
  function showAnnotationDetail(session, entry, index) {
8198
8815
  activeAnnotationId = entry.id;
8199
8816
  $("#annotation-detail-session").textContent = session.title;
8817
+ const detailTime = $("#annotation-detail-time");
8818
+ detailTime.textContent = annotationTimeLabel(entry.created_at);
8819
+ detailTime.title = entry.created_at;
8200
8820
  $("#annotation-detail-step").textContent = `${index + 1}/${session.entries.length}`;
8201
8821
  const location2 = $("#annotation-detail-location");
8202
8822
  location2.textContent = annotationLocationLabel(entry);
@@ -8409,8 +9029,21 @@ ${frontmatter.yaml}
8409
9029
  }
8410
9030
  });
8411
9031
  }
9032
+ const ANNOTATION_PANEL_DEFAULT_WIDTH = 380;
9033
+ const ANNOTATION_PANEL_MIN_WIDTH = 260;
9034
+ const ANNOTATION_PANEL_MAX_WIDTH = 720;
9035
+ function annotationPanelMaxWidth() {
9036
+ return Math.max(ANNOTATION_PANEL_MIN_WIDTH, Math.min(ANNOTATION_PANEL_MAX_WIDTH, window.innerWidth - 32));
9037
+ }
9038
+ function applyAnnotationPanelWidth(width, persist = true) {
9039
+ const clamped = Math.max(ANNOTATION_PANEL_MIN_WIDTH, Math.min(annotationPanelMaxWidth(), width));
9040
+ document.documentElement.style.setProperty("--annotation-panel-w", `${clamped}px`);
9041
+ if (persist)
9042
+ deps.setAnnotationPanelWidth(clamped);
9043
+ }
8412
9044
  if (deps.getAnnotationPanelOpen())
8413
9045
  setAnnotationPanelOpen(true);
9046
+ applyAnnotationPanelWidth(deps.getAnnotationPanelWidth() ?? ANNOTATION_PANEL_DEFAULT_WIDTH, false);
8414
9047
  updateDatabaseCaptureButton();
8415
9048
  $("#annotations-toggle").addEventListener("click", () => {
8416
9049
  setAnnotationPanelOpen(annotationPanel.hidden);
@@ -8464,6 +9097,7 @@ ${frontmatter.yaml}
8464
9097
  restoreSessionFromUrl,
8465
9098
  openAnnotationEntry,
8466
9099
  setAnnotationPanelOpen,
9100
+ applyAnnotationPanelWidth,
8467
9101
  getActiveSessionEntries() {
8468
9102
  const session = ANNOTATIONS.sessions.find((s2) => s2.id === activeSessionId);
8469
9103
  return session ? session.entries : [];
@@ -8575,449 +9209,6 @@ ${frontmatter.yaml}
8575
9209
  return sha.slice(0, 7);
8576
9210
  }
8577
9211
 
8578
- // web-src/views/media-embed.ts
8579
- var MEDIA_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico|mp4|webm|mov|mp3|wav|ogg|flac|m4a|aac|opus)(\?.*)?$/i;
8580
- var IMAGE_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)(\?.*)?$/i;
8581
- var VIDEO_RE = /\.(mp4|webm|mov)$/i;
8582
- var AUDIO_RE = /\.(mp3|wav|ogg|flac|m4a|aac|opus)$/i;
8583
- function isMedia(p2) {
8584
- return MEDIA_RE.test(p2);
8585
- }
8586
- function isImage(p2) {
8587
- return IMAGE_RE.test(p2);
8588
- }
8589
- function isVideo(p2) {
8590
- return VIDEO_RE.test(p2);
8591
- }
8592
- function isAudio(p2) {
8593
- return AUDIO_RE.test(p2);
8594
- }
8595
- function fileURL(path, ref) {
8596
- return `/_file?path=${encodeURIComponent(path)}&ref=${ref}`;
8597
- }
8598
- function mediaTag(path, ref) {
8599
- const url = fileURL(path, ref);
8600
- if (isVideo(path)) {
8601
- return `<video src="${url}" controls preload="metadata"></video>`;
8602
- }
8603
- if (isAudio(path)) {
8604
- return `<audio src="${url}" controls preload="metadata"></audio>`;
8605
- }
8606
- return `<img src="${url}" alt="" loading="lazy">`;
8607
- }
8608
- function enhanceMediaCard(file, card) {
8609
- const path = file.path;
8610
- if (!file.media_kind && !isMedia(path))
8611
- return;
8612
- const wrapper = card.querySelector(".d2h-file-wrapper");
8613
- if (!wrapper)
8614
- return;
8615
- const body = wrapper.querySelector(".d2h-files-diff") || wrapper.querySelector(".d2h-file-diff");
8616
- if (!body)
8617
- return;
8618
- const container = document.createElement("div");
8619
- container.className = "gdp-media";
8620
- let leftHTML;
8621
- let rightHTML;
8622
- if (file.status === "A") {
8623
- leftHTML = '<div class="media-empty">Not in HEAD</div>';
8624
- rightHTML = mediaTag(path, "worktree");
8625
- } else if (file.status === "D") {
8626
- leftHTML = mediaTag(path, "HEAD");
8627
- rightHTML = '<div class="media-empty">Deleted</div>';
8628
- } else {
8629
- leftHTML = mediaTag(path, "HEAD");
8630
- rightHTML = mediaTag(path, "worktree");
8631
- }
8632
- container.innerHTML = '<div class="media-side"><div class="media-label del">Before</div>' + leftHTML + "</div>" + '<div class="media-side"><div class="media-label add">After</div>' + rightHTML + "</div>";
8633
- body.replaceWith(container);
8634
- }
8635
-
8636
- // web-src/core/source-meta.ts
8637
- var SOURCE_SHIKI_LANG_ALIASES = {
8638
- makefile: "make",
8639
- objectivec: "c",
8640
- "objective-c": "c",
8641
- "objective-cpp": "cpp",
8642
- starlark: "python"
8643
- };
8644
- function normalizeSourceShikiLang(lang) {
8645
- if (!lang)
8646
- return null;
8647
- return SOURCE_SHIKI_LANG_ALIASES[lang] || lang;
8648
- }
8649
- function isPreviewableSource(path) {
8650
- return /\.(md|markdown|mdown|mkdn|mdx|html|htm)$/i.test(path);
8651
- }
8652
- function sourcePreviewKind(path) {
8653
- if (/\.(md|markdown|mdown|mkdn|mdx)$/i.test(path))
8654
- return "markdown";
8655
- if (/\.(html|htm)$/i.test(path))
8656
- return "html";
8657
- return null;
8658
- }
8659
- var EXT_TO_LANG = {
8660
- js: "javascript",
8661
- mjs: "javascript",
8662
- cjs: "javascript",
8663
- ts: "typescript",
8664
- tsx: "typescript",
8665
- jsx: "javascript",
8666
- py: "python",
8667
- rb: "ruby",
8668
- go: "go",
8669
- rs: "rust",
8670
- java: "java",
8671
- kt: "kotlin",
8672
- swift: "swift",
8673
- c: "c",
8674
- h: "c",
8675
- cc: "cpp",
8676
- cpp: "cpp",
8677
- hpp: "cpp",
8678
- cs: "csharp",
8679
- php: "php",
8680
- lua: "lua",
8681
- sh: "bash",
8682
- bash: "bash",
8683
- zsh: "bash",
8684
- fish: "bash",
8685
- sql: "sql",
8686
- json: "json",
8687
- yaml: "yaml",
8688
- yml: "yaml",
8689
- toml: "toml",
8690
- tf: "terraform",
8691
- tfvars: "terraform",
8692
- hcl: "terraform",
8693
- xml: "xml",
8694
- html: "xml",
8695
- vue: "xml",
8696
- css: "css",
8697
- scss: "scss",
8698
- md: "markdown",
8699
- dockerfile: "dockerfile",
8700
- proto: "protobuf",
8701
- gradle: "gradle",
8702
- properties: "properties",
8703
- patch: "diff",
8704
- diff: "diff",
8705
- nix: "nix",
8706
- cue: "cue",
8707
- rego: "rego",
8708
- bicep: "bicep",
8709
- bazel: "starlark",
8710
- bzl: "starlark",
8711
- cmake: "cmake",
8712
- groovy: "groovy",
8713
- dart: "dart",
8714
- scala: "scala",
8715
- clj: "clojure",
8716
- cljs: "clojure",
8717
- cljc: "clojure",
8718
- edn: "clojure",
8719
- ex: "elixir",
8720
- exs: "elixir",
8721
- erl: "erlang",
8722
- hrl: "erlang",
8723
- hs: "haskell",
8724
- lhs: "haskell",
8725
- ml: "ocaml",
8726
- mli: "ocaml",
8727
- jl: "julia",
8728
- r: "r",
8729
- rmd: "r",
8730
- pl: "perl",
8731
- pm: "perl",
8732
- tcl: "tcl",
8733
- vim: "vim",
8734
- f: "fortran",
8735
- f90: "fortran",
8736
- m: "objective-c",
8737
- mm: "objective-cpp",
8738
- tex: "tex",
8739
- bib: "bibtex",
8740
- rst: "rst"
8741
- };
8742
- var TEXT_SOURCE_EXTENSIONS = new Set([
8743
- ...Object.keys(EXT_TO_LANG),
8744
- "txt",
8745
- "md",
8746
- "markdown",
8747
- "mdown",
8748
- "mkdn",
8749
- "mdx",
8750
- "json",
8751
- "jsonc",
8752
- "csv",
8753
- "tsv",
8754
- "yaml",
8755
- "yml",
8756
- "toml",
8757
- "hcl",
8758
- "tf",
8759
- "tfvars",
8760
- "tfstate",
8761
- "xml",
8762
- "html",
8763
- "htm",
8764
- "css",
8765
- "scss",
8766
- "sass",
8767
- "less",
8768
- "js",
8769
- "jsx",
8770
- "mjs",
8771
- "cjs",
8772
- "ts",
8773
- "tsx",
8774
- "mts",
8775
- "cts",
8776
- "vue",
8777
- "svelte",
8778
- "astro",
8779
- "rs",
8780
- "go",
8781
- "py",
8782
- "rb",
8783
- "php",
8784
- "java",
8785
- "kt",
8786
- "kts",
8787
- "c",
8788
- "cc",
8789
- "cpp",
8790
- "cxx",
8791
- "h",
8792
- "hpp",
8793
- "cs",
8794
- "swift",
8795
- "sh",
8796
- "bash",
8797
- "zsh",
8798
- "fish",
8799
- "ps1",
8800
- "sql",
8801
- "graphql",
8802
- "graphqls",
8803
- "gql",
8804
- "ini",
8805
- "conf",
8806
- "env",
8807
- "properties",
8808
- "gitignore",
8809
- "dockerignore",
8810
- "editorconfig",
8811
- "lock",
8812
- "log",
8813
- "patch",
8814
- "diff",
8815
- "sum",
8816
- "mk",
8817
- "proto",
8818
- "thrift",
8819
- "prisma",
8820
- "gradle",
8821
- "cmake",
8822
- "nix",
8823
- "cue",
8824
- "rego",
8825
- "bicep",
8826
- "bazel",
8827
- "bzl",
8828
- "dart",
8829
- "scala",
8830
- "clj",
8831
- "cljs",
8832
- "cljc",
8833
- "edn",
8834
- "ex",
8835
- "exs",
8836
- "erl",
8837
- "hrl",
8838
- "hs",
8839
- "lhs",
8840
- "ml",
8841
- "mli",
8842
- "jl",
8843
- "r",
8844
- "rmd",
8845
- "pl",
8846
- "pm",
8847
- "tcl",
8848
- "vim",
8849
- "groovy",
8850
- "f",
8851
- "f90",
8852
- "m",
8853
- "mm",
8854
- "pas",
8855
- "tex",
8856
- "bib",
8857
- "rst",
8858
- "adoc",
8859
- "org",
8860
- "ipynb",
8861
- "ejs",
8862
- "hbs",
8863
- "mustache",
8864
- "liquid",
8865
- "pug"
8866
- ]);
8867
- var TEXT_SOURCE_FILENAMES = new Set([
8868
- "readme",
8869
- "license",
8870
- "copying",
8871
- "authors",
8872
- "contributors",
8873
- "notice",
8874
- "changelog",
8875
- "todo",
8876
- "manifest",
8877
- "version",
8878
- "codeowners",
8879
- "go.mod",
8880
- "build.bazel",
8881
- "workspace.bazel",
8882
- "module.bazel",
8883
- "gemfile",
8884
- "rakefile",
8885
- "procfile",
8886
- "brewfile",
8887
- "gnumakefile",
8888
- "bsdmakefile",
8889
- ".gitattributes",
8890
- ".gitmodules",
8891
- ".npmrc",
8892
- ".nvmrc",
8893
- ".yarnrc",
8894
- ".prettierrc",
8895
- ".eslintrc",
8896
- ".babelrc",
8897
- ".stylelintrc"
8898
- ]);
8899
- var FILENAME_TO_LANG = {
8900
- dockerfile: "dockerfile",
8901
- makefile: "makefile",
8902
- gnumakefile: "makefile",
8903
- bsdmakefile: "makefile",
8904
- "go.mod": "go",
8905
- "build.bazel": "starlark",
8906
- "workspace.bazel": "starlark",
8907
- "module.bazel": "starlark"
8908
- };
8909
- function sourceFileName(path) {
8910
- return (path.split("/").pop() || path).toLowerCase();
8911
- }
8912
- function sourceFileExtension(name) {
8913
- const index = name.lastIndexOf(".");
8914
- return index >= 0 ? name.slice(index + 1) : "";
8915
- }
8916
- function isDockerfileName(name) {
8917
- return /^dockerfile(?:[.-].+)?$/i.test(name);
8918
- }
8919
- function isMakefileName(name) {
8920
- return /^makefile(?:[.-].+)?$/i.test(name);
8921
- }
8922
- function isDotenvName(name) {
8923
- return /^(?:\.?env|.*\.env)(?:[.-].+)?$/i.test(name);
8924
- }
8925
- function sourceDisplayKind(path) {
8926
- if (isVideo(path))
8927
- return "video";
8928
- if (isAudio(path))
8929
- return "audio";
8930
- if (isImage(path))
8931
- return "image";
8932
- if (/\.pdf$/i.test(path))
8933
- return "pdf";
8934
- const name = sourceFileName(path);
8935
- const ext = sourceFileExtension(name);
8936
- if (TEXT_SOURCE_EXTENSIONS.has(ext))
8937
- return "text";
8938
- if (TEXT_SOURCE_FILENAMES.has(name))
8939
- return "text";
8940
- if (isDotenvName(name))
8941
- return "text";
8942
- if (isDockerfileName(name) || isMakefileName(name))
8943
- return "text";
8944
- return "unsupported";
8945
- }
8946
- function formatBytes(bytes) {
8947
- if (!Number.isFinite(bytes) || bytes < 0)
8948
- return "";
8949
- const units = ["B", "KB", "MB", "GB"];
8950
- let value = bytes;
8951
- let unit = 0;
8952
- while (value >= 1024 && unit < units.length - 1) {
8953
- value /= 1024;
8954
- unit++;
8955
- }
8956
- return (unit === 0 ? String(value) : value.toFixed(value >= 10 ? 1 : 2).replace(/\.0+$/, "")) + " " + units[unit];
8957
- }
8958
- function formatFileDate(value) {
8959
- if (!value)
8960
- return "";
8961
- const date = new Date(value);
8962
- if (Number.isNaN(date.getTime()))
8963
- return "";
8964
- return date.toLocaleString(undefined, {
8965
- year: "numeric",
8966
- month: "short",
8967
- day: "numeric",
8968
- hour: "2-digit",
8969
- minute: "2-digit"
8970
- });
8971
- }
8972
- function humanFileKind(path, mime, fallback) {
8973
- const ext = (path.split(".").pop() || "").toLowerCase();
8974
- if (ext === "png")
8975
- return "PNG image";
8976
- if (ext === "jpg" || ext === "jpeg")
8977
- return "JPEG image";
8978
- if (ext === "gif")
8979
- return "GIF image";
8980
- if (ext === "webp")
8981
- return "WebP image";
8982
- if (ext === "svg")
8983
- return "SVG image";
8984
- if (ext === "pdf")
8985
- return "PDF document";
8986
- if (ext === "zip")
8987
- return "ZIP archive";
8988
- if (ext === "mp4")
8989
- return "MP4 video";
8990
- if (ext === "webm")
8991
- return "WebM video";
8992
- if (ext === "mp3")
8993
- return "MP3 audio";
8994
- if (ext === "wav")
8995
- return "WAV audio";
8996
- if (ext === "ogg")
8997
- return "Ogg audio";
8998
- if (ext === "flac")
8999
- return "FLAC audio";
9000
- if (ext === "m4a")
9001
- return "M4A audio";
9002
- if (ext === "aac")
9003
- return "AAC audio";
9004
- if (ext === "opus")
9005
- return "Opus audio";
9006
- if (ext === "mid" || ext === "midi")
9007
- return "MIDI file";
9008
- if (mime?.startsWith("image/"))
9009
- return "Image";
9010
- if (mime?.startsWith("video/"))
9011
- return "Video";
9012
- if (mime?.startsWith("audio/"))
9013
- return "Audio";
9014
- if (mime === "application/pdf")
9015
- return "PDF document";
9016
- if (fallback === "unsupported file")
9017
- return "Binary file";
9018
- return fallback.charAt(0).toUpperCase() + fallback.slice(1);
9019
- }
9020
-
9021
9212
  // web-src/views/file-shell.ts
9022
9213
  function isBlobOrBlameFileRoute(route) {
9023
9214
  return route.screen === "file" && (route.view === "blob" || route.view === "blame");
@@ -9113,7 +9304,7 @@ ${frontmatter.yaml}
9113
9304
  });
9114
9305
  return copy;
9115
9306
  }
9116
- function createFileShellSticky(deps, target, activeTab) {
9307
+ function createFileShellSticky(deps, target, activeTab, options = {}) {
9117
9308
  const sticky = document.createElement("div");
9118
9309
  sticky.className = "gdp-file-detail-sticky";
9119
9310
  const header = document.createElement("div");
@@ -9126,7 +9317,7 @@ ${frontmatter.yaml}
9126
9317
  sticky.appendChild(header);
9127
9318
  const tabsHost = document.createElement("div");
9128
9319
  tabsHost.className = "gdp-file-detail-tabs";
9129
- tabsHost.appendChild(createFileViewTabs(deps, target, activeTab));
9320
+ tabsHost.appendChild(createFileViewTabs(deps, target, activeTab, options));
9130
9321
  sticky.appendChild(tabsHost);
9131
9322
  return { sticky, header, tabsHost };
9132
9323
  }
@@ -20173,8 +20364,10 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20173
20364
  deps.pill.show(next.path, start, end);
20174
20365
  }
20175
20366
  function clear() {
20367
+ const hadSelection = !!selection || !!drag;
20176
20368
  drag = null;
20177
20369
  applySelection(null);
20370
+ return hadSelection;
20178
20371
  }
20179
20372
  const diff = document.querySelector("#diff");
20180
20373
  if (!diff)
@@ -20213,44 +20406,13 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
20213
20406
  document.addEventListener("keydown", (e2) => {
20214
20407
  if (isImeComposing(e2))
20215
20408
  return;
20216
- if (e2.key === "Escape" && selection && !drag)
20409
+ if (e2.key === "Escape" && selection && !drag && !isEditableKeyTarget(e2.target)) {
20217
20410
  clear();
20411
+ }
20218
20412
  });
20219
20413
  return { clear };
20220
20414
  }
20221
20415
 
20222
- // web-src/core/file-path-copy.ts
20223
- function filePathClipboardText(path) {
20224
- return path || "";
20225
- }
20226
- function fileNameClipboardText(path) {
20227
- if (!path)
20228
- return "";
20229
- const parts = path.split("/").filter(Boolean);
20230
- return parts[parts.length - 1] || "";
20231
- }
20232
- function fileReferenceClipboardText(path, start, end) {
20233
- if (!path)
20234
- return "";
20235
- const a2 = Math.max(1, Math.floor(Math.min(start, end)));
20236
- const b2 = Math.max(1, Math.floor(Math.max(start, end)));
20237
- return a2 === b2 ? `@${path}#${a2}` : `@${path}#${a2}-${b2}`;
20238
- }
20239
- function fileReferenceWithCodeClipboardText(path, start, end, lines, lang) {
20240
- const ref = fileReferenceClipboardText(path, start, end);
20241
- if (!ref)
20242
- return "";
20243
- if (!lines || lines.length === 0)
20244
- return ref;
20245
- const fence2 = (lang || "").trim();
20246
- return `${ref}
20247
-
20248
- \`\`\`${fence2}
20249
- ${lines.join(`
20250
- `)}
20251
- \`\`\``;
20252
- }
20253
-
20254
20416
  // web-src/core/ws-highlight.ts
20255
20417
  function isWhitespaceOnlyInlineHighlight(text2) {
20256
20418
  return !!text2 && !/\S/.test(text2);
@@ -22669,6 +22831,16 @@ ${lines.join(`
22669
22831
  selectors: [{ action: "toggle-theme" }],
22670
22832
  description: { en: "Toggle theme", ja: "テーマ切り替え" }
22671
22833
  },
22834
+ {
22835
+ selectors: [
22836
+ { action: "copy-ai-context" },
22837
+ { action: "copy-ai-context-with-code" }
22838
+ ],
22839
+ description: {
22840
+ en: "Copy AI context (with code if a selection is active)",
22841
+ ja: "AI 用コンテキストをコピー(選択行があればコード付き)"
22842
+ }
22843
+ },
22672
22844
  {
22673
22845
  selectors: [
22674
22846
  { action: "annotation-previous" },
@@ -23060,7 +23232,7 @@ code-viewer doctor --cwd /path/to/repo --port 64160 --json`
23060
23232
  rows: [
23061
23233
  [
23062
23234
  "settings.json",
23063
- "Viewer Settings — diff layout, theme, language, sidebar/history widths, font sizes, syntax highlight, ignore-whitespace, hide-tests, scope overrides (omitted dirs / excluded names), upload toggle, annotation panel open/follow/mute/rate, and the last viewed diff range."
23235
+ "Viewer Settings — diff layout, theme, language, sidebar/history widths, font sizes, syntax highlight, ignore-whitespace, hide-tests, scope overrides (omitted dirs / excluded names), upload toggle, annotation panel open/width/follow/mute/rate, and the last viewed diff range."
23064
23236
  ],
23065
23237
  [
23066
23238
  "view-state.json",
@@ -23198,9 +23370,13 @@ code-viewer annotate add-db --db app.db --tab query \\
23198
23370
  "Copy as AI prompt",
23199
23371
  "Each annotation has a copy button that produces a paste-ready prompt block referencing the annotation URL, so you can hand it back to the originating agent."
23200
23372
  ],
23373
+ [
23374
+ "Timestamps and width",
23375
+ "Session and entry rows show creation time. The panel can be resized, and its width is saved per project."
23376
+ ],
23201
23377
  [
23202
23378
  "Persistent state",
23203
- "Open/closed, follow, mute, and rate are stored under .code-viewer/settings.json; annotations themselves live in .code-viewer/annotations.json."
23379
+ "Open/closed, width, follow, mute, and rate are stored under .code-viewer/settings.json; annotations themselves live in .code-viewer/annotations.json."
23204
23380
  ]
23205
23381
  ]
23206
23382
  }
@@ -23569,15 +23745,19 @@ code-viewer query snapshot delete --id snap-abc123`
23569
23745
  rows: [
23570
23746
  [
23571
23747
  "Drag on line numbers",
23572
- "Highlight a range; the floating copy pill prepares @path#L1-L9"
23748
+ "Highlight a range; the floating copy pill prepares @path#1-9"
23573
23749
  ],
23574
23750
  [
23575
23751
  "Click the pill",
23576
- "Copy @path#L1-L9 to the clipboard for pasting into Claude Code / Codex"
23752
+ "Copy @path#1-9 to the clipboard for pasting into an AI agent"
23577
23753
  ],
23578
23754
  [
23579
23755
  "Shift+Click the pill",
23580
- "Copy @path#L1-L9 plus a fenced code block of the selected lines — paste straight to an AI without re-fetching the file"
23756
+ "Copy @path#1-9 plus a fenced code block of the selected lines — paste straight to an AI without re-fetching the file"
23757
+ ],
23758
+ [
23759
+ "x button / Escape",
23760
+ "Clear the selected range and hide the floating copy pill"
23581
23761
  ]
23582
23762
  ]
23583
23763
  }
@@ -23704,7 +23884,7 @@ code-viewer doctor --cwd /path/to/repo --port 64160 --json`
23704
23884
  rows: [
23705
23885
  [
23706
23886
  "settings.json",
23707
- "Viewer Settings — diff レイアウト、テーマ、言語、サイドバー/履歴幅、フォントサイズ、シンタックスハイライト、whitespace 無視、テスト非表示、scope 上書き(除外ディレクトリ / 除外名)、アップロード許可、注釈パネルの開閉/follow/ミュート/再生速度、最後に表示した diff 範囲を保存します。"
23887
+ "Viewer Settings — diff レイアウト、テーマ、言語、サイドバー/履歴幅、フォントサイズ、シンタックスハイライト、whitespace 無視、テスト非表示、scope 上書き(除外ディレクトリ / 除外名)、アップロード許可、注釈パネルの開閉/幅/follow/ミュート/再生速度、最後に表示した diff 範囲を保存します。"
23708
23888
  ],
23709
23889
  [
23710
23890
  "view-state.json",
@@ -23842,9 +24022,13 @@ code-viewer annotate add-db --db app.db --tab query \\
23842
24022
  "AI 用プロンプトとしてコピー",
23843
24023
  "各注釈のコピーボタンが、注釈 URL を含む貼り付け可能なプロンプトを生成します。元のエージェントへそのまま戻せます。"
23844
24024
  ],
24025
+ [
24026
+ "日時と幅",
24027
+ "セッションと注釈の行には作成日時が出ます。パネル幅はリサイズでき、プロジェクト単位で保存されます。"
24028
+ ],
23845
24029
  [
23846
24030
  "永続化",
23847
- "パネルの開閉・follow・ミュート・速度は .code-viewer/settings.json に、注釈自体は .code-viewer/annotations.json に保存されます。"
24031
+ "パネルの開閉・幅・follow・ミュート・速度は .code-viewer/settings.json に、注釈自体は .code-viewer/annotations.json に保存されます。"
23848
24032
  ]
23849
24033
  ]
23850
24034
  }
@@ -24210,15 +24394,19 @@ code-viewer query snapshot delete --id snap-abc123`
24210
24394
  rows: [
24211
24395
  [
24212
24396
  "行番号でドラッグ",
24213
- "範囲をハイライトし、フロートする Copy ピルが @path#L1-L9 を用意"
24397
+ "範囲をハイライトし、フロートする Copy ピルが @path#1-9 を用意"
24214
24398
  ],
24215
24399
  [
24216
24400
  "ピルをクリック",
24217
- "@path#L1-L9 をコピー(Claude Code / Codex への貼り付け用)"
24401
+ "@path#1-9 をコピー(AI エージェントへの貼り付け用)"
24218
24402
  ],
24219
24403
  [
24220
24404
  "Shift+ピルをクリック",
24221
- "@path#L1-L9 と選択行の実コード(フェンス付き)をまとめてコピー — AI へ直接貼れて、ファイル再取得不要"
24405
+ "@path#1-9 と選択行の実コード(フェンス付き)をまとめてコピー — AI へ直接貼れて、ファイル再取得不要"
24406
+ ],
24407
+ [
24408
+ "x ボタン / Escape",
24409
+ "選択範囲を解除し、フロートする Copy ピルを閉じる"
24222
24410
  ]
24223
24411
  ]
24224
24412
  }
@@ -24839,35 +25027,57 @@ code-viewer query snapshot delete --id snap-abc123`
24839
25027
  }
24840
25028
  return ordered;
24841
25029
  }
24842
- function createLineRefPill() {
24843
- const pill = document.createElement("button");
25030
+ function createLineRefPill(deps) {
25031
+ const pill = document.createElement("div");
24844
25032
  pill.id = "line-ref-pill";
24845
- pill.type = "button";
24846
- pill.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
24847
25033
  pill.hidden = true;
25034
+ const copyButton = document.createElement("button");
25035
+ copyButton.id = "line-ref-pill-copy";
25036
+ copyButton.type = "button";
25037
+ copyButton.title = "選択行の参照をコピー(Claude Code / Codex に貼り付け用)。Shift+Click でコード本体も添付。";
25038
+ const closeButton = document.createElement("button");
25039
+ closeButton.id = "line-ref-pill-close";
25040
+ closeButton.type = "button";
25041
+ closeButton.title = "選択を解除";
25042
+ closeButton.setAttribute("aria-label", "選択を解除");
25043
+ closeButton.textContent = "×";
25044
+ pill.appendChild(copyButton);
25045
+ pill.appendChild(closeButton);
24848
25046
  document.body.appendChild(pill);
24849
25047
  let refText = "";
24850
25048
  let currentPath = "";
24851
25049
  let currentStart = 0;
24852
25050
  let currentEnd = 0;
24853
25051
  let feedbackTimer = null;
25052
+ function selectionLineCount() {
25053
+ return currentEnd - currentStart + 1;
25054
+ }
25055
+ function countBadgeHtml() {
25056
+ const count = selectionLineCount();
25057
+ if (count <= 1)
25058
+ return "";
25059
+ const warn = count >= AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD;
25060
+ return `<span class="lrp-count${warn ? " lrp-count-warn" : ""}">${count} lines</span>`;
25061
+ }
24854
25062
  function render(state) {
24855
25063
  pill.classList.toggle("copied", state === "copied" || state === "copied-code");
24856
25064
  if (state === "copied") {
24857
- pill.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
25065
+ copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied!</span>`;
24858
25066
  return;
24859
25067
  }
24860
25068
  if (state === "copied-code") {
24861
- pill.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code</span>`;
25069
+ const count = selectionLineCount();
25070
+ const suffix = count > 1 ? ` (${count} lines)` : "";
25071
+ copyButton.innerHTML = `${CHECK_ICON}<span class="lrp-label">Copied + code${suffix}</span>`;
24862
25072
  return;
24863
25073
  }
24864
25074
  if (state === "failed") {
24865
- pill.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
25075
+ copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">copy failed</span>`;
24866
25076
  return;
24867
25077
  }
24868
- pill.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>`;
25078
+ copyButton.innerHTML = `${COPY_ICON}<span class="lrp-label">Copy</span>` + `<span class="lrp-ref">${escapeHtml2(refText)}</span>` + countBadgeHtml();
24869
25079
  }
24870
- pill.addEventListener("click", async (event) => {
25080
+ copyButton.addEventListener("click", async (event) => {
24871
25081
  if (!refText)
24872
25082
  return;
24873
25083
  const withCode = event.shiftKey;
@@ -24894,6 +25104,9 @@ code-viewer query snapshot delete --id snap-abc123`
24894
25104
  render("ready");
24895
25105
  }, 1200);
24896
25106
  });
25107
+ closeButton.addEventListener("click", () => {
25108
+ deps.onClose();
25109
+ });
24897
25110
  return {
24898
25111
  show(path, start, end) {
24899
25112
  const next = fileReferenceClipboardText(path, start, end);
@@ -26418,8 +26631,8 @@ code-viewer query snapshot delete --id snap-abc123`
26418
26631
  return $("#filelist").classList.contains("tree-virtual");
26419
26632
  }
26420
26633
  function virtualSidebarActiveIndex() {
26421
- const activePath = SIDEBAR_VIRTUAL_ACTIVE_PATH || STATE.activeFile || "";
26422
- return SIDEBAR_VISIBLE_ROWS.findIndex((row) => row.path === activePath);
26634
+ const activePath2 = SIDEBAR_VIRTUAL_ACTIVE_PATH || STATE.activeFile || "";
26635
+ return SIDEBAR_VISIBLE_ROWS.findIndex((row) => row.path === activePath2);
26423
26636
  }
26424
26637
  function selectVirtualSidebarIndex(index, options) {
26425
26638
  if (!SIDEBAR_VISIBLE_ROWS.length)
@@ -28337,6 +28550,8 @@ code-viewer query snapshot delete --id snap-abc123`
28337
28550
  const VIRTUAL_SOURCE_PAGE_SIZE = 2000;
28338
28551
  const VIRTUAL_SOURCE_ROW_HEIGHT = 20;
28339
28552
  const VIRTUAL_SOURCE_HIGHLIGHT_MAX_LINE_LENGTH = 2000;
28553
+ const UNKNOWN_TEXT_SNIFF_BYTES = 8192;
28554
+ const SOURCE_LOADING_SLOW_THRESHOLD_MS = 3000;
28340
28555
  let sourceShikiLoadPromise = null;
28341
28556
  let PREFERRED_SOURCE_TAB = null;
28342
28557
  let SOURCE_CURSOR = null;
@@ -28626,13 +28841,17 @@ code-viewer query snapshot delete --id snap-abc123`
28626
28841
  view.className = "gdp-source-viewer loading";
28627
28842
  const content = document.createElement("div");
28628
28843
  content.className = "gdp-source-loading-content";
28844
+ const spinner = document.createElement("span");
28845
+ spinner.className = "gdp-source-loading-spinner";
28846
+ spinner.setAttribute("aria-hidden", "true");
28847
+ spinner.textContent = "";
28629
28848
  const title = document.createElement("strong");
28630
28849
  title.className = "gdp-source-loading-title";
28631
28850
  title.textContent = "Loading file";
28632
28851
  const message = document.createElement("div");
28633
28852
  message.className = "gdp-source-loading-message";
28634
28853
  message.textContent = `${target.path} at ${target.ref}`;
28635
- content.append(title, message);
28854
+ content.append(spinner, title, message);
28636
28855
  if (onCancel) {
28637
28856
  const button = document.createElement("button");
28638
28857
  button.type = "button";
@@ -28646,6 +28865,27 @@ code-viewer query snapshot delete --id snap-abc123`
28646
28865
  content.appendChild(button);
28647
28866
  }
28648
28867
  view.appendChild(content);
28868
+ const startedAt = Date.now();
28869
+ window.setTimeout(() => {
28870
+ if (!view.isConnected)
28871
+ return;
28872
+ const slowNote = document.createElement("div");
28873
+ slowNote.className = "gdp-source-loading-slow-note";
28874
+ content.insertBefore(slowNote, onCancel ? content.lastChild : null);
28875
+ let interval = null;
28876
+ const updateSlowNote = () => {
28877
+ if (!view.isConnected) {
28878
+ if (interval !== null)
28879
+ window.clearInterval(interval);
28880
+ return;
28881
+ }
28882
+ const elapsed = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
28883
+ title.textContent = `Still loading file (${elapsed}s)`;
28884
+ slowNote.textContent = `Taking longer than usual (${elapsed}s elapsed). You can cancel below.`;
28885
+ };
28886
+ updateSlowNote();
28887
+ interval = window.setInterval(updateSlowNote, 1000);
28888
+ }, SOURCE_LOADING_SLOW_THRESHOLD_MS);
28649
28889
  if (body)
28650
28890
  body.replaceWith(view);
28651
28891
  else
@@ -28703,16 +28943,42 @@ code-viewer query snapshot delete --id snap-abc123`
28703
28943
  else
28704
28944
  card.appendChild(view);
28705
28945
  }
28946
+ function renderSourceInternalPath(card, target, kind) {
28947
+ const body = card.querySelector(".gdp-file-detail-body, .d2h-files-diff, .d2h-file-diff, .gdp-media, .gdp-source-viewer");
28948
+ const info = createSourceFileInfo(target, "internal metadata", {
28949
+ loadMeta: false
28950
+ });
28951
+ const extraChildren = [info];
28952
+ if (kind === "code-viewer") {
28953
+ const link2 = document.createElement("a");
28954
+ link2.className = "gdp-btn gdp-btn-sm gdp-source-download";
28955
+ link2.href = buildRawFileUrl(target);
28956
+ link2.textContent = "Download raw";
28957
+ link2.target = "_blank";
28958
+ link2.rel = "noreferrer";
28959
+ extraChildren.push(link2);
28960
+ }
28961
+ const view = renderUnsupportedPreview({
28962
+ message: kind === "code-viewer" ? "This path is managed by code-viewer and is not previewed from the file viewer." : "Git internal metadata is not previewed from the file viewer.",
28963
+ extraChildren
28964
+ });
28965
+ if (body)
28966
+ body.replaceWith(view);
28967
+ else
28968
+ card.appendChild(view);
28969
+ }
28706
28970
  function renderHtmlPreview(target, html) {
28707
28971
  return renderHtmlPreviewFrame(`${target.path} preview`, html);
28708
28972
  }
28709
- function createSourceFileInfo(target, kind) {
28973
+ function createSourceFileInfo(target, kind, options = {}) {
28710
28974
  const info = document.createElement("div");
28711
28975
  info.className = "gdp-source-file-info";
28712
28976
  const type = document.createElement("span");
28713
28977
  type.className = "kind";
28714
28978
  type.textContent = humanFileKind(target.path, undefined, kind);
28715
28979
  info.appendChild(type);
28980
+ if (options.loadMeta === false)
28981
+ return info;
28716
28982
  loadRawFileInfo(target).then((meta) => {
28717
28983
  type.textContent = humanFileKind(target.path, meta.type, kind);
28718
28984
  if (meta.size != null) {
@@ -28937,6 +29203,18 @@ code-viewer query snapshot delete --id snap-abc123`
28937
29203
  function shouldVirtualizeSource(textValue, lines) {
28938
29204
  return textValue.length >= VIRTUAL_SOURCE_SIZE_THRESHOLD || lines.length >= VIRTUAL_SOURCE_LINE_THRESHOLD;
28939
29205
  }
29206
+ async function sourceLooksTextByContent(target, meta, signal) {
29207
+ if (meta.size === 0)
29208
+ return true;
29209
+ const end = meta.size != null ? Math.max(0, Math.min(meta.size, UNKNOWN_TEXT_SNIFF_BYTES) - 1) : UNKNOWN_TEXT_SNIFF_BYTES - 1;
29210
+ const response = await trackLoad(fetch(buildRawFileUrl(target), {
29211
+ headers: { Range: `bytes=0-${end}` },
29212
+ signal
29213
+ }));
29214
+ if (!response.ok && response.status !== 206)
29215
+ return false;
29216
+ return isLikelyTextBytes(new Uint8Array(await response.arrayBuffer()));
29217
+ }
28940
29218
  function isVirtualSourceDisabled() {
28941
29219
  return deps.STATE.route.screen === "file" && deps.STATE.route.virtual === "off";
28942
29220
  }
@@ -29633,15 +29911,16 @@ code-viewer query snapshot delete --id snap-abc123`
29633
29911
  const card = document.createElement("article");
29634
29912
  card.className = "gdp-file-shell loaded gdp-standalone-source gdp-source-mode";
29635
29913
  card.dataset.path = target.path;
29914
+ const internalKind = sourceInternalPathKind(target.path);
29636
29915
  const wrapper = document.createElement("div");
29637
29916
  wrapper.className = "gdp-file-detail-wrapper";
29638
- const activeTab = STATE.route.screen === "file" && STATE.route.preview ? "preview" : "code";
29917
+ const activeTab = !internalKind && STATE.route.screen === "file" && STATE.route.preview ? "preview" : "code";
29639
29918
  const { sticky, header } = createFileShellSticky({
29640
29919
  currentRange,
29641
29920
  setRoute,
29642
29921
  setPreferredSourceTab,
29643
29922
  createFileBreadcrumb
29644
- }, target, activeTab);
29923
+ }, target, activeTab, internalKind ? { includeFileTabs: false, previewable: false } : {});
29645
29924
  const pathActions = header.querySelector(".gdp-file-detail-path") || header;
29646
29925
  pathActions.appendChild(createOpenPathButton(target.path, "file-parent", "open parent folder in OS"));
29647
29926
  if (repoTarget && canTrashWorktreeRef(repoTarget)) {
@@ -29651,11 +29930,13 @@ code-viewer query snapshot delete --id snap-abc123`
29651
29930
  loadRepo();
29652
29931
  }));
29653
29932
  }
29654
- loadRawFileInfo(target).then((meta) => {
29655
- if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
29656
- return;
29657
- header.appendChild(createFileDetailMeta(target, meta));
29658
- });
29933
+ if (!internalKind) {
29934
+ loadRawFileInfo(target).then((meta) => {
29935
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
29936
+ return;
29937
+ header.appendChild(createFileDetailMeta(target, meta));
29938
+ });
29939
+ }
29659
29940
  if (!repoTarget) {
29660
29941
  const back = document.createElement("button");
29661
29942
  back.type = "button";
@@ -29674,11 +29955,24 @@ code-viewer query snapshot delete --id snap-abc123`
29674
29955
  wrapper.appendChild(detailBody);
29675
29956
  card.appendChild(wrapper);
29676
29957
  mountFileShellCard({ $, repoFileTargetFromRoute, renderRepoBlobSidebar, placeSidebarToggle }, target, card, repoTarget);
29958
+ if (internalKind) {
29959
+ renderSourceInternalPath(card, target, internalKind);
29960
+ return;
29961
+ }
29677
29962
  const controller = new AbortController;
29678
29963
  ACTIVE_SOURCE_LOAD = { controller, req, target, card };
29679
29964
  renderSourceLoading(card, target, () => cancelActiveSourceLoad("user"));
29680
29965
  try {
29681
- const displayKind = sourceDisplayKind(target.path);
29966
+ let displayKind = sourceDisplayKind(target.path);
29967
+ let rawInfo = null;
29968
+ if (displayKind === "unsupported") {
29969
+ rawInfo = await loadRawFileInfo(target);
29970
+ if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
29971
+ return;
29972
+ if (await sourceLooksTextByContent(target, rawInfo, controller.signal)) {
29973
+ displayKind = "text";
29974
+ }
29975
+ }
29682
29976
  if (displayKind === "unsupported") {
29683
29977
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
29684
29978
  return;
@@ -29694,7 +29988,7 @@ code-viewer query snapshot delete --id snap-abc123`
29694
29988
  return;
29695
29989
  }
29696
29990
  if (displayKind === "text") {
29697
- const meta = await loadRawFileInfo(target);
29991
+ const meta = rawInfo ?? await loadRawFileInfo(target);
29698
29992
  if (req !== SOURCE_REQ_SEQ || !sourceTargetsEqual(sourceTargetFromRoute(), target))
29699
29993
  return;
29700
29994
  if (!isVirtualSourceDisabled() && meta.size != null && meta.size >= VIRTUAL_SOURCE_SIZE_THRESHOLD) {
@@ -30259,7 +30553,7 @@ code-viewer query snapshot delete --id snap-abc123`
30259
30553
  function routeFromLocation() {
30260
30554
  const savedLanguage = viewerLanguageFromSearch(window.location.search) || savedViewerLanguage();
30261
30555
  const parsedRoute = parseRoute(window.location.pathname, window.location.search, savedRange());
30262
- const routeBase = parsedRoute.screen === "unknown" ? { screen: "diff", range: parsedRoute.range } : parsedRoute;
30556
+ const routeBase = parsedRoute.screen === "unknown" ? { screen: "diff", range: parsedRoute.range } : normalizeInternalFileRoute(parsedRoute);
30263
30557
  return routeBase.screen === "help" && !new URLSearchParams(window.location.search).has("lang") ? { ...routeBase, lang: savedLanguage } : routeBase;
30264
30558
  }
30265
30559
  function applyPersistedStateToState() {
@@ -30292,6 +30586,7 @@ code-viewer query snapshot delete --id snap-abc123`
30292
30586
  applySidebarHidden(STATE.sidebarHidden, { persist: false });
30293
30587
  applyHistoryWidth(STATE.historyWidth, false);
30294
30588
  applySidebarWidth(STATE.sbWidth, { persist: false });
30589
+ ANNOTATIONS_UI?.applyAnnotationPanelWidth(APP_SETTINGS.annotationPanelWidth ?? 380, false);
30295
30590
  setLayout(STATE.layout, false);
30296
30591
  applyTheme();
30297
30592
  localizeViewerChrome();
@@ -30324,8 +30619,30 @@ code-viewer query snapshot delete --id snap-abc123`
30324
30619
  })();
30325
30620
  let highlightConfigured = false;
30326
30621
  let REPO_SIDEBAR_REF = null;
30327
- const LINE_REF_PILL = createLineRefPill();
30622
+ const LINE_REF_PILL = createLineRefPill({
30623
+ onClose: () => {
30624
+ clearLineSelection();
30625
+ }
30626
+ });
30328
30627
  const DIFF_LINE_SELECT = createDiffLineSelect({ pill: LINE_REF_PILL });
30628
+ function clearRenderedSourceLineTargets() {
30629
+ document.querySelectorAll(".gdp-source-line-target").forEach((row) => {
30630
+ row.classList.remove("gdp-source-line-target");
30631
+ });
30632
+ }
30633
+ function clearLineSelection() {
30634
+ const route = STATE.route;
30635
+ if (route.screen === "file" && route.line) {
30636
+ const { line, ...rest } = route;
30637
+ setRoute(rest, true);
30638
+ return true;
30639
+ }
30640
+ if (route.screen === "diff") {
30641
+ return DIFF_LINE_SELECT.clear();
30642
+ }
30643
+ LINE_REF_PILL.hide();
30644
+ return false;
30645
+ }
30329
30646
  function syncLineRefPill() {
30330
30647
  const route = STATE.route;
30331
30648
  if (route.screen === "diff")
@@ -30335,7 +30652,10 @@ code-viewer query snapshot delete --id snap-abc123`
30335
30652
  const start = typeof route.line === "number" ? route.line : route.line.start;
30336
30653
  const end = typeof route.line === "number" ? route.line : route.line.end;
30337
30654
  LINE_REF_PILL.show(route.path, start, end);
30655
+ return;
30338
30656
  }
30657
+ if (route.screen === "file")
30658
+ clearRenderedSourceLineTargets();
30339
30659
  }
30340
30660
  const SIDEBAR = createSidebar({
30341
30661
  $,
@@ -30558,7 +30878,12 @@ code-viewer query snapshot delete --id snap-abc123`
30558
30878
  queryHistory: "query history",
30559
30879
  settings: "viewer settings",
30560
30880
  theme: "toggle theme",
30561
- product: "code viewer"
30881
+ product: "code viewer",
30882
+ copyAiContext: "Copy AI context (Shift+Click to include code)",
30883
+ copyAiContextCopied: "Copied AI context",
30884
+ copyAiContextCopiedWithCode: (lines) => `Copied AI context + code (${lines} line${lines === 1 ? "" : "s"})`,
30885
+ copyAiContextFailed: "Copy failed",
30886
+ copyAiContextEmpty: "Nothing to copy here"
30562
30887
  },
30563
30888
  topbar: {
30564
30889
  resetRange: "reset to HEAD .. worktree",
@@ -30664,7 +30989,12 @@ code-viewer query snapshot delete --id snap-abc123`
30664
30989
  queryHistory: "クエリ履歴",
30665
30990
  settings: "ビューア設定",
30666
30991
  theme: "テーマ切り替え",
30667
- product: "code viewer"
30992
+ product: "code viewer",
30993
+ copyAiContext: "AI 用コンテキストをコピー(Shift+Click でコードも添付)",
30994
+ copyAiContextCopied: "コピーしました",
30995
+ copyAiContextCopiedWithCode: (lines) => `コピーしました(コード付き・${lines}行)`,
30996
+ copyAiContextFailed: "コピーに失敗しました",
30997
+ copyAiContextEmpty: "コピーする内容がありません"
30668
30998
  },
30669
30999
  topbar: {
30670
31000
  resetRange: "HEAD .. worktree に戻す",
@@ -30798,8 +31128,15 @@ code-viewer query snapshot delete --id snap-abc123`
30798
31128
  viewerSettings.setAttribute("aria-label", text2.global.settings);
30799
31129
  }
30800
31130
  const theme = document.querySelector("#theme");
30801
- if (theme)
31131
+ if (theme) {
30802
31132
  theme.title = text2.global.theme;
31133
+ theme.setAttribute("aria-label", text2.global.theme);
31134
+ }
31135
+ const copyAiContext = document.querySelector("#copy-ai-context");
31136
+ if (copyAiContext) {
31137
+ copyAiContext.title = text2.global.copyAiContext;
31138
+ copyAiContext.setAttribute("aria-label", text2.global.copyAiContext);
31139
+ }
30803
31140
  const refReset = document.querySelector("#ref-reset");
30804
31141
  if (refReset)
30805
31142
  refReset.title = text2.topbar.resetRange;
@@ -31011,6 +31348,22 @@ code-viewer query snapshot delete --id snap-abc123`
31011
31348
  });
31012
31349
  return highlightLoadPromise;
31013
31350
  }
31351
+ function routeCanUseSyntaxHighlighter(route = STATE.route) {
31352
+ if (!STATE.syntaxHighlight)
31353
+ return false;
31354
+ if (route.screen === "diff" || route.screen === "history")
31355
+ return true;
31356
+ return route.screen === "file" && sourceInternalPathKind(route.path) === null;
31357
+ }
31358
+ function ensureSyntaxHighlighterForRoute() {
31359
+ if (!routeCanUseSyntaxHighlighter())
31360
+ return;
31361
+ loadSyntaxHighlighter().then((hljsRef) => {
31362
+ if (!hljsRef)
31363
+ return;
31364
+ rerenderLoadedDiffs();
31365
+ });
31366
+ }
31014
31367
  function setLayout(layout, persist = true) {
31015
31368
  STATE.layout = layout;
31016
31369
  if (persist)
@@ -31290,6 +31643,21 @@ code-viewer query snapshot delete --id snap-abc123`
31290
31643
  function isHistoryPanelRoute(route) {
31291
31644
  return route.screen === "history" || isFileHistoryRoute(route);
31292
31645
  }
31646
+ function normalizeInternalFileRoute(route) {
31647
+ if (route.screen !== "file")
31648
+ return route;
31649
+ if (sourceInternalPathKind(route.path) === null)
31650
+ return route;
31651
+ if (route.view === "blob" && !route.preview && !route.line)
31652
+ return route;
31653
+ return {
31654
+ screen: "file",
31655
+ path: route.path,
31656
+ ref: route.ref,
31657
+ range: route.range,
31658
+ view: "blob"
31659
+ };
31660
+ }
31293
31661
  function parkRangeForHistory() {
31294
31662
  if (preHistoryRange === null)
31295
31663
  preHistoryRange = { from: STATE.from, to: STATE.to };
@@ -31396,7 +31764,7 @@ code-viewer query snapshot delete --id snap-abc123`
31396
31764
  }
31397
31765
  function setRoute(route, replace2 = false) {
31398
31766
  const previousRoute = STATE.route;
31399
- let nextRoute = route.screen === "unknown" ? { screen: "diff", range: route.range } : route;
31767
+ let nextRoute = route.screen === "unknown" ? { screen: "diff", range: route.range } : normalizeInternalFileRoute(route);
31400
31768
  if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute)) {
31401
31769
  if (preHistoryRange)
31402
31770
  nextRoute = { ...nextRoute, range: preHistoryRange };
@@ -31676,6 +32044,24 @@ code-viewer query snapshot delete --id snap-abc123`
31676
32044
  applyViewedState,
31677
32045
  enqueueInitialLoads
31678
32046
  } = DIFF_VIEW;
32047
+ function setGlobalHeaderIcons() {
32048
+ const annotationsIcon = document.querySelector("#annotations-toggle .goi-icon");
32049
+ if (annotationsIcon) {
32050
+ annotationsIcon.innerHTML = iconSvg("octicon-comment-discussion", COMMENT_DISCUSSION_16_PATH);
32051
+ }
32052
+ const doctorIcon = document.querySelector("#doctor-btn .goi-icon");
32053
+ if (doctorIcon) {
32054
+ doctorIcon.innerHTML = iconSvg("octicon-pulse", PULSE_16_PATH);
32055
+ }
32056
+ const themeButton = document.querySelector("#theme");
32057
+ if (themeButton) {
32058
+ themeButton.innerHTML = iconSvg("octicon-moon", MOON_16_PATH);
32059
+ }
32060
+ const copyAiContextButton = document.querySelector("#copy-ai-context");
32061
+ if (copyAiContextButton) {
32062
+ copyAiContextButton.innerHTML = iconSvg("octicon-copy", COPY_16_PATHS);
32063
+ }
32064
+ }
31679
32065
  applySidebarFontSize();
31680
32066
  applyCodeFontSize();
31681
32067
  applySidebarHidden(STATE.sidebarHidden, { persist: false });
@@ -31683,6 +32069,7 @@ code-viewer query snapshot delete --id snap-abc123`
31683
32069
  installHistoryPageDom();
31684
32070
  hydrateRefSelectorMounts();
31685
32071
  setSidebarTreeActionIcons();
32072
+ setGlobalHeaderIcons();
31686
32073
  $$(".sb-view-seg button").forEach((b2) => {
31687
32074
  b2.addEventListener("click", () => {
31688
32075
  STATE.sbView = b2.dataset.view || "tree";
@@ -31698,6 +32085,67 @@ code-viewer query snapshot delete --id snap-abc123`
31698
32085
  event.preventDefault();
31699
32086
  toggleDoctorSheet();
31700
32087
  });
32088
+ let copyAiContextFeedbackTimer = null;
32089
+ $("#copy-ai-context")?.addEventListener("click", async (event) => {
32090
+ const button = event.currentTarget;
32091
+ const feedback = document.querySelector("#copy-ai-context-feedback");
32092
+ const selectionTarget = resolveSelectionTarget(STATE.route);
32093
+ let selectionCode;
32094
+ if (event.shiftKey && selectionTarget) {
32095
+ const renderedLines = readRenderedLines(selectionTarget.path, selectionTarget.start, selectionTarget.end);
32096
+ if (renderedLines.length > 0) {
32097
+ selectionCode = {
32098
+ lines: renderedLines,
32099
+ lang: langFromPath(selectionTarget.path)
32100
+ };
32101
+ }
32102
+ }
32103
+ const text2 = aiContextClipboardText({
32104
+ route: STATE.route,
32105
+ diffFrom: STATE.from,
32106
+ diffTo: STATE.to,
32107
+ selectionCode
32108
+ });
32109
+ const finish = (ok, withCode, lineCount, reason) => {
32110
+ const label = reason === "empty" ? uiText().global.copyAiContextEmpty : ok ? withCode ? uiText().global.copyAiContextCopiedWithCode(lineCount) : uiText().global.copyAiContextCopied : uiText().global.copyAiContextFailed;
32111
+ const isLargeCopy = ok && withCode && lineCount >= AI_CONTEXT_LARGE_SELECTION_LINE_THRESHOLD;
32112
+ const stateClass = reason === "empty" ? "" : ok ? isLargeCopy ? "warn" : "copied" : "failed";
32113
+ button.classList.remove("copied", "failed", "warn");
32114
+ if (stateClass)
32115
+ button.classList.add(stateClass);
32116
+ button.title = label;
32117
+ button.setAttribute("aria-label", label);
32118
+ if (feedback) {
32119
+ feedback.textContent = label;
32120
+ feedback.classList.remove("copied", "failed", "warn");
32121
+ if (stateClass)
32122
+ feedback.classList.add(stateClass);
32123
+ feedback.hidden = false;
32124
+ }
32125
+ if (copyAiContextFeedbackTimer)
32126
+ clearTimeout(copyAiContextFeedbackTimer);
32127
+ copyAiContextFeedbackTimer = setTimeout(() => {
32128
+ copyAiContextFeedbackTimer = null;
32129
+ button.classList.remove("copied", "failed", "warn");
32130
+ button.title = uiText().global.copyAiContext;
32131
+ button.setAttribute("aria-label", uiText().global.copyAiContext);
32132
+ if (feedback) {
32133
+ feedback.hidden = true;
32134
+ feedback.classList.remove("copied", "failed", "warn");
32135
+ }
32136
+ }, 1200);
32137
+ };
32138
+ if (!text2) {
32139
+ finish(false, false, 0, "empty");
32140
+ return;
32141
+ }
32142
+ try {
32143
+ await navigator.clipboard.writeText(text2);
32144
+ finish(true, !!selectionCode, selectionCode?.lines.length ?? 0);
32145
+ } catch {
32146
+ finish(false, false, 0);
32147
+ }
32148
+ });
31701
32149
  $("#scope-settings-close")?.addEventListener("click", closeScopeSettings);
31702
32150
  $("#scope-omit-reset")?.addEventListener("click", resetScopeSettings);
31703
32151
  $("#viewer-language")?.addEventListener("change", (event) => {
@@ -32050,6 +32498,18 @@ code-viewer query snapshot delete --id snap-abc123`
32050
32498
  $("#theme").click();
32051
32499
  return true;
32052
32500
  }
32501
+ if (action === "copy-ai-context") {
32502
+ $("#copy-ai-context")?.click();
32503
+ return true;
32504
+ }
32505
+ if (action === "copy-ai-context-with-code") {
32506
+ $("#copy-ai-context")?.dispatchEvent(new MouseEvent("click", {
32507
+ bubbles: true,
32508
+ cancelable: true,
32509
+ shiftKey: true
32510
+ }));
32511
+ return true;
32512
+ }
32053
32513
  if (action === "open-help") {
32054
32514
  openHelpKeybindings({
32055
32515
  getRoute: () => STATE.route,
@@ -32078,6 +32538,10 @@ code-viewer query snapshot delete --id snap-abc123`
32078
32538
  if (e2.__gdpVirtualSourcePagingHandled)
32079
32539
  return;
32080
32540
  const targetEl = e2.target;
32541
+ if (e2.key === "Escape" && !isEditableKeyTarget(targetEl) && clearLineSelection()) {
32542
+ e2.preventDefault();
32543
+ return;
32544
+ }
32081
32545
  if ((e2.ctrlKey || e2.metaKey) && !e2.shiftKey && !e2.altKey && e2.key.toLowerCase() === "z" && !isEditableKeyTarget(targetEl)) {
32082
32546
  if (await undoLastAction())
32083
32547
  e2.preventDefault();
@@ -32390,7 +32854,8 @@ code-viewer query snapshot delete --id snap-abc123`
32390
32854
  const routeLanguage = viewerLanguageFromSearch(window.location.search);
32391
32855
  if (routeLanguage && routeLanguage !== STATE.language)
32392
32856
  setViewerLanguage(routeLanguage);
32393
- const nextRoute = parsedRoute.screen === "unknown" ? { screen: "diff", range: parsedRoute.range } : parsedRoute;
32857
+ let nextRoute = parsedRoute.screen === "unknown" ? { screen: "diff", range: parsedRoute.range } : parsedRoute;
32858
+ nextRoute = normalizeInternalFileRoute(nextRoute);
32394
32859
  if (previousRoute.screen === "database" && nextRoute.screen !== "database")
32395
32860
  DATABASE_VIEW.suspend();
32396
32861
  if (isHistoryPanelRoute(previousRoute) && !isHistoryPanelRoute(nextRoute))
@@ -32400,6 +32865,7 @@ code-viewer query snapshot delete --id snap-abc123`
32400
32865
  STATE.route = nextRoute.screen === "help" && !new URLSearchParams(window.location.search).has("lang") ? { ...nextRoute, lang: STATE.language } : nextRoute;
32401
32866
  STATE.from = STATE.route.range.from;
32402
32867
  STATE.to = STATE.route.range.to;
32868
+ ensureSyntaxHighlighterForRoute();
32403
32869
  if (STATE.route.screen === "repo" || STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history"))
32404
32870
  STATE.repoRef = STATE.route.ref || "worktree";
32405
32871
  ANNOTATIONS_UI?.restoreSessionFromUrl();
@@ -32491,11 +32957,7 @@ code-viewer query snapshot delete --id snap-abc123`
32491
32957
  patchSettings({ syntaxHighlight: on });
32492
32958
  setHighlightButton(on && getHljs() ? "loaded" : "idle");
32493
32959
  if (on) {
32494
- loadSyntaxHighlighter().then((hljsRef) => {
32495
- if (!hljsRef)
32496
- return;
32497
- rerenderLoadedDiffs();
32498
- });
32960
+ ensureSyntaxHighlighterForRoute();
32499
32961
  } else {
32500
32962
  rerenderLoadedDiffs();
32501
32963
  }
@@ -32581,6 +33043,8 @@ code-viewer query snapshot delete --id snap-abc123`
32581
33043
  getRoute: () => STATE.route,
32582
33044
  getAnnotationPanelOpen: () => APP_SETTINGS.annotationPanelOpen === true,
32583
33045
  setAnnotationPanelOpenState: (open) => patchSettings({ annotationPanelOpen: open }),
33046
+ getAnnotationPanelWidth: () => APP_SETTINGS.annotationPanelWidth,
33047
+ setAnnotationPanelWidth: (width) => patchSettings({ annotationPanelWidth: width }),
32584
33048
  getAnnotationFollow: () => APP_SETTINGS.annotationFollow !== false,
32585
33049
  setAnnotationFollow: (follow) => patchSettings({ annotationFollow: follow }),
32586
33050
  leaveDatabaseView: () => {
@@ -32600,6 +33064,34 @@ code-viewer query snapshot delete --id snap-abc123`
32600
33064
  patchSettings({ range: currentRange() });
32601
33065
  }
32602
33066
  });
33067
+ (function setupAnnotationPanelResizer() {
33068
+ const panel = document.getElementById("annotation-panel");
33069
+ const handle = document.getElementById("annotation-panel-resizer");
33070
+ if (!panel || !handle)
33071
+ return;
33072
+ let dragging = false;
33073
+ let startX = 0;
33074
+ let startW = 0;
33075
+ handle.addEventListener("mousedown", (e2) => {
33076
+ dragging = true;
33077
+ startX = e2.clientX;
33078
+ startW = panel.offsetWidth;
33079
+ document.body.classList.add("gdp-annotation-resizing");
33080
+ e2.preventDefault();
33081
+ });
33082
+ window.addEventListener("mousemove", (e2) => {
33083
+ if (!dragging)
33084
+ return;
33085
+ ANNOTATIONS_UI?.applyAnnotationPanelWidth(startW - (e2.clientX - startX), false);
33086
+ });
33087
+ window.addEventListener("mouseup", () => {
33088
+ if (!dragging)
33089
+ return;
33090
+ dragging = false;
33091
+ document.body.classList.remove("gdp-annotation-resizing");
33092
+ ANNOTATIONS_UI?.applyAnnotationPanelWidth(panel.offsetWidth);
33093
+ });
33094
+ })();
32603
33095
  replaceUrlWithCurrentRoute();
32604
33096
  createAnnotationsPlayer({
32605
33097
  $,
@@ -32835,46 +33327,85 @@ code-viewer query snapshot delete --id snap-abc123`
32835
33327
  }
32836
33328
  }, 350);
32837
33329
  }
32838
- const es = new EventSource("/events");
32839
33330
  const catchUpGate = createCatchUpGate(() => Date.now(), 1000);
32840
33331
  let openedOnce = false;
32841
- es.addEventListener("update", (event) => {
32842
- const raw = event.data;
32843
- let paths = null;
32844
- if (raw && raw !== "tick") {
32845
- try {
32846
- const parsed = JSON.parse(raw);
32847
- if (Array.isArray(parsed.paths))
32848
- paths = parsed.paths;
32849
- } catch {}
32850
- }
32851
- scheduleSseLoad(paths);
32852
- });
32853
- es.addEventListener("watch-limit", (event) => {
32854
- const raw = event.data;
32855
- const limit = Number(raw);
32856
- if (Number.isFinite(limit) && limit > 0)
32857
- showWatchLimitBanner(limit);
32858
- });
32859
- es.addEventListener("reload", () => location.reload());
32860
- es.addEventListener("annotation", (event) => {
32861
- ANNOTATIONS_UI?.handleSse(event.data);
32862
- });
32863
- es.addEventListener("db-query", (event) => {
32864
- DATABASE_VIEW.handleSse("db-query", event.data);
32865
- });
32866
- es.addEventListener("db-snapshot", (event) => {
32867
- DATABASE_VIEW.handleSse("db-snapshot", event.data);
32868
- });
32869
- es.addEventListener("error", () => setStatus("error"));
32870
- es.addEventListener("open", () => {
32871
- setStatus("live");
32872
- if (!openedOnce) {
32873
- openedOnce = true;
33332
+ let eventSource = null;
33333
+ let eventSourceConnectTimer = null;
33334
+ function shouldConnectEventSource() {
33335
+ return document.visibilityState === "visible";
33336
+ }
33337
+ function disconnectEventSource() {
33338
+ if (eventSourceConnectTimer !== null) {
33339
+ window.clearTimeout(eventSourceConnectTimer);
33340
+ eventSourceConnectTimer = null;
33341
+ }
33342
+ eventSource?.close();
33343
+ eventSource = null;
33344
+ }
33345
+ function connectEventSource() {
33346
+ if (!shouldConnectEventSource())
33347
+ return;
33348
+ if (eventSource)
33349
+ return;
33350
+ const es = new EventSource("/events");
33351
+ eventSource = es;
33352
+ es.addEventListener("update", (event) => {
33353
+ const raw = event.data;
33354
+ let paths = null;
33355
+ if (raw && raw !== "tick") {
33356
+ try {
33357
+ const parsed = JSON.parse(raw);
33358
+ if (Array.isArray(parsed.paths))
33359
+ paths = parsed.paths;
33360
+ } catch {}
33361
+ }
33362
+ scheduleSseLoad(paths);
33363
+ });
33364
+ es.addEventListener("watch-limit", (event) => {
33365
+ const raw = event.data;
33366
+ const limit = Number(raw);
33367
+ if (Number.isFinite(limit) && limit > 0)
33368
+ showWatchLimitBanner(limit);
33369
+ });
33370
+ es.addEventListener("reload", () => location.reload());
33371
+ es.addEventListener("annotation", (event) => {
33372
+ ANNOTATIONS_UI?.handleSse(event.data);
33373
+ });
33374
+ es.addEventListener("db-query", (event) => {
33375
+ DATABASE_VIEW.handleSse("db-query", event.data);
33376
+ });
33377
+ es.addEventListener("db-snapshot", (event) => {
33378
+ DATABASE_VIEW.handleSse("db-snapshot", event.data);
33379
+ });
33380
+ es.addEventListener("error", () => setStatus("error"));
33381
+ es.addEventListener("open", () => {
33382
+ setStatus("live");
33383
+ if (!openedOnce) {
33384
+ openedOnce = true;
33385
+ return;
33386
+ }
33387
+ catchUpDiff();
33388
+ });
33389
+ }
33390
+ function scheduleEventSourceConnect() {
33391
+ if (!shouldConnectEventSource())
33392
+ return;
33393
+ if (eventSource || eventSourceConnectTimer !== null)
33394
+ return;
33395
+ const schedule = () => {
33396
+ eventSourceConnectTimer = window.setTimeout(() => {
33397
+ eventSourceConnectTimer = null;
33398
+ connectEventSource();
33399
+ }, 1000);
33400
+ };
33401
+ if (document.readyState === "complete") {
33402
+ schedule();
32874
33403
  return;
32875
33404
  }
32876
- catchUpDiff();
32877
- });
33405
+ window.addEventListener("load", schedule, { once: true });
33406
+ }
33407
+ scheduleEventSourceConnect();
33408
+ window.addEventListener("pagehide", disconnectEventSource);
32878
33409
  function catchUpDiff() {
32879
33410
  const historyWorktreeSelected = HISTORY_VIEW.isWorktreeSelected();
32880
33411
  if (!shouldAutoLoadCurrentRoute())
@@ -32890,9 +33421,18 @@ code-viewer query snapshot delete --id snap-abc123`
32890
33421
  load({ force: true });
32891
33422
  }
32892
33423
  document.addEventListener("visibilitychange", () => {
32893
- if (!document.hidden)
32894
- catchUpDiff();
33424
+ if (document.hidden) {
33425
+ disconnectEventSource();
33426
+ return;
33427
+ }
33428
+ scheduleEventSourceConnect();
33429
+ catchUpDiff();
33430
+ ANNOTATIONS_UI?.refreshAnnotations();
33431
+ });
33432
+ window.addEventListener("focus", () => {
33433
+ scheduleEventSourceConnect();
33434
+ catchUpDiff();
33435
+ ANNOTATIONS_UI?.refreshAnnotations();
32895
33436
  });
32896
- window.addEventListener("focus", catchUpDiff);
32897
33437
  })();
32898
33438
  })();