@tensorix/metahub 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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":"server.d.ts","sourceRoot":"","sources":["../../../src/core/sync/server.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qFAAqF;IACrF,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wFAAwF;IACxF,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC5C,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,6EAA6E;AAC7E,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAQxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,aAAa,CA8InE"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/core/sync/server.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qFAAqF;IACrF,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wFAAwF;IACxF,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC5C,8CAA8C;IAC9C,QAAQ,EAAE,OAAO,CAAC;IAClB,qEAAqE;IACrE,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAED,6EAA6E;AAC7E,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;CAQxB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAYD,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,IAAI,GAAE,aAAkB,GAAG,aAAa,CAkJnE"}
@@ -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,CAsBvE"}
package/dist/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  // @bun
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -121,7 +125,7 @@ function observeHlc(db, node, remote, now = Date.now()) {
121
125
  }
122
126
 
123
127
  // src/core/blocks.ts
124
- function parseBlocks(md) {
128
+ function parseDocBlocks(md) {
125
129
  if (!md)
126
130
  return [];
127
131
  const lines = md.replace(/\r\n?/g, `
@@ -131,24 +135,24 @@ function parseBlocks(md) {
131
135
  let cur = [];
132
136
  let fenceChar = null;
133
137
  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 = [];
138
+ const closeBlock = () => {
139
+ if (cur.length) {
140
+ blocks.push({ text: cur.join(`
141
+ `), blankAfter: 0 });
142
+ cur = [];
143
+ }
144
+ };
145
+ const onBlank = () => {
146
+ closeBlock();
147
+ if (blocks.length)
148
+ blocks[blocks.length - 1].blankAfter++;
143
149
  };
144
150
  for (const raw of lines) {
145
151
  if (fenceChar) {
146
152
  cur.push(raw);
147
153
  const m = raw.match(/^\s*(`{3,}|~{3,})\s*$/);
148
154
  if (m && m[1][0] === fenceChar && m[1].length >= fenceLen) {
149
- blocks.push(cur.join(`
150
- `));
151
- cur = [];
155
+ closeBlock();
152
156
  fenceChar = null;
153
157
  fenceLen = 0;
154
158
  }
@@ -156,28 +160,34 @@ function parseBlocks(md) {
156
160
  }
157
161
  const open = raw.match(/^\s*(`{3,}|~{3,})/);
158
162
  if (open) {
159
- flushPara();
163
+ closeBlock();
160
164
  fenceChar = open[1][0];
161
165
  fenceLen = open[1].length;
162
166
  cur.push(raw);
163
167
  } else if (raw.trim() === "") {
164
- flushPara();
168
+ onBlank();
165
169
  } else {
166
170
  cur.push(raw);
167
171
  }
168
172
  }
169
- if (fenceChar) {
170
- if (cur.length)
171
- blocks.push(cur.join(`
172
- `));
173
- } else {
174
- flushPara();
175
- }
173
+ closeBlock();
174
+ for (let i = 0;i < blocks.length - 1; i++)
175
+ blocks[i].blankAfter = Math.max(0, blocks[i].blankAfter - 1);
176
176
  return blocks;
177
177
  }
178
- function serializeBlocks(texts) {
179
- return texts.filter((t) => !!t && t.length > 0).join(`
180
-
178
+ function serializeDocBlocks(blocks) {
179
+ const live = blocks.filter((b) => !!b.text && b.text.length > 0);
180
+ const out = [];
181
+ live.forEach((b, i) => {
182
+ out.push(...b.text.split(`
183
+ `));
184
+ const extra = Math.max(0, b.blankAfter ?? 0);
185
+ if (i < live.length - 1)
186
+ out.push("");
187
+ for (let k = 0;k < extra; k++)
188
+ out.push("");
189
+ });
190
+ return out.join(`
181
191
  `);
182
192
  }
183
193
  function reconcile(oldTexts, newTexts) {
@@ -234,9 +244,9 @@ function isBlockManaged(db, docId) {
234
244
  return db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId) != null;
235
245
  }
236
246
  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);
247
+ 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
248
  ensureRow(db, "documents", docId);
239
- db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeBlocks(rows.map((r) => r.text)), docId);
249
+ db.query("UPDATE documents SET body = ? WHERE id = ?").run(serializeDocBlocks(rows.map((r) => ({ text: r.text, blankAfter: r.blank_after ?? 0 }))), docId);
240
250
  }
