@youtyan/code-viewer 0.9.2 → 0.10.0

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
@@ -574,7 +574,6 @@ ${lines.join(`
574
574
  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";
575
575
  var QUESTION_16_PATH = "M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.92 6.085h.001a.749.749 0 1 1-1.342-.67c.169-.339.436-.701.849-.977C6.845 4.16 7.369 4 8 4a2.756 2.756 0 0 1 1.637.525c.503.377.863.965.863 1.725 0 .448-.115.83-.329 1.15-.205.307-.47.513-.692.662-.109.072-.22.138-.313.195l-.006.004a6.24 6.24 0 0 0-.26.16.952.952 0 0 0-.276.245.75.75 0 0 1-1.248-.832c.184-.264.42-.489.692-.661.103-.067.207-.132.313-.195l.007-.004c.1-.061.182-.11.258-.161a.969.969 0 0 0 .277-.245C8.96 6.514 9 6.427 9 6.25a.612.612 0 0 0-.262-.525A1.27 1.27 0 0 0 8 5.5c-.369 0-.595.09-.74.187a1.01 1.01 0 0 0-.34.398ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z";
576
576
  var MARK_GITHUB_16_PATH = "M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656";
577
- var BOOK_16_PATH = "M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z";
578
577
  var SEARCH_16_PATH = "M10.68 11.74a6 6 0 0 1-7.917-8.984 6 6 0 0 1 8.997 7.905l3.05 3.05a.75.75 0 1 1-1.06 1.06l-3.07-3.03ZM11.5 7a4.499 4.499 0 1 1-8.997 0A4.499 4.499 0 0 1 11.5 7Z";
579
578
  var DOWNLOAD_16_PATHS = [
580
579
  "M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z",
@@ -875,6 +874,19 @@ ${lines.join(`
875
874
  return { url: `${target}${lineHash}`, provider: "github" };
876
875
  }
877
876
 
877
+ // web-src/core/tools.ts
878
+ var TOOL_IDS = [
879
+ "markdown",
880
+ "mermaid",
881
+ "json"
882
+ ];
883
+ var DEFAULT_TOOL_ID = "markdown";
884
+ var MIN_TOOLS_SHEET_WIDTH = 480;
885
+ var MAX_TOOLS_SHEET_WIDTH = 4000;
886
+ function isToolId(value) {
887
+ return TOOL_IDS.includes(value);
888
+ }
889
+
878
890
  // web-src/core/routes.ts
879
891
  function assertNever(value) {
880
892
  throw new Error(`unhandled route: ${JSON.stringify(value)}`);
@@ -1145,18 +1157,28 @@ ${lines.join(`
1145
1157
  const params = new URLSearchParams(search);
1146
1158
  return params.get("doctor") === "open";
1147
1159
  }
1148
- function withDoctorOverlay(url, open) {
1160
+ function withQueryParam(url, key, value) {
1149
1161
  const queryIdx = url.indexOf("?");
1150
1162
  const base = queryIdx >= 0 ? url.slice(0, queryIdx) : url;
1151
1163
  const query = queryIdx >= 0 ? url.slice(queryIdx + 1) : "";
1152
1164
  const params = new URLSearchParams(query);
1153
- if (open)
1154
- params.set("doctor", "open");
1165
+ if (value === null)
1166
+ params.delete(key);
1155
1167
  else
1156
- params.delete("doctor");
1168
+ params.set(key, value);
1157
1169
  const qs = params.toString();
1158
1170
  return qs ? `${base}?${qs}` : base;
1159
1171
  }
1172
+ function withDoctorOverlay(url, open) {
1173
+ return withQueryParam(url, "doctor", open ? "open" : null);
1174
+ }
1175
+ function parseToolsOverlay(search) {
1176
+ const raw = new URLSearchParams(search).get("tools");
1177
+ return isToolId(raw) ? raw : null;
1178
+ }
1179
+ function withToolsOverlay(url, tool) {
1180
+ return withQueryParam(url, "tools", tool);
1181
+ }
1160
1182
 
1161
1183
  // web-src/views/media-embed.ts
1162
1184
  var MEDIA_RE = /\.(png|jpe?g|gif|webp|svg|avif|bmp|ico|mp4|webm|mov|mp3|wav|ogg|flac|m4a|aac|opus)(\?.*)?$/i;
@@ -7606,6 +7628,19 @@ ${lines.join(`
7606
7628
  }
7607
7629
 
7608
7630
  // web-src/core/shiki-loader.ts
7631
+ function highlightToInnerHtml(code2, lang, highlighter) {
7632
+ if (!highlighter || !code2)
7633
+ return "";
7634
+ const html = highlighter.codeToHtml(code2, {
7635
+ lang,
7636
+ themes: { light: "github-light", dark: "github-dark" },
7637
+ defaultColor: false
7638
+ });
7639
+ const template = document.createElement("template");
7640
+ template.innerHTML = html;
7641
+ const pre = template.content.querySelector("pre");
7642
+ return pre ? pre.innerHTML : "";
7643
+ }
7609
7644
  var cache = new Map;
7610
7645
  function loadShikiHighlighter(options) {
7611
7646
  const key = JSON.stringify({
@@ -7992,7 +8027,9 @@ ${frontmatter.yaml}
7992
8027
  else
7993
8028
  break;
7994
8029
  }
7995
- if (window.innerHeight + scrollRoot.scrollTop >= scrollRoot.scrollHeight - 4) {
8030
+ const container = scrollableAncestor(root);
8031
+ const atBottom = container ? container.scrollTop + container.clientHeight >= container.scrollHeight - 4 : window.innerHeight + scrollRoot.scrollTop >= scrollRoot.scrollHeight - 4;
8032
+ if (atBottom) {
7996
8033
  active = entries[entries.length - 1];
7997
8034
  }
7998
8035
  entries.forEach((entry) => {
@@ -8004,8 +8041,9 @@ ${frontmatter.yaml}
8004
8041
  if (!raf)
8005
8042
  raf = requestAnimationFrame(update);
8006
8043
  };
8007
- window.addEventListener("scroll", schedule, {
8044
+ document.addEventListener("scroll", schedule, {
8008
8045
  passive: true,
8046
+ capture: true,
8009
8047
  signal: controller.signal
8010
8048
  });
8011
8049
  window.addEventListener("resize", schedule, { signal: controller.signal });
@@ -8028,7 +8066,21 @@ ${frontmatter.yaml}
8028
8066
  function decodeHashFragment(hash) {
8029
8067
  return decodeUriComponentSafe(hash.startsWith("#") ? hash.slice(1) : hash);
8030
8068
  }
8069
+ function scrollableAncestor(element) {
8070
+ for (let node = element.parentElement;node; node = node.parentElement) {
8071
+ const style = getComputedStyle(node);
8072
+ if (/(auto|scroll)/.test(style.overflowY) && node.scrollHeight > node.clientHeight)
8073
+ return node;
8074
+ }
8075
+ return null;
8076
+ }
8031
8077
  function scrollMarkdownSectionIntoView(section, behavior) {
8078
+ const container = scrollableAncestor(section);
8079
+ if (container) {
8080
+ const top2 = section.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop - 12;
8081
+ container.scrollTo({ top: Math.max(0, top2), behavior });
8082
+ return;
8083
+ }
8032
8084
  const top = section.getBoundingClientRect().top + window.scrollY - markdownAnchorOffset() - 12;
8033
8085
  window.scrollTo({ top: Math.max(0, top), behavior });
8034
8086
  }
@@ -13165,6 +13217,75 @@ ${frontmatter.yaml}
13165
13217
  };
13166
13218
  }
13167
13219
 
13220
+ // web-src/core/diagram-viewport.ts
13221
+ var MIN_SCALE = 0.2;
13222
+ var MAX_SCALE = 3;
13223
+ var BUTTON_STEP = 0.2;
13224
+ var WHEEL_STEP = 0.1;
13225
+ function clampScale(value) {
13226
+ return Math.max(MIN_SCALE, Math.min(MAX_SCALE, value));
13227
+ }
13228
+ function createDiagramViewport(options) {
13229
+ const container = document.createElement("div");
13230
+ container.className = options.containerClassName;
13231
+ const content = document.createElement("div");
13232
+ content.className = options.contentClassName;
13233
+ container.appendChild(content);
13234
+ let scale = 1;
13235
+ function applyZoom(next) {
13236
+ scale = clampScale(next);
13237
+ content.style.transform = `scale(${scale})`;
13238
+ content.style.transformOrigin = "top left";
13239
+ }
13240
+ let dragState = null;
13241
+ container.addEventListener("mousedown", (event) => {
13242
+ if (event.button !== 0)
13243
+ return;
13244
+ dragState = {
13245
+ x: event.clientX,
13246
+ y: event.clientY,
13247
+ sl: container.scrollLeft,
13248
+ st: container.scrollTop
13249
+ };
13250
+ container.style.cursor = "grabbing";
13251
+ event.preventDefault();
13252
+ });
13253
+ const onWindowMouseMove = (event) => {
13254
+ if (!dragState)
13255
+ return;
13256
+ container.scrollLeft = dragState.sl - (event.clientX - dragState.x);
13257
+ container.scrollTop = dragState.st - (event.clientY - dragState.y);
13258
+ };
13259
+ const onWindowMouseUp = () => {
13260
+ if (!dragState)
13261
+ return;
13262
+ dragState = null;
13263
+ container.style.cursor = "";
13264
+ };
13265
+ window.addEventListener("mousemove", onWindowMouseMove);
13266
+ window.addEventListener("mouseup", onWindowMouseUp);
13267
+ container.addEventListener("wheel", (event) => {
13268
+ if (!event.ctrlKey && !event.metaKey)
13269
+ return;
13270
+ event.preventDefault();
13271
+ applyZoom(scale + (event.deltaY > 0 ? -WHEEL_STEP : WHEEL_STEP));
13272
+ }, { passive: false });
13273
+ applyZoom(1);
13274
+ return {
13275
+ container,
13276
+ content,
13277
+ zoomIn: () => applyZoom(scale + BUTTON_STEP),
13278
+ zoomOut: () => applyZoom(scale - BUTTON_STEP),
13279
+ reset: () => applyZoom(1),
13280
+ dispose() {
13281
+ dragState = null;
13282
+ container.style.cursor = "";
13283
+ window.removeEventListener("mousemove", onWindowMouseMove);
13284
+ window.removeEventListener("mouseup", onWindowMouseUp);
13285
+ }
13286
+ };
13287
+ }
13288
+
13168
13289
  // web-src/views/database/er-diagram.ts
13169
13290
  function mermaidType(sqlType) {
13170
13291
  const upper = sqlType.toUpperCase();
@@ -13255,30 +13376,17 @@ ${frontmatter.yaml}
13255
13376
  copyBtn.textContent = text3().er.copyMermaid;
13256
13377
  copyBtn.title = text3().er.copyMermaidTitle;
13257
13378
  toolbar.append(zoomIn, zoomOut, zoomReset, copyBtn);
13258
- const container = document.createElement("div");
13259
- container.className = "db-er-container";
13260
- const svgWrap = document.createElement("div");
13261
- svgWrap.className = "db-er-svg-wrap";
13262
- container.appendChild(svgWrap);
13379
+ const viewport = createDiagramViewport({
13380
+ containerClassName: "db-er-container",
13381
+ contentClassName: "db-er-svg-wrap"
13382
+ });
13383
+ const container = viewport.container;
13384
+ const svgWrap = viewport.content;
13263
13385
  el.append(toolbar, container);
13264
- let scale = 1;
13265
13386
  let lastMarkup = "";
13266
- function applyZoom() {
13267
- svgWrap.style.transform = `scale(${scale})`;
13268
- svgWrap.style.transformOrigin = "top left";
13269
- }
13270
- zoomIn.addEventListener("click", () => {
13271
- scale = Math.min(3, scale + 0.2);
13272
- applyZoom();
13273
- });
13274
- zoomOut.addEventListener("click", () => {
13275
- scale = Math.max(0.2, scale - 0.2);
13276
- applyZoom();
13277
- });
13278
- zoomReset.addEventListener("click", () => {
13279
- scale = 1;
13280
- applyZoom();
13281
- });
13387
+ zoomIn.addEventListener("click", () => viewport.zoomIn());
13388
+ zoomOut.addEventListener("click", () => viewport.zoomOut());
13389
+ zoomReset.addEventListener("click", () => viewport.reset());
13282
13390
  copyBtn.addEventListener("click", () => {
13283
13391
  if (lastMarkup) {
13284
13392
  navigator.clipboard.writeText(lastMarkup).then(() => {
@@ -13291,46 +13399,10 @@ ${frontmatter.yaml}
13291
13399
  });
13292
13400
  }
13293
13401
  });
13294
- let dragState = null;
13295
- container.addEventListener("mousedown", (e2) => {
13296
- if (e2.button !== 0)
13297
- return;
13298
- dragState = {
13299
- x: e2.clientX,
13300
- y: e2.clientY,
13301
- sl: container.scrollLeft,
13302
- st: container.scrollTop
13303
- };
13304
- container.style.cursor = "grabbing";
13305
- e2.preventDefault();
13306
- });
13307
- const onWindowMouseMove = (e2) => {
13308
- if (!dragState)
13309
- return;
13310
- container.scrollLeft = dragState.sl - (e2.clientX - dragState.x);
13311
- container.scrollTop = dragState.st - (e2.clientY - dragState.y);
13312
- };
13313
- const onWindowMouseUp = () => {
13314
- if (dragState) {
13315
- dragState = null;
13316
- container.style.cursor = "";
13317
- }
13318
- };
13319
- window.addEventListener("mousemove", onWindowMouseMove);
13320
- window.addEventListener("mouseup", onWindowMouseUp);
13321
- container.addEventListener("wheel", (e2) => {
13322
- if (e2.ctrlKey || e2.metaKey) {
13323
- e2.preventDefault();
13324
- const delta = e2.deltaY > 0 ? -0.1 : 0.1;
13325
- scale = Math.max(0.2, Math.min(3, scale + delta));
13326
- applyZoom();
13327
- }
13328
- }, { passive: false });
13329
13402
  async function render(schema, columnsMap) {
13330
13403
  el.hidden = false;
13331
13404
  svgWrap.innerHTML = "";
13332
- scale = 1;
13333
- applyZoom();
13405
+ viewport.reset();
13334
13406
  const tables = schema.tables.filter((t2) => t2.type === "table");
13335
13407
  if (tables.length === 0) {
13336
13408
  svgWrap.textContent = text3().er.noTables;
@@ -13357,13 +13429,10 @@ ${frontmatter.yaml}
13357
13429
  el.hidden = true;
13358
13430
  svgWrap.innerHTML = "";
13359
13431
  lastMarkup = "";
13360
- dragState = null;
13361
- container.style.cursor = "";
13362
13432
  }
13363
13433
  function dispose() {
13364
13434
  clear();
13365
- window.removeEventListener("mousemove", onWindowMouseMove);
13366
- window.removeEventListener("mouseup", onWindowMouseUp);
13435
+ viewport.dispose();
13367
13436
  }
13368
13437
  function localize() {
13369
13438
  const t2 = text3().er;
@@ -13651,17 +13720,7 @@ ${frontmatter.yaml}
13651
13720
 
13652
13721
  // web-src/views/database/shiki-sql.ts
13653
13722
  function highlightSqlToInnerHtml(code2, highlighter) {
13654
- if (!highlighter || !code2)
13655
- return "";
13656
- const html = highlighter.codeToHtml(code2, {
13657
- lang: "sql",
13658
- themes: { light: "github-light", dark: "github-dark" },
13659
- defaultColor: false
13660
- });
13661
- const template = document.createElement("template");
13662
- template.innerHTML = html;
13663
- const pre = template.content.querySelector("pre");
13664
- return pre ? pre.innerHTML : "";
13723
+ return highlightToInnerHtml(code2, "sql", highlighter);
13665
13724
  }
13666
13725
 
13667
13726
  // web-src/views/database/query-editor.ts
@@ -26455,6 +26514,19 @@ ${entry.message}` : entry.detail ?? entry.message ?? "";
26455
26514
  }
26456
26515
  ]
26457
26516
  },
26517
+ {
26518
+ title: "Scratch on pasted text",
26519
+ blocks: [
26520
+ {
26521
+ kind: "paragraph",
26522
+ text: "The Tools item in the header menu opens a drawer for text you paste, without leaving the screen you are on. It holds a Markdown preview (the same renderer as the file preview, so the table of contents, task lists, frontmatter, code highlighting and ```mermaid fences all work), a Mermaid preview with zoom and drag-pan, and a JSON / YAML tool that reads either format and re-emits it as formatted JSON or YAML — a validator and a converter in one."
26523
+ },
26524
+ {
26525
+ kind: "paragraph",
26526
+ text: "Each tool keeps its own draft in .code-viewer/tools.json, so the drawer reopens where you left it. The open tool is part of the URL (?tools=markdown), which makes it shareable and survives a reload, and the drawer width is draggable from the grip on its left edge."
26527
+ }
26528
+ ]
26529
+ },
26458
26530
  {
26459
26531
  title: "Environment doctor",
26460
26532
  blocks: [
@@ -26513,6 +26585,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
26513
26585
  "db-ui.json",
26514
26586
  "Datastore UI preferences — column widths per (DB, table, column) and toggle states such as Rails FK inference and the S3 tooltip."
26515
26587
  ],
26588
+ [
26589
+ "tools.json",
26590
+ "Tools drawer state — the pasted draft for each tool (Markdown, Mermaid, JSON / YAML), the tool you used last, and the drawer width."
26591
+ ],
26516
26592
  [
26517
26593
  "annotations.json",
26518
26594
  "AI annotation walkthroughs — sessions with their ordered steps (file, line range, title, body), used by the annotation panel and synced live to open tabs over SSE."
@@ -27150,6 +27226,19 @@ code-viewer query agent-help`
27150
27226
  }
27151
27227
  ]
27152
27228
  },
27229
+ {
27230
+ title: "貼り付けたテキストを扱う",
27231
+ blocks: [
27232
+ {
27233
+ kind: "paragraph",
27234
+ text: "ヘッダメニューの Tools は、いま見ている画面を離れずに使える貼り付け用のドロワーを開きます。Markdown プレビュー(ファイルプレビューと同じ描画なので、目次・タスクリスト・frontmatter・コードハイライト・```mermaid フェンスがそのまま効きます)、ズームとドラッグ移動ができる Mermaid プレビュー、JSON と YAML のどちらでも読み取って整形し直す JSON / YAML ツール(検証と相互変換を兼ねます)が入っています。"
27235
+ },
27236
+ {
27237
+ kind: "paragraph",
27238
+ text: "各ツールの入力は .code-viewer/tools.json に保存されるので、開き直すと続きから使えます。開いているツールは URL(?tools=markdown)に載るので共有もリロードもでき、ドロワーの幅は左端のグリップをドラッグして変えられます。"
27239
+ }
27240
+ ]
27241
+ },
27153
27242
  {
27154
27243
  title: "環境ドクター",
27155
27244
  blocks: [
@@ -27208,6 +27297,10 @@ code-viewer doctor --bin git=/opt/bin/git --bin docker=/opt/bin/docker --bin gh=
27208
27297
  "db-ui.json",
27209
27298
  "データストア UI の設定 — (DB, テーブル, カラム) ごとの列幅、Rails FK 推測や S3 ツールチップなどのトグル状態。"
27210
27299
  ],
27300
+ [
27301
+ "tools.json",
27302
+ "ツールドロワーの状態 — 各ツール(Markdown / Mermaid / JSON・YAML)に貼り付けた下書き、最後に使ったツール、ドロワーの幅。"
27303
+ ],
27211
27304
  [
27212
27305
  "annotations.json",
27213
27306
  "AI 注釈のウォークスルー — セッションと順序付きステップ(ファイル、行範囲、タイトル、本文)。注釈パネルが読み、SSE で開いているタブへもライブ同期されます。"
@@ -36195,6 +36288,1017 @@ code-viewer query agent-help`
36195
36288
  };
36196
36289
  }
36197
36290
 
36291
+ // web-src/views/tools/i18n.ts
36292
+ var TEXT2 = {
36293
+ en: {
36294
+ title: "Tools",
36295
+ open: "tools (Markdown / Mermaid / JSON)",
36296
+ close: "Close",
36297
+ resizeSheet: "Resize the tools drawer",
36298
+ saveFailed: "could not save this draft",
36299
+ loadFailed: "could not load saved drafts",
36300
+ tabs: {
36301
+ markdown: "Markdown",
36302
+ mermaid: "Mermaid",
36303
+ json: "JSON / YAML"
36304
+ },
36305
+ pane: {
36306
+ input: "Input",
36307
+ clear: "clear",
36308
+ clearTitle: "Clear the input",
36309
+ copy: "copy",
36310
+ copyTitle: "Copy the output",
36311
+ copied: "copied",
36312
+ copyFailed: "copy failed",
36313
+ resize: "Resize input and output panes",
36314
+ emptyInput: "Paste something on the left to see it rendered here."
36315
+ },
36316
+ markdown: {
36317
+ output: "Preview",
36318
+ placeholder: "Paste Markdown here…"
36319
+ },
36320
+ mermaid: {
36321
+ output: "Diagram",
36322
+ placeholder: `Paste Mermaid source here, for example:
36323
+ graph TD
36324
+ A --> B`,
36325
+ zoomIn: "Zoom in",
36326
+ zoomOut: "Zoom out",
36327
+ zoomReset: "Reset zoom",
36328
+ loadError: "Failed to load the Mermaid renderer.",
36329
+ renderError: "Could not render this diagram. Check the syntax."
36330
+ },
36331
+ json: {
36332
+ output: "Output",
36333
+ placeholder: "Paste JSON or YAML here…",
36334
+ outputFormat: "output format",
36335
+ asJson: "JSON",
36336
+ asYaml: "YAML",
36337
+ minify: "minify",
36338
+ minifyTitle: "Emit JSON on a single line",
36339
+ minifyYamlTitle: "Minify applies to JSON output only",
36340
+ parsedAsJson: "read as JSON",
36341
+ parsedAsYaml: "read as YAML",
36342
+ invalid: (message) => `Invalid input: ${message}`,
36343
+ loadError: "Failed to load the YAML parser."
36344
+ }
36345
+ },
36346
+ ja: {
36347
+ title: "ツール",
36348
+ open: "ツール (Markdown / Mermaid / JSON)",
36349
+ close: "閉じる",
36350
+ resizeSheet: "ツールの幅を変える",
36351
+ saveFailed: "この下書きを保存できません",
36352
+ loadFailed: "保存済みの下書きを読み出せません",
36353
+ tabs: {
36354
+ markdown: "Markdown",
36355
+ mermaid: "Mermaid",
36356
+ json: "JSON / YAML"
36357
+ },
36358
+ pane: {
36359
+ input: "入力",
36360
+ clear: "クリア",
36361
+ clearTitle: "入力を空にする",
36362
+ copy: "コピー",
36363
+ copyTitle: "出力をコピーする",
36364
+ copied: "コピーしました",
36365
+ copyFailed: "コピーできません",
36366
+ resize: "入力と出力の幅を変える",
36367
+ emptyInput: "左に貼り付けると、ここに結果が出ます。"
36368
+ },
36369
+ markdown: {
36370
+ output: "プレビュー",
36371
+ placeholder: "Markdown を貼り付け…"
36372
+ },
36373
+ mermaid: {
36374
+ output: "図",
36375
+ placeholder: `Mermaid の記法を貼り付け。例:
36376
+ graph TD
36377
+ A --> B`,
36378
+ zoomIn: "拡大",
36379
+ zoomOut: "縮小",
36380
+ zoomReset: "等倍に戻す",
36381
+ loadError: "Mermaid の読み込みに失敗しました。",
36382
+ renderError: "この図を描けませんでした。記法を確認してください。"
36383
+ },
36384
+ json: {
36385
+ output: "出力",
36386
+ placeholder: "JSON か YAML を貼り付け…",
36387
+ outputFormat: "出力形式",
36388
+ asJson: "JSON",
36389
+ asYaml: "YAML",
36390
+ minify: "1行化",
36391
+ minifyTitle: "JSON を1行で出力する",
36392
+ minifyYamlTitle: "1行化は JSON 出力のときだけ効きます",
36393
+ parsedAsJson: "JSON として解釈",
36394
+ parsedAsYaml: "YAML として解釈",
36395
+ invalid: (message) => `解釈できません: ${message}`,
36396
+ loadError: "YAML パーサの読み込みに失敗しました。"
36397
+ }
36398
+ }
36399
+ };
36400
+ function toolsText(lang) {
36401
+ return TEXT2[lang];
36402
+ }
36403
+
36404
+ // web-src/core/drag-resizer.ts
36405
+ var DEFAULT_KEYBOARD_STEP = 16;
36406
+ function attachDragResizer(options) {
36407
+ const { handle } = options;
36408
+ const step = options.keyboardStep ?? DEFAULT_KEYBOARD_STEP;
36409
+ let pointerId = null;
36410
+ let startX = 0;
36411
+ let startSize = 0;
36412
+ function setActive(active) {
36413
+ if (!options.activeClassTarget || !options.activeClassName)
36414
+ return;
36415
+ options.activeClassTarget.classList.toggle(options.activeClassName, active);
36416
+ }
36417
+ const onPointerDown = (event) => {
36418
+ if (event.button !== 0)
36419
+ return;
36420
+ pointerId = event.pointerId;
36421
+ startX = event.clientX;
36422
+ startSize = options.getSize();
36423
+ try {
36424
+ handle.setPointerCapture?.(event.pointerId);
36425
+ } catch {}
36426
+ setActive(true);
36427
+ event.preventDefault();
36428
+ };
36429
+ const onPointerMove = (event) => {
36430
+ if (pointerId !== event.pointerId)
36431
+ return;
36432
+ options.applySize(startSize + (event.clientX - startX) * options.direction);
36433
+ };
36434
+ const onPointerEnd = (event) => {
36435
+ if (pointerId !== event.pointerId)
36436
+ return;
36437
+ pointerId = null;
36438
+ setActive(false);
36439
+ options.onEnd?.();
36440
+ };
36441
+ const onKeyDown = (event) => {
36442
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight")
36443
+ return;
36444
+ const delta = (event.key === "ArrowRight" ? 1 : -1) * options.direction;
36445
+ options.applySize(options.getSize() + delta * step * (event.shiftKey ? 4 : 1));
36446
+ event.preventDefault();
36447
+ options.onEnd?.();
36448
+ };
36449
+ handle.addEventListener("pointerdown", onPointerDown);
36450
+ handle.addEventListener("pointermove", onPointerMove);
36451
+ handle.addEventListener("pointerup", onPointerEnd);
36452
+ handle.addEventListener("pointercancel", onPointerEnd);
36453
+ handle.addEventListener("keydown", onKeyDown);
36454
+ return () => {
36455
+ handle.removeEventListener("pointerdown", onPointerDown);
36456
+ handle.removeEventListener("pointermove", onPointerMove);
36457
+ handle.removeEventListener("pointerup", onPointerEnd);
36458
+ handle.removeEventListener("pointercancel", onPointerEnd);
36459
+ handle.removeEventListener("keydown", onKeyDown);
36460
+ pointerId = null;
36461
+ setActive(false);
36462
+ };
36463
+ }
36464
+
36465
+ // web-src/core/yaml-loader.ts
36466
+ var yamlPromise = null;
36467
+ function loadYaml() {
36468
+ if (!yamlPromise) {
36469
+ yamlPromise = import("/yaml.js").then((mod) => mod).catch(() => null);
36470
+ }
36471
+ return yamlPromise;
36472
+ }
36473
+
36474
+ // web-src/views/tools/scratchpad-pane.ts
36475
+ var RENDER_DEBOUNCE_MS = 200;
36476
+ var STATUS_CLEAR_MS = 1800;
36477
+ function createPaneAction(label, title) {
36478
+ const button = document.createElement("button");
36479
+ button.type = "button";
36480
+ button.className = "tools-pane-action";
36481
+ button.textContent = label;
36482
+ button.title = title;
36483
+ return button;
36484
+ }
36485
+ function createHead(title) {
36486
+ const head = document.createElement("header");
36487
+ head.className = "tools-pane-head";
36488
+ const titleEl = document.createElement("span");
36489
+ titleEl.className = "tools-pane-title";
36490
+ titleEl.textContent = title;
36491
+ head.appendChild(titleEl);
36492
+ return { head, titleEl };
36493
+ }
36494
+ function createScratchpadPane(text3, outputTitle, placeholder, options) {
36495
+ let currentText = text3;
36496
+ const el = document.createElement("div");
36497
+ el.className = `tools-pane ${options.toolClassName}`;
36498
+ const inputSide = document.createElement("section");
36499
+ inputSide.className = "tools-pane-side tools-pane-input";
36500
+ const { head: inputHead, titleEl: inputTitle } = createHead(text3.pane.input);
36501
+ const clearBtn = document.createElement("button");
36502
+ clearBtn.type = "button";
36503
+ clearBtn.className = "tools-pane-action";
36504
+ clearBtn.textContent = text3.pane.clear;
36505
+ clearBtn.title = text3.pane.clearTitle;
36506
+ inputHead.appendChild(clearBtn);
36507
+ const input2 = document.createElement("textarea");
36508
+ input2.className = "tools-textarea";
36509
+ input2.spellcheck = false;
36510
+ input2.placeholder = placeholder;
36511
+ input2.value = options.initialText;
36512
+ inputSide.append(inputHead, input2);
36513
+ const resizer = document.createElement("div");
36514
+ resizer.className = "tools-pane-resizer";
36515
+ resizer.role = "separator";
36516
+ resizer.tabIndex = 0;
36517
+ resizer.setAttribute("aria-orientation", "vertical");
36518
+ resizer.setAttribute("aria-label", text3.pane.resize);
36519
+ const outputSide = document.createElement("section");
36520
+ outputSide.className = "tools-pane-side tools-pane-output";
36521
+ const { head: outputHead, titleEl: outputTitleEl } = createHead(outputTitle);
36522
+ const actions = document.createElement("div");
36523
+ actions.className = "tools-pane-actions";
36524
+ for (const action of options.outputActions ?? [])
36525
+ actions.appendChild(action);
36526
+ const status = document.createElement("span");
36527
+ status.className = "tools-pane-status";
36528
+ status.role = "status";
36529
+ outputHead.append(actions, status);
36530
+ const output = document.createElement("div");
36531
+ output.className = "tools-pane-body";
36532
+ outputSide.append(outputHead, output);
36533
+ el.append(inputSide, resizer, outputSide);
36534
+ let statusTimer = null;
36535
+ let pinnedStatus = null;
36536
+ let normalStatus = { message: "", tone: "info" };
36537
+ function applyStatus(state) {
36538
+ status.textContent = state.message;
36539
+ status.classList.toggle("tools-pane-status-error", state.tone === "error");
36540
+ }
36541
+ function clearStatusTimer() {
36542
+ if (!statusTimer)
36543
+ return;
36544
+ clearTimeout(statusTimer);
36545
+ statusTimer = null;
36546
+ }
36547
+ function setStatus(message, tone = "info") {
36548
+ clearStatusTimer();
36549
+ normalStatus = { message, tone };
36550
+ if (pinnedStatus)
36551
+ return;
36552
+ applyStatus(normalStatus);
36553
+ }
36554
+ function flashStatus(message, tone = "info") {
36555
+ clearStatusTimer();
36556
+ if (pinnedStatus)
36557
+ return;
36558
+ applyStatus({ message, tone });
36559
+ statusTimer = setTimeout(() => {
36560
+ statusTimer = null;
36561
+ applyStatus(pinnedStatus ?? normalStatus);
36562
+ }, STATUS_CLEAR_MS);
36563
+ }
36564
+ function setPinnedStatus(message, tone = "error") {
36565
+ clearStatusTimer();
36566
+ pinnedStatus = message ? { message, tone } : null;
36567
+ applyStatus(pinnedStatus ?? normalStatus);
36568
+ }
36569
+ let renderController = null;
36570
+ let debounceTimer = null;
36571
+ function renderNow() {
36572
+ renderController?.abort();
36573
+ const controller = new AbortController;
36574
+ renderController = controller;
36575
+ const value = input2.value;
36576
+ if (!value.trim()) {
36577
+ const empty = document.createElement("p");
36578
+ empty.className = "tools-pane-empty";
36579
+ empty.textContent = currentText.pane.emptyInput;
36580
+ output.replaceChildren(empty);
36581
+ setStatus("");
36582
+ return;
36583
+ }
36584
+ (async () => {
36585
+ try {
36586
+ await options.render(value, output, controller.signal);
36587
+ } catch (err) {
36588
+ if (controller.signal.aborted)
36589
+ return;
36590
+ const message = err instanceof Error ? err.message : String(err);
36591
+ setStatus(message, "error");
36592
+ }
36593
+ })();
36594
+ }
36595
+ function scheduleRender() {
36596
+ if (debounceTimer)
36597
+ clearTimeout(debounceTimer);
36598
+ debounceTimer = setTimeout(() => {
36599
+ debounceTimer = null;
36600
+ renderNow();
36601
+ }, RENDER_DEBOUNCE_MS);
36602
+ }
36603
+ input2.addEventListener("input", () => {
36604
+ options.onInput(input2.value);
36605
+ renderController?.abort();
36606
+ renderController = null;
36607
+ scheduleRender();
36608
+ });
36609
+ clearBtn.addEventListener("click", () => {
36610
+ if (!input2.value)
36611
+ return;
36612
+ input2.value = "";
36613
+ options.onInput("");
36614
+ input2.focus();
36615
+ renderNow();
36616
+ });
36617
+ const MIN_SIDE_WIDTH = 220;
36618
+ function applyInputWidth(width) {
36619
+ const total = el.getBoundingClientRect().width;
36620
+ const max = Math.max(MIN_SIDE_WIDTH, total - MIN_SIDE_WIDTH);
36621
+ const clamped = Math.max(MIN_SIDE_WIDTH, Math.min(max, width));
36622
+ el.style.setProperty("--tools-input-width", `${Math.round(clamped)}px`);
36623
+ }
36624
+ const detachResizer = attachDragResizer({
36625
+ handle: resizer,
36626
+ getSize: () => inputSide.getBoundingClientRect().width,
36627
+ applySize: applyInputWidth,
36628
+ direction: 1,
36629
+ activeClassTarget: el,
36630
+ activeClassName: "tools-pane-resizing"
36631
+ });
36632
+ return {
36633
+ el,
36634
+ input: input2,
36635
+ output,
36636
+ setStatus,
36637
+ flashStatus,
36638
+ setPinnedStatus,
36639
+ refresh: renderNow,
36640
+ setText(next) {
36641
+ input2.value = next;
36642
+ },
36643
+ getText: () => input2.value,
36644
+ localize(nextText, nextOutputTitle, nextPlaceholder) {
36645
+ currentText = nextText;
36646
+ inputTitle.textContent = nextText.pane.input;
36647
+ clearBtn.textContent = nextText.pane.clear;
36648
+ clearBtn.title = nextText.pane.clearTitle;
36649
+ resizer.setAttribute("aria-label", nextText.pane.resize);
36650
+ outputTitleEl.textContent = nextOutputTitle;
36651
+ input2.placeholder = nextPlaceholder;
36652
+ },
36653
+ focus: () => input2.focus(),
36654
+ dispose() {
36655
+ detachResizer();
36656
+ renderController?.abort();
36657
+ renderController = null;
36658
+ if (debounceTimer)
36659
+ clearTimeout(debounceTimer);
36660
+ debounceTimer = null;
36661
+ if (statusTimer)
36662
+ clearTimeout(statusTimer);
36663
+ statusTimer = null;
36664
+ }
36665
+ };
36666
+ }
36667
+
36668
+ // web-src/views/tools/structured-text.ts
36669
+ var JSON_INDENT = 2;
36670
+ function parseStructuredText(text3, yaml) {
36671
+ const trimmed = text3.trim();
36672
+ if (!trimmed)
36673
+ return { status: "error", message: "empty input" };
36674
+ let jsonMessage = "";
36675
+ try {
36676
+ return { status: "ok", source: "json", value: JSON.parse(trimmed) };
36677
+ } catch (err) {
36678
+ jsonMessage = err instanceof Error ? err.message : String(err);
36679
+ }
36680
+ if (!yaml)
36681
+ return { status: "error", message: jsonMessage };
36682
+ try {
36683
+ const doc = yaml.parseDocument(text3);
36684
+ if (doc.errors.length > 0)
36685
+ return {
36686
+ status: "error",
36687
+ message: doc.errors.map((issue) => issue.message).join(`
36688
+
36689
+ `)
36690
+ };
36691
+ const warnings = doc.warnings.map((issue) => issue.message);
36692
+ return {
36693
+ status: "ok",
36694
+ source: "yaml",
36695
+ value: doc.toJS(),
36696
+ ...warnings.length ? { warnings } : {}
36697
+ };
36698
+ } catch (err) {
36699
+ return {
36700
+ status: "error",
36701
+ message: err instanceof Error ? err.message : String(err)
36702
+ };
36703
+ }
36704
+ }
36705
+ function formatStructured(value, format2, minify, yaml) {
36706
+ if (format2 === "yaml") {
36707
+ if (!yaml)
36708
+ return null;
36709
+ return yaml.stringify(value, { indent: JSON_INDENT });
36710
+ }
36711
+ return JSON.stringify(value, null, minify ? 0 : JSON_INDENT) ?? "";
36712
+ }
36713
+
36714
+ // web-src/views/tools/json-tool.ts
36715
+ async function resolveInput(value, format2, signal) {
36716
+ const direct = parseStructuredText(value, null);
36717
+ if (direct.status === "ok" && format2 === "json")
36718
+ return { parsed: direct, yaml: null };
36719
+ const yaml = await loadYaml();
36720
+ if (signal.aborted)
36721
+ return null;
36722
+ return {
36723
+ parsed: direct.status === "ok" ? direct : parseStructuredText(value, yaml),
36724
+ yaml
36725
+ };
36726
+ }
36727
+ function createJsonTool(text3, initialText, onInput) {
36728
+ let currentText = text3;
36729
+ let format2 = "json";
36730
+ let minify = false;
36731
+ let lastOutput = "";
36732
+ const seg = document.createElement("div");
36733
+ seg.className = "seg";
36734
+ seg.role = "group";
36735
+ seg.setAttribute("aria-label", text3.json.outputFormat);
36736
+ const jsonBtn = document.createElement("button");
36737
+ jsonBtn.type = "button";
36738
+ jsonBtn.textContent = text3.json.asJson;
36739
+ jsonBtn.classList.add("active");
36740
+ const yamlBtn = document.createElement("button");
36741
+ yamlBtn.type = "button";
36742
+ yamlBtn.textContent = text3.json.asYaml;
36743
+ seg.append(jsonBtn, yamlBtn);
36744
+ const minifyBtn = createPaneAction(text3.json.minify, text3.json.minifyTitle);
36745
+ const copyBtn = createPaneAction(text3.pane.copy, text3.pane.copyTitle);
36746
+ function syncActionState() {
36747
+ jsonBtn.classList.toggle("active", format2 === "json");
36748
+ yamlBtn.classList.toggle("active", format2 === "yaml");
36749
+ minifyBtn.classList.toggle("active", minify);
36750
+ minifyBtn.setAttribute("aria-pressed", String(minify));
36751
+ minifyBtn.disabled = format2 === "yaml";
36752
+ minifyBtn.title = format2 === "yaml" ? currentText.json.minifyYamlTitle : currentText.json.minifyTitle;
36753
+ }
36754
+ const pane = createScratchpadPane(text3, text3.json.output, text3.json.placeholder, {
36755
+ toolClassName: "tools-pane-json",
36756
+ initialText,
36757
+ onInput,
36758
+ outputActions: [seg, minifyBtn, copyBtn],
36759
+ render: async (value, output, signal) => {
36760
+ const resolved = await resolveInput(value, format2, signal);
36761
+ if (!resolved)
36762
+ return;
36763
+ const { parsed, yaml } = resolved;
36764
+ if (parsed.status === "error") {
36765
+ lastOutput = "";
36766
+ const error2 = document.createElement("p");
36767
+ error2.className = "tools-pane-error";
36768
+ error2.textContent = currentText.json.invalid(parsed.message);
36769
+ output.replaceChildren(error2);
36770
+ pane.setStatus("");
36771
+ return;
36772
+ }
36773
+ const formatted = formatStructured(parsed.value, format2, minify, yaml);
36774
+ if (formatted === null) {
36775
+ lastOutput = "";
36776
+ const error2 = document.createElement("p");
36777
+ error2.className = "tools-pane-error";
36778
+ error2.textContent = currentText.json.loadError;
36779
+ output.replaceChildren(error2);
36780
+ pane.setStatus("");
36781
+ return;
36782
+ }
36783
+ lastOutput = formatted;
36784
+ const highlighter = await loadShikiHighlighter({
36785
+ themes: ["github-light", "github-dark"],
36786
+ langs: ["json", "yaml"]
36787
+ });
36788
+ if (signal.aborted)
36789
+ return;
36790
+ const pre = document.createElement("pre");
36791
+ pre.className = "tools-code";
36792
+ const html = highlightToInnerHtml(formatted, format2, highlighter);
36793
+ if (html)
36794
+ pre.innerHTML = html;
36795
+ else
36796
+ pre.textContent = formatted;
36797
+ if (parsed.warnings?.length) {
36798
+ const warning = document.createElement("p");
36799
+ warning.className = "tools-pane-warning";
36800
+ warning.textContent = parsed.warnings.join(`
36801
+
36802
+ `);
36803
+ output.replaceChildren(warning, pre);
36804
+ } else {
36805
+ output.replaceChildren(pre);
36806
+ }
36807
+ pane.setStatus(parsed.source === "json" ? currentText.json.parsedAsJson : currentText.json.parsedAsYaml);
36808
+ }
36809
+ });
36810
+ jsonBtn.addEventListener("click", () => {
36811
+ if (format2 === "json")
36812
+ return;
36813
+ format2 = "json";
36814
+ syncActionState();
36815
+ pane.refresh();
36816
+ });
36817
+ yamlBtn.addEventListener("click", () => {
36818
+ if (format2 === "yaml")
36819
+ return;
36820
+ format2 = "yaml";
36821
+ syncActionState();
36822
+ pane.refresh();
36823
+ });
36824
+ minifyBtn.addEventListener("click", () => {
36825
+ minify = !minify;
36826
+ syncActionState();
36827
+ pane.refresh();
36828
+ });
36829
+ copyBtn.addEventListener("click", () => {
36830
+ if (!lastOutput)
36831
+ return;
36832
+ navigator.clipboard.writeText(lastOutput).then(() => pane.flashStatus(currentText.pane.copied), () => pane.flashStatus(currentText.pane.copyFailed, "error"));
36833
+ });
36834
+ syncActionState();
36835
+ return {
36836
+ ...pane,
36837
+ localizeActions(nextText) {
36838
+ currentText = nextText;
36839
+ seg.setAttribute("aria-label", nextText.json.outputFormat);
36840
+ jsonBtn.textContent = nextText.json.asJson;
36841
+ yamlBtn.textContent = nextText.json.asYaml;
36842
+ minifyBtn.textContent = nextText.json.minify;
36843
+ copyBtn.textContent = nextText.pane.copy;
36844
+ copyBtn.title = nextText.pane.copyTitle;
36845
+ syncActionState();
36846
+ }
36847
+ };
36848
+ }
36849
+
36850
+ // web-src/views/tools/markdown-tool.ts
36851
+ var SCRATCHPAD_TARGET = { path: "scratchpad.md", ref: "worktree" };
36852
+ function createMarkdownTool(text3, initialText, onInput) {
36853
+ return createScratchpadPane(text3, text3.markdown.output, text3.markdown.placeholder, {
36854
+ toolClassName: "tools-pane-markdown",
36855
+ initialText,
36856
+ onInput,
36857
+ render: async (value, output, signal) => {
36858
+ const rendered = await renderMarkdownPreview(value, SCRATCHPAD_TARGET, {
36859
+ syntaxHighlight: true,
36860
+ signal
36861
+ });
36862
+ if (signal.aborted)
36863
+ return;
36864
+ output.replaceChildren(rendered);
36865
+ }
36866
+ });
36867
+ }
36868
+
36869
+ // web-src/views/tools/mermaid-tool.ts
36870
+ function createMermaidTool(text3, initialText, onInput) {
36871
+ let currentText = text3;
36872
+ const viewport = createDiagramViewport({
36873
+ containerClassName: "tools-mermaid-container",
36874
+ contentClassName: "tools-mermaid-canvas"
36875
+ });
36876
+ const zoomIn = createPaneAction("+", text3.mermaid.zoomIn);
36877
+ const zoomOut = createPaneAction("−", text3.mermaid.zoomOut);
36878
+ const zoomReset = createPaneAction("1:1", text3.mermaid.zoomReset);
36879
+ zoomIn.addEventListener("click", () => viewport.zoomIn());
36880
+ zoomOut.addEventListener("click", () => viewport.zoomOut());
36881
+ zoomReset.addEventListener("click", () => viewport.reset());
36882
+ const pane = createScratchpadPane(text3, text3.mermaid.output, text3.mermaid.placeholder, {
36883
+ toolClassName: "tools-pane-mermaid",
36884
+ initialText,
36885
+ onInput,
36886
+ outputActions: [zoomIn, zoomOut, zoomReset],
36887
+ render: async (value, output, signal) => {
36888
+ const mermaid = await loadMermaid();
36889
+ if (signal.aborted)
36890
+ return;
36891
+ if (!mermaid) {
36892
+ pane.setStatus(currentText.mermaid.loadError, "error");
36893
+ return;
36894
+ }
36895
+ const node = document.createElement("div");
36896
+ node.className = "mermaid";
36897
+ node.textContent = value;
36898
+ viewport.content.replaceChildren(node);
36899
+ viewport.reset();
36900
+ output.replaceChildren(viewport.container);
36901
+ try {
36902
+ await mermaid.run({ nodes: [node], suppressErrors: true });
36903
+ } catch {
36904
+ if (signal.aborted)
36905
+ return;
36906
+ pane.setStatus(currentText.mermaid.renderError, "error");
36907
+ return;
36908
+ }
36909
+ if (signal.aborted)
36910
+ return;
36911
+ if (node.querySelector("svg"))
36912
+ pane.setStatus("");
36913
+ else
36914
+ pane.setStatus(currentText.mermaid.renderError, "error");
36915
+ }
36916
+ });
36917
+ const disposePane = pane.dispose;
36918
+ return {
36919
+ ...pane,
36920
+ localizeActions(nextText) {
36921
+ currentText = nextText;
36922
+ zoomIn.title = nextText.mermaid.zoomIn;
36923
+ zoomOut.title = nextText.mermaid.zoomOut;
36924
+ zoomReset.title = nextText.mermaid.zoomReset;
36925
+ },
36926
+ dispose() {
36927
+ disposePane();
36928
+ viewport.dispose();
36929
+ }
36930
+ };
36931
+ }
36932
+
36933
+ // web-src/views/tools/tools-view.ts
36934
+ var SAVE_DEBOUNCE_MS = 800;
36935
+ var SAVE_RETRY_MS = 1500;
36936
+ var SAVE_RETRY_LIMIT = 3;
36937
+ var KEEPALIVE_BODY_LIMIT_BYTES = 64 * 1024;
36938
+ function createToolsView(deps) {
36939
+ let panes = null;
36940
+ let tabs = null;
36941
+ let titleEl = null;
36942
+ let closeBtn = null;
36943
+ let activeTool = DEFAULT_TOOL_ID;
36944
+ let stateLoaded = false;
36945
+ let savedActiveTool = null;
36946
+ const drafts = {};
36947
+ let saveTimer = null;
36948
+ let saveRetryTimer = null;
36949
+ let dirty = false;
36950
+ let saveInFlight = false;
36951
+ let saveController = null;
36952
+ let saveFailed = false;
36953
+ let saveRetries = 0;
36954
+ let loadFailed = false;
36955
+ let disposed = false;
36956
+ let sheetWidth = null;
36957
+ let toolTouched = false;
36958
+ let widthTouched = false;
36959
+ let generation = 0;
36960
+ function text3() {
36961
+ return toolsText(deps.getLanguage());
36962
+ }
36963
+ function getMount() {
36964
+ return deps.$("#tools-sheet");
36965
+ }
36966
+ function getOverlay() {
36967
+ return deps.$("#tools-sheet-overlay");
36968
+ }
36969
+ function isPermanentSaveFailure(status) {
36970
+ return status >= 400 && status < 500;
36971
+ }
36972
+ function syncFailureStatus() {
36973
+ if (!panes)
36974
+ return;
36975
+ const current = text3();
36976
+ const message = loadFailed ? current.loadFailed : saveFailed ? current.saveFailed : null;
36977
+ for (const id of TOOL_IDS)
36978
+ panes[id].setPinnedStatus(message);
36979
+ }
36980
+ function reportSaveFailure() {
36981
+ saveFailed = true;
36982
+ syncFailureStatus();
36983
+ }
36984
+ function clearSaveFailure() {
36985
+ saveRetries = 0;
36986
+ if (!saveFailed)
36987
+ return;
36988
+ saveFailed = false;
36989
+ syncFailureStatus();
36990
+ }
36991
+ function reportLoadFailure() {
36992
+ loadFailed = true;
36993
+ syncFailureStatus();
36994
+ }
36995
+ function save(options = {}) {
36996
+ if (saveTimer) {
36997
+ clearTimeout(saveTimer);
36998
+ saveTimer = null;
36999
+ }
37000
+ if (disposed)
37001
+ return;
37002
+ if (!options.keepalive && saveRetries > SAVE_RETRY_LIMIT)
37003
+ return;
37004
+ const pending = dirty || options.keepalive === true && saveInFlight;
37005
+ if (!pending)
37006
+ return;
37007
+ if (saveInFlight && !options.keepalive)
37008
+ return;
37009
+ if (options.keepalive)
37010
+ saveController?.abort();
37011
+ dirty = false;
37012
+ const body = JSON.stringify({
37013
+ activeTool,
37014
+ drafts,
37015
+ ...sheetWidth === null ? {} : { width: sheetWidth }
37016
+ });
37017
+ const keepalive = options.keepalive === true && new TextEncoder().encode(body).length <= KEEPALIVE_BODY_LIMIT_BYTES;
37018
+ const controller = new AbortController;
37019
+ if (!options.keepalive) {
37020
+ saveInFlight = true;
37021
+ saveController = controller;
37022
+ }
37023
+ deps.trackLoad(fetch("/_state/tools", {
37024
+ method: "PATCH",
37025
+ headers: deps.actionHeaders(),
37026
+ body,
37027
+ keepalive,
37028
+ signal: controller.signal
37029
+ })).then((res) => {
37030
+ if (res.ok) {
37031
+ clearSaveFailure();
37032
+ return;
37033
+ }
37034
+ if (isPermanentSaveFailure(res.status)) {
37035
+ reportSaveFailure();
37036
+ return;
37037
+ }
37038
+ throw new Error(`HTTP ${res.status}`);
37039
+ }).catch(() => {
37040
+ if (disposed)
37041
+ return;
37042
+ dirty = true;
37043
+ saveRetries += 1;
37044
+ if (saveRetries > SAVE_RETRY_LIMIT) {
37045
+ reportSaveFailure();
37046
+ return;
37047
+ }
37048
+ if (options.keepalive || saveRetryTimer)
37049
+ return;
37050
+ saveRetryTimer = setTimeout(() => {
37051
+ saveRetryTimer = null;
37052
+ save();
37053
+ }, SAVE_RETRY_MS);
37054
+ }).finally(() => {
37055
+ if (options.keepalive)
37056
+ return;
37057
+ saveInFlight = false;
37058
+ if (saveController === controller)
37059
+ saveController = null;
37060
+ if (disposed)
37061
+ return;
37062
+ if (dirty && !saveRetryTimer)
37063
+ save();
37064
+ });
37065
+ }
37066
+ function scheduleSave() {
37067
+ dirty = true;
37068
+ saveRetries = 0;
37069
+ if (saveTimer)
37070
+ clearTimeout(saveTimer);
37071
+ saveTimer = setTimeout(() => {
37072
+ saveTimer = null;
37073
+ save();
37074
+ }, SAVE_DEBOUNCE_MS);
37075
+ }
37076
+ function onDraftInput(tool, value) {
37077
+ drafts[tool] = value;
37078
+ toolTouched = true;
37079
+ scheduleSave();
37080
+ }
37081
+ function createPanes(current) {
37082
+ return {
37083
+ markdown: createMarkdownTool(current, drafts.markdown ?? "", (value) => onDraftInput("markdown", value)),
37084
+ mermaid: createMermaidTool(current, drafts.mermaid ?? "", (value) => onDraftInput("mermaid", value)),
37085
+ json: createJsonTool(current, drafts.json ?? "", (value) => onDraftInput("json", value))
37086
+ };
37087
+ }
37088
+ function setActiveTool(tool, notify) {
37089
+ activeTool = tool;
37090
+ if (!panes || !tabs)
37091
+ return;
37092
+ for (const id of TOOL_IDS) {
37093
+ tabs[id].classList.toggle("active", id === tool);
37094
+ tabs[id].setAttribute("aria-pressed", String(id === tool));
37095
+ panes[id].el.hidden = id !== tool;
37096
+ }
37097
+ panes[tool].refresh();
37098
+ panes[tool].focus();
37099
+ if (notify)
37100
+ deps.onToolChange?.(tool);
37101
+ if (savedActiveTool === tool)
37102
+ return;
37103
+ savedActiveTool = tool;
37104
+ scheduleSave();
37105
+ }
37106
+ function applySheetWidth(host, width) {
37107
+ const clamped = Math.round(Math.max(MIN_TOOLS_SHEET_WIDTH, Math.min(MAX_TOOLS_SHEET_WIDTH, width)));
37108
+ sheetWidth = clamped;
37109
+ host.style.setProperty("--tools-sheet-width", `${clamped}px`);
37110
+ }
37111
+ function mount(host) {
37112
+ if (panes)
37113
+ return;
37114
+ const current = text3();
37115
+ host.replaceChildren();
37116
+ const sheetResizer = document.createElement("div");
37117
+ sheetResizer.className = "tools-sheet-resizer";
37118
+ sheetResizer.role = "separator";
37119
+ sheetResizer.tabIndex = 0;
37120
+ sheetResizer.setAttribute("aria-orientation", "vertical");
37121
+ sheetResizer.setAttribute("aria-label", current.resizeSheet);
37122
+ attachDragResizer({
37123
+ handle: sheetResizer,
37124
+ getSize: () => host.getBoundingClientRect().width,
37125
+ applySize: (width) => {
37126
+ widthTouched = true;
37127
+ applySheetWidth(host, width);
37128
+ },
37129
+ direction: -1,
37130
+ onEnd: scheduleSave,
37131
+ activeClassTarget: host,
37132
+ activeClassName: "tools-sheet-resizing"
37133
+ });
37134
+ const header = document.createElement("header");
37135
+ header.className = "tools-header";
37136
+ titleEl = document.createElement("h1");
37137
+ titleEl.textContent = current.title;
37138
+ const tabList = document.createElement("div");
37139
+ tabList.className = "seg tools-tabs";
37140
+ tabList.role = "group";
37141
+ tabList.setAttribute("aria-label", current.title);
37142
+ closeBtn = document.createElement("button");
37143
+ closeBtn.type = "button";
37144
+ closeBtn.className = "tools-close";
37145
+ closeBtn.textContent = "×";
37146
+ closeBtn.title = current.close;
37147
+ closeBtn.setAttribute("aria-label", current.close);
37148
+ closeBtn.addEventListener("click", (event) => {
37149
+ event.preventDefault();
37150
+ deps.onCloseRequest?.();
37151
+ });
37152
+ header.append(titleEl, tabList, closeBtn);
37153
+ panes = createPanes(current);
37154
+ tabs = {};
37155
+ const body = document.createElement("div");
37156
+ body.className = "tools-body";
37157
+ for (const id of TOOL_IDS) {
37158
+ const tab = document.createElement("button");
37159
+ tab.type = "button";
37160
+ tab.textContent = current.tabs[id];
37161
+ tab.addEventListener("click", () => {
37162
+ toolTouched = true;
37163
+ setActiveTool(id, true);
37164
+ });
37165
+ tabs[id] = tab;
37166
+ tabList.appendChild(tab);
37167
+ panes[id].el.hidden = id !== activeTool;
37168
+ tab.classList.toggle("active", id === activeTool);
37169
+ body.appendChild(panes[id].el);
37170
+ }
37171
+ host.append(sheetResizer, header, body);
37172
+ }
37173
+ async function loadState(myGen) {
37174
+ if (stateLoaded)
37175
+ return;
37176
+ try {
37177
+ const res = await deps.trackLoad(fetch("/_state/tools"));
37178
+ if (myGen !== generation)
37179
+ return;
37180
+ if (!res.ok) {
37181
+ reportLoadFailure();
37182
+ return;
37183
+ }
37184
+ const data = await res.json();
37185
+ if (myGen !== generation)
37186
+ return;
37187
+ stateLoaded = true;
37188
+ if (loadFailed) {
37189
+ loadFailed = false;
37190
+ syncFailureStatus();
37191
+ }
37192
+ if (!toolTouched)
37193
+ savedActiveTool = data.activeTool ?? null;
37194
+ const host = getMount();
37195
+ if (host && !widthTouched && typeof data.width === "number")
37196
+ applySheetWidth(host, data.width);
37197
+ for (const id of TOOL_IDS) {
37198
+ const draft = data.drafts?.[id];
37199
+ if (typeof draft !== "string")
37200
+ continue;
37201
+ if (id in drafts)
37202
+ continue;
37203
+ drafts[id] = draft;
37204
+ panes?.[id].setText(draft);
37205
+ }
37206
+ } catch {
37207
+ if (myGen === generation)
37208
+ reportLoadFailure();
37209
+ }
37210
+ }
37211
+ function isOpen() {
37212
+ const host = getMount();
37213
+ return host ? !host.hidden : false;
37214
+ }
37215
+ async function open(tool) {
37216
+ const host = getMount();
37217
+ if (!host)
37218
+ return;
37219
+ const myGen = ++generation;
37220
+ mount(host);
37221
+ host.hidden = false;
37222
+ host.setAttribute("aria-hidden", "false");
37223
+ host.removeAttribute("inert");
37224
+ const overlay = getOverlay();
37225
+ if (overlay) {
37226
+ overlay.hidden = false;
37227
+ overlay.setAttribute("aria-hidden", "false");
37228
+ }
37229
+ document.body.classList.add("tools-sheet-open");
37230
+ await loadState(myGen);
37231
+ if (myGen !== generation || !isOpen())
37232
+ return;
37233
+ setActiveTool(tool ?? savedActiveTool ?? DEFAULT_TOOL_ID, !tool);
37234
+ }
37235
+ function close() {
37236
+ const host = getMount();
37237
+ if (!host)
37238
+ return;
37239
+ host.hidden = true;
37240
+ host.setAttribute("aria-hidden", "true");
37241
+ host.setAttribute("inert", "");
37242
+ const overlay = getOverlay();
37243
+ if (overlay) {
37244
+ overlay.hidden = true;
37245
+ overlay.setAttribute("aria-hidden", "true");
37246
+ }
37247
+ document.body.classList.remove("tools-sheet-open");
37248
+ generation += 1;
37249
+ save();
37250
+ }
37251
+ function localize() {
37252
+ if (!panes || !tabs)
37253
+ return;
37254
+ const current = text3();
37255
+ if (titleEl)
37256
+ titleEl.textContent = current.title;
37257
+ if (closeBtn) {
37258
+ closeBtn.title = current.close;
37259
+ closeBtn.setAttribute("aria-label", current.close);
37260
+ }
37261
+ for (const id of TOOL_IDS)
37262
+ tabs[id].textContent = current.tabs[id];
37263
+ panes.markdown.localize(current, current.markdown.output, current.markdown.placeholder);
37264
+ panes.mermaid.localize(current, current.mermaid.output, current.mermaid.placeholder);
37265
+ panes.json.localize(current, current.json.output, current.json.placeholder);
37266
+ for (const id of TOOL_IDS)
37267
+ panes[id].localizeActions?.(current);
37268
+ panes[activeTool].refresh();
37269
+ syncFailureStatus();
37270
+ }
37271
+ const onBeforeUnload = () => {
37272
+ if (dirty || saveInFlight)
37273
+ save({ keepalive: true });
37274
+ };
37275
+ window.addEventListener("beforeunload", onBeforeUnload);
37276
+ return {
37277
+ open,
37278
+ close,
37279
+ isOpen,
37280
+ getActiveTool: () => activeTool,
37281
+ localize,
37282
+ dispose() {
37283
+ disposed = true;
37284
+ generation += 1;
37285
+ saveController?.abort();
37286
+ saveController = null;
37287
+ window.removeEventListener("beforeunload", onBeforeUnload);
37288
+ if (saveTimer)
37289
+ clearTimeout(saveTimer);
37290
+ saveTimer = null;
37291
+ if (saveRetryTimer)
37292
+ clearTimeout(saveRetryTimer);
37293
+ saveRetryTimer = null;
37294
+ if (!panes)
37295
+ return;
37296
+ for (const id of TOOL_IDS)
37297
+ panes[id].dispose();
37298
+ }
37299
+ };
37300
+ }
37301
+
36198
37302
  // web-src/app.ts
36199
37303
  window.GdpExpandLogic = GdpExpandLogic;
36200
37304
  (() => {
@@ -37029,6 +38133,7 @@ code-viewer query agent-help`
37029
38133
  history: "History",
37030
38134
  journal: "Work Log",
37031
38135
  database: "Datastores",
38136
+ tools: "Tools",
37032
38137
  help: "Help"
37033
38138
  },
37034
38139
  global: {
@@ -37322,6 +38427,7 @@ code-viewer query agent-help`
37322
38427
  history: "履歴",
37323
38428
  journal: "ワークログ",
37324
38429
  database: "データストア",
38430
+ tools: "ツール",
37325
38431
  help: "ヘルプ"
37326
38432
  },
37327
38433
  global: {
@@ -37636,11 +38742,6 @@ code-viewer query agent-help`
37636
38742
  if (route && text3.nav[route])
37637
38743
  link2.textContent = text3.nav[route];
37638
38744
  });
37639
- const helpLink = document.querySelector(".global-icon-link[data-route='help']");
37640
- if (helpLink) {
37641
- helpLink.title = text3.nav.help;
37642
- helpLink.setAttribute("aria-label", text3.nav.help);
37643
- }
37644
38745
  const repoWebLink = document.querySelector("#repo-web-link");
37645
38746
  if (repoWebLink) {
37646
38747
  repoWebLink.title = text3.global.repoWebLink;
@@ -37676,6 +38777,12 @@ code-viewer query agent-help`
37676
38777
  doctorBtn.setAttribute("aria-label", doctorTitle);
37677
38778
  }
37678
38779
  document.querySelector("#doctor-sheet")?.setAttribute("aria-label", doctorTitle);
38780
+ const toolsChrome = toolsText(STATE.language);
38781
+ const toolsBtn = document.querySelector("#tools-btn");
38782
+ if (toolsBtn)
38783
+ toolsBtn.title = toolsChrome.open;
38784
+ document.querySelector("#tools-sheet")?.setAttribute("aria-label", toolsChrome.title);
38785
+ relocalizeTools?.();
37679
38786
  const copyAiContext = document.querySelector("#copy-ai-context");
37680
38787
  if (copyAiContext) {
37681
38788
  copyAiContext.title = text3.global.copyAiContext;
@@ -37837,6 +38944,7 @@ code-viewer query agent-help`
37837
38944
  }
37838
38945
  let relocalizeHistory = null;
37839
38946
  let relocalizeJournal = null;
38947
+ let relocalizeTools = null;
37840
38948
  let relocalizeDatabase = null;
37841
38949
  let QUICK_HELP = null;
37842
38950
  function setViewerLanguage(language, persist = true) {
@@ -38297,8 +39405,11 @@ code-viewer query agent-help`
38297
39405
  function withAnnotationSessionParam(rawUrl) {
38298
39406
  return ANNOTATIONS_UI ? ANNOTATIONS_UI.withSessionParam(rawUrl) : rawUrl;
38299
39407
  }
39408
+ function withOverlayState(url) {
39409
+ return withToolsOverlay(withDoctorOverlay(url, parseDoctorOverlay(window.location.pathname, window.location.search)), parseToolsOverlay(window.location.search));
39410
+ }
38300
39411
  function urlForRoute(route) {
38301
- return withDoctorOverlay(withAnnotationSessionParam(buildRoute(route)), parseDoctorOverlay(window.location.pathname, window.location.search));
39412
+ return withOverlayState(withAnnotationSessionParam(buildRoute(route)));
38302
39413
  }
38303
39414
  function historyStateForRoute(route) {
38304
39415
  return route.screen === "file" ? {
@@ -38309,8 +39420,7 @@ code-viewer query agent-help`
38309
39420
  } : { view: route.screen };
38310
39421
  }
38311
39422
  function replaceUrlWithCurrentRoute() {
38312
- const base2 = withAnnotationSessionParam(buildRoute(STATE.route));
38313
- const url = withDoctorOverlay(base2, parseDoctorOverlay(window.location.pathname, window.location.search));
39423
+ const url = urlForRoute(STATE.route);
38314
39424
  const current = window.location.pathname + window.location.search;
38315
39425
  if (url !== current) {
38316
39426
  history.replaceState(historyStateForRoute(STATE.route), "", url + window.location.hash);
@@ -38458,51 +39568,57 @@ code-viewer query agent-help`
38458
39568
  applyHideTests();
38459
39569
  }
38460
39570
  function syncHeaderMenu() {
38461
- document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
39571
+ const toolsBtn = document.querySelector("#tools-btn");
39572
+ if (toolsBtn) {
39573
+ const open = parseToolsOverlay(window.location.search) !== null;
39574
+ toolsBtn.classList.toggle("active", open);
39575
+ toolsBtn.setAttribute("aria-pressed", String(open));
39576
+ }
39577
+ document.querySelectorAll("a.app-menu-item, a.global-icon-link").forEach((link2) => {
38462
39578
  const fileRouteOwner = STATE.route.screen === "file" && (STATE.route.view === "blob" || STATE.route.view === "blame" || STATE.route.view === "history") ? "repo" : "diff";
38463
39579
  const active = link2.dataset.route === STATE.route.screen || STATE.route.screen === "file" && link2.dataset.route === fileRouteOwner;
38464
39580
  link2.classList.toggle("active", active);
38465
39581
  link2.setAttribute("aria-current", active ? "page" : "false");
38466
39582
  if (link2.dataset.route === "repo") {
38467
- link2.href = buildRoute({
39583
+ link2.href = withOverlayState(buildRoute({
38468
39584
  screen: "repo",
38469
39585
  ref: STATE.repoRef || "worktree",
38470
39586
  path: "",
38471
39587
  range: currentRange()
38472
- });
39588
+ }));
38473
39589
  }
38474
39590
  if (link2.dataset.route === "diff") {
38475
- link2.href = buildRoute({
39591
+ link2.href = withOverlayState(buildRoute({
38476
39592
  screen: "diff",
38477
39593
  range: preHistoryRange ?? currentRange()
38478
- });
39594
+ }));
38479
39595
  }
38480
39596
  if (link2.dataset.route === "history") {
38481
- link2.href = buildRoute({
39597
+ link2.href = withOverlayState(buildRoute({
38482
39598
  screen: "history",
38483
39599
  ref: "HEAD",
38484
39600
  range: currentRange()
38485
- });
39601
+ }));
38486
39602
  }
38487
39603
  if (link2.dataset.route === "journal") {
38488
- link2.href = buildRoute({
39604
+ link2.href = withOverlayState(buildRoute({
38489
39605
  screen: "journal",
38490
39606
  range: currentRange()
38491
- });
39607
+ }));
38492
39608
  }
38493
39609
  if (link2.dataset.route === "database") {
38494
- link2.href = buildRoute({
39610
+ link2.href = withOverlayState(buildRoute({
38495
39611
  screen: "database",
38496
39612
  range: currentRange()
38497
- });
39613
+ }));
38498
39614
  }
38499
39615
  if (link2.dataset.route === "help") {
38500
- link2.href = buildRoute({
39616
+ link2.href = withOverlayState(buildRoute({
38501
39617
  screen: "help",
38502
39618
  lang: STATE.route.screen === "help" ? helpLanguageFromRoute(STATE.route) : STATE.language,
38503
39619
  section: helpSectionFromRoute(STATE.route),
38504
39620
  range: currentRange()
38505
- });
39621
+ }));
38506
39622
  }
38507
39623
  });
38508
39624
  }
@@ -38712,10 +39828,6 @@ code-viewer query agent-help`
38712
39828
  if (quickHelpIcon) {
38713
39829
  quickHelpIcon.innerHTML = iconSvg("octicon-question", QUESTION_16_PATH);
38714
39830
  }
38715
- const helpLinkIcon = document.querySelector(".global-icon-link[data-route='help'] .goi-icon");
38716
- if (helpLinkIcon) {
38717
- helpLinkIcon.innerHTML = iconSvg("octicon-book", BOOK_16_PATH);
38718
- }
38719
39831
  const branchIcon = document.querySelector("#project-branch .goi-icon");
38720
39832
  if (branchIcon) {
38721
39833
  branchIcon.innerHTML = iconSvg("octicon-git-branch", GIT_BRANCH_16_PATH);
@@ -38757,6 +39869,10 @@ code-viewer query agent-help`
38757
39869
  event.preventDefault();
38758
39870
  toggleDoctorSheet();
38759
39871
  });
39872
+ $("#tools-btn")?.addEventListener("click", (event) => {
39873
+ event.preventDefault();
39874
+ toggleToolsSheet();
39875
+ });
38760
39876
  let copyAiContextFeedbackTimer = null;
38761
39877
  $("#copy-ai-context")?.addEventListener("click", async (event) => {
38762
39878
  const button = event.currentTarget;
@@ -39440,6 +40556,7 @@ code-viewer query agent-help`
39440
40556
  load();
39441
40557
  syncLineRefPill();
39442
40558
  syncDoctorSheetFromUrl();
40559
+ syncToolsSheetFromUrl();
39443
40560
  });
39444
40561
  function syncRefInputs() {
39445
40562
  const fi = $("#ref-from"), ti = $("#ref-to");
@@ -39615,6 +40732,57 @@ code-viewer query agent-help`
39615
40732
  event.preventDefault();
39616
40733
  closeDoctorSheet();
39617
40734
  });
40735
+ const TOOLS_VIEW = createToolsView({
40736
+ $: (sel) => document.querySelector(sel),
40737
+ trackLoad,
40738
+ getLanguage: () => STATE.language,
40739
+ actionHeaders,
40740
+ onCloseRequest: () => closeToolsSheet(),
40741
+ onToolChange: (tool) => updateUrlForToolsOverlay(tool)
40742
+ });
40743
+ relocalizeTools = () => TOOLS_VIEW.localize();
40744
+ function openToolsOverlay() {
40745
+ return parseToolsOverlay(window.location.search);
40746
+ }
40747
+ function updateUrlForToolsOverlay(tool) {
40748
+ const current = window.location.pathname + window.location.search;
40749
+ const next = withToolsOverlay(current, tool);
40750
+ if (next !== current) {
40751
+ history.replaceState(history.state, "", next + window.location.hash);
40752
+ }
40753
+ syncHeaderMenu();
40754
+ }
40755
+ function openToolsSheet(tool) {
40756
+ updateUrlForToolsOverlay(tool ?? TOOLS_VIEW.getActiveTool());
40757
+ TOOLS_VIEW.open(tool);
40758
+ }
40759
+ function closeToolsSheet() {
40760
+ TOOLS_VIEW.close();
40761
+ updateUrlForToolsOverlay(null);
40762
+ }
40763
+ function toggleToolsSheet() {
40764
+ if (openToolsOverlay() || TOOLS_VIEW.isOpen())
40765
+ closeToolsSheet();
40766
+ else
40767
+ openToolsSheet();
40768
+ }
40769
+ function syncToolsSheetFromUrl() {
40770
+ const tool = openToolsOverlay();
40771
+ const open = TOOLS_VIEW.isOpen();
40772
+ if (tool && (!open || TOOLS_VIEW.getActiveTool() !== tool))
40773
+ TOOLS_VIEW.open(tool);
40774
+ else if (!tool && open)
40775
+ TOOLS_VIEW.close();
40776
+ }
40777
+ document.getElementById("tools-sheet-overlay")?.addEventListener("click", () => closeToolsSheet());
40778
+ document.addEventListener("keydown", (event) => {
40779
+ if (event.key !== "Escape")
40780
+ return;
40781
+ if (!TOOLS_VIEW.isOpen())
40782
+ return;
40783
+ event.preventDefault();
40784
+ closeToolsSheet();
40785
+ });
39618
40786
  JOURNAL_VIEW = createJournalView({
39619
40787
  getRoute: () => STATE.route,
39620
40788
  setRoute,
@@ -39692,6 +40860,7 @@ code-viewer query agent-help`
39692
40860
  syncHeaderMenu();
39693
40861
  syncLineRefPill();
39694
40862
  syncDoctorSheetFromUrl();
40863
+ syncToolsSheetFromUrl();
39695
40864
  if (isSameBlobFileRoute(previousRoute, STATE.route) && routeBlobPreview(previousRoute) !== routeBlobPreview(STATE.route) && switchSourceTab(routeBlobPreview(STATE.route) ? "preview" : "code", {
39696
40865
  updateRoute: false
39697
40866
  })) {
@@ -39752,7 +40921,7 @@ code-viewer query agent-help`
39752
40921
  }
39753
40922
  window.addEventListener("popstate", applyRouteFromLocation);
39754
40923
  window.addEventListener("pagehide", () => flushViewStatePatch(true));
39755
- document.querySelectorAll(".app-menu-item, .global-icon-link").forEach((link2) => {
40924
+ document.querySelectorAll("a.app-menu-item, a.global-icon-link").forEach((link2) => {
39756
40925
  if (link2.target === "_blank")
39757
40926
  return;
39758
40927
  link2.addEventListener("click", (e2) => {