@tensorix/metahub 0.1.4 → 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.
@@ -1,7 +1,26 @@
1
- /** Split markdown into block texts (no leading/trailing blank lines, no empties). */
2
- export declare function parseBlocks(md: string): string[];
3
- /** Join block texts back into a markdown body. */
4
- export declare function serializeBlocks(texts: readonly (string | null)[]): string;
1
+ /**
2
+ * A document block plus the blank lines the user left after it. `blankAfter` is
3
+ * the count *beyond* the standard single-blank-line separator (so a normal
4
+ * paragraph break is 0); for the last block it's the trailing blank-line run.
5
+ * This is what lets deliberate vertical spacing survive a save/reload without
6
+ * representing blank lines as zero-content blocks (which would churn the
7
+ * text-keyed reconcile). See [[blocks]] doc-block model.
8
+ */
9
+ export interface DocBlock {
10
+ text: string;
11
+ blankAfter: number;
12
+ }
13
+ /**
14
+ * Split markdown into doc blocks, preserving blank-line runs as `blankAfter`.
15
+ * Leading blank lines (before the first block) are dropped; one blank line
16
+ * between two blocks is the standard separator, every extra blank line is kept.
17
+ */
18
+ export declare function parseDocBlocks(md: string): DocBlock[];
19
+ /** Serialize doc blocks back to a body, re-emitting kept blank-line runs. */
20
+ export declare function serializeDocBlocks(blocks: readonly {
21
+ text: string | null;
22
+ blankAfter?: number;
23
+ }[]): string;
5
24
  export type ReconcileItem = {
6
25
  keep: number;
7
26
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/core/blocks.ts"],"names":[],"mappings":"AAIA,qFAAqF;AACrF,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,CA8ChD;AAED,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,MAAM,CAEzE;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAClE,MAAM,WAAW,aAAa;IAC5B,uFAAuF;IACvF,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,aAAa,CAa/E"}
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../src/core/blocks.ts"],"names":[],"mappings":"AAIA;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,EAAE,CAmDrD;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,GAC9D,MAAM,CAUR;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAClE,MAAM,WAAW,aAAa;IAC5B,uFAAuF;IACvF,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,oDAAoD;IACpD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,aAAa,CAa/E"}
package/dist/core/db.d.ts CHANGED
@@ -14,6 +14,12 @@ export declare function migrateRecords(db: Database): void;
14
14
  * never drops the table so existing replication cursors survive.
15
15
  */
16
16
  export declare function migratePeers(db: Database): void;
17
+ /**
18
+ * Add the `blank_after` column to a legacy `doc_blocks` table. Idempotent —
19
+ * guarded by hasColumn; existing blocks default to 0 (canonical single-blank-line
20
+ * separators), so the migration never changes an existing document's body.
21
+ */
22
+ export declare function migrateDocBlocks(db: Database): void;
17
23
  /** Open (and migrate) the on-disk metahub database for the resolved home. */
18
24
  export declare function openMetahub(): Database;
19
25
  //# sourceMappingURL=db.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAa/C;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,IAAI,QAAQ,CAQtC"}
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/core/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAMtC,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAO5C;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAKlD;AAeD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBjD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAa/C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAGnD;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,IAAI,QAAQ,CAStC"}
@@ -1 +1 @@
1
- {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/core/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AAmFxD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAShE;AAID,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,WAAW,CAWb;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMxE;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,eAAe,EAAE,CAYnB;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzF,WAAW,CAoBb;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,aAAa,CAmCf;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUpF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUrF;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAUhE"}
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/core/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAM3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AA6FxD,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAShE;AAID,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/E,WAAW,CAWb;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAMxE;AAED,wBAAgB,aAAa,CAC3B,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACtD,eAAe,EAAE,CAYnB;AAED,wBAAgB,cAAc,CAC5B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzF,WAAW,CAoBb;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,aAAa,CAmCf;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUpF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAUrF;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAUhE"}
@@ -1,3 +1,3 @@
1
- export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\nCREATE TABLE IF NOT EXISTS crdt_changes (\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n PRIMARY KEY (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n\n-- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,\n-- so no change is ever skipped regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_status TEXT,\n last_error TEXT\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id TEXT PRIMARY KEY,\n doc_id TEXT,\n text TEXT,\n order_key TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);\n\n-- A named static site (Supabase-Storage-style bucket). Served at /sites/<name>/.\nCREATE TABLE IF NOT EXISTS sites (\n id TEXT PRIMARY KEY,\n name TEXT, -- URL slug; resolved by getSiteByName\n title TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- One row per file in a site. content holds inline utf8/base64 text, or a blob\n-- hash (see cache.ts) when encoding = 'blob'. (site_id, path) maps to a stable\n-- id so re-uploads merge as a CRDT register instead of duplicating.\nCREATE TABLE IF NOT EXISTS site_files (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n path TEXT,\n content_type TEXT,\n encoding TEXT, -- 'utf8' | 'base64' | 'blob'\n content TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_files_site ON site_files(site_id);\n";
1
+ export declare const CORE_SCHEMA = "\nCREATE TABLE IF NOT EXISTS meta (\n key TEXT PRIMARY KEY,\n value TEXT\n);\n\n-- CRDT oplog: one row per field assignment. Source of truth for sync.\nCREATE TABLE IF NOT EXISTS crdt_changes (\n hlc TEXT NOT NULL,\n node_id TEXT NOT NULL,\n dataset TEXT NOT NULL,\n row_id TEXT NOT NULL,\n col TEXT NOT NULL,\n value TEXT,\n PRIMARY KEY (dataset, row_id, col, hlc)\n);\nCREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);\n\n-- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,\n-- so no change is ever skipped regardless of HLC/clock skew). Outbound side of\n-- a pairing: \"token\" is the credential the *remote* issued to us (sent as a\n-- Bearer header when we sync to them). See migratePeers in db.ts for the columns\n-- added to legacy databases.\nCREATE TABLE IF NOT EXISTS peers (\n url TEXT PRIMARY KEY,\n pull_cursor INTEGER NOT NULL DEFAULT 0,\n push_cursor INTEGER NOT NULL DEFAULT 0,\n token TEXT,\n label TEXT,\n node_id TEXT,\n enabled INTEGER NOT NULL DEFAULT 1,\n last_sync_at INTEGER,\n last_status TEXT,\n last_error TEXT\n);\n\n-- Inbound side of a pairing: credentials *we* issued to remote devices. A peer\n-- presents one of these on /sync; acceptsSyncToken() checks it alongside the\n-- managed master token. Revoke by deleting the row.\nCREATE TABLE IF NOT EXISTS peer_grants (\n token TEXT PRIMARY KEY,\n peer_url TEXT,\n node_id TEXT,\n created_at INTEGER\n);\n\n-- One-time pairing codes minted by \"mh config peer code\" / POST /api/pair/new.\n-- Short-lived and single-use: redeemed during the pairing handshake, then the\n-- two devices exchange durable peer_grants. used/exp gate redemption.\nCREATE TABLE IF NOT EXISTS pairing_codes (\n code TEXT PRIMARY KEY,\n exp INTEGER NOT NULL,\n used INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS databases (\n id TEXT PRIMARY KEY,\n name TEXT,\n icon TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS properties (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n name TEXT,\n type TEXT,\n config TEXT,\n position REAL,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_properties_db ON properties(database_id);\n\n-- One row per record. Field cells live in the data JSON object, keyed by\n-- property id (rename-safe). Each (record,property) is still an independent\n-- CRDT register in the oplog; materialize() folds the winner into data via\n-- json_set/json_remove. Hot fields get expression indexes on data->>'propid'\n-- (see indexing.ts) so filter/sort/limit push down to SQL.\nCREATE TABLE IF NOT EXISTS records (\n id TEXT PRIMARY KEY,\n database_id TEXT,\n created_hlc TEXT,\n order_key TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_records_db_hlc ON records(database_id, created_hlc);\n\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n title TEXT,\n body TEXT, -- materialized cache: serialized from doc_blocks\n database_id TEXT,\n parent_id TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- Document body as an ordered list of blocks; each (block,field) is an\n-- independent CRDT register so concurrent edits to different blocks merge.\n-- order_key is a fractional index (ties broken by id) giving display order.\nCREATE TABLE IF NOT EXISTS doc_blocks (\n id TEXT PRIMARY KEY,\n doc_id TEXT,\n text TEXT,\n order_key TEXT,\n blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);\n\n-- A named static site (Supabase-Storage-style bucket). Served at /sites/<name>/.\nCREATE TABLE IF NOT EXISTS sites (\n id TEXT PRIMARY KEY,\n name TEXT, -- URL slug; resolved by getSiteByName\n title TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\n\n-- One row per file in a site. content holds inline utf8/base64 text, or a blob\n-- hash (see cache.ts) when encoding = 'blob'. (site_id, path) maps to a stable\n-- id so re-uploads merge as a CRDT register instead of duplicating.\nCREATE TABLE IF NOT EXISTS site_files (\n id TEXT PRIMARY KEY,\n site_id TEXT,\n path TEXT,\n content_type TEXT,\n encoding TEXT, -- 'utf8' | 'base64' | 'blob'\n content TEXT,\n created_hlc TEXT,\n __deleted INTEGER NOT NULL DEFAULT 0\n);\nCREATE INDEX IF NOT EXISTS idx_site_files_site ON site_files(site_id);\n";
2
2
  export declare const FTS_SCHEMA = "\nCREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(\n kind UNINDEXED,\n id UNINDEXED,\n database_id UNINDEXED,\n title,\n body\n);\n";
3
3
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,mpJAuIvB,CAAC;AAGF,eAAO,MAAM,UAAU,sJAQtB,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/core/schema.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,mwJAwIvB,CAAC;AAGF,eAAO,MAAM,UAAU,sJAQtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"webui.d.ts","sourceRoot":"","sources":["../../../src/core/sync/webui.ts"],"names":[],"mappings":"AAotBA;;;8EAG8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE/C;AAoBD;;;;2EAI2E;AAC3E,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/C;AAED;0CAC0C;AAC1C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiBvE"}
1
+ {"version":3,"file":"webui.d.ts","sourceRoot":"","sources":["../../../src/core/sync/webui.ts"],"names":[],"mappings":"AAgvBA;;;8EAG8E;AAC9E,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE/C;AAoBD;;;;2EAI2E;AAC3E,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAM/C;AAED;0CAC0C;AAC1C,wBAAsB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAiBvE"}
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",
@@ -605,6 +611,7 @@ var HTML = `<!doctype html>
605
611
  --radius:8px; --radius-sm:6px;
606
612
  --ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
607
613
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
614
+ --note-lh:1.6;
608
615
  --code-bg:#f8f8f6;
609
616
  --hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
610
617
  --hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
@@ -637,7 +644,7 @@ var HTML = `<!doctype html>
637
644
  * { box-sizing: border-box; }
638
645
  html, body { margin: 0; height: 100%; }
639
646
  body { font-family: var(--ui); font-size: 14px; line-height: 1.55; color: var(--fg); background: var(--bg);
640
- overflow: hidden; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
647
+ overflow: hidden; text-rendering: auto; }
641
648
  button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
642
649
  input, textarea { font: inherit; color: inherit; }
643
650
  .muted { color: var(--muted); }
@@ -693,6 +700,8 @@ var HTML = `<!doctype html>
693
700
  .dragging { opacity:.35; }
694
701
  .sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
695
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; }
696
705
 
697
706
  /* settings page */
698
707
  .set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
@@ -702,8 +711,19 @@ var HTML = `<!doctype html>
702
711
  .set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
703
712
  .set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
704
713
  .set-footer { margin-top:40px; padding-top:20px; border-top:1px solid var(--line); display:flex;
705
- justify-content:center; gap:8px; color:var(--muted); font-size:12px; }
706
- .set-footer-sep { opacity:.5; }
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; } }
707
727
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
708
728
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
709
729
  text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
@@ -796,8 +816,10 @@ var HTML = `<!doctype html>
796
816
  /* The list item's host gutter controls the whole unit, so suppress the nested
797
817
  code block's own gutter (it would overlap the list marker on the same row). */
798
818
  .block.list-code-host + .block-wrap.nested .gutter { display:none; }
799
- .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); }
800
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:""; }
801
823
  .b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
802
824
  .b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
803
825
  .b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
@@ -849,7 +871,8 @@ var HTML = `<!doctype html>
849
871
  .hljs-emphasis { font-style:italic; }
850
872
  .hljs-strong { font-weight:700; }
851
873
  .marker { width:24px; flex:none; text-align:center; color:var(--fg-soft); user-select:none; padding-top:4px; }
852
- .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; }
853
876
  .b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
854
877
  .b-divider { padding:10px 0; }
855
878
  .b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
@@ -1036,14 +1059,14 @@ var HTML = `<!doctype html>
1036
1059
  .optrow .x:hover { background:var(--hover-2); color:var(--danger); }
1037
1060
 
1038
1061
  /* modal */
1039
- .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;
1040
1063
  opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
1041
1064
  .modal-scrim.open { opacity:1; pointer-events:auto; }
1042
- .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; }
1043
1066
  .modal-head { padding:20px 22px 0; }
1044
1067
  .modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
1045
1068
  .modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
1046
- .modal-body { padding:18px 22px; }
1069
+ .modal-body { padding:18px 22px; overflow-y:auto; }
1047
1070
  .modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
1048
1071
  .field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
1049
1072
  .field-label:first-child { margin-top:0; }
@@ -1262,6 +1285,17 @@ var HTML = `<!doctype html>
1262
1285
  .qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
1263
1286
  .qn-body .doc-title { font-size:24px; margin-bottom:2px; }
1264
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"); }
1265
1299
  /* Compact menus inside the quick-note window only (dropdown + slash command
1266
1300
  share .pop/.item/.lico with the main app; scope under body.quicknote so the
1267
1301
  main app keeps its roomier density). */
@@ -1434,11 +1468,12 @@ CREATE TABLE IF NOT EXISTS documents (
1434
1468
  -- independent CRDT register so concurrent edits to different blocks merge.
1435
1469
  -- order_key is a fractional index (ties broken by id) giving display order.
1436
1470
  CREATE TABLE IF NOT EXISTS doc_blocks (
1437
- id TEXT PRIMARY KEY,
1438
- doc_id TEXT,
1439
- text TEXT,
1440
- order_key TEXT,
1441
- __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
1442
1477
  );
1443
1478
  CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
1444
1479
 
@@ -2126,6 +2161,10 @@ function migratePeers(db) {
2126
2161
  db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
2127
2162
  }
2128
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
+ }
2129
2168
  function openMetahub() {
2130
2169
  ensureDirs();
2131
2170
  const db = new Database(dbPath(), { create: true });
@@ -2133,6 +2172,7 @@ function openMetahub() {
2133
2172
  runSchema(db);
2134
2173
  migrateRecords(db);
2135
2174
  migratePeers(db);
2175
+ migrateDocBlocks(db);
2136
2176
  return db;
2137
2177
  }
2138
2178
 
@@ -2164,7 +2204,7 @@ function clearCurrentDatabase(db) {
2164
2204
  init_ids();
2165
2205
  init_crdt();
2166
2206
  function liveBlocks(db, docId) {
2167
- 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);
2168
2208
  }
2169
2209
  function makeBlockId(text) {
2170
2210
  return newId("blk", text.split(`
@@ -2174,29 +2214,37 @@ function emitBlock(db, docId, fields) {
2174
2214
  const id = makeBlockId(fields.text);
2175
2215
  emit(db, "doc_blocks", id, "doc_id", docId);
2176
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);
2177
2219
  emit(db, "doc_blocks", id, "text", fields.text);
2178
2220
  }
2179
- function insertBlocks(db, docId, texts, after, before) {
2180
- const keys = keysBetween(after, before, texts.length);
2181
- 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 }));
2182
2224
  }
2183
2225
  function ensureBlocks(db, docId) {
2184
2226
  if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
2185
2227
  return;
2186
2228
  const row = db.query("SELECT body FROM documents WHERE id = ?").get(docId);
2187
- const texts = parseBlocks(row?.body ?? "");
2188
- if (texts.length)
2189
- insertBlocks(db, docId, texts, null, null);
2229
+ const blocks = parseDocBlocks(row?.body ?? "");
2230
+ if (blocks.length)
2231
+ insertBlocks(db, docId, blocks, null, null);
2190
2232
  }
2191
2233
  function reconcileBody(db, docId, body) {
2192
2234
  const old = liveBlocks(db, docId);
2193
- 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));
2194
2237
  for (const oi of plan.deleted)
2195
2238
  emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
2196
2239
  const { items } = plan;
2197
2240
  let i = 0;
2198
2241
  while (i < items.length) {
2199
- 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);
2200
2248
  i++;
2201
2249
  continue;
2202
2250
  }
@@ -2205,8 +2253,7 @@ function reconcileBody(db, docId, body) {
2205
2253
  j++;
2206
2254
  const left = i > 0 ? old[items[i - 1].keep].order_key : null;
2207
2255
  const right = j < items.length ? old[items[j].keep].order_key : null;
2208
- const texts = items.slice(i, j).map((it) => it.insert);
2209
- insertBlocks(db, docId, texts, left, right);
2256
+ insertBlocks(db, docId, next.slice(i, j), left, right);
2210
2257
  i = j;
2211
2258
  }
2212
2259
  }
@@ -2225,9 +2272,9 @@ function createDocument(db, opts) {
2225
2272
  if (opts.parent_id !== undefined)
2226
2273
  emit(db, "documents", id, "parent_id", opts.parent_id);
2227
2274
  if (opts.body !== undefined) {
2228
- const texts = parseBlocks(opts.body);
2229
- if (texts.length)
2230
- insertBlocks(db, id, texts, null, null);
2275
+ const blocks = parseDocBlocks(opts.body);
2276
+ if (blocks.length)
2277
+ insertBlocks(db, id, blocks, null, null);
2231
2278
  }
2232
2279
  return getDocument(db, id);
2233
2280
  }
@@ -2273,7 +2320,7 @@ function editDocument(db, id, opts) {
2273
2320
  throw new Error("--old must not be empty");
2274
2321
  ensureBlocks(db, id);
2275
2322
  const blocks = liveBlocks(db, id);
2276
- const body = serializeBlocks(blocks.map((b) => b.text));
2323
+ const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
2277
2324
  const count = countOccurrences(body, opts.old);
2278
2325
  if (count === 0)
2279
2326
  throw new Error(`anchor not found: no match for --old`);
@@ -2300,25 +2347,25 @@ function appendDocument(db, id, body) {
2300
2347
  if (!getDocument(db, id))
2301
2348
  throw new Error(`no such document: ${id}`);
2302
2349
  ensureBlocks(db, id);
2303
- const texts = parseBlocks(body);
2304
- if (texts.length) {
2350
+ const next = parseDocBlocks(body);
2351
+ if (next.length) {
2305
2352
  const blocks = liveBlocks(db, id);
2306
2353
  const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
2307
- insertBlocks(db, id, texts, last, null);
2354
+ insertBlocks(db, id, next, last, null);
2308
2355
  }
2309
- 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) };
2310
2357
  }
2311
2358
  function prependDocument(db, id, body) {
2312
2359
  if (!getDocument(db, id))
2313
2360
  throw new Error(`no such document: ${id}`);
2314
2361
  ensureBlocks(db, id);
2315
- const texts = parseBlocks(body);
2316
- if (texts.length) {
2362
+ const next = parseDocBlocks(body);
2363
+ if (next.length) {
2317
2364
  const blocks = liveBlocks(db, id);
2318
2365
  const first = blocks.length ? blocks[0].order_key : null;
2319
- insertBlocks(db, id, texts, null, first);
2366
+ insertBlocks(db, id, next, null, first);
2320
2367
  }
2321
- 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) };
2322
2369
  }
2323
2370
  function deleteDocument(db, id) {
2324
2371
  if (!getDocument(db, id))
@@ -16964,8 +17011,9 @@ function getServerConfig(db) {
16964
17011
  // package.json
16965
17012
  var package_default = {
16966
17013
  name: "@tensorix/metahub",
16967
- version: "0.1.4",
17014
+ version: "0.1.6",
16968
17015
  description: "Metahub core library and CLI",
17016
+ license: "AGPL-3.0-only",
16969
17017
  type: "module",
16970
17018
  main: "./dist/index.js",
16971
17019
  module: "./dist/index.js",
@@ -16983,7 +17031,9 @@ var package_default = {
16983
17031
  },
16984
17032
  files: [
16985
17033
  "dist",
16986
- "README.md"
17034
+ "README.md",
17035
+ "README.zh-CN.md",
17036
+ "LICENSE"
16987
17037
  ],
16988
17038
  scripts: {
16989
17039
  dev: "bun --hot src/cli/index.ts",
@@ -17960,7 +18010,7 @@ export {
17960
18010
  slugify,
17961
18011
  setPropertyWidth,
17962
18012
  setCurrentDatabase,
17963
- serializeBlocks,
18013
+ serializeDocBlocks,
17964
18014
  search,
17965
18015
  runSchema,
17966
18016
  resolveSite,
@@ -17975,7 +18025,7 @@ export {
17975
18025
  putBlob,
17976
18026
  prependDocument,
17977
18027
  parseHlc,
17978
- parseBlocks,
18028
+ parseDocBlocks,
17979
18029
  openMetahub,
17980
18030
  observeHlc,
17981
18031
  nextHlc,
@@ -17983,6 +18033,7 @@ export {
17983
18033
  moveRecord,
17984
18034
  migrateRecords,
17985
18035
  migratePeers,
18036
+ migrateDocBlocks,
17986
18037
  metahubHome,
17987
18038
  maybeAutoIndex,
17988
18039
  makeId,
@@ -18049,4 +18100,4 @@ export {
18049
18100
  ChangeSchema
18050
18101
  };
18051
18102
 
18052
- //# debugId=539C6C8168968A4864756E2164756E21
18103
+ //# debugId=F7BD99C8461B833164756E2164756E21