mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
feat(plugins): hero widget slot + Koffi reference plugin
Widget plugins can now declare capabilities.widget.slot 'hero' to render as a transparent, click-through overlay sitting on the boarding-pass bar's top edge (migration 157 persists capabilities; manifest validation server+SDK, feed exposes the slot, dashboard mounts hero frames above the pass). Sidebar stays the default slot. Replaces the trip-countdown example with Koffi, the TREK mascot: an animated suitcase with a 14-state behavior engine driven by real trip data — walking, waving, napping, trolley rolls, passport-stamp stickers, a split-flap luggage- tag countdown under 7 days, and sunglasses while the trip runs. Validated by the SDK suite like any author plugin; published as mauriceboe/trek-plugin-koffi in the registry. Migrations 156/157 follow the idempotent ALTER pattern (the reconciliation test re-runs everything from v135, so duplicate-column must stay non-fatal).
This commit is contained in:
@@ -21,7 +21,7 @@ const { testDb } = vi.hoisted(() => {
|
||||
const db = new Database(':memory:');
|
||||
db.exec(`
|
||||
CREATE TABLE plugins (id TEXT PRIMARY KEY, name TEXT, description TEXT, type TEXT, icon TEXT, version TEXT,
|
||||
api_version INTEGER, min_trek_version TEXT, permissions TEXT, granted_permissions TEXT, status TEXT, config TEXT,
|
||||
api_version INTEGER, min_trek_version TEXT, permissions TEXT, capabilities TEXT DEFAULT '{}', granted_permissions TEXT, status TEXT, config TEXT,
|
||||
source_repo TEXT, source_commit TEXT, sha256 TEXT, reviewed_at TEXT, updated_at TEXT);
|
||||
CREATE TABLE plugin_settings_fields (plugin_id TEXT, field_key TEXT, label TEXT, input_type TEXT, placeholder TEXT, hint TEXT, required INTEGER, secret INTEGER, scope TEXT, options TEXT, oauth_config TEXT, sort_order INTEGER);
|
||||
CREATE TABLE plugin_error_log (id INTEGER PRIMARY KEY AUTOINCREMENT, plugin_id TEXT, level TEXT, message TEXT, ts TEXT);`);
|
||||
|
||||
@@ -27,7 +27,7 @@ beforeEach(() => {
|
||||
db = new Database(':memory:');
|
||||
db.exec(`
|
||||
CREATE TABLE plugins (id TEXT PRIMARY KEY, name TEXT, description TEXT, type TEXT, icon TEXT, version TEXT,
|
||||
api_version INTEGER, min_trek_version TEXT, permissions TEXT, granted_permissions TEXT, status TEXT, config TEXT, updated_at TEXT);
|
||||
api_version INTEGER, min_trek_version TEXT, permissions TEXT, capabilities TEXT DEFAULT '{}', granted_permissions TEXT, status TEXT, config TEXT, updated_at TEXT);
|
||||
CREATE TABLE plugin_settings_fields (plugin_id TEXT, field_key TEXT, label TEXT, input_type TEXT, placeholder TEXT, hint TEXT,
|
||||
required INTEGER, secret INTEGER, scope TEXT, options TEXT, oauth_config TEXT, sort_order INTEGER);
|
||||
CREATE TABLE plugin_error_log (id INTEGER PRIMARY KEY AUTOINCREMENT, plugin_id TEXT, level TEXT, message TEXT, ts TEXT);`);
|
||||
|
||||
@@ -47,3 +47,17 @@ describe('parseManifest', () => {
|
||||
expect(() => parseManifest(input)).toThrow(re as RegExp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseManifest capabilities', () => {
|
||||
it('parses a hero widget slot and defaults to sidebar', () => {
|
||||
const hero = parseManifest({ ...base, capabilities: { widget: { slot: 'hero', title: 'T' } } });
|
||||
expect(hero.capabilities.widget?.slot).toBe('hero');
|
||||
const plain = parseManifest({ ...base, capabilities: { widget: {} } });
|
||||
expect(plain.capabilities.widget?.slot).toBe('sidebar');
|
||||
expect(parseManifest(base).capabilities).toEqual({});
|
||||
});
|
||||
|
||||
it('rejects an unknown widget slot', () => {
|
||||
expect(() => parseManifest({ ...base, capabilities: { widget: { slot: 'floating' } } })).toThrow(ManifestError);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ const { testDb } = vi.hoisted(() => {
|
||||
const db = new Database(':memory:');
|
||||
db.exec(`CREATE TABLE plugins (
|
||||
id TEXT PRIMARY KEY, name TEXT, description TEXT, type TEXT, icon TEXT, version TEXT,
|
||||
status TEXT, enabled INTEGER DEFAULT 0, last_error TEXT, reviewed_at TEXT, source_repo TEXT, config TEXT DEFAULT '{}', updated_at TEXT,
|
||||
status TEXT, enabled INTEGER DEFAULT 0, last_error TEXT, reviewed_at TEXT, source_repo TEXT, config TEXT DEFAULT '{}', capabilities TEXT DEFAULT '{}', updated_at TEXT,
|
||||
sort_order INTEGER DEFAULT 0);
|
||||
CREATE TABLE plugin_settings_fields (plugin_id TEXT, field_key TEXT, scope TEXT, secret INTEGER);
|
||||
CREATE TABLE plugin_error_log (id INTEGER PRIMARY KEY AUTOINCREMENT, plugin_id TEXT, level TEXT, message TEXT, ts TEXT DEFAULT '2026-01-01');`);
|
||||
@@ -66,11 +66,20 @@ describe('PluginsFeedController (client feed)', () => {
|
||||
|
||||
process.env.TREK_PLUGINS_ENABLED = 'true';
|
||||
const active = feed.list();
|
||||
expect(active.plugins).toEqual([{ id: 'w', name: 'W', type: 'widget', icon: 'Box' }]);
|
||||
expect(active.plugins).toEqual([{ id: 'w', name: 'W', type: 'widget', icon: 'Box', slot: 'sidebar' }]);
|
||||
|
||||
process.env.TREK_PLUGINS_ENABLED = 'false';
|
||||
expect(feed.list().plugins).toEqual([]);
|
||||
});
|
||||
|
||||
it('exposes the widget slot from capabilities (hero) and defaults on bad JSON', () => {
|
||||
testDb.prepare("INSERT INTO plugins (id, name, type, icon, status, capabilities) VALUES ('h','H','widget','Box','active','{\"widget\":{\"slot\":\"hero\"}}')").run();
|
||||
testDb.prepare("INSERT INTO plugins (id, name, type, icon, status, capabilities) VALUES ('b','B','widget','Box','active','not-json')").run();
|
||||
process.env.TREK_PLUGINS_ENABLED = 'true';
|
||||
const out = new PluginsFeedController().list();
|
||||
expect(out.plugins.find((p) => p.id === 'h')?.slot).toBe('hero');
|
||||
expect(out.plugins.find((p) => p.id === 'b')?.slot).toBe('sidebar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('PluginsController M2 endpoints', () => {
|
||||
|
||||
Reference in New Issue
Block a user