@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/cli.js CHANGED
@@ -801,7 +801,7 @@ var package_default;
801
801
  var init_package = __esm(() => {
802
802
  package_default = {
803
803
  name: "@tensorix/metahub",
804
- version: "0.5.4",
804
+ version: "0.5.7",
805
805
  description: "Metahub core library and CLI",
806
806
  license: "AGPL-3.0-only",
807
807
  type: "module",
@@ -8160,6 +8160,45 @@ function removeSitePublishStates(db, siteId) {
8160
8160
  }
8161
8161
  var META_KEY = "site_publish_rollbacks", STATE_META_KEY = "site_publish_states";
8162
8162
 
8163
+ // src/core/copy-views.ts
8164
+ function copyViews(raw, props, records, selfRelations) {
8165
+ if (!Array.isArray(raw))
8166
+ return raw;
8167
+ const object = (x) => !!x && typeof x === "object" && !Array.isArray(x);
8168
+ const prop = (x) => typeof x === "string" ? props.get(x) ?? x : x;
8169
+ return raw.map((v) => {
8170
+ if (!object(v))
8171
+ return v;
8172
+ const out = { ...v };
8173
+ for (const key of ["group", "dateProp", "start", "end"])
8174
+ if (key in out)
8175
+ out[key] = prop(out[key]);
8176
+ if (Array.isArray(v.hidden))
8177
+ out.hidden = v.hidden.map(prop);
8178
+ if (Array.isArray(v.sort))
8179
+ out.sort = v.sort.map((s) => object(s) ? { ...s, prop: prop(s.prop) } : s);
8180
+ if (object(v.filter) && Array.isArray(v.filter.rules))
8181
+ out.filter = {
8182
+ ...v.filter,
8183
+ rules: v.filter.rules.map((r) => {
8184
+ if (!object(r))
8185
+ return r;
8186
+ return {
8187
+ ...r,
8188
+ prop: prop(r.prop),
8189
+ ...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) } : {}
8190
+ };
8191
+ })
8192
+ };
8193
+ if (object(v.calc))
8194
+ out.calc = Object.fromEntries(Object.entries(v.calc).map(([id, value]) => [
8195
+ props.get(id) ?? id,
8196
+ value
8197
+ ]));
8198
+ return out;
8199
+ });
8200
+ }
8201
+
8163
8202
  // src/core/indexing.ts
8164
8203
  function sanitize(s) {
8165
8204
  return s.replace(/[^a-zA-Z0-9_]/g, "_");
@@ -8971,10 +9010,6 @@ var init_databases = __esm(() => {
8971
9010
  name: opts.name ?? src.name,
8972
9011
  icon: opts.icon ?? src.icon ?? undefined
8973
9012
  });
8974
- if (src.meta !== null) {
8975
- const { collapsed: _hidden, ...meta } = src.meta;
8976
- emitMetaPatch(db, dup.id, null, meta);
8977
- }
8978
9013
  const propIdMap = new Map;
8979
9014
  const selfRelNew = new Set;
8980
9015
  for (const p of listProperties(db, id)) {
@@ -9009,6 +9044,12 @@ var init_databases = __esm(() => {
9009
9044
  if (Object.keys(patch).length)
9010
9045
  updateRecord(db, recIdMap.get(r.id), patch);
9011
9046
  }
9047
+ if (src.meta !== null) {
9048
+ const { collapsed: _hidden, ...meta } = src.meta;
9049
+ if ("views" in meta)
9050
+ 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)));
9051
+ emitMetaPatch(db, dup.id, null, meta);
9052
+ }
9012
9053
  return getDatabase(db, dup.id);
9013
9054
  });