241
251
  function materialize(db, dataset, rowId, col, valueJson) {
242
252
  if (dataset === "records") {
@@ -336,7 +346,7 @@ var init_crdt = __esm(() => {
336
346
  },
337
347
  doc_blocks: {
338
348
  table: "doc_blocks",
339
- cols: new Set(["doc_id", "text", "order_key", "__deleted"])
349
+ cols: new Set(["doc_id", "text", "order_key", "blank_after", "__deleted"])
340
350
  },
341
351
  sites: {
342
352
  table: "sites",
@@ -572,6 +582,7 @@ async function serveWebui(req) {
572
582
  headers: { "content-type": "text/javascript; charset=utf-8" }
573
583
  });
574
584
  } catch (e) {
585
+ console.error("[webui] failed to serve /webui.js \u2014", e);
575
586
  return new Response(`webui build failed: ${e}`, { status: 500 });
576
587
  }
577
588
  }
@@ -605,6 +616,7 @@ var HTML = `<!doctype html>
605
616
  --radius:8px; --radius-sm:6px;
606
617
  --ui:"Hanken Grotesk",-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
607
618
  --mono:"JetBrains Mono",ui-monospace,SFMono-Regular,monospace;
619
+ --note-lh:1.6;
608
620
  --code-bg:#f8f8f6;
609
621
  --hl-cmt:#9a9aa2; --hl-key:#a626a4; --hl-str:#28823f; --hl-num:#b76a00;
610
622
  --hl-fn:#3f51d6; --hl-type:#0a7ea4; --hl-var:#c2410c;
@@ -637,7 +649,7 @@ var HTML = `<!doctype html>
637
649
  * { box-sizing: border-box; }
638
650
  html, body { margin: 0; height: 100%; }
639
651
  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; }
652
+ overflow: hidden; text-rendering: auto; }
641
653
  button { font: inherit; color: inherit; cursor: pointer; background: none; border: 0; padding: 0; }
642
654
  input, textarea { font: inherit; color: inherit; }
643
655
  .muted { color: var(--muted); }
@@ -693,6 +705,8 @@ var HTML = `<!doctype html>
693
705
  .dragging { opacity:.35; }
694
706
  .sb-footer { flex:none; padding:6px 8px; border-top:1px solid var(--line); }
695
707
  .sb-footer .navitem { font-size:13.5px; }
708
+ .nav-dot { flex:none; width:7px; height:7px; border-radius:50%; background:var(--danger);
709
+ box-shadow:0 0 0 2px var(--surface); margin-right:2px; }
696
710
 
697
711
  /* settings page */
698
712
  .set-page { max-width:680px; margin:0 auto; padding:40px 28px 64px; }
@@ -702,8 +716,19 @@ var HTML = `<!doctype html>
702
716
  .set-section-head { font-size:12px; font-weight:600; text-transform:uppercase; letter-spacing:.07em; color:var(--muted); margin-bottom:4px; }
703
717
  .set-section-desc { color:var(--fg-soft); margin-bottom:16px; }
704
718
  .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; }
719
+ flex-wrap:wrap; justify-content:center; align-items:center; gap:7px; color:var(--muted); font-size:12px; }
720
+ .set-footer-sep { opacity:.45; }
721
+ .ver-num { font-family:var(--mono); font-size:11px; color:var(--fg-soft); letter-spacing:-.01em; }
722
+ .ver-up { display:inline-flex; align-items:center; gap:6px; }
723
+ .ver-up.err .ver-msg { color:var(--danger); }
724
+ .ver-act { background:none; border:0; padding:0; margin:0; font:inherit; font-size:12px; line-height:1;
725
+ color:var(--muted); cursor:pointer; transition:color .12s; }
726
+ .ver-act:hover { color:var(--accent); }
727
+ .ver-act.accent { color:var(--accent); font-weight:600; }
728
+ .ver-act[aria-disabled="true"] { opacity:.6; cursor:default; pointer-events:none; }
729
+ .ver-dot { width:6px; height:6px; border-radius:50%; background:var(--accent); flex:none; }
730
+ .ver-dot.pulse { animation:verpulse 1.8s ease-in-out infinite; }
731
+ @keyframes verpulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }
707
732
  .theme-grid { display:grid; grid-template-columns:repeat(3,1fr); gap:12px; }
