- spa_server.py: strip /magpie prefix before API check and file lookup;
all HTTP methods call _normalise_path() first so /magpie/api/v1/* proxies
correctly and /magpie/assets/* resolve against the dist root
- manage.sh: pass --base /magpie to spa_server.py so the handler knows
the deployment prefix
- main.ts: pass import.meta.env.BASE_URL to createWebHistory so Vue Router
strips the /magpie prefix before matching routes
Without these fixes, assets returned index.html (MIME type error), API
calls returned HTML instead of JSON (stats.posts undefined), and router
routes never matched (matched: []).
Closes: #12
refresh_session() was spawning post.py --login without the REDDIT_SESSION_FILE env
var, so Playwright wrote the refreshed session to the legacy session.json default
instead of sessions/alan_reddit.json. The poster then read a stale file and
failed with 'Playwright re-login failed'.
- Add os import; pass env={...os.environ, REDDIT_SESSION_FILE: str(session_file)}
to subprocess.run so post.py writes to the correct per-account path
- manage.sh status now checks sessions/alan_reddit.json (canonical) not session.json
- Replace session.json with a symlink to sessions/alan_reddit.json for legacy compat
Python's http.server returns 404 for any path that isn't a real file,
breaking Vue Router history-mode navigation (/campaigns, /queue, etc.).
scripts/spa_server.py: minimal static server that falls back to index.html
for any path that doesn't resolve to a real file in the dist directory.
Both _start_web and the `serve` subcommand now use it.
manage.sh now checks for frontend/dist/index.html at startup.
If present, uses Python http.server (no host-blocking, production mode).
Falls back to Vite dev server only when dist is absent.
Fixes Vite's "Blocked request. This host is not allowed" error
when proxied behind Caddy at menagerie.circuitforge.tech.
#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
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.