@tensorix/metahub 0.5.4 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/cli.js +677 -155
  2. package/dist/cli.js.map +23 -17
  3. package/dist/core/copy-views.d.ts +3 -0
  4. package/dist/core/copy-views.d.ts.map +1 -0
  5. package/dist/core/csv-columns.d.ts +6 -0
  6. package/dist/core/csv-columns.d.ts.map +1 -0
  7. package/dist/core/databases.d.ts.map +1 -1
  8. package/dist/core/sync/auth.d.ts.map +1 -1
  9. package/dist/core/sync/cf-oauth.d.ts.map +1 -1
  10. package/dist/core/sync/files.d.ts.map +1 -1
  11. package/dist/core/sync/page-messages.d.ts +5 -0
  12. package/dist/core/sync/page-messages.d.ts.map +1 -1
  13. package/dist/core/sync/remote-shares-cache.d.ts.map +1 -1
  14. package/dist/core/sync/room-serve.d.ts.map +1 -1
  15. package/dist/core/sync/share-actions.d.ts.map +1 -1
  16. package/dist/core/sync/share-copy.d.ts +1 -1
  17. package/dist/core/sync/share-copy.d.ts.map +1 -1
  18. package/dist/core/sync/share-export.d.ts +4 -2
  19. package/dist/core/sync/share-export.d.ts.map +1 -1
  20. package/dist/core/sync/share-file-styles.d.ts +2 -0
  21. package/dist/core/sync/share-file-styles.d.ts.map +1 -0
  22. package/dist/core/sync/share-link.d.ts +27 -0
  23. package/dist/core/sync/share-link.d.ts.map +1 -0
  24. package/dist/core/sync/share-serve.d.ts.map +1 -1
  25. package/dist/core/sync/share-short-links.d.ts +8 -0
  26. package/dist/core/sync/share-short-links.d.ts.map +1 -0
  27. package/dist/core/sync/sites-serve.d.ts.map +1 -1
  28. package/dist/core/ui-theme.d.ts +44 -0
  29. package/dist/core/ui-theme.d.ts.map +1 -0
  30. package/dist/db-worker.js +64 -64
  31. package/dist/edge-worker.js +137 -8
  32. package/dist/index.js +630 -131
  33. package/dist/index.js.map +20 -15
  34. package/dist/metahub-sdk.js +4 -4
  35. package/dist/mh-runtime.js +2 -2
  36. package/dist/sw.js +9 -6
  37. package/dist/webui/server/assets.d.ts.map +1 -1
  38. package/dist/webui.js +130 -130
  39. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1168,6 +1168,45 @@ var init_crdt = __esm(() => {
1168
1168
  ]);
1169
1169
  });
1170
1170
 
1171
+ // src/core/copy-views.ts
1172
+ function copyViews(raw, props, records, selfRelations) {
1173
+ if (!Array.isArray(raw))
1174
+ return raw;
1175
+ const object = (x) => !!x && typeof x === "object" && !Array.isArray(x);
1176
+ const prop = (x) => typeof x === "string" ? props.get(x) ?? x : x;
1177
+ return raw.map((v) => {
1178
+ if (!object(v))
1179
+ return v;
1180
+ const out = { ...v };
1181
+ for (const key of ["group", "dateProp", "start", "end"])
1182
+ if (key in out)
1183
+ out[key] = prop(out[key]);
1184
+ if (Array.isArray(v.hidden))
1185
+ out.hidden = v.hidden.map(prop);
1186
+ if (Array.isArray(v.sort))
1187
+ out.sort = v.sort.map((s) => object(s) ? { ...s, prop: prop(s.prop) } : s);
1188
+ if (object(v.filter) && Array.isArray(v.filter.rules))
1189
+ out.filter = {
1190
+ ...v.filter,
1191
+ rules: v.filter.rules.map((r) => {
1192
+ if (!object(r))
1193
+ return r;
1194
+ return {
1195
+ ...r,
1196
+ prop: prop(r.prop),
1197
+ ...typeof r.prop === "string" && selfRelations.has(r.prop) && Array.isArray(r.value) ? { value: r.value.map((id) => typeof id === "string" ? records.get(id) ?? id : id) } : {}
1198
+ };
1199
+ })
1200
+ };
1201
+ if (object(v.calc))
1202
+ out.calc = Object.fromEntries(Object.entries(v.calc).map(([id, value]) => [
1203
+ props.get(id) ?? id,
1204
+ value
1205
+ ]));
1206
+ return out;
1207
+ });
1208
+ }
1209
+
1171
1210
  // src/core/indexing.ts
1172
1211
  function sanitize(s) {
1173
1212
  return s.replace(/[^a-zA-Z0-9_]/g, "_");
@@ -1458,10 +1497,6 @@ var init_databases = __esm(() => {
1458
1497
  name: opts.name ?? src.name,
1459
1498
  icon: opts.icon ?? src.icon ?? undefined
1460
1499
  });
1461
- if (src.meta !== null) {
1462
- const { collapsed: _hidden, ...meta } = src.meta;
1463
- emitMetaPatch(db, dup.id, null, meta);
1464
- }
1465
1500
  const propIdMap = new Map;
1466
1501
  const selfRelNew = new Set;
1467
1502
  for (const p of listProperties(db, id)) {
@@ -1496,6 +1531,12 @@ var init_databases = __esm(() => {
1496
1531
  if (Object.keys(patch).length)
1497
1532
  updateRecord(db, recIdMap.get(r.id), patch);
1498
1533
  }
1534
+ if (src.meta !== null) {
1535
+ const { collapsed: _hidden, ...meta } = src.meta;
1536
+ if ("views" in meta)
1537
+ meta.views = copyViews(meta.views, propIdMap, recIdMap, new Set(listProperties(db, id).filter((p) => p.type === "relation" && p.config?.database === id).map((p) => p.id)));
1538
+ emitMetaPatch(db, dup.id, null, meta);
1539
+ }
1499
1540
  return getDatabase(db, dup.id);
1500
1541
  });
1501
1542
  deleteDatabase = grouped(function deleteDatabase(db, id) {
@@ -9348,7 +9389,7 @@ var package_default;
9348
9389
  var init_package = __esm(() => {
9349
9390
  package_default = {
9350
9391
  name: "@tensorix/metahub",
9351
- version: "0.5.4",
9392
+ version: "0.5.7",
9352
9393
  description: "Metahub core library and CLI",
9353
9394
  license: "AGPL-3.0-only",
9354
9395
  type: "module",
@@ -13282,6 +13323,11 @@ var init_page_messages = __esm(() => {
13282
13323
  "\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u8F93\u5165\u4EE4\u724C\u3002": "Session expired. Enter the token again.",
13283
13324
  \u{9644}\u{4ef6}\u{5df2}\u{5931}\u{6548}: "Attachment unavailable",
13284
13325
  \u{94fe}\u{63a5}\u{7f3a}\u{5c11}\u{5206}\u{4eab}\u{6570}\u{636e}: "The link is missing share data",
13326
+ \u{65e0}\u{6cd5}\u{6253}\u{5f00}\u{8fd9}\u{4efd}\u{5206}\u{4eab}: "Unable to open this share",
13327
+ "\u8BF7\u4ECE\u539F\u6D88\u606F\u4E2D\u91CD\u65B0\u590D\u5236\u5B8C\u6574\u94FE\u63A5\uFF0C\u518D\u6253\u5F00\u8BD5\u8BD5\u3002": "Copy the full link from the original message, then open it again.",
13328
+ "\u5982\u679C\u4ECD\u7136\u65E0\u6CD5\u6253\u5F00\uFF0C\u8BF7\u8054\u7CFB\u5206\u4EAB\u8005\u91CD\u65B0\u53D1\u9001\u94FE\u63A5\u3002": "If it still won't open, ask the sender for a new link.",
13329
+ "\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u540E\u91CD\u8BD5\uFF0C\u6216\u8054\u7CFB\u5206\u4EAB\u8005\u786E\u8BA4\u94FE\u63A5\u662F\u5426\u4ECD\u7136\u6709\u6548\u3002": "Check your connection and try again, or ask the sender whether the link is still valid.",
13330
+ \u{91cd}\u{65b0}\u{52a0}\u{8f7d}: "Reload",
13285
13331
  \u{8bf7}\u{8f93}\u{5165}\u{5206}\u{4eab}\u{53e3}\u{4ee4}: "Enter the share passcode",
13286
13332
  \u{94fe}\u{63a5}\u{7f3a}\u{5c11}\u{5bc6}\u{94a5}: "The link is missing its key",
13287
13333
  \u{53e3}\u{4ee4}\u{9519}\u{8bef}\u{6216}\u{94fe}\u{63a5}\u{5df2}\u{5931}\u{6548}: "Wrong passcode or the link has expired",
@@ -13496,6 +13542,92 @@ function getDropKnobs(db, siteId) {
13496
13542
  }
13497
13543
  var EXPECTED_EDGE_WORKER_VERSION = "3", CONFIG_KEY = "edge_config", DEPLOY_KEY = "edge_deploy_progress", KNOBS_PREFIX = "drop_knobs:", R2_PROVISION_KEY = "edge_r2_provision_progress";
13498
13544
 
13545
+ // src/core/ui-theme.ts
13546
+ var LIGHT_THEME, DARK_THEME, declarations = (palette) => Object.entries(palette).map(([k, v]) => `--${k}:${v};`).join(""), THEME_CSS, THEME_INIT_SCRIPT;
13547
+ var init_ui_theme = __esm(() => {
13548
+ LIGHT_THEME = {
13549
+ bg: "#ffffff",
13550
+ paper: "#ffffff",
13551
+ surface: "#fafaf9",
13552
+ "surface-2": "#f1f1ef",
13553
+ sidebar: "#f7f7f5",
13554
+ fg: "#37352f",
13555
+ "fg-soft": "#595750",
13556
+ muted: "#6f6d66",
13557
+ line: "#eae9e6",
13558
+ "line-strong": "#d8d7d3",
13559
+ accent: "#3867b2",
13560
+ "accent-fg": "#ffffff",
13561
+ "accent-soft": "#edf2f9",
13562
+ hover: "rgba(55,53,47,.035)",
13563
+ "hover-2": "rgba(55,53,47,.07)",
13564
+ "code-bg": "#f6f5f3",
13565
+ "selected-bg": "#f7f9fc",
13566
+ "selected-border": "#a8b8ce",
13567
+ "selected-icon-bg": "#edf2f8",
13568
+ "selected-icon-fg": "#4d678b",
13569
+ "selected-muted": "#626b78",
13570
+ "selected-hover-bg": "#f1f5fa",
13571
+ "selected-hover-border": "#8199bb",
13572
+ danger: "#b6423a",
13573
+ "danger-fg": "#ffffff",
13574
+ "danger-soft": "#f9eeec",
13575
+ success: "#347653",
13576
+ warning: "#866022",
13577
+ "warning-soft": "#faf3e5",
13578
+ "brand-purple": "#79619d",
13579
+ "brand-teal": "#397d76",
13580
+ "brand-orange": "#a06436",
13581
+ "shadow-sm": "0 1px 2px rgba(35,32,25,.035)",
13582
+ "shadow-md": "0 4px 14px rgba(35,32,25,.08)",
13583
+ "shadow-lg": "0 16px 48px rgba(35,32,25,.13)",
13584
+ "brand-stripe": "linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"
13585
+ };
13586
+ DARK_THEME = {
13587
+ bg: "#191919",
13588
+ paper: "#191919",
13589
+ surface: "#222222",
13590
+ "surface-2": "#2c2c2b",
13591
+ sidebar: "#202020",
13592
+ fg: "#e8e7e4",
13593
+ "fg-soft": "#c0beb8",
13594
+ muted: "#aaa8a2",
13595
+ line: "#30302f",
13596
+ "line-strong": "#42423f",
13597
+ accent: "#8aaddb",
13598
+ "accent-fg": "#172235",
13599
+ "accent-soft": "#263242",
13600
+ hover: "rgba(255,255,255,.035)",
13601
+ "hover-2": "rgba(255,255,255,.065)",
13602
+ "code-bg": "#242423",
13603
+ "selected-bg": "#242a32",
13604
+ "selected-border": "#56657a",
13605
+ "selected-icon-bg": "#2d3745",
13606
+ "selected-icon-fg": "#afc5e2",
13607
+ "selected-muted": "#b5bfcd",
13608
+ "selected-hover-bg": "#2a313b",
13609
+ "selected-hover-border": "#7287a3",
13610
+ danger: "#e89890",
13611
+ "danger-fg": "#2b1716",
13612
+ "danger-soft": "#3b2927",
13613
+ success: "#92bd9b",
13614
+ warning: "#d6b578",
13615
+ "warning-soft": "#363024",
13616
+ "brand-purple": "#baa6d1",
13617
+ "brand-teal": "#8cbbb4",
13618
+ "brand-orange": "#d4a37d",
13619
+ "shadow-sm": "0 1px 2px rgba(0,0,0,.15)",
13620
+ "shadow-md": "0 4px 14px rgba(0,0,0,.25)",
13621
+ "shadow-lg": "0 16px 48px rgba(0,0,0,.35)",
13622
+ "brand-stripe": "linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"
13623
+ };
13624
+ THEME_CSS = `:root{color-scheme:light;${declarations(LIGHT_THEME)}--card:var(--surface-2);--ok:var(--success)}
13625
+ @media(prefers-color-scheme:dark){:root:not([data-resolved]){color-scheme:dark;${declarations(DARK_THEME)}}}
13626
+ :root[data-resolved="dark"]{color-scheme:dark;${declarations(DARK_THEME)}}
13627
+ ::selection{background:var(--accent-soft);color:var(--accent)}`;
13628
+ THEME_INIT_SCRIPT = `(function(){var t;try{t=localStorage.getItem('mh-theme')}catch(e){}var d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.dataset.resolved=d?'dark':'light';var m=document.querySelector('meta[name="theme-color"]');if(m)m.content=d?'${DARK_THEME.bg}':'${LIGHT_THEME.bg}'})();`;
13629
+ });
13630
+
13499
13631
  // src/core/inject-runtime.ts
13500
13632
  function injectRuntimeTag(html) {
13501
13633
  const i = html.toLowerCase().indexOf("<head>");
@@ -13606,17 +13738,9 @@ function unlockPage(locale = "zh-CN") {
13606
13738
  return `<!doctype html><html lang="${locale}"><head>
13607
13739
  <meta charset="utf-8">
13608
13740
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
13609
- <meta name="theme-color" content="#ffffff">
13741
+ <meta name="theme-color" content="${LIGHT_THEME.bg}">
13610
13742
  <title>${m("\u9A8C\u8BC1\u8BBF\u95EE\u4EE4\u724C \xB7 metahub")}</title>
13611
- <script>
13612
- // FOUC-guard theme resolution, same key/logic as src/webui/theme.ts.
13613
- try {
13614
- var mt = localStorage.getItem("mh-theme");
13615
- var dark = mt === "dark" || (mt !== "light" && matchMedia("(prefers-color-scheme: dark)").matches);
13616
- document.documentElement.dataset.resolved = dark ? "dark" : "light";
13617
- document.head.querySelector('meta[name=theme-color]').setAttribute("content", dark ? "#1a1a1c" : "#ffffff");
13618
- } catch (e) {}
13619
- </script>
13743
+ <script>${THEME_INIT_SCRIPT}</script>
13620
13744
  <style>${UNLOCK_CSS}</style></head><body>
13621
13745
  <div class="boot" id="boot"><svg viewBox="0 0 24 24">${CUBE_SVG}</svg></div>
13622
13746
  <form class="card" id="f" style="display:none" autocomplete="on">
@@ -13765,15 +13889,20 @@ function cookieMutationRejection(req, url, cfg) {
13765
13889
  function unauthorized() {
13766
13890
  return Response.json({ error: "unauthorized" }, { status: 401 });
13767
13891
  }
13768
- var COOKIE = "mh_token", COOKIE_ATTRS = "path=/; SameSite=Strict; Max-Age=31536000", HTML_HEADERS, CUBE_SVG = '<path d="M21 7.5 12 2 3 7.5v9L12 22l9-5.5z"/><path d="m3 7.5 9 5.5 9-5.5M12 22v-9"/>', EYE_SVG = '<path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>', EYE_OFF_SVG = '<path d="M4 4l16 16M9.5 9.5a3 3 0 0 0 4 4M6.5 6.7C4.5 8 3 10 2 12c2 4 6 7 10 7 1.6 0 3-.4 4.4-1.1M9.5 5.2A9.7 9.7 0 0 1 12 5c4 0 8 3 10 7-.6 1.3-1.5 2.5-2.5 3.5"/>', UNLOCK_CSS = `
13769
- :root{color-scheme:light;
13770
- --bg:#ffffff;--surface:#fbfbfa;--surface-2:#f1f1ef;--fg:#2c2c30;--fg-soft:#5b5b62;
13771
- --muted:#75757f;--line:#ebebe8;--line-strong:#dededb;--accent:#4a55d6;--accent-fg:#ffffff;
13772
- --accent-soft:#eef0ff;--danger:#d6473b;--danger-soft:#fdeceb;--code-bg:#f8f8f6}
13773
- :root[data-resolved="dark"]{color-scheme:dark;
13774
- --bg:#1a1a1c;--surface:#202022;--surface-2:#2a2a2d;--fg:#e6e6e9;--fg-soft:#b4b4bb;
13775
- --muted:#8b8b95;--line:#2c2c30;--line-strong:#3a3a40;--accent:#7b86ff;--accent-fg:#14141a;
13776
- --accent-soft:#23243a;--danger:#f87168;--danger-soft:#341e1c;--code-bg:#26262a}
13892
+ var COOKIE = "mh_token", COOKIE_ATTRS = "path=/; SameSite=Strict; Max-Age=31536000", HTML_HEADERS, CUBE_SVG = '<path d="M21 7.5 12 2 3 7.5v9L12 22l9-5.5z"/><path d="m3 7.5 9 5.5 9-5.5M12 22v-9"/>', EYE_SVG = '<path d="M2 12s3.6-7 10-7 10 7 10 7-3.6 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>', EYE_OFF_SVG = '<path d="M4 4l16 16M9.5 9.5a3 3 0 0 0 4 4M6.5 6.7C4.5 8 3 10 2 12c2 4 6 7 10 7 1.6 0 3-.4 4.4-1.1M9.5 5.2A9.7 9.7 0 0 1 12 5c4 0 8 3 10 7-.6 1.3-1.5 2.5-2.5 3.5"/>', UNLOCK_CSS, escapeAttr = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13893
+ var init_auth = __esm(() => {
13894
+ init_ui_theme();
13895
+ init_page_messages();
13896
+ init_inject_runtime();
13897
+ init_token();
13898
+ init_pairing();
13899
+ init_protocol();
13900
+ HTML_HEADERS = {
13901
+ "content-type": "text/html; charset=utf-8",
13902
+ "referrer-policy": "same-origin"
13903
+ };
13904
+ UNLOCK_CSS = `
13905
+ ${THEME_CSS}
13777
13906
  *{box-sizing:border-box}
13778
13907
  html,body{margin:0;min-height:100%}
13779
13908
  body{font-family:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
@@ -13838,17 +13967,7 @@ var COOKIE = "mh_token", COOKIE_ATTRS = "path=/; SameSite=Strict; Max-Age=315360
13838
13967
  @keyframes spin{to{transform:rotate(360deg)}}
13839
13968
  @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important}
13840
13969
  .brand path{stroke-dashoffset:0}}
13841
- `, escapeAttr = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13842
- var init_auth = __esm(() => {
13843
- init_page_messages();
13844
- init_inject_runtime();
13845
- init_token();
13846
- init_pairing();
13847
- init_protocol();
13848
- HTML_HEADERS = {
13849
- "content-type": "text/html; charset=utf-8",
13850
- "referrer-policy": "same-origin"
13851
- };
13970
+ `;
13852
13971
  });
13853
13972
 
13854
13973
  // src/core/sync/http-status.ts
@@ -14732,8 +14851,7 @@ function notFoundResponse(locale, title, detail) {
14732
14851
  <meta name="robots" content="noindex">
14733
14852
  <title>${escapeHtml(title)}</title>
14734
14853
  <style>
14735
- :root{--bg:#ffffff;--fg:#1f2328;--muted:#6e7781;--line:#d0d7de;--accent:#0969da;--card:#f6f8fa}
14736
- @media (prefers-color-scheme: dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8b949e;--line:#30363d;--accent:#4493f8;--card:#161b22}}
14854
+ ${THEME_CSS}
14737
14855
  *{box-sizing:border-box}
14738
14856
  body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;
14739
14857
  min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
@@ -14757,6 +14875,7 @@ function notFoundResponse(locale, title, detail) {
14757
14875
  });
14758
14876
  }
14759
14877
  var init_sites_serve = __esm(() => {
14878
+ init_ui_theme();
14760
14879
  init_sites();
14761
14880
  init_site_channel_store();
14762
14881
  init_grants_core();
@@ -14819,6 +14938,20 @@ async function mintGuestSession(scope, opts) {
14819
14938
  return { sub: opts.sub, exp, cookie };
14820
14939
  }
14821
14940
 
14941
+ // src/core/sync/share-file-styles.ts
14942
+ var SHARE_FILE_CSS = `
14943
+ article.doc p.mh-file{margin:0}
14944
+ article.doc .mh-file+.mh-file{border-top:1px solid var(--line)}
14945
+ article.doc .mh-file a{display:block;position:relative;min-height:44px;max-width:100%;padding:11px 36px;border:0;border-radius:4px;background:transparent;color:var(--fg);font-size:14px;line-height:22px;text-decoration:none;overflow-wrap:anywhere}
14946
+ article.doc .mh-file a::before,article.doc .mh-file a::after{content:"";position:absolute;top:14px;width:16px;height:16px;background:currentColor;opacity:.65;mask:var(--file-icon) center/contain no-repeat}
14947
+ article.doc .mh-file a::before{left:10px;--file-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z'/%3E%3Cpath d='M14 2v6h6M8 13h8M8 17h5'/%3E%3C/svg%3E")}
14948
+ article.doc .mh-file a::after{right:10px;--file-icon:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3v12m-4-4 4 4 4-4M5 16v4a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-4'/%3E%3C/svg%3E")}
14949
+ article.doc .mh-file a[href]:not([aria-disabled="true"]):hover{background:var(--card);color:var(--accent);text-decoration:underline;text-underline-offset:3px}
14950
+ article.doc .mh-file a:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;background:var(--card);color:var(--accent)}
14951
+ article.doc .mh-file a[aria-disabled="true"]{color:var(--muted);pointer-events:none}
14952
+ article.doc .mh-file a[aria-disabled="true"]::after{display:none}
14953
+ `;
14954
+
14822
14955
  // src/core/sync/share-copy.ts
14823
14956
  function markdownTable(header, rows) {
14824
14957
  const cell = (s) => s.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
@@ -14852,12 +14985,11 @@ var ICON_COPY = '<svg class="ic-copy" viewBox="0 0 24 24" aria-hidden="true"><re
14852
14985
  .copy-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
14853
14986
  .copy-btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
14854
14987
  .copy-btn .ic-check{display:none}
14855
- .copy-btn.done{color:var(--ok,#1a7f37);border-color:var(--ok,#1a7f37)}
14988
+ .copy-btn.done{color:var(--success);border-color:var(--success)}
14856
14989
  .copy-btn.done .ic-copy{display:none}.copy-btn.done .ic-check{display:block}
14857
14990
  .copy-btn.done .ic-check path{stroke-dasharray:24;stroke-dashoffset:24;animation:mh-draw .22s ease-out forwards}
14858
- .copy-btn.fail{color:#cf222e;border-color:#cf222e}
14991
+ .copy-btn.fail{color:var(--danger);border-color:var(--danger)}
14859
14992
  @keyframes mh-draw{to{stroke-dashoffset:0}}
14860
- @media (prefers-color-scheme:dark){:root{--ok:#3fb950}}
14861
14993
  @media (max-width:480px){.copy-btn span{display:none}.copy-btn{padding:0 9px}}
14862
14994
  @media (prefers-reduced-motion:reduce){.copy-btn .ic-check path{animation:none;stroke-dashoffset:0}}`;
14863
14995
  var init_share_copy = __esm(() => {
@@ -15272,14 +15404,13 @@ function pageShell(title, inner, opts) {
15272
15404
  <meta name="robots" content="noindex">
15273
15405
  <title>${escapeHtml(title)}</title>
15274
15406
  <style>
15275
- :root{--bg:#ffffff;--fg:#1f2328;--muted:#6e7781;--line:#d0d7de;--accent:#0969da;--card:#f6f8fa}
15276
- @media (prefers-color-scheme: dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8b949e;--line:#30363d;--accent:#4493f8;--card:#161b22}}
15407
+ ${THEME_CSS}
15277
15408
  *{box-sizing:border-box}
15278
15409
  body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif}
15279
15410
  .wrap{max-width:820px;margin:0 auto;padding:32px 20px 80px}
15280
15411
  header.mh{display:flex;align-items:center;gap:12px;justify-content:space-between;margin-bottom:20px;border-bottom:1px solid var(--line);padding-bottom:14px}
15281
15412
  header.mh h1.title{font-size:22px;margin:0}
15282
- .edit-btn{background:var(--accent);color:#fff;border:0;border-radius:7px;height:32px;padding:0 14px;cursor:pointer;font-size:13px;font-weight:500;font-family:inherit}
15413
+ .edit-btn{background:var(--accent);color:var(--accent-fg);border:0;border-radius:7px;height:32px;padding:0 14px;cursor:pointer;font-size:13px;font-weight:500;font-family:inherit}
15283
15414
  ${COPY_CSS}
15284
15415
  .hint{color:var(--muted);font-size:13px;margin:0 0 14px}
15285
15416
  article.doc h1,article.doc h2,article.doc h3{line-height:1.3;margin:1.4em 0 .5em}
@@ -15293,11 +15424,11 @@ function pageShell(title, inner, opts) {
15293
15424
  article.doc td,article.doc th,table.db td,table.db th{border:1px solid var(--line);padding:7px 10px;text-align:left}
15294
15425
  table.db th{background:var(--card)}
15295
15426
  .table-wrap{overflow:auto}
15296
- td[contenteditable]{outline:none} td.saving{opacity:.5} td.saved{background:rgba(46,160,67,.18)}
15427
+ td[contenteditable]{outline:none} td.saving{opacity:.5} td.saved{background:color-mix(in srgb,var(--success) 18%,transparent)}
15297
15428
  .tag{display:inline-block;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:1px 9px;font-size:12px}
15298
15429
  .muted{color:var(--muted)} .mh-img{text-align:center}
15299
15430
  .mh-media{text-align:center} .mh-media video,.mh-media audio{max-width:100%}
15300
- .mh-file a{display:inline-block;background:var(--card);border:1px solid var(--line);border-radius:8px;padding:8px 14px;color:var(--accent);text-decoration:none}
15431
+ ${SHARE_FILE_CSS}
15301
15432
  .mh-doclink{background:var(--card);border:1px solid var(--line);border-radius:6px;padding:0 5px;white-space:nowrap}
15302
15433
  .mh-doclink::before{content:"\uD83D\uDCC4";font-size:.85em;margin-right:3px}
15303
15434
  iframe.mh-embed{width:100%;border:1px solid var(--line);border-radius:8px;min-height:240px}
@@ -15306,8 +15437,8 @@ function pageShell(title, inner, opts) {
15306
15437
  .row{display:flex;gap:10px;align-items:center;margin:10px 0}
15307
15438
  form.pw{max-width:320px;margin:12vh auto 0;display:flex;flex-direction:column;gap:12px}
15308
15439
  form.pw input{padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--fg)}
15309
- form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:#fff;cursor:pointer}
15310
- form.pw .err{color:#f85149;font-size:13px;margin:0}
15440
+ form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:var(--accent-fg);cursor:pointer}
15441
+ form.pw .err{color:var(--danger);font-size:13px;margin:0}
15311
15442
  footer.mh{margin-top:48px;border-top:1px solid var(--line);padding-top:14px;color:var(--muted);font-size:12px;text-align:center}
15312
15443
  </style></head><body>
15313
15444
  ${opts.bare ? `<div class="wrap">${inner}</div>` : `<div class="wrap">
@@ -15321,6 +15452,7 @@ ${script}
15321
15452
  }
15322
15453
  var HTML, HASH_RE2, SHARE_SESSION_TTL_MS;
15323
15454
  var init_share_serve = __esm(() => {
15455
+ init_ui_theme();
15324
15456
  init_ids();
15325
15457
  init_shares();
15326
15458
  init_documents();
@@ -16487,24 +16619,15 @@ function startServer(opts = {}) {
16487
16619
  };
16488
16620
  }
16489
16621
  // src/webui/server/assets.ts
16622
+ init_ui_theme();
16490
16623
  import { fileURLToPath } from "url";
16491
16624
  import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
16492
16625
  import { join as join5, dirname } from "path";
16493
16626
 
16494
16627
  // src/webui/styles.css
16495
16628
  var styles_default = ` :root {
16496
- /* Tells browser chrome (status-bar text, form controls, soft keyboard,
16497
- overscroll canvas) which appearance the page renders. Keyed off
16498
- data-resolved, so a manual theme toggle retunes it too. */
16499
- color-scheme: light;
16500
- --bg:#ffffff; --surface:#fbfbfa; --surface-2:#f1f1ef; --sidebar:#f7f7f5;
16501
- --fg:#2c2c30; --fg-soft:#5b5b62; --muted:#75757f; --line:#ebebe8; --line-strong:#dededb;
16502
- --accent:#4a55d6; --accent-fg:#ffffff; --accent-soft:#eef0ff; --danger:#d6473b; --danger-soft:#fdeceb;
16503
- --success:#1f8f3e;
16504
- --hover:rgba(45,45,55,.045); --hover-2:rgba(45,45,55,.08);
16505
- --shadow-sm:0 1px 2px rgba(20,20,40,.06),0 1px 1px rgba(20,20,40,.04);
16506
- --shadow-md:0 4px 12px rgba(20,20,40,.08),0 2px 4px rgba(20,20,40,.05);
16507
- --shadow-lg:0 16px 48px rgba(20,20,40,.16),0 4px 12px rgba(20,20,40,.08);
16629
+ /* Palette comes from core/ui-theme.ts; dimensions and typography stay here. */
16630
+
16508
16631
  --radius:8px; --radius-sm:6px; --radius-lg:14px; --radius-xl:16px;
16509
16632
  --fs-body:14px; --fs-ui:13.5px; --fs-sm:12.5px; --fs-xs:11.5px; --fs-micro:11px;
16510
16633
  --ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
@@ -16512,23 +16635,12 @@ var styles_default = ` :root {
16512
16635
  --kbd-font:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
16513
16636
  --tip-bg:#1f1f23; --tip-fg:#ffffff; --tip-kbd-bg:rgba(255,255,255,.14); --tip-kbd-fg:rgba(255,255,255,.92);
16514
16637
  --note-lh:1.6;
16515
- --code-bg:#f8f8f6;
16516
16638
  --hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
16517
16639
  --hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
16518
16640
  }
16519
- /* Dark palette \u2014 exactly once. JS resolves the user's choice (including
16520
- * "system" \u2192 the OS preference) to <html data-resolved="light|dark"> before
16521
- * first paint (FOUC script in the shell) and on every change (theme.ts), so
16522
- * CSS never needs a prefers-color-scheme duplicate of this block. */
16641
+ /* Dark tooltip and syntax tokens; shared UI colors/elevation live in core/ui-theme.ts. */
16523
16642
  :root[data-resolved="dark"] {
16524
- color-scheme: dark;
16525
- --bg:#1a1a1c; --surface:#202022; --surface-2:#2a2a2d; --sidebar:#171719;
16526
- --fg:#e6e6e9; --fg-soft:#b4b4bb; --muted:#8b8b95; --line:#2c2c30; --line-strong:#3a3a40;
16527
- --accent:#7b86ff; --accent-fg:#14141a; --accent-soft:#23243a; --danger:#f87168; --danger-soft:#341e1c;
16528
- --success:#3fb950;
16529
- --hover:rgba(255,255,255,.05); --hover-2:rgba(255,255,255,.09);
16530
- --shadow-sm:0 1px 2px rgba(0,0,0,.4); --shadow-md:0 4px 14px rgba(0,0,0,.45); --shadow-lg:0 20px 56px rgba(0,0,0,.6);
16531
- --code-bg:#1d1d20;
16643
+
16532
16644
  --tip-bg:#2e2e32; --tip-fg:var(--fg); --tip-kbd-bg:rgba(255,255,255,.08); --tip-kbd-fg:var(--fg-soft);
16533
16645
  --hl-cmt:#7d7d86; --hl-key:#d291e4; --hl-str:#7ed492; --hl-num:#e0a566;
16534
16646
  --hl-fn:#86a8ff; --hl-type:#5fc6e0; --hl-var:#f0936b;
@@ -16655,7 +16767,7 @@ var styles_default = ` :root {
16655
16767
  .navitem { display:flex; align-items:center; gap:3px; width:100%; text-align:left; padding:5px 6px; margin-bottom:2px; border-radius:var(--radius-sm);
16656
16768
  color:var(--fg-soft); font-size:var(--fs-ui); position:relative; white-space:nowrap; transition:background .1s; }
16657
16769
  .navitem:hover { background:var(--hover-2); }
16658
- .navitem.active { background:var(--bg); color:var(--fg); font-weight:600; box-shadow:var(--shadow-sm); }
16770
+ .navitem.active { background:var(--hover-2); color:var(--fg); font-weight:600; box-shadow:none; }
16659
16771
  .navitem .tw { width:18px; height:18px; display:grid; place-items:center; color:var(--muted); border-radius:4px; flex:none; }
16660
16772
  .navitem .tw:hover { background:var(--hover-2); }
16661
16773
  .navitem .tw svg { width:13px; height:13px; transition:transform .12s; }
@@ -16730,13 +16842,13 @@ var styles_default = ` :root {
16730
16842
  (driven by --mark-y / --mark-h from JS, eased by CSS) */
16731
16843
  .set-rail::before { content:""; position:absolute; left:0; top:4px; bottom:4px;
16732
16844
  width:2px; border-radius:2px; background:var(--line); }
16733
- .set-rail-mark { position:absolute; left:0; width:2px; border-radius:2px; background:var(--accent);
16845
+ .set-rail-mark { position:absolute; left:0; width:2px; border-radius:2px; background:var(--fg-soft);
16734
16846
  height:var(--mark-h,0); transform:translateY(var(--mark-y,0));
16735
16847
  transition:transform .26s cubic-bezier(.4,0,.2,1), height .26s cubic-bezier(.4,0,.2,1); }
16736
16848
  .set-rail-row { display:flex; align-items:center; gap:10px; padding:7px 10px; border-radius:var(--radius-sm);
16737
16849
  color:var(--muted); text-align:left; cursor:pointer; transition:color .14s, background .14s; }
16738
16850
  .set-rail-row:hover { background:var(--hover); color:var(--fg); }
16739
- .set-rail-row.active { color:var(--accent); }
16851
+ .set-rail-row.active { color:var(--fg); }
16740
16852
  .set-rail-ico { flex:none; display:grid; place-items:center; width:16px; height:16px; opacity:.85; }
16741
16853
  .set-rail-ico svg { width:15px; height:15px; }
16742
16854
  .set-rail-label { font-size:var(--fs-sm); font-weight:550; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -16793,7 +16905,7 @@ var styles_default = ` :root {
16793
16905
  .set-row.dim > .set-row-line { opacity:.62; }
16794
16906
  /* status dot in a caption: the only colour a row carries */
16795
16907
  .cap-dot { display:inline-block; flex:none; width:7px; height:7px; border-radius:50%; background:var(--muted); }
16796
- .cap-dot.ok { background:var(--success); } .cap-dot.warn { background:var(--warning,#9a6700); } .cap-dot.err { background:var(--danger); }
16908
+ .cap-dot.ok { background:var(--success); } .cap-dot.warn { background:var(--warning); } .cap-dot.err { background:var(--danger); }
16797
16909
  .cap-err { color:var(--danger); }
16798
16910
  /* tag variants next to a title: neutral outline (default), accent (roles), self (soft fill) */
16799
16911
  .set-row-title .set-row-tag { margin-left:0; vertical-align:0; flex:none; }
@@ -16864,8 +16976,8 @@ var styles_default = ` :root {
16864
16976
  .audit-actor { flex:none; font-size:11px; font-weight:600; padding:1px 7px; border-radius:999px;
16865
16977
  background:var(--surface); border:1px solid var(--line); color:var(--fg-soft); max-width:110px;
16866
16978
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
16867
- .audit-actor.ai { background:color-mix(in srgb, #8b5cf6 14%, transparent);
16868
- border-color:color-mix(in srgb, #8b5cf6 35%, transparent); color:#8b5cf6; }
16979
+ .audit-actor.ai { background:color-mix(in srgb, var(--brand-purple) 14%, transparent);
16980
+ border-color:color-mix(in srgb, var(--brand-purple) 35%, transparent); color:var(--brand-purple); }
16869
16981
  .audit-summary { font-size:var(--fs-ui); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
16870
16982
  .audit-ent { color:inherit; font-weight:600; text-decoration:none; margin-left:2px; }
16871
16983
  .audit-ent[href]:hover { color:var(--accent); text-decoration:underline; }
@@ -16884,7 +16996,7 @@ var styles_default = ` :root {
16884
16996
  .audit-end { display:flex; align-items:center; gap:12px; margin:18px 0 0; color:var(--muted); font-size:var(--fs-xs); }
16885
16997
  .audit-end::before, .audit-end::after { content:""; flex:1; height:1px; background:var(--line); }
16886
16998
  @media (hover:none) { .audit-revert { opacity:1; } }
16887
- .set-callout.warn { border-color:color-mix(in srgb,var(--warning,#9a6700) 32%,var(--line)); }
16999
+ .set-callout.warn { border-color:color-mix(in srgb,var(--warning) 32%,var(--line)); }
16888
17000
  /* a quiet helper line under a field / chip group */
16889
17001
  .set-hint { font-size:var(--fs-xs); color:var(--muted); line-height:1.5; margin:3px 0 4px; }
16890
17002
  /* progressive disclosure ("\u5DE5\u4F5C\u539F\u7406" / "\u8FDB\u9636") \u2014 native details, themed */
@@ -16903,7 +17015,7 @@ var styles_default = ` :root {
16903
17015
  .provider-chip { padding:6px 12px; border-radius:999px; border:1px solid var(--line-strong); background:var(--surface);
16904
17016
  color:var(--fg-soft); font-size:var(--fs-sm); font-weight:550; transition:border-color .12s, background .12s, color .12s; }
16905
17017
  .provider-chip:hover { border-color:var(--muted); color:var(--fg); }
16906
- .provider-chip.sel { border-color:var(--accent); background:var(--accent-soft); color:var(--accent); box-shadow:0 0 0 1px var(--accent) inset; }
17018
+ .provider-chip.sel { border-color:var(--selected-border); background:var(--selected-bg); color:var(--accent); box-shadow:none; }
16907
17019
  /* ---- \u5173\u4E8E page -----------------------------------------------------------
16908
17020
  Product identity hero over the standard SetSection/SetRow stack. The cube
16909
17021
  mark sizes to a nav-icon's proportions scaled up; the version line reuses
@@ -16943,8 +17055,8 @@ var styles_default = ` :root {
16943
17055
  @keyframes ver-indet { 0% { left:-42%; } 100% { left:100%; } }
16944
17056
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
16945
17057
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
16946
- text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
16947
- background:var(--surface); color:var(--fg-soft); box-shadow:var(--shadow-sm);
17058
+ text-align:left; padding:16px; border:1px solid var(--line); border-radius:var(--radius);
17059
+ background:var(--bg); color:var(--fg-soft); box-shadow:none;
16948
17060
  transition:border-color .14s, background .14s, transform .14s, box-shadow .14s; }
16949
17061
  .theme-card:hover { border-color:var(--muted); transform:translateY(-1px); }
16950
17062
  .theme-card .tc-ico { width:34px; height:34px; display:grid; place-items:center; border-radius:9px;
@@ -16955,8 +17067,10 @@ var styles_default = ` :root {
16955
17067
  .theme-card .tc-check { position:absolute; top:10px; right:10px; width:18px; height:18px; border-radius:50%;
16956
17068
  background:var(--accent); color:var(--accent-fg); display:grid; place-items:center; opacity:0; transform:scale(.6); transition:opacity .14s, transform .14s; }
16957
17069
  .theme-card .tc-check svg { width:12px; height:12px; stroke-width:2.6; }
16958
- .theme-card.sel { border-color:var(--accent); background:var(--accent-soft); box-shadow:0 0 0 1px var(--accent) inset, var(--shadow-sm); }
16959
- .theme-card.sel .tc-ico { background:var(--accent); color:var(--accent-fg); }
17070
+ .theme-card.sel { border-color:var(--selected-border); background:var(--selected-bg); box-shadow:none; }
17071
+ .theme-card.sel:hover { background:var(--selected-hover-bg); border-color:var(--selected-hover-border); }
17072
+ .theme-card.sel .tc-desc { color:var(--selected-muted); }
17073
+ .theme-card.sel .tc-ico { background:var(--selected-icon-bg); color:var(--selected-icon-fg); }
16960
17074
  .theme-card.sel .tc-check { opacity:1; transform:scale(1); }
16961
17075
  @media (max-width:560px) { .theme-grid { grid-template-columns:1fr; } }
16962
17076
  .brand-grid { grid-template-columns:repeat(auto-fit,minmax(min(100%,120px),1fr)); }
@@ -16973,7 +17087,7 @@ var styles_default = ` :root {
16973
17087
  .sync-health-sub { display:inline-flex; align-items:center; gap:8px; max-width:100%; min-width:0; }
16974
17088
  .sync-health-dot { flex:none; width:8px; height:8px; border-radius:50%; }
16975
17089
  .sync-health-dot.dot-ok { background:var(--success); }
16976
- .sync-health-dot.dot-warn { background:var(--warning,#9a6700); }
17090
+ .sync-health-dot.dot-warn { background:var(--warning); }
16977
17091
  .sync-health-dot.dot-error { background:var(--danger); }
16978
17092
  /* shared expander chevron (bucket + device roster rows) */
16979
17093
  .row-chev { flex:none; color:var(--muted); display:flex; transition:transform .15s; }
@@ -17070,7 +17184,7 @@ var styles_default = ` :root {
17070
17184
  .btn-secondary:hover { background:var(--surface-2); }
17071
17185
  .btn-primary { background:var(--accent); color:var(--accent-fg); box-shadow:var(--shadow-sm); }
17072
17186
  .btn-primary:hover { filter:brightness(1.07); }
17073
- .btn-danger { background:var(--danger); color:#fff; } .btn-danger:hover { filter:brightness(1.05); }
17187
+ .btn-danger { background:var(--danger); color:var(--danger-fg); } .btn-danger:hover { filter:brightness(1.05); }
17074
17188
  .btn.sm { padding:3px 8px; font-size:12px; }
17075
17189
  /* Notion-style ambient sync indicator: a quiet ghost control living in the
17076
17190
  topbar icon row. State reads through the *text/icon colour* \u2014 no filled pill
@@ -17943,7 +18057,7 @@ var styles_default = ` :root {
17943
18057
  .shv-ico svg { width:19px; height:19px; }
17944
18058
  .shv-ico.k-doc { color:var(--accent); background:var(--accent-soft); }
17945
18059
  .shv-ico.k-database { color:var(--fg-soft); }
17946
- .shv-ico.k-site { color:#1a7f5a; }
18060
+ .shv-ico.k-site { color:var(--brand-teal); }
17947
18061
  .shv-main { min-width:0; }
17948
18062
  .shv-title { display:block; max-width:100%; font-weight:600; color:var(--fg); letter-spacing:-.01em;
17949
18063
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; }
@@ -17970,8 +18084,8 @@ var styles_default = ` :root {
17970
18084
  font-variant-numeric:tabular-nums; white-space:nowrap; }
17971
18085
  .shv-dot { width:8px; height:8px; border-radius:50%; background:var(--muted); flex:none; }
17972
18086
  .shv-status.tone-ok .shv-dot { background:var(--success); }
17973
- .shv-status.tone-warn { color:var(--warning,#9a6700); }
17974
- .shv-status.tone-warn .shv-dot { background:var(--warning,#9a6700); }
18087
+ .shv-status.tone-warn { color:var(--warning); }
18088
+ .shv-status.tone-warn .shv-dot { background:var(--warning); }
17975
18089
  .shv-status.tone-muted { color:var(--muted); }
17976
18090
  .shv-status.tone-muted .shv-dot { background:transparent; border:1.5px solid var(--line-strong); }
17977
18091
  .shv-status .sync-ring { width:10px; height:10px; }
@@ -18056,6 +18170,73 @@ var styles_default = ` :root {
18056
18170
  .shv-group-head .btn { margin-left:0; width:100%; justify-content:center; padding:7px 10px; }
18057
18171
  }
18058
18172
 
18173
+ /* Share workspace: quiet Notion-like controls and aligned document rows. */
18174
+ .shares-page { --shv-quiet:color-mix(in srgb,var(--fg) 4%,var(--bg)); --shv-rule:color-mix(in srgb,var(--fg) 8%,var(--bg)); container-type:inline-size; }
18175
+ .shares-page .db-head { margin-bottom:28px; }
18176
+ .shares-page .db-title { display:flex; align-items:center; gap:14px; margin:0; font-size:32px; line-height:1.35; }
18177
+ .shares-page .db-title > .ico { width:27px; height:27px; flex:none; }
18178
+ .shares-page .db-desc { max-width:none; margin-top:12px; line-height:1.7; }
18179
+ .shv-toolbar { margin-bottom:20px; padding:8px 0; }
18180
+ .shv-tools-right { display:flex; width:100%; gap:24px; margin:0; }
18181
+ .shv-seg { gap:8px; border:0; padding:0; background:none; flex:none; }
18182
+ .shv-seg-btn { min-height:34px; padding:7px 12px; border-radius:6px; gap:3px; transition:background .15s; }
18183
+ .shv-seg-btn:hover { background:var(--shv-quiet); }
18184
+ .shv-seg-btn.active { background:color-mix(in srgb,var(--fg) 6%,var(--bg)); box-shadow:none; }
18185
+ .shv-seg-btn .n { font-family:inherit; min-width:1ch; color:var(--fg-soft); }
18186
+ .shv-search { margin-left:auto; max-width:300px; padding:8px 12px; background:var(--shv-quiet); border:1px solid transparent; border-radius:7px; }
18187
+ .shv-search input::placeholder { color:var(--fg-soft); }
18188
+ .shv-search:focus-within { border-color:var(--accent); }
18189
+ .shv-src.btn { background:none; border-color:transparent; box-shadow:none; padding:7px 4px; white-space:nowrap; }
18190
+ .shv-columns { display:grid; grid-template-columns:minmax(0,1fr) 172px 142px; gap:20px; padding:10px 16px; border-radius:6px; background:var(--shv-quiet); color:var(--fg-soft); font-size:12px; }
18191
+ .shv-list { gap:0; }
18192
+ .shv-list[hidden] { display:none; }
18193
+ .shv-row { grid-template-columns:28px minmax(0,1fr) 172px 142px; gap:16px 20px; min-height:96px; padding:20px 16px; border:0; border-bottom:1px solid var(--shv-rule); border-radius:0; background:none; transition:background .15s; }
18194
+ .shv-row:hover { background:color-mix(in srgb,var(--fg) 2%,var(--bg)); box-shadow:none; border-color:var(--shv-rule); }
18195
+ .shv-ico, .shv-ico.k-doc, .shv-ico.k-site { width:28px; height:32px; border-radius:0; background:none; color:var(--fg-soft); }
18196
+ .shv-ico svg { width:24px; height:24px; stroke-width:1.5; }
18197
+ .shv-title { font-size:15px; font-weight:550; line-height:1.5; }
18198
+ .shv-sub { margin-top:5px; font-size:12px; color:var(--fg-soft); }
18199
+ .shv-status { justify-self:start; min-width:0; padding:5px 8px; gap:6px; border-radius:5px; font-size:12px; }
18200
+ .shv-status .ico { width:14px; height:14px; flex:none; }
18201
+ .shv-status.tone-ok { color:var(--success); background:color-mix(in srgb,var(--success) 8%,var(--bg)); }
18202
+ .shv-status.tone-warn { background:color-mix(in srgb,var(--warning) 8%,var(--bg)); }
18203
+ .shv-status.tone-muted { background:var(--shv-quiet); color:var(--fg-soft); }
18204
+ .shv-actions { justify-content:space-between; gap:12px; }
18205
+ .shv-actions .btn { min-height:30px; min-width:88px; border-radius:6px; }
18206
+ .shv-actions .shv-copy { background:none; border-color:transparent; box-shadow:none; }
18207
+ .shv-actions .ico { width:15px; height:15px; }
18208
+ .shv-group-head { margin:16px 0; font-weight:400; }
18209
+ .shv-group-toggle { display:flex; align-items:center; gap:10px; padding:6px; color:var(--fg); border-radius:6px; }
18210
+ .shv-group-toggle:hover { background:var(--shv-quiet); }
18211
+ .shv-group-toggle .ico { width:14px; height:14px; color:var(--fg-soft); }
18212
+ .shv-group-head .n { font-family:inherit; background:var(--shv-quiet); padding:2px 7px; border-radius:5px; color:var(--fg-soft); }
18213
+ .shv-group-head .btn { background:none; border-color:transparent; box-shadow:none; color:var(--fg-soft); }
18214
+ .shares-page button:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
18215
+ @container (max-width:850px) {
18216
+ .shv-tools-right { flex-wrap:wrap; gap:12px; }
18217
+ .shv-seg { width:100%; }
18218
+ .shv-search { margin-left:0; max-width:none; }
18219
+ .shv-columns { grid-template-columns:minmax(0,1fr) 145px 124px; gap:12px; }
18220
+ .shv-row { grid-template-columns:24px minmax(0,1fr) 145px 124px; gap:12px; }
18221
+ }
18222
+ @container (max-width:600px) {
18223
+ .shv-columns { display:none; }
18224
+ .shv-seg { gap:3px; }
18225
+ .shv-seg-btn { flex:1; min-width:0; justify-content:center; padding:8px 3px; font-size:12px; gap:2px; }
18226
+ .shv-seg-btn .n { margin-left:2px; font-size:11px; }
18227
+ .shv-src { flex:none; }
18228
+ .shv-toolbar { margin-bottom:8px; }
18229
+ .shv-row { grid-template-columns:24px minmax(0,1fr); padding:18px 4px; }
18230
+ .shv-title { white-space:normal; display:block; overflow-wrap:anywhere; }
18231
+ .shv-sub { flex-wrap:wrap; white-space:normal; overflow-wrap:anywhere; }
18232
+ .shv-sub > * { flex-shrink:1; min-width:0; }
18233
+ .shv-status { grid-column:2; }
18234
+ .shv-actions { grid-column:2; justify-content:flex-start; }
18235
+ .shv-actions .btn { flex:none; min-height:36px; }
18236
+ .shv-actions .iconbtn { margin-left:auto; }
18237
+ .shv-group-head .btn { width:auto; margin-left:auto; }
18238
+ }
18239
+
18059
18240
  /* in-context "\u5DF2\u5206\u4EAB" badge on docs / tables / site cards */
18060
18241
  .share-badge { display:inline-grid; place-items:center; width:22px; height:22px; border-radius:6px; color:var(--accent);
18061
18242
  background:var(--accent-soft); flex:none; cursor:pointer; transition:filter .12s; }
@@ -18192,7 +18373,7 @@ var styles_default = ` :root {
18192
18373
  .selbar .cnt { font-size:13px; font-weight:600; margin-right:6px; }
18193
18374
  .selbar button { display:flex; align-items:center; gap:6px; color:var(--fg-soft); padding:6px 10px; border-radius:7px; font-size:13px; font-weight:500; }
18194
18375
  .selbar button:hover { background:var(--hover-2); color:var(--fg); }
18195
- .selbar .del:hover { background:var(--danger); color:#fff; }
18376
+ .selbar .del:hover { background:var(--danger); color:var(--danger-fg); }
18196
18377
 
18197
18378
  /* record peek */
18198
18379
  .scrim { position:fixed; inset:0; background:rgba(15,15,25,.32); z-index:70; opacity:0; pointer-events:none; transition:opacity .22s; }
@@ -18521,14 +18702,22 @@ var styles_default = ` :root {
18521
18702
  .tmpl .tt { font-weight:600; font-size:var(--fs-ui); } .tmpl .td { font-size:var(--fs-xs); color:var(--muted); margin-top:2px; }
18522
18703
 
18523
18704
  /* toast */
18524
- .toasts { position:fixed; bottom:20px; left:20px; z-index:110; display:flex; flex-direction:column; gap:8px; }
18525
- .toast { background:var(--fg); color:var(--bg); padding:10px 14px; border-radius:9px; font-size:13px; box-shadow:var(--shadow-lg);
18705
+ .toasts { position:fixed; bottom:20px; left:20px; max-width:calc(100vw - 40px); z-index:110; display:flex; flex-direction:column; gap:8px; }
18706
+ .toast { position:relative; box-sizing:border-box; touch-action:pan-y; user-select:none; transform:translateX(var(--toast-offset, 0px)); transition:transform .18s ease-out, opacity .16s ease; background:var(--fg); color:var(--bg); padding:10px 14px; border-radius:9px; font-size:13px; box-shadow:var(--shadow-lg);
18526
18707
  display:flex; align-items:center; gap:8px; animation:slidein .2s ease; }
18527
18708
  @keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
18528
- .toast.error { background:var(--danger); color:#fff; }
18709
+ .toast.error { background:var(--danger); color:var(--danger-fg); }
18529
18710
  .toast.leaving { animation:toast-out .16s ease-in forwards; pointer-events:none; }
18530
18711
  @keyframes toast-out { to { opacity:0; transform:translateX(-8px); } }
18531
- .toast-msg { min-width:0; }
18712
+ .toast-msg { min-width:0; overflow-wrap:anywhere; }
18713
+ .toast > .ico, .toast-act { flex-shrink:0; }
18714
+ .toast.dragging { animation:none; transition:none; cursor:grabbing; }
18715
+ .toast.leaving.swipe-out { animation:none; transition:transform .16s ease-in, opacity .16s ease-in; }
18716
+ .toast-act:focus-visible { outline:2px solid currentColor; outline-offset:2px; }
18717
+ @media (prefers-reduced-motion:reduce) {
18718
+ .toast, .toast.dragging, .toast.leaving, .toast.leaving.swipe-out { animation:none; transition:none; transform:none; }
18719
+ .toast.leaving { opacity:0; }
18720
+ }
18532
18721
  .toast-act { margin-left:4px; padding:3px 9px; border-radius:6px; font-weight:600; color:inherit; background:color-mix(in srgb, var(--bg) 16%, transparent); white-space:nowrap; }
18533
18722
  .toast-act:hover { background:color-mix(in srgb, var(--bg) 28%, transparent); }
18534
18723
 
@@ -18611,6 +18800,93 @@ var styles_default = ` :root {
18611
18800
  .doc-skel { max-width:720px; margin:40px auto; padding:0 24px; }
18612
18801
 
18613
18802
  /* share / publish modal (share-modal.tsx) */
18803
+ /* Hosting-first object sharing: compact link rows and disclosed details. */
18804
+ .modal:has(.share-dialog) { border-radius:12px; }
18805
+ .modal:has(.share-dialog) .modal-head { position:relative; display:flex; align-items:baseline; gap:12px; padding:24px 56px 0 28px; min-width:0; }
18806
+ .modal:has(.share-dialog) .modal-head h3 { font-size:18px; font-weight:600; letter-spacing:0; padding:0; margin:0; flex:none; max-width:100%; }
18807
+ .modal:has(.share-dialog) .modal-head p { margin:0; font-size:var(--fs-ui); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
18808
+ .modal-close { position:absolute; right:16px; top:18px; color:var(--muted); }
18809
+ .modal:has(.share-dialog) .modal-body { padding:22px 28px 16px; }
18810
+ .modal:has(.share-dialog) .modal-foot { background:var(--bg); padding:12px 28px; justify-content:space-between; gap:12px; }
18811
+ .modal:has(.share-dialog) .modal-foot .btn { min-height:36px; font-weight:500; box-shadow:none; }
18812
+ .share-dialog { display:flex; flex-direction:column; gap:20px; font-size:var(--fs-ui); }
18813
+ .share-host { padding-bottom:20px; border-bottom:1px solid var(--line); }
18814
+ .share-host-label { display:block; margin-bottom:10px; color:var(--muted); font-size:var(--fs-sm); }
18815
+ .share-host-control { display:flex; align-items:center; gap:10px; min-width:0; }
18816
+ .share-host-control > .ico { width:20px; height:20px; color:var(--fg-soft); }
18817
+ .share-host-control select { flex:1; min-width:0; min-height:40px; padding:8px 10px; border:1px solid var(--line-strong); border-radius:var(--radius-sm); background:var(--bg); color:var(--fg); font:inherit; font-weight:500; }
18818
+ .share-host-behavior { margin:12px 0 0 30px; }
18819
+ .share-host-behavior strong { color:var(--fg-soft); font-size:var(--fs-sm); font-weight:500; }
18820
+ .share-host-behavior p { margin:5px 0 0; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
18821
+ .share-list-heading { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:var(--fs-sm); padding-bottom:8px; }
18822
+ .share-link-list { list-style:none; padding:0; margin:0; }
18823
+ .share-link-list > li + li { border-top:1px solid var(--line); }
18824
+ .share-link-row { display:flex; align-items:center; gap:12px; padding:16px 0; }
18825
+ .share-link-row > .ico { flex:none; width:20px; height:20px; color:var(--fg-soft); }
18826
+ .share-link-info { flex:1; min-width:0; }
18827
+ .share-link-name { display:flex; align-items:center; gap:4px; max-width:100%; color:var(--fg); font-size:var(--fs-ui); font-weight:600; text-align:left; border-radius:var(--radius-sm); }
18828
+ .share-link-name > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
18829
+ .share-link-name > .ico { flex:none; color:var(--muted); width:12px; }
18830
+ .share-link-meta { display:flex; flex-wrap:wrap; gap:2px 8px; margin-top:4px; font-variant-numeric:tabular-nums; font-size:var(--fs-xs); color:var(--muted); line-height:1.6; }
18831
+ .share-link-meta > span { white-space:nowrap; }
18832
+ .share-link-meta > span + span::before { content:"\xB7"; margin-right:8px; }
18833
+ .share-link-actions { display:flex; align-items:center; gap:6px; flex:none; }
18834
+ .share-link-actions .btn { min-height:32px; min-width:84px; justify-content:center; padding:5px 9px; font-size:var(--fs-sm); font-weight:500; box-shadow:none; white-space:nowrap; }
18835
+ .share-link-actions .iconbtn { width:32px; height:32px; color:var(--muted); }
18836
+ .share-link-detail { margin:0 0 16px 32px; }
18837
+ .share-link-detail > p, .share-link-warning { margin:0 0 12px; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
18838
+ .share-link-warning { margin:4px 0 0; }
18839
+ .share-link-name:hover { background:var(--hover); }
18840
+ .share-link-name:focus-visible, .share-secondary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
18841
+ @media (max-width:560px) {
18842
+ .share-link-row { flex-wrap:wrap; gap:8px; }
18843
+ .share-link-info { flex-basis:calc(100% - 32px); }
18844
+ .share-link-actions { margin-left:28px; }
18845
+ .share-link-actions .btn, .share-link-actions .iconbtn { min-height:40px; }
18846
+ }
18847
+ .share-facts { margin:0; display:flex; flex-direction:column; gap:12px; }
18848
+ .share-facts > div { display:flex; gap:20px; align-items:baseline; }
18849
+ .share-facts dt { flex:0 0 88px; color:var(--muted); font-size:var(--fs-sm); }
18850
+ .share-facts dd { margin:0; flex:1; text-align:right; color:var(--fg-soft); font-size:var(--fs-sm); overflow-wrap:anywhere; font-variant-numeric:tabular-nums; }
18851
+ .share-explanation { margin:-6px 0 0; font-size:var(--fs-sm); line-height:1.7; color:var(--muted); }
18852
+ .share-details { color:var(--fg-soft); font-size:var(--fs-sm); }
18853
+ .share-details summary { cursor:pointer; padding:4px 0; line-height:1.5; }
18854
+ .share-details summary:hover { color:var(--fg); }
18855
+ .share-details summary:focus-visible { outline:2px solid var(--accent); outline-offset:4px; border-radius:2px; }
18856
+ .share-details summary::marker { color:var(--muted); font-size:10px; }
18857
+ .share-details summary > span { margin-left:10px; color:var(--muted); font-size:var(--fs-xs); }
18858
+ .share-details > div, .share-details > dl { margin-top:16px; }
18859
+ .share-secondary { display:inline-flex; align-items:center; gap:6px; color:var(--muted); font-size:var(--fs-sm); border-radius:var(--radius-sm); }
18860
+ .share-secondary { padding:6px 0; }
18861
+ .share-secondary:hover { color:var(--fg); }
18862
+ .share-dialog button:disabled, .modal:has(.share-dialog) .modal-foot button:disabled { cursor:default; opacity:.5; }
18863
+ .share-settings { display:flex; flex-direction:column; gap:8px; }
18864
+ .share-setting { display:flex; align-items:center; gap:16px; min-height:38px; }
18865
+ .share-setting > span { flex:0 0 90px; color:var(--fg-soft); font-size:var(--fs-sm); }
18866
+ .share-setting select { flex:1; min-width:0; max-width:100%; min-height:34px; font:inherit; font-size:var(--fs-ui); color:var(--fg); border:1px solid var(--line); border-radius:var(--radius-sm); padding:5px 8px; background:var(--bg); }
18867
+ .share-setting select:disabled { color:var(--muted); background:var(--surface); opacity:1; }
18868
+ .share-setting input, .share-url { min-width:0; flex:1; min-height:34px; border:1px solid var(--line); border-radius:var(--radius-sm); padding:6px 9px; background:var(--bg); color:var(--fg); font-size:var(--fs-ui); }
18869
+ .share-setting input::placeholder { color:var(--muted); }
18870
+ .share-setting input:focus-visible, .share-url:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
18871
+ .share-dialog > .share-details { border-top:1px solid var(--line); padding-top:12px; }
18872
+ .share-url { display:block; width:100%; margin-top:12px; }
18873
+ .share-message { margin:0; padding:10px 12px; background:var(--surface); border-radius:var(--radius-sm); color:var(--fg-soft); font-size:var(--fs-sm); line-height:1.6; overflow-wrap:anywhere; }
18874
+ .share-message.error { color:var(--danger); background:var(--danger-soft); }
18875
+ .share-load-error p { margin-top:0; color:var(--muted); }
18876
+ .share-loading { display:grid; gap:14px; padding:4px 0; }
18877
+ .share-loading span { height:14px; background:var(--surface-2); border-radius:3px; }
18878
+ .share-loading span:first-child { width:55%; }
18879
+ .share-loading span:last-child { width:75%; }
18880
+ .share-sr-status { position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
18881
+ @media (max-width:560px) {
18882
+ .modal:has(.share-dialog) .modal-head { padding:22px 52px 0 20px; gap:10px; }
18883
+ .modal:has(.share-dialog) .modal-body { padding:20px 20px 12px; }
18884
+ .modal:has(.share-dialog) .modal-foot { padding:14px 20px; flex-wrap:wrap; }
18885
+ .modal:has(.share-dialog) .modal-foot .btn, .share-secondary { min-height:40px; }
18886
+ .share-setting { gap:12px; }
18887
+ .share-setting > span { flex-basis:80px; }
18888
+ .share-setting select, .share-setting input { min-height:40px; }
18889
+ }
18614
18890
  .mhshare-body { display:flex; flex-direction:column; gap:10px; }
18615
18891
  .mhshare-section { font-size:var(--fs-micro); font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; margin-top:6px; }
18616
18892
  .mhshare-field { display:flex; align-items:center; gap:12px; }
@@ -19188,22 +19464,31 @@ var styles_default = ` :root {
19188
19464
  /* File-editor window (desktop, loaded at #file?path=\u2026 \u2014 the .txt/.md "open
19189
19465
  with" surface). Same shell idiom as quicknote: top bar = drag region with
19190
19466
  opted-out buttons, single full-bleed column, popups clamped below the bar. */
19191
- body.file-window { --pop-top-inset:46px; }
19467
+ body.file-window { --pop-top-inset:48px; }
19192
19468
  body.file-window #app { display:block; width:100vw; height:100vh; height:100dvh; }
19193
19469
  .fw { display:flex; flex-direction:column; width:100%; height:100vh; height:100dvh; overflow:hidden; background:var(--bg); }
19194
- .fw-bar { display:flex; align-items:center; gap:8px; padding:7px 12px;
19470
+ .fw-bar { display:flex; align-items:center; gap:12px; padding:0 16px; height:48px; flex:none;
19195
19471
  user-select:none; -webkit-app-region:drag;
19196
19472
  background:var(--surface); border-bottom:1px solid var(--line); }
19197
19473
  /* macOS only: clear the inset traffic lights (frame is native elsewhere). */
19198
- body.desktop-mac .fw-bar { padding-left:84px; }
19199
- .fw-name { flex:1; min-width:0; font-size:13px; font-weight:600; color:var(--fg-soft);
19474
+ body.desktop-mac .fw-bar { padding-left:88px; }
19475
+ .fw-title { display:flex; align-items:center; gap:8px; flex:1; min-width:0; color:var(--muted); }
19476
+ .fw-name { min-width:0; font-size:var(--fs-sm); font-weight:500; color:var(--fg-soft);
19200
19477
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
19201
- .fw-dirty { color:var(--accent); font-size:15px; }
19202
- .fw-actions { display:flex; align-items:center; gap:6px; -webkit-app-region:no-drag; }
19203
- .fw-actions .btn { padding:4px 10px; font-size:12px; }
19478
+ .fw-dirty { width:6px; height:6px; flex:none; border-radius:50%; background:var(--accent); }
19479
+ .fw-status { display:flex; align-items:center; gap:6px; flex:none; color:var(--muted); font-size:var(--fs-xs); white-space:nowrap; }
19480
+ .fw-status:not(.is-dirty) .ico { color:var(--success); }
19481
+ .fw-actions { display:flex; align-items:center; gap:6px; flex:none; -webkit-app-region:no-drag; }
19482
+ .fw-actions .btn { min-height:30px; font-size:var(--fs-sm); }
19483
+ .fw-body .doc { padding-top:64px; padding-bottom:24vh; }
19204
19484
  .fw-body { flex:1; overflow:auto; scrollbar-gutter:stable; scrollbar-width:thin; -webkit-app-region:no-drag; }
19205
- /* Bar is ~45px, not the main ~50px topbar the find bar assumes. */
19206
- body.file-window .find-bar { top:52px; }
19485
+ body.file-window .find-bar { top:54px; }
19486
+ @media (max-width:720px) {
19487
+ .fw-bar { gap:8px; padding-right:12px; padding-left:12px; }
19488
+ .fw-status span, .fw-mode span, .fw-open span { display:none; }
19489
+ .fw-mode, .fw-open { width:30px; padding:0; justify-content:center; }
19490
+ .fw-body .doc { padding:36px 32px 24vh; }
19491
+ }
19207
19492
 
19208
19493
  /* No-origin bootstrap: enroll screen + hydration splash (data-blind shell). */
19209
19494
  .enroll-splash { min-height:100dvh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }
@@ -19428,6 +19713,37 @@ var styles_default = ` :root {
19428
19713
  .blob-mgr-row { grid-template-columns:26px 30px minmax(0,1fr) auto auto 28px; }
19429
19714
  .blob-mgr-age { display:none; }
19430
19715
  }
19716
+
19717
+ /* Standalone library document: the same editor, with a compact native shell. */
19718
+ .document-window { min-width:0; height:100dvh; }
19719
+ .document-window .dw-toolbar { padding:0 16px; height:48px; min-height:48px; flex:none;
19720
+ flex-wrap:nowrap; gap:12px; background:var(--surface); -webkit-app-region:drag; }
19721
+ body.desktop-mac .document-window .dw-toolbar { padding-left:88px; }
19722
+ .document-window .dw-toolbar button { -webkit-app-region:no-drag; }
19723
+ .dw-toolbar > button, .dw-toolbar .crumb > .ico { flex:none; }
19724
+ .dw-toolbar .crumb { min-width:0; color:var(--muted); font-size:var(--fs-sm); }
19725
+ .dw-path { display:flex; align-items:center; gap:8px; min-width:0; }
19726
+ .dw-path .crumb-link { max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
19727
+ .dw-current { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--fg-soft); }
19728
+ .dw-save { display:flex; align-items:center; gap:6px; flex:none; font-size:var(--fs-xs); color:var(--muted);
19729
+ padding:6px 4px; border-radius:var(--radius-sm); }
19730
+ .dw-save:not(:disabled):hover { background:var(--hover); }
19731
+ .dw-save-saved .ico { color:var(--success); }
19732
+ .dw-save-error, .dw-save-conflict { color:var(--danger); }
19733
+ .dw-content .doc { padding-top:64px; padding-bottom:24vh; }
19734
+ .dw-content .doc-title { overflow-wrap:anywhere; }
19735
+ .dw-footer { display:flex; align-items:center; height:30px; flex:none; padding:0 18px;
19736
+ border-top:1px solid var(--line); color:var(--muted); font-size:var(--fs-xs); }
19737
+ .document-window .doc-wc { bottom:5px; right:16px; border:0; box-shadow:none; background:transparent; }
19738
+ .document-window .doc-toc { top:72px; }
19739
+ .document-window .find-bar { top:54px; }
19740
+ @media (max-width:720px) {
19741
+ .dw-save span { display:none; }
19742
+ .dw-path { display:none; }
19743
+ .document-window .dw-toolbar { gap:8px; padding:0 12px; }
19744
+ .dw-content .doc { padding:36px 32px 24vh; }
19745
+ .dw-content .doc-title { font-size:32px; }
19746
+ }
19431
19747
  `;
19432
19748
 
19433
19749
  // src/webui/server/icons.ts
@@ -19486,7 +19802,7 @@ var HTML2 = `<!doctype html>
19486
19802
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
19487
19803
  <!-- Mobile browser chrome (status/address bar) tints to theme-color; app.tsx
19488
19804
  keeps #theme-color-meta in sync with the active theme + visible surface. -->
19489
- <meta name="theme-color" id="theme-color-meta" content="#ffffff" />
19805
+ <meta name="theme-color" id="theme-color-meta" content="${LIGHT_THEME.bg}" />
19490
19806
  <meta name="apple-mobile-web-app-capable" content="yes" />
19491
19807
  <meta name="apple-mobile-web-app-status-bar-style" content="default" />
19492
19808
  <title>Metahub</title>
@@ -19501,9 +19817,10 @@ var HTML2 = `<!doctype html>
19501
19817
  <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"></noscript>
19502
19818
  <!-- FOUC guard: resolve the stored choice (system \u2192 OS preference) to an
19503
19819
  explicit data-resolved before first paint. CSS keys off data-resolved
19504
- alone, so the dark palette exists exactly once \u2014 see styles.css. Dark also
19820
+ alone, so the dark palette exists exactly once \u2014 see core/ui-theme.ts. Dark also
19505
19821
  pre-tints the status bar (hex mirrors --bg) so the first frame isn't white. -->
19506
- <script>try{var t=localStorage.getItem('mh-theme'),d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.dataset.resolved=d?'dark':'light';if(d)document.getElementById('theme-color-meta').content='#1a1a1c'}catch(e){}</script>
19822
+ <style>${THEME_CSS}html,body{background:var(--bg);color:var(--fg)}</style>
19823
+ <script>${THEME_INIT_SCRIPT}</script>
19507
19824
  <!-- Same for the UI language: mirror src/webui/i18n/locale.ts so <html lang>
19508
19825
  is right before the bundle runs. -->
19509
19826
  <script>try{var l=localStorage.getItem('mh-lang');if(l!=='zh-CN'&&l!=='en'){var ls=navigator.languages&&navigator.languages.length?navigator.languages:[navigator.language];l='en';for(var i=0;i<ls.length;i++){var g=ls[i]||'';if(/^zh\\b/i.test(g)){l='zh-CN';break}if(/^en\\b/i.test(g))break}}document.documentElement.lang=l}catch(e){}</script>
@@ -19633,9 +19950,9 @@ var getRuntime = bundleGetter({ key: "runtime", entry: "../runtime.ts", dist: "m
19633
19950
  var getSdk = bundleGetter({ key: "sdk", entry: "../../sdk/client.ts", dist: "metahub-sdk.js", extraDirs: ["../../sdk"] });
19634
19951
  async function getCss() {
19635
19952
  if (RUNNING_FROM_SOURCE) {
19636
- return Bun.file(fileURLToPath(new URL("../styles.css", import.meta.url))).text();
19953
+ return THEME_CSS + await Bun.file(fileURLToPath(new URL("../styles.css", import.meta.url))).text();
19637
19954
  }
19638
- return styles_default;
19955
+ return THEME_CSS + styles_default;
19639
19956
  }
19640
19957
  async function getJsStamped() {
19641
19958
  return (await getJs()).replaceAll("__MH_WEBUI_VERSION__", package_default.version);
@@ -19665,8 +19982,8 @@ var MANIFEST = JSON.stringify({
19665
19982
  start_url: "/",
19666
19983
  scope: "/",
19667
19984
  display: "standalone",
19668
- background_color: "#ffffff",
19669
- theme_color: "#ffffff",
19985
+ background_color: LIGHT_THEME.bg,
19986
+ theme_color: LIGHT_THEME.bg,
19670
19987
  icons: [
19671
19988
  { src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
19672
19989
  { src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
@@ -20979,6 +21296,162 @@ init_records();
20979
21296
  init_blobs_core();
20980
21297
  init_storage();
20981
21298
  init_storage_s3_sign();
21299
+
21300
+ // src/core/sync/share-short-links.ts
21301
+ init_e2ee();
21302
+ init_storage();
21303
+
21304
+ // src/core/sync/share-link.ts
21305
+ init_e2ee();
21306
+ async function tokenHash(token) {
21307
+ return toB64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token))));
21308
+ }
21309
+ async function linkId(token) {
21310
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
21311
+ return toB64url(new Uint8Array(digest).slice(0, 16));
21312
+ }
21313
+ function signedUrlExpiry(value) {
21314
+ try {
21315
+ const u = new URL(value);
21316
+ if (u.protocol !== "https:" || u.username || u.password || u.hash || value.length > 8192)
21317
+ return null;
21318
+ const date = u.searchParams.get("X-Amz-Date") ?? "";
21319
+ const seconds = Number(u.searchParams.get("X-Amz-Expires"));
21320
+ if (!/^\d{8}T\d{6}Z$/.test(date) || !Number.isInteger(seconds) || seconds < 1 || seconds > 604800)
21321
+ return null;
21322
+ if (!/^[a-f0-9]{64}$/i.test(u.searchParams.get("X-Amz-Signature") ?? ""))
21323
+ return null;
21324
+ const iso = `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}T${date.slice(9, 11)}:${date.slice(11, 13)}:${date.slice(13, 15)}Z`;
21325
+ const start = Date.parse(iso);
21326
+ return Number.isFinite(start) ? start + seconds * 1000 : null;
21327
+ } catch {
21328
+ return null;
21329
+ }
21330
+ }
21331
+ function longShareUrl(base, link) {
21332
+ return `${base.replace(/\/+$/, "")}/#m=${encodeURIComponent(link.manifestUrl)}&${link.keyB64 ? `k=${link.keyB64}` : `s=${link.saltB64}`}`;
21333
+ }
21334
+
21335
+ // src/core/sync/share-short-links.ts
21336
+ var TIMEOUT = 8000;
21337
+ function root(config, slug) {
21338
+ return `${config.prefix.replace(/^\/+|\/+$/g, "") ? `${config.prefix.replace(/^\/+|\/+$/g, "")}/` : ""}shares/${slug}/links/`;
21339
+ }
21340
+ function viewerBase(base) {
21341
+ const url = new URL(base);
21342
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
21343
+ throw new Error("invalid viewer");
21344
+ if (url.pathname !== "/")
21345
+ throw new Error("static subpath viewer");
21346
+ return url.origin;
21347
+ }
21348
+ async function unpack(config, bytes) {
21349
+ const raw = config.encrypt && config.masterKey ? await decryptBytes(fromB64(config.masterKey), bytes) : bytes;
21350
+ const value = JSON.parse(new TextDecoder().decode(raw));
21351
+ if (value.v !== 1 || !/^[A-Za-z0-9_-]{43}$/.test(value.token) || viewerBase(value.base) !== value.base)
21352
+ throw new Error("invalid short-link control");
21353
+ return value;
21354
+ }
21355
+ async function controlFor(config, slug, base) {
21356
+ const client = storageClientFor(config);
21357
+ const path = `${root(config, slug)}${await tokenHash(base)}.json`;
21358
+ const existing = await client.get(path);
21359
+ if (existing) {
21360
+ const control = await unpack(config, existing);
21361
+ if (control.base !== base)
21362
+ throw new Error("viewer mismatch");
21363
+ return control;
21364
+ }
21365
+ const control = { v: 1, base, token: toB64url(generateMasterKey()) };
21366
+ const plain = new TextEncoder().encode(JSON.stringify(control));
21367
+ const bytes = config.encrypt && config.masterKey ? await encryptBytes(fromB64(config.masterKey), plain) : plain;
21368
+ try {
21369
+ await client.put(path, bytes, {
21370
+ ifNoneMatch: true,
21371
+ contentType: "application/octet-stream"
21372
+ });
21373
+ } catch (error) {
21374
+ const winner = await client.get(path);
21375
+ if (!winner)
21376
+ throw error;
21377
+ const value = await unpack(config, winner);
21378
+ if (value.base !== base)
21379
+ throw new Error("viewer mismatch");
21380
+ return value;
21381
+ }
21382
+ return control;
21383
+ }
21384
+ function request(url, init = {}) {
21385
+ return fetch(url, {
21386
+ ...init,
21387
+ redirect: "error",
21388
+ credentials: "omit",
21389
+ cache: "no-store",
21390
+ signal: AbortSignal.timeout(TIMEOUT)
21391
+ });
21392
+ }
21393
+ async function bucketShareUrl(config, slug, base, link) {
21394
+ const fallback = longShareUrl(base, link);
21395
+ try {
21396
+ const origin = viewerBase(base);
21397
+ const expiresAt = Math.min(link.presignExp, signedUrlExpiry(link.manifestUrl) ?? 0);
21398
+ if (expiresAt <= Date.now())
21399
+ return fallback;
21400
+ const probe = await request(`${origin}/api/links`);
21401
+ if (!probe.ok || (await probe.json()).service !== "metahub-share-links")
21402
+ throw new Error("static viewer");
21403
+ const control = await controlFor(config, slug, origin);
21404
+ const id = await linkId(control.token);
21405
+ const endpoint = `${origin}/api/links/${id}`;
21406
+ const authorization = `Bearer ${control.token}`;
21407
+ const descriptor = {
21408
+ v: 1,
21409
+ manifestUrl: link.manifestUrl,
21410
+ expiresAt,
21411
+ ...link.saltB64 ? { salt: toB64url(fromB64(link.saltB64)) } : {}
21412
+ };
21413
+ const current = await request(endpoint, { headers: { authorization } });
21414
+ if (!current.ok && current.status !== 404)
21415
+ throw new Error("short-link lookup failed");
21416
+ const etag = current.headers.get("etag");
21417
+ if (current.ok && !etag)
21418
+ throw new Error("not a short-link API");
21419
+ const result = await request(endpoint, {
21420
+ method: "PUT",
21421
+ headers: {
21422
+ authorization,
21423
+ "content-type": "application/json",
21424
+ ...current.ok ? { "if-match": etag } : { "if-none-match": "*" }
21425
+ },
21426
+ body: JSON.stringify(descriptor)
21427
+ });
21428
+ if (!result.ok || !result.headers.get("etag") || (await result.json()).ok !== true)
21429
+ throw new Error("short-link registration failed");
21430
+ return `${origin}/s/${id}${link.keyB64 ? `#k=${toB64url(fromB64(link.keyB64))}` : ""}`;
21431
+ } catch {
21432
+ console.warn("share: short-link service unavailable; using the original long link");
21433
+ return fallback;
21434
+ }
21435
+ }
21436
+ async function revokeBucketShortLinks(config, slug) {
21437
+ const client = storageClientFor(config);
21438
+ for (const object of await client.list(root(config, slug))) {
21439
+ if (!object.key.endsWith(".json"))
21440
+ continue;
21441
+ const raw = await client.get(object.key);
21442
+ if (!raw)
21443
+ continue;
21444
+ const control = await unpack(config, raw);
21445
+ const response = await request(`${control.base}/api/links/${await linkId(control.token)}`, {
21446
+ method: "DELETE",
21447
+ headers: { authorization: `Bearer ${control.token}` }
21448
+ });
21449
+ if (!response.ok || (await response.json()).ok !== true)
21450
+ throw new Error("short-link revocation has not been confirmed; retry later");
21451
+ }
21452
+ }
21453
+
21454
+ // src/core/sync/share-export.ts
20982
21455
  init_e2ee();
20983
21456
  function sharesRoot(prefix) {
20984
21457
  const p = (prefix || "").replace(/^\/+|\/+$/g, "");
@@ -21119,6 +21592,8 @@ async function renewBucketShare(db, config, slug, expiresSec = MAX_PRESIGN_SECON
21119
21592
  const meta = await readMeta2(config, slug);
21120
21593
  if (!meta)
21121
21594
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21595
+ if (meta.revoking)
21596
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21122
21597
  const shareKey = fromB64(meta.key);
21123
21598
  const exp = clampExpiry(expiresSec);
21124
21599
  const { manifestUrl, objects, title } = await writeShareObjects(db, {
@@ -21154,6 +21629,8 @@ async function represignBucketShare(config, slug, expiresSec = MAX_PRESIGN_SECON
21154
21629
  const meta = await readMeta2(config, slug);
21155
21630
  if (!meta)
21156
21631
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21632
+ if (meta.revoking)
21633
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21157
21634
  const client = storageClientFor(config);
21158
21635
  const root = sharesRoot(config.prefix);
21159
21636
  const shareKey = fromB64(meta.key);
@@ -21193,15 +21670,19 @@ async function listBucketShares(config) {
21193
21670
  async function deleteBucketShareObjects(config, slug) {
21194
21671
  const client = storageClientFor(config);
21195
21672
  const root = sharesRoot(config.prefix);
21196
- await client.del(metaKey(root, slug)).catch(() => {
21197
- return;
21198
- });
21199
- const meta = await readMeta2(config, slug).catch(() => null);
21200
- const objs = meta?.objects ?? (await client.list(`${root}/${slug}/`).catch(() => [])).map((o) => o.key);
21673
+ const meta = await readMeta2(config, slug);
21674
+ if (meta)
21675
+ await client.put(metaKey(root, slug), await metaBytes(config, { ...meta, revoking: true }), { contentType: "application/octet-stream" });
21676
+ await revokeBucketShortLinks(config, slug);
21677
+ const objs = new Set([...meta?.objects ?? [], ...(await client.list(`${root}/${slug}/`)).map((o) => o.key)]);
21678
+ const metaPath = metaKey(root, slug);
21201
21679
  for (const key of objs)
21202
- await client.del(key).catch(() => {
21203
- return;
21204
- });
21680
+ if (key !== metaPath && !key.includes(`/links/`))
21681
+ await client.del(key);
21682
+ for (const key of objs)
21683
+ if (key.includes(`/links/`))
21684
+ await client.del(key);
21685
+ await client.del(metaPath);
21205
21686
  }
21206
21687
  function clampExpiry(sec) {
21207
21688
  return Math.min(Math.max(60, Math.floor(sec)), MAX_PRESIGN_SECONDS);
@@ -21236,7 +21717,8 @@ function bucketShareItem(bucket, m) {
21236
21717
  hosting: "s3",
21237
21718
  expiresAt: m.presign_exp,
21238
21719
  hasPassword: m.has_password,
21239
- contentUpdatedAt: m.content_updated_at ?? m.created_at
21720
+ contentUpdatedAt: m.content_updated_at ?? m.created_at,
21721
+ ...m.revoking ? { lifecycle: "cleanup_pending" } : {}
21240
21722
  };
21241
21723
  }
21242
21724
  async function fetchPeerShares(url, token, targetId) {
@@ -21438,7 +21920,7 @@ function pickBucket(db, bucketUrl) {
21438
21920
  throw new MhError("invalid_input", `multiple buckets \u2014 pass --bucket <url> (one of: ${buckets.map((b) => b.url).join(", ")})`);
21439
21921
  return buckets[0].url;
21440
21922
  }
21441
- function viewerBase(db, override) {
21923
+ function viewerBase2(db, override) {
21442
21924
  if (override)
21443
21925
  return override.replace(/\/+$/, "");
21444
21926
  const row = db.query("SELECT value FROM meta WHERE key='cfg_share_viewer_url'").get();
@@ -21451,10 +21933,6 @@ function viewerOriginOf(base) {
21451
21933
  return;
21452
21934
  }
21453
21935
  }
21454
- function bucketLink(base, manifestUrl, keyB64, saltB64) {
21455
- const frag = `m=${encodeURIComponent(manifestUrl)}&` + (keyB64 ? `k=${keyB64}` : `s=${saltB64}`);
21456
- return `${base}/#${frag}`;
21457
- }
21458
21936
  function assertShareCombo(req) {
21459
21937
  const fail = (msg) => {
21460
21938
  throw new MhError("invalid_input", msg);
@@ -21481,7 +21959,7 @@ async function createShareAction(db, req) {
21481
21959
  const target = resolveTarget(db, req.kind, req.ref);
21482
21960
  const bucketUrl = pickBucket(db, req.bucketUrl);
21483
21961
  const { config, label } = s3PeerConfig2(db, bucketUrl);
21484
- const base = viewerBase(db, req.viewerBase);
21962
+ const base = viewerBase2(db, req.viewerBase);
21485
21963
  const slug = freshSlug(db);
21486
21964
  const out = await createBucketShare(db, {
21487
21965
  slug,
@@ -21499,7 +21977,7 @@ async function createShareAction(db, req) {
21499
21977
  permission: "view",
21500
21978
  transport: "s3",
21501
21979
  hosting: "s3",
21502
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
21980
+ url: await bucketShareUrl(config, slug, base, out),
21503
21981
  expiresAt: out.presignExp,
21504
21982
  source: `\u6876 ${label}`
21505
21983
  };
@@ -21784,7 +22262,13 @@ async function revokeShareAction(db, slug) {
21784
22262
  const owner = await bucketOwning(db, slug);
21785
22263
  if (!owner)
21786
22264
  return { ok: false, status: "not_found" };
21787
- await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22265
+ try {
22266
+ await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22267
+ } catch {
22268
+ if (owner.item)
22269
+ primeRemoteShare(db, owner.peer.url, { ...owner.item, lifecycle: "cleanup_pending" });
22270
+ return { ok: false, status: "cleanup_pending" };
22271
+ }
21788
22272
  dropRemoteShare(db, owner.peer.url, slug);
21789
22273
  return { ok: true, status: "revoked" };
21790
22274
  }
@@ -21806,7 +22290,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21806
22290
  throw new MhError("not_found", `no such object-storage share: ${slug}`);
21807
22291
  const { peer: p } = owner;
21808
22292
  const config = JSON.parse(p.config);
21809
- const base = viewerBase(db, viewerBaseOverride);
22293
+ const base = viewerBase2(db, viewerBaseOverride);
21810
22294
  const out = opts?.refreshContent ? await renewBucketShare(db, config, slug) : await represignBucketShare(config, slug);
21811
22295
  if (owner.item)
21812
22296
  primeRemoteShare(db, p.url, {
@@ -21821,7 +22305,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21821
22305
  permission: "view",
21822
22306
  transport: "s3",
21823
22307
  hosting: "s3",
21824
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
22308
+ url: await bucketShareUrl(config, slug, base, out),
21825
22309
  expiresAt: out.presignExp,
21826
22310
  source: `\u6876 ${p.label ?? p.url}`
21827
22311
  };
@@ -22899,6 +23383,7 @@ async function enableWorkerSubdomain(cfg) {
22899
23383
  }
22900
23384
 
22901
23385
  // src/core/sync/room-serve.ts
23386
+ init_ui_theme();
22902
23387
  init_errors();
22903
23388
  init_http_status();
22904
23389
  init_ids();
@@ -22910,6 +23395,19 @@ init_sites_core();
22910
23395
  init_shares();
22911
23396
  init_room_protocol();
22912
23397
  var ROOM_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
23398
+ var PAGE_CSS = `
23399
+ ${THEME_CSS}
23400
+ *{box-sizing:border-box}
23401
+ body{margin:0;background:var(--bg);color:var(--fg);font:16px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;
23402
+ min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
23403
+ main{max-width:420px;width:100%}
23404
+ form.pw{display:flex;flex-direction:column;gap:12px}
23405
+ form.pw input{padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--fg)}
23406
+ form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:var(--accent-fg);cursor:pointer}
23407
+ .err{color:var(--danger);font-size:13px;margin:0}
23408
+ .muted{color:var(--muted);font-size:14px}
23409
+ h1{font-size:20px;margin:0 0 10px;text-align:center}
23410
+ footer{margin-top:34px;color:var(--muted);font-size:12px;text-align:center}`;
22913
23411
 
22914
23412
  // src/workers/room.ts
22915
23413
  init_room_protocol();
@@ -23384,6 +23882,7 @@ function disconnectEdge(db) {
23384
23882
  }
23385
23883
 
23386
23884
  // src/core/sync/cf-oauth.ts
23885
+ init_ui_theme();
23387
23886
  init_errors();
23388
23887
  init_page_messages();
23389
23888
  init_e2ee();
@@ -23546,7 +24045,7 @@ async function startCfLogin(opts = {}) {
23546
24045
  }
23547
24046
  };
23548
24047
  }
23549
- var callbackHtml = (locale) => `<!doctype html><html lang="${locale}"><meta charset="utf-8"><title>MetaHub</title>
24048
+ var callbackHtml = (locale) => `<!doctype html><html lang="${locale}"><meta charset="utf-8"><title>MetaHub</title><style>${THEME_CSS}body{background:var(--bg);color:var(--fg)}h2{color:var(--success)}p{color:var(--muted)}</style>
23550
24049
  <body style="font:15px system-ui;display:grid;place-items:center;height:100vh;margin:0">
23551
24050
  <div style="text-align:center"><h2>${pm(locale, "\u2705 \u5DF2\u6388\u6743")}</h2><p>${pm(locale, "\u53EF\u4EE5\u5173\u95ED\u6B64\u6807\u7B7E\u9875\uFF0C\u56DE\u5230 MetaHub \u7EE7\u7EED\u90E8\u7F72\u3002")}</p></div>`;
23552
24051
 
@@ -24853,4 +25352,4 @@ export {
24853
25352
  writeTx
24854
25353
  };
24855
25354
 
24856
- //# debugId=313E5D38AF56C98B64756E2164756E21
25355
+ //# debugId=ACE65D6746A1B19864756E2164756E21