708
733
  .theme-card { position:relative; display:flex; flex-direction:column; align-items:flex-start; gap:10px;
709
734
  text-align:left; padding:16px; border:1px solid var(--line-strong); border-radius:var(--radius);
@@ -796,8 +821,10 @@ var HTML = `<!doctype html>
796
821
  /* The list item's host gutter controls the whole unit, so suppress the nested
797
822
  code block's own gutter (it would overlap the list marker on the same row). */
798
823
  .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; }
824
+ .editable { outline:none; flex:1; min-width:0; padding:3px 2px; line-height:var(--note-lh); }
800
825
  .editable:empty::before { content:attr(data-ph); color:var(--muted); pointer-events:none; }
826
+ /* The generic "/" prompt only shows on the focused line; idle blank lines (paragraphs and list items, any depth) stay blank. */
827
+ .editable[data-ph-hint="slash"]:empty:not(:focus)::before { content:""; }
801
828
  .b-h1 .editable { font-size:28px; font-weight:700; letter-spacing:-.02em; padding-top:12px; }
802
829
  .b-h2 .editable { font-size:22px; font-weight:650; letter-spacing:-.01em; padding-top:8px; }
803
830
  .b-h3 .editable { font-size:18px; font-weight:600; padding-top:4px; }
@@ -849,7 +876,8 @@ var HTML = `<!doctype html>
849
876
  .hljs-emphasis { font-style:italic; }
850
877
  .hljs-strong { font-weight:700; }
851
878
  .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; }
879
+ .b-todo .marker { box-sizing:content-box; padding-top:3px; height:calc(1em * var(--note-lh));
880
+ display:flex; align-items:center; justify-content:center; }
853
881
  .b-todo .marker input { width:15px; height:15px; accent-color:var(--accent); cursor:pointer; }
854
882
  .b-divider { padding:10px 0; }
855
883
  .b-divider hr { border:0; border-top:1px solid var(--line-strong); margin:0; width:100%; }
@@ -1036,14 +1064,14 @@ var HTML = `<!doctype html>
1036
1064
  .optrow .x:hover { background:var(--hover-2); color:var(--danger); }
1037
1065
 
1038
1066
  /* modal */
1039
- .modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:grid; place-items:center;
1067
+ .modal-scrim { position:fixed; inset:0; background:rgba(15,15,25,.4); z-index:100; display:flex; align-items:center; justify-content:center;
1040
1068
  opacity:0; pointer-events:none; transition:opacity .15s; padding:20px; }
1041
1069
  .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; }
1070
+ .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
1071
  .modal-head { padding:20px 22px 0; }
1044
1072
  .modal-head h3 { margin:0; font-size:18px; font-weight:700; letter-spacing:-.01em; }
1045
1073
  .modal-head p { margin:6px 0 0; color:var(--muted); font-size:13.5px; }
1046
- .modal-body { padding:18px 22px; }
1074
+ .modal-body { padding:18px 22px; overflow-y:auto; }
1047
1075
  .modal-foot { display:flex; justify-content:flex-end; gap:8px; padding:14px 22px; background:var(--surface); border-top:1px solid var(--line); }
1048
1076
  .field-label { font-size:12px; font-weight:600; color:var(--fg-soft); margin:12px 0 5px; }
1049
1077
  .field-label:first-child { margin-top:0; }
@@ -1262,6 +1290,17 @@ var HTML = `<!doctype html>
1262
1290
  .qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
1263
1291
  .qn-body .doc-title { font-size:24px; margin-bottom:2px; }
1264
1292
  .qn-body .doc-meta { display:none; }
