@tensorix/metahub 0.2.2 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/cli.js +1195 -267
  2. package/dist/cli.js.map +19 -15
  3. package/dist/core/databases.d.ts +5 -1
  4. package/dist/core/databases.d.ts.map +1 -1
  5. package/dist/core/documents.d.ts +19 -0
  6. package/dist/core/documents.d.ts.map +1 -1
  7. package/dist/core/md/grammar.d.ts +40 -0
  8. package/dist/core/md/grammar.d.ts.map +1 -0
  9. package/dist/core/md/heal.d.ts +2 -0
  10. package/dist/core/md/heal.d.ts.map +1 -0
  11. package/dist/core/md/inline.d.ts +20 -0
  12. package/dist/core/md/inline.d.ts.map +1 -0
  13. package/dist/core/schema-init.d.ts +13 -0
  14. package/dist/core/schema-init.d.ts.map +1 -1
  15. package/dist/core/schema.d.ts +1 -1
  16. package/dist/core/schema.d.ts.map +1 -1
  17. package/dist/core/sync/auth.d.ts +3 -1
  18. package/dist/core/sync/auth.d.ts.map +1 -1
  19. package/dist/core/sync/share-render.d.ts +3 -1
  20. package/dist/core/sync/share-render.d.ts.map +1 -1
  21. package/dist/db-worker.js +51 -41
  22. package/dist/index.js +1094 -254
  23. package/dist/index.js.map +15 -11
  24. package/dist/mh-runtime.js +1 -1
  25. package/dist/sw.js +1 -1
  26. package/dist/webui/fmt/manifest.d.ts +17 -0
  27. package/dist/webui/fmt/manifest.d.ts.map +1 -0
  28. package/dist/webui/server/assets.d.ts +5 -0
  29. package/dist/webui/server/assets.d.ts.map +1 -1
  30. package/dist/webui-fmt-clang.js +1 -0
  31. package/dist/webui-fmt-clang.wasm +0 -0
  32. package/dist/webui-fmt-gofmt.js +1 -0
  33. package/dist/webui-fmt-gofmt.wasm +0 -0
  34. package/dist/webui-fmt-lua.js +2 -0
  35. package/dist/webui-fmt-lua.wasm +0 -0
  36. package/dist/webui-fmt-ruff.js +2 -0
  37. package/dist/webui-fmt-ruff.wasm +0 -0
  38. package/dist/webui-fmt-sh.js +8 -0
  39. package/dist/webui-fmt-taplo.js +2 -0
  40. package/dist/webui-fmt-taplo.wasm +0 -0
  41. package/dist/webui-fmt.js +425 -0
  42. package/dist/webui.js +148 -75
  43. package/package.json +15 -1
package/dist/index.js CHANGED
@@ -395,7 +395,7 @@ var init_crdt = __esm(() => {
395
395
  DOMAIN = {
396
396
  databases: {
397
397
  table: "databases",
398
- cols: new Set(["name", "icon", "created_hlc", "__deleted"])
398
+ cols: new Set(["name", "icon", "meta", "created_hlc", "__deleted"])
399
399
  },
400
400
  properties: {
401
401
  table: "properties",
@@ -576,11 +576,17 @@ var init_properties = __esm(() => {
576
576
  });
577
577
 
578
578
  // src/core/databases.ts
579
+ function rowOut(r) {
580
+ if (!r)
581
+ return null;
582
+ return { ...r, meta: r.meta === null ? null : JSON.parse(r.meta) };
583
+ }
579
584
  function getDatabase(db, id) {
580
- return db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE id = ? AND __deleted = 0`).get(id);
585
+ return rowOut(db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE id = ? AND __deleted = 0`).get(id));
581
586
  }
582
587
  function listDatabases(db) {
583
- return db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE __deleted = 0 ORDER BY created_hlc`).all();
588
+ const rows = db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE __deleted = 0 ORDER BY created_hlc`).all();
589
+ return rows.map((r) => rowOut(r));
584
590
  }
585
591
  function liveChildren(db, table, col, parentId) {
586
592
  return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r) => r.id);
