For Agents

txArchive 2025 is a unified full-stack rewrite of both the backend (abandoned Rust monolith → Supabase) and the frontend (stock-shadcn React SPA → fresh Vite + React 18 + TanStack stack), plus a one-time data/file migration, real OCR → archive-wide full-text search, CI/CD, and DNS cutover. Authoritative artifact: txarchive_2025/docs/txarchive-2025-unified-design.md (one program, one plan). Companion: txarchive-2025-implementation-plan.md (being written). Status: unified design written; implementation plan in progress; adversarial validation loop to follow. The frontend’s old “backend API contract” (§8 of the frontend spec) is OBSOLETE — see txArchive Supabase Contract.

This note captures what txArchive 2025 is and the key decisions — it’s a reference, not a copy of the design doc. For full detail, read the design doc itself.

What this is

A single coherent program to rewrite the entire txArchive product:

  • Backend — the abandoned Rust monolith (Axum 0.6 / SeaORM 0.12 / Postgres / Redis / serenity 0.11 / Cloudflare R2, ~3,848 LOC, last on Koyeb; half-finished SeaORM→Diesel migration; OCR is a non-functional stub; no FKs anywhere; duplicate likes; a /dev/token endpoint that dumps env vars) → Supabase: Postgres + RLS as the real authorization layer + Auth (Discord provider) + Realtime + Edge Functions (Deno/TS) + pg_net/pg_cron. TypeScript. Cloudflare R2 retained for blobs (same bucket, same object keys). Plus one tiny always-on discord.js v14 bot (the /archive slash command + webhook “joke” notifications).
  • Frontend — the essentially-unmodified stock shadcn/ui “default” + slate scaffold (permanent light mode, no design language, Recoil-as-DI hack, three fetch wrappers, two comment subsystems one dead, non-functional likes in the UI, Images.tsxApprovals.tsx ~80% dup, per-page <Nav>, the ?reload=1 dance, ~21 MB of uncompressed PNGs) → a fresh modern React SPA: Vite 5 + React 18 + TypeScript (strict) + TanStack Router + TanStack Query v5 + Tailwind 3 + shadcn/ui + @supabase/supabase-js v2 + Zustand (UI-only state) + vite-plugin-pwa. Talks Supabase directly. A real design language (light/dark/system). See txArchive Frontend Architecture.
  • Data + file migration — a one-time Postgres→Postgres ETL (scripts/migrate-data.ts); R2 untouched; staging-first, idempotent/resumable, with a maintenance window and a rollback plan.
  • OCR for real — Google Cloud Vision TEXT_DETECTION (pluggable behind a runOcr() interface) → ocr.search tsvector → an archive-wide full-text search feature (genuinely new). The dead ocr_migrator.rs is what this realizes.
  • CI/CD + cutover — GitHub Actions per repo; frontend on Cloudflare Pages, bot on Railway; a documented cutover runbook ending in a DNS flip of txarchive.org.

What txArchive is (the product)

A private, invite-only, Discord-guild-gated screenshot/media archive for the txAdmin Discord community. txarchive.org; blobs from cdn.txarchive.org. Discord-OAuth-only login + a guild gate (must be a member of guild 577993482761928734; on login also guilds.joins you into guild 1171963758072057997 where the bot’s /archive lives). Closed membership — members invite (“enroll”) new ones with a chosen subset of scopes (VIEW, ADD, DELETE, APPROVE, ENROLL, ESCALATE). Content (images + GIF/MP4/MP3) needs moderator approval before it hits the public timeline; social features = likes, comments, deep-link sharing; real-time feed + comment threads. A bespoke dark “streaming service” page (the Yorick & Wowjesus stories page) catalogues in-community machinima MP4s. Installable PWA SPA. Identity to preserve verbatim: the name txArchive (lowercase “tx”), the tagline, the login disclaimer, the crude tone of the webhook messages.

Target architecture

Target system topology

