837aa77bfc
Weather, Stocks, and Bookmarks move into backend/src/widgets/<id>/ alongside PoE2, each a full WidgetPlugin rather than a thin wrapper: their tables are renamed to the widget_<id>_ convention (stock_tickers, bookmarks) or moved off global_settings entirely into the generic widget_kv store (weather's location/unit config and forecast cache). Every widget's routes move under a consistent /api/widget/<id> (public) and /api/admin/widget/<id> (admin) namespace, replacing the previous flat /api/weather, /api/admin/poe2/browse, etc. The registry-management routes (list/upload/enable/delete any widget) stay at /api/admin/widgets since they address the collection, not one widget's own data. This also closes the gap where an uploaded widget had backend data plumbing but no visible presence anywhere: a widget's poll() can now publish to a generic GET /api/widget/:id/report feed (registered once, works for any id with zero per-widget route registration, so it's live immediately after upload with no restart); the sidebar renders it via a new GenericWidgetCard, or via a new DynamicWidgetSlot that dynamic-imports an optional pre-built vanilla-JS frontend bundle the widget can ship (served from a new /widget-assets/:id/* route) — plain browser import(), not blocked by SvelteKit's ahead-of-time Svelte compilation the way raw .svelte source would be. A widget's own custom API routes still need a restart to register (a hard Fastify limitation), but its data/poll/report and any custom frontend UI now work fully live. The admin Widgets tab gained an upload form and a list of installed pluggable widgets with enable/delete. Verified against a copy of the real dev DB (all three renames + the weather config/cache migration fire once and are idempotent on a second boot) and through a real browser: uploaded a widget with both a report-driven poll and a custom frontend bundle, confirmed it renders live in the sidebar with no backend restart, then deleted it and confirmed it disappears along with all of its data (table/kv rows/on-disk files). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014c1L8ghNBFjfiH64UMViP8