@@ -592,7 +598,7 @@ var init_databases = __esm(() => {
592
598
  init_errors();
593
599
  init_properties();
594
600
  init_records();
595
- DATABASE_COLS = ["id", "name", "icon", "created_hlc"];
601
+ DATABASE_COLS = ["id", "name", "icon", "meta", "created_hlc"];
596
602
  DATABASE_SELECT = DATABASE_COLS.join(", ");
597
603
  createDatabase = grouped(function createDatabase2(db, opts) {
598
604
  const id = newId("db", opts.name);
@@ -609,6 +615,11 @@ var init_databases = __esm(() => {
609
615
  emit(db, "databases", id, "name", fields.name);
610
616
  if (fields.icon !== undefined)
611
617
  emit(db, "databases", id, "icon", fields.icon);
618
+ if (fields.meta !== undefined) {
619
+ if (fields.meta !== null && (typeof fields.meta !== "object" || Array.isArray(fields.meta)))
620
+ throw new MhError("invalid_input", "meta must be a JSON object or null");
621
+ emit(db, "databases", id, "meta", fields.meta);
622
+ }
612
623
  return getDatabase(db, id);
613
624
  });
614
625
  duplicateDatabase = grouped(function duplicateDatabase2(db, id, opts = {}) {
@@ -619,6 +630,11 @@ var init_databases = __esm(() => {
619
630
  name: opts.name ?? src.name,
620
631
  icon: opts.icon ?? src.icon ?? undefined
621
632
  });
633
+ if (src.meta !== null) {
634
+ const { collapsed: _hidden, ...meta } = src.meta;
635
+ if (Object.keys(meta).length)
636
+ emit(db, "databases", dup.id, "meta", meta);
637
+ }
622
638
  const propIdMap = new Map;
623
639
  for (const p of listProperties(db, id)) {
624
640
  const config = p.config?.database === id ? { ...p.config, database: dup.id } : p.config ?? undefined;
@@ -1259,7 +1275,7 @@ function countOccurrences(haystack, needle) {
1259
1275
  }
1260
1276
  return n;
1261
1277
  }
1262
- var DOCUMENT_COLS, DOCUMENT_SELECT, DOC_SUMMARY_COLS, DOC_SUMMARY_SELECT, createDocument, ORDER_BY = "ORDER BY order_key IS NULL, order_key, created_hlc, id", backfillDocumentOrderKeys, moveDocument, duplicateDocument, updateDocument, editDocument, appendDocument, prependDocument, deleteDocument;
1278
+ var DOCUMENT_COLS, DOCUMENT_SELECT, DOC_SUMMARY_COLS, DOC_SUMMARY_SELECT, createDocument, ORDER_BY = "ORDER BY order_key IS NULL, order_key, created_hlc, id", backfillDocumentOrderKeys, moveDocument, duplicateDocument, updateDocument, editDocument, editDocumentBatch, appendDocument, prependDocument, deleteDocument;
1263
1279
  var init_documents = __esm(() => {
1264
1280
  init_ids();
1265
1281
  init_crdt();
@@ -1389,6 +1405,33 @@ var init_documents = __esm(() => {
1389
1405
  reconcileBody(db, id, newBody);
1390
1406
  return { id, changed: newBody !== body, replaced: opts.replaceAll ? count : 1, version: documentVersion(db, id) };
1391
1407
  });
1408
+ editDocumentBatch = grouped(function editDocumentBatch2(db, id, opts) {
1409
+ if (!getDocument(db, id))
1410
+ throw new MhError("not_found", `no such document: ${id}`);
1411
+ if (opts.edits.length === 0)
1412
+ throw new MhError("invalid_input", "no edits given");
1413
+ if (opts.ifMatch !== undefined && documentVersion(db, id) !== opts.ifMatch)
1414
+ throw new MhError("stale", `stale: document changed since ${opts.ifMatch}; re-read first`);
1415
+ ensureBlocks(db, id);
1416
+ const orig = serializeDocBlocks(liveBlocks(db, id).map((b) => ({ text: b.text, blankAfter: b.blank_after })));
1417
+ let body = orig;
1418
+ let total = 0;
1419
+ opts.edits.forEach((e, i) => {
1420
+ if (e.old === "")
1421
+ throw new MhError("invalid_input", `edit[${i}]: old must not be empty`);
1422
+ const count = countOccurrences(body, e.old);
1423
+ if (count === 0)
1424
+ throw new MhError("invalid_input", `edit[${i}]: anchor not found: no match for old`);
1425
+ if (count > 1 && !e.replaceAll)
1426
+ throw new MhError("ambiguous", `edit[${i}]: ${count} matches for old; add surrounding context or set replaceAll`);
1427
+ const repl = e.new ?? "";
1428
+ body = e.replaceAll ? body.replaceAll(e.old, repl) : body.replace(e.old, repl);
1429
+ total += e.replaceAll ? count : 1;
1430
+ });
1431
+ if (body !== orig)
1432
+ reconcileBody(db, id, body);
1433
+ return { id, changed: body !== orig, replaced: total, version: documentVersion(db, id) };
1434
+ });
1392
1435
  appendDocument = grouped(function appendDocument2(db, id, body) {
1393
1436
  if (!getDocument(db, id))
1394
1437
  throw new MhError("not_found", `no such document: ${id}`);
@@ -18759,29 +18802,280 @@ var init_shares = __esm(() => {
18759
18802
  SHARE_KINDS = new Set(["doc", "database", "site"]);
18760
18803
  });
18761
18804
 
18805
+ // src/core/md/grammar.ts
18806
+ function matchQuoteLine(text) {
18807
+ const m = text.match(RE.quote);
18808
+ return m ? m[1] : null;
18809
+ }
18810
+ function matchListLine(line, minIndent) {
18811
+ const indent = leadingIndent(line);
18812
+ if (indent < minIndent)
18813
+ return null;
18814
+ const text = stripIndent(line, indent);
18815
+ let m = text.match(/^[-*+]\s+\[([ xX])\][ \t](.*)$/);
18816
+ if (m) {
18817
+ return {
18818
+ indent,
18819
+ type: "todo",
18820
+ checked: m[1].toLowerCase() === "x",
18821
+ content: m[2]
18822
+ };
18823
+ }
18824
+ m = text.match(/^(\d+)[.)][ \t]+(.*)$/);
18825
+ if (m)
18826
+ return { indent, type: "numbered", content: m[2], num: parseInt(m[1], 10) };
18827
+ m = text.match(/^[-*+][ \t]+(.*)$/);
18828
+ if (m)
18829
+ return { indent, type: "bullet", content: m[1] };
18830
+ return null;
18831
+ }
18832
+ function leadingIndent(line) {
18833
+ let indent = 0;
18834
+ for (const ch of line) {
18835
+ if (ch === " ")
18836
+ indent++;
18837
+ else if (ch === "\t")
18838
+ indent += 4;
18839
+ else
18840
+ break;
18841
+ }
18842
+ return indent;
18843
+ }
18844
+ function stripIndent(line, columns) {
18845
+ let indent = 0;
18846
+ let i = 0;
18847
+ for (;i < line.length && indent < columns; i++) {
18848
+ const ch = line[i];
18849
+ if (ch === " ")
18850
+ indent++;
18851
+ else if (ch === "\t")
18852
+ indent += 4;
18853
+ else
18854
+ break;
18855
+ }
18856
+ return line.slice(i);
18857
+ }
18858
+ function isFenceClose(line, char, len) {
18859
+ const trimmed = line.trim();
18860
+ if (!trimmed || trimmed[0] !== char)
18861
+ return false;
18862
+ for (const ch of trimmed) {
18863
+ if (ch !== char)
18864
+ return false;
18865
+ }
18866
+ return trimmed.length >= len;
18867
+ }
18868
+ function cleanLang(lang) {
18869
+ return lang.trim().replace(/[^A-Za-z0-9_+.#-]/g, "");
18870
+ }
18871
+ function looksLikeTableAt(lines, i, minIndent) {
18872
+ const head = lines[i];
18873
+ const delim = lines[i + 1];
18874
+ if (head == null || delim == null)
18875
+ return false;
18876
+ if (leadingIndent(head) < minIndent || leadingIndent(delim) < minIndent)
18877
+ return false;
18878
+ const headText = stripIndent(head, minIndent);
18879
+ const delimText = stripIndent(delim, minIndent);
18880
+ if (!headText.includes("|") || !delimText.includes("|"))
18881
+ return false;
18882
+ const cells = splitTableRow(delimText);
18883
+ return cells.length > 0 && cells.every((c) => RE_DELIM_CELL.test(c));
18884
+ }
18885
+ function splitTableRow(line) {
18886
+ const cells = [];
18887
+ let cur = "";
18888
+ for (let i = 0;i < line.length; i++) {
18889
+ const ch = line[i];
18890
+ if (ch === "\\" && line[i + 1] === "|") {
18891
+ cur += "|";
18892
+ i++;
18893
+ continue;
18894
+ }
18895
+ if (ch === "|") {
18896
+ cells.push(cur);
18897
+ cur = "";
18898
+ continue;
18899
+ }
18900
+ cur += ch;
18901
+ }
18902
+ cells.push(cur);
18903
+ if (cells.length && cells[0].trim() === "")
18904
+ cells.shift();
18905
+ if (cells.length && cells[cells.length - 1].trim() === "")
18906
+ cells.pop();
18907
+ return cells.map((c) => c.trim());
18908
+ }
18909
+ var HTML_FENCE = "mh-html", RE, RE_DELIM_CELL;
18910
+ var init_grammar = __esm(() => {
18911
+ RE = {
18912
+ fenceOpen: /^\s*(`{3,}|~{3,})\s*([^\s`]*)?.*$/,
18913
+ h: /^(#{1,6})\s+(.*)$/,
18914
+ todo: /^\s*[-*+]\s+\[([ xX])\][ \t](.*)$/,
18915
+ bullet: /^\s*[-*+]\s+(.*)$/,
18916
+ numbered: /^\s*(\d+)[.)]\s+(.*)$/,
18917
+ quote: /^>[ \t](.*)$/,
18918
+ divider: /^\s*(?:-{3,}|\*{3,}|_{3,})\s*$/
18919
+ };
18920
+ RE_DELIM_CELL = /^\s*:?-+:?\s*$/;
18921
+ });
18922
+
18923
+ // src/core/md/inline.ts
18924
+ function tokenizeInline(text) {
18925
+ if (!text)
18926
+ return [];
18927
+ const cands = [];
18928
+ for (const p of PATTERNS) {
18929
+ p.re.lastIndex = 0;
18930
+ let m;
18931
+ while (m = p.re.exec(text)) {
18932
+ const start = m.index;
18933
+ const end = start + m[0].length;
18934
+ let innerFrom;
18935
+ let innerTo;
18936
+ let url2;
18937
+ let alt;
18938
+ if (p.kind === "link") {
18939
+ innerFrom = start + 1;
18940
+ innerTo = start + 1 + m[1].length;
18941
+ url2 = m[2];
18942
+ } else if (p.kind === "image") {
18943
+ innerFrom = start + 2;
18944
+ innerTo = start + 2 + m[1].length;
18945
+ url2 = m[2];
18946
+ alt = m[1];
18947
+ } else {
18948
+ innerFrom = start + p.delim;
18949
+ innerTo = end - p.delim;
18950
+ if (innerTo <= innerFrom)
18951
+ continue;
18952
+ }
18953
+ cands.push({ kind: p.kind, start, end, innerFrom, innerTo, url: url2, alt, rank: p.rank });
18954
+ }
18955
+ }
18956
+ cands.sort((a, b) => a.start - b.start || a.rank - b.rank || b.end - a.end);
18957
+ const out = [];
18958
+ let cursor = 0;
18959
+ for (const c of cands) {
18960
+ if (c.start < cursor)
18961
+ continue;
18962
+ cursor = c.end;
18963
+ out.push({
18964
+ kind: c.kind,
18965
+ start: c.start,
18966
+ end: c.end,
18967
+ innerFrom: c.innerFrom,
18968
+ innerTo: c.innerTo,
18969
+ ...c.url !== undefined ? { url: c.url } : {},
18970
+ ...c.alt !== undefined ? { alt: c.alt } : {}
18971
+ });
18972
+ }
18973
+ return out;
18974
+ }
18975
+ var PATTERNS;
18976
+ var init_inline = __esm(() => {
18977
+ PATTERNS = [
18978
+ { kind: "code", re: /(?<!\\)`([^`\n]+)`/g, delim: 1, rank: 0 },
18979
+ { kind: "image", re: /(?<!\\)!\[([^\]\n]*)\]\(([^)\s]+)\)/g, delim: 0, rank: 1 },
18980
+ { kind: "strong", re: /(?<!\\)\*\*([^*\n]+?)(?<!\\)\*\*/g, delim: 2, rank: 2 },
18981
+ { kind: "strong", re: /(?<!\\)__([^_\n]+?)(?<!\\)__/g, delim: 2, rank: 2 },
18982
+ { kind: "del", re: /(?<!\\)~~([^~\n]+?)(?<!\\)~~/g, delim: 2, rank: 3 },
18983
+ { kind: "em", re: /(?<![*\w])(?<!\\)\*([^*\n]+?)(?<!\\)\*(?!\*)/g, delim: 1, rank: 4 },
18984
+ { kind: "em", re: /(?<![_\w])(?<!\\)_([^_\n]+?)(?<!\\)_(?!_)/g, delim: 1, rank: 4 },
18985
+ { kind: "link", re: /(?<!\\)\[([^\]\n]+)\]\(([^)\s]+)\)/g, delim: 0, rank: 5 }
18986
+ ];
18987
+ });
18988
+
18989
+ // src/core/md/heal.ts
18990
+ function fenceMask(lines) {
18991
+ const mask = new Array(lines.length).fill(false);
18992
+ for (let i = 0;i < lines.length; i++) {
18993
+ const m = lines[i].match(RE.fenceOpen);
18994
+ if (!m)
18995
+ continue;
18996
+ const ch = m[1][0];
18997
+ const len = m[1].length;
18998
+ let close = -1;
18999
+ for (let j = i + 1;j < lines.length; j++) {
19000
+ if (isFenceClose(lines[j], ch, len)) {
19001
+ close = j;
19002
+ break;
19003
+ }
19004
+ }
19005
+ if (close === -1)
19006
+ continue;
19007
+ for (let j = i;j <= close; j++)
19008
+ mask[j] = true;
19009
+ i = close;
19010
+ }
19011
+ return mask;
19012
+ }
19013
+ function healLegacyMarkdown(src) {
19014
+ if (!MAYBE_LEGACY.test(src))
19015
+ return src;
19016
+ const lines = src.split(`
19017
+ `);
19018
+ const inFence = fenceMask(lines);
19019
+ const quoteish = (i) => i >= 0 && i < lines.length && !inFence[i] && matchQuoteLine(lines[i].replace(/^[ \t]*/, "")) !== null;
19020
+ let changed = false;
19021
+ for (let i = 0;i < lines.length; i++) {
19022
+ if (inFence[i])
19023
+ continue;
19024
+ const line = lines[i];
19025
+ if (LEGACY_EMPTY_TODO.test(line)) {
19026
+ lines[i] = line + " ";
19027
+ changed = true;
19028
+ } else if (BARE_QUOTE.test(line) && (quoteish(i - 1) || quoteish(i + 1))) {
19029
+ lines[i] = line + " ";
19030
+ changed = true;
19031
+ }
19032
+ }
19033
+ return changed ? lines.join(`
19034
+ `) : src;
19035
+ }
19036
+ var LEGACY_EMPTY_TODO, BARE_QUOTE, MAYBE_LEGACY;
19037
+ var init_heal = __esm(() => {
19038
+ init_grammar();
19039
+ LEGACY_EMPTY_TODO = /^[ \t]*[-*+][ \t]+\[[ xX]\]$/;
19040
+ BARE_QUOTE = /^[ \t]*>$/;
19041
+ MAYBE_LEGACY = /^[ \t]*(?:[-*+][ \t]+\[[ xX]\]|>)$/m;
19042
+ });
19043
+
18762
19044
  // src/core/sync/share-render.ts
18763
19045
  function escapeHtml(s) {
18764
19046
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
18765
19047
  }
18766
19048
  function renderInline(src, opts = {}) {
18767
- const codes = [];
18768
- let s = src.replace(/`([^`]+)`/g, (_m, c) => {
18769
- codes.push(c);
18770
- return A + (codes.length - 1) + B;
18771
- });
18772
- s = escapeHtml(s);
18773
- s = s.replace(/!\[([^\]]*)\]\(([^)\s]+)\)/g, (_m, alt, u) => {
18774
- const url2 = rewrite(String(u), opts).replace(/"/g, "&quot;");
18775
- return `<img src="${url2}" alt="${escapeHtml(String(alt))}" loading="lazy">`;
18776
- });
18777
- s = s.replace(/\[([^\]]+)\]\(([^)\s]+)\)/g, (_m, t, u) => {
18778
- const url2 = rewrite(String(u), opts).replace(/"/g, "&quot;");
18779
- return `<a href="${url2}" target="_blank" rel="noreferrer noopener">${t}</a>`;
18780
- });
18781
- s = s.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
18782
- s = s.replace(/(^|[^*])\*([^*\n]+)\*/g, "$1<em>$2</em>");
18783
- s = s.replace(RESTORE, (_m, i) => `<code>${escapeHtml(codes[+i] ?? "")}</code>`);
18784
- return s;
19049
+ const tokens = tokenizeInline(src);
19050
+ let out = "";
19051
+ let pos = 0;
19052
+ for (const t of tokens) {
19053
+ out += escapeHtml(src.slice(pos, t.start));
19054
+ const inner = src.slice(t.innerFrom, t.innerTo);
19055
+ const url2 = rewrite(t.url ?? "", opts).replace(/"/g, "&quot;");
19056
+ switch (t.kind) {
19057
+ case "code":
19058
+ out += `<code>${escapeHtml(inner)}</code>`;
19059
+ break;
19060
+ case "strong":
19061
+ out += `<strong>${escapeHtml(inner)}</strong>`;
19062
+ break;
19063
+ case "em":
19064
+ out += `<em>${escapeHtml(inner)}</em>`;
19065
+ break;
19066
+ case "del":
19067
+ out += `<del>${escapeHtml(inner)}</del>`;
19068
+ break;
19069
+ case "link":
19070
+ out += `<a href="${url2}" target="_blank" rel="noreferrer noopener">${escapeHtml(inner)}</a>`;
19071
+ break;
19072
+ case "image":
19073
+ out += `<img src="${url2}" alt="${escapeHtml(t.alt ?? "")}" loading="lazy">`;
19074
+ break;
19075
+ }
19076
+ pos = t.end;
19077
+ }
19078
+ return out + escapeHtml(src.slice(pos));
18785
19079
  }
18786
19080
  function rewrite(url2, opts) {
18787
19081
  if (opts.rewriteBlob && url2.startsWith("/blob/"))
@@ -18789,8 +19083,9 @@ function rewrite(url2, opts) {
18789
19083
  return url2;
18790
19084
  }
18791
19085
  function renderMarkdown(md, opts = {}) {
18792
- const lines = (md ?? "").replace(/\r\n?/g, `
18793
- `).split(`
19086
+ const healed = healLegacyMarkdown((md ?? "").replace(/\r\n?/g, `
19087
+ `));
19088
+ const lines = healed.split(`
18794
19089
  `);
18795
19090
  const out = [];
18796
19091
  let i = 0;
@@ -18803,38 +19098,39 @@ function renderMarkdown(md, opts = {}) {
18803
19098
  };
18804
19099
  while (i < lines.length) {
18805
19100
  const line = lines[i];
18806
- const fence = line.match(FENCE);
19101
+ const fence = line.match(RE.fenceOpen);
18807
19102
  if (fence) {
18808
- flushPara();
18809
19103
  const marker = fence[1][0];
18810
- const info = (fence[2] ?? "").trim();
18811
- const body = [];
18812
- i++;
18813
- while (i < lines.length) {
18814
- const m = lines[i].match(/^\s*(`{3,}|~{3,})\s*$/);
18815
- if (m && m[1][0] === marker) {
18816
- i++;
19104
+ const len = fence[1].length;
19105
+ let close = -1;
19106
+ for (let j = i + 1;j < lines.length; j++) {
19107
+ if (isFenceClose(lines[j], marker, len)) {
19108
+ close = j;
18817
19109
  break;
18818
19110
  }
18819
- body.push(lines[i]);
18820
- i++;
18821
19111
  }
18822
- if (info === "mh-html") {
18823
- out.push(`<iframe class="mh-embed" sandbox="allow-scripts allow-popups" srcdoc="${escapeHtml(body.join(`
19112
+ if (close !== -1) {
19113
+ flushPara();
19114
+ const info = cleanLang(fence[2] ?? "");
19115
+ const body = lines.slice(i + 1, close);
19116
+ i = close + 1;
19117
+ if (info === HTML_FENCE) {
19118
+ out.push(`<iframe class="mh-embed" sandbox="allow-scripts allow-popups" srcdoc="${escapeHtml(body.join(`
18824
19119
  `))}"></iframe>`);
18825
- } else {
18826
- const cls = info ? ` class="language-${escapeHtml(info)}"` : "";
18827
- out.push(`<pre><code${cls}>${escapeHtml(body.join(`
19120
+ } else {
19121
+ const cls = info ? ` class="language-${escapeHtml(info)}"` : "";
19122
+ out.push(`<pre><code${cls}>${escapeHtml(body.join(`
18828
19123
  `))}</code></pre>`);
19124
+ }
19125
+ continue;
18829
19126
  }
18830
- continue;
18831
19127
  }
18832
19128
  if (line.trim() === "") {
18833
19129
  flushPara();
18834
19130
  i++;
18835
19131
  continue;
18836
19132
  }
18837
- const h = line.match(/^(#{1,6})\s+(.*)$/);
19133
+ const h = stripLead(line).match(RE.h);
18838
19134
  if (h) {
18839
19135
  flushPara();
18840
19136
  const level = h[1].length;
@@ -18842,30 +19138,33 @@ function renderMarkdown(md, opts = {}) {
18842
19138
  i++;
18843
19139
  continue;
18844
19140
  }
18845
- if (/^\s*([-*_])(\s*\1){2,}\s*$/.test(line)) {
19141
+ if (RE.divider.test(line)) {
18846
19142
  flushPara();
18847
19143
  out.push("<hr>");
18848
19144
  i++;
18849
19145
  continue;
18850
19146
  }
18851
- if (/^\s*>\s?/.test(line)) {
19147
+ if (matchQuoteLine(stripLead(line)) !== null) {
18852
19148
  flushPara();
18853
19149
  const q = [];
18854
- while (i < lines.length && /^\s*>\s?/.test(lines[i])) {
18855
- q.push(lines[i].replace(/^\s*>\s?/, ""));
19150
+ while (i < lines.length) {
19151
+ const content = matchQuoteLine(stripLead(lines[i]));
19152
+ if (content === null)
19153
+ break;
19154
+ q.push(content);
18856
19155
  i++;
18857
19156
  }
18858
19157
  out.push(`<blockquote>${renderMarkdown(q.join(`
18859
19158
  `), opts)}</blockquote>`);
18860
19159
  continue;
18861
19160
  }
18862
- if (line.includes("|") && i + 1 < lines.length && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].includes("-")) {
19161
+ if (looksLikeTableAt(lines, i, 0)) {
18863
19162
  flushPara();
18864
19163
  const rows = [];
18865
- const head = splitRow(line);
19164
+ const head = splitTableRow(stripLead(lines[i]));
18866
19165
  i += 2;
18867
19166
  while (i < lines.length && lines[i].includes("|") && lines[i].trim() !== "") {
18868
- rows.push(splitRow(lines[i]));
19167
+ rows.push(splitTableRow(stripLead(lines[i])));
18869
19168
  i++;
18870
19169
  }
18871
19170
  const th = head.map((c) => `<th>${renderInline(c, opts)}</th>`).join("");
@@ -18873,19 +19172,25 @@ function renderMarkdown(md, opts = {}) {
18873
19172
  out.push(`<table><thead><tr>${th}</tr></thead><tbody>${body}</tbody></table>`);
18874
19173
  continue;
18875
19174
  }
18876
- const ul = line.match(/^\s*[-*+]\s+/);
18877
- const ol = line.match(/^\s*\d+[.)]\s+/);
18878
- if (ul || ol) {
19175
+ const first = matchListLine(line, 0);
19176
+ if (first) {
18879
19177
  flushPara();
18880
- const ordered = !!ol;
18881
- const items = [];
18882
- const re = ordered ? /^\s*\d+[.)]\s+/ : /^\s*[-*+]\s+/;
18883
- while (i < lines.length && re.test(lines[i])) {
18884
- items.push(lines[i].replace(re, ""));
19178
+ const ordered = first.type === "numbered";
19179
+ const lis = [];
19180
+ while (i < lines.length) {
19181
+ const it = matchListLine(lines[i], 0);
19182
+ if (!it || it.type === "numbered" !== ordered)
19183
+ break;
19184
+ if (it.type === "todo") {
19185
+ lis.push(`<li class="todo"><input type="checkbox" disabled${it.checked ? " checked" : ""}> ${renderInline(it.content, opts)}</li>`);
19186
+ } else if (it.type === "numbered" && it.num !== undefined) {
19187
+ lis.push(`<li value="${it.num}">${renderInline(it.content, opts)}</li>`);
19188
+ } else {
19189
+ lis.push(`<li>${renderInline(it.content, opts)}</li>`);
19190
+ }
18885
19191
  i++;
18886
19192
  }
18887
- const lis = items.map((t) => `<li>${renderInline(t, opts)}</li>`).join("");
18888
- out.push(ordered ? `<ol>${lis}</ol>` : `<ul>${lis}</ul>`);
19193
+ out.push(ordered ? `<ol>${lis.join("")}</ol>` : `<ul>${lis.join("")}</ul>`);
18889
19194
  continue;
18890
19195
  }
18891
19196
  if (/^\s*!\[[^\]]*\]\([^)\s]+\)\s*$/.test(line)) {
@@ -18901,20 +19206,13 @@ function renderMarkdown(md, opts = {}) {
18901
19206
  return out.join(`
18902
19207
  `);
18903
19208
  }
18904
- function splitRow(line) {
18905
- let s = line.trim();
18906
- if (s.startsWith("|"))
18907
- s = s.slice(1);
18908
- if (s.endsWith("|"))
18909
- s = s.slice(0, -1);
18910
- return s.split("|").map((c) => c.trim());
19209
+ function stripLead(line) {
19210
+ return stripIndent(line, Number.MAX_SAFE_INTEGER);
18911
19211
  }
18912
- var A, B, RESTORE, FENCE;
18913
19212
  var init_share_render = __esm(() => {
18914
- A = String.fromCharCode(57344);
18915
- B = String.fromCharCode(57345);
18916
- RESTORE = new RegExp(A + "(\\d+)" + B, "g");
18917
- FENCE = /^\s*(`{3,}|~{3,})(.*)$/;
19213
+ init_grammar();
19214
+ init_inline();
19215
+ init_heal();
18918
19216
  });
18919
19217
 
18920
19218
  // src/core/sync/share-serve.ts
@@ -19509,6 +19807,7 @@ CREATE TABLE IF NOT EXISTS databases (
19509
19807
  id TEXT PRIMARY KEY,
19510
19808
  name TEXT,
19511
19809
  icon TEXT,
19810
+ meta TEXT, -- JSON: generic replicated metadata (e.g. UI flags), one LWW register
19512
19811
  created_hlc TEXT,
19513
19812
  __deleted INTEGER NOT NULL DEFAULT 0
19514
19813
  );
@@ -19791,9 +20090,25 @@ function migrateStoragePeerUrls(db) {
19791
20090
  });
19792
20091
  tx();
19793
20092
  }
20093
+ function migrateDatabases(db) {
20094
+ if (hasColumn(db, "databases", "meta"))
20095
+ return;
20096
+ db.exec("ALTER TABLE databases ADD COLUMN meta TEXT");
20097
+ db.exec(`
20098
+ UPDATE databases SET meta = (
20099
+ SELECT c.value FROM crdt_changes c
20100
+ WHERE c.dataset = 'databases' AND c.col = 'meta' AND c.row_id = databases.id
20101
+ ORDER BY c.hlc DESC LIMIT 1
20102
+ )
20103
+ WHERE id IN (
20104
+ SELECT DISTINCT row_id FROM crdt_changes WHERE dataset = 'databases' AND col = 'meta'
20105
+ )
20106
+ `);
20107
+ }
19794
20108
  function initSchema(db) {
19795
20109
  runSchema(db);
19796
20110
  migrateOplog(db);
20111
+ migrateDatabases(db);
19797
20112
  migrateCrdtChangesSeq(db);
19798
20113
  migrateRecords(db);
19799
20114
  migratePeers(db);
@@ -20033,7 +20348,7 @@ init_config();
20033
20348
  // package.json
20034
20349
  var package_default = {
20035
20350
  name: "@tensorix/metahub",
20036
- version: "0.2.2",
20351
+ version: "0.3.1",
20037
20352
  description: "Metahub core library and CLI",
20038
20353
  license: "AGPL-3.0-only",
20039
20354
  type: "module",
@@ -20063,16 +20378,21 @@ var package_default = {
20063
20378
  "build:shell": "bun run scripts/build-shell.ts",
20064
20379
  "build:share-viewer": "bun run scripts/build-share-viewer.ts",
20065
20380
  "build:binaries": "bun run scripts/compile-binaries.ts",
20381
+ "build:binaries:host": "HOST_ONLY=1 bun run scripts/compile-binaries.ts",
20066
20382
  prepublishOnly: "bun run build",
20067
20383
  test: "bun test",
20068
20384
  release: "bun run scripts/tag-release.ts"
20069
20385
  },
20070
20386
  dependencies: {
20071
20387
  "@clack/prompts": "^1.5.0",
20388
+ "@codemirror/commands": "6.10.4",
20389
+ "@codemirror/state": "6.7.0",
20390
+ "@codemirror/view": "6.43.4",
20072
20391
  "@sqlite.org/sqlite-wasm": "^3.53.0-build1",
20073
20392
  aws4fetch: "^1.0.20",
20074
20393
  citty: "^0.1.6",
20075
20394
  "highlight.js": "^11.11.1",
20395
+ jsqr: "^1.4.0",
20076
20396
  preact: "^10.29.2",
20077
20397
  "qrcode-generator": "^2.0.4",
20078
20398
  turndown: "^7.2.4",
@@ -20081,8 +20401,17 @@ var package_default = {
20081
20401
  },
20082
20402
  devDependencies: {
20083
20403
  "@happy-dom/global-registrator": "^20.10.2",
20404
+ "@prettier/plugin-php": "^0.25.0",
20084
20405
  "@types/bun": "latest",
20085
20406
  "@types/turndown": "^5.0.6",
20407
+ "@wasm-fmt/clang-format": "^22.1.8",
20408
+ "@wasm-fmt/gofmt": "^0.7.3",
20409
+ "@wasm-fmt/lua_fmt": "^0.3.3",
20410
+ "@wasm-fmt/ruff_fmt": "^0.15.20",
20411
+ "@wasm-fmt/taplo_fmt": "^0.2.5",
20412
+ "mvdan-sh": "^0.10.1",
20413
+ prettier: "^3.6",
20414
+ "sql-formatter": "^15",
20086
20415
  typescript: "^5"
20087
20416
  }
20088
20417
  };
@@ -20208,56 +20537,196 @@ function wantsHtml(req) {
20208
20537
  return req.method === "GET" && (req.headers.get("accept") ?? "").includes("text/html");
20209
20538
  }
20210
20539
  var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
20540
+ var 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"/>';
20541
+ var 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"/>';
20542
+ var 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"/>';
20543
+ var UNLOCK_CSS = `
20544
+ :root{color-scheme:light;
20545
+ --bg:#ffffff;--surface:#fbfbfa;--surface-2:#f1f1ef;--fg:#2c2c30;--fg-soft:#5b5b62;
20546
+ --muted:#75757f;--line:#ebebe8;--line-strong:#dededb;--accent:#4a55d6;--accent-fg:#ffffff;
20547
+ --accent-soft:#eef0ff;--danger:#d6473b;--danger-soft:#fdeceb;--code-bg:#f8f8f6}
20548
+ :root[data-resolved="dark"]{color-scheme:dark;
20549
+ --bg:#1a1a1c;--surface:#202022;--surface-2:#2a2a2d;--fg:#e6e6e9;--fg-soft:#b4b4bb;
20550
+ --muted:#8b8b95;--line:#2c2c30;--line-strong:#3a3a40;--accent:#7b86ff;--accent-fg:#14141a;
20551
+ --accent-soft:#23243a;--danger:#f87168;--danger-soft:#341e1c;--code-bg:#26262a}
20552
+ *{box-sizing:border-box}
20553
+ html,body{margin:0;min-height:100%}
20554
+ body{font-family:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
20555
+ color:var(--fg);background:var(--bg);line-height:1.55;
20556
+ display:flex;align-items:center;justify-content:center;min-height:100dvh;
20557
+ padding:calc(24px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right)) calc(24px + env(safe-area-inset-bottom)) calc(20px + env(safe-area-inset-left));
20558
+ -webkit-font-smoothing:antialiased;-webkit-tap-highlight-color:transparent;overflow:hidden}
20559
+ /* Ambient wash: two faint accent blooms behind the card. */
20560
+ body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
20561
+ background:
20562
+ radial-gradient(58vmax 58vmax at 14% -8%,color-mix(in srgb,var(--accent) 13%,transparent),transparent 60%),
20563
+ radial-gradient(50vmax 50vmax at 108% 112%,color-mix(in srgb,var(--accent) 10%,transparent),transparent 55%)}
20564
+ .card{position:relative;z-index:1;width:100%;max-width:420px;background:var(--surface);
20565
+ border:1px solid var(--line);border-radius:18px;padding:32px 26px 26px;
20566
+ box-shadow:0 24px 60px rgba(20,20,40,.14),0 4px 14px rgba(20,20,40,.06);
20567
+ animation:rise .45s cubic-bezier(.2,.7,.3,1) both}
20568
+ @keyframes rise{from{opacity:0;transform:translateY(12px) scale(.985)}to{opacity:1;transform:none}}
20569
+ .brand{width:52px;height:52px;border-radius:14px;display:flex;align-items:center;justify-content:center;
20570
+ background:var(--accent-soft);color:var(--accent);margin-bottom:18px}
20571
+ .brand svg{width:28px;height:28px;fill:none;stroke:currentColor;stroke-width:1.6;
20572
+ stroke-linecap:round;stroke-linejoin:round}
20573
+ .brand path{stroke-dasharray:120;stroke-dashoffset:120;animation:draw .9s .12s ease forwards}
20574
+ @keyframes draw{to{stroke-dashoffset:0}}
20575
+ h1{font-size:21px;font-weight:650;letter-spacing:-.01em;margin:0 0 8px}
20576
+ .sub{color:var(--muted);font-size:13.5px;margin:0 0 22px;line-height:1.6}
20577
+ .sub code{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;font-size:12.5px;
20578
+ background:var(--code-bg);border:1px solid var(--line);border-radius:5px;padding:1px 6px;color:var(--fg-soft);
20579
+ white-space:nowrap}
20580
+ .field{position:relative;display:flex;align-items:center}
20581
+ input{width:100%;height:50px;padding:0 48px 0 15px;font-size:16px; /* 16px: never let iOS Safari zoom on focus */
20582
+ font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;letter-spacing:.01em;
20583
+ color:var(--fg);background:var(--bg);border:1.5px solid var(--line-strong);border-radius:12px;
20584
+ outline:none;transition:border-color .15s,box-shadow .15s}
20585
+ input::placeholder{font-family:var(--ui,inherit);letter-spacing:normal;color:var(--muted);opacity:.8}
20586
+ input:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 22%,transparent)}
20587
+ .card.bad input{border-color:var(--danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 20%,transparent)}
20588
+ .card.bad .field{animation:shake .34s cubic-bezier(.36,.07,.19,.97)}
20589
+ @keyframes shake{10%,90%{transform:translateX(-1px)}20%,80%{transform:translateX(2px)}
20590
+ 30%,50%,70%{transform:translateX(-5px)}40%,60%{transform:translateX(5px)}}
20591
+ .peek{position:absolute;right:6px;width:40px;height:40px;border:0;background:none;cursor:pointer;
20592
+ color:var(--muted);display:flex;align-items:center;justify-content:center;border-radius:9px}
20593
+ .peek:hover{color:var(--fg-soft)}
20594
+ .peek svg{width:19px;height:19px;fill:none;stroke:currentColor;stroke-width:1.7;
20595
+ stroke-linecap:round;stroke-linejoin:round}
20596
+ .err{color:var(--danger);font-size:13px;font-weight:500;margin:11px 2px 0;
20597
+ display:flex;gap:6px;align-items:center;animation:fade .2s ease both}
20598
+ @keyframes fade{from{opacity:0}to{opacity:1}}
20599
+ button.go{width:100%;height:48px;margin-top:20px;border:0;border-radius:12px;cursor:pointer;
20600
+ background:var(--accent);color:var(--accent-fg);font-size:15px;font-weight:600;font-family:inherit;
20601
+ display:flex;align-items:center;justify-content:center;gap:9px;
20602
+ transition:filter .15s,transform .05s}
20603
+ button.go:hover{filter:brightness(1.06)}
20604
+ button.go:active{transform:translateY(1px)}
20605
+ button.go:disabled{opacity:.7;cursor:default}
20606
+ .spin{width:17px;height:17px;border:2px solid color-mix(in srgb,var(--accent-fg) 45%,transparent);
20607
+ border-top-color:var(--accent-fg);border-radius:50%;animation:spin .7s linear infinite}
20608
+ /* Full-page loader shown while a silent renewal is in flight. */
20609
+ .boot{position:fixed;inset:0;z-index:2;display:flex;align-items:center;justify-content:center;background:var(--bg)}
20610
+ .boot svg{width:40px;height:40px;fill:none;stroke:var(--accent);stroke-width:1.4;
20611
+ stroke-linecap:round;stroke-linejoin:round;animation:breathe 1.5s ease-in-out infinite}
20612
+ @keyframes breathe{0%,100%{opacity:.35;transform:scale(.94)}50%{opacity:1;transform:scale(1)}}
20613
+ @keyframes spin{to{transform:rotate(360deg)}}
20614
+ @media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important}
20615
+ .brand path{stroke-dashoffset:0}}
20616
+ `;
20211
20617
  function unlockPage() {
20212
- return `<!doctype html><html lang="en"><head>
20213
- <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
20214
- <title>Unlock</title>
20215
- <style>
20216
- body{font:14px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
20217
- display:flex;min-height:100vh;margin:0;align-items:center;justify-content:center;
20218
- background:#0d1117;color:#e6edf3}
20219
- form{background:#161b22;border:1px solid #30363d;border-radius:10px;padding:28px;width:300px}
20220
- h1{font-size:16px;margin:0 0 14px}
20221
- input{width:100%;box-sizing:border-box;padding:8px;border-radius:6px;border:1px solid #30363d;
20222
- background:#0d1117;color:inherit;margin-bottom:12px}
20223
- button{width:100%;padding:8px;border:0;border-radius:6px;background:#238636;color:#fff;cursor:pointer}
20224
- .err{color:#f85149;margin:0 0 12px;font-size:13px}
20225
- </style></head><body>
20226
- <form id="f" style="display:none">
20227
- <h1>\uD83D\uDD12 This server requires a token</h1>
20228
- <p class="err" id="e" style="display:none">Token rejected \u2014 try again.</p>
20229
- <input id="t" type="password" placeholder="Token" autocomplete="current-password">
20230
- <button type="submit">Unlock</button>
20618
+ return `<!doctype html><html lang="zh-CN"><head>
20619
+ <meta charset="utf-8">
20620
+ <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
20621
+ <meta name="theme-color" content="#ffffff">
20622
+ <title>\u9A8C\u8BC1\u8BBF\u95EE\u4EE4\u724C \xB7 metahub</title>
20623
+ <script>
20624
+ // FOUC-guard theme resolution, same key/logic as src/webui/theme.ts.
20625
+ try {
20626
+ var mt = localStorage.getItem("mh-theme");
20627
+ var dark = mt === "dark" || (mt !== "light" && matchMedia("(prefers-color-scheme: dark)").matches);
20628
+ document.documentElement.dataset.resolved = dark ? "dark" : "light";
20629
+ document.head.querySelector('meta[name=theme-color]').setAttribute("content", dark ? "#1a1a1c" : "#ffffff");
20630
+ } catch (e) {}
20631
+ </script>
20632
+ <style>${UNLOCK_CSS}</style></head><body>
20633
+ <div class="boot" id="boot"><svg viewBox="0 0 24 24">${CUBE_SVG}</svg></div>
20634
+ <form class="card" id="f" style="display:none" autocomplete="on">
20635
+ <div class="brand"><svg viewBox="0 0 24 24">${CUBE_SVG}</svg></div>
20636
+ <h1>\u8F93\u5165\u8BBF\u95EE\u4EE4\u724C</h1>
20637
+ <p class="sub">\u6B64\u670D\u52A1\u5668\u5DF2\u5F00\u542F\u8BBF\u95EE\u4FDD\u62A4\u3002\u5728\u5DF2\u767B\u5F55\u8BBE\u5907\u7684\u300C\u8BBE\u7F6E \u2192 \u8BBE\u5907\u4E0E\u6388\u6743\u300D\u83B7\u53D6\u767B\u5F55\u94FE\u63A5\u6216\u4E8C\u7EF4\u7801,\u6216\u5728\u670D\u52A1\u5668\u4E0A\u8FD0\u884C <code>mh token show</code>\u3002</p>
20638
+ <div class="field">
20639
+ <input id="t" type="password" placeholder="\u7C98\u8D34\u8BBF\u95EE\u4EE4\u724C\u6216\u767B\u5F55\u94FE\u63A5"
20640
+ autocomplete="current-password" autocapitalize="off" autocorrect="off" spellcheck="false" enterkeyhint="go">
20641
+ <button type="button" class="peek" id="peek" aria-label="\u663E\u793A\u4EE4\u724C"><svg viewBox="0 0 24 24">${EYE_SVG}</svg></button>
20642
+ </div>
20643
+ <p class="err" id="e" style="display:none"></p>
20644
+ <button type="submit" class="go" id="go">\u89E3\u9501\u5E76\u8FDB\u5165</button>
20231
20645
  </form>
20232
20646
  <script>
20233
20647
  var KEY = "mh_token";
20648
+ var form = document.getElementById("f"), boot = document.getElementById("boot");
20649
+ var input = document.getElementById("t"), errEl = document.getElementById("e");
20650
+ var goBtn = document.getElementById("go"), peek = document.getElementById("peek");
20651
+
20234
20652
  function save(t) {
20235
20653
  localStorage.setItem(KEY, t);
20236
20654
  document.cookie = "${COOKIE}=" + encodeURIComponent(t) + "; ${COOKIE_ATTRS}" +
20237
20655
  (location.protocol === "https:" ? "; Secure" : "");
20238
20656
  }
20239
- function showForm() {
20240
- document.getElementById("f").style.display = "block";
20241
- // A leftover token means an attempt (or silent renewal) failed the gate.
20242
- if (localStorage.getItem(KEY)) document.getElementById("e").style.display = "block";
20243
- document.getElementById("t").focus();
20244
- }
20245
- document.getElementById("f").addEventListener("submit", function (ev) {
20657
+ // Accept a pasted "\u2026/?token=xxx" login link, not just the bare token.
20658
+ function normalize(v) {
20659
+ v = (v || "").trim();
20660
+ var m = /[?&#]token=([^&\\s]+)/.exec(v);
20661
+ if (m) { try { return decodeURIComponent(m[1]); } catch (e) { return m[1]; } }
20662
+ return v;
20663
+ }
20664
+ function showForm(msg) {
20665
+ boot.style.display = "none";
20666
+ form.style.display = "block";
20667
+ if (msg) { errEl.textContent = msg; errEl.style.display = "flex"; }
20668
+ input.focus();
20669
+ }
20670
+ function fail(msg) {
20671
+ goBtn.disabled = false;
20672
+ goBtn.innerHTML = "\u89E3\u9501\u5E76\u8FDB\u5165";
20673
+ errEl.textContent = msg;
20674
+ errEl.style.display = "flex";
20675
+ form.classList.remove("bad");
20676
+ void form.offsetWidth; // reflow so the shake animation restarts
20677
+ form.classList.add("bad");
20678
+ input.focus();
20679
+ input.select();
20680
+ }
20681
+ peek.addEventListener("click", function () {
20682
+ var pw = input.type === "password";
20683
+ input.type = pw ? "text" : "password";
20684
+ peek.innerHTML = '<svg viewBox="0 0 24 24">' + (pw ? ${JSON.stringify(EYE_OFF_SVG)} : ${JSON.stringify(EYE_SVG)}) + "</svg>";
20685
+ peek.setAttribute("aria-label", pw ? "\u9690\u85CF\u4EE4\u724C" : "\u663E\u793A\u4EE4\u724C");
20686
+ input.focus();
20687
+ });
20688
+ input.addEventListener("input", function () {
20689
+ if (errEl.style.display !== "none") { errEl.style.display = "none"; form.classList.remove("bad"); }
20690
+ });
20691
+
20692
+ form.addEventListener("submit", function (ev) {
20246
20693
  ev.preventDefault();
20247
- save(document.getElementById("t").value);
20248
- location.reload();
20694
+ var token = normalize(input.value);
20695
+ if (!token) { fail("\u8BF7\u8F93\u5165\u8BBF\u95EE\u4EE4\u724C\u3002"); return; }
20696
+ goBtn.disabled = true;
20697
+ goBtn.innerHTML = '<span class="spin"></span>\u9A8C\u8BC1\u4E2D\u2026';
20698
+ var hdr = { headers: { authorization: "Bearer " + token } };
20699
+ // Prefer the renewal endpoint (managed mode returns the canonical token,
20700
+ // also upgrading a pasted in-grace token). Fall back to a cheap
20701
+ // authenticated probe so we can reject a bad token *inline* instead of
20702
+ // storing it and reloading into another gate.
20703
+ fetch("${RENEW_PATH}", hdr)
20704
+ .then(function (r) { return r.ok ? r.json() : null; })
20705
+ .then(function (d) {
20706
+ if (d && d.token) { save(d.token); location.reload(); return; }
20707
+ return fetch("/api/version", hdr).then(function (r) {
20708
+ if (r.status === 401) { fail("\u4EE4\u724C\u65E0\u6548,\u8BF7\u68C0\u67E5\u540E\u91CD\u8BD5\u3002"); return; }
20709
+ // 200 (static-token mode) or no UI (404) \u2192 trust it and reload.
20710
+ save(token); location.reload();
20711
+ });
20712
+ })
20713
+ .catch(function () { save(token); location.reload(); }); // network hiccup \u2192 old behavior
20249
20714
  });
20250
- // Seamless renewal for top-level navigation: if we hold a token, try swapping
20251
- // it for the current one before prompting. Covers the case where the token
20252
- // rotated while this browser held the (in-grace) previous one.
20715
+
20716
+ // Silent renewal on load: a held token may just need swapping for the current
20717
+ // one (rotation while this browser held the in-grace previous). Show the boot
20718
+ // loader meanwhile; drop to the form (with a hint) if it can't be renewed.
20253
20719
  var stored = localStorage.getItem(KEY);
20254
20720
  if (stored) {
20255
20721
  fetch("${RENEW_PATH}", { headers: { authorization: "Bearer " + stored } })
20256
20722
  .then(function (r) { return r.ok ? r.json() : null; })
20257
- .then(function (d) { if (d && d.token) { save(d.token); location.reload(); } else { showForm(); } })
20258
- .catch(showForm);
20723
+ .then(function (d) {
20724
+ if (d && d.token) { save(d.token); location.reload(); }
20725
+ else showForm("\u767B\u5F55\u5DF2\u8FC7\u671F,\u8BF7\u91CD\u65B0\u8F93\u5165\u4EE4\u724C\u3002");
20726
+ })
20727
+ .catch(function () { showForm(""); });
20259
20728
  } else {
20260
- showForm();
20729
+ showForm("");
20261
20730
  }
20262
20731
  </script>
20263
20732
  </body></html>`;
@@ -20529,6 +20998,11 @@ var styles_default = ` :root {
20529
20998
  svg { display: block; }
20530
20999
  .ico { width:16px; height:16px; stroke:currentColor; fill:none; stroke-width:1.75; stroke-linecap:round; stroke-linejoin:round; flex:none; }
20531
21000
  .ico.sm { width:14px; height:14px; }
21001
+ /* Every window (incl. quicknote) gets the same thin translucent scrollbar:
21002
+ transparent track, rounded translucent thumb. In Chromium 121+ the
21003
+ inherited html scrollbar-width/color renders it; the ::-webkit rules
21004
+ cover Safari. Both force classic (layout-consuming) mode \u2014 tight layouts
21005
+ pair scrollbar-gutter:stable with rebalanced insets (see .qn-body). */
20532
21006
  ::-webkit-scrollbar { width:10px; height:10px; }
20533
21007
  ::-webkit-scrollbar-thumb { background:var(--line-strong); border-radius:8px; border:3px solid transparent; background-clip:padding-box; }
20534
21008
  ::-webkit-scrollbar-thumb:hover { background:var(--muted); background-clip:padding-box; border:3px solid transparent; }
@@ -20577,12 +21051,45 @@ var styles_default = ` :root {
20577
21051
  .sb-search input { border:0; background:none; outline:none; color:var(--fg); width:100%; }
20578
21052
  .sb-search kbd { font-family:var(--mono); font-size:10px; color:var(--muted); border:1px solid var(--line-strong); border-radius:4px; padding:1px 4px; }
20579
21053
  .sb-scroll { flex:1; overflow-y:auto; padding:4px 8px 24px; }
20580
- .sb-section { margin-top:12px; }
20581
- .sb-section-head { display:flex; align-items:center; justify-content:space-between;
20582
- font-size:var(--fs-micro); font-weight:600; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); padding:4px 8px; }
20583
- .sb-section-head .add { opacity:0; padding:3px; border-radius:5px; color:var(--muted); transition:opacity .12s; }
20584
- .sb-section:hover .add { opacity:1; }
20585
- .sb-section-head .add:hover { background:var(--hover-2); color:var(--fg); }
21054
+ /* Notion-style list switcher: the active tab is an icon+label pill, inactive
21055
+ tabs are icon-only. One absolutely-positioned indicator slides & stretches
21056
+ under the active button; JS keeps it in sync via ResizeObserver so it
21057
+ chases the label's 0fr\u21921fr expansion instead of jumping to a stale width. */
21058
+ .sb-tabs { position:relative; display:flex; align-items:center; gap:2px; margin:2px 12px 8px; }
21059
+ .sb-tab { position:relative; z-index:1; display:flex; align-items:center;
21060
+ padding:5px 7px; border-radius:var(--radius-sm); color:var(--muted); cursor:pointer;
21061
+ font-size:var(--fs-ui); transition:color .18s; }
21062
+ .sb-tab:hover { color:var(--fg-soft); }
21063
+ .sb-tab.on { color:var(--fg); font-weight:600; }
21064
+ /* Label expansion: animating the grid track times correctly for any label
21065
+ width (max-width can't); the inner span clips during the ride. The
21066
+ icon-text gap rides the same curve so collapsing leaves no seam. */
21067
+ .sb-tab .tab-label { display:grid; grid-template-columns:0fr; opacity:0;
21068
+ transition:grid-template-columns .24s cubic-bezier(.22,1,.36,1), opacity .18s ease; }
21069
+ .sb-tab .tab-label > span { overflow:hidden; white-space:nowrap; min-width:0;
21070
+ transition:padding-left .24s cubic-bezier(.22,1,.36,1); }
21071
+ .sb-tab.on .tab-label { grid-template-columns:1fr; opacity:1; }
21072
+ .sb-tab.on .tab-label > span { padding-left:6px; }
21073
+ .sb-tab-ind { position:absolute; left:0; top:0; bottom:0; z-index:0; width:0;
21074
+ border-radius:var(--radius-sm); background:var(--hover-2);
21075
+ transition:transform .24s cubic-bezier(.22,1,.36,1), width .24s cubic-bezier(.22,1,.36,1); }
21076
+ .sb-tabs .add { margin-left:auto; padding:3px; border-radius:5px; color:var(--muted); }
21077
+ .sb-tabs .add:hover { background:var(--hover-2); color:var(--fg); }
21078
+ /* Pane swap: the entering list slides in from the side its tab lives on. */
21079
+ .sb-pane { margin-top:4px; }
21080
+ .sb-pane.in-r { animation:sb-pane-r .2s cubic-bezier(.22,1,.36,1); }
21081
+ .sb-pane.in-l { animation:sb-pane-l .2s cubic-bezier(.22,1,.36,1); }
21082
+ @keyframes sb-pane-r { from { opacity:0; transform:translateX(14px); } }
21083
+ @keyframes sb-pane-l { from { opacity:0; transform:translateX(-14px); } }
21084
+ .sb-subfold .chev { transition:transform .12s; }
21085
+ .sb-subfold .chev.open { transform:rotate(90deg); }
21086
+ /* Collapsed-databases group: a quiet tail row inside the \u6570\u636E\u8868 list for
21087
+ tables flagged meta.collapsed (site-facing / rarely browsed). */
21088
+ .sb-subfold { display:flex; align-items:center; gap:2px; width:100%; text-align:left;
21089
+ padding:4px 6px; margin-top:2px; border-radius:var(--radius-sm);
21090
+ color:var(--muted); font-size:var(--fs-micro); cursor:pointer; }
21091
+ .sb-subfold:hover { background:var(--hover-2); color:var(--fg-soft); }
21092
+ .navitem.dim { opacity:.62; }
20586
21093
  .navitem { display:flex; align-items:center; gap:3px; width:100%; text-align:left; padding:5px 6px; margin-bottom:2px; border-radius:var(--radius-sm);
20587
21094
  color:var(--fg-soft); font-size:var(--fs-ui); position:relative; white-space:nowrap; transition:background .1s; }
20588
21095
  .navitem:hover { background:var(--hover-2); }
@@ -20672,6 +21179,23 @@ var styles_default = ` :root {
20672
21179
  .set-block-title { font-size:14px; font-weight:650; color:var(--fg); letter-spacing:-.005em; }
20673
21180
  .set-block-desc { color:var(--fg-soft); font-size:var(--fs-sm); line-height:1.55; margin-bottom:14px; max-width:56ch; }
20674
21181
  .set-block-desc:last-child { margin-bottom:0; }
21182
+ /* title+desc on the left, a control (e.g. a switch) pinned right \u2014 used for
21183
+ single boolean settings where a whole card of chrome would be overkill */
21184
+ .set-toggle-row { display:flex; align-items:center; justify-content:space-between; gap:24px; }
21185
+ .set-toggle-text { min-width:0; }
21186
+ .set-toggle-text .set-block-head { margin-bottom:4px; }
21187
+ .set-toggle-text .set-block-desc { margin-bottom:0; }
21188
+ /* iOS-style switch: a hidden checkbox drives a sliding track+thumb */
21189
+ .set-switch { position:relative; flex:none; display:inline-flex; align-items:center; cursor:pointer; }
21190
+ .set-switch input { position:absolute; width:1px; height:1px; opacity:0; margin:0; }
21191
+ .set-switch-track { display:inline-flex; align-items:center; width:40px; height:23px; padding:0 2px;
21192
+ border-radius:999px; background:var(--line-strong); transition:background .2s ease; }
21193
+ .set-switch-thumb { width:19px; height:19px; border-radius:50%; background:#fff;
21194
+ box-shadow:0 1px 3px rgba(20,20,40,.28); transition:transform .2s cubic-bezier(.4,0,.2,1); }
21195
+ .set-switch input:checked + .set-switch-track { background:var(--accent); }
21196
+ .set-switch input:checked + .set-switch-track .set-switch-thumb { transform:translateX(17px); }
21197
+ .set-switch:hover .set-switch-track { filter:brightness(.97); }
21198
+ .set-switch input:focus-visible + .set-switch-track { outline:2px solid var(--accent); outline-offset:2px; }
20675
21199
  /* two-fact meta line under a block title (who publishes \xB7 where creds live) \u2014
20676
21200
  answers the questions hiding under "is this bucket for server or browser?" */
20677
21201
  .set-meta { display:flex; flex-wrap:wrap; gap:8px 16px; margin:0 0 12px; }
@@ -20871,10 +21395,19 @@ var styles_default = ` :root {
20871
21395
  .sidebar.collapsed { overflow:hidden; }
20872
21396
  .sidebar.collapsed .sb-resizer { pointer-events:none; }
20873
21397
  .crumb { display:flex; align-items:center; gap:7px; flex:1; min-width:0; font-size:var(--fs-ui); color:var(--fg); font-weight:500; }
20874
- .crumb .emoji { font-size:15px; } .crumb .sub { color:var(--muted); font-weight:400; }
21398
+ /* Long titles ellipsize on one line \u2014 never wrap and grow the topbar. */
21399
+ .crumb > span:not(.emoji) { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
21400
+ .crumb .emoji { font-size:15px; flex:none; } .crumb .sub { color:var(--muted); font-weight:400; }
20875
21401
  .iconbtn { width:30px; height:30px; display:grid; place-items:center; border-radius:var(--radius-sm); color:var(--fg-soft); transition:background .12s; }
20876
21402
  .iconbtn:hover { background:var(--hover-2); color:var(--fg); }
20877
- .content { flex:1; overflow:auto; }
21403
+ /* Reserve the classic-scrollbar gutter up front: view width never changes
21404
+ when content grows past one screen \u2014 no jump / rewrap flicker. stable
21405
+ ONLY (never both-edges): a left-edge gutter desyncs Chromium's composited
21406
+ scroll cull rect by the gutter width, leaving a permanent unpainted
21407
+ stripe over the rightmost 15px of the scrollport (content reaching into
21408
+ it gets visibly sliced \u2014 first seen slicing the quicknote file card).
21409
+ Inert on mobile, where .content becomes overflow:visible. */
21410
+ .content { flex:1; overflow:auto; scrollbar-gutter:stable; }
20878
21411
  .btn { display:inline-flex; align-items:center; gap:6px; padding:6px 12px; border-radius:var(--radius-sm); font-size:13px; font-weight:500; transition:all .12s; white-space:nowrap; }
20879
21412
  .btn-ghost { color:var(--fg-soft); } .btn-ghost:hover { background:var(--hover-2); color:var(--fg); }
20880
21413
  .btn-secondary { background:var(--bg); border:1px solid var(--line-strong); color:var(--fg); box-shadow:var(--shadow-sm); }
@@ -20939,9 +21472,9 @@ var styles_default = ` :root {
20939
21472
  .doc-conflict { display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:10px 14px; margin-bottom:16px; border:1px solid var(--danger); border-radius:var(--radius-sm); background:var(--danger-soft); font-size:13px; }
20940
21473
  .doc-conflict-msg { color:var(--danger); font-weight:600; flex:1; min-width:180px; }
20941
21474
  .doc.source-mode { min-height:100%; }
20942
- .doc-source { display:block; width:100%; min-height:calc(100vh - 210px); min-height:calc(100dvh - 210px); resize:none; border:0;
20943
- background:transparent; color:var(--fg); outline:none; overflow:hidden; padding:3px 2px 36vh;
20944
- font-family:var(--ui); font-size:15px; line-height:1.7; tab-size:2; }
21475
+ /* wide mode (\u22EF menu): lift the column cap; the 60px side padding stays as the
21476
+ reading gutter and keeps the fixed .doc-toc ticks off the text. */
21477
+ .doc.wide-mode { max-width:none; }
20945
21478
  /* floating table of contents: a column of indented tick marks in the right
20946
21479
  gutter that expands to a labelled list on hover (Notion / Vercel-docs style).
20947
21480
  Fixed to the viewport so it never disturbs the centred .doc column. */
@@ -20980,49 +21513,33 @@ var styles_default = ` :root {
20980
21513
  /* not enough room for the gutter \u2192 hide entirely (also covers mobile) */
20981
21514
  @media (max-width:1100px) { .doc-toc { display:none; } }
20982
21515
 
20983
- .block-wrap { position:relative; }
20984
- .block-wrap.nested { padding-left:24px; }
20985
- /* Subtle indent guide only for true sub-lists; suppressed for code/other
20986
- nested content so code-in-list items don't stack a thicket of lines. */
20987
- .block-wrap.nested:has(> .block.b-bullet, > .block.b-numbered, > .block.b-todo)::before {
20988
- content:""; position:absolute; left:8px; top:2px; bottom:2px; width:1px; background:var(--line); }
20989
- .block { position:relative; display:flex; align-items:flex-start; padding:1px 0; border-radius:4px; scroll-margin-top:60px; }
20990
- .block .gutter { position:absolute; left:-52px; top:2px; display:flex; gap:1px; opacity:0; transition:opacity .12s; }
20991
- .block:hover .gutter, .block.drop-before .gutter, .block.drop-after .gutter, .block.dragging .gutter { opacity:1; }
20992
- .gutter button { width:22px; height:24px; display:grid; place-items:center; color:var(--muted); border-radius:5px; }
20993
- .gutter button:hover { background:var(--hover-2); color:var(--fg); }
20994
- .gutter .grip { cursor:grab; } .gutter .grip:active { cursor:grabbing; }
20995
- .block.dragging { opacity:.42; background:var(--hover); }
20996
- .block.drop-before::before, .block.drop-after::after { content:""; position:absolute; left:0; right:0; height:2px; background:var(--accent);
20997
- border-radius:2px; pointer-events:none; box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 18%,transparent); }
20998
- .block.drop-before::before { top:-1px; }
20999
- .block.drop-after::after { bottom:-1px; }
21000
- /* multi-block selection: whole-block tint + suppress stray native text highlight while dragging */
21001
- .block.selected { background:var(--accent-soft); }
21002
- .block.selected .editable::selection, .block.selected .editable *::selection { background:transparent; }
21003
- .doc.selecting, .doc.selecting * { user-select:none; -webkit-user-select:none; cursor:default; }
21004
- .block.list-code-host { height:0; min-height:0; padding:0; overflow:visible; }
21005
- .block-wrap:hover > .block.list-code-host .gutter { opacity:1; }
21006
- .block.list-code-host .marker { position:absolute; left:0; top:0; padding-top:4px; z-index:1; }
21007
- /* The list item's host gutter controls the whole unit, so suppress the nested
21008
- code block's own gutter (it would overlap the list marker on the same row). */
21009
- .block.list-code-host + .block-wrap.nested .gutter { display:none; }
21010
- .editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
21011
- .editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
21012
- /* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
21013
- .editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
21014
- .b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
21015
- .b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
21016
- .b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
21017
- .b-h4 .editable { font-size:16px; font-weight:600; padding-top:2px; }
21018
- .b-h5 .editable { font-size:14px; font-weight:600; }
21019
- .b-h6 .editable { font-size:13px; font-weight:600; color:var(--fg-soft); }
21020
- .b-h1 .gutter { top:22px; } .b-h2 .gutter { top:14px; } .b-h3 .gutter { top:7px; }
21021
- .b-h4 .gutter { top:4px; } .b-h5 .gutter { top:3px; } .b-h6 .gutter { top:2px; }
21022
- .b-quote .editable { border-left:3px solid var(--line-strong); padding:1px 0 1px 16px; color:var(--fg-soft); font-style:italic; }
21516
+ /* floating word count: a small pill in the bottom-right gutter (fixed to the
21517
+ viewport, aligned with .doc-toc's right edge). Collapsed it hugs the \u5B57\u6570;
21518
+ hover expands the whole pill (square-off + surface bg) and reveals the char
21519
+ count + reading estimate. Centre-aligned throughout, and the collapsed detail
21520
+ is width:0 so its wider text can't stretch the pill. Purely client-side. */
21521
+ .doc-wc { position:fixed; right:18px; bottom:calc(16px + env(safe-area-inset-bottom)); z-index:40;
21522
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
21523
+ padding:4px 12px; border:1px solid var(--line); border-radius:999px; background:var(--bg);
21524
+ color:var(--muted); font-size:var(--fs-sm); line-height:1.2; user-select:none;
21525
+ box-shadow:var(--shadow-sm); opacity:.66;
21526
+ transition:opacity .18s ease, border-radius .2s ease, background .18s ease, box-shadow .2s ease, color .18s ease; }
21527
+ .doc-wc:hover { opacity:1; border-radius:var(--radius); background:var(--surface); border-color:var(--line-strong);
21528
+ box-shadow:var(--shadow-md); color:var(--fg-soft); }
21529
+ .doc-wc-num { font-variant-numeric:tabular-nums; white-space:nowrap; }
21530
+ .doc-wc.sel .doc-wc-num { color:var(--accent); font-weight:550; }
21531
+ /* detail rows: collapsed to zero size until hover. width:0 keeps them from
21532
+ widening the pill; no container gap (a phantom gap would push the collapsed
21533
+ count off-centre) \u2014 the spacing comes from margin-top only when expanded. */
21534
+ .doc-wc-detail { display:flex; flex-direction:column; align-items:center; gap:2px;
21535
+ width:0; max-height:0; margin-top:0; overflow:hidden; opacity:0; font-size:12px; color:var(--muted);
21536
+ transition:max-height .2s ease, opacity .18s ease, margin-top .2s ease; }
21537
+ .doc-wc-detail span { font-variant-numeric:tabular-nums; white-space:nowrap; }
21538
+ .doc-wc:hover .doc-wc-detail { width:auto; max-height:40px; margin-top:4px; opacity:1; }
21539
+ /* quick-note mini window has no room for it */
21540
+ .qn-body .doc-wc { display:none; }
21023
21541
 
21024
21542
  /* code block: transparent textarea over a highlight.js mirror */
21025
- .b-code .codeblock { flex:1; min-width:0; }
21026
21543
  .codeblock { position:relative; --code-fs:12.5px; --code-lh:1.55; --code-pad:8px; margin:2px 0;
21027
21544
  background:var(--code-bg); border:1px solid var(--line); border-radius:8px; overflow:hidden; box-shadow:var(--shadow-sm); }
21028
21545
  /* lang + copy float bottom-right, revealed on hover */
@@ -21031,27 +21548,57 @@ var styles_default = ` :root {
21031
21548
  background:color-mix(in srgb,var(--code-bg) 82%,transparent); backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
21032
21549
  opacity:0; transform:translateY(3px); pointer-events:none; transition:opacity .12s,transform .12s; }
21033
21550
  .codeblock:hover .code-tools, .code-tools:focus-within { opacity:1; transform:none; pointer-events:auto; }
21551
+ /* hidden while editing (textarea focused) or whole-block selected \u2014 the pill would cover the
21552
+ bottom-right of the editing area; keep it if focus is on the pill itself (lang dropdown open) */
21553
+ .cm-void-code:focus-within .code-tools:not(:focus-within),
21554
+ .codeblock.ci-selected .code-tools:not(:focus-within) { opacity:0; transform:translateY(3px); pointer-events:none; }
21034
21555
  .code-lang { position:relative; display:inline-flex; align-items:center; }
21035
21556
  .code-lang select { appearance:none; -webkit-appearance:none; border:0; outline:none; background:none; cursor:pointer;
21036
21557
  font-family:var(--mono); font-size:10.5px; font-weight:500; color:var(--fg-soft); padding:3px 17px 3px 7px; border-radius:5px; }
21037
21558
  .code-lang select:hover { background:var(--hover-2); }
21038
21559
  .code-lang svg { position:absolute; right:3px; width:12px; height:12px; pointer-events:none; color:var(--muted); }
21039
- .code-copy { display:inline-flex; align-items:center; gap:4px; font-size:10.5px; font-weight:500; color:var(--muted);
21040
- padding:3px 7px; border-radius:5px; transition:background .12s,color .12s; }
21041
- .code-copy:hover { background:var(--hover-2); color:var(--fg-soft); }
21042
- .code-copy.ok { color:var(--accent); }
21043
- .code-body { display:flex; align-items:stretch; font-family:var(--mono); font-size:var(--code-fs); line-height:var(--code-lh); }
21560
+ .code-copy, .code-fmt, .code-wrap-btn { display:inline-flex; align-items:center; gap:4px; font-size:10.5px; font-weight:500; color:var(--muted);
21561
+ padding:3px 7px; border-radius:5px; transition:background .12s,color .12s; white-space:nowrap; }
21562
+ .code-copy:hover, .code-fmt:hover, .code-wrap-btn:hover { background:var(--hover-2); color:var(--fg-soft); }
21563
+ .code-copy.ok, .code-fmt.ok, .code-wrap-btn.on { color:var(--accent); }
21564
+ .code-fmt.err { color:var(--danger); }
21565
+ /* Default (no wrap): lines run long, the textarea scrolls horizontally and
21566
+ mirrors its scrollLeft onto the highlight layer; the gutter is a separate
21567
+ flex column of "1\\n2\\n3" (every logical line is exactly one row high). */
21568
+ .code-body { display:flex; align-items:stretch; font-family:var(--mono); font-size:var(--code-fs); line-height:var(--code-lh);
21569
+ --code-ln-ch:1ch; --code-gutter-w:calc(var(--code-ln-ch) + 20px); }
21044
21570
  .code-gutter { flex:none; text-align:right; white-space:pre; user-select:none; color:var(--muted); opacity:.55;
21045
21571
  padding:var(--code-pad) 8px var(--code-pad) 12px; border-right:1px solid var(--line); font-variant-numeric:tabular-nums; }
21046
21572
  .code-scroll { position:relative; flex:1; min-width:0; }
21047
21573
  .code-hl, .code-input { margin:0; font-family:var(--mono); font-size:var(--code-fs); line-height:var(--code-lh);
21048
21574
  padding:var(--code-pad) 16px; tab-size:2; -moz-tab-size:2; white-space:pre; letter-spacing:0; }
21049
21575
  .code-hl { position:absolute; inset:0; z-index:0; overflow:hidden; pointer-events:none; background:none; color:var(--fg); }
21050
- .code-hl code { display:block; font:inherit; background:none; padding:0; }
21576
+ .code-hl code { display:block; font:inherit; background:none; padding:0; counter-reset:code-ln; }
21577
+ .code-hl-line { counter-increment:code-ln;
21578
+ min-height:calc(var(--code-fs) * var(--code-lh)); /* empty logical line still occupies its row */ }
21051
21579
  .code-input { display:block; position:relative; z-index:1; width:100%; border:0; outline:none; resize:none;
21052
21580
  overflow-x:auto; overflow-y:hidden; background:transparent; color:transparent; caret-color:var(--fg);
21053
21581
  min-height:calc(var(--code-fs) * var(--code-lh) + var(--code-pad) * 2); }
21054
21582
  .code-input::placeholder { color:var(--muted); }
21583
+ /* Wrap mode (.wrap, global toggle in the tools pill, persisted): textarea and
21584
+ mirror share pre-wrap + break-word (the UA textarea wrap rules), so their
21585
+ wrap points match and the caret stays on the glyphs. The flex gutter would
21586
+ misalign (a logical line can span several visual rows), so it hides and
21587
+ line numbers become CSS counters on the mirror's per-logical-line blocks \u2014
21588
+ a wrapped line grows its block and the next number moves with it, no JS
21589
+ height sync. --code-ln-ch (digit count) is set by paint(). */
21590
+ .code-body.wrap { display:block; position:relative; }
21591
+ .code-body.wrap::before { content:""; position:absolute; left:var(--code-gutter-w); top:0; bottom:0;
21592
+ width:1px; background:var(--line); }
21593
+ .code-body.wrap .code-gutter { display:none; }
21594
+ .code-body.wrap .code-hl, .code-body.wrap .code-input {
21595
+ padding-left:calc(var(--code-gutter-w) + 17px);
21596
+ white-space:pre-wrap; overflow-wrap:break-word; }
21597
+ .code-body.wrap .code-input { overflow:hidden; }
21598
+ .code-body.wrap .code-hl-line { position:relative; }
21599
+ .code-body.wrap .code-hl-line::before { content:counter(code-ln); position:absolute; top:0;
21600
+ left:calc(-17px - var(--code-gutter-w)); width:calc(var(--code-gutter-w) - 8px);
21601
+ text-align:right; color:var(--muted); opacity:.55; font-variant-numeric:tabular-nums; }
21055
21602
 
21056
21603
  /* highlight.js token theme (cohesive with the app palette) */
21057
21604
  .hljs-comment, .hljs-quote { color:var(--hl-cmt); font-style:italic; }
@@ -21065,24 +21612,50 @@ var styles_default = ` :root {
21065
21612
  .hljs-params, .hljs-subst { color:var(--fg); }
21066
21613
  .hljs-emphasis { font-style:italic; }
21067
21614
  .hljs-strong { font-weight:700; }
21068
- .marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
21069
- .b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
21070
- display:flex; align-items:center; justify-content:center; }
21071
- .b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
21072
- .b-divider { padding:10px 0; }
21073
- .b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
21074
- .b-done .editable { color:var(--muted); text-decoration:line-through; }
21075
- .editable b, .editable strong { font-weight:700; }
21076
- .editable code { font-family:var(--mono); background:var(--surface-2); padding:1px 5px; border-radius:4px; font-size:.9em; }
21077
- .editable a { color:var(--accent); }
21078
- /* document images (pasted/dropped \u2192 /blob/<hash>); fetched on demand */
21079
- .editable img.doc-img { max-width:100%; height:auto; border-radius:8px; margin:6px 0; display:block; vertical-align:bottom; }
21080
21615
 
21081
- /* ---- block-level void embeds: image / video / audio / file / html ---- */
21082
- .void-host { flex:1; min-width:0; outline:none; padding:2px 0; }
21083
- .block.selected:has(.void-host) { background:transparent; }
21084
- .block.selected .void-block, .void-host:focus-visible .void-block { box-shadow:0 0 0 2px var(--accent); }
21616
+ /* CodeIsland (CM6 void widget): the tools pill (lang + copy) is the only
21617
+ focus-state chrome \u2014 an absolute overlay, so revealing it never changes the
21618
+ block's height (no layout shift on click). .ci-selected marks the island
21619
+ when the CM selection spans the whole void (Escape from inside / Backspace
21620
+ from the line below). */
21621
+ .cm-void-code .codeblock.ci-selected { box-shadow:0 0 0 2px var(--accent); }
21622
+
21623
+ /* Whole-void selected (the CM selection spans the block \u2014 click-to-select on
21624
+ media, Backspace-from-below, select-all): 2px accent ring, the old editor's
21625
+ \`.block.selected .void-block\` treatment. Code draws its own via .ci-selected. */
21626
+ /* Intrinsic-width firewall (same problem the doc table solved with
21627
+ contain:inline-size on .doc-table-wrap): a void widget's max-content \u2014
21628
+ e.g. a file card whose nowrap filename caps at 480px \u2014 must not propagate
21629
+ into .cm-content and push the whole editor wider than a narrow window
21630
+ (quick notes), shoving the card's right edge offscreen. width:0 zeroes the
21631
+ host's intrinsic contribution; min-width:100% restores the line width at
21632
+ layout time; the card's own min-width:0 + ellipsis then get to run.
21633
+ NOT contain:inline-size: Chromium 130's ink-overflow culling clips a
21634
+ size-contained host's paint at a stale boundary on 2x displays \u2014 the
21635
+ card's right border and selection ring were visibly sliced off. */
21636
+ .cm-void { box-sizing:border-box; width:0; min-width:100%; }
21637
+ .cm-void.selected .void-block { box-shadow:0 0 0 2px var(--accent); }
21638
+ .cm-void.selected table.doc-table { box-shadow:0 0 0 2px var(--accent), var(--shadow-sm); }
21639
+
21640
+ /* Editor text selection: one accent tint for BOTH the native ::selection and
21641
+ .cm-mk-sel, the class block-deco puts on marker widgets (bullet / ordered
21642
+ number / checkbox) whose marker range the selection covers. Native selection
21643
+ cannot paint replaced widgets, which left holes in the marker column during
21644
+ drag-select / select-all; unifying the two colors makes the line read as one
21645
+ continuous highlight (and tracks the theme in dark mode). */
21646
+ .cm-content ::selection { background: color-mix(in srgb, var(--accent) 24%, transparent); }
21647
+ .cm-content .cm-mk-sel { background: color-mix(in srgb, var(--accent) 24%, transparent); }
21648
+ /* bullet/number boxes are 24px + 2px margin-right; the margin can't take a
21649
+ background, so a same-color shadow fills the seam up to the content column */
21650
+ .cm-content .cm-bullet.cm-mk-sel, .cm-content .cm-ol-num.cm-mk-sel {
21651
+ box-shadow: 2px 0 0 color-mix(in srgb, var(--accent) 24%, transparent);
21652
+ }
21653
+ /* the glyph/number text inside a painted marker also sits in the DOM selection;
21654
+ without this it gets the ::selection tint ON TOP of the span background and
21655
+ reads darker than the rest of the line */
21656
+ .cm-content .cm-mk-sel::selection, .cm-content .cm-mk-sel ::selection { background: transparent; }
21085
21657
 
21658
+ /* ---- block-level void embeds: image / video / audio / file / html ---- */
21086
21659
  .void-image { position:relative; display:inline-block; max-width:100%; border-radius:8px; line-height:0; }
21087
21660
  .void-image img { display:block; width:100%; max-width:100%; height:auto; border-radius:8px; user-select:none; }
21088
21661
  .void-image .void-expand { position:absolute; top:8px; right:8px; width:28px; height:28px; display:grid; place-items:center;
@@ -21120,7 +21693,6 @@ var styles_default = ` :root {
21120
21693
  color:var(--fg-soft); font-size:var(--fs-micro); transition:background .12s; }
21121
21694
  .void-html .html-toggle:hover { background:var(--hover-2); color:var(--fg); }
21122
21695
  .void-html .html-toggle.on { background:var(--accent-soft); color:var(--accent); }
21123
- .void-html .html-source { width:100%; min-height:120px; padding:12px; border:0; background:var(--bg); resize:vertical; }
21124
21696
  .void-html .html-frame { display:block; width:100%; border:0; background:var(--bg); }
21125
21697
  .void-html .html-empty { padding:24px; text-align:center; color:var(--muted); cursor:text; font-size:var(--fs-ui); }
21126
21698
 
@@ -21131,30 +21703,47 @@ var styles_default = ` :root {
21131
21703
  .lightbox-toolbar { flex:none; display:flex; align-items:center; gap:6px; padding:10px 14px; color:#fff; }
21132
21704
  .lightbox-toolbar .lightbox-name { font-size:var(--fs-ui); opacity:.85; max-width:40vw; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
21133
21705
  .lightbox-toolbar .lightbox-spacer { flex:1; }
21134
- .lightbox-toolbar button { width:34px; height:34px; display:grid; place-items:center; border-radius:8px; color:#fff;
21706
+ .lightbox-toolbar > button { width:34px; height:34px; display:grid; place-items:center; border-radius:8px; color:#fff;
21135
21707
  background:rgba(255,255,255,.08); transition:background .12s; }
21136
- .lightbox-toolbar button:hover { background:rgba(255,255,255,.2); }
21708
+ .lightbox-toolbar > button:hover { background:rgba(255,255,255,.2); }
21137
21709
  .lightbox-toolbar .lightbox-pct { width:auto; min-width:52px; font-size:var(--fs-micro); font-variant-numeric:tabular-nums; }
21710
+ .lightbox-toolbar .ico { width:16px; height:16px; stroke-width:2; }
21138
21711
  .lightbox-stage { flex:1; min-height:0; overflow:hidden; display:grid; place-items:center; }
21139
21712
  .lightbox-stage img { max-width:94vw; max-height:100%; will-change:transform; -webkit-user-drag:none; user-select:none; }
21140
21713
 
21141
21714
  /* ---- image annotator ---- */
21142
21715
  .annot { flex:1; min-height:0; display:flex; flex-direction:column; }
21143
- .annot-toolbar { flex:none; display:flex; flex-wrap:wrap; align-items:center; gap:14px; padding:8px 14px; }
21144
- .annot-tools, .annot-colors, .annot-actions { display:flex; align-items:center; gap:6px; }
21145
- .annot-actions { margin-left:auto; }
21146
- .annot-tool { width:34px; height:34px; display:grid; place-items:center; border-radius:8px; color:#fff; background:rgba(255,255,255,.08); transition:background .12s; }
21147
- .annot-tool:hover { background:rgba(255,255,255,.2); }
21148
- .annot-tool.on { background:var(--accent); color:var(--accent-fg); }
21149
- .annot-tool:disabled { opacity:.4; cursor:default; }
21150
- .annot-swatch { width:22px; height:22px; border-radius:50%; border:2px solid rgba(255,255,255,.5); transition:transform .1s; }
21151
- .annot-swatch.on { transform:scale(1.18); border-color:#fff; box-shadow:0 0 0 2px var(--accent); }
21716
+ /* Single-row chrome: filename \xB7 segmented tools \xB7 colors \xB7 undo/redo \xB7
21717
+ cancel/save \xB7 close. Wraps only as a narrow-window fallback. */
21718
+ .annot-bar { flex-wrap:wrap; row-gap:6px; }
21719
+ .annot-seg { display:flex; align-items:center; gap:2px; padding:2px; border-radius:9px; background:rgba(255,255,255,.1); }
21720
+ .annot-seg-btn { width:30px; height:26px; display:grid; place-items:center; border-radius:7px;
21721
+ color:rgba(255,255,255,.72); transition:background .12s, color .12s; }
21722
+ .annot-seg-btn:hover { color:#fff; background:rgba(255,255,255,.12); }
21723
+ .annot-seg-btn.on { background:#fff; color:#1c1c26; box-shadow:0 1px 2px rgba(0,0,0,.3); }
21724
+ .annot-div { flex:none; width:1px; height:18px; margin:0 5px; background:rgba(255,255,255,.18); }
21725
+ .annot-colors { display:flex; align-items:center; gap:8px; padding:0 2px; }
21726
+ .annot-swatch { width:15px; height:15px; border-radius:50%; box-shadow:inset 0 0 0 1px rgba(255,255,255,.3);
21727
+ transition:transform .12s, box-shadow .12s; }
21728
+ .annot-swatch:hover { transform:scale(1.18); }
21729
+ .annot-swatch.on { box-shadow:inset 0 0 0 1px rgba(255,255,255,.3), 0 0 0 2px rgba(12,12,18,.9), 0 0 0 3.5px rgba(255,255,255,.92); }
21730
+ .annot-bar .annot-icon-btn { width:30px; height:30px; display:grid; place-items:center; border-radius:7px;
21731
+ color:rgba(255,255,255,.85); background:none; transition:background .12s; }
21732
+ .annot-bar .annot-icon-btn:hover { background:rgba(255,255,255,.14); }
21733
+ .annot-bar .annot-icon-btn:disabled { opacity:.35; cursor:default; background:none; }
21734
+ .annot-actions { display:flex; align-items:center; gap:8px; margin:0 2px 0 4px; }
21152
21735
  .annot-stage { flex:1; min-height:0; overflow:auto; display:grid; place-items:center; padding:8px 14px 18px; }
21153
21736
  .annot-img-wrap { position:relative; line-height:0; }
21154
21737
  .annot-img-wrap img { display:block; max-width:90vw; max-height:78vh; width:auto; height:auto; -webkit-user-drag:none; user-select:none; }
21155
- .annot-canvas { position:absolute; inset:0; width:100%; height:100%; }
21738
+ .annot-canvas { position:absolute; inset:0; width:100%; height:100%; touch-action:none; }
21156
21739
  .annot-canvas.tool-text { cursor:text; }
21157
21740
  .annot-canvas.tool-rect, .annot-canvas.tool-arrow { cursor:crosshair; }
21741
+ /* Inline text overlay: WYSIWYG twin of the flattened canvas text (same font,
21742
+ line-height and halo), plus an accent ring so the edit region reads. */
21743
+ .annot-text-input { position:absolute; z-index:2; min-width:60px; padding:2px 4px; margin:0; border:0; outline:none;
21744
+ resize:none; overflow:hidden; white-space:pre; background:transparent; border-radius:4px; caret-color:currentColor;
21745
+ font:600 20px/1.3 system-ui, -apple-system, sans-serif; text-shadow:0 1px 2px rgba(0,0,0,.55);
21746
+ box-shadow:0 0 0 1.5px var(--accent), 0 0 0 4px color-mix(in srgb, var(--accent) 25%, transparent); }
21158
21747
 
21159
21748
  /* ---- transient "uploading" placeholder + unavailable fallback ---- */
21160
21749
  .ico.spin { animation:mh-spin .8s linear infinite; }
@@ -21163,7 +21752,10 @@ var styles_default = ` :root {
21163
21752
  display:flex; align-items:center; gap:12px; margin:2px 0; max-width:480px; padding:12px 14px;
21164
21753
  border:1px solid var(--line-strong); border-radius:var(--radius); background:var(--surface); color:var(--fg-soft); }
21165
21754
  .void-uploading { border-style:dashed; }
21166
- .void-uploading .vu-name, .void-unavailable .vu-name { font-weight:600; color:var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
21755
+ /* min-width:0: .void-uploading's vu-name is a direct flex item \u2014 without it a
21756
+ long nowrap filename sets the item's min-content floor and the placeholder
21757
+ card overflows a narrow window instead of ellipsizing. */
21758
+ .void-uploading .vu-name, .void-unavailable .vu-name { font-weight:600; color:var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0; }
21167
21759
  .void-uploading .vu-sub, .void-unavailable .vu-sub { font-size:var(--fs-micro); color:var(--muted); }
21168
21760
  .void-unavailable { border-color:var(--danger-soft); color:var(--danger); }
21169
21761
  .void-unavailable .ico { color:var(--danger); flex:none; }
@@ -21209,24 +21801,53 @@ var styles_default = ` :root {
21209
21801
  /* Chrome rides the theme-tinted glass \u2192 theme-aware (var(--fg)), readable on a
21210
21802
  light or dark tint alike. */
21211
21803
  body.preview-window .lightbox-toolbar { color:var(--fg); }
21212
- body.preview-window .lightbox-toolbar button,
21213
- body.preview-window .annot-tool { color:var(--fg); background:color-mix(in srgb, var(--fg) 10%, transparent); }
21214
- body.preview-window .lightbox-toolbar button:hover,
21215
- body.preview-window .annot-tool:hover { background:color-mix(in srgb, var(--fg) 20%, transparent); }
21216
- body.preview-window .annot-tool.on { color:var(--accent-fg); background:var(--accent); }
21217
- body.preview-window .annot-swatch { border-color:color-mix(in srgb, var(--fg) 45%, transparent); }
21804
+ body.preview-window .lightbox-toolbar > button { color:var(--fg); background:color-mix(in srgb, var(--fg) 10%, transparent); }
21805
+ body.preview-window .lightbox-toolbar > button:hover { background:color-mix(in srgb, var(--fg) 20%, transparent); }
21806
+ body.preview-window .annot-seg { background:color-mix(in srgb, var(--fg) 8%, transparent); }
21807
+ body.preview-window .annot-seg-btn { color:color-mix(in srgb, var(--fg) 65%, transparent); }
21808
+ body.preview-window .annot-seg-btn:hover { color:var(--fg); background:color-mix(in srgb, var(--fg) 10%, transparent); }
21809
+ body.preview-window .annot-seg-btn.on { background:var(--bg); color:var(--fg); box-shadow:var(--shadow-sm); }
21810
+ body.preview-window .annot-div { background:color-mix(in srgb, var(--fg) 15%, transparent); }
21811
+ body.preview-window .annot-swatch { box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 25%, transparent); }
21812
+ body.preview-window .annot-swatch.on { box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--fg) 25%, transparent),
21813
+ 0 0 0 2px var(--bg), 0 0 0 3.5px color-mix(in srgb, var(--fg) 78%, transparent); }
21814
+ body.preview-window .annot-bar .annot-icon-btn { color:var(--fg); background:none; }
21815
+ body.preview-window .annot-bar .annot-icon-btn:hover { background:color-mix(in srgb, var(--fg) 12%, transparent); }
21816
+ body.preview-window .annot-bar .annot-icon-btn:disabled { background:none; }
21218
21817
  /* In the frameless window the toolbar doubles as the OS drag region; its
21219
21818
  buttons opt back out so they stay clickable. */
21220
21819
  .lightbox-toolbar.app-drag { -webkit-app-region:drag; }
21221
21820
  .lightbox-toolbar.app-drag button { -webkit-app-region:no-drag; }
21222
21821
 
21223
- /* document table (GFM pipe table block) */
21224
- .b-table .doc-table-wrap { flex:1; min-width:0; margin:4px 0; }
21822
+ /* document table (GFM pipe table block). contain:inline-size zeroes the
21823
+ widget's intrinsic-width contribution \u2014 otherwise the inner
21824
+ width:max-content propagates through overflow-x:auto into .cm-content's
21825
+ min-width:auto floor (intrinsic sizing ignores overflow) and pushes the
21826
+ whole page wider than the viewport on narrow screens. With the intrinsic
21827
+ loop broken, .doc-table-wrap resolves to the doc column width and the
21828
+ scroll box below finally gets to scroll. container-type additionally
21829
+ unlocks cqw for the sticky add-row bar (old engines fall back to contain);
21830
+ position:relative anchors the gutter row handle explicitly rather than
21831
+ leaning on container-type's implied containing block. */
21832
+ .doc-table-wrap { position:relative; contain:inline-size; container-type:inline-size; }
21225
21833
  .doc-table-scroll { overflow-x:auto; padding:2px 0; }
21226
- .doc-table-inner { width:max-content; }
21834
+ .doc-table-inner { position:relative; width:max-content; } /* anchors the drag outline / drop lines so they track horizontal scroll */
21227
21835
  .doc-table-row { display:flex; align-items:stretch; }
21228
21836
  table.doc-table { border-collapse:separate; border-spacing:0; table-layout:fixed; width:max-content;
21229
21837
  border:1px solid var(--line); border-radius:var(--radius-sm); overflow:hidden; box-shadow:var(--shadow-sm); }
21838
+ /* Default (no manual widths yet): browser auto table layout sizes columns by
21839
+ content, capped to the wrap (cqw works despite the max-content .doc-table-inner;
21840
+ 26px keeps the add-col button inside) so it wraps instead of scrolling. The
21841
+ first resizer drag freezes measured widths and drops this class (table-block.tsx). */
21842
+ table.doc-table.autofit { table-layout:auto; width:auto; max-width:calc(100cqw - 26px); }
21843
+ table.doc-table.autofit .doc-td { min-width:48px; } /* empty columns don't collapse */
21844
+ /* Code tokens are atomic: raising the column's min-content makes the auto
21845
+ algorithm shed width into wrappable prose columns instead of breaking
21846
+ \`file-names.md\` at hyphens; an over-long token falls back to the scroll
21847
+ box. Autofit only \u2014 in manual fixed layout nowrap can't grow the column,
21848
+ it would overflow the cell. Phones relax this (see the 560px block):
21849
+ there the atom IS the cap, and it crushes prose columns to their floor. */
21850
+ table.doc-table.autofit .doc-td code { white-space:nowrap; }
21230
21851
  table.doc-table td { position:relative; vertical-align:top; padding:0;
21231
21852
  border-right:1px solid var(--line); border-bottom:1px solid var(--line); }
21232
21853
  table.doc-table tr td:last-child { border-right:0; }
@@ -21238,24 +21859,59 @@ var styles_default = ` :root {
21238
21859
  .doc-td b, .doc-td strong { font-weight:700; }
21239
21860
  .doc-td code { font-family:var(--mono); background:var(--surface-2); padding:1px 5px; border-radius:4px; font-size:.9em; }
21240
21861
  .doc-td a { color:var(--accent); }
21241
- /* header cell: column menu + resize handle */
21242
- .doc-col-menu { position:absolute; top:4px; right:4px; width:18px; height:18px; display:grid; place-items:center;
21243
- color:var(--muted); border-radius:4px; opacity:0; transition:opacity .12s,background .12s; }
21244
- .doc-th:hover .doc-col-menu { opacity:1; }
21245
- .doc-col-menu:hover { background:var(--hover-2); color:var(--fg); }
21246
21862
  .doc-col-resizer { position:absolute; top:0; right:-4px; width:7px; height:100%; cursor:col-resize; z-index:3; touch-action:none; }
21247
- /* per-row delete: small handle floating at the first cell on row hover */
21248
- .doc-row-del { position:absolute; top:3px; left:2px; width:18px; height:18px; display:grid; place-items:center;
21249
- color:var(--muted); background:var(--bg); border:1px solid var(--line); border-radius:5px; box-shadow:var(--shadow-sm);
21250
- opacity:0; transition:opacity .12s; }
21251
- table.doc-table tr:hover .doc-row-del { opacity:1; }
21252
- .doc-row-del:hover { background:var(--danger-soft); color:var(--danger); border-color:var(--danger-soft); }
21863
+ /* Row/column pill handles (Notion-style): one per axis, tracked onto the
21864
+ active cell (hovered on desktop, focused on touch) by JS. They live on
21865
+ .doc-table-wrap OUTSIDE .doc-table-scroll so overflow-x can't clip them,
21866
+ straddling the table's left/top border \u2014 never on cell text. Resting =
21867
+ quiet grey pill; pressed/dragging (.grab) = accent pill with light dots
21868
+ plus the source outline + drop line below. touch-action:none so a finger
21869
+ drag on the handle reorders instead of scrolling. Data rows only get a
21870
+ row pill (the block gutter's +/grip pins at the header row's y). */
21871
+ .doc-row-handle, .doc-col-handle { position:absolute; display:grid; place-items:center; z-index:7;
21872
+ color:var(--muted); background:var(--bg); border:1px solid var(--line); box-shadow:var(--shadow-sm);
21873
+ cursor:grab; touch-action:none; animation:gripFade .12s ease both;
21874
+ transition:background .12s,color .12s,border-color .12s; }
21875
+ .doc-row-handle { left:-8px; width:15px; height:28px; border-radius:8px; }
21876
+ .doc-col-handle { top:-6px; width:28px; height:15px; border-radius:8px; transform:translateX(-50%); }
21877
+ .doc-row-handle svg, .doc-col-handle svg { width:13px; height:13px; }
21878
+ .doc-row-handle:hover, .doc-col-handle:hover { background:var(--surface); color:var(--fg); }
21879
+ .doc-row-handle.grab, .doc-col-handle.grab { background:var(--accent); color:var(--accent-fg,#fff);
21880
+ border-color:var(--accent); cursor:grabbing; }
21881
+ .doc-row-handle::after { content:""; position:absolute; inset:-4px; }
21882
+ .doc-col-handle::after { content:""; position:absolute; inset:-4px; }
21883
+ /* drag-reorder overlays (inside .doc-table-inner, so they scroll with the
21884
+ table): accent outline on the source row/column, 2px line at the drop
21885
+ boundary (grid .rowdrop's cousin) */
21886
+ .doc-drag-outline { position:absolute; border:2px solid var(--accent); border-radius:6px;
21887
+ pointer-events:none; z-index:6; box-sizing:border-box; }
21888
+ .doc-rowdrop { position:absolute; height:2px; margin-top:-1px; background:var(--accent); border-radius:1px; pointer-events:none; z-index:6; }
21889
+ .doc-coldrop { position:absolute; width:2px; margin-left:-1px; background:var(--accent); border-radius:1px; pointer-events:none; z-index:6; }
21890
+ /* touch selection action bar: the OSK has no Delete/\u2318C, so the cell
21891
+ rectangle gets on-screen verbs; sits on the wrap layer above the selection */
21892
+ .doc-sel-bar { position:absolute; z-index:8; display:flex; gap:2px; padding:3px;
21893
+ background:var(--accent); border-radius:9px; box-shadow:var(--shadow-sm); animation:gripFade .12s ease both; }
21894
+ .doc-sel-bar button { color:var(--accent-fg,#fff); font-size:13px; line-height:1; padding:5px 9px; border-radius:6px; }
21895
+ .doc-sel-bar button:active { background:color-mix(in srgb, #000 18%, transparent); }
21253
21896
  /* add-column (right edge) and add-row (bottom edge) buttons */
21254
21897
  .doc-table-addcol { width:22px; margin-left:4px; flex:none; display:grid; place-items:center; color:var(--muted);
21255
- border:1px dashed var(--line-strong); border-radius:var(--radius-sm); background:var(--surface); transition:background .12s,color .12s,border-color .12s; }
21256
- .doc-table-addrow { width:100%; height:22px; margin-top:4px; display:grid; place-items:center; color:var(--muted);
21257
- border:1px dashed var(--line-strong); border-radius:var(--radius-sm); background:var(--surface); transition:background .12s,color .12s,border-color .12s; }
21898
+ border:1px dashed var(--line-strong); border-radius:var(--radius-sm); background:var(--surface); transition:background .12s,color .12s,border-color .12s,opacity .12s; }
21899
+ /* sticky + 100cqw (the wrap is the container): on a wide table the bar pins
21900
+ to the scrollport at any horizontal offset; narrow tables never hit the
21901
+ max-width and keep their table-wide bar. Engines without cqw just ignore
21902
+ the cap and fall back to scrolling with the table. */
21903
+ .doc-table-addrow { width:100%; max-width:100cqw; position:sticky; left:0; height:22px; margin-top:4px; display:grid; place-items:center; color:var(--muted);
21904
+ border:1px dashed var(--line-strong); border-radius:var(--radius-sm); background:var(--surface); transition:background .12s,color .12s,border-color .12s,opacity .12s; }
21258
21905
  .doc-table-addcol:hover, .doc-table-addrow:hover { background:var(--accent-soft); color:var(--accent); border-color:var(--accent); }
21906
+ /* Notion-style: the add bars only materialize while the table is active \u2014
21907
+ hover or keyboard focus on desktop, an editing (focused) cell on touch.
21908
+ Opacity-only so nothing shifts; pointer-events gates off the invisible
21909
+ bars (a touch tap must not hit a bar it can't see). */
21910
+ .doc-table-addrow, .doc-table-addcol { opacity:0; pointer-events:none; }
21911
+ .doc-table-wrap:focus-within :is(.doc-table-addrow, .doc-table-addcol) { opacity:1; pointer-events:auto; }
21912
+ @media (hover:hover) and (pointer:fine) {
21913
+ .doc-table-wrap:hover :is(.doc-table-addrow, .doc-table-addcol) { opacity:1; pointer-events:auto; }
21914
+ }
21259
21915
  /* cell range selection (box select): soft fill per cell; the accent rectangle
21260
21916
  outline is drawn via inline inset box-shadow on the edge cells (see
21261
21917
  cell-select.ts edgeShadow). While selecting, suppress the caret + native
@@ -21450,7 +22106,7 @@ var styles_default = ` :root {
21450
22106
  .shv-search { display:flex; align-items:center; gap:6px; background:var(--bg); border:1px solid var(--line-strong); border-radius:var(--radius-sm); padding:5px 10px; color:var(--muted); }
21451
22107
  .shv-search input { border:0; background:none; outline:none; color:var(--fg); font-size:13px; width:180px; }
21452
22108
 
21453
- .shv-list { display:flex; flex-direction:column; gap:8px; }
22109
+ .shv-list { display:flex; flex-direction:column; gap:8px; list-style:none; padding:0; margin:0; }
21454
22110
  .shv-row { display:flex; align-items:center; gap:13px; padding:13px 15px; background:var(--bg); border:1px solid var(--line);
21455
22111
  border-radius:var(--radius-lg); transition:border-color .12s, box-shadow .12s; }
21456
22112
  .shv-row:hover { border-color:var(--line-strong); box-shadow:var(--shadow-sm); }
@@ -21472,7 +22128,25 @@ var styles_default = ` :root {
21472
22128
  .shv-exp.dead { text-decoration:line-through; }
21473
22129
  .shv-actions { display:flex; align-items:center; gap:6px; flex:none; }
21474
22130
  .shv-actions .btn { padding:5px 10px; font-size:12px; }
21475
- @media (max-width:640px){ .shv-row{ flex-wrap:wrap; } .shv-actions{ width:100%; justify-content:flex-end; } }
22131
+ @media (max-width:640px){
22132
+ /* head stacks: title/desc full row, then segmented tabs, then search */
22133
+ .shares-page .db-head { flex-direction:column; align-items:stretch; gap:12px; }
22134
+ .shv-tools { margin-left:0; width:100%; }
22135
+ .shv-seg { display:flex; width:100%; }
22136
+ .shv-seg-btn { flex:1; text-align:center; }
22137
+ .shv-search { flex:1; }
22138
+ .shv-search input { width:100%; flex:1; }
22139
+
22140
+ /* cards: title clamps to two lines, meta wraps naturally */
22141
+ .shv-row { flex-wrap:wrap; }
22142
+ .shv-title { max-width:100%; white-space:normal; display:-webkit-box;
22143
+ -webkit-line-clamp:2; -webkit-box-orient:vertical; text-align:left; }
22144
+ .shv-meta { white-space:normal; overflow:visible; text-overflow:clip; }
22145
+
22146
+ /* actions: full row of evenly stretched touch targets */
22147
+ .shv-actions { width:100%; }
22148
+ .shv-actions .btn { flex:1; justify-content:center; padding:7px 10px; }
22149
+ }
21476
22150
 
21477
22151
  /* in-context "\u5DF2\u5206\u4EAB" badge on docs / tables / site cards */
21478
22152
  .share-badge { display:inline-grid; place-items:center; width:22px; height:22px; border-radius:6px; color:var(--accent);
@@ -21549,7 +22223,9 @@ var styles_default = ` :root {
21549
22223
  box-shadow:var(--shadow-lg); z-index:71; transform:translateX(100%); transition:transform .24s cubic-bezier(.32,.72,0,1); display:flex; flex-direction:column; }
21550
22224
  .peek.open { transform:none; }
21551
22225
  .peek-head { display:flex; align-items:center; gap:4px; padding:12px 16px; border-bottom:1px solid var(--line); }
21552
- .peek-body { overflow:auto; padding:28px 32px; }
22226
+ /* Same stable-gutter treatment as .content (stable only \u2014 see .content on
22227
+ why both-edges is forbidden). */
22228
+ .peek-body { overflow:auto; scrollbar-gutter:stable; padding:28px 32px; }
21553
22229
  .peek h2 { font-size:27px; font-weight:700; letter-spacing:-.02em; margin:0 0 20px; outline:none; }
21554
22230
  .proprow { display:flex; gap:8px; align-items:flex-start; margin:1px 0; }
21555
22231
  .proprow .k { width:160px; flex:none; color:var(--muted); display:flex; align-items:center; gap:7px; font-size:13px; padding:7px 8px; border-radius:var(--radius-sm); }
@@ -21606,7 +22282,7 @@ var styles_default = ` :root {
21606
22282
  .hist-restore { margin-top:8px; align-self:flex-start; }
21607
22283
 
21608
22284
  /* menu / popover */
21609
- .menu-layer { position:fixed; inset:0; z-index:90; }
22285
+ .menu-layer { position:fixed; inset:0; z-index:115; }
21610
22286
  .pop { position:fixed; background:var(--bg); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg);
21611
22287
  padding:6px; min-width:232px; max-height:70vh; overflow:auto; animation:pop .12s ease; }
21612
22288
  @keyframes pop { from { opacity:0; transform:translateY(-4px); } }
@@ -21624,6 +22300,9 @@ var styles_default = ` :root {
21624
22300
  .pop .item .meta { flex:1; min-width:0; }
21625
22301
  .pop .item .t { font-size:var(--fs-ui); } .pop .item .d { font-size:var(--fs-xs); color:var(--muted); display:block; }
21626
22302
  .pop .item .chk { color:var(--accent); margin-left:auto; }
22303
+ /* shortcut badge, mirrors .sb-search kbd; margin-left:auto pins it right (and
22304
+ a trailing .chk, whose own auto then collapses, rides the gap beside it) */
22305
+ .pop .item kbd { margin-left:auto; font-family:var(--mono); font-size:10px; color:var(--muted); border:1px solid var(--line-strong); border-radius:4px; padding:1px 4px; }
21627
22306
  .pop .item.danger { color:var(--danger); } .pop .item.danger .lico { color:var(--danger); }
21628
22307
  .pop .sep { height:1px; background:var(--line); margin:5px 4px; }
21629
22308
  .pop .typegrid { display:grid; grid-template-columns:repeat(4,1fr); gap:4px; padding:2px 2px 4px; }
@@ -21743,24 +22422,28 @@ var styles_default = ` :root {
21743
22422
  .view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
21744
22423
  .ico.flip { transform:scaleX(-1); }
21745
22424
 
21746
- /* ---- find in document (Ctrl-F) ---- */
21747
- /* CSS Custom Highlight overlays \u2014 no DOM mutation, safe over contentEditable. */
21748
- ::highlight(mh-find-all) { background:color-mix(in srgb, var(--accent) 26%, transparent); border-radius:2px; }
21749
- ::highlight(mh-find-current) { background:var(--accent); color:var(--accent-fg); }
22425
+ /* ---- find in document (Ctrl-F, CM6 bar) ---- */
22426
+ /* Match highlights are CM6 Decoration.marks (.cm-find/.cm-find-cur, editor-theme.ts).
22427
+ Matches INSIDE void widgets (code mirror, table cells) can't be mark
22428
+ decorations \u2014 the block replace widget swallows them \u2014 so those paint via
22429
+ the CSS Custom Highlight API (find.ts collectMatches/applyHighlights). */
22430
+ ::highlight(mh-find-all) { background: color-mix(in srgb, var(--accent) 26%, transparent); border-radius: 2px; }
22431
+ ::highlight(mh-find-current) { background: var(--accent); color: var(--accent-fg); }
21750
22432
  .find-bar {
21751
- position:fixed; top:58px; right:18px; z-index:40; /* below the ~50px topbar */
22433
+ position:fixed; top:58px; right:18px; z-index:96; /* below the ~50px topbar */
21752
22434
  display:flex; align-items:center; gap:4px;
21753
22435
  padding:5px 6px; background:var(--bg); border:1px solid var(--line-strong);
21754
22436
  border-radius:var(--radius); box-shadow:var(--shadow-lg);
21755
22437
  }
21756
22438
  .find-bar .find-ico { color:var(--muted); margin:0 2px; flex:none; }
21757
22439
  .find-bar input {
21758
- width:200px; max-width:42vw; border:0; outline:0; background:transparent;
22440
+ width:200px; max-width:42vw; min-width:0; border:0; outline:0; background:transparent;
21759
22441
  color:var(--fg); font-size:13px; padding:3px 2px;
21760
22442
  }
21761
22443
  .find-bar input::placeholder { color:var(--muted); }
21762
- .find-count { font-size:12px; color:var(--muted); min-width:48px; text-align:right; padding:0 2px; font-variant-numeric:tabular-nums; }
22444
+ .find-count { font-size:12px; color:var(--muted); min-width:48px; text-align:right; padding:0 2px; font-variant-numeric:tabular-nums; flex:none; }
21763
22445
  .find-opt {
22446
+ flex:none; white-space:nowrap;
21764
22447
  border:1px solid var(--line-strong); background:transparent; color:var(--fg-soft);
21765
22448
  border-radius:var(--radius-sm); font-size:12px; line-height:1; padding:4px 6px; cursor:pointer;
21766
22449
  transition:background .12s, color .12s, border-color .12s;
@@ -21780,6 +22463,14 @@ var styles_default = ` :root {
21780
22463
  .find-bar { left:12px; right:12px; }
21781
22464
  .find-bar input { width:auto; flex:1; max-width:none; }
21782
22465
  }
22466
+ /* Very narrow windows (quicknote resizes down to 280px): drop the reserved
22467
+ count width and tighten gaps so the option buttons never get crushed. */
22468
+ @media (max-width:360px) {
22469
+ .find-bar { gap:2px; padding:5px 4px; }
22470
+ .find-count { min-width:0; }
22471
+ }
22472
+ /* Quicknote window: its qn-bar is ~34px, not the main ~50px topbar. */
22473
+ body.quicknote .find-bar { top:42px; }
21783
22474
 
21784
22475
  /* ---- board (kanban) view ---- */
21785
22476
  .board-wrap { margin-top:4px; }
@@ -21936,8 +22627,7 @@ var styles_default = ` :root {
21936
22627
  .navitem { padding:9px 8px; font-size:16px; }
21937
22628
  .navitem .emoji { font-size:18px; }
21938
22629
  .navitem .tw { width:26px; height:26px; }
21939
- .sb-section-head { font-size:12px; }
21940
- .sb-search kbd { display:none; } /* no hardware keyboard, no shortcut hint */
22630
+ .sb-search kbd, .pop .item kbd { display:none; } /* no hardware keyboard, no shortcut hint */
21941
22631
  /* Search box is hidden until the header search button reveals it; it then
21942
22632
  slides+fades down in. Desktop base (.sb-search{display:flex}) is untouched,
21943
22633
  so search stays always-on there. reduced-motion is handled globally. */
@@ -21952,17 +22642,26 @@ var styles_default = ` :root {
21952
22642
  /* Document: bigger body text, tighter side padding for narrow screens. */
21953
22643
  .doc { padding:24px 18px calc(36vh + env(safe-area-inset-bottom)); }
21954
22644
  .doc-title { font-size:30px; }
21955
- .doc .editable { font-size:16px; }
22645
+ /* CM6 editor (single-document): its .cm-editor inherits from here, so the whole
22646
+ body incl. table cells gets 16px on mobile \u2014 same iOS anti-zoom reason. */
22647
+ .doc .cm-doc-body { font-size:16px; }
21956
22648
  /* 16px (not 14): a focused code textarea below 16px makes iOS auto-zoom. */
21957
22649
  .codeblock { --code-fs:16px; }
21958
- .block .gutter { display:none; }
21959
- /* Anchor/TOC jumps: clear the mobile topbar (49px min + notch inset);
21960
- the desktop 60px estimate leaves blocks half-hidden behind it. */
21961
- .block { scroll-margin-top:calc(49px + env(safe-area-inset-top) + 8px); }
22650
+ /* CM6 hover gutter: no hover on touch, and -52px overlays would overflow
22651
+ the tight mobile margins \u2014 hide. */
22652
+ .cm-gutter-layer { display:none; }
22653
+ /* Autofit tables: on phones the atomic code span IS the width cap \u2014 one
22654
+ long \`file-name.md\` becomes the column's min-content, eats the whole
22655
+ 100cqw budget and crushes prose columns to their 48px floor. Let code
22656
+ wrap at its natural break points (hyphens/slashes) instead; wider
22657
+ layouts keep the atomic-code tradeoff (see the autofit rules). */
22658
+ table.doc-table.autofit .doc-td code { white-space:pre-wrap; overflow-wrap:normal; word-break:normal; }
21962
22659
  /* Find bar clears the taller mobile topbar (49px min + notch inset). */
21963
22660
  .find-bar { top:calc(49px + env(safe-area-inset-top) + 8px); }
21964
22661
  /* narrower handle gutter on phones; .db left padding must be \u2265 --grip-gutter */
21965
22662
  .db { padding:16px 14px 90px 24px; }
22663
+ /* pages reusing .db without row grips don't need the left gutter \u2014 keep padding symmetric */
22664
+ .shares-page.db { padding-left:14px; }
21966
22665
  .gridhost { --grip-gutter:24px; }
21967
22666
  .peek { width:100%; }
21968
22667
  /* history drawer: the side-by-side list/preview doesn't fit a phone \u2014 stack. */
@@ -21997,13 +22696,19 @@ var styles_default = ` :root {
21997
22696
  @media (pointer: coarse) {
21998
22697
  /* iOS Safari auto-zooms the page when a focused field is below 16px; the
21999
22698
  class selectors out-specify the element-level sizes set on these. */
22000
- input, textarea, .text-input, .doc-source, input.inlineedit { font-size:16px; }
22699
+ input, textarea, .text-input, input.inlineedit { font-size:16px; }
22001
22700
  .navitem .acts button { width:34px; height:34px; }
22701
+ .sb-tab { min-height:40px; padding:8px 10px; }
22702
+ .sb-tabs .add { width:40px; height:40px; display:grid; place-items:center; padding:0; }
22002
22703
  .iconbtn { width:40px; height:40px; }
22003
22704
  .btn { padding:9px 14px; min-height:40px; }
22004
22705
  .pop .item { padding:11px 10px; }
22005
22706
  /* Column/sidebar resize handles: 7px is a mouse affordance, not a finger's. */
22006
22707
  .col-resizer, .doc-col-resizer, .sb-resizer { width:16px; right:-8px; }
22708
+ /* cell-rectangle corner handle: big enough to grab with a finger (it is the
22709
+ only way to grow a touch selection \u2014 pan gestures stay scrolls) */
22710
+ .doc-cell-handle { width:12px; height:12px; right:-6px; bottom:-6px; }
22711
+ .doc-cell-handle::after { inset:-10px; }
22007
22712
  }
22008
22713
 
22009
22714
  /* No hover available (touch): controls that only materialize on :hover are
@@ -22012,9 +22717,11 @@ var styles_default = ` :root {
22012
22717
  @media (hover: none) {
22013
22718
  .code-tools { opacity:1; transform:none; pointer-events:auto; }
22014
22719
  .rowactions { opacity:1; pointer-events:auto; }
22015
- .doc-th .doc-col-menu, .cal-add, .filerow .facts,
22016
- .navitem .acts, .sb-section-head .add { opacity:1; }
22017
- table.doc-table .doc-row-del { opacity:1; }
22720
+ .cal-add, .filerow .facts,
22721
+ .navitem .acts { opacity:1; }
22722
+ /* the pill handles survive on touch (focus-driven, not hover-driven), but
22723
+ sticky tap-hover must not tint them */
22724
+ .doc-row-handle:hover, .doc-col-handle:hover { background:var(--bg); color:var(--muted); }
22018
22725
  .selcell input { opacity:1; }
22019
22726
  .tl-item .tl-h { opacity:1; }
22020
22727
  /* Neutralize the stickiest tap-residue tints (a tap "hovers" until the
@@ -22026,12 +22733,16 @@ var styles_default = ` :root {
22026
22733
  .filerow:hover { background:none; }
22027
22734
  .cal-day:hover { background:none; }
22028
22735
  .cal-day.dim:hover { background:var(--surface); }
22736
+ .doc-table-addcol:hover, .doc-table-addrow:hover { background:var(--surface); color:var(--muted); border-color:var(--line-strong); }
22029
22737
  }
22030
22738
 
22031
22739
  /* Quick Notes window (desktop, loaded at #quick). A transparent body lets the
22032
22740
  macOS vibrancy blur show through; a translucent panel keeps text readable.
22033
22741
  The top bar is the window drag region; its buttons opt back out. */
22034
- body.quicknote { background:transparent; }
22742
+ /* --pop-top-inset: editor popups (slash menu) clamp their upward growth below
22743
+ this line, so they never cover the drag bar / macOS traffic lights (bar =
22744
+ 26px actions + 2\xD74px padding). */
22745
+ body.quicknote { background:transparent; --pop-top-inset:38px; }
22035
22746
  /* html carries an opaque bg for the iOS 26 Safari toolbar tint; the vibrancy
22036
22747
  window needs the root transparent again or the blur is walled off. */
22037
22748
  html:has(body.quicknote) { background:transparent; }
@@ -22051,22 +22762,32 @@ var styles_default = ` :root {
22051
22762
  .qn-actions { display:flex; gap:2px; -webkit-app-region:no-drag; }
22052
22763
  .qn-actions .iconbtn { width:26px; height:26px; }
22053
22764
  .qn-actions .iconbtn.active { color:var(--accent); background:var(--hover-2); }
22054
- .qn-body { flex:1; overflow:auto; -webkit-app-region:no-drag; }
22055
- .qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
22765
+ /* Quicknote scrolling: the app-wide thin translucent scrollbar (transparent
22766
+ track, so the reserved lane just reads as right margin). stable reserves
22767
+ the lane up front so width never jumps when the doc grows past one
22768
+ screen; the doc's right padding + lane \u2248 left padding, keeping the prose
22769
+ visually centered. stable only, NEVER both-edges \u2014 see .content: a left
22770
+ gutter desyncs Chromium's scroll cull rect and paints a blank stripe over
22771
+ the rightmost 15px of content (it sliced this window's file cards). */
22772
+ /* scrollbar-width does NOT inherit (unlike scrollbar-color): without an
22773
+ explicit thin here the lane is the classic 15px. thin = 11px, so the
22774
+ doc's 1px right padding + 11px lane mirrors the 12px left padding. */
22775
+ .qn-body { flex:1; overflow:auto; scrollbar-gutter:stable; scrollbar-width:thin; -webkit-app-region:no-drag; }
22776
+ .qn-body .doc { max-width:none; margin:0; padding:14px 1px 30vh 12px; }
22056
22777
  .qn-body .doc-title { font-size:24px; margin-bottom:2px; }
22057
22778
  .qn-body .doc-meta { display:none; }
22058
- /* The narrow-width gutter hide is now gated on (pointer:coarse), which this
22059
- desktop window never matches \u2014 re-hide explicitly: quick notes offer no
22779
+ /* The CM6 hover gutter never fits the compact window: quick notes offer no
22060
22780
  block insert/drag handles (Enter / slash commands cover insertion). */
22061
- body.quicknote .block .gutter { display:none; }
22781
+ body.quicknote .cm-gutter-layer { display:none; }
22062
22782
  /* A native checkbox paints an opaque white box when unchecked, which breaks
22063
22783
  the window's vibrancy blur. Draw a custom box so the unchecked background
22064
- stays transparent; checked falls back to the accent fill + checkmark. */
22065
- body.quicknote .b-todo .marker input {
22784
+ stays transparent; checked falls back to the accent fill + checkmark.
22785
+ (Same treatment main gave \`.b-todo .marker input\`.) */
22786
+ body.quicknote .cm-todo-box {
22066
22787
  appearance:none; -webkit-appearance:none; box-sizing:border-box;
22067
22788
  border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
22068
22789
  border-radius:4px; background:transparent; }
22069
- body.quicknote .b-todo .marker input:checked {
22790
+ body.quicknote .cm-todo-box:checked {
22070
22791
  border-color:var(--accent);
22071
22792
  background:var(--accent) center/10px no-repeat
22072
22793
  url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12l5 5L20 7'/%3E%3C/svg%3E"); }
@@ -22088,8 +22809,11 @@ var styles_default = ` :root {
22088
22809
  .qn-set-main { flex:1; min-width:0; }
22089
22810
  .qn-set-name { font-weight:600; }
22090
22811
  .qn-set-desc { color:var(--muted); font-size:13px; margin-top:2px; }
22091
- .qn-shortcut { min-width:140px; justify-content:center; font-family:var(--mono); }
22812
+ .qn-shortcut { min-width:140px; justify-content:center; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC",sans-serif; }
22092
22813
  .qn-shortcut.capturing { outline:2px solid var(--accent); color:var(--accent); }
22814
+ .qn-keys { display:inline-flex; align-items:center; gap:6px; }
22815
+ .qn-key { line-height:1; }
22816
+ .qn-key.sym { font-size:1.15em; line-height:1; } /* \u2318 \u21E7 glyphs render smaller \u2014 bump to match letters */
22093
22817
 
22094
22818
  /* No-origin bootstrap: enroll screen + hydration splash (data-blind shell). */
22095
22819
  .enroll-splash { min-height:100dvh; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px; padding:24px; }
@@ -22103,6 +22827,44 @@ var styles_default = ` :root {
22103
22827
  .enroll-err { color:var(--danger); font-size:13px; margin-top:12px; }
22104
22828
  .enroll-go { width:100%; justify-content:center; margin-top:18px; }
22105
22829
  .enroll-hint { font-size:12px; margin-top:14px; line-height:1.5; }
22830
+ .enroll-card { animation:enroll-in .35s ease both; }
22831
+ @keyframes enroll-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
22832
+
22833
+ /* Scan-first enroll: square viewfinder (corner frame + sweeping accent beam)
22834
+ over the live camera feed. The chip stays black in both themes \u2014 the feed
22835
+ brings its own colors; accent carries the beam and the success flash. */
22836
+ .enroll-scan { position:relative; aspect-ratio:1/1; margin:2px 0 12px; border-radius:16px; overflow:hidden; background:#0b0b0e; }
22837
+ .enroll-scan video { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }
22838
+ .enroll-scan::after { content:""; position:absolute; inset:0; border-radius:inherit; pointer-events:none; box-shadow:inset 0 0 44px rgba(0,0,0,.35); }
22839
+ .enroll-scan-frame { position:absolute; inset:0; width:100%; height:100%; fill:none; stroke:rgba(255,255,255,.92); stroke-width:2.5; stroke-linecap:round; pointer-events:none; }
22840
+ .enroll-scan-beam { position:absolute; left:9%; right:9%; top:12%; height:2px; border-radius:2px; pointer-events:none; opacity:.85;
22841
+ background:linear-gradient(90deg, transparent, var(--accent), transparent);
22842
+ animation:enroll-beam 2.4s ease-in-out infinite alternate; }
22843
+ @keyframes enroll-beam { to { top:86%; } }
22844
+ .enroll-scan.hit .enroll-scan-frame { stroke:var(--accent); }
22845
+ .enroll-scan.hit::after { animation:enroll-hit .38s ease-out; }
22846
+ @keyframes enroll-hit { from { box-shadow:inset 0 0 0 3px var(--accent), inset 0 0 60px rgba(0,0,0,.2); } to { box-shadow:inset 0 0 44px rgba(0,0,0,.35); } }
22847
+ .enroll-scan-wait { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:rgba(255,255,255,.45); }
22848
+ .enroll-scan-waitico { width:30px; height:30px; animation:enroll-wait 1.6s ease-in-out infinite; }
22849
+ @keyframes enroll-wait { 0%,100% { opacity:.4; } 50% { opacity:.9; } }
22850
+ .enroll-cam-err { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; padding:20px; text-align:center; color:rgba(255,255,255,.9); font-size:13px; }
22851
+ .enroll-cam-err .muted { color:rgba(255,255,255,.55); font-size:12px; }
22852
+ .enroll-cam-errico { width:26px; height:26px; opacity:.7; }
22853
+ .enroll-cam-err .btn { margin-top:8px; }
22854
+ .enroll-paste { width:100%; font-size:13px; }
22855
+ .enroll-switch { display:flex; justify-content:center; align-items:center; gap:10px; margin-top:14px; }
22856
+ .enroll-switch-dot { color:var(--muted); }
22857
+ .enroll-link { background:none; border:0; padding:0; font:inherit; font-size:13px; color:var(--accent); cursor:pointer; }
22858
+ .enroll-link:hover { text-decoration:underline; }
22859
+ /* Unlock step: compact bucket summary chip above the passphrase field. */
22860
+ .enroll-bucket-card { display:flex; align-items:center; gap:10px; padding:12px 14px; margin:2px 0 14px; border:1px solid var(--line); border-radius:12px; background:var(--bg); }
22861
+ .enroll-bucket-ico { width:18px; height:18px; color:var(--muted); }
22862
+ .enroll-bucket-meta { flex:1; min-width:0; }
22863
+ .enroll-bucket-name { font-weight:600; font-size:14px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
22864
+ .enroll-bucket-host { font-family:var(--mono); font-size:12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
22865
+ .enroll-bucket-badge { display:inline-flex; align-items:center; gap:4px; flex:none; font-size:11px; color:var(--accent); padding:3px 8px; border-radius:999px;
22866
+ border:1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
22867
+ .enroll-bucket-badge .ico { width:12px; height:12px; }
22106
22868
 
22107
22869
  /* Enroll QR (settings \u2192 \u540C\u6B65\u5B58\u50A8 \u2192 \u5728\u624B\u673A\u4E0A\u6253\u5F00). The QR inverts with the
22108
22870
  theme: light = dark modules on a light chip, dark = light modules on a dark
@@ -22305,6 +23067,42 @@ var ICON_192 = "iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAAAXNSR0IArs4c6QAA
22305
23067
  var ICON_512 = "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";
22306
23068
  var ICON_180 = "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";
22307
23069
 
23070
+ // src/webui/fmt/manifest.ts
23071
+ var FMT_PROVIDERS = [
23072
+ { id: "core", js: "/webui-fmt.js", entry: "fmt/fmt-core.ts" },
23073
+ {
23074
+ id: "ruff",
23075
+ js: "/webui-fmt-ruff.js",
23076
+ entry: "fmt/fmt-ruff.ts",
23077
+ wasm: { route: "/webui-fmt-ruff.wasm", pkg: "@wasm-fmt/ruff_fmt", file: "ruff_fmt_bg.wasm" }
23078
+ },
23079
+ {
23080
+ id: "gofmt",
23081
+ js: "/webui-fmt-gofmt.js",
23082
+ entry: "fmt/fmt-gofmt.ts",
23083
+ wasm: { route: "/webui-fmt-gofmt.wasm", pkg: "@wasm-fmt/gofmt", file: "gofmt.wasm" }
23084
+ },
23085
+ {
23086
+ id: "clang",
23087
+ js: "/webui-fmt-clang.js",
23088
+ entry: "fmt/fmt-clang.ts",
23089
+ wasm: { route: "/webui-fmt-clang.wasm", pkg: "@wasm-fmt/clang-format", file: "clang-format.wasm" }
23090
+ },
23091
+ {
23092
+ id: "lua",
23093
+ js: "/webui-fmt-lua.js",
23094
+ entry: "fmt/fmt-lua.ts",
23095
+ wasm: { route: "/webui-fmt-lua.wasm", pkg: "@wasm-fmt/lua_fmt", file: "lua_fmt_bg.wasm" }
23096
+ },
23097
+ {
23098
+ id: "taplo",
23099
+ js: "/webui-fmt-taplo.js",
23100
+ entry: "fmt/fmt-taplo.ts",
23101
+ wasm: { route: "/webui-fmt-taplo.wasm", pkg: "@wasm-fmt/taplo_fmt", file: "taplo_fmt_bg.wasm" }
23102
+ },
23103
+ { id: "sh", js: "/webui-fmt-sh.js", entry: "fmt/fmt-sh.ts" }
23104
+ ];
23105
+
22308
23106
  // src/webui/server/assets.ts
22309
23107
  var RUNNING_FROM_SOURCE = import.meta.url.includes("/src/webui/");
22310
23108
  var HTML2 = `<!doctype html>
@@ -22349,14 +23147,25 @@ var injected = {
22349
23147
  sdk: null
22350
23148
  };
22351
23149
  var injectedWasm = null;
23150
+ var injectedFmtWasm = {};
23151
+ function toArrayBuffer(w) {
23152
+ return w.byteOffset === 0 && w.byteLength === w.buffer.byteLength ? w.buffer : w.slice().buffer;
23153
+ }
22352
23154
  function setWebuiBundle(bundle) {
22353
23155
  injected.js = bundle.js;
22354
23156
  injected.sw = bundle.sw;
22355
23157
  injected.dbWorker = bundle.dbWorker;
22356
23158
  injected.runtime = bundle.runtime;
22357
23159
  injected.sdk = bundle.sdk;
22358
- const w = bundle.wasm;
22359
- injectedWasm = w.byteOffset === 0 && w.byteLength === w.buffer.byteLength ? w.buffer : w.slice().buffer;
23160
+ injectedWasm = toArrayBuffer(bundle.wasm);
23161
+ for (const p of FMT_PROVIDERS) {
23162
+ const js = bundle.fmt?.[p.js];
23163
+ if (js != null)
23164
+ injected[`fmt_${p.id}`] = js;
23165
+ const w = p.wasm && bundle.fmtWasm?.[p.wasm.route];
23166
+ if (p.wasm && w)
23167
+ injectedFmtWasm[p.wasm.route] = toArrayBuffer(w);
23168
+ }
22360
23169
  }
22361
23170
  function newestSourceMtime(dir) {
22362
23171
  let newest = 0;
@@ -22406,6 +23215,24 @@ function bundleGetter(opts) {
22406
23215
  }
22407
23216
  var getJs = bundleGetter({ key: "js", entry: "../app.tsx", dist: "webui.js" });
22408
23217
  var getSwRaw = bundleGetter({ key: "sw", entry: "../sw.ts", dist: "sw.js" });
23218
+ var fmtJs = {};
23219
+ for (const p of FMT_PROVIDERS) {
23220
+ injected[`fmt_${p.id}`] = null;
23221
+ fmtJs[p.js] = bundleGetter({ key: `fmt_${p.id}`, entry: `../${p.entry}`, dist: p.js.slice(1) });
23222
+ }
23223
+ var fmtWasmByRoute = Object.fromEntries(FMT_PROVIDERS.filter((p) => p.wasm).map((p) => [p.wasm.route, p]));
23224
+ async function getFmtWasm(p) {
23225
+ const w = p.wasm;
23226
+ const embedded = injectedFmtWasm[w.route];
23227
+ if (embedded != null)
23228
+ return embedded;
23229
+ const path = RUNNING_FROM_SOURCE ? join4(dirname(Bun.resolveSync(w.pkg, fileURLToPath(new URL(".", import.meta.url)))), w.file) : fileURLToPath(new URL(`.${w.route}`, import.meta.url));
23230
+ const f = Bun.file(path);
23231
+ if (!await f.exists()) {
23232
+ throw new Error(`${w.route} missing at ${path} \u2014 run \`bun install\` / \`bun run build\``);
23233
+ }
23234
+ return f.arrayBuffer();
23235
+ }
22409
23236
  var getDbWorker = bundleGetter({ key: "dbWorker", entry: "../data/db-worker.ts", dist: "db-worker.js" });
22410
23237
  var getRuntime = bundleGetter({ key: "runtime", entry: "../runtime.ts", dist: "mh-runtime.js" });
22411
23238
  var getSdk = bundleGetter({ key: "sdk", entry: "../../sdk/client.ts", dist: "metahub-sdk.js", extraDirs: ["../../sdk"] });
@@ -22487,12 +23314,24 @@ async function serveWebui(req) {
22487
23314
  return new Response(`sqlite3.wasm unavailable: ${e}`, { status: 500 });
22488
23315
  }
22489
23316
  }
23317
+ const fmtWasmProvider = fmtWasmByRoute[pathname];
23318
+ if (fmtWasmProvider) {
23319
+ try {
23320
+ return new Response(await getFmtWasm(fmtWasmProvider), {
23321
+ headers: { "content-type": "application/wasm", "cache-control": "public, max-age=86400" }
23322
+ });
23323
+ } catch (e) {
23324
+ console.error(`[webui] failed to serve ${pathname} \u2014`, e);
23325
+ return new Response(`${pathname} unavailable: ${e}`, { status: 500 });
23326
+ }
23327
+ }
22490
23328
  const script = {
22491
23329
  "/webui.js": getJsStamped,
22492
23330
  "/sw.js": getSw,
22493
23331
  "/db-worker.js": getDbWorker,
22494
23332
  "/mh-runtime.js": getRuntime,
22495
- "/metahub-sdk.js": getSdk
23333
+ "/metahub-sdk.js": getSdk,
23334
+ ...fmtJs
22496
23335
  };
22497
23336
  const getter = script[pathname];
22498
23337
  if (getter) {
@@ -24370,6 +25209,7 @@ export {
24370
25209
  ensureDirs,
24371
25210
  emitFields,
24372
25211
  emit,
25212
+ editDocumentBatch,
24373
25213
  editDocument,
24374
25214
  duplicateDocument,
24375
25215
  duplicateDatabase,
@@ -24422,4 +25262,4 @@ export {
24422
25262
  BLOB_HASH_HEX
24423
25263
  };
24424
25264
 
24425
- //# debugId=53C623352D26C68364756E2164756E21
25265
+ //# debugId=F05E548741611AE364756E2164756E21