9014
9055
  deleteDatabase = grouped(function deleteDatabase(db, id) {
@@ -13768,6 +13809,92 @@ var init_devices = __esm(() => {
13768
13809
  init_storage();
13769
13810
  });
13770
13811
 
13812
+ // src/core/ui-theme.ts
13813
+ var LIGHT_THEME, DARK_THEME, declarations = (palette) => Object.entries(palette).map(([k, v]) => `--${k}:${v};`).join(""), THEME_CSS, THEME_INIT_SCRIPT;
13814
+ var init_ui_theme = __esm(() => {
13815
+ LIGHT_THEME = {
13816
+ bg: "#ffffff",
13817
+ paper: "#ffffff",
13818
+ surface: "#fafaf9",
13819
+ "surface-2": "#f1f1ef",
13820
+ sidebar: "#f7f7f5",
13821
+ fg: "#37352f",
13822
+ "fg-soft": "#595750",
13823
+ muted: "#6f6d66",
13824
+ line: "#eae9e6",
13825
+ "line-strong": "#d8d7d3",
13826
+ accent: "#3867b2",
13827
+ "accent-fg": "#ffffff",
13828
+ "accent-soft": "#edf2f9",
13829
+ hover: "rgba(55,53,47,.035)",
13830
+ "hover-2": "rgba(55,53,47,.07)",
13831
+ "code-bg": "#f6f5f3",
13832
+ "selected-bg": "#f7f9fc",
13833
+ "selected-border": "#a8b8ce",
13834
+ "selected-icon-bg": "#edf2f8",
13835
+ "selected-icon-fg": "#4d678b",
13836
+ "selected-muted": "#626b78",
13837
+ "selected-hover-bg": "#f1f5fa",
13838
+ "selected-hover-border": "#8199bb",
13839
+ danger: "#b6423a",
13840
+ "danger-fg": "#ffffff",
13841
+ "danger-soft": "#f9eeec",
13842
+ success: "#347653",
13843
+ warning: "#866022",
13844
+ "warning-soft": "#faf3e5",
13845
+ "brand-purple": "#79619d",
13846
+ "brand-teal": "#397d76",
13847
+ "brand-orange": "#a06436",
13848
+ "shadow-sm": "0 1px 2px rgba(35,32,25,.035)",
13849
+ "shadow-md": "0 4px 14px rgba(35,32,25,.08)",
13850
+ "shadow-lg": "0 16px 48px rgba(35,32,25,.13)",
13851
+ "brand-stripe": "linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"
13852
+ };
13853
+ DARK_THEME = {
13854
+ bg: "#191919",
13855
+ paper: "#191919",
13856
+ surface: "#222222",
13857
+ "surface-2": "#2c2c2b",
13858
+ sidebar: "#202020",
13859
+ fg: "#e8e7e4",
13860
+ "fg-soft": "#c0beb8",
13861
+ muted: "#aaa8a2",
13862
+ line: "#30302f",
13863
+ "line-strong": "#42423f",
13864
+ accent: "#8aaddb",
13865
+ "accent-fg": "#172235",
13866
+ "accent-soft": "#263242",
13867
+ hover: "rgba(255,255,255,.035)",
13868
+ "hover-2": "rgba(255,255,255,.065)",
13869
+ "code-bg": "#242423",
13870
+ "selected-bg": "#242a32",
13871
+ "selected-border": "#56657a",
13872
+ "selected-icon-bg": "#2d3745",
13873
+ "selected-icon-fg": "#afc5e2",
13874
+ "selected-muted": "#b5bfcd",
13875
+ "selected-hover-bg": "#2a313b",
13876
+ "selected-hover-border": "#7287a3",
13877
+ danger: "#e89890",
13878
+ "danger-fg": "#2b1716",
13879
+ "danger-soft": "#3b2927",
13880
+ success: "#92bd9b",
13881
+ warning: "#d6b578",
13882
+ "warning-soft": "#363024",
13883
+ "brand-purple": "#baa6d1",
13884
+ "brand-teal": "#8cbbb4",
13885
+ "brand-orange": "#d4a37d",
13886
+ "shadow-sm": "0 1px 2px rgba(0,0,0,.15)",
13887
+ "shadow-md": "0 4px 14px rgba(0,0,0,.25)",
13888
+ "shadow-lg": "0 16px 48px rgba(0,0,0,.35)",
13889
+ "brand-stripe": "linear-gradient(to right,#335cff 0% 25%,#8967ed 25% 50%,#18b7b6 50% 75%,#f28b55 75% 100%)"
13890
+ };
13891
+ THEME_CSS = `:root{color-scheme:light;${declarations(LIGHT_THEME)}--card:var(--surface-2);--ok:var(--success)}
13892
+ @media(prefers-color-scheme:dark){:root:not([data-resolved]){color-scheme:dark;${declarations(DARK_THEME)}}}
13893
+ :root[data-resolved="dark"]{color-scheme:dark;${declarations(DARK_THEME)}}
13894
+ ::selection{background:var(--accent-soft);color:var(--accent)}`;
13895
+ 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}'})();`;
13896
+ });
13897
+
13771
13898
  // src/core/sync/http-status.ts
13772
13899
  function httpStatusForCode(code) {
13773
13900
  return code ? STATUS[code] ?? 400 : 400;
@@ -13915,6 +14042,11 @@ var init_page_messages = __esm(() => {
13915
14042
  "\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u8F93\u5165\u4EE4\u724C\u3002": "Session expired. Enter the token again.",
13916
14043
  \u{9644}\u{4ef6}\u{5df2}\u{5931}\u{6548}: "Attachment unavailable",
13917
14044
  \u{94fe}\u{63a5}\u{7f3a}\u{5c11}\u{5206}\u{4eab}\u{6570}\u{636e}: "The link is missing share data",
14045
+ \u{65e0}\u{6cd5}\u{6253}\u{5f00}\u{8fd9}\u{4efd}\u{5206}\u{4eab}: "Unable to open this share",
14046
+ "\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.",
14047
+ "\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.",
14048
+ "\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.",
14049
+ \u{91cd}\u{65b0}\u{52a0}\u{8f7d}: "Reload",
13918
14050
  \u{8bf7}\u{8f93}\u{5165}\u{5206}\u{4eab}\u{53e3}\u{4ee4}: "Enter the share passcode",
13919
14051
  \u{94fe}\u{63a5}\u{7f3a}\u{5c11}\u{5bc6}\u{94a5}: "The link is missing its key",
13920
14052
  \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",
@@ -15450,17 +15582,9 @@ function unlockPage(locale = "zh-CN") {
15450
15582
  return `<!doctype html><html lang="${locale}"><head>
15451
15583
  <meta charset="utf-8">
15452
15584
  <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
15453
- <meta name="theme-color" content="#ffffff">
15585
+ <meta name="theme-color" content="${LIGHT_THEME.bg}">
15454
15586
  <title>${m("\u9A8C\u8BC1\u8BBF\u95EE\u4EE4\u724C \xB7 metahub")}</title>
15455
- <script>
15456
- // FOUC-guard theme resolution, same key/logic as src/webui/theme.ts.
15457
- try {
15458
- var mt = localStorage.getItem("mh-theme");
15459
- var dark = mt === "dark" || (mt !== "light" && matchMedia("(prefers-color-scheme: dark)").matches);
15460
- document.documentElement.dataset.resolved = dark ? "dark" : "light";
15461
- document.head.querySelector('meta[name=theme-color]').setAttribute("content", dark ? "#1a1a1c" : "#ffffff");
15462
- } catch (e) {}
15463
- </script>
15587
+ <script>${THEME_INIT_SCRIPT}</script>
15464
15588
  <style>${UNLOCK_CSS}</style></head><body>
15465
15589
  <div class="boot" id="boot"><svg viewBox="0 0 24 24">${CUBE_SVG}</svg></div>
15466
15590
  <form class="card" id="f" style="display:none" autocomplete="on">
@@ -15609,15 +15733,20 @@ function cookieMutationRejection(req, url, cfg) {
15609
15733
  function unauthorized() {
15610
15734
  return Response.json({ error: "unauthorized" }, { status: 401 });
15611
15735
  }
15612
- 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 = `
15613
- :root{color-scheme:light;
15614
- --bg:#ffffff;--surface:#fbfbfa;--surface-2:#f1f1ef;--fg:#2c2c30;--fg-soft:#5b5b62;
15615
- --muted:#75757f;--line:#ebebe8;--line-strong:#dededb;--accent:#4a55d6;--accent-fg:#ffffff;
15616
- --accent-soft:#eef0ff;--danger:#d6473b;--danger-soft:#fdeceb;--code-bg:#f8f8f6}
15617
- :root[data-resolved="dark"]{color-scheme:dark;
15618
- --bg:#1a1a1c;--surface:#202022;--surface-2:#2a2a2d;--fg:#e6e6e9;--fg-soft:#b4b4bb;
15619
- --muted:#8b8b95;--line:#2c2c30;--line-strong:#3a3a40;--accent:#7b86ff;--accent-fg:#14141a;
15620
- --accent-soft:#23243a;--danger:#f87168;--danger-soft:#341e1c;--code-bg:#26262a}
15736
+ 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;");
15737
+ var init_auth = __esm(() => {
15738
+ init_ui_theme();
15739
+ init_page_messages();
15740
+ init_inject_runtime();
15741
+ init_token();
15742
+ init_pairing();
15743
+ init_protocol();
15744
+ HTML_HEADERS = {
15745
+ "content-type": "text/html; charset=utf-8",
15746
+ "referrer-policy": "same-origin"
15747
+ };
15748
+ UNLOCK_CSS = `
15749
+ ${THEME_CSS}
15621
15750
  *{box-sizing:border-box}
15622
15751
  html,body{margin:0;min-height:100%}
15623
15752
  body{font-family:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
@@ -15682,17 +15811,7 @@ var COOKIE = "mh_token", COOKIE_ATTRS = "path=/; SameSite=Strict; Max-Age=315360
15682
15811
  @keyframes spin{to{transform:rotate(360deg)}}
15683
15812
  @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important}
15684
15813
  .brand path{stroke-dashoffset:0}}
15685
- `, escapeAttr = (v) => v.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
15686
- var init_auth = __esm(() => {
15687
- init_page_messages();
15688
- init_inject_runtime();
15689
- init_token();
15690
- init_pairing();
15691
- init_protocol();
15692
- HTML_HEADERS = {
15693
- "content-type": "text/html; charset=utf-8",
15694
- "referrer-policy": "same-origin"
15695
- };
15814
+ `;
15696
15815
  });
15697
15816
 
15698
15817
  // src/core/md/grammar.ts
@@ -16322,8 +16441,7 @@ function notFoundResponse(locale, title, detail) {
16322
16441
  <meta name="robots" content="noindex">
16323
16442
  <title>${escapeHtml(title)}</title>
16324
16443
  <style>
16325
- :root{--bg:#ffffff;--fg:#1f2328;--muted:#6e7781;--line:#d0d7de;--accent:#0969da;--card:#f6f8fa}
16326
- @media (prefers-color-scheme: dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8b949e;--line:#30363d;--accent:#4493f8;--card:#161b22}}
16444
+ ${THEME_CSS}
16327
16445
  *{box-sizing:border-box}
16328
16446
  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;
16329
16447
  min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
@@ -16347,6 +16465,7 @@ function notFoundResponse(locale, title, detail) {
16347
16465
  });
16348
16466
  }
16349
16467
  var init_sites_serve = __esm(() => {
16468
+ init_ui_theme();
16350
16469
  init_sites();
16351
16470
  init_site_channel_store();
16352
16471
  init_grants_core();
@@ -16358,6 +16477,20 @@ var init_sites_serve = __esm(() => {
16358
16477
  init_auth();
16359
16478
  });
16360
16479
 
16480
+ // src/core/sync/share-file-styles.ts
16481
+ var SHARE_FILE_CSS = `
16482
+ article.doc p.mh-file{margin:0}
16483
+ article.doc .mh-file+.mh-file{border-top:1px solid var(--line)}
16484
+ 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}
16485
+ 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}
16486
+ 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")}
16487
+ 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")}
16488
+ article.doc .mh-file a[href]:not([aria-disabled="true"]):hover{background:var(--card);color:var(--accent);text-decoration:underline;text-underline-offset:3px}
16489
+ article.doc .mh-file a:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;background:var(--card);color:var(--accent)}
16490
+ article.doc .mh-file a[aria-disabled="true"]{color:var(--muted);pointer-events:none}
16491
+ article.doc .mh-file a[aria-disabled="true"]::after{display:none}
16492
+ `;
16493
+
16361
16494
  // src/core/sync/share-copy.ts
16362
16495
  function markdownTable(header, rows) {
16363
16496
  const cell = (s) => s.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
@@ -16391,12 +16524,11 @@ var ICON_COPY = '<svg class="ic-copy" viewBox="0 0 24 24" aria-hidden="true"><re
16391
16524
  .copy-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
16392
16525
  .copy-btn svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;flex:none}
16393
16526
  .copy-btn .ic-check{display:none}
16394
- .copy-btn.done{color:var(--ok,#1a7f37);border-color:var(--ok,#1a7f37)}
16527
+ .copy-btn.done{color:var(--success);border-color:var(--success)}
16395
16528
  .copy-btn.done .ic-copy{display:none}.copy-btn.done .ic-check{display:block}
16396
16529
  .copy-btn.done .ic-check path{stroke-dasharray:24;stroke-dashoffset:24;animation:mh-draw .22s ease-out forwards}
16397
- .copy-btn.fail{color:#cf222e;border-color:#cf222e}
16530
+ .copy-btn.fail{color:var(--danger);border-color:var(--danger)}
16398
16531
  @keyframes mh-draw{to{stroke-dashoffset:0}}
16399
- @media (prefers-color-scheme:dark){:root{--ok:#3fb950}}
16400
16532
  @media (max-width:480px){.copy-btn span{display:none}.copy-btn{padding:0 9px}}
16401
16533
  @media (prefers-reduced-motion:reduce){.copy-btn .ic-check path{animation:none;stroke-dashoffset:0}}`;
16402
16534
  var init_share_copy = __esm(() => {
@@ -16811,14 +16943,13 @@ function pageShell(title, inner, opts) {
16811
16943
  <meta name="robots" content="noindex">
16812
16944
  <title>${escapeHtml(title)}</title>
16813
16945
  <style>
16814
- :root{--bg:#ffffff;--fg:#1f2328;--muted:#6e7781;--line:#d0d7de;--accent:#0969da;--card:#f6f8fa}
16815
- @media (prefers-color-scheme: dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#8b949e;--line:#30363d;--accent:#4493f8;--card:#161b22}}
16946
+ ${THEME_CSS}
16816
16947
  *{box-sizing:border-box}
16817
16948
  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}
16818
16949
  .wrap{max-width:820px;margin:0 auto;padding:32px 20px 80px}
16819
16950
  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}
16820
16951
  header.mh h1.title{font-size:22px;margin:0}
16821
- .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}
16952
+ .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}
16822
16953
  ${COPY_CSS}
16823
16954
  .hint{color:var(--muted);font-size:13px;margin:0 0 14px}
16824
16955
  article.doc h1,article.doc h2,article.doc h3{line-height:1.3;margin:1.4em 0 .5em}
@@ -16832,11 +16963,11 @@ function pageShell(title, inner, opts) {
16832
16963
  article.doc td,article.doc th,table.db td,table.db th{border:1px solid var(--line);padding:7px 10px;text-align:left}
16833
16964
  table.db th{background:var(--card)}
16834
16965
  .table-wrap{overflow:auto}
16835
- td[contenteditable]{outline:none} td.saving{opacity:.5} td.saved{background:rgba(46,160,67,.18)}
16966
+ td[contenteditable]{outline:none} td.saving{opacity:.5} td.saved{background:color-mix(in srgb,var(--success) 18%,transparent)}
16836
16967
  .tag{display:inline-block;background:var(--card);border:1px solid var(--line);border-radius:999px;padding:1px 9px;font-size:12px}
16837
16968
  .muted{color:var(--muted)} .mh-img{text-align:center}
16838
16969
  .mh-media{text-align:center} .mh-media video,.mh-media audio{max-width:100%}
16839
- .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}
16970
+ ${SHARE_FILE_CSS}
16840
16971
  .mh-doclink{background:var(--card);border:1px solid var(--line);border-radius:6px;padding:0 5px;white-space:nowrap}
16841
16972
  .mh-doclink::before{content:"\uD83D\uDCC4";font-size:.85em;margin-right:3px}
16842
16973
  iframe.mh-embed{width:100%;border:1px solid var(--line);border-radius:8px;min-height:240px}
@@ -16845,8 +16976,8 @@ function pageShell(title, inner, opts) {
16845
16976
  .row{display:flex;gap:10px;align-items:center;margin:10px 0}
16846
16977
  form.pw{max-width:320px;margin:12vh auto 0;display:flex;flex-direction:column;gap:12px}
16847
16978
  form.pw input{padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--fg)}
16848
- form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:#fff;cursor:pointer}
16849
- form.pw .err{color:#f85149;font-size:13px;margin:0}
16979
+ form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:var(--accent-fg);cursor:pointer}
16980
+ form.pw .err{color:var(--danger);font-size:13px;margin:0}
16850
16981
  footer.mh{margin-top:48px;border-top:1px solid var(--line);padding-top:14px;color:var(--muted);font-size:12px;text-align:center}
16851
16982
  </style></head><body>
16852
16983
  ${opts.bare ? `<div class="wrap">${inner}</div>` : `<div class="wrap">
@@ -16860,6 +16991,7 @@ ${script}
16860
16991
  }
16861
16992
  var HTML, HASH_RE3, SHARE_SESSION_TTL_MS;
16862
16993
  var init_share_serve = __esm(() => {
16994
+ init_ui_theme();
16863
16995
  init_ids();
16864
16996
  init_shares();
16865
16997
  init_documents();
@@ -20903,6 +21035,162 @@ init_records();
20903
21035
  init_blobs_core();
20904
21036
  init_storage();
20905
21037
  init_storage_s3_sign();
21038
+
21039
+ // src/core/sync/share-short-links.ts
21040
+ init_e2ee();
21041
+ init_storage();
21042
+
21043
+ // src/core/sync/share-link.ts
21044
+ init_e2ee();
21045
+ async function tokenHash(token) {
21046
+ return toB64url(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token))));
21047
+ }
21048
+ async function linkId(token) {
21049
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
21050
+ return toB64url(new Uint8Array(digest).slice(0, 16));
21051
+ }
21052
+ function signedUrlExpiry(value) {
21053
+ try {
21054
+ const u = new URL(value);
21055
+ if (u.protocol !== "https:" || u.username || u.password || u.hash || value.length > 8192)
21056
+ return null;
21057
+ const date = u.searchParams.get("X-Amz-Date") ?? "";
21058
+ const seconds = Number(u.searchParams.get("X-Amz-Expires"));
21059
+ if (!/^\d{8}T\d{6}Z$/.test(date) || !Number.isInteger(seconds) || seconds < 1 || seconds > 604800)
21060
+ return null;
21061
+ if (!/^[a-f0-9]{64}$/i.test(u.searchParams.get("X-Amz-Signature") ?? ""))
21062
+ return null;
21063
+ 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`;
21064
+ const start = Date.parse(iso);
21065
+ return Number.isFinite(start) ? start + seconds * 1000 : null;
21066
+ } catch {
21067
+ return null;
21068
+ }
21069
+ }
21070
+ function longShareUrl(base, link) {
21071
+ return `${base.replace(/\/+$/, "")}/#m=${encodeURIComponent(link.manifestUrl)}&${link.keyB64 ? `k=${link.keyB64}` : `s=${link.saltB64}`}`;
21072
+ }
21073
+
21074
+ // src/core/sync/share-short-links.ts
21075
+ var TIMEOUT = 8000;
21076
+ function root(config, slug) {
21077
+ return `${config.prefix.replace(/^\/+|\/+$/g, "") ? `${config.prefix.replace(/^\/+|\/+$/g, "")}/` : ""}shares/${slug}/links/`;
21078
+ }
21079
+ function viewerBase(base) {
21080
+ const url = new URL(base);
21081
+ if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash)
21082
+ throw new Error("invalid viewer");
21083
+ if (url.pathname !== "/")
21084
+ throw new Error("static subpath viewer");
21085
+ return url.origin;
21086
+ }
21087
+ async function unpack(config, bytes) {
21088
+ const raw = config.encrypt && config.masterKey ? await decryptBytes(fromB64(config.masterKey), bytes) : bytes;
21089
+ const value = JSON.parse(new TextDecoder().decode(raw));
21090
+ if (value.v !== 1 || !/^[A-Za-z0-9_-]{43}$/.test(value.token) || viewerBase(value.base) !== value.base)
21091
+ throw new Error("invalid short-link control");
21092
+ return value;
21093
+ }
21094
+ async function controlFor(config, slug, base) {
21095
+ const client = storageClientFor(config);
21096
+ const path = `${root(config, slug)}${await tokenHash(base)}.json`;
21097
+ const existing = await client.get(path);
21098
+ if (existing) {
21099
+ const control = await unpack(config, existing);
21100
+ if (control.base !== base)
21101
+ throw new Error("viewer mismatch");
21102
+ return control;
21103
+ }
21104
+ const control = { v: 1, base, token: toB64url(generateMasterKey()) };
21105
+ const plain = new TextEncoder().encode(JSON.stringify(control));
21106
+ const bytes = config.encrypt && config.masterKey ? await encryptBytes(fromB64(config.masterKey), plain) : plain;
21107
+ try {
21108
+ await client.put(path, bytes, {
21109
+ ifNoneMatch: true,
21110
+ contentType: "application/octet-stream"
21111
+ });
21112
+ } catch (error) {
21113
+ const winner = await client.get(path);
21114
+ if (!winner)
21115
+ throw error;
21116
+ const value = await unpack(config, winner);
21117
+ if (value.base !== base)
21118
+ throw new Error("viewer mismatch");
21119
+ return value;
21120
+ }
21121
+ return control;
21122
+ }
21123
+ function request(url, init = {}) {
21124
+ return fetch(url, {
21125
+ ...init,
21126
+ redirect: "error",
21127
+ credentials: "omit",
21128
+ cache: "no-store",
21129
+ signal: AbortSignal.timeout(TIMEOUT)
21130
+ });
21131
+ }
21132
+ async function bucketShareUrl(config, slug, base, link) {
21133
+ const fallback = longShareUrl(base, link);
21134
+ try {
21135
+ const origin = viewerBase(base);
21136
+ const expiresAt = Math.min(link.presignExp, signedUrlExpiry(link.manifestUrl) ?? 0);
21137
+ if (expiresAt <= Date.now())
21138
+ return fallback;
21139
+ const probe = await request(`${origin}/api/links`);
21140
+ if (!probe.ok || (await probe.json()).service !== "metahub-share-links")
21141
+ throw new Error("static viewer");
21142
+ const control = await controlFor(config, slug, origin);
21143
+ const id = await linkId(control.token);
21144
+ const endpoint = `${origin}/api/links/${id}`;
21145
+ const authorization = `Bearer ${control.token}`;
21146
+ const descriptor = {
21147
+ v: 1,
21148
+ manifestUrl: link.manifestUrl,
21149
+ expiresAt,
21150
+ ...link.saltB64 ? { salt: toB64url(fromB64(link.saltB64)) } : {}
21151
+ };
21152
+ const current = await request(endpoint, { headers: { authorization } });
21153
+ if (!current.ok && current.status !== 404)
21154
+ throw new Error("short-link lookup failed");
21155
+ const etag = current.headers.get("etag");
21156
+ if (current.ok && !etag)
21157
+ throw new Error("not a short-link API");
21158
+ const result = await request(endpoint, {
21159
+ method: "PUT",
21160
+ headers: {
21161
+ authorization,
21162
+ "content-type": "application/json",
21163
+ ...current.ok ? { "if-match": etag } : { "if-none-match": "*" }
21164
+ },
21165
+ body: JSON.stringify(descriptor)
21166
+ });
21167
+ if (!result.ok || !result.headers.get("etag") || (await result.json()).ok !== true)
21168
+ throw new Error("short-link registration failed");
21169
+ return `${origin}/s/${id}${link.keyB64 ? `#k=${toB64url(fromB64(link.keyB64))}` : ""}`;
21170
+ } catch {
21171
+ console.warn("share: short-link service unavailable; using the original long link");
21172
+ return fallback;
21173
+ }
21174
+ }
21175
+ async function revokeBucketShortLinks(config, slug) {
21176
+ const client = storageClientFor(config);
21177
+ for (const object of await client.list(root(config, slug))) {
21178
+ if (!object.key.endsWith(".json"))
21179
+ continue;
21180
+ const raw = await client.get(object.key);
21181
+ if (!raw)
21182
+ continue;
21183
+ const control = await unpack(config, raw);
21184
+ const response = await request(`${control.base}/api/links/${await linkId(control.token)}`, {
21185
+ method: "DELETE",
21186
+ headers: { authorization: `Bearer ${control.token}` }
21187
+ });
21188
+ if (!response.ok || (await response.json()).ok !== true)
21189
+ throw new Error("short-link revocation has not been confirmed; retry later");
21190
+ }
21191
+ }
21192
+
21193
+ // src/core/sync/share-export.ts
20906
21194
  init_e2ee();
20907
21195
  function sharesRoot(prefix) {
20908
21196
  const p = (prefix || "").replace(/^\/+|\/+$/g, "");
@@ -21043,6 +21331,8 @@ async function renewBucketShare(db, config, slug, expiresSec = MAX_PRESIGN_SECON
21043
21331
  const meta = await readMeta2(config, slug);
21044
21332
  if (!meta)
21045
21333
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21334
+ if (meta.revoking)
21335
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21046
21336
  const shareKey = fromB64(meta.key);
21047
21337
  const exp = clampExpiry(expiresSec);
21048
21338
  const { manifestUrl, objects, title } = await writeShareObjects(db, {
@@ -21078,6 +21368,8 @@ async function represignBucketShare(config, slug, expiresSec = MAX_PRESIGN_SECON
21078
21368
  const meta = await readMeta2(config, slug);
21079
21369
  if (!meta)
21080
21370
  throw new MhError("not_found", `no such bucket share: ${slug}`);
21371
+ if (meta.revoking)
21372
+ throw new MhError("conflict", "share revocation is pending; retry revocation instead");
21081
21373
  const client = storageClientFor(config);
21082
21374
  const root = sharesRoot(config.prefix);
21083
21375
  const shareKey = fromB64(meta.key);
@@ -21117,15 +21409,19 @@ async function listBucketShares(config) {
21117
21409
  async function deleteBucketShareObjects(config, slug) {
21118
21410
  const client = storageClientFor(config);
21119
21411
  const root = sharesRoot(config.prefix);
21120
- await client.del(metaKey(root, slug)).catch(() => {
21121
- return;
21122
- });
21123
- const meta = await readMeta2(config, slug).catch(() => null);
21124
- const objs = meta?.objects ?? (await client.list(`${root}/${slug}/`).catch(() => [])).map((o) => o.key);
21412
+ const meta = await readMeta2(config, slug);
21413
+ if (meta)
21414
+ await client.put(metaKey(root, slug), await metaBytes(config, { ...meta, revoking: true }), { contentType: "application/octet-stream" });
21415
+ await revokeBucketShortLinks(config, slug);
21416
+ const objs = new Set([...meta?.objects ?? [], ...(await client.list(`${root}/${slug}/`)).map((o) => o.key)]);
21417
+ const metaPath = metaKey(root, slug);
21125
21418
  for (const key of objs)
21126
- await client.del(key).catch(() => {
21127
- return;
21128
- });
21419
+ if (key !== metaPath && !key.includes(`/links/`))
21420
+ await client.del(key);
21421
+ for (const key of objs)
21422
+ if (key.includes(`/links/`))
21423
+ await client.del(key);
21424
+ await client.del(metaPath);
21129
21425
  }
21130
21426
  function clampExpiry(sec) {
21131
21427
  return Math.min(Math.max(60, Math.floor(sec)), MAX_PRESIGN_SECONDS);
@@ -21160,7 +21456,8 @@ function bucketShareItem(bucket, m) {
21160
21456
  hosting: "s3",
21161
21457
  expiresAt: m.presign_exp,
21162
21458
  hasPassword: m.has_password,
21163
- contentUpdatedAt: m.content_updated_at ?? m.created_at
21459
+ contentUpdatedAt: m.content_updated_at ?? m.created_at,
21460
+ ...m.revoking ? { lifecycle: "cleanup_pending" } : {}
21164
21461
  };
21165
21462
  }
21166
21463
  async function fetchPeerShares(url, token, targetId) {
@@ -21371,7 +21668,7 @@ function pickBucket(db, bucketUrl) {
21371
21668
  throw new MhError("invalid_input", `multiple buckets \u2014 pass --bucket <url> (one of: ${buckets.map((b) => b.url).join(", ")})`);
21372
21669
  return buckets[0].url;
21373
21670
  }
21374
- function viewerBase(db, override) {
21671
+ function viewerBase2(db, override) {
21375
21672
  if (override)
21376
21673
  return override.replace(/\/+$/, "");
21377
21674
  const row = db.query("SELECT value FROM meta WHERE key='cfg_share_viewer_url'").get();
@@ -21384,10 +21681,6 @@ function viewerOriginOf(base) {
21384
21681
  return;
21385
21682
  }
21386
21683
  }
21387
- function bucketLink(base, manifestUrl, keyB64, saltB64) {
21388
- const frag = `m=${encodeURIComponent(manifestUrl)}&` + (keyB64 ? `k=${keyB64}` : `s=${saltB64}`);
21389
- return `${base}/#${frag}`;
21390
- }
21391
21684
  function assertShareCombo(req) {
21392
21685
  const fail = (msg) => {
21393
21686
  throw new MhError("invalid_input", msg);
@@ -21414,7 +21707,7 @@ async function createShareAction(db, req) {
21414
21707
  const target = resolveTarget(db, req.kind, req.ref);
21415
21708
  const bucketUrl = pickBucket(db, req.bucketUrl);
21416
21709
  const { config, label } = s3PeerConfig(db, bucketUrl);
21417
- const base = viewerBase(db, req.viewerBase);
21710
+ const base = viewerBase2(db, req.viewerBase);
21418
21711
  const slug = freshSlug(db);
21419
21712
  const out = await createBucketShare(db, {
21420
21713
  slug,
@@ -21432,7 +21725,7 @@ async function createShareAction(db, req) {
21432
21725
  permission: "view",
21433
21726
  transport: "s3",
21434
21727
  hosting: "s3",
21435
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
21728
+ url: await bucketShareUrl(config, slug, base, out),
21436
21729
  expiresAt: out.presignExp,
21437
21730
  source: `\u6876 ${label}`
21438
21731
  };
@@ -21717,7 +22010,13 @@ async function revokeShareAction(db, slug) {
21717
22010
  const owner = await bucketOwning(db, slug);
21718
22011
  if (!owner)
21719
22012
  return { ok: false, status: "not_found" };
21720
- await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22013
+ try {
22014
+ await deleteBucketShareObjects(JSON.parse(owner.peer.config), slug);
22015
+ } catch {
22016
+ if (owner.item)
22017
+ primeRemoteShare(db, owner.peer.url, { ...owner.item, lifecycle: "cleanup_pending" });
22018
+ return { ok: false, status: "cleanup_pending" };
22019
+ }
21721
22020
  dropRemoteShare(db, owner.peer.url, slug);
21722
22021
  return { ok: true, status: "revoked" };
21723
22022
  }
@@ -21739,7 +22038,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21739
22038
  throw new MhError("not_found", `no such object-storage share: ${slug}`);
21740
22039
  const { peer: p } = owner;
21741
22040
  const config = JSON.parse(p.config);
21742
- const base = viewerBase(db, viewerBaseOverride);
22041
+ const base = viewerBase2(db, viewerBaseOverride);
21743
22042
  const out = opts?.refreshContent ? await renewBucketShare(db, config, slug) : await represignBucketShare(config, slug);
21744
22043
  if (owner.item)
21745
22044
  primeRemoteShare(db, p.url, {
@@ -21754,7 +22053,7 @@ async function renewShareAction(db, slug, viewerBaseOverride, opts) {
21754
22053
  permission: "view",
21755
22054
  transport: "s3",
21756
22055
  hosting: "s3",
21757
- url: bucketLink(base, out.manifestUrl, out.keyB64, out.saltB64),
22056
+ url: await bucketShareUrl(config, slug, base, out),
21758
22057
  expiresAt: out.presignExp,
21759
22058
  source: `\u6876 ${p.label ?? p.url}`
21760
22059
  };
@@ -22553,7 +22852,7 @@ var revoke = defineCommand({
22553
22852
  return;
22554
22853
  }
22555
22854
  const result = await revokeShareAction(db, args.slug);
22556
- print({ ...result, revoked: result.ok ? args.slug : null }, () => result.status === "cleanup_pending" ? `revocation pending: Edge has not confirmed room destruction for ${args.slug}` : result.ok ? `revoked ${args.slug}` : `no such share: ${args.slug}`);
22855
+ print({ ...result, revoked: result.ok ? args.slug : null }, () => result.status === "cleanup_pending" ? `revocation pending: remote cleanup has not been confirmed for ${args.slug}; retry revoke` : result.ok ? `revoked ${args.slug}` : `no such share: ${args.slug}`);
22557
22856
  })
22558
22857
  });
22559
22858
  var renew = defineCommand({
@@ -22730,6 +23029,15 @@ var completion_default = defineCommand({
22730
23029
  init_client();
22731
23030
  init_peers();
22732
23031
 
23032
+ // src/core/csv-columns.ts
23033
+ function csvPropertyHeaders(props) {
23034
+ const ids = new Set(props.map((p) => p.id));
23035
+ const counts = new Map;
23036
+ for (const p of props)
23037
+ counts.set(p.name.toLowerCase(), (counts.get(p.name.toLowerCase()) ?? 0) + 1);
23038
+ return props.map((p) => p.name.toLowerCase() === "id" || counts.get(p.name.toLowerCase()) !== 1 || ids.has(p.name) ? p.id : p.name);
23039
+ }
23040
+
22733
23041
  // src/core/sync/files.ts
22734
23042
  init_resolve();
22735
23043
  init_documents();
@@ -22901,10 +23209,10 @@ async function exportDb(db, id, path) {
22901
23209
  }
22902
23210
  const docTitles = props.some((p) => p.type === "doc") ? documentTitleMap(db) : new Map;
22903
23211
  const titlesFor = (p) => p.type === "doc" ? docTitles : relTitles.get(p.config?.database ?? "") ?? new Map;
22904
- const header = ["id", ...props.map((p) => p.name)];
23212
+ const header = ["id", ...csvPropertyHeaders(props)];
22905
23213
  const rows = recs.map((r) => [
22906
23214
  r.id,
22907
- ...props.map((p) => p.type === "relation" || p.type === "doc" ? encodeRelationCell(r.cells[p.id], titlesFor(p)) : cellToString(r.values[p.name]))
23215
+ ...props.map((p) => p.type === "relation" || p.type === "doc" ? encodeRelationCell(r.cells[p.id], titlesFor(p)) : cellToString(r.cells[p.id]))
22908
23216
  ]);
22909
23217
  const csv = toCsv([header, ...rows]);
22910
23218
  await Bun.write(path, csv);
@@ -22921,29 +23229,43 @@ async function importDb(db, id, path) {
22921
23229
  return { direction: "import", kind: "db", id, path, rows: 0 };
22922
23230
  const header = grid[0];
22923
23231
  const idCol = header.indexOf("id");
22924
- const propByName = new Map;
22925
- for (const p of listProperties(db, id))
22926
- if (!propByName.has(p.name))
22927
- propByName.set(p.name, p);
23232
+ if (header.filter((key) => key === "id").length > 1)
23233
+ throw new MhError("invalid_input", "duplicate CSV record id column");
23234
+ const props = listProperties(db, id);
23235
+ const seen = new Set;
23236
+ const columns = header.map((key, col) => {
23237
+ if (col === idCol)
23238
+ return null;
23239
+ const p = resolveData(props, { [key]: null })[0].prop;
23240
+ if (seen.has(p.id))
23241
+ throw new MhError("invalid_input", `duplicate CSV property: ${key}`);
23242
+ seen.add(p.id);
23243
+ return p;
23244
+ });
22928
23245
  let count = 0;
22929
23246
  for (let i = 1;i < grid.length; i++) {
22930
- const cells = grid[i];
22931
- const data = {};
22932
- for (let c = 0;c < header.length; c++) {
22933
- if (c === idCol)
22934
- continue;
22935
- const raw = cells[c];
22936
- if (raw == null || raw === "")
22937
- continue;
22938
- const colType = propByName.get(header[c])?.type;
22939
- data[header[c]] = colType === "relation" || colType === "doc" ? decodeRelationCell(raw) : parseCell(raw);
22940
- }
22941
- const rowId = idCol >= 0 ? cells[idCol]?.trim() : undefined;
22942
- if (rowId && getRecord(db, rowId))
22943
- updateRecord(db, rowId, data);
22944
- else
22945
- createRecord(db, id, data);
22946
- count++;
23247
+ writeTx(db, () => {
23248
+ const cells = grid[i];
23249
+ const data = {};
23250
+ for (let c = 0;c < header.length; c++) {
23251
+ if (c === idCol)
23252
+ continue;
23253
+ const raw = cells[c];
23254
+ if (raw == null || raw === "")
23255
+ continue;
23256
+ const colType = columns[c].type;
23257
+ data[columns[c].id] = colType === "relation" || colType === "doc" ? decodeRelationCell(raw) : parseCell(raw);
23258
+ }
23259
+ const rowId = idCol >= 0 ? cells[idCol]?.trim() : undefined;
23260
+ const existing = rowId ? getRecord(db, rowId) : null;
23261
+ if (existing && existing.database_id !== id)
23262
+ throw new MhError("invalid_input", `CSV row ${i + 1}: record ${rowId} belongs to another database`);
23263
+ if (existing)
23264
+ updateRecord(db, rowId, data);
23265
+ else
23266
+ createRecord(db, id, data);
23267
+ count++;
23268
+ });
22947
23269
  }
22948
23270
  return { direction: "import", kind: "db", id, path, rows: count };
22949
23271
  }
@@ -25325,6 +25647,7 @@ async function enableWorkerSubdomain(cfg) {
25325
25647
  }
25326
25648
 
25327
25649
  // src/core/sync/room-serve.ts
25650
+ init_ui_theme();
25328
25651
  init_errors();
25329
25652
  init_http_status();
25330
25653
  init_ids();
@@ -25336,6 +25659,19 @@ init_sites_core();
25336
25659
  init_shares();
25337
25660
  init_room_protocol();
25338
25661
  var ROOM_SESSION_TTL_MS = 12 * 60 * 60 * 1000;
25662
+ var PAGE_CSS = `
25663
+ ${THEME_CSS}
25664
+ *{box-sizing:border-box}
25665
+ 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;
25666
+ min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:24px}
25667
+ main{max-width:420px;width:100%}
25668
+ form.pw{display:flex;flex-direction:column;gap:12px}
25669
+ form.pw input{padding:9px;border:1px solid var(--line);border-radius:7px;background:var(--card);color:var(--fg)}
25670
+ form.pw button{padding:9px;border:0;border-radius:7px;background:var(--accent);color:var(--accent-fg);cursor:pointer}
25671
+ .err{color:var(--danger);font-size:13px;margin:0}
25672
+ .muted{color:var(--muted);font-size:14px}
25673
+ h1{font-size:20px;margin:0 0 10px;text-align:center}
25674
+ footer{margin-top:34px;color:var(--muted);font-size:12px;text-align:center}`;
25339
25675
 
25340
25676
  // src/workers/room.ts
25341
25677
  init_room_protocol();
@@ -25705,6 +26041,7 @@ function disconnectEdge(db) {
25705
26041
  }
25706
26042
 
25707
26043
  // src/core/sync/cf-oauth.ts
26044
+ init_ui_theme();
25708
26045
  init_errors();
25709
26046
  init_page_messages();
25710
26047
  init_e2ee();
@@ -25873,7 +26210,7 @@ async function startCfLogin(opts = {}) {
25873
26210
  }
25874
26211
  };
25875
26212
  }
25876
- var callbackHtml = (locale) => `<!doctype html><html lang="${locale}"><meta charset="utf-8"><title>MetaHub</title>
26213
+ 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>
25877
26214
  <body style="font:15px system-ui;display:grid;place-items:center;height:100vh;margin:0">
25878
26215
  <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>`;
25879
26216
 
@@ -28008,24 +28345,15 @@ function startServer(opts = {}) {
28008
28345
  init_errors();
28009
28346
 
28010
28347
  // src/webui/server/assets.ts
28348
+ init_ui_theme();
28011
28349
  import { fileURLToPath as fileURLToPath2 } from "url";
28012
28350
  import { readdirSync as readdirSync4, statSync as statSync3 } from "fs";
28013
28351
  import { join as join11, dirname as dirname2 } from "path";
28014
28352
 
28015
28353
  // src/webui/styles.css
28016
28354
  var styles_default = ` :root {
28017
- /* Tells browser chrome (status-bar text, form controls, soft keyboard,
28018
- overscroll canvas) which appearance the page renders. Keyed off
28019
- data-resolved, so a manual theme toggle retunes it too. */
28020
- color-scheme: light;
28021
- --bg:#ffffff; --surface:#fbfbfa; --surface-2:#f1f1ef; --sidebar:#f7f7f5;
28022
- --fg:#2c2c30; --fg-soft:#5b5b62; --muted:#75757f; --line:#ebebe8; --line-strong:#dededb;
28023
- --accent:#4a55d6; --accent-fg:#ffffff; --accent-soft:#eef0ff; --danger:#d6473b; --danger-soft:#fdeceb;
28024
- --success:#1f8f3e;
28025
- --hover:rgba(45,45,55,.045); --hover-2:rgba(45,45,55,.08);
28026
- --shadow-sm:0 1px 2px rgba(20,20,40,.06),0 1px 1px rgba(20,20,40,.04);
28027
- --shadow-md:0 4px 12px rgba(20,20,40,.08),0 2px 4px rgba(20,20,40,.05);
28028
- --shadow-lg:0 16px 48px rgba(20,20,40,.16),0 4px 12px rgba(20,20,40,.08);
28355
+ /* Palette comes from core/ui-theme.ts; dimensions and typography stay here. */
28356
+
28029
28357
  --radius:8px; --radius-sm:6px; --radius-lg:14px; --radius-xl:16px;
28030
28358
  --fs-body:14px; --fs-ui:13.5px; --fs-sm:12.5px; --fs-xs:11.5px; --fs-micro:11px;
28031
28359
  --ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
@@ -28033,23 +28361,12 @@ var styles_default = ` :root {
28033
28361
  --kbd-font:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
28034
28362
  --tip-bg:#1f1f23; --tip-fg:#ffffff; --tip-kbd-bg:rgba(255,255,255,.14); --tip-kbd-fg:rgba(255,255,255,.92);
28035
28363
  --note-lh:1.6;
28036
- --code-bg:#f8f8f6;
28037
28364
  --hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
28038
28365
  --hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
28039
28366
  }
28040
- /* Dark palette \u2014 exactly once. JS resolves the user's choice (including
28041
- * "system" \u2192 the OS preference) to <html data-resolved="light|dark"> before
28042
- * first paint (FOUC script in the shell) and on every change (theme.ts), so
28043
- * CSS never needs a prefers-color-scheme duplicate of this block. */
28367
+ /* Dark tooltip and syntax tokens; shared UI colors/elevation live in core/ui-theme.ts. */
28044
28368
  :root[data-resolved="dark"] {
28045
- color-scheme: dark;
28046
- --bg:#1a1a1c; --surface:#202022; --surface-2:#2a2a2d; --sidebar:#171719;
28047
- --fg:#e6e6e9; --fg-soft:#b4b4bb; --muted:#8b8b95; --line:#2c2c30; --line-strong:#3a3a40;
28048
- --accent:#7b86ff; --accent-fg:#14141a; --accent-soft:#23243a; --danger:#f87168; --danger-soft:#341e1c;
28049
- --success:#3fb950;
28050
- --hover:rgba(255,255,255,.05); --hover-2:rgba(255,255,255,.09);
28051
- --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);
28052
- --code-bg:#1d1d20;
28369
+
28053
28370
  --tip-bg:#2e2e32; --tip-fg:var(--fg); --tip-kbd-bg:rgba(255,255,255,.08); --tip-kbd-fg:var(--fg-soft);
28054
28371
  --hl-cmt:#7d7d86; --hl-key:#d291e4; --hl-str:#7ed492; --hl-num:#e0a566;
28055
28372
  --hl-fn:#86a8ff; --hl-type:#5fc6e0; --hl-var:#f0936b;
@@ -28176,7 +28493,7 @@ var styles_default = ` :root {
28176
28493
  .navitem { display:flex; align-items:center; gap:3px; width:100%; text-align:left; padding:5px 6px; margin-bottom:2px; border-radius:var(--radius-sm);
28177
28494
  color:var(--fg-soft); font-size:var(--fs-ui); position:relative; white-space:nowrap; transition:background .1s; }
28178
28495
  .navitem:hover { background:var(--hover-2); }
28179
- .navitem.active { background:var(--bg); color:var(--fg); font-weight:600; box-shadow:var(--shadow-sm); }
28496
+ .navitem.active { background:var(--hover-2); color:var(--fg); font-weight:600; box-shadow:none; }
28180
28497
  .navitem .tw { width:18px; height:18px; display:grid; place-items:center; color:var(--muted); border-radius:4px; flex:none; }
28181
28498
  .navitem .tw:hover { background:var(--hover-2); }
28182
28499
  .navitem .tw svg { width:13px; height:13px; transition:transform .12s; }
@@ -28251,13 +28568,13 @@ var styles_default = ` :root {
28251
28568
  (driven by --mark-y / --mark-h from JS, eased by CSS) */
28252
28569
  .set-rail::before { content:""; position:absolute; left:0; top:4px; bottom:4px;
28253
28570
  width:2px; border-radius:2px; background:var(--line); }
28254
- .set-rail-mark { position:absolute; left:0; width:2px; border-radius:2px; background:var(--accent);
28571
+ .set-rail-mark { position:absolute; left:0; width:2px; border-radius:2px; background:var(--fg-soft);
28255
28572
  height:var(--mark-h,0); transform:translateY(var(--mark-y,0));
28256
28573
  transition:transform .26s cubic-bezier(.4,0,.2,1), height .26s cubic-bezier(.4,0,.2,1); }
28257
28574
  .set-rail-row { display:flex; align-items:center; gap:10px; padding:7px 10px; border-radius:var(--radius-sm);
28258
28575
  color:var(--muted); text-align:left; cursor:pointer; transition:color .14s, background .14s; }
28259
28576
  .set-rail-row:hover { background:var(--hover); color:var(--fg); }
28260
- .set-rail-row.active { color:var(--accent); }
28577
+ .set-rail-row.active { color:var(--fg); }
28261
28578
  .set-rail-ico { flex:none; display:grid; place-items:center; width:16px; height:16px; opacity:.85; }
28262
28579
  .set-rail-ico svg { width:15px; height:15px; }
28263
28580
  .set-rail-label { font-size:var(--fs-sm); font-weight:550; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
@@ -28314,7 +28631,7 @@ var styles_default = ` :root {
28314
28631
  .set-row.dim > .set-row-line { opacity:.62; }
28315
28632
  /* status dot in a caption: the only colour a row carries */
28316
28633
  .cap-dot { display:inline-block; flex:none; width:7px; height:7px; border-radius:50%; background:var(--muted); }
28317
- .cap-dot.ok { background:var(--success); } .cap-dot.warn { background:var(--warning,#9a6700); } .cap-dot.err { background:var(--danger); }
28634
+ .cap-dot.ok { background:var(--success); } .cap-dot.warn { background:var(--warning); } .cap-dot.err { background:var(--danger); }
28318
28635
  .cap-err { color:var(--danger); }
28319
28636
  /* tag variants next to a title: neutral outline (default), accent (roles), self (soft fill) */
28320
28637
  .set-row-title .set-row-tag { margin-left:0; vertical-align:0; flex:none; }
@@ -28385,8 +28702,8 @@ var styles_default = ` :root {
28385
28702
  .audit-actor { flex:none; font-size:11px; font-weight:600; padding:1px 7px; border-radius:999px;
28386
28703
  background:var(--surface); border:1px solid var(--line); color:var(--fg-soft); max-width:110px;
28387
28704
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
28388
- .audit-actor.ai { background:color-mix(in srgb, #8b5cf6 14%, transparent);
28389
- border-color:color-mix(in srgb, #8b5cf6 35%, transparent); color:#8b5cf6; }
28705
+ .audit-actor.ai { background:color-mix(in srgb, var(--brand-purple) 14%, transparent);
28706
+ border-color:color-mix(in srgb, var(--brand-purple) 35%, transparent); color:var(--brand-purple); }
28390
28707
  .audit-summary { font-size:var(--fs-ui); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
28391
28708
  .audit-ent { color:inherit; font-weight:600; text-decoration:none; margin-left:2px; }
28392
28709
  .audit-ent[href]:hover { color:var(--accent); text-decoration:underline; }
@@ -28405,7 +28722,7 @@ var styles_default = ` :root {
28405
28722
  .audit-end { display:flex; align-items:center; gap:12px; margin:18px 0 0; color:var(--muted); font-size:var(--fs-xs); }
28406
28723
  .audit-end::before, .audit-end::after { content:""; flex:1; height:1px; background:var(--line); }
28407
28724
  @media (hover:none) { .audit-revert { opacity:1; } }
28408
- .set-callout.warn { border-color:color-mix(in srgb,var(--warning,#9a6700) 32%,var(--line)); }
28725
+ .set-callout.warn { border-color:color-mix(in srgb,var(--warning) 32%,var(--line)); }
28409
28726
  /* a quiet helper line under a field / chip group */
28410
28727
  .set-hint { font-size:var(--fs-xs); color:var(--muted); line-height:1.5; margin:3px 0 4px; }
28411
28728
  /* progressive disclosure ("\u5DE5\u4F5C\u539F\u7406" / "\u8FDB\u9636") \u2014 native details, themed */
@@ -28424,7 +28741,7 @@ var styles_default = ` :root {
28424
28741
  .provider-chip { padding:6px 12px; border-radius:999px; border:1px solid var(--line-strong); background:var(--surface);
28425
28742
  color:var(--fg-soft); font-size:var(--fs-sm); font-weight:550; transition:border-color .12s, background .12s, color .12s; }
28426
28743
  .provider-chip:hover { border-color:var(--muted); color:var(--fg); }
28427
- .provider-chip.sel { border-color:var(--accent); background:var(--accent-soft); color:var(--accent); box-shadow:0 0 0 1px var(--accent) inset; }
28744
+ .provider-chip.sel { border-color:var(--selected-border); background:var(--selected-bg); color:var(--accent); box-shadow:none; }
28428
28745
  /* ---- \u5173\u4E8E page -----------------------------------------------------------
28429
28746
  Product identity hero over the standard SetSection/SetRow stack. The cube
28430
28747
  mark sizes to a nav-icon's proportions scaled up; the version line reuses
@@ -28464,8 +28781,8 @@ var styles_default = ` :root {
28464
28781
  @keyframes ver-indet { 0% { left:-42%; } 100% { left:100%; } }
28465
28782
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
28466
28783
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
28467
- text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
28468
- background:var(--surface); color:var(--fg-soft); box-shadow:var(--shadow-sm);
28784
+ text-align:left; padding:16px; border:1px solid var(--line); border-radius:var(--radius);
28785
+ background:var(--bg); color:var(--fg-soft); box-shadow:none;
28469
28786
  transition:border-color .14s, background .14s, transform .14s, box-shadow .14s; }
28470
28787
  .theme-card:hover { border-color:var(--muted); transform:translateY(-1px); }
28471
28788
  .theme-card .tc-ico { width:34px; height:34px; display:grid; place-items:center; border-radius:9px;
@@ -28476,8 +28793,10 @@ var styles_default = ` :root {
28476
28793
  .theme-card .tc-check { position:absolute; top:10px; right:10px; width:18px; height:18px; border-radius:50%;
28477
28794
  background:var(--accent); color:var(--accent-fg); display:grid; place-items:center; opacity:0; transform:scale(.6); transition:opacity .14s, transform .14s; }
28478
28795
  .theme-card .tc-check svg { width:12px; height:12px; stroke-width:2.6; }
28479
- .theme-card.sel { border-color:var(--accent); background:var(--accent-soft); box-shadow:0 0 0 1px var(--accent) inset, var(--shadow-sm); }
28480
- .theme-card.sel .tc-ico { background:var(--accent); color:var(--accent-fg); }
28796
+ .theme-card.sel { border-color:var(--selected-border); background:var(--selected-bg); box-shadow:none; }
28797
+ .theme-card.sel:hover { background:var(--selected-hover-bg); border-color:var(--selected-hover-border); }
28798
+ .theme-card.sel .tc-desc { color:var(--selected-muted); }
28799
+ .theme-card.sel .tc-ico { background:var(--selected-icon-bg); color:var(--selected-icon-fg); }
28481
28800
  .theme-card.sel .tc-check { opacity:1; transform:scale(1); }
28482
28801
  @media (max-width:560px) { .theme-grid { grid-template-columns:1fr; } }
28483
28802
  .brand-grid { grid-template-columns:repeat(auto-fit,minmax(min(100%,120px),1fr)); }
@@ -28494,7 +28813,7 @@ var styles_default = ` :root {
28494
28813
  .sync-health-sub { display:inline-flex; align-items:center; gap:8px; max-width:100%; min-width:0; }
28495
28814
  .sync-health-dot { flex:none; width:8px; height:8px; border-radius:50%; }
28496
28815
  .sync-health-dot.dot-ok { background:var(--success); }
28497
- .sync-health-dot.dot-warn { background:var(--warning,#9a6700); }
28816
+ .sync-health-dot.dot-warn { background:var(--warning); }
28498
28817
  .sync-health-dot.dot-error { background:var(--danger); }
28499
28818
  /* shared expander chevron (bucket + device roster rows) */
28500
28819
  .row-chev { flex:none; color:var(--muted); display:flex; transition:transform .15s; }
@@ -28591,7 +28910,7 @@ var styles_default = ` :root {
28591
28910
  .btn-secondary:hover { background:var(--surface-2); }
28592
28911
  .btn-primary { background:var(--accent); color:var(--accent-fg); box-shadow:var(--shadow-sm); }
28593
28912
  .btn-primary:hover { filter:brightness(1.07); }
28594
- .btn-danger { background:var(--danger); color:#fff; } .btn-danger:hover { filter:brightness(1.05); }
28913
+ .btn-danger { background:var(--danger); color:var(--danger-fg); } .btn-danger:hover { filter:brightness(1.05); }
28595
28914
  .btn.sm { padding:3px 8px; font-size:12px; }
28596
28915
  /* Notion-style ambient sync indicator: a quiet ghost control living in the
28597
28916
  topbar icon row. State reads through the *text/icon colour* \u2014 no filled pill
@@ -29464,7 +29783,7 @@ var styles_default = ` :root {
29464
29783
  .shv-ico svg { width:19px; height:19px; }
29465
29784
  .shv-ico.k-doc { color:var(--accent); background:var(--accent-soft); }
29466
29785
  .shv-ico.k-database { color:var(--fg-soft); }
29467
- .shv-ico.k-site { color:#1a7f5a; }
29786
+ .shv-ico.k-site { color:var(--brand-teal); }
29468
29787
  .shv-main { min-width:0; }
29469
29788
  .shv-title { display:block; max-width:100%; font-weight:600; color:var(--fg); letter-spacing:-.01em;
29470
29789
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-align:left; }
@@ -29491,8 +29810,8 @@ var styles_default = ` :root {
29491
29810
  font-variant-numeric:tabular-nums; white-space:nowrap; }
29492
29811
  .shv-dot { width:8px; height:8px; border-radius:50%; background:var(--muted); flex:none; }
29493
29812
  .shv-status.tone-ok .shv-dot { background:var(--success); }
29494
- .shv-status.tone-warn { color:var(--warning,#9a6700); }
29495
- .shv-status.tone-warn .shv-dot { background:var(--warning,#9a6700); }
29813
+ .shv-status.tone-warn { color:var(--warning); }
29814
+ .shv-status.tone-warn .shv-dot { background:var(--warning); }
29496
29815
  .shv-status.tone-muted { color:var(--muted); }
29497
29816
  .shv-status.tone-muted .shv-dot { background:transparent; border:1.5px solid var(--line-strong); }
29498
29817
  .shv-status .sync-ring { width:10px; height:10px; }
@@ -29577,6 +29896,73 @@ var styles_default = ` :root {
29577
29896
  .shv-group-head .btn { margin-left:0; width:100%; justify-content:center; padding:7px 10px; }
29578
29897
  }
29579
29898
 
29899
+ /* Share workspace: quiet Notion-like controls and aligned document rows. */
29900
+ .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; }
29901
+ .shares-page .db-head { margin-bottom:28px; }
29902
+ .shares-page .db-title { display:flex; align-items:center; gap:14px; margin:0; font-size:32px; line-height:1.35; }
29903
+ .shares-page .db-title > .ico { width:27px; height:27px; flex:none; }
29904
+ .shares-page .db-desc { max-width:none; margin-top:12px; line-height:1.7; }
29905
+ .shv-toolbar { margin-bottom:20px; padding:8px 0; }
29906
+ .shv-tools-right { display:flex; width:100%; gap:24px; margin:0; }
29907
+ .shv-seg { gap:8px; border:0; padding:0; background:none; flex:none; }
29908
+ .shv-seg-btn { min-height:34px; padding:7px 12px; border-radius:6px; gap:3px; transition:background .15s; }
29909
+ .shv-seg-btn:hover { background:var(--shv-quiet); }
29910
+ .shv-seg-btn.active { background:color-mix(in srgb,var(--fg) 6%,var(--bg)); box-shadow:none; }
29911
+ .shv-seg-btn .n { font-family:inherit; min-width:1ch; color:var(--fg-soft); }
29912
+ .shv-search { margin-left:auto; max-width:300px; padding:8px 12px; background:var(--shv-quiet); border:1px solid transparent; border-radius:7px; }
29913
+ .shv-search input::placeholder { color:var(--fg-soft); }
29914
+ .shv-search:focus-within { border-color:var(--accent); }
29915
+ .shv-src.btn { background:none; border-color:transparent; box-shadow:none; padding:7px 4px; white-space:nowrap; }
29916
+ .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; }
29917
+ .shv-list { gap:0; }
29918
+ .shv-list[hidden] { display:none; }
29919
+ .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; }
29920
+ .shv-row:hover { background:color-mix(in srgb,var(--fg) 2%,var(--bg)); box-shadow:none; border-color:var(--shv-rule); }
29921
+ .shv-ico, .shv-ico.k-doc, .shv-ico.k-site { width:28px; height:32px; border-radius:0; background:none; color:var(--fg-soft); }
29922
+ .shv-ico svg { width:24px; height:24px; stroke-width:1.5; }
29923
+ .shv-title { font-size:15px; font-weight:550; line-height:1.5; }
29924
+ .shv-sub { margin-top:5px; font-size:12px; color:var(--fg-soft); }
29925
+ .shv-status { justify-self:start; min-width:0; padding:5px 8px; gap:6px; border-radius:5px; font-size:12px; }
29926
+ .shv-status .ico { width:14px; height:14px; flex:none; }
29927
+ .shv-status.tone-ok { color:var(--success); background:color-mix(in srgb,var(--success) 8%,var(--bg)); }
29928
+ .shv-status.tone-warn { background:color-mix(in srgb,var(--warning) 8%,var(--bg)); }
29929
+ .shv-status.tone-muted { background:var(--shv-quiet); color:var(--fg-soft); }
29930
+ .shv-actions { justify-content:space-between; gap:12px; }
29931
+ .shv-actions .btn { min-height:30px; min-width:88px; border-radius:6px; }
29932
+ .shv-actions .shv-copy { background:none; border-color:transparent; box-shadow:none; }
29933
+ .shv-actions .ico { width:15px; height:15px; }
29934
+ .shv-group-head { margin:16px 0; font-weight:400; }
29935
+ .shv-group-toggle { display:flex; align-items:center; gap:10px; padding:6px; color:var(--fg); border-radius:6px; }
29936
+ .shv-group-toggle:hover { background:var(--shv-quiet); }
29937
+ .shv-group-toggle .ico { width:14px; height:14px; color:var(--fg-soft); }
29938
+ .shv-group-head .n { font-family:inherit; background:var(--shv-quiet); padding:2px 7px; border-radius:5px; color:var(--fg-soft); }
29939
+ .shv-group-head .btn { background:none; border-color:transparent; box-shadow:none; color:var(--fg-soft); }
29940
+ .shares-page button:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
29941
+ @container (max-width:850px) {
29942
+ .shv-tools-right { flex-wrap:wrap; gap:12px; }
29943
+ .shv-seg { width:100%; }
29944
+ .shv-search { margin-left:0; max-width:none; }
29945
+ .shv-columns { grid-template-columns:minmax(0,1fr) 145px 124px; gap:12px; }
29946
+ .shv-row { grid-template-columns:24px minmax(0,1fr) 145px 124px; gap:12px; }
29947
+ }
29948
+ @container (max-width:600px) {
29949
+ .shv-columns { display:none; }
29950
+ .shv-seg { gap:3px; }
29951
+ .shv-seg-btn { flex:1; min-width:0; justify-content:center; padding:8px 3px; font-size:12px; gap:2px; }
29952
+ .shv-seg-btn .n { margin-left:2px; font-size:11px; }
29953
+ .shv-src { flex:none; }
29954
+ .shv-toolbar { margin-bottom:8px; }
29955
+ .shv-row { grid-template-columns:24px minmax(0,1fr); padding:18px 4px; }
29956
+ .shv-title { white-space:normal; display:block; overflow-wrap:anywhere; }
29957
+ .shv-sub { flex-wrap:wrap; white-space:normal; overflow-wrap:anywhere; }
29958
+ .shv-sub > * { flex-shrink:1; min-width:0; }
29959
+ .shv-status { grid-column:2; }
29960
+ .shv-actions { grid-column:2; justify-content:flex-start; }
29961
+ .shv-actions .btn { flex:none; min-height:36px; }
29962
+ .shv-actions .iconbtn { margin-left:auto; }
29963
+ .shv-group-head .btn { width:auto; margin-left:auto; }
29964
+ }
29965
+
29580
29966
  /* in-context "\u5DF2\u5206\u4EAB" badge on docs / tables / site cards */
29581
29967
  .share-badge { display:inline-grid; place-items:center; width:22px; height:22px; border-radius:6px; color:var(--accent);
29582
29968
  background:var(--accent-soft); flex:none; cursor:pointer; transition:filter .12s; }
@@ -29713,7 +30099,7 @@ var styles_default = ` :root {
29713
30099
  .selbar .cnt { font-size:13px; font-weight:600; margin-right:6px; }
29714
30100
  .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; }
29715
30101
  .selbar button:hover { background:var(--hover-2); color:var(--fg); }
29716
- .selbar .del:hover { background:var(--danger); color:#fff; }
30102
+ .selbar .del:hover { background:var(--danger); color:var(--danger-fg); }
29717
30103
 
29718
30104
  /* record peek */
29719
30105
  .scrim { position:fixed; inset:0; background:rgba(15,15,25,.32); z-index:70; opacity:0; pointer-events:none; transition:opacity .22s; }
@@ -30042,14 +30428,22 @@ var styles_default = ` :root {
30042
30428
  .tmpl .tt { font-weight:600; font-size:var(--fs-ui); } .tmpl .td { font-size:var(--fs-xs); color:var(--muted); margin-top:2px; }
30043
30429
 
30044
30430
  /* toast */
30045
- .toasts { position:fixed; bottom:20px; left:20px; z-index:110; display:flex; flex-direction:column; gap:8px; }
30046
- .toast { background:var(--fg); color:var(--bg); padding:10px 14px; border-radius:9px; font-size:13px; box-shadow:var(--shadow-lg);
30431
+ .toasts { position:fixed; bottom:20px; left:20px; max-width:calc(100vw - 40px); z-index:110; display:flex; flex-direction:column; gap:8px; }
30432
+ .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);
30047
30433
  display:flex; align-items:center; gap:8px; animation:slidein .2s ease; }
30048
30434
  @keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
30049
- .toast.error { background:var(--danger); color:#fff; }
30435
+ .toast.error { background:var(--danger); color:var(--danger-fg); }
30050
30436
  .toast.leaving { animation:toast-out .16s ease-in forwards; pointer-events:none; }
30051
30437
  @keyframes toast-out { to { opacity:0; transform:translateX(-8px); } }
30052
- .toast-msg { min-width:0; }
30438
+ .toast-msg { min-width:0; overflow-wrap:anywhere; }
30439
+ .toast > .ico, .toast-act { flex-shrink:0; }
30440
+ .toast.dragging { animation:none; transition:none; cursor:grabbing; }
30441
+ .toast.leaving.swipe-out { animation:none; transition:transform .16s ease-in, opacity .16s ease-in; }
30442
+ .toast-act:focus-visible { outline:2px solid currentColor; outline-offset:2px; }
30443
+ @media (prefers-reduced-motion:reduce) {
30444
+ .toast, .toast.dragging, .toast.leaving, .toast.leaving.swipe-out { animation:none; transition:none; transform:none; }
30445
+ .toast.leaving { opacity:0; }
30446
+ }
30053
30447
  .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; }
30054
30448
  .toast-act:hover { background:color-mix(in srgb, var(--bg) 28%, transparent); }
30055
30449
 
@@ -30132,6 +30526,93 @@ var styles_default = ` :root {
30132
30526
  .doc-skel { max-width:720px; margin:40px auto; padding:0 24px; }
30133
30527
 
30134
30528
  /* share / publish modal (share-modal.tsx) */
30529
+ /* Hosting-first object sharing: compact link rows and disclosed details. */
30530
+ .modal:has(.share-dialog) { border-radius:12px; }
30531
+ .modal:has(.share-dialog) .modal-head { position:relative; display:flex; align-items:baseline; gap:12px; padding:24px 56px 0 28px; min-width:0; }
30532
+ .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%; }
30533
+ .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; }
30534
+ .modal-close { position:absolute; right:16px; top:18px; color:var(--muted); }
30535
+ .modal:has(.share-dialog) .modal-body { padding:22px 28px 16px; }
30536
+ .modal:has(.share-dialog) .modal-foot { background:var(--bg); padding:12px 28px; justify-content:space-between; gap:12px; }
30537
+ .modal:has(.share-dialog) .modal-foot .btn { min-height:36px; font-weight:500; box-shadow:none; }
30538
+ .share-dialog { display:flex; flex-direction:column; gap:20px; font-size:var(--fs-ui); }
30539
+ .share-host { padding-bottom:20px; border-bottom:1px solid var(--line); }
30540
+ .share-host-label { display:block; margin-bottom:10px; color:var(--muted); font-size:var(--fs-sm); }
30541
+ .share-host-control { display:flex; align-items:center; gap:10px; min-width:0; }
30542
+ .share-host-control > .ico { width:20px; height:20px; color:var(--fg-soft); }
30543
+ .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; }
30544
+ .share-host-behavior { margin:12px 0 0 30px; }
30545
+ .share-host-behavior strong { color:var(--fg-soft); font-size:var(--fs-sm); font-weight:500; }
30546
+ .share-host-behavior p { margin:5px 0 0; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
30547
+ .share-list-heading { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:var(--fs-sm); padding-bottom:8px; }
30548
+ .share-link-list { list-style:none; padding:0; margin:0; }
30549
+ .share-link-list > li + li { border-top:1px solid var(--line); }
30550
+ .share-link-row { display:flex; align-items:center; gap:12px; padding:16px 0; }
30551
+ .share-link-row > .ico { flex:none; width:20px; height:20px; color:var(--fg-soft); }
30552
+ .share-link-info { flex:1; min-width:0; }
30553
+ .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); }
30554
+ .share-link-name > span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
30555
+ .share-link-name > .ico { flex:none; color:var(--muted); width:12px; }
30556
+ .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; }
30557
+ .share-link-meta > span { white-space:nowrap; }
30558
+ .share-link-meta > span + span::before { content:"\xB7"; margin-right:8px; }
30559
+ .share-link-actions { display:flex; align-items:center; gap:6px; flex:none; }
30560
+ .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; }
30561
+ .share-link-actions .iconbtn { width:32px; height:32px; color:var(--muted); }
30562
+ .share-link-detail { margin:0 0 16px 32px; }
30563
+ .share-link-detail > p, .share-link-warning { margin:0 0 12px; color:var(--muted); font-size:var(--fs-sm); line-height:1.65; }
30564
+ .share-link-warning { margin:4px 0 0; }
30565
+ .share-link-name:hover { background:var(--hover); }
30566
+ .share-link-name:focus-visible, .share-secondary:focus-visible { outline:2px solid var(--accent); outline-offset:3px; }
30567
+ @media (max-width:560px) {
30568
+ .share-link-row { flex-wrap:wrap; gap:8px; }
30569
+ .share-link-info { flex-basis:calc(100% - 32px); }
30570
+ .share-link-actions { margin-left:28px; }
30571
+ .share-link-actions .btn, .share-link-actions .iconbtn { min-height:40px; }
30572
+ }
30573
+ .share-facts { margin:0; display:flex; flex-direction:column; gap:12px; }
30574
+ .share-facts > div { display:flex; gap:20px; align-items:baseline; }
30575
+ .share-facts dt { flex:0 0 88px; color:var(--muted); font-size:var(--fs-sm); }
30576
+ .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; }
30577
+ .share-explanation { margin:-6px 0 0; font-size:var(--fs-sm); line-height:1.7; color:var(--muted); }
30578
+ .share-details { color:var(--fg-soft); font-size:var(--fs-sm); }
30579
+ .share-details summary { cursor:pointer; padding:4px 0; line-height:1.5; }
30580
+ .share-details summary:hover { color:var(--fg); }
30581
+ .share-details summary:focus-visible { outline:2px solid var(--accent); outline-offset:4px; border-radius:2px; }
30582
+ .share-details summary::marker { color:var(--muted); font-size:10px; }
30583
+ .share-details summary > span { margin-left:10px; color:var(--muted); font-size:var(--fs-xs); }
30584
+ .share-details > div, .share-details > dl { margin-top:16px; }
30585
+ .share-secondary { display:inline-flex; align-items:center; gap:6px; color:var(--muted); font-size:var(--fs-sm); border-radius:var(--radius-sm); }
30586
+ .share-secondary { padding:6px 0; }
30587
+ .share-secondary:hover { color:var(--fg); }
30588
+ .share-dialog button:disabled, .modal:has(.share-dialog) .modal-foot button:disabled { cursor:default; opacity:.5; }
30589
+ .share-settings { display:flex; flex-direction:column; gap:8px; }
30590
+ .share-setting { display:flex; align-items:center; gap:16px; min-height:38px; }
30591
+ .share-setting > span { flex:0 0 90px; color:var(--fg-soft); font-size:var(--fs-sm); }
30592
+ .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); }
30593
+ .share-setting select:disabled { color:var(--muted); background:var(--surface); opacity:1; }
30594
+ .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); }
30595
+ .share-setting input::placeholder { color:var(--muted); }
30596
+ .share-setting input:focus-visible, .share-url:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
30597
+ .share-dialog > .share-details { border-top:1px solid var(--line); padding-top:12px; }
30598
+ .share-url { display:block; width:100%; margin-top:12px; }
30599
+ .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; }
30600
+ .share-message.error { color:var(--danger); background:var(--danger-soft); }
30601
+ .share-load-error p { margin-top:0; color:var(--muted); }
30602
+ .share-loading { display:grid; gap:14px; padding:4px 0; }
30603
+ .share-loading span { height:14px; background:var(--surface-2); border-radius:3px; }
30604
+ .share-loading span:first-child { width:55%; }
30605
+ .share-loading span:last-child { width:75%; }
30606
+ .share-sr-status { position:absolute; width:1px; height:1px; padding:0; overflow:hidden; clip-path:inset(50%); white-space:nowrap; }
30607
+ @media (max-width:560px) {
30608
+ .modal:has(.share-dialog) .modal-head { padding:22px 52px 0 20px; gap:10px; }
30609
+ .modal:has(.share-dialog) .modal-body { padding:20px 20px 12px; }
30610
+ .modal:has(.share-dialog) .modal-foot { padding:14px 20px; flex-wrap:wrap; }
30611
+ .modal:has(.share-dialog) .modal-foot .btn, .share-secondary { min-height:40px; }
30612
+ .share-setting { gap:12px; }
30613
+ .share-setting > span { flex-basis:80px; }
30614
+ .share-setting select, .share-setting input { min-height:40px; }
30615
+ }
30135
30616
  .mhshare-body { display:flex; flex-direction:column; gap:10px; }
30136
30617
  .mhshare-section { font-size:var(--fs-micro); font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; margin-top:6px; }
30137
30618
  .mhshare-field { display:flex; align-items:center; gap:12px; }
@@ -30709,22 +31190,31 @@ var styles_default = ` :root {
30709
31190
  /* File-editor window (desktop, loaded at #file?path=\u2026 \u2014 the .txt/.md "open
30710
31191
  with" surface). Same shell idiom as quicknote: top bar = drag region with
30711
31192
  opted-out buttons, single full-bleed column, popups clamped below the bar. */
30712
- body.file-window { --pop-top-inset:46px; }
31193
+ body.file-window { --pop-top-inset:48px; }
30713
31194
  body.file-window #app { display:block; width:100vw; height:100vh; height:100dvh; }
30714
31195
  .fw { display:flex; flex-direction:column; width:100%; height:100vh; height:100dvh; overflow:hidden; background:var(--bg); }
30715
- .fw-bar { display:flex; align-items:center; gap:8px; padding:7px 12px;
31196
+ .fw-bar { display:flex; align-items:center; gap:12px; padding:0 16px; height:48px; flex:none;
30716
31197
  user-select:none; -webkit-app-region:drag;
30717
31198
  background:var(--surface); border-bottom:1px solid var(--line); }
30718
31199
  /* macOS only: clear the inset traffic lights (frame is native elsewhere). */
30719
- body.desktop-mac .fw-bar { padding-left:84px; }
30720
- .fw-name { flex:1; min-width:0; font-size:13px; font-weight:600; color:var(--fg-soft);
31200
+ body.desktop-mac .fw-bar { padding-left:88px; }
31201
+ .fw-title { display:flex; align-items:center; gap:8px; flex:1; min-width:0; color:var(--muted); }
31202
+ .fw-name { min-width:0; font-size:var(--fs-sm); font-weight:500; color:var(--fg-soft);
30721
31203
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
30722
- .fw-dirty { color:var(--accent); font-size:15px; }
30723
- .fw-actions { display:flex; align-items:center; gap:6px; -webkit-app-region:no-drag; }
30724
- .fw-actions .btn { padding:4px 10px; font-size:12px; }
31204
+ .fw-dirty { width:6px; height:6px; flex:none; border-radius:50%; background:var(--accent); }
31205
+ .fw-status { display:flex; align-items:center; gap:6px; flex:none; color:var(--muted); font-size:var(--fs-xs); white-space:nowrap; }
31206
+ .fw-status:not(.is-dirty) .ico { color:var(--success); }
31207
+ .fw-actions { display:flex; align-items:center; gap:6px; flex:none; -webkit-app-region:no-drag; }
31208
+ .fw-actions .btn { min-height:30px; font-size:var(--fs-sm); }
31209
+ .fw-body .doc { padding-top:64px; padding-bottom:24vh; }
30725
31210
  .fw-body { flex:1; overflow:auto; scrollbar-gutter:stable; scrollbar-width:thin; -webkit-app-region:no-drag; }
30726
- /* Bar is ~45px, not the main ~50px topbar the find bar assumes. */
30727
- body.file-window .find-bar { top:52px; }
31211
+ body.file-window .find-bar { top:54px; }
31212
+ @media (max-width:720px) {
31213
+ .fw-bar { gap:8px; padding-right:12px; padding-left:12px; }
31214
+ .fw-status span, .fw-mode span, .fw-open span { display:none; }
31215
+ .fw-mode, .fw-open { width:30px; padding:0; justify-content:center; }
31216
+ .fw-body .doc { padding:36px 32px 24vh; }
31217
+ }
30728
31218
 
30729
31219
  /* No-origin bootstrap: enroll screen + hydration splash (data-blind shell). */
30730
31220
  .enroll-splash { min-height:100dvh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }
@@ -30949,6 +31439,37 @@ var styles_default = ` :root {
30949
31439
  .blob-mgr-row { grid-template-columns:26px 30px minmax(0,1fr) auto auto 28px; }
30950
31440
  .blob-mgr-age { display:none; }
30951
31441
  }
31442
+
31443
+ /* Standalone library document: the same editor, with a compact native shell. */
31444
+ .document-window { min-width:0; height:100dvh; }
31445
+ .document-window .dw-toolbar { padding:0 16px; height:48px; min-height:48px; flex:none;
31446
+ flex-wrap:nowrap; gap:12px; background:var(--surface); -webkit-app-region:drag; }
31447
+ body.desktop-mac .document-window .dw-toolbar { padding-left:88px; }
31448
+ .document-window .dw-toolbar button { -webkit-app-region:no-drag; }
31449
+ .dw-toolbar > button, .dw-toolbar .crumb > .ico { flex:none; }
31450
+ .dw-toolbar .crumb { min-width:0; color:var(--muted); font-size:var(--fs-sm); }
31451
+ .dw-path { display:flex; align-items:center; gap:8px; min-width:0; }
31452
+ .dw-path .crumb-link { max-width:160px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
31453
+ .dw-current { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; color:var(--fg-soft); }
31454
+ .dw-save { display:flex; align-items:center; gap:6px; flex:none; font-size:var(--fs-xs); color:var(--muted);
31455
+ padding:6px 4px; border-radius:var(--radius-sm); }
31456
+ .dw-save:not(:disabled):hover { background:var(--hover); }
31457
+ .dw-save-saved .ico { color:var(--success); }
31458
+ .dw-save-error, .dw-save-conflict { color:var(--danger); }
31459
+ .dw-content .doc { padding-top:64px; padding-bottom:24vh; }
31460
+ .dw-content .doc-title { overflow-wrap:anywhere; }
31461
+ .dw-footer { display:flex; align-items:center; height:30px; flex:none; padding:0 18px;
31462
+ border-top:1px solid var(--line); color:var(--muted); font-size:var(--fs-xs); }
31463
+ .document-window .doc-wc { bottom:5px; right:16px; border:0; box-shadow:none; background:transparent; }
31464
+ .document-window .doc-toc { top:72px; }
31465
+ .document-window .find-bar { top:54px; }
31466
+ @media (max-width:720px) {
31467
+ .dw-save span { display:none; }
31468
+ .dw-path { display:none; }
31469
+ .document-window .dw-toolbar { gap:8px; padding:0 12px; }
31470
+ .dw-content .doc { padding:36px 32px 24vh; }
31471
+ .dw-content .doc-title { font-size:32px; }
31472
+ }
30952
31473
  `;
30953
31474
 
30954
31475
  // src/webui/server/icons.ts
@@ -31007,7 +31528,7 @@ var HTML2 = `<!doctype html>
31007
31528
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
31008
31529
  <!-- Mobile browser chrome (status/address bar) tints to theme-color; app.tsx
31009
31530
  keeps #theme-color-meta in sync with the active theme + visible surface. -->
31010
- <meta name="theme-color" id="theme-color-meta" content="#ffffff" />
31531
+ <meta name="theme-color" id="theme-color-meta" content="${LIGHT_THEME.bg}" />
31011
31532
  <meta name="apple-mobile-web-app-capable" content="yes" />
31012
31533
  <meta name="apple-mobile-web-app-status-bar-style" content="default" />
31013
31534
  <title>Metahub</title>
@@ -31022,9 +31543,10 @@ var HTML2 = `<!doctype html>
31022
31543
  <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>
31023
31544
  <!-- FOUC guard: resolve the stored choice (system \u2192 OS preference) to an
31024
31545
  explicit data-resolved before first paint. CSS keys off data-resolved
31025
- alone, so the dark palette exists exactly once \u2014 see styles.css. Dark also
31546
+ alone, so the dark palette exists exactly once \u2014 see core/ui-theme.ts. Dark also
31026
31547
  pre-tints the status bar (hex mirrors --bg) so the first frame isn't white. -->
31027
- <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>
31548
+ <style>${THEME_CSS}html,body{background:var(--bg);color:var(--fg)}</style>
31549
+ <script>${THEME_INIT_SCRIPT}</script>
31028
31550
  <!-- Same for the UI language: mirror src/webui/i18n/locale.ts so <html lang>
31029
31551
  is right before the bundle runs. -->
31030
31552
  <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>
@@ -31132,9 +31654,9 @@ var getRuntime = bundleGetter({ key: "runtime", entry: "../runtime.ts", dist: "m
31132
31654
  var getSdk = bundleGetter({ key: "sdk", entry: "../../sdk/client.ts", dist: "metahub-sdk.js", extraDirs: ["../../sdk"] });
31133
31655
  async function getCss() {
31134
31656
  if (RUNNING_FROM_SOURCE2) {
31135
- return Bun.file(fileURLToPath2(new URL("../styles.css", import.meta.url))).text();
31657
+ return THEME_CSS + await Bun.file(fileURLToPath2(new URL("../styles.css", import.meta.url))).text();
31136
31658
  }
31137
- return styles_default;
31659
+ return THEME_CSS + styles_default;
31138
31660
  }
31139
31661
  async function getJsStamped() {
31140
31662
  return (await getJs()).replaceAll("__MH_WEBUI_VERSION__", package_default.version);
@@ -31164,8 +31686,8 @@ var MANIFEST = JSON.stringify({
31164
31686
  start_url: "/",
31165
31687
  scope: "/",
31166
31688
  display: "standalone",
31167
- background_color: "#ffffff",
31168
- theme_color: "#ffffff",
31689
+ background_color: LIGHT_THEME.bg,
31690
+ theme_color: LIGHT_THEME.bg,
31169
31691
  icons: [
31170
31692
  { src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
31171
31693
  { src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
@@ -33887,4 +34409,4 @@ if (scopedConfigHelp) {
33887
34409
  runMain(main, { showUsage: showUsage2 });
33888
34410
  }
33889
34411
 
33890
- //# debugId=11E0EB61DE3E1BC764756E2164756E21
34412
+ //# debugId=D3711020F39376D264756E2164756E21