@tensorix/metahub 0.1.3 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -121,7 +121,7 @@ function observeHlc(db, node, remote, now = Date.now()) {
121
121
  }
122
122
 
123
123
  // src/core/blocks.ts
124
- function parseBlocks(md) {
124
+ function parseDocBlocks(md) {
125
125
  if (!md)
126
126
  return [];
127
127
  const lines = md.replace(/\r\n?/g, `
@@ -131,24 +131,24 @@ function parseBlocks(md) {
131
131
  let cur = [];
132
132
  let fenceChar = null;
133
133
  let fenceLen = 0;
134
- const flushPara = () => {
135
- while (cur.length && cur[cur.length - 1].trim() === "")
136
- cur.pop();
137
- while (cur.length && cur[0].trim() === "")
138
- cur.shift();
139
- if (cur.length)
140
- blocks.push(cur.join(`
141
- `));
142
- cur = [];
134
+ const closeBlock = () => {
135
+ if (cur.length) {
136
+ blocks.push({ text: cur.join(`
137
+ `), blankAfter: 0 });
138
+ cur = [];
139
+ }
140
+ };
141
+ const onBlank = () => {
142
+ closeBlock();
143
+ if (blocks.length)
144
+ blocks[blocks.length - 1].blankAfter++;
143
145
  };
144
146
  for (const raw of lines) {
145
147
  if (fenceChar) {
146
148
  cur.push(raw);
147
149
  const m = raw.match(/^\s*(`{3,}|~{3,})\s*$/);
148
150
  if (m && m[1][0] === fenceChar && m[1].length >= fenceLen) {
149
- blocks.push(cur.join(`
150
- `));
151
- cur = [];
151
+ closeBlock();
152
152
  fenceChar = null;
153
153
  fenceLen = 0;
154
154
  }
@@ -156,28 +156,34 @@ function parseBlocks(md) {
156
156
  }
157
157
  const open = raw.match(/^\s*(`{3,}|~{3,})/);
158
158
  if (open) {
159
- flushPara();
159
+ closeBlock();
160
160
  fenceChar = open[1][0];
161
161
  fenceLen = open[1].length;
162
162
  cur.push(raw);
163
163
  } else if (raw.trim() === "") {
164
- flushPara();
164
+ onBlank();
165
165
  } else {
166
166
  cur.push(raw);
167
167
  }
168
168
  }
169
- if (fenceChar) {
170
- if (cur.length)
171
- blocks.push(cur.join(`
172
- `));
173
- } else {
174
- flushPara();
175
- }
169
+ closeBlock();
170
+ for (let i = 0;i < blocks.length - 1; i++)
171
+ blocks[i].blankAfter = Math.max(0, blocks[i].blankAfter - 1);
176
172
  return blocks;
177
173
  }
178
- function serializeBlocks(texts) {
179
- return texts.filter((t) => !!t && t.length > 0).join(`
180
-
174
+ function serializeDocBlocks(blocks) {
175
+ const live = blocks.filter((b) => !!b.text && b.text.length > 0);
176
+ const out = [];
177
+ live.forEach((b, i) => {
178
+ out.push(...b.text.split(`
179
+ `));
180
+ const extra = Math.max(0, b.blankAfter ?? 0);
181
+ if (i < live.length - 1)
182
+ out.push("");
183
+ for (let k = 0;k < extra; k++)
184
+ out.push("");
185
+ });
186
+ return out.join(`
181
187
  `);
182
188
  }
183
189
  function reconcile(oldTexts, newTexts) {
@@ -234,9 +240,9 @@ function isBlockManaged(db, docId) {
234
240
  return db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId) != null;
235
241
  }
236
242
  function recomputeDocBody(db, docId) {
237
- const rows = db.query("SELECT text FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
243
+ const rows = db.query("SELECT text, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
238
244
  ensureRow(db, "documents", docId);
239
- db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeBlocks(rows.map((r) => r.text)), docId);
245
+ db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeDocBlocks(rows.map((r) => ({ text: r.text, blankAfter: r.blank_after ?? 0 }))), docId);
240
246
  }
241
247
  function materialize(db, dataset, rowId, col, valueJson) {
242
248
  if (dataset === "records") {
@@ -336,7 +342,7 @@ var init_crdt = __esm(() => {
336
342
  },
337
343
  doc_blocks: {
338
344
  table: "doc_blocks",
339
- cols: new Set(["doc_id", "text", "order_key", "__deleted"])
345
+ cols: new Set(["doc_id", "text", "order_key", "blank_after", "__deleted"])
340
346
  },
341
347
  sites: {
342
348
  table: "sites",
@@ -531,9 +537,14 @@ var init_sites = __esm(() => {
531
537
  // src/core/sync/webui.ts
532
538
  var exports_webui = {};
533
539
  __export(exports_webui, {
540
+ warmWebui: () => warmWebui,
541
+ setWebuiBundle: () => setWebuiBundle,
534
542
  serveWebui: () => serveWebui
535
543
  });
536
544
  import { fileURLToPath } from "url";
545
+ function setWebuiBundle(js) {
546
+ cachedJs = js;
547
+ }
537
548
  async function getJs() {
538
549
  if (cachedJs != null)
539
550
  return cachedJs;
@@ -549,6 +560,11 @@ async function getJs() {
549
560
  cachedJs = await res.outputs[0].text();
550
561
  return cachedJs;
551
562
  }
563
+ async function warmWebui() {
564
+ try {
565
+ await getJs();
566
+ } catch {}
567
+ }
552
568
  async function serveWebui(req) {
553
569
  if (req.method !== "GET")
554
570
  return null;
@@ -575,7 +591,13 @@ var HTML = `<!doctype html>
575
591
  <title>Metahub</title>
576
592
  <link rel="preconnect" href="https://fonts.googleapis.com">
577
593
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
578
- <link href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
594
+ <!-- Load web fonts without blocking first paint: media="print" keeps the
595
+ stylesheet non-render-blocking, then onload flips it to "all". The page
596
+ paints instantly with the system-font fallbacks baked into --ui/--mono and
597
+ swaps to the web fonts when they arrive (or stays on system fonts offline).
598
+ This is what kept the desktop cold-start white for seconds on slow networks. -->
599
+ <link rel="stylesheet" media="print" onload="this.media='all'" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap">
600
+ <noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Hanken+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"></noscript>
579
601
  <script>try{document.documentElement.dataset.theme=localStorage.getItem('mh-theme')||'system'}catch(e){}</script>
580
602
  <style>
581
603
  :root {
@@ -589,6 +611,7 @@ var HTML = `<!doctype html>
589
611
  --radius:8px; --radius-sm:6px;
590
612
  --ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
591
613
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
614
+ --note-lh:1.6;
592
615
  --code-bg:#f8f8f6;
593
616
  --hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
594
617
  --hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
@@ -621,7 +644,7 @@ var HTML = `<!doctype html>
621
644
  * { box-sizing: border-box; }
622
645
  html, body { margin: 0; height: 100%; }
623
646
  body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
624
- overflow: hidden; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
647
+ overflow: hidden; text-rendering: auto; }
625
648
  button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
626
649
  input, textarea { font: inherit; color: inherit; }
627
650
  .muted { color: var(--muted); }
@@ -677,6 +700,8 @@ var HTML = `<!doctype html>
677
700
  .dragging { opacity:.35; }
678
701
  .sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
679
702
  .sb-footer .navitem { font-size:13.5px; }
703
+ .nav-dot { flex:none; width:7px; height:7px; border-radius:50%; background:var(--danger);
704
+ box-shadow:0 0 0 2px var(--surface); margin-right:2px; }
680
705
 
681
706
  /* settings page */
682
707
  .set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
@@ -685,6 +710,20 @@ var HTML = `<!doctype html>
685
710
  .set-section { margin-top:34px; }
686
711
  .set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
687
712
  .set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
713
+ .set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
714
+ flex-wrap:wrap; justify-content:center; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
715
+ .set-footer-sep { opacity:.45; }
716
+ .ver-num { font-family:var(--mono); font-size:11px; color:var(--fg-soft); letter-spacing:-.01em; }
717
+ .ver-up { display:inline-flex; align-items:center; gap:6px; }
718
+ .ver-up.err .ver-msg { color:var(--danger); }
719
+ .ver-act { background:none; border:0; padding:0; margin:0; font:inherit; font-size:12px; line-height:1;
720
+ color:var(--muted); cursor:pointer; transition:color .12s; }
721
+ .ver-act:hover { color:var(--accent); }
722
+ .ver-act.accent { color:var(--accent); font-weight:600; }
723
+ .ver-act[aria-disabled="true"] { opacity:.6; cursor:default; pointer-events:none; }
724
+ .ver-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none; }
725
+ .ver-dot.pulse { animation:verpulse 1.8s ease-in-out infinite; }
726
+ @keyframes verpulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
688
727
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
689
728
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
690
729
  text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
@@ -722,6 +761,7 @@ var HTML = `<!doctype html>
722
761
  /* main */
723
762
  .main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
724
763
  .topbar { display:flex; align-items:center; gap:6px; padding:10px 18px; border-bottom:1px solid var(--line); min-height:49px; }
764
+ .topbar.bare { border-bottom:0; }
725
765
  .topbar .hamburger { display:none; }
726
766
  .topbar .hamburger.show-collapsed { display:grid; }
727
767
  .sidebar.collapsed { overflow:hidden; }
@@ -776,8 +816,10 @@ var HTML = `<!doctype html>
776
816
  /* The list item's host gutter controls the whole unit, so suppress the nested
777
817
  code block's own gutter (it would overlap the list marker on the same row). */
778
818
  .block.list-code-host + .block-wrap.nested .gutter { display:none; }
779
- .editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:1.6; }
819
+ .editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
780
820
  .editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
821
+ /* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
822
+ .editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
781
823
  .b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
782
824
  .b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
783
825
  .b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
@@ -829,7 +871,8 @@ var HTML = `<!doctype html>
829
871
  .hljs-emphasis { font-style:italic; }
830
872
  .hljs-strong { font-weight:700; }
831
873
  .marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
832
- .b-todo .marker { padding-top:5px; }
874
+ .b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
875
+ display:flex; align-items:center; justify-content:center; }
833
876
  .b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
834
877
  .b-divider { padding:10px 0; }
835
878
  .b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
@@ -1016,14 +1059,14 @@ var HTML = `<!doctype html>
1016
1059
  .optrow .x:hover { background:var(--hover-2); color:var(--danger); }
1017
1060
 
1018
1061
  /* modal */
1019
- .modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:grid; place-items:center;
1062
+ .modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
1020
1063
  opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
1021
1064
  .modal-scrim.open { opacity:1; pointer-events:auto; }
1022
- .modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; overflow:hidden; }
1065
+ .modal { background:var(--bg); border-radius:16px; box-shadow:var(--shadow-lg); width:440px; max-width:100%; max-height:100%; display:flex; flex-direction:column; overflow:hidden; }
1023
1066
  .modal-head { padding:20px 22px 0; }
1024
1067
  .modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
1025
1068
  .modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
1026
- .modal-body { padding:18px 22px; }
1069
+ .modal-body { padding:18px 22px; overflow-y:auto; }
1027
1070
  .modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
1028
1071
  .field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
1029
1072
  .field-label:first-child { margin-top:0; }
@@ -1046,7 +1089,71 @@ var HTML = `<!doctype html>
1046
1089
  @keyframes slidein { from { opacity:0; transform:translateX(-12px); } }
1047
1090
 
1048
1091
  /* misc */
1049
- .empty { display:grid; place-items:center; height:100%; color:var(--muted); text-align:center; padding:40px; }
1092
+ .empty { display:flex; flex-direction:column; align-items:center; justify-content:center; height:100%;
1093
+ color:var(--muted); text-align:center; padding:40px; }
1094
+
1095
+ /* ---- empty state: \u77E5\u8BC6\u751F\u957F\u63D2\u753B ---- */
1096
+ .estate-art { width:240px; height:208px; overflow:visible; animation:idleFloat 5s ease-in-out 1.6s infinite; }
1097
+ .estate-art .stroke-a { fill:none; stroke:var(--accent); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
1098
+ .estate-art .stroke { fill:none; stroke:var(--line-strong); stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
1099
+ .estate-art .fill-a { fill:var(--accent); }
1100
+ .estate-art .fill-soft { fill:var(--accent-soft); }
1101
+ .estate-art .fill-surface { fill:var(--bg); }
1102
+ .estate-art .ground { fill:var(--accent); }
1103
+ .estate-title { margin:28px 0 0; font-size:18px; font-weight:600; letter-spacing:.2px; color:var(--fg);
1104
+ opacity:0; animation:fadeUp .5s ease forwards 1.0s; }
1105
+ .estate-hint { margin:9px 0 0; font-size:13.5px; line-height:1.6; color:var(--muted); max-width:300px;
1106
+ opacity:0; animation:fadeUp .5s ease forwards 1.15s; }
1107
+ .estate-link { margin:18px 0 0; font-size:14px; font-weight:600; color:var(--accent);
1108
+ display:inline-flex; align-items:center; gap:6px; padding:9px 8px; border-radius:var(--radius-sm);
1109
+ opacity:0; animation:fadeUp .5s ease forwards 1.3s; transition:opacity .15s; }
1110
+ .estate-link:hover { opacity:.7; }
1111
+ /* intro */
1112
+ .draw { stroke-dasharray:100; stroke-dashoffset:100; }
1113
+ @keyframes draw { to { stroke-dashoffset:0; } }
1114
+ @keyframes leafGrow { from { opacity:0; transform:scale(.2) rotate(-10deg); } to { opacity:1; transform:scale(1) rotate(0); } }
1115
+ @keyframes cardUp { from { opacity:0; transform:translateY(16px) scale(.96); } to { opacity:1; transform:translateY(0) scale(1); } }
1116
+ @keyframes groundIn { to { opacity:.1; } }
1117
+ @keyframes glowIn { to { opacity:.6; } }
1118
+ @keyframes fadeUp { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:translateY(0); } }
1119
+ /* idle loops \u2014 each 0% equals the settled rest state to avoid a jump */
1120
+ @keyframes idleFloat { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
1121
+ @keyframes plantSway { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-1.3deg); } 75% { transform:rotate(1.3deg); } }
1122
+ @keyframes leafSwayA { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(-2.6deg); } 75% { transform:rotate(2.6deg); } }
1123
+ @keyframes leafSwayB { 0%,100% { transform:rotate(0deg); } 25% { transform:rotate(2.6deg); } 75% { transform:rotate(-2.6deg); } }
1124
+ @keyframes groundBreathe { 0%,100% { transform:scale(1); opacity:.10; } 50% { transform:scale(1.07); opacity:.15; } }
1125
+ @keyframes glowPulse { 0%,100% { transform:scale(1); opacity:.6; } 50% { transform:scale(1.1); opacity:.85; } }
1126
+ @keyframes particle { 0% { opacity:0; transform:translate(0,0) scale(.4); } 35% { opacity:.85; transform:translate(2px,-4px) scale(1); } 70% { opacity:0; transform:translate(4px,-9px) scale(.5); } 100% { opacity:0; transform:translate(0,0) scale(.4); } }
1127
+ /* wiring */
1128
+ .kg-ground { transform-box:fill-box; transform-origin:center; opacity:0;
1129
+ animation:groundIn .5s ease forwards .1s, groundBreathe 6s ease-in-out 1.6s infinite; }
1130
+ .kg-glow { filter:blur(9px); transform-box:fill-box; transform-origin:center; opacity:0;
1131
+ animation:glowIn .6s ease forwards .1s, glowPulse 4.6s ease-in-out 1.6s infinite; }
1132
+ .kg-cardB { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .2s; }
1133
+ .kg-cardA { transform-box:fill-box; transform-origin:center; opacity:0; animation:cardUp .55s cubic-bezier(.2,.8,.2,1) forwards .32s; }
1134
+ .kg-plant { transform-box:fill-box; transform-origin:50% 100%; animation:plantSway 6s ease-in-out 1.7s infinite; }
1135
+ .kg-stem { animation:draw .9s ease-out forwards .4s; }
1136
+ .kg-leaf { transform-box:fill-box; }
1137
+ .kg-grow { transform-box:fill-box; opacity:0; }
1138
+ .kg-leaf.l1 { transform-origin:90% 100%; animation:leafSwayA 4.6s ease-in-out 1.7s infinite; }
1139
+ .kg-leaf.l2 { transform-origin:0% 80%; animation:leafSwayB 5.2s ease-in-out 1.8s infinite; }
1140
+ .kg-leaf.l3 { transform-origin:0% 100%; animation:leafSwayA 5.0s ease-in-out 1.9s infinite; }
1141
+ .kg-leaf.l1 .kg-grow { transform-origin:90% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards .85s; }
1142
+ .kg-leaf.l2 .kg-grow { transform-origin:0% 80%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.0s; }
1143
+ .kg-leaf.l3 .kg-grow { transform-origin:0% 100%; animation:leafGrow .52s cubic-bezier(.18,.9,.24,1.22) forwards 1.15s; }
1144
+ .kg-particle { transform-box:fill-box; transform-origin:center; opacity:0; }
1145
+ .kg-particle.p1 { animation:particle 4.5s ease-in-out 1.6s infinite; }
1146
+ .kg-particle.p2 { animation:particle 5.2s ease-in-out 2.0s infinite; }
1147
+ .kg-particle.p3 { animation:particle 4.8s ease-in-out 2.6s infinite; }
1148
+ .kg-particle.p4 { animation:particle 5.6s ease-in-out 3.1s infinite; }
1149
+ .kg-particle.p5 { animation:particle 4.2s ease-in-out 2.3s infinite; }
1150
+ @media (prefers-reduced-motion: reduce) {
1151
+ .estate-art, .estate-art *, .estate-title, .estate-hint, .estate-link { animation:none !important; opacity:1 !important; transform:none !important; }
1152
+ .draw { stroke-dashoffset:0 !important; }
1153
+ .kg-ground { opacity:.1 !important; }
1154
+ .kg-glow { opacity:.6 !important; }
1155
+ .kg-particle { opacity:0 !important; }
1156
+ }
1050
1157
  .error-bar { margin:12px 24px; padding:10px 14px; background:var(--danger-soft); color:var(--danger); border-radius:var(--radius-sm); font-size:13px; cursor:pointer; }
1051
1158
  .view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
1052
1159
  .ico.flip { transform:scaleX(-1); }
@@ -1178,6 +1285,17 @@ var HTML = `<!doctype html>
1178
1285
  .qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
1179
1286
  .qn-body .doc-title { font-size:24px; margin-bottom:2px; }
1180
1287
  .qn-body .doc-meta { display:none; }
1288
+ /* A native checkbox paints an opaque white box when unchecked, which breaks
1289
+ the window's vibrancy blur. Draw a custom box so the unchecked background
1290
+ stays transparent; checked falls back to the accent fill + checkmark. */
1291
+ body.quicknote .b-todo .marker input {
1292
+ appearance:none; -webkit-appearance:none; box-sizing:border-box;
1293
+ border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
1294
+ border-radius:4px; background:transparent; }
1295
+ body.quicknote .b-todo .marker input:checked {
1296
+ border-color:var(--accent);
1297
+ background:var(--accent) center/10px no-repeat
1298
+ 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"); }
1181
1299
  /* Compact menus inside the quick-note window only (dropdown + slash command
1182
1300
  share .pop/.item/.lico with the main app; scope under body.quicknote so the
1183
1301
  main app keeps its roomier density). */
@@ -1350,11 +1468,12 @@ CREATE TABLE IF NOT EXISTS documents (
1350
1468
  -- independent CRDT register so concurrent edits to different blocks merge.
1351
1469
  -- order_key is a fractional index (ties broken by id) giving display order.
1352
1470
  CREATE TABLE IF NOT EXISTS doc_blocks (
1353
- id TEXT PRIMARY KEY,
1354
- doc_id TEXT,
1355
- text TEXT,
1356
- order_key TEXT,
1357
- __deleted INTEGER NOT NULL DEFAULT 0
1471
+ id TEXT PRIMARY KEY,
1472
+ doc_id TEXT,
1473
+ text TEXT,
1474
+ order_key TEXT,
1475
+ blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)
1476
+ __deleted INTEGER NOT NULL DEFAULT 0
1358
1477
  );
1359
1478
  CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
1360
1479
 
@@ -1426,9 +1545,20 @@ function deleteDatabase(db, id) {
1426
1545
  if (!getDatabase(db, id))
1427
1546
  return false;
1428
1547
  emit(db, "databases", id, "__deleted", 1);
1548
+ const tombstone = (table) => {
1549
+ for (const r of liveChildren(db, table, "database_id", id))
1550
+ emit(db, table, r, "__deleted", 1);
1551
+ };
1552
+ tombstone("properties");
1553
+ tombstone("records");
1554
+ for (const d of liveChildren(db, "documents", "database_id", id))
1555
+ emit(db, "documents", d, "database_id", null);
1429
1556
  db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
1430
1557
  return true;
1431
1558
  }
1559
+ function liveChildren(db, table, col, parentId) {
1560
+ return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r) => r.id);
1561
+ }
1432
1562
 
1433
1563
  // src/core/properties.ts
1434
1564
  init_ids();
@@ -1557,9 +1687,13 @@ function setPropertyWidth(db, id, width) {
1557
1687
  return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w } });
1558
1688
  }
1559
1689
  function removeProperty(db, id) {
1560
- if (!getProperty(db, id))
1690
+ const prop = getProperty(db, id);
1691
+ if (!prop)
1561
1692
  return false;
1562
1693
  emit(db, "properties", id, "__deleted", 1);
1694
+ const rows = db.query("SELECT id FROM records WHERE database_id = ? AND __deleted = 0 AND data ->> ? IS NOT NULL").all(prop.database_id, id);
1695
+ for (const r of rows)
1696
+ emit(db, "records", r.id, id, undefined);
1563
1697
  return true;
1564
1698
  }
1565
1699
 
@@ -2027,6 +2161,10 @@ function migratePeers(db) {
2027
2161
  db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
2028
2162
  }
2029
2163
  }
2164
+ function migrateDocBlocks(db) {
2165
+ if (!hasColumn(db, "doc_blocks", "blank_after"))
2166
+ db.exec("ALTER TABLE doc_blocks ADD COLUMN blank_after INTEGER NOT NULL DEFAULT 0");
2167
+ }
2030
2168
  function openMetahub() {
2031
2169
  ensureDirs();
2032
2170
  const db = new Database(dbPath(), { create: true });
@@ -2034,6 +2172,7 @@ function openMetahub() {
2034
2172
  runSchema(db);
2035
2173
  migrateRecords(db);
2036
2174
  migratePeers(db);
2175
+ migrateDocBlocks(db);
2037
2176
  return db;
2038
2177
  }
2039
2178
 
@@ -2065,7 +2204,7 @@ function clearCurrentDatabase(db) {
2065
2204
  init_ids();
2066
2205
  init_crdt();
2067
2206
  function liveBlocks(db, docId) {
2068
- return db.query("SELECT id, text, order_key FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
2207
+ return db.query("SELECT id, text, order_key, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
2069
2208
  }
2070
2209
  function makeBlockId(text) {
2071
2210
  return newId("blk", text.split(`
@@ -2075,29 +2214,37 @@ function emitBlock(db, docId, fields) {
2075
2214
  const id = makeBlockId(fields.text);
2076
2215
  emit(db, "doc_blocks", id, "doc_id", docId);
2077
2216
  emit(db, "doc_blocks", id, "order_key", fields.order_key);
2217
+ if (fields.blankAfter)
2218
+ emit(db, "doc_blocks", id, "blank_after", fields.blankAfter);
2078
2219
  emit(db, "doc_blocks", id, "text", fields.text);
2079
2220
  }
2080
- function insertBlocks(db, docId, texts, after, before) {
2081
- const keys = keysBetween(after, before, texts.length);
2082
- texts.forEach((text, i) => emitBlock(db, docId, { text, order_key: keys[i] }));
2221
+ function insertBlocks(db, docId, blocks, after, before) {
2222
+ const keys = keysBetween(after, before, blocks.length);
2223
+ blocks.forEach((b, i) => emitBlock(db, docId, { text: b.text, order_key: keys[i], blankAfter: b.blankAfter }));
2083
2224
  }
2084
2225
  function ensureBlocks(db, docId) {
2085
2226
  if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
2086
2227
  return;
2087
2228
  const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
2088
- const texts = parseBlocks(row?.body ?? "");
2089
- if (texts.length)
2090
- insertBlocks(db, docId, texts, null, null);
2229
+ const blocks = parseDocBlocks(row?.body ?? "");
2230
+ if (blocks.length)
2231
+ insertBlocks(db, docId, blocks, null, null);
2091
2232
  }
2092
2233
  function reconcileBody(db, docId, body) {
2093
2234
  const old = liveBlocks(db, docId);
2094
- const plan = reconcile(old.map((b) => b.text), parseBlocks(body));
2235
+ const next = parseDocBlocks(body);
2236
+ const plan = reconcile(old.map((b) => b.text), next.map((b) => b.text));
2095
2237
  for (const oi of plan.deleted)
2096
2238
  emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
2097
2239
  const { items } = plan;
2098
2240
  let i = 0;
2099
2241
  while (i < items.length) {
2100
- if ("keep" in items[i]) {
2242
+ const it = items[i];
2243
+ if ("keep" in it) {
2244
+ const oldB = old[it.keep];
2245
+ const want = next[i].blankAfter;
2246
+ if ((oldB.blank_after ?? 0) !== want)
2247
+ emit(db, "doc_blocks", oldB.id, "blank_after", want);
2101
2248
  i++;
2102
2249
  continue;
2103
2250
  }
@@ -2106,8 +2253,7 @@ function reconcileBody(db, docId, body) {
2106
2253
  j++;
2107
2254
  const left = i > 0 ? old[items[i - 1].keep].order_key : null;
2108
2255
  const right = j < items.length ? old[items[j].keep].order_key : null;
2109
- const texts = items.slice(i, j).map((it) => it.insert);
2110
- insertBlocks(db, docId, texts, left, right);
2256
+ insertBlocks(db, docId, next.slice(i, j), left, right);
2111
2257
  i = j;
2112
2258
  }
2113
2259
  }
@@ -2126,9 +2272,9 @@ function createDocument(db, opts) {
2126
2272
  if (opts.parent_id !== undefined)
2127
2273
  emit(db, "documents", id, "parent_id", opts.parent_id);
2128
2274
  if (opts.body !== undefined) {
2129
- const texts = parseBlocks(opts.body);
2130
- if (texts.length)
2131
- insertBlocks(db, id, texts, null, null);
2275
+ const blocks = parseDocBlocks(opts.body);
2276
+ if (blocks.length)
2277
+ insertBlocks(db, id, blocks, null, null);
2132
2278
  }
2133
2279
  return getDocument(db, id);
2134
2280
  }
@@ -2174,7 +2320,7 @@ function editDocument(db, id, opts) {
2174
2320
  throw new Error("--old must not be empty");
2175
2321
  ensureBlocks(db, id);
2176
2322
  const blocks = liveBlocks(db, id);
2177
- const body = serializeBlocks(blocks.map((b) => b.text));
2323
+ const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
2178
2324
  const count = countOccurrences(body, opts.old);
2179
2325
  if (count === 0)
2180
2326
  throw new Error(`anchor not found: no match for --old`);
@@ -2201,32 +2347,39 @@ function appendDocument(db, id, body) {
2201
2347
  if (!getDocument(db, id))
2202
2348
  throw new Error(`no such document: ${id}`);
2203
2349
  ensureBlocks(db, id);
2204
- const texts = parseBlocks(body);
2205
- if (texts.length) {
2350
+ const next = parseDocBlocks(body);
2351
+ if (next.length) {
2206
2352
  const blocks = liveBlocks(db, id);
2207
2353
  const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
2208
- insertBlocks(db, id, texts, last, null);
2354
+ insertBlocks(db, id, next, last, null);
2209
2355
  }
2210
- return { id, changed: texts.length > 0, replaced: texts.length, version: documentVersion(db, id) };
2356
+ return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
2211
2357
  }
2212
2358
  function prependDocument(db, id, body) {
2213
2359
  if (!getDocument(db, id))
2214
2360
  throw new Error(`no such document: ${id}`);
2215
2361
  ensureBlocks(db, id);
2216
- const texts = parseBlocks(body);
2217
- if (texts.length) {
2362
+ const next = parseDocBlocks(body);
2363
+ if (next.length) {
2218
2364
  const blocks = liveBlocks(db, id);
2219
2365
  const first = blocks.length ? blocks[0].order_key : null;
2220
- insertBlocks(db, id, texts, null, first);
2366
+ insertBlocks(db, id, next, null, first);
2221
2367
  }
2222
- return { id, changed: texts.length > 0, replaced: texts.length, version: documentVersion(db, id) };
2368
+ return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
2223
2369
  }
2224
2370
  function deleteDocument(db, id) {
2225
2371
  if (!getDocument(db, id))
2226
2372
  return false;
2227
2373
  emit(db, "documents", id, "__deleted", 1);
2374
+ for (const b of liveChildIds(db, "doc_blocks", "doc_id", id))
2375
+ emit(db, "doc_blocks", b, "__deleted", 1);
2376
+ for (const c of liveChildIds(db, "documents", "parent_id", id))
2377
+ emit(db, "documents", c, "parent_id", null);
2228
2378
  return true;
2229
2379
  }
2380
+ function liveChildIds(db, table, col, parentId) {
2381
+ return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r) => r.id);
2382
+ }
2230
2383
  function countOccurrences(haystack, needle) {
2231
2384
  if (needle === "")
2232
2385
  return 0;
@@ -2239,26 +2392,112 @@ function countOccurrences(haystack, needle) {
2239
2392
  return n;
2240
2393
  }
2241
2394
  // src/core/search.ts
2395
+ init_crdt();
2242
2396
  var TEXT_TYPES = "('text','url','select','multi_select','date')";
2243
- function ensureIndex(db) {
2244
- if (!ftsAvailable(db))
2245
- return false;
2246
- const max = db.query("SELECT MAX(hlc) AS h FROM crdt_changes").get().h ?? "";
2247
- const indexed = db.query("SELECT value FROM meta WHERE key = 'search_hlc'").get()?.value ?? "";
2248
- if (max === indexed)
2249
- return true;
2250
- db.query("DELETE FROM search_fts").run();
2251
- for (const d of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all()) {
2397
+ var SEARCH_INDEX_VERSION = "1";
2398
+ function readMeta(db, key) {
2399
+ return db.query("SELECT value FROM meta WHERE key = ?").get(key)?.value ?? null;
2400
+ }
2401
+ function writeMeta(db, key, value) {
2402
+ db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, value);
2403
+ }
2404
+ function reindexDocument(db, id) {
2405
+ db.query("DELETE FROM search_fts WHERE kind = 'document' AND id = ?").run(id);
2406
+ const d = db.query("SELECT id, title, body, database_id FROM documents WHERE id = ? AND __deleted = 0").get(id);
2407
+ if (d)
2252
2408
  db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d.id, d.database_id, d.title ?? "", d.body ?? "");
2253
- }
2254
- for (const r of db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
2409
+ }
2410
+ function recordBodyRows(db, where, ...args) {
2411
+ return db.query(`SELECT r.id AS id, r.database_id AS database_id, group_concat(je.value, ' ') AS body
2255
2412
  FROM records r, json_each(r.data) je
2256
2413
  JOIN properties p ON p.id = je.key AND p.__deleted = 0
2257
- WHERE r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
2258
- GROUP BY r.id`).all()) {
2259
- db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r.id, r.database_id, r.id, r.body ?? "");
2414
+ WHERE ${where} AND r.__deleted = 0 AND p.type IN ${TEXT_TYPES}
2415
+ GROUP BY r.id`).all(...args);
2416
+ }
2417
+ function insertRecordRow(db, r) {
2418
+ db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('record', ?, ?, ?, ?)").run(r.id, r.database_id, r.id, r.body ?? "");
2419
+ }
2420
+ function reindexRecord(db, id) {
2421
+ db.query("DELETE FROM search_fts WHERE kind = 'record' AND id = ?").run(id);
2422
+ const r = recordBodyRows(db, "r.id = ?", id)[0];
2423
+ if (r)
2424
+ insertRecordRow(db, r);
2425
+ }
2426
+ function reindexDatabaseRecords(db, databaseId) {
2427
+ db.query("DELETE FROM search_fts WHERE kind = 'record' AND database_id = ?").run(databaseId);
2428
+ for (const r of recordBodyRows(db, "r.database_id = ?", databaseId))
2429
+ insertRecordRow(db, r);
2430
+ }
2431
+ function fullRebuild(db) {
2432
+ db.query("DELETE FROM search_fts").run();
2433
+ for (const d of db.query("SELECT id, title, body, database_id FROM documents WHERE __deleted = 0").all())
2434
+ db.query("INSERT INTO search_fts (kind, id, database_id, title, body) VALUES ('document', ?, ?, ?, ?)").run(d.id, d.database_id, d.title ?? "", d.body ?? "");
2435
+ for (const r of recordBodyRows(db, "1 = 1"))
2436
+ insertRecordRow(db, r);
2437
+ const max = db.query("SELECT MAX(rowid) AS m FROM crdt_changes").get().m ?? 0;
2438
+ writeMeta(db, "search_seq", String(max));
2439
+ writeMeta(db, "search_index_version", SEARCH_INDEX_VERSION);
2440
+ }
2441
+ function incrementalUpdate(db) {
2442
+ const cursor = Number(readMeta(db, "search_seq") ?? "0");
2443
+ const { changes, cursor: next } = changesAfterSeq(db, cursor);
2444
+ if (changes.length === 0)
2445
+ return;
2446
+ const docIds = new Set;
2447
+ const recordIds = new Set;
2448
+ const dbIds = new Set;
2449
+ for (const c of changes) {
2450
+ switch (c.dataset) {
2451
+ case "documents":
2452
+ docIds.add(c.row_id);
2453
+ break;
2454
+ case "doc_blocks": {
2455
+ const blk = db.query("SELECT doc_id FROM doc_blocks WHERE id = ?").get(c.row_id);
2456
+ if (blk?.doc_id)
2457
+ docIds.add(blk.doc_id);
2458
+ break;
2459
+ }
2460
+ case "records":
2461
+ recordIds.add(c.row_id);
2462
+ break;
2463
+ case "properties":
2464
+ if (c.col === "type" || c.col === "__deleted" || c.col === "database_id") {
2465
+ const p = db.query("SELECT database_id FROM properties WHERE id = ?").get(c.row_id);
2466
+ if (p?.database_id)
2467
+ dbIds.add(p.database_id);
2468
+ }
2469
+ break;
2470
+ }
2260
2471
  }
2261
- db.query("INSERT INTO meta (key, value) VALUES ('search_hlc', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(max);
2472
+ for (const id of docIds)
2473
+ reindexDocument(db, id);
2474
+ for (const dbId of dbIds)
2475
+ reindexDatabaseRecords(db, dbId);
2476
+ for (const id of recordIds) {
2477
+ const r = db.query("SELECT database_id FROM records WHERE id = ?").get(id);
2478
+ if (r?.database_id && dbIds.has(r.database_id))
2479
+ continue;
2480
+ reindexRecord(db, id);
2481
+ }
2482
+ writeMeta(db, "search_seq", String(next));
2483
+ }
2484
+ function ensureIndex(db) {
2485
+ if (!ftsAvailable(db))
2486
+ return false;
2487
+ const version = readMeta(db, "search_index_version");
2488
+ const seq = readMeta(db, "search_seq");
2489
+ db.transaction(() => {
2490
+ if (version !== SEARCH_INDEX_VERSION || seq === null)
2491
+ fullRebuild(db);
2492
+ else
2493
+ incrementalUpdate(db);
2494
+ })();
2495
+ return true;
2496
+ }
2497
+ function rebuildSearchIndex(db) {
2498
+ if (!ftsAvailable(db))
2499
+ return false;
2500
+ db.transaction(() => fullRebuild(db))();
2262
2501
  return true;
2263
2502
  }
2264
2503
  function ftsSearch(db, query, limit) {
@@ -16772,8 +17011,9 @@ function getServerConfig(db) {
16772
17011
  // package.json
16773
17012
  var package_default = {
16774
17013
  name: "@tensorix/metahub",
16775
- version: "0.1.3",
17014
+ version: "0.1.6",
16776
17015
  description: "Metahub core library and CLI",
17016
+ license: "AGPL-3.0-only",
16777
17017
  type: "module",
16778
17018
  main: "./dist/index.js",
16779
17019
  module: "./dist/index.js",
@@ -16791,14 +17031,17 @@ var package_default = {
16791
17031
  },
16792
17032
  files: [
16793
17033
  "dist",
16794
- "README.md"
17034
+ "README.md",
17035
+ "README.zh-CN.md",
17036
+ "LICENSE"
16795
17037
  ],
16796
17038
  scripts: {
16797
17039
  dev: "bun --hot src/cli/index.ts",
16798
17040
  build: "bun run scripts/build.ts",
16799
17041
  "build:binaries": "bun run scripts/compile-binaries.ts",
16800
17042
  prepublishOnly: "bun run build",
16801
- test: "bun test"
17043
+ test: "bun test",
17044
+ release: "bun run scripts/tag-release.ts"
16802
17045
  },
16803
17046
  dependencies: {
16804
17047
  "@clack/prompts": "^1.5.0",
@@ -16907,7 +17150,15 @@ function handle(fn) {
16907
17150
  }
16908
17151
  };
16909
17152
  }
17153
+ var VersionSchema = exports_external.object({ version: exports_external.string() });
16910
17154
  var webuiRoutes = [
17155
+ {
17156
+ method: "GET",
17157
+ path: "/api/version",
17158
+ summary: "Version of the running core (sidecar). Used by the desktop app's settings footer.",
17159
+ response: VersionSchema,
17160
+ handler: () => Response.json({ version: package_default.version })
17161
+ },
16911
17162
  {
16912
17163
  method: "GET",
16913
17164
  path: "/api/databases",
@@ -17635,6 +17886,16 @@ function unauthorized() {
17635
17886
  }
17636
17887
 
17637
17888
  // src/core/sync/server.ts
17889
+ class PortInUseError extends Error {
17890
+ port;
17891
+ host;
17892
+ constructor(port, host) {
17893
+ super(`port ${port} is already in use \u2014 another mh server may be running. ` + `Stop it, or start on a free port with --port <n>.`);
17894
+ this.port = port;
17895
+ this.host = host;
17896
+ this.name = "PortInUseError";
17897
+ }
17898
+ }
17638
17899
  function scalarHtml(specUrl) {
17639
17900
  return `<!doctype html><html><head>
17640
17901
  <meta charset="utf-8"><title>Metahub API</title>
@@ -17659,48 +17920,60 @@ function startServer(opts = {}) {
17659
17920
  ttlMs: DEFAULT_TTL_MS,
17660
17921
  graceMs: DEFAULT_GRACE_MS
17661
17922
  };
17662
- const server = Bun.serve({
17663
- port,
17664
- hostname: host,
17665
- async fetch(req) {
17666
- const url2 = new URL(req.url);
17667
- if (url2.pathname === SYNC_PATH) {
17668
- if (!acceptsSyncToken(req, url2, auth, db))
17669
- return unauthorized();
17670
- } else {
17671
- const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
17672
- if (!exempt && !hasValidToken(req, url2, auth)) {
17673
- return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
17923
+ let server;
17924
+ try {
17925
+ server = Bun.serve({
17926
+ port,
17927
+ hostname: host,
17928
+ async fetch(req) {
17929
+ const url2 = new URL(req.url);
17930
+ if (url2.pathname === SYNC_PATH) {
17931
+ if (!acceptsSyncToken(req, url2, auth, db))
17932
+ return unauthorized();
17933
+ } else {
17934
+ const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
17935
+ if (!exempt && !hasValidToken(req, url2, auth)) {
17936
+ return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
17937
+ }
17674
17938
  }
17939
+ if (url2.pathname === RENEW_PATH) {
17940
+ const r = renewToken(req, url2, auth);
17941
+ return r ? Response.json(r) : unauthorized();
17942
+ }
17943
+ if (req.method === "GET" && url2.pathname === "/docs.json") {
17944
+ return Response.json(buildOpenApi(package_default.version));
17945
+ }
17946
+ if (req.method === "GET" && url2.pathname === "/docs") {
17947
+ return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
17948
+ }
17949
+ if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
17950
+ const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
17951
+ const res = await serveWebui2(req);
17952
+ if (res)
17953
+ return withShim(res, auth);
17954
+ }
17955
+ if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
17956
+ const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
17957
+ const res = await serveSite2(req, ctx);
17958
+ if (res)
17959
+ return withShim(res, auth);
17960
+ }
17961
+ const route = routes.find((r) => r.method === req.method && r.path === url2.pathname);
17962
+ if (route)
17963
+ return route.handler(req, ctx);
17964
+ return new Response("not found", { status: 404 });
17675
17965
  }
17676
- if (url2.pathname === RENEW_PATH) {
17677
- const r = renewToken(req, url2, auth);
17678
- return r ? Response.json(r) : unauthorized();
17679
- }
17680
- if (req.method === "GET" && url2.pathname === "/docs.json") {
17681
- return Response.json(buildOpenApi(package_default.version));
17682
- }
17683
- if (req.method === "GET" && url2.pathname === "/docs") {
17684
- return withShim(new Response(scalarHtml("/docs.json"), { headers: HTML_HEADERS }), auth);
17685
- }
17686
- if (req.method === "GET" && (url2.pathname === "/" || url2.pathname === "/webui.js")) {
17687
- const { serveWebui: serveWebui2 } = await Promise.resolve().then(() => (init_webui(), exports_webui));
17688
- const res = await serveWebui2(req);
17689
- if (res)
17690
- return withShim(res, auth);
17691
- }
17692
- if (req.method === "GET" && url2.pathname.startsWith("/sites/")) {
17693
- const { serveSite: serveSite2 } = await Promise.resolve().then(() => (init_sites_serve(), exports_sites_serve));
17694
- const res = await serveSite2(req, ctx);
17695
- if (res)
17696
- return withShim(res, auth);
17697
- }
17698
- const route = routes.find((r) => r.method === req.method && r.path === url2.pathname);
17699
- if (route)
17700
- return route.handler(req, ctx);
17701
- return new Response("not found", { status: 404 });
17966
+ });
17967
+ } catch (e) {
17968
+ const code = e?.code;
17969
+ if (code === "EADDRINUSE") {
17970
+ throw new PortInUseError(port, host);
17702
17971
  }
17703
- });
17972
+ if (code === "EACCES") {
17973
+ throw new Error(`permission denied binding ${host}:${port} \u2014 ports below 1024 need elevated privileges; pick a higher port with --port`);
17974
+ }
17975
+ throw e;
17976
+ }
17704
17977
  let timer = null;
17705
17978
  if (autoSync && syncIntervalMs > 0) {
17706
17979
  timer = setInterval(() => {
@@ -17709,12 +17982,17 @@ function startServer(opts = {}) {
17709
17982
  timer.unref?.();
17710
17983
  }
17711
17984
  const active = activeToken(auth);
17985
+ const authMode = opts.debug ? "disabled" : opts.token ? "static" : "managed";
17712
17986
  return {
17713
17987
  server,
17714
17988
  node,
17715
17989
  port: server.port ?? port,
17990
+ host,
17716
17991
  token: active?.token ?? null,
17717
17992
  exp: active?.exp ?? null,
17993
+ authMode,
17994
+ autoSync: Boolean(autoSync && syncIntervalMs > 0),
17995
+ syncIntervalMs,
17718
17996
  stop() {
17719
17997
  if (timer)
17720
17998
  clearInterval(timer);
@@ -17732,7 +18010,7 @@ export {
17732
18010
  slugify,
17733
18011
  setPropertyWidth,
17734
18012
  setCurrentDatabase,
17735
- serializeBlocks,
18013
+ serializeDocBlocks,
17736
18014
  search,
17737
18015
  runSchema,
17738
18016
  resolveSite,
@@ -17741,12 +18019,13 @@ export {
17741
18019
  resolveCandidates,
17742
18020
  removeProperty,
17743
18021
  reconcile,
18022
+ rebuildSearchIndex,
17744
18023
  randomSuffix,
17745
18024
  putFile,
17746
18025
  putBlob,
17747
18026
  prependDocument,
17748
18027
  parseHlc,
17749
- parseBlocks,
18028
+ parseDocBlocks,
17750
18029
  openMetahub,
17751
18030
  observeHlc,
17752
18031
  nextHlc,
@@ -17754,6 +18033,7 @@ export {
17754
18033
  moveRecord,
17755
18034
  migrateRecords,
17756
18035
  migratePeers,
18036
+ migrateDocBlocks,
17757
18037
  metahubHome,
17758
18038
  maybeAutoIndex,
17759
18039
  makeId,
@@ -17810,6 +18090,7 @@ export {
17810
18090
  SyncRequestSchema,
17811
18091
  SYNC_PATH,
17812
18092
  RENEW_PATH,
18093
+ PortInUseError,
17813
18094
  PairResponseSchema,
17814
18095
  PairRequestSchema,
17815
18096
  PROP_TYPES,
@@ -17819,4 +18100,4 @@ export {
17819
18100
  ChangeSchema
17820
18101
  };
17821
18102
 
17822
- //# debugId=52E20C0E56A04E8A64756E2164756E21
18103
+ //# debugId=F7BD99C8461B833164756E2164756E21