v2.0.10 — snappier boot + non-blocking relay push on save

DB
- Six indices on the cards table (position+created_at, bucket_id,
  parent_id, updated_at, caldav_uid, relay_uid). The /api/cards
  ORDER BY position was a full-table scan + sort on every board
  reload; now it's an index walk.
- PRAGMA synchronous=NORMAL (safe under WAL) + temp_store=MEMORY.

Save path
- pushCardToRelayRead and deleteCardFromRelayRead now run AFTER the
  response is flushed via fastcgi_finish_request (falls back to
  ignore_user_abort + flush on non-FPM setups). The user's save no
  longer blocks on the relay round-trip.
- cURL timeouts dropped from 10s/30s to 5s/10s, plus a 2s
  connect-timeout. Worst-case on a dead relay goes from ~160s down
  to ~15s — and now that worst case is in the background anyway.

Client
- loadCards phase-2 batches bumped from 25 to 500 cards per fetch.
  For 300 cards that's 1 HTTP trip instead of 12 — spinner wall time
  drops from ~3s to ~0.25s.
- renderBoard() is now requestAnimationFrame-coalesced. The boot
  sequence calls renderBoard from cache paint, meta paint, body
  paint, and the cards.reloaded plugin hook; they used to each
  trigger an innerHTML swap + layout pass. Now they collapse into
  one actual render per frame. Callers that really need a sync
  render (rare) can use the new renderBoardSync().

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-18 20:41:48 +02:00
1 parent 87ebcd156b
commit 2d58cd993e
7 files changed
+124 -28

No files matched your search

+20
View File
@@ -19,6 +19,14 @@ class Database
self::$pdo->exec('PRAGMA journal_mode = WAL');
self::$pdo->exec('PRAGMA foreign_keys = ON');
// Safe speedups under WAL. synchronous=NORMAL trades a tiny
// durability window on power-loss (WAL checkpoint hasn't
// flushed) for noticeably quicker write-heavy flows — fine for
// a single-user local-first app. temp_store=MEMORY keeps
// transient sort / group scratch in RAM instead of touching
// disk for every ORDER BY.
self::$pdo->exec('PRAGMA synchronous = NORMAL');
self::$pdo->exec('PRAGMA temp_store = MEMORY');
self::migrate();
}
@@ -188,5 +196,17 @@ class Database
)
');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_login_attempts_ip_ep ON login_attempts(ip, endpoint, attempted_at)');
// Hot-path indices for the board. /api/cards is called on every
// reload and runs `SELECT … FROM cards LEFT JOIN buckets ORDER BY
// position, created_at`. Without an index on position this is a
// full-table sort on every call — fine at 10 cards, noticeable at
// 300+.
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_position ON cards(position, created_at)');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_bucket_id ON cards(bucket_id)');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_parent_id ON cards(parent_id)');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_updated_at ON cards(updated_at DESC)');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_caldav_uid ON cards(caldav_uid) WHERE caldav_uid IS NOT NULL');
self::$pdo->exec('CREATE INDEX IF NOT EXISTS idx_cards_relay_uid ON cards(relay_uid) WHERE relay_uid IS NOT NULL');
}
}
+18
View File
@@ -89,6 +89,24 @@ function hyprcrdRelayDecrypt(string $envelope, string $secret): string {
return $envelope;
}
/**
* Finalise the HTTP response (headers + body already echoed) and let
* PHP continue working in the background without blocking the client.
* Under php-fpm this uses fastcgi_finish_request. Under mod_php / the
* built-in dev server we fall back to ignore_user_abort + flush — the
* client may still see the connection hold open, but at least it
* doesn't error on disconnect and the response is already buffered.
*/
function hyprcrdFinishRequest(): void {
if (function_exists('fastcgi_finish_request')) {
fastcgi_finish_request();
return;
}
@ignore_user_abort(true);
if (ob_get_level() > 0) { @ob_end_flush(); }
@flush();
}
function hyprcrdUrlHostIsPublic(string $url): bool {
$host = parse_url($url, PHP_URL_HOST);
if (!$host) return false;