- Add app/services/lemmy/discovery.py: searches 5 major Lemmy instances,
deduplicates by actor_id (AP canonical URL), skips NSFW communities,
uses community@instance naming convention matching existing Lemmy client
- Update POST /subs/discover: accepts platforms[] param (default both),
fans out to Reddit + Lemmy search, merges and sorts by subscribers
- Add platform field to all discovery result dicts (Reddit and Lemmy)
- Fix: remove dead _get() call left in search_subs() during earlier refactor
- Frontend: show platform badge on each discovery row, correct hyperlink
format for Lemmy (https://{instance}/c/{community}), pass r.platform
to upsertRules on import so Lemmy subs land in the lemmy platform slot
- Add SessionWidget.vue: sidebar widget showing Reddit session status
(dot color: green/yellow/red by age), age in hours, and a refresh
button that triggers the Playwright re-login (~30s, blocking POST)
- Add SessionStatus + RefreshResult types and api.reddit.* methods to
api.ts (sessionStatus, refreshSession)
- Mount widget at bottom of sidebar in App.vue via margin-top:auto
- Fix spa_server.py: add reverse proxy for /api/* → FastAPI :8532
(without this, all API calls returned index.html in production mode)
- Guard ageText/dotClass computeds against undefined age_hours with
typeof check + == null (catches both null and undefined from stale
browser-cached HTML responses)
Closes: #12
/api/v1 (absolute) was routing to menagerie.circuitforge.tech/api/v1 which
hit the global @api_no_session gate instead of the Magpie API at /magpie/api*.
Using import.meta.env.BASE_URL (set to /magpie/ at build time via VITE_BASE_URL)
produces /magpie/api/v1 in production and /api/v1 in dev (where Vite proxies /api).
Also adds vite-env.d.ts so TypeScript recognises import.meta.env.
#19 — link_url on campaign variants (migration 019)
- ADD COLUMN link_url TEXT on campaign_variants
- create_variant, upsert_variant, update_variant all carry link_url
- RedditClient.post() supports kind=link when link_url set + body empty
- RedditPostStrategy passes link_url from extra dict
- poster.py merges link_url from variant into extra (same as slug/tags)
- API VariantCreate/VariantUpdate schemas include link_url
- CampaignDetail: link_url field in Add Variant form with copy button;
link_url shown in variant list with clickable link + copy button
- Variant button disabled if neither body nor link_url is set
#18 — Multi-user team accounts (migrations 020-022)
- 020: team_accounts table (display_name, platform, username, session_file)
- 021: opportunities.assigned_to + post_as FK → team_accounts
- 022: posts.posted_by_account_id FK → team_accounts
- Store: list/get/get_by_username/create_team_account, assign_opportunity
- API: GET/POST /api/v1/team; POST /api/v1/team/{id}/assign
- config.py: sessions_dir added; reddit_session_file now points to
sessions/alan_reddit.json (backward compat path kept)
- scripts/migrate_sessions.py: one-shot move session.json →
sessions/alan_reddit.json + creates placeholder files for future accounts
- manage.sh: build (VITE_BASE_URL=/magpie/ npm build), serve (static),
migrate-sessions subcommands added; login updated to new session path
- Caddy: @magpie_no_session gate + handle /magpie/api* and /magpie*
blocks added to menagerie.circuitforge.tech site block
#14 — Structured logging
- app/core/logging_config.py: configure_logging() sets stdout handler
with timestamped format; called at import time in main.py
- Global FastAPI exception_handler logs 500s with full traceback
- opportunities.py: logger added; create/approve/mark-posted/dismiss
each emit an info line so failures are traceable
#15 — Frontend error handling
- frontend/src/composables/useToast.ts: shared toast composable
(error/success/info, auto-dismiss, module-level singleton)
- frontend/src/components/ToastList.vue: fixed-position overlay,
theme-aware, accessible (role=alert, aria-live=polite)
- OpportunitiesView: all 6 async actions have catch + toast.error()
- CampaignDetail: onMounted + all 6 mutation functions wrapped
#16 — Aggregate stats
- app/api/endpoints/stats.py: GET /api/v1/stats — single DB pass
via GROUP BY; returns posts totals, 7-day count, top communities,
platform breakdown, and opportunity queue counts
- frontend/src/components/StatsBar.vue: slim header bar above
router-view; chips for posts ok/failed/week, queue pending/approved/
posted, top community; hides gracefully on API error
FastAPI backend (SQLite + APScheduler), Vue 3 frontend, MCP server for
Claude integration, and Docker Compose stack. Includes campaign data model
(campaigns → variants → subs), post history, sub rules, and Playwright-based
Reddit posting layer migrated from claude-bridge/reddit-poster.
Also seeds legacy campaigns (6) and sub rules (14) from reddit-poster history.
Closes#1 (scaffold), resolves migration from claude-bridge.