flowchart LR
  FE["txarchive-frontend<br/>(new Vite+React SPA,<br/>@supabase/supabase-js)<br/>Cloudflare Pages → txarchive.org"]
  subgraph SB["Supabase project"]
    AUTH["Auth — Discord provider<br/>+ custom-access-token hook<br/>(scopes claim)"]
    PG[("Postgres<br/>RLS · triggers · views · RPCs · pg_cron")]
    RT["Realtime<br/>(RLS-filtered postgres_changes)"]
    EF["Edge Functions (Deno/TS):<br/>complete-login · create-upload · delete-upload<br/>notify-discord · run-ocr · health"]
  end
  R2[("Cloudflare R2<br/>cdn.txarchive.org/{file_key}")]
  BOT["txarchive-bot<br/>(Node + discord.js v14, Railway)"]
  DISC["Discord<br/>(OAuth · /users/@me · guilds.join · webhook · gateway)"]
  VISION["Google Cloud Vision<br/>(TEXT_DETECTION)"]
  FE -->|"signInWithOAuth('discord')"| AUTH
  FE -->|"select / rpc (PostgREST)"| PG
  FE -->|"channel().on('postgres_changes')"| RT
  FE -->|"complete-login · create-upload · delete-upload · health"| EF
  EF --> PG
  EF -->|"PUT/GET/DELETE objects"| R2
  EF -->|"guild check · guilds.join · webhook"| DISC
  EF -->|"OCR a screenshot"| VISION
  PG -->|"AFTER-trigger via pg_net (X-Edge-Secret)"| EF
  PG -->|"pg_cron → run-ocr (drain ocr_jobs)"| EF
  BOT -->|"/archive → create-upload<br/>(X-Edge-Secret + x-acting-discord-id)"| EF
  BOT -->|"gateway"| DISC
  AUTH -. reads .-> PG
  style FE fill:#264653,stroke:#2a9d8f,color:#fff
  style PG fill:#2d2d2d,stroke:#888,color:#fff
  style R2 fill:#3d2020,stroke:#a66,color:#fff
  style BOT fill:#2d2d3d,stroke:#88a,color:#fff
ComponentOwns
Supabase PostgresThe entire data model (8 tables: profiles, user_scopes, enrollments, uploads, comments, likes, ocr, ocr_jobs); RLS as the real authorization layer; triggers that enqueue async work / fire webhooks via pg_net; the timeline / pending_uploads / post_detail views; RPCs (toggle_like, enroll_user, is_liked, search_archive, set_user_scopes, cancel_enrollment); pg_cron draining ocr_jobs; config GUCs.
Supabase AuthDiscord OAuth, sessions, auto token-refresh; a custom access-token hook injects user_scopes into app_metadata.scopes (UI gating only — RLS is the real gate).
Supabase RealtimeLive updates — replaces the entire custom WebSocket subsystem; RLS-filtered automatically; no interest store, no manual auth.
Edge Functions (Deno/TS)The few paths needing server-only secrets or multi-step external calls: complete-login, create-upload, delete-upload, notify-discord, run-ocr, health. Trigger-invoked ones verify a shared X-Edge-Secret.
Cloudflare R2The file blobs — unchanged; same bucket, same keys; written/read/deleted only by Edge Functions; public reads via cdn.txarchive.org/{file_key}.
txarchive-bot (Node + discord.js v14, always-on, Railway)The Discord gateway + the /archive slash command — thin: scope-check via supabase-js (service key), then forward the file to create-upload. No DB writes, no business logic.
txarchive-frontend (Vite SPA, Cloudflare Pages)The UI: PostgREST reads + RPC + Realtime + RLS via @supabase/supabase-js; Edge Functions only for complete-login / create-upload / delete-upload / health; composes blob & avatar URLs client-side.
Google Cloud VisionOCR (TEXT_DETECTION) — the default runOcr() implementation; pluggable.

Gone for good: Redis · the auth table (OAuth tokens → Supabase Auth) · the custom WS “interests” protocol · the 150-day JWT · /dev/token · SeaORM + the half-done Diesel migration · the {response,error,success,status} envelope · ?skip= offset URLs · the JWT-in-redirect-query handshake · the JS-readable auth cookie · Recoil + the Recoil-as-DI pattern · wouter · SWR + the three fetch wrappers · the two comment subsystems · the per-page <Nav> · the dangling /users link (now real) · the ?reload=1 dance · setAuth-during-render · RecoilizeDebugger in prod builds · the abandoned branches (wj/v2, v2-sveltekit, comments, wj/net-v6, production, snoozie-*).

Key decisions (the ~20 open questions, resolved)

The unified design merged the backend design doc’s open questions, the frontend spec’s open questions, and a handful of cross-cutting choices. The headline ones:

