Library rows installed from the community embed now carry the source
post slug + installed version_number in the engine sqlite. The SPA
uses both surfaces: a "community" pill next to the row name (so users
can tell at a glance which entries were authored locally vs pulled
from the embed), and a warn-coloured "v{N}" pill when the post has
shipped a newer version since install.
Engine:
- Migration 013 adds community_post_slug + community_version_number
to modules + bundles. Both nullable; locally-authored rows stay
NULL forever. Implemented as a .py migration with PRAGMA
table_info guards so rerunning against an already-migrated DB
(test fixtures rewind migrations to exercise older paths) is a
clean no-op rather than "duplicate column name".
- _insert_module / _insert_bundle (importer.py) persist the fields
when the entity dict carries them.
- _row_to_module / _row_to_bundle (repositories.py) surface them on
every read path. Try/except guard for pre-013 DB shape so a
connection opened mid-rollout does not crash.
Manager:
- installEnvelope grows optional `origin: { post_slug, version_number }`
in InstallOptions. When set, stamps every module + bundle in the
selection before the commit roundtrip.
- Host bridge in main.ts threads origin from `install(envelope, opts)`
through to installEnvelope. ABI stays at 1 (additive).
- New communityUpdateStore walks the library snapshot, batch-fetches
each unique community post (deduped, 8-wide concurrency), surfaces
rows whose installed version is behind the post's
latest_version_number. Network failures clear the store silently
so an offline session does not render misleading "v2 available"
pills.
- AppLayout kicks one check after the initial library fetch
resolves. No background polling.
- ModuleListView renders two distinct pills per row: a static
"community" indicator on origin-stamped rows, and a separate
warn-coloured "v{N}" pill when the update store has an entry.
Migration tests updated for the new schema head + bundle/module
column sets.
50 lines
2.2 KiB
Python
50 lines
2.2 KiB
Python
"""Migration 013 — community install origin columns.
|
|
|
|
Adds ``community_post_slug`` and ``community_version_number`` to both
|
|
``modules`` and ``bundles``. Stamped at install time by the host-bridge
|
|
``install()`` path (via the embed's ``origin`` opt) so the SPA can:
|
|
|
|
- Render an "installed from community" pill on origin-stamped rows.
|
|
- Run an update-availability check that compares
|
|
``community_version_number`` against the post's
|
|
``latest_version_number`` in the community API.
|
|
|
|
Both columns are nullable on purpose — locally-authored rows (Create
|
|
flow, Import-from-file, starter set) leave them NULL forever.
|
|
|
|
Idempotent: each ALTER is gated on ``PRAGMA table_info`` so re-running
|
|
the migration against a DB that already has the columns is a no-op.
|
|
The migration runner's existing version table guards against re-runs
|
|
in production; this guard exists for tests that explicitly rewind the
|
|
``migrations`` table to exercise the migration on a populated DB
|
|
(see ``tests/engine/db/test_migration_010.py``).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
|
|
|
|
def _has_column(conn: sqlite3.Connection, table: str, column: str) -> bool:
|
|
cur = conn.execute(f"PRAGMA table_info({table});")
|
|
return any(row[1] == column for row in cur.fetchall())
|
|
|
|
|
|
def up(conn: sqlite3.Connection) -> None:
|
|
with conn:
|
|
if not _has_column(conn, "modules", "community_post_slug"):
|
|
conn.execute("ALTER TABLE modules ADD COLUMN community_post_slug TEXT;")
|
|
if not _has_column(conn, "modules", "community_version_number"):
|
|
conn.execute("ALTER TABLE modules ADD COLUMN community_version_number INTEGER;")
|
|
if not _has_column(conn, "bundles", "community_post_slug"):
|
|
conn.execute("ALTER TABLE bundles ADD COLUMN community_post_slug TEXT;")
|
|
if not _has_column(conn, "bundles", "community_version_number"):
|
|
conn.execute("ALTER TABLE bundles ADD COLUMN community_version_number INTEGER;")
|
|
conn.execute(
|
|
"CREATE INDEX IF NOT EXISTS idx_modules_community_slug "
|
|
"ON modules(community_post_slug);"
|
|
)
|
|
conn.execute(
|
|
"CREATE INDEX IF NOT EXISTS idx_bundles_community_slug "
|
|
"ON bundles(community_post_slug);"
|
|
)
|