1293
+ /* A native checkbox paints an opaque white box when unchecked, which breaks
1294
+ the window's vibrancy blur. Draw a custom box so the unchecked background
1295
+ stays transparent; checked falls back to the accent fill + checkmark. */
1296
+ body.quicknote .b-todo .marker input {
1297
+ appearance:none; -webkit-appearance:none; box-sizing:border-box;
1298
+ border:1.5px solid color-mix(in srgb, var(--fg-soft) 55%, transparent);
1299
+ border-radius:4px; background:transparent; }
1300
+ body.quicknote .b-todo .marker input:checked {
1301
+ border-color:var(--accent);
1302
+ background:var(--accent) center/10px no-repeat
1303
+ 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
1304
  /* Compact menus inside the quick-note window only (dropdown + slash command
1266
1305
  share .pop/.item/.lico with the main app; scope under body.quicknote so the
1267
1306
  main app keeps its roomier density). */
@@ -1434,11 +1473,12 @@ CREATE TABLE IF NOT EXISTS documents (
1434
1473
  -- independent CRDT register so concurrent edits to different blocks merge.
1435
1474
  -- order_key is a fractional index (ties broken by id) giving display order.
1436
1475
  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
1476
+ id TEXT PRIMARY KEY,
1477
+ doc_id TEXT,
1478
+ text TEXT,
1479
+ order_key TEXT,
1480
+ blank_after INTEGER NOT NULL DEFAULT 0, -- extra blank lines kept after this block (user spacing)
1481
+ __deleted INTEGER NOT NULL DEFAULT 0
1442
1482
  );
1443
1483
  CREATE INDEX IF NOT EXISTS idx_doc_blocks_doc ON doc_blocks(doc_id);
1444
1484
 
@@ -2126,6 +2166,10 @@ function migratePeers(db) {
2126
2166
  db.exec(`ALTER TABLE peers ADD COLUMN ${col} ${decl}`);
2127
2167
  }
2128
2168
  }
2169
+ function migrateDocBlocks(db) {
2170
+ if (!hasColumn(db, "doc_blocks", "blank_after"))
2171
+ db.exec("ALTER TABLE doc_blocks ADD COLUMN blank_after INTEGER NOT NULL DEFAULT 0");
2172
+ }
2129
2173
  function openMetahub() {
2130
2174
  ensureDirs();
2131
2175
  const db = new Database(dbPath(), { create: true });
@@ -2133,6 +2177,7 @@ function openMetahub() {
2133
2177
  runSchema(db);
2134
2178
  migrateRecords(db);
2135
2179
  migratePeers(db);
2180
+ migrateDocBlocks(db);
2136
2181
  return db;
2137
2182
  }
2138
2183
 
@@ -2164,7 +2209,7 @@ function clearCurrentDatabase(db) {
2164
2209
  init_ids();
2165
2210
  init_crdt();
2166
2211
  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);
2212
+ 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
2213
  }