#Decision
Unified planOne full-stack program (not separate backend/frontend efforts). Product & visual design is Phase 0 of the implementation plan.
Frontend stackVite 5 + React 18 + TS strict · TanStack Router (typed routes, nested layouts, route guards, modal routes; replaces wouter) · TanStack Query v5 (server-state cache, useInfiniteQuery, optimistic mutations; replaces SWR + the Recoil async selectors + all three fetch wrappers) · @supabase/supabase-js v2 (the one typed client) · Tailwind 3 + shadcn/ui (kept) · Zustand (UI-only state: theme, mobile-drawer, active modal — replaces the Recoil atoms) · vite-plugin-pwa · react-dropzone (kept) · lucide-react (one icon set). Vitest + Playwright.
OCR engineGoogle Cloud Vision TEXT_DETECTION (OCR_ENGINE=google-vision; GOOGLE_VISION_API_KEY restricted to the Vision API). ~$1.50/1k images, 1k/mo free, very accurate, no per-token LLM cost. Pluggable behind runOcr(bytes, mime) in _shared/ocr/; openai.ts / tesseract.ts stay as documented alternates. Non-image MIME (MP4/MP3) → don’t enqueue an OCR job.
User-ID strategyMigrate to Supabase Auth UUIDs. profiles.id uuid = auth.users.id; the Discord snowflake kept as profiles.discord_id (unique) — the join key during migration and in the bot.
Frontend access patternFrontend talks Supabase directly (PostgREST reads + RPC + Realtime + RLS). Edge Functions only for complete-login, create-upload, delete-upload (+ health). The contract = the DB schema + a few function signatures.
Guild-gate placementIn the complete-login Edge Function, called post-login. A non-member who authenticates gets no profile, no scopes, nothing visible (RLS); complete-login additionally deletes their Supabase user and 403s not_in_guild. The Before-User-Created hook hardening is future work.
The /users pageBuild a real “Members” admin page at /members (gated ENROLLDELETE) — implements the old dangling /users link AND finally surfaces moderation: list members + scopes; grant/revoke scopes (set_user_scopes RPC); view & admin-delete a member’s uploads; manage pending invites (cancel_enrollment).
Likes & realtimeOptimistic likes (TanStack Query optimistic mutation over toggle_like RPC) + Supabase Realtime (postgres_changes on likes/uploads/comments). Replaces the custom WS subsystem entirely; no interest store, no manual auth (Realtime authenticates via the user’s JWT, RLS-filters). Fixes the biggest functional gap: likes that visibly do nothing today.
Yorick pageKeep its deliberately-distinct dark “streaming service” identity (own dark header, the hero, the poster grid → MP4s on R2) — re-cast into the new design system’s dark mode, stays dark regardless of the user’s theme. Drive the catalog from a checked-in src/features/yorick/yorick-catalog.json ([{title, description, comingSoon, poster, video}]) — “add a movie” becomes a one-line change. Play videos in an in-app <video> modal (fall back to a new tab). Migrate the 7 current entries verbatim.
ThemeLight / dark / system; system-default; a toggle in the nav/user-menu; persisted in localStorage (Zustand). Both palettes defined in Phase 0.
Deep-links/post/:id (replaces /timeline?postId=). Share button copies https://txarchive.org/post/<id>; notify-discord builds the same via FRONTEND_POST_URL_TEMPLATE. No back-compat for old ?postId= links.
Default post-login page/timeline (the feed) — not “My Uploads” as today.
Repo structureStay split. txArchive repo = supabase/ + bot/ + scripts/ + (post-cutover) legacy/. txarchive-frontend repo = the new SPA. txarchive_2025 = just the design + implementation-plan docs (a neutral cross-repo location, not a code repo).
HostingFrontend → Cloudflare Pages (pairs with the existing R2 / cdn.txarchive.org; PR preview deploys). Bot → Railway (Fly.io as the documented alternative). No Koyeb.
Full-text searchAdd it — a new /search page over search_archive(q) RPC over ocr.search. The payoff of doing OCR for real.

Other resolved questions: brand identity / logo / favicon.svg / mstile decided in Phase 0 (clean slate — there’s no design language to be faithful to); exact ALLOWED_MIME_TYPES & UPLOAD_SIZE_LIMIT verified against the Rust source in Phase 1 (one source of truth = the env, frontend mirrors); responsive-first mobile with a proper drawer; a defined a11y bar (labels, focus traps, keyboard nav, alt text, prefers-reduced-motion, AA contrast); flat comments + delete-own + the 1–2000 char limit; offset pagination + infinite scroll where it helps; app_started webhook kept.

