@tensorix/metahub 0.5.4 → 0.5.6

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 +666 -152
  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 +619 -128
  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.6",
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; }
@@ -18525,7 +18706,7 @@ var styles_default = ` :root {
18525
18706
  .toast { 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
18712
  .toast-msg { min-width:0; }
@@ -18611,6 +18792,93 @@ var styles_default = ` :root {
18611
18792
  .doc-skel { max-width:720px; margin:40px auto; padding:0 24px; }
18612
18793
 
18613
18794
  /* share / publish modal (share-modal.tsx) */
18795
+ /* Hosting-first object sharing: compact link rows and disclosed details. */
18796
+ .modal:has(.share-dialog) { border-radius:12px; }
18797
+ .modal:has(.share-dialog) .modal-head { position:relative; display:flex; align-items:baseline; gap:12px; padding:24px 56px 0 28px; min-width:0; }
18798
+ .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%; }
18799
+ .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; }
18800
+ .modal-close { position:absolute; right:16px; top:18px; color:var(--muted); }
18801
+ .modal:has(.share-dialog) .modal-body { padding:22px 28px 16px; }
18802
+ .modal:has(.share-dialog) .modal-foot { background:var(--bg); padding:12px 28px; justify-content:space-between; gap:12px; }
18803
+ .modal:has(.share-dialog) .modal-foot .btn { min-height:36px; font-weight:500; box-shadow:none; }
18804
+ .share-dialog { display:flex; flex-direction:column; gap:20px; font-size:var(--fs-ui); }
18805
+ .share-host { padding-bottom:20px; border-bottom:1px solid var(--line); }
18806
+ .share-host-label { display:block; margin-bottom:10px; color:var(--muted); font-size:var(--fs-sm); }
18807
+ .share-host-control { display:flex; align-items:center; gap:10px; min-width:0; }
18808
+ .share-host-control > .ico { width:20px; height:20px; color:var(--fg-soft); }
18809
+ .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; }
18810
+ .share-host-behavior { margin:12px 0 0 30px; }
18811
+ .share-host-behavior strong { color:var(--fg-soft); font-size:var(--fs-sm); font-weight:500; }
18812
+ .share-host-behavior p { margin:5px 0 0; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
18813
+ .share-list-heading { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:var(--fs-sm); padding-bottom:8px; }
18814
+ .share-link-list { list-style:none; padding:0; margin:0; }
18815
+ .share-link-list > li + li { border-top:1px solid var(--line); }
18816
+ .share-link-row { display:flex; align-items:center; gap:12px; padding:16px 0; }
18817
+ .share-link-row > .ico { flex:none; width:20px; height:20px; color:var(--fg-soft); }
18818
+ .share-link-info { flex:1; min-width:0; }
18819
+ .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); }
18820
+ .share-link-name > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
18821
+ .share-link-name > .ico { flex:none; color:var(--muted); width:12px; }
18822
+ .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; }
18823
+ .share-link-meta > span { white-space:nowrap; }
18824
+ .share-link-meta > span + span::before { content:"\xB7"; margin-right:8px; }
18825
+ .share-link-actions { display:flex; align-items:center; gap:6px; flex:none; }
18826
+ .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; }
18827
+ .share-link-actions .iconbtn { width:32px; height:32px; color:var(--muted); }
18828
+ .share-link-detail { margin:0 0 16px 32px; }
18829
+ .share-link-detail > p, .share-link-warning { margin:0 0 12px; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
18830
+ .share-link-warning { margin:4px 0 0; }
18831
+ .share-link-name:hover { background:var(--hover); }
18832
+ .share-link-name:focus-visible, .share-secondary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
18833
+ @media (max-width:560px) {
18834
+ .share-link-row { flex-wrap:wrap; gap:8px; }
18835
+ .share-link-info { flex-basis:calc(100% - 32px); }
18836
+ .share-link-actions { margin-left:28px; }
18837
+ .share-link-actions .btn, .share-link-actions .iconbtn { min-height:40px; }
18838
+ }
18839
+ .share-facts { margin:0; display:flex; flex-direction:column; gap:12px; }
18840
+ .share-facts > div { display:flex; gap:20px; align-items:baseline; }
18841
+ .share-facts dt { flex:0 0 88px; color:var(--muted); font-size:var(--fs-sm); }
18842
+ .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; }
18843
+ .share-explanation { margin:-6px 0 0; font-size:var(--fs-sm); line-height:1.7; color:var(--muted); }
18844
+ .share-details { color:var(--fg-soft); font-size:var(--fs-sm); }
18845
+ .share-details summary { cursor:pointer; padding:4px 0; line-height:1.5; }
18846
+ .share-details summary:hover { color:var(--fg); }
18847
+ .share-details summary:focus-visible { outline:2px solid var(--accent); outline-offset:4px; border-radius:2px; }
18848
+ .share-details summary::marker { color:var(--muted); font-size:10px; }
18849
+ .share-details summary > span { margin-left:10px; color:var(--muted); font-size:var(--fs-xs); }
18850
+ .share-details > div, .share-details > dl { margin-top:16px; }
18851
+ .share-secondary { display:inline-flex; align-items:center; gap:6px; color:var(--muted); font-size:var(--fs-sm); border-radius:var(--radius-sm); }
18852
+ .share-secondary { padding:6px 0; }
18853
+ .share-secondary:hover { color:var(--fg); }
18854
+ .share-dialog button:disabled, .modal:has(.share-dialog) .modal-foot button:disabled { cursor:default; opacity:.5; }
18855
+ .share-settings { display:flex; flex-direction:column; gap:8px; }
18856
+ .share-setting { display:flex; align-items:center; gap:16px; min-height:38px; }
18857
+ .share-setting > span { flex:0 0 90px; color:var(--fg-soft); font-size:var(--fs-sm); }
18858
+ .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); }
18859
+ .share-setting select:disabled { color:var(--muted); background:var(--surface); opacity:1; }
18860
+ .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); }
18861
+ .share-setting input::placeholder { color:var(--muted); }
18862
+ .share-setting input:focus-visible, .share-url:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
18863
+ .share-dialog > .share-details { border-top:1px solid var(--line); padding-top:12px; }
18864
+ .share-url { display:block; width:100%; margin-top:12px; }
18865
+ .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; }
18866
+ .share-message.error { color:var(--danger); background:var(--danger-soft); }
18867
+ .share-load-error p { margin-top:0; color:var(--muted); }
18868
+ .share-loading { display:grid; gap:14px; padding:4px 0; }
18869
+ .share-loading span { height:14px; background:var(--surface-2); border-radius:3px; }
18870
+ .share-loading span:first-child { width:55%; }
18871
+ .share-loading span:last-child { width:75%; }
18872
+ .share-sr-status { position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
18873
+ @media (max-width:560px) {
18874
+ .modal:has(.share-dialog) .modal-head { padding:22px 52px 0 20px; gap:10px; }
18875
+ .modal:has(.share-dialog) .modal-body { padding:20px 20px 12px; }
18876
+ .modal:has(.share-dialog) .modal-foot { padding:14px 20px; flex-wrap:wrap; }
18877
+ .modal:has(.share-dialog) .modal-foot .btn, .share-secondary { min-height:40px; }
18878
+ .share-setting { gap:12px; }
18879
+ .share-setting > span { flex-basis:80px; }
18880
+ .share-setting select, .share-setting input { min-height:40px; }
18881
+ }
18614
18882
  .mhshare-body { display:flex; flex-direction:column; gap:10px; }
18615
18883
  .mhshare-section { font-size:var(--fs-micro); font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; margin-top:6px; }
18616
18884
  .mhshare-field { display:flex; align-items:center; gap:12px; }
@@ -19188,22 +19456,31 @@ var styles_default = ` :root {
19188
19456
  /* File-editor window (desktop, loaded at #file?path=\u2026 \u2014 the .txt/.md "open
19189
19457
  with" surface). Same shell idiom as quicknote: top bar = drag region with
19190
19458
  opted-out buttons, single full-bleed column, popups clamped below the bar. */
19191
- body.file-window { --pop-top-inset:46px; }
19459
+ body.file-window { --pop-top-inset:48px; }
19192
19460
  body.file-window #app { display:block; width:100vw; height:100vh; height:100dvh; }
19193
19461
  .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;
19462
+ .fw-bar { display:flex; align-items:center; gap:12px; padding:0 16px; height:48px; flex:none;
19195
19463
  user-select:none; -webkit-app-region:drag;
19196
19464
  background:var(--surface); border-bottom:1px solid var(--line); }
19197
19465
  /* 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);
19466
+ body.desktop-mac .fw-bar { padding-left:88px; }
19467
+ .fw-title { display:flex; align-items:center; gap:8px; flex:1; min-width:0; color:var(--muted); }
19468
+ .fw-name { min-width:0; font-size:var(--fs-sm); font-weight:500; color:var(--fg-soft);
19200
19469
  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; }
19470
+ .fw-dirty { width:6px; height:6px; flex:none; border-radius:50%; background:var(--accent); }
19471
+ .fw-status { display:flex; align-items:center; gap:6px; flex:none; color:var(--muted); font-size:var(--fs-xs); white-space:nowrap; }
19472
+ .fw-status:not(.is-dirty) .ico { color:var(--success); }
19473
+ .fw-actions { display:flex; align-items:center; gap:6px; flex:none; -webkit-app-region:no-drag; }
19474
+ .fw-actions .btn { min-height:30px; font-size:var(--fs-sm); }
19475
+ .fw-body .doc { padding-top:64px; padding-bottom:24vh; }
19204
19476
  .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; }
19477
+ body.file-window .find-bar { top:54px; }
19478
+ @media (max-width:720px) {
19479
+ .fw-bar { gap:8px; padding-right:12px; padding-left:12px; }
19480
+ .fw-status span, .fw-mode span, .fw-open span { display:none; }
19481
+ .fw-mode, .fw-open { width:30px; padding:0; justify-content:center; }
19482
+ .fw-body .doc { padding:36px 32px 24vh; }
19483
+ }
19207
19484
 
19208
19485
  /* No-origin bootstrap: enroll screen + hydration splash (data-blind shell). */
19209
19486
  .enroll-splash { min-height:100dvh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }
@@ -19428,6 +19705,37 @@ var styles_default = ` :root {
19428
19705
  .blob-mgr-row { grid-template-columns:26px 30px minmax(0,1fr) auto auto 28px; }
19429
19706
  .blob-mgr-age { display:none; }
19430
19707
  }
19708
+
19709
+ /* Standalone library document: the same editor, with a compact native shell. */
19710
+ .document-window { min-width:0; height:100dvh; }
19711
+ .document-window .dw-toolbar { padding:0 16px; height:48px; min-height:48px; flex:none;
19712
+ flex-wrap:nowrap; gap:12px; background:var(--surface); -webkit-app-region:drag; }
19713
+ body.desktop-mac .document-window .dw-toolbar { padding-left:88px; }
19714
+ .document-window .dw-toolbar button { -webkit-app-region:no-drag; }
19715
+ .dw-toolbar > button, .dw-toolbar .crumb > .ico { flex:none; }
19716
+ .dw-toolbar .crumb { min-width:0; color:var(--muted); font-size:var(--fs-sm); }
19717
+ .dw-path { display:flex; align-items:center; gap:8px; min-width:0; }
19718
+ .dw-path .crumb-link { max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
19719
+ .dw-current { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--fg-soft); }
19720
+ .dw-save { display:flex; align-items:center; gap:6px; flex:none; font-size:var(--fs-xs); color:var(--muted);
19721
+ padding:6px 4px; border-radius:var(--radius-sm); }
19722
+ .dw-save:not(:disabled):hover { background:var(--hover); }
19723
+ .dw-save-saved .ico { color:var(--success); }
19724
+ .dw-save-error, .dw-save-conflict { color:var(--danger); }
19725
+ .dw-content .doc { padding-top:64px; padding-bottom:24vh; }
19726
+ .dw-content .doc-title { overflow-wrap:anywhere; }
19727
+ .dw-footer { display:flex; align-items:center; height:30px; flex:none; padding:0 18px;
19728
+ border-top:1px solid var(--line); color:var(--muted); font-size:var(--fs-xs); }
19729
+ .document-window .doc-wc { bottom:5px; right:16px; border:0; box-shadow:none; background:transparent; }
19730
+ .document-window .doc-toc { top:72px; }
19731
+ .document-window .find-bar { top:54px; }
19732
+ @media (max-width:720px) {
19733
+ .dw-save span { display:none; }
19734
+ .dw-path { display:none; }
19735
+ .document-window .dw-toolbar { gap:8px; padding:0 12px; }
19736
+ .dw-content .doc { padding:36px 32px 24vh; }
19737
+ .dw-content .doc-title { font-size:32px; }
19738
+ }
19431
19739
  `;
19432
19740
 
19433
19741
  // src/webui/server/icons.ts
@@ -19486,7 +19794,7 @@ var HTML2 = `<!doctype html>
19486
19794
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
19487
19795
  <!-- Mobile browser chrome (status/address bar) tints to theme-color; app.tsx
19488
19796
  keeps #theme-color-meta in sync with the active theme + visible surface. -->
19489
- <meta name="theme-color" id="theme-color-meta" content="#ffffff" />
19797
+ <meta name="theme-color" id="theme-color-meta" content="${LIGHT_THEME.bg}" />
19490
19798
  <meta name="apple-mobile-web-app-capable" content="yes" />
19491
19799
  <meta name="apple-mobile-web-app-status-bar-style" content="default" />
19492
19800
  <title>Metahub</title>
@@ -19501,9 +19809,10 @@ var HTML2 = `<!doctype html>
19501
19809
  <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
19810
  <!-- FOUC guard: resolve the stored choice (system \u2192 OS preference) to an
19503
19811
  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
19812
+ alone, so the dark palette exists exactly once \u2014 see core/ui-theme.ts. Dark also
19505
19813
  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>
19814
+ <style>${THEME_CSS}html,body{background:var(--bg);color:var(--fg)}</style>
19815
+ <script>${THEME_INIT_SCRIPT}</script>
19507
19816
  <!-- Same for the UI language: mirror src/webui/i18n/locale.ts so <html lang>
19508
19817
  is right before the bundle runs. -->
19509
19818
  <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 +19942,9 @@ var getRuntime = bundleGetter({ key: "runtime", entry: "../runtime.ts", dist: "m
19633
19942
  var getSdk = bundleGetter({ key: "sdk", entry: "../../sdk/client.ts", dist: "metahub-sdk.js", extraDirs: ["../../sdk"] });
19634
19943
  async function getCss() {
19635
19944
  if (RUNNING_FROM_SOURCE) {
19636
- return Bun.file(fileURLToPath(new URL("../styles.css", import.meta.url))).text();
19945
+ return THEME_CSS + await Bun.file(fileURLToPath(new URL("../styles.css", import.meta.url))).text();
19637
19946
  }
19638
- return styles_default;
19947
+ return THEME_CSS + styles_default;
19639
19948
  }
19640
19949
  async function getJsStamped() {
19641
19950
  return (await getJs()).replaceAll("__MH_WEBUI_VERSION__", package_default.version);
@@ -19665,8 +19974,8 @@ var MANIFEST = JSON.stringify({
19665
19974
  start_url: "/",
19666
19975
  scope: "/",
19667
19976
  display: "standalone",
19668
- background_color: "#ffffff",
19669
- theme_color: "#ffffff",
19977
+ background_color: LIGHT_THEME.bg,
19978
+ theme_color: LIGHT_THEME.bg,
19670
19979
  icons: [
19671
19980
  { src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
19672
19981
  { src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
@@ -20979,6 +21288,162 @@ init_records();
20979
21288
  init_blobs_core();
20980
21289
  init_storage();
20981
21290
  init_storage_s3_sign();
21291
+
21292
+ // src/core/sync/share-short-links.ts
21293
+ init_e2ee();
21294
+ init_storage();
21295
+
21296
+ // src/core/sync/share-link.ts
21297
+ init_e2ee();
21298
+ async function tokenHash(token) {
21299
+ return toB64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token))));
21300
+ }
21301
+ async function linkId(token) {
21302
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
21303
+ return toB64url(new Uint8Array(digest).slice(0, 16));
21304
+ }
21305
+ function signedUrlExpiry(value) {
21306
+ try {
21307
+ const u = new URL(value);
21308
+ if (u.protocol !== "https:" || u.username || u.password || u.hash || value.length > 8192)
21309
+ return null;
21310
+ const date = u.searchParams.get("X-Amz-Date") ?? "";
21311
+ const seconds = Number(u.searchParams.get("X-Amz-Expires"));
21312
+ if (!/^\d{8}T\d{6}Z$/.test(date) || !Number.isInteger(seconds) || seconds < 1 || seconds > 604800)
21313
+ return null;
21314
+ if (!/^[a-f0-9]{64}$/i.test(u.searchParams.get("X-Amz-Signature") ?? ""))
21315
+ return null;
21316
+ 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`;
21317
+ const start = Date.parse(iso);
21318
+ return Number.isFinite(start) ? start + seconds * 1000 : null;
21319
+ } catch {
21320
+ return null;
21321
+ }
21322
+ }
21323
+ function longShareUrl(base, link) {
21324
+ return `${base.replace(/\/+$/, "")}/#m=${encodeURIComponent(link.manifestUrl)}&${link.keyB64 ? `k=${link.keyB64}` : `s=${link.saltB64}`}`;
21325
+ }
21326
+
21327
+ // src/core/sync/share-short-links.ts
21328
+ var TIMEOUT = 8000;
21329
+ function root(config, slug) {
21330
+ return `${config.prefix.replace(/^\/+|\/+$/g, "") ? `${config.prefix.replace(/^\/+|\/+$/g, "")}/` : ""}shares/${slug}/links/`;
21331
+ }
21332
+ function viewerBase(base) {
21333
+ const url = new URL(base);
21334
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
21335
+ throw new Error("invalid viewer");
21336
+ if (url.pathname !== "/")
21337
+ throw new Error("static subpath viewer");
21338
+ return url.origin;
21339
+ }
21340
+ async function unpack(config, bytes) {
21341
+ const raw = config.encrypt && config.masterKey ? await decryptBytes(fromB64(config.masterKey), bytes) : bytes;
21342
+ const value = JSON.parse(new TextDecoder().decode(raw));
21343
+ if (value.v !== 1 || !/^[A-Za-z0-9_-]{43}$/.test(value.token) || viewerBase(value.base) !== value.base)
21344
+ throw new Error("invalid short-link control");
21345
+ return value;
21346
+ }
21347
+ async function controlFor(config, slug, base) {
21348
+ const client = storageClientFor(config);
21349
+ const path = `${root(config, slug)}${await tokenHash(base)}.json`;
21350
+ const existing = await client.get(path);
21351
+ if (existing) {
21352
+ const control = await unpack(config, existing);
21353
+ if (control.base !== base)
21354
+ throw new Error("viewer mismatch");
21355
+ return control;
21356
+ }
21357
+ const control = { v: 1, base, token: toB64url(generateMasterKey()) };
21358
+ const plain = new TextEncoder().encode(JSON.stringify(control));
21359
+ const bytes = config.encrypt && config.masterKey ? await encryptBytes(fromB64(config.masterKey), plain) : plain;
21360
+ try {
21361
+ await client.put(path, bytes, {
21362
+ ifNoneMatch: true,
21363
+ contentType: "application/octet-stream"
21364
+ });
21365
+ } catch (error) {
21366
+ const winner = await client.get(path);
21367
+ if (!winner)
21368
+ throw error;
21369
+ const value = await unpack(config, winner);
21370
+ if (value.base !== base)
21371
+ throw new Error("viewer mismatch");
21372
+ return value;
21373
+ }
21374
+ return control;
21375
+ }
21376
+ function request(url, init = {}) {
21377
+ return fetch(url, {
21378
+ ...init,
21379
+ redirect: "error",
21380
+ credentials: "omit",
21381
+ cache: "no-store",
21382
+ signal: AbortSignal.timeout(TIMEOUT)
21383
+ });
21384
+ }
21385
+ async function bucketShareUrl(config, slug, base, link) {
21386
+ const fallback = longShareUrl(base, link);
21387
+ try {
21388
+ const origin = viewerBase(base);
21389
+ const expiresAt = Math.min(link.presignExp, signedUrlExpiry(link.manifestUrl) ?? 0);
21390
+ if (expiresAt <= Date.now())
21391
+ return fallback;
21392
+ const probe = await request(`${origin}/api/links`);
21393
+ if (!probe.ok || (await probe.json()).service !== "metahub-share-links")
21394
+ throw new Error("static viewer");
21395
+ const control = await controlFor(config, slug, origin);
21396
+ const id = await linkId(control.token);
21397
+ const endpoint = `${origin}/api/links/${id}`;
21398
+ const authorization = `Bearer ${control.token}`;
21399
+ const descriptor = {
21400
+ v: 1,
21401
+ manifestUrl: link.manifestUrl,
21402
+ expiresAt,
21403
+ ...link.saltB64 ? { salt: toB64url(fromB64(link.saltB64)) } : {}
21404
+ };
21405
+ const current = await request(endpoint, { headers: { authorization } });
21406
+ if (!current.ok && current.status !== 404)
21407
+ throw new Error("short-link lookup failed");
21408
+ const etag = current.headers.get("etag");
21409
+ if (current.ok && !etag)
21410
+ throw new Error("not a short-link API");
21411
+ const result = await request(endpoint, {
21412
+ method: "PUT",
21413
+ headers: {
21414
+ authorization,
21415
+ "content-type": "application/json",
21416
+ ...current.ok ? { "if-match": etag } : { "if-none-match": "*" }
21417
+ },
21418
+ body: JSON.stringify(descriptor)
21419
+ });
21420
+ if (!result.ok || !result.headers.get("etag") || (await result.json()).ok !== true)
21421
+ throw new Error("short-link registration failed");
21422
+ return `${origin}/s/${id}${link.keyB64 ? `#k=${toB64url(fromB64(link.keyB64))}` : ""}`;
21423
+ } catch {
21424
+ console.warn("share: short-link service unavailable; using the original long link");
21425
+ return fallback;
21426
+ }
21427
+ }
21428
+ async function revokeBucketShortLinks(config, slug) {
21429
+ const client = storageClientFor(config);
21430
+ for (const object of await client.list(root(config, slug))) {
21431
+ if (!object.key.endsWith(".json"))
21432
+ continue;
21433
+ const raw = await client.get(object.key);
21434
+ if (!raw)
21435
+ continue;
21436
+ const control = await unpack(config, raw);
21437
+ const response = await request(`${control.base}/api/links/${await linkId(control.token)}`, {
21438
+ method: "DELETE",
21439
+ headers: { authorization: `Bearer ${control.token}` }
21440
+ });
21441
+ if (!response.ok || (await response.json()).ok !== true)
21442
+ throw new Error("short-link revocation has not been confirmed; retry later");
21443
+ }
21444
+ }
21445
+
21446
+ // src/core/sync/share-export.ts
20982
21447
  init_e2ee();
20983
21448
  function sharesRoot(prefix) {
20984
21449
  const p = (prefix || "").replace(/^\/+|\/+$/g, "");
@@ -21119,6 +21584,8 @@ async function renewBucketShare(db, config, slug, expiresSec = MAX_PRESIGN_SECON
21119
21584
  const meta = await readMeta2(config, slug);
21120
21585
  if (!meta)
21121
21586
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21587
+ if (meta.revoking)
21588
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21122
21589
  const shareKey = fromB64(meta.key);
21123
21590
  const exp = clampExpiry(expiresSec);
21124
21591
  const { manifestUrl, objects, title } = await writeShareObjects(db, {
@@ -21154,6 +21621,8 @@ async function represignBucketShare(config, slug, expiresSec = MAX_PRESIGN_SECON
21154
21621
  const meta = await readMeta2(config, slug);
21155
21622
  if (!meta)
21156
21623
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21624
+ if (meta.revoking)
21625
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21157
21626
  const client = storageClientFor(config);
21158
21627
  const root = sharesRoot(config.prefix);
21159
21628
  const shareKey = fromB64(meta.key);
@@ -21193,15 +21662,19 @@ async function listBucketShares(config) {
21193
21662
  async function deleteBucketShareObjects(config, slug) {
21194
21663
  const client = storageClientFor(config);
21195
21664
  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);
21665
+ const meta = await readMeta2(config, slug);
21666
+ if (meta)
21667
+ await client.put(metaKey(root, slug), await metaBytes(config, { ...meta, revoking: true }), { contentType: "application/octet-stream" });
21668
+ await revokeBucketShortLinks(config, slug);
21669
+ const objs = new Set([...meta?.objects ?? [], ...(await client.list(`${root}/${slug}/`)).map((o) => o.key)]);
21670
+ const metaPath = metaKey(root, slug);
21201
21671
  for (const key of objs)
21202
- await client.del(key).catch(() => {
21203
- return;
21204
- });
21672
+ if (key !== metaPath && !key.includes(`/links/`))
21673
+ await client.del(key);
21674
+ for (const key of objs)
21675
+ if (key.includes(`/links/`))
21676
+ await client.del(key);
21677
+ await client.del(metaPath);
21205
21678
  }
21206
21679
  function clampExpiry(sec) {
21207
21680
  return Math.min(Math.max(60, Math.floor(sec)), MAX_PRESIGN_SECONDS);
@@ -21236,7 +21709,8 @@ function bucketShareItem(bucket, m) {
21236
21709
  hosting: "s3",
21237
21710
  expiresAt: m.presign_exp,
21238
21711
  hasPassword: m.has_password,
21239
- contentUpdatedAt: m.content_updated_at ?? m.created_at
21712
+ contentUpdatedAt: m.content_updated_at ?? m.created_at,
21713
+ ...m.revoking ? { lifecycle: "cleanup_pending" } : {}
21240
21714
  };
21241
21715
  }
21242
21716
  async function fetchPeerShares(url, token, targetId) {
@@ -21438,7 +21912,7 @@ function pickBucket(db, bucketUrl) {
21438
21912
  throw new MhError("invalid_input", `multiple buckets \u2014 pass --bucket <url> (one of: ${buckets.map((b) => b.url).join(", ")})`);
21439
21913
  return buckets[0].url;
21440
21914
  }
21441
- function viewerBase(db, override) {
21915
+ function viewerBase2(db, override) {
21442
21916
  if (override)
21443
21917
  return override.replace(/\/+$/, "");
21444
21918
  const row = db.query("SELECT value FROM meta WHERE key='cfg_share_viewer_url'").get();
@@ -21451,10 +21925,6 @@ function viewerOriginOf(base) {
21451
21925
  return;
21452
21926
  }
21453
21927
  }
21454
- function bucketLink(base, manifestUrl, keyB64, saltB64) {
21455
- const frag = `m=${encodeURIComponent(manifestUrl)}&` + (keyB64 ? `k=${keyB64}` : `s=${saltB64}`);
21456
- return `${base}/#${frag}`;
21457
- }
21458
21928
  function assertShareCombo(req) {
21459
21929
  const fail = (msg) => {
21460
21930
  throw new MhError("invalid_input", msg);
@@ -21481,7 +21951,7 @@ async function createShareAction(db, req) {
21481
21951
  const target = resolveTarget(db, req.kind, req.ref);
21482
21952
  const bucketUrl = pickBucket(db, req.bucketUrl);
21483
21953
  const { config, label } = s3PeerConfig2(db, bucketUrl);
21484
- const base = viewerBase(db, req.viewerBase);
21954
+ const base = viewerBase2(db, req.viewerBase);
21485
21955
  const slug = freshSlug(db);
21486
21956
  const out = await createBucketShare(db, {
21487
21957
  slug,
@@ -21499,7 +21969,7 @@ async function createShareAction(db, req) {
21499
21969
  permission: "view",
21500
21970
  transport: "s3",
21501
21971
  hosting: "s3",
21502
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
21972
+ url: await bucketShareUrl(config, slug, base, out),
21503
21973
  expiresAt: out.presignExp,
21504
21974
  source: `\u6876 ${label}`
21505
21975
  };
@@ -21784,7 +22254,13 @@ async function revokeShareAction(db, slug) {
21784
22254
  const owner = await bucketOwning(db, slug);
21785
22255
  if (!owner)
21786
22256
  return { ok: false, status: "not_found" };
21787
- await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22257
+ try {
22258
+ await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22259
+ } catch {
22260
+ if (owner.item)
22261
+ primeRemoteShare(db, owner.peer.url, { ...owner.item, lifecycle: "cleanup_pending" });
22262
+ return { ok: false, status: "cleanup_pending" };
22263
+ }
21788
22264
  dropRemoteShare(db, owner.peer.url, slug);
21789
22265
  return { ok: true, status: "revoked" };
21790
22266
  }
@@ -21806,7 +22282,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21806
22282
  throw new MhError("not_found", `no such object-storage share: ${slug}`);
21807
22283
  const { peer: p } = owner;
21808
22284
  const config = JSON.parse(p.config);
21809
- const base = viewerBase(db, viewerBaseOverride);
22285
+ const base = viewerBase2(db, viewerBaseOverride);
21810
22286
  const out = opts?.refreshContent ? await renewBucketShare(db, config, slug) : await represignBucketShare(config, slug);
21811
22287
  if (owner.item)
21812
22288
  primeRemoteShare(db, p.url, {
@@ -21821,7 +22297,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21821
22297
  permission: "view",
21822
22298
  transport: "s3",
21823
22299
  hosting: "s3",
21824
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
22300
+ url: await bucketShareUrl(config, slug, base, out),
21825
22301
  expiresAt: out.presignExp,
21826
22302
  source: `\u6876 ${p.label ?? p.url}`
21827
22303
  };
@@ -22899,6 +23375,7 @@ async function enableWorkerSubdomain(cfg) {
22899
23375
  }
22900
23376
 
22901
23377
  // src/core/sync/room-serve.ts
23378
+ init_ui_theme();
22902
23379
  init_errors();
22903
23380
  init_http_status();
22904
23381
  init_ids();
@@ -22910,6 +23387,19 @@ init_sites_core();
22910
23387
  init_shares();
22911
23388
  init_room_protocol();
22912
23389
  var ROOM_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
23390
+ var PAGE_CSS = `
23391
+ ${THEME_CSS}
23392
+ *{box-sizing:border-box}
23393
+ 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;
23394
+ min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
23395
+ main{max-width:420px;width:100%}
23396
+ form.pw{display:flex;flex-direction:column;gap:12px}
23397
+ form.pw input{padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--fg)}
23398
+ form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:var(--accent-fg);cursor:pointer}
23399
+ .err{color:var(--danger);font-size:13px;margin:0}
23400
+ .muted{color:var(--muted);font-size:14px}
23401
+ h1{font-size:20px;margin:0 0 10px;text-align:center}
23402
+ footer{margin-top:34px;color:var(--muted);font-size:12px;text-align:center}`;
22913
23403
 
22914
23404
  // src/workers/room.ts
22915
23405
  init_room_protocol();
@@ -23384,6 +23874,7 @@ function disconnectEdge(db) {
23384
23874
  }
23385
23875
 
23386
23876
  // src/core/sync/cf-oauth.ts
23877
+ init_ui_theme();
23387
23878
  init_errors();
23388
23879
  init_page_messages();
23389
23880
  init_e2ee();
@@ -23546,7 +24037,7 @@ async function startCfLogin(opts = {}) {
23546
24037
  }
23547
24038
  };
23548
24039
  }
23549
- var callbackHtml = (locale) => `<!doctype html><html lang="${locale}"><meta charset="utf-8"><title>MetaHub</title>
24040
+ 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
24041
  <body style="font:15px system-ui;display:grid;place-items:center;height:100vh;margin:0">
23551
24042
  <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
24043
 
@@ -24853,4 +25344,4 @@ export {
24853
25344
  writeTx
24854
25345
  };
24855
25346
 
24856
- //# debugId=313E5D38AF56C98B64756E2164756E21
25347
+ //# debugId=39484D6BDF841CDA64756E2164756E21