2169
2214
  function makeBlockId(text) {
2170
2215
  return newId("blk", text.split(`
@@ -2174,29 +2219,37 @@ function emitBlock(db, docId, fields) {
2174
2219
  const id = makeBlockId(fields.text);
2175
2220
  emit(db, "doc_blocks", id, "doc_id", docId);
2176
2221
  emit(db, "doc_blocks", id, "order_key", fields.order_key);
2222
+ if (fields.blankAfter)
2223
+ emit(db, "doc_blocks", id, "blank_after", fields.blankAfter);
2177
2224
  emit(db, "doc_blocks", id, "text", fields.text);
2178
2225
  }
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] }));
2226
+ function insertBlocks(db, docId, blocks, after, before) {
2227
+ const keys = keysBetween(after, before, blocks.length);
2228
+ blocks.forEach((b, i) => emitBlock(db, docId, { text: b.text, order_key: keys[i], blankAfter: b.blankAfter }));
2182
2229
  }
2183
2230
  function ensureBlocks(db, docId) {
2184
2231
  if (db.query("SELECT 1 AS x FROM doc_blocks WHERE doc_id = ? LIMIT 1").get(docId))
2185
2232
  return;
2186
2233
  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);
2234
+ const blocks = parseDocBlocks(row?.body ?? "");
2235
+ if (blocks.length)
2236
+ insertBlocks(db, docId, blocks, null, null);
2190
2237
  }
2191
2238
  function reconcileBody(db, docId, body) {
2192
2239
  const old = liveBlocks(db, docId);
2193
- const plan = reconcile(old.map((b) => b.text), parseBlocks(body));
2240
+ const next = parseDocBlocks(body);
2241
+ const plan = reconcile(old.map((b) => b.text), next.map((b) => b.text));
2194
2242
  for (const oi of plan.deleted)
2195
2243
  emit(db, "doc_blocks", old[oi].id, "__deleted", 1);
2196
2244
  const { items } = plan;
2197
2245
  let i = 0;
2198
2246
  while (i < items.length) {
2199
- if ("keep" in items[i]) {
2247
+ const it = items[i];
2248
+ if ("keep" in it) {
2249
+ const oldB = old[it.keep];
2250
+ const want = next[i].blankAfter;
2251
+ if ((oldB.blank_after ?? 0) !== want)
2252
+ emit(db, "doc_blocks", oldB.id, "blank_after", want);
2200
2253
  i++;
2201
2254
  continue;
2202
2255
  }
@@ -2205,8 +2258,7 @@ function reconcileBody(db, docId, body) {
2205
2258
  j++;
2206
2259
  const left = i > 0 ? old[items[i - 1].keep].order_key : null;
2207
2260
  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);
2261
+ insertBlocks(db, docId, next.slice(i, j), left, right);
2210
2262
  i = j;
2211
2263
  }
2212
2264
  }
@@ -2225,9 +2277,9 @@ function createDocument(db, opts) {
2225
2277
  if (opts.parent_id !== undefined)
2226
2278
  emit(db, "documents", id, "parent_id", opts.parent_id);
2227
2279
  if (opts.body !== undefined) {
2228
- const texts = parseBlocks(opts.body);
2229
- if (texts.length)
2230
- insertBlocks(db, id, texts, null, null);
2280
+ const blocks = parseDocBlocks(opts.body);
2281
+ if (blocks.length)
2282
+ insertBlocks(db, id, blocks, null, null);
2231
2283
  }
2232
2284
  return getDocument(db, id);
2233
2285
  }
@@ -2273,7 +2325,7 @@ function editDocument(db, id, opts) {
2273
2325
  throw new Error("--old must not be empty");
2274
2326
  ensureBlocks(db, id);
2275
2327
  const blocks = liveBlocks(db, id);
2276
- const body = serializeBlocks(blocks.map((b) => b.text));
2328
+ const body = serializeDocBlocks(blocks.map((b) => ({ text: b.text, blankAfter: b.blank_after })));
2277
2329
  const count = countOccurrences(body, opts.old);
2278
2330
  if (count === 0)
2279
2331
  throw new Error(`anchor not found: no match for --old`);
@@ -2300,25 +2352,25 @@ function appendDocument(db, id, body) {
2300
2352
  if (!getDocument(db, id))
2301
2353
  throw new Error(`no such document: ${id}`);
2302
2354
  ensureBlocks(db, id);
2303
- const texts = parseBlocks(body);
2304
- if (texts.length) {
2355
+ const next = parseDocBlocks(body);
2356
+ if (next.length) {
2305
2357
  const blocks = liveBlocks(db, id);
2306
2358
  const last = blocks.length ? blocks[blocks.length - 1].order_key : null;
2307
- insertBlocks(db, id, texts, last, null);
2359
+ insertBlocks(db, id, next, last, null);
2308
2360
  }
2309
- return { id, changed: texts.length > 0, replaced: texts.length, version: documentVersion(db, id) };
2361
+ return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
2310
2362
  }
2311
2363
  function prependDocument(db, id, body) {
2312
2364
  if (!getDocument(db, id))
2313
2365
  throw new Error(`no such document: ${id}`);
2314
2366
  ensureBlocks(db, id);
2315
- const texts = parseBlocks(body);
2316
- if (texts.length) {
2367
+ const next = parseDocBlocks(body);
2368
+ if (next.length) {
2317
2369
  const blocks = liveBlocks(db, id);
2318
2370
  const first = blocks.length ? blocks[0].order_key : null;
2319
- insertBlocks(db, id, texts, null, first);
2371
+ insertBlocks(db, id, next, null, first);
2320
2372
  }
2321
- return { id, changed: texts.length > 0, replaced: texts.length, version: documentVersion(db, id) };
2373
+ return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
2322
2374
  }
2323
2375
  function deleteDocument(db, id) {
2324
2376
  if (!getDocument(db, id))
@@ -13981,7 +14033,7 @@ function finalize(ctx, schema) {
13981
14033
  result.$schema = "http://json-schema.org/draft-07/schema#";
13982
14034
  } else if (ctx.target === "draft-04") {
13983
14035
  result.$schema = "http://json-schema.org/draft-04/schema#";
13984
- } else if (ctx.target === "openapi-3.0") {} else {}
14036
+ } else if (ctx.target === "openapi-3.0") {}
13985
14037
  if (ctx.external?.uri) {
13986
14038
  const id = ctx.external.registry.get(schema)?.id;
13987
14039
  if (!id)
@@ -14225,7 +14277,7 @@ var literalProcessor = (schema, ctx, json, _params) => {
14225
14277
  if (val === undefined) {
14226
14278
  if (ctx.unrepresentable === "throw") {
14227
14279
  throw new Error("Literal `undefined` cannot be represented in JSON Schema");
14228
- } else {}
14280
+ }
14229
14281
  } else if (typeof val === "bigint") {
14230
14282
  if (ctx.unrepresentable === "throw") {
14231
14283
  throw new Error("BigInt literals cannot be represented in JSON Schema");
@@ -16964,8 +17016,9 @@ function getServerConfig(db) {
16964
17016
  // package.json
16965
17017
  var package_default = {
16966
17018
  name: "@tensorix/metahub",
16967
- version: "0.1.4",
17019
+ version: "0.1.7",
16968
17020
  description: "Metahub core library and CLI",
17021
+ license: "AGPL-3.0-only",
16969
17022
  type: "module",
16970
17023
  main: "./dist/index.js",
16971
17024
  module: "./dist/index.js",
@@ -16983,7 +17036,9 @@ var package_default = {
16983
17036
  },
16984
17037
  files: [
16985
17038
  "dist",
16986
- "README.md"
17039
+ "README.md",
17040
+ "README.zh-CN.md",
17041
+ "LICENSE"
16987
17042
  ],
16988
17043
  scripts: {
16989
17044
  dev: "bun --hot src/cli/index.ts",
@@ -17927,7 +17982,7 @@ function startServer(opts = {}) {
17927
17982
  let timer = null;
17928
17983
  if (autoSync && syncIntervalMs > 0) {
17929
17984
  timer = setInterval(() => {
17930
- syncAllPeers(db).catch(() => {});
17985
+ syncAllPeers(db).catch((e) => console.error("[sync] auto-sync tick failed \u2014", e));
17931
17986
  }, syncIntervalMs);
17932
17987
  timer.unref?.();
17933
17988
  }
@@ -17960,7 +18015,7 @@ export {
17960
18015
  slugify,
17961
18016
  setPropertyWidth,
17962
18017
  setCurrentDatabase,
17963
- serializeBlocks,
18018
+ serializeDocBlocks,
17964
18019
  search,
17965
18020
  runSchema,
17966
18021
  resolveSite,
@@ -17975,7 +18030,7 @@ export {
17975
18030
  putBlob,
17976
18031
  prependDocument,
17977
18032
  parseHlc,
17978
- parseBlocks,
18033
+ parseDocBlocks,
17979
18034
  openMetahub,
17980
18035
  observeHlc,
17981
18036
  nextHlc,
@@ -17983,6 +18038,7 @@ export {
17983
18038
  moveRecord,
17984
18039
  migrateRecords,
17985
18040
  migratePeers,
18041
+ migrateDocBlocks,
17986
18042
  metahubHome,
17987
18043
  maybeAutoIndex,
17988
18044
  makeId,
@@ -18049,4 +18105,4 @@ export {
18049
18105
  ChangeSchema
18050
18106
  };
18051
18107
 
18052
- //# debugId=539C6C8168968A4864756E2164756E21
18108
+ //# debugId=548591A808956A3F64756E2164756E21