The frontend’s old “backend contract” is OBSOLETE

Do not use the frontend spec's §8 ("Backend API contract — this stays")

That section described the abandoned Rust REST API (GET /timeline?skip=, GET /uploads/:id, the {response,error,success,status} envelope, the JWT-in-redirect handshake, etc.). It is superseded entirely by §6 of the unified design doc — the new contract is Supabase: PostgREST tables/views + RPCs + Realtime channels + 3 Edge Functions + cdn.txarchive.org/{file_key}. See txArchive Supabase Contract for the new contract (old endpoint → new mechanism crosswalk + data shapes).

Doc locations

Where the authoritative docs live

DocPathRole
Unified design (authoritative)txarchive_2025/docs/txarchive-2025-unified-design.mdThe one program. Incorporates the backend design doc by reference; records deltas (§5); §6 = the new frontend contract.
Implementation plantxarchive_2025/docs/txarchive-2025-implementation-plan.mdPhases 0–N. Being written next.
Backend design doctxArchive/docs/superpowers/specs/2026-05-11-txarchive-supabase-rewrite-design.mdAuthoritative for the backend. The schema, RLS matrix, views, RPCs, triggers/pg_net/pg_cron, Edge Functions, Auth setup, the migration ETL. Re-read it; the unified design only layers deltas.
Frontend spectxarchive-frontend/docs/frontend-modernization-spec.mdThe as-built reference + parity checklist + design-system baseline. Valid as input — except its §8 (“backend API contract”) is obsolete (superseded by §6 of the unified design).

“If you read only one doc before implementing, read this one [the unified design] plus the backend design doc (which it leans on).” The frontend spec is the encyclopedic “what the old UI did” reference.

Status

  • The unified design is written and approved for planning.
  • The implementation plan (txarchive-2025-implementation-plan.md) is being written next.
  • An adversarial validation loop follows — architecture / security / migration / frontend-parity / testing / ops reviewers iterating until the plan is solid.
  • Phases (per the design): Phase 0 = product & visual design (brand colour, logo, palettes, typography, host confirmation); Phases 1–7 = backend (schema/RLS/views/RPCs → triggers/pg_net/pg_cron → Edge Functions → Auth/guild-gate/scopes → OCR → the bot → migration ETL); Phases 8–10 = frontend (the new SPA, parity, search/members/dark-mode/a11y); then the cutover runbook (staging-first, then prod, then the DNS flip, then retire the Rust).

Cross-repo layout

txArchive/                              # backend repo — build on a branch; main stays on Rust until cutover
  supabase/{config.toml, migrations/, functions/_shared + the 6 fns, seed.sql}
  bot/                                  # txarchive-bot (Node 20 + discord.js v14, Dockerfile)
  scripts/migrate-data.ts               # the one-time ETL
  legacy/                               # (post-cutover) the old Rust app — or tag v1-rust and delete
  .github/workflows/{ci.yml, deploy.yml}
txarchive-frontend/                     # frontend repo — new Vite SPA
  src/ public/ index.html vite.config.ts tailwind.config.* components.json ...
  .github/workflows/{ci.yml, deploy.yml}
txarchive_2025/docs/                    # the design + implementation-plan docs (not a code repo)

Verify-against-source list (resolve during implementation)

Spots both source docs flag as “read the Rust to be sure”: the exact ALLOWED_MIMES (incl. whether the last is audio/mpeg or video/mpeg) in services/upload_service.rs; the hardcoded snowflake→scopes list in services/dto/auth_dtos.rs; the UNLIMITED_UPLOADERS id (399955365120442389) and DAILY_UPLOAD_LIMIT (10); the two guild ids (577993482761928734 gate, 1171963758072057997 join/bot); the UPLOAD_SIZE_LIMIT env value (and whether to raise it); the verbatim Discord webhook strings; the exact Yorick MP4 object keys in R2 (incl. the ...wh... one — typo or real?); the like-toggle return value the old API used (the new toggle_like returns 'LIKED'/'NOT_LIKED' — just be consistent).

  • txArchive Supabase Contract — the new backend contract (§6): old endpoint → new mechanism, data shapes, Realtime channels, the 3 Edge Functions.
  • txArchive Frontend Architecture — the new SPA: stack, routing, data layer, realtime integration, theming, the rebuilt feature set.
  • LOG — txArchive activity log.
  • TOPICS — txArchive topic index.