Swipe for your stack. LFG across games.
Dating-app energy for Looking For Group — filter by game, swipe the pool,
party-code rooms, Discord connect, hide Discord + in-app chat.
Open live app · Marketing · Downloads · Quick Start · Features
MIT · free forever core · calver
26.07.18
Surface URL Live app stackup-lfg.is-a.dev · fallback stackup-three.vercel.app Marketing GitHub Pages ( /docs)Desktop Releases — Windows .exe+ Mac arm64.dmgNot affiliated with Riot Games.
| Swipe discovery | Pass / Interested card stack — not a boring table |
| LFG rooms | Party codes (in-game or StackUp) · join by code · room chat |
| Discord | OAuth connect · optional hide Discord · Rich Presence on desktop |
| In-app chat | DMs after Interested + room threads |
| Filters | Game · server · language · rank · role · vibe · Verified |
| Desktop | Electron Win + Mac · Windows in-app updates |
| Skins | Radiant / Noir Brass / Harbor / Signal |
| Ops | Admin console · optional Discord.js boards + feedback triage |
Next.js 16 · Auth.js · Supabase · Discord.js · Electron · Tailwind 4 · GitHub Pages marketing
git clone https://github.com/1tsRajOwO/stackup.git
cd stackup
npm install
cp .env.example .env.local # Discord/Google OAuth + Supabase
npm run dev # http://localhost:3000Sign in → swipe Looking cards → Start LFG → join a room → Chat → Profile → Hide Discord.
Demo credentials (STACKUP_ALLOW_DEMO=1) are for local/tests only — not packaged desktop.
- Create an app at Discord Developer Portal
- General Information → App Icon — upload StackUp’s logo (
electron/resources/icon.png, 512×512+). Without this, Discord’s authorize page shows a blank icon when users click Continue with Discord. Re-upload anytime withnpm run discord:upload-icon, or full branding (icon + cover + bot banner + description) withnpm run discord:brand. - OAuth2 → Redirects (must match exactly):
- Web /
npm run dev:http://localhost:3000/api/auth/callback/discord - Desktop app:
http://localhost:17832/api/auth/callback/discord - Optional twin:
http://127.0.0.1:17832/api/auth/callback/discord - Production web URL later (e.g.
https://YOUR_DOMAIN/api/auth/callback/discord)
- Web /
- Copy Client ID →
AUTH_DISCORD_ID(and optionallyDISCORD_BOT_CLIENT_ID) - Copy Client Secret →
AUTH_DISCORD_SECRET - (Optional) Bot token →
DISCORD_BOT_TOKEN - General Information → Terms of Service URL and Privacy Policy URL (required for many public / discoverable Discord apps):
- Prefer the deployed Next.js app (
NEXT_PUBLIC_APP_URL):- Terms:
https://YOUR_DOMAIN/terms - Privacy:
https://YOUR_DOMAIN/privacy
- Terms:
- Until the app is public, you can use the GitHub Pages mirrors:
- Terms:
https://1tsrajowo.github.io/stackup/terms.html - Privacy:
https://1tsrajowo.github.io/stackup/privacy.html
- Terms:
- Community Guidelines (optional support / description link):
/guidelinesorhttps://1tsrajowo.github.io/stackup/guidelines.html
- Prefer the deployed Next.js app (
- (Optional, desktop) Rich Presence → Art Assets — upload keys
stackup,looking,in_match,idlefromdocs/assets/discord-rp-*.png(generated by branding prep) so Discord shows images with Looking / In Match / LFG status. Portal only — not uploadable via Bot API.
Local StackUp already uses Discord app 1527795268878205092 in .env.local — confirm both redirect URIs above are listed in the Developer Portal.
- Create OAuth client at Google Cloud Console
- Application type: Desktop app (Installed)
- Copy Client ID →
AUTH_GOOGLE_ID, Client Secret →AUTH_GOOGLE_SECRET - Keep
AUTH_GOOGLE_MODE=desktop(default)
StackUp’s Electron shell runs Google OAuth via loopback + PKCE (system browser → http://127.0.0.1:<port>), then finishes the Auth.js session. Use npm run desktop:dev (or the packaged app) for Google sign-in.
For browser-only Google (Web application client), set AUTH_GOOGLE_MODE=web and add redirect http://localhost:3000/api/auth/callback/google.
You can enable Discord, Google, or both. At least one is required for production auth.
openssl rand -base64 32Set as AUTH_SECRET.
Optional anti-bot: AUTH_MIN_DISCORD_AGE_DAYS=7 (default) rejects brand-new Discord accounts.
- Create a project at supabase.com
- Copy URL →
SUPABASE_URL, service role key →SUPABASE_SERVICE_ROLE_KEY - SQL Editor: run
supabase/schema.sql(or apply migrations undersupabase/migrations/)
Set ADMIN_DISCORD_IDS to your Discord user ID(s), comma-separated (Developer Mode → Copy User ID).
Optional: ADMIN_DISCORD_USERNAMES=1tsraj (case-insensitive) for feedback triage by username.
In-app Report bug / Review posts to a Discord channel when configured. Staff triage from Discord buttons (or /feedback set) — status syncs with the ops Feedback tab.
Free path (recommended — no Railway): button clicks are handled by Vercel at /api/discord/interactions.
- Create a private channel (e.g.
#stackup-feedback) on your ops guild - Set on Vercel:
DISCORD_FEEDBACK_CHANNEL_ID(falls back to reports → logs),DISCORD_BOT_TOKEN,DISCORD_PUBLIC_KEY,ADMIN_DISCORD_IDS/ADMIN_DISCORD_USERNAMES, Supabase keys - Discord Developer Portal → your app → Interactions Endpoint URL (must be live HTTPS):
https://stackup-three.vercel.app/api/discord/interactions
Whenstackup-lfg.is-a.devDNS is live, switch to
https://stackup-lfg.is-a.dev/api/discord/interactions.
Discord will PING the endpoint — it must return PONG (requires a deploy with this route). - Ensure the bot token can Send Messages, Embed Links, and message components in that channel
- Optional:
DISCORD_STAFF_ROLE_IDfor mods; always includesADMIN_DISCORD_IDS/ADMIN_DISCORD_USERNAMES
Apply migration supabase/migrations/20260718150000_product_feedback_discord.sql (or re-run schema.sql) so feedback rows can store Discord message IDs for sync.
Room joins require add_room_member_if_capacity and leaves require remove_room_member_and_reopen (migrations 20260718143000_* / 20260718160000_*). Apply those before deploying join-by-code / leave changes or joins will 500.
NEXT_PUBLIC_APP_URL=https://stackup-lfg.is-a.dev
AUTH_URL=https://stackup-lfg.is-a.devUsed for metadata, PWA, wiring GitHub Pages CTAs (?app= or <meta name="stackup-app"> in /docs), and as the base for Discord Developer Portal Terms / Privacy URLs (/terms, /privacy).
| Surface | Terms | Privacy | Guidelines |
|---|---|---|---|
| Next.js app (prefer for Discord Portal) | https://stackup-lfg.is-a.dev/terms |
https://stackup-lfg.is-a.dev/privacy |
https://stackup-lfg.is-a.dev/guidelines |
| GitHub Pages marketing | terms.html | privacy.html | guidelines.html |
| Surface | Where | Notes |
|---|---|---|
| Web app | Vercel + free is-a.dev | Canonical: stackup-lfg.is-a.dev |
| Marketing | GitHub Pages | Served from main /docs → 1tsrajowo.github.io/stackup |
| Desktop | GitHub Releases | Windows Setup .exe + Mac arm64 .dmg / .zip |
| Database | Supabase free tier | Postgres via SUPABASE_* |
| Discord boards / feedback posts | Vercel → Discord REST | DISCORD_BOT_TOKEN + channel IDs |
| Feedback triage buttons | Vercel /api/discord/interactions |
Free 24/7 — works even if bot looks offline |
Discord Online presence + /lfg |
Mac LaunchAgent (free) or Fly.io | Gateway WebSocket — see below |
Registered via GitHub (no DigitalPlat / no browser signup):
- PR opened: is-a-dev/register#44024
- Domain already added on the Vercel
stackupproject (pending DNS verify) - After maintainers merge the PR, DNS publishes in a few minutes → Vercel auto-verifies
- Set production env
NEXT_PUBLIC_APP_URL+AUTH_URLtohttps://stackup-lfg.is-a.dev - Discord OAuth redirect:
https://stackup-lfg.is-a.dev/api/auth/callback/discord
Until then, the app stays live at https://stackup-three.vercel.app
npx vercel login
npx vercel # preview
npx vercel --prod # productionThen in the Vercel project → Settings → Environment Variables, paste values from .env.example. Set Discord OAuth redirect to https://stackup-lfg.is-a.dev/api/auth/callback/discord.
Keep / set on Vercel:
| Variable | Purpose |
|---|---|
DISCORD_BOT_TOKEN |
Channel posts via Discord REST from Next.js |
DISCORD_LOOKING_CHANNEL_ID / DISCORD_IN_MATCH_CHANNEL_ID / DISCORD_IDLE_CHANNEL_ID |
Live boards |
DISCORD_LOGS_CHANNEL_ID |
Patch / ops details (never use generic webhook for private patch details) |
DISCORD_LOGS_WEBHOOK_URL |
Optional logs-only webhook for patches |
DISCORD_REPORTS_CHANNEL_ID |
Reports + appeals (optional; falls back to logs) |
DISCORD_FEEDBACK_CHANNEL_ID |
Product bug / review embeds |
DISCORD_WEBHOOK_URL |
Optional alternate post path (public boards — not for patch details) |
DESKTOP_ATTEST_SECRET |
Same value on Vercel + GitHub Actions DESKTOP_ATTEST_SECRET secret — desktop join gate |
DISCORD_PUBLIC_KEY |
Interactions signature verify (Developer Portal → Public Key) |
ADMIN_DISCORD_IDS |
Ops console + feedback triage (Discord user snowflakes) |
ADMIN_DISCORD_USERNAMES |
Optional triage by username (e.g. 1tsraj) |
Channel map from npm run discord:provision (DISCORD_CH_*) |
Optional lean data channels |
Unset / remove on Vercel (unless you intentionally use bot ingest):
| Variable | Why |
|---|---|
DISCORD_BOT_INGEST_URL |
Points at optional Railway/local bot HTTP ingest — not required for REST posts |
DISCORD_BOT_INGEST_SECRET |
Only for optional gateway bot |
Without ingest vars on Vercel, Discord notifications still work via REST/webhooks. Feedback button clicks work via /api/discord/interactions (no always-on bot).
- Deploy the app so
POST /api/discord/interactionsis live. - Discord Developer Portal → Application → Interactions Endpoint URL =
https://stackup-three.vercel.app/api/discord/interactions
(switch tohttps://stackup-lfg.is-a.dev/api/discord/interactionsonce that domain resolves to Vercel). - Save — Discord sends a PING; the route must respond with type
1(PONG). - Set
ADMIN_DISCORD_IDS/ADMIN_DISCORD_USERNAMESon Vercel so your account can press Reviewing / Resolved / Dismissed. - You can run without a paid always-on host — buttons hit Vercel.
Discord only shows StackUp Online when a gateway process holds a WebSocket (same idea as Lumen running on your Mac). Feedback buttons do not need this.
Free on your Mac (like Lumen):
# Foreground
npm run bot
# or
bash scripts/run-bot-local.sh
# Optional: start at login (KeepAlive)
cp scripts/com.stackup.discord-bot.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.stackup.discord-bot.plistUnload with launchctl bootout gui/$(id -u)/com.stackup.discord-bot. Do not run this and Fly at the same time (one token = one session).
Fly.io (remote 24/7): fly.toml + Dockerfile.bot + scripts/deploy-bot-fly.sh. Fly’s free allowance still requires a card on file (no charge for small bots in many regions). After billing is enabled:
fly auth login
bash scripts/deploy-bot-fly.sh
# Health: https://stackup-discord-bot.fly.dev/healthThen stop the local LaunchAgent / npm run bot so sessions don’t fight.
Ops / desktop CTAs already target the free domain:
<meta name="stackup-app" content="https://stackup-lfg.is-a.dev" />Override anytime with ?app=https://….
- Desktop (Chrome/Edge): address bar → install icon, or menu → “Install StackUp”
- iOS Safari: Share → Add to Home Screen
- Android Chrome: menu → Install app / Add to Home screen
Starts at /looking in standalone mode.
StackUp ships as a lightweight Electron shell around the Next.js standalone server on a fixed loopback port (http://localhost:17832) so Discord OAuth redirects stay stable.
Public distribution is Windows + Mac (Apple Silicon). Users get the NSIS installer / Mac DMG from GitHub Releases. Windows also gets in-app updates (latest.yml + StackUp-Setup-*.exe).
| Platform | Asset | Role |
|---|---|---|
| Windows | StackUp-Setup-26.7.18.exe |
Public desktop + auto-update feed |
| Mac (arm64) | StackUp-26.7.18-mac-arm64.dmg |
Public desktop (ad-hoc signed; use Fix Gatekeeper if needed) |
Also: all releases · tag v26.07.18 (calver). Artifact filenames use electron-builder’s 26.7.18.
Desktop shell features
- Context isolation, sandbox, no
remote, DevTools stripped in production - Ships Next standalone + static only (not the full monorepo
node_modules) - System tray — close hides to tray; menu: Show · Check for updates · Quit
- Discord Rich Presence — Looking / In Match / LFG room on Discord (Profile → Desktop → toggle)
- In-app updates —
electron-updater(silent check on launch + Update button in header / Profile) - Subtle window fade-in + polished update button motion
npm run desktop:devStarts next dev and opens Electron at http://127.0.0.1:3000.
Prefer GitHub Actions (tag push attaches Windows + Mac installers to the Release):
- Push a version tag (
v26.07.18) or run Actions → Desktop builds → Run workflow - Users install from Releases (Windows can also update in-app via Check for updates)
Edit patches.json (newest first). Each entry has a public summary and organized details.
| Surface | What users see |
|---|---|
| App (What's new + desktop settings) | Summary only |
Website /updates |
Summary only |
| GitHub Release body | Summary only (CI writes release-notes.md) |
Discord #stackup-logs |
Full organized details (channel is private) |
# Dry-run / post full details to Discord logs
npm run patch:announce
npm run patch:announce -- 26.07.18
# Write release-notes.md for GitHub (summary)
npm run patch:github-notesRe-apply private log channels after Discord layout changes:
npm run discord:provisionLocally (on Windows):
npm run desktop:build:winMac is published on public Releases (DMG + zip + latest-mac.yml). CI also keeps the stackup-mac-arm64 artifact.
npm run desktop:build:macBuilds use ad-hoc codesign (identity: "-") — no paid Apple Developer / CA cert required. The DMG includes Fix Gatekeeper.command (manual only — never auto-runs on app launch). Install once: quit StackUp, drag to Applications (or accept the in-app Install prompt), eject the DMG, and keep only /Applications/StackUp.app.
That is Gatekeeper quarantine on an unsigned/ad-hoc build — not a broken file. After dragging StackUp.app into Applications:
- Double-click Fix Gatekeeper.command on the DMG, or
- Run:
xattr -cr /Applications/StackUp.app - Or right-click StackUp → Open once (if macOS offers that path)
- Then launch StackUp from Applications (not from the Terminal helper)
Usually an old copy is still installed alongside a new one. Before installing a new DMG:
- Quit StackUp fully (tray menu → Quit, or Activity Monitor → quit all StackUp)
- Remove older copies:
/Applications/StackUp.app, anyStackUp.appin Downloads, and eject/unmount old DMGs - Drag the new
StackUp.appinto Applications (replace when asked) - Launch only from Applications — do not keep running from the mounted DMG
The app enforces a single instance (com.stackup.app).
npm run desktop:pack| Script | Description |
|---|---|
npm run desktop:dev |
Electron + Next dev |
npm run desktop:build:win |
Windows NSIS .exe (public Release) |
npm run desktop:build:mac |
Mac arm64 DMG/zip (public Release) |
npm run desktop:pack |
Unpacked app dir |
Security: Release installers load the hosted web origin (STACKUP_WEB_ORIGIN, default https://stackup-lfg.is-a.dev). They do not ship SUPABASE_SERVICE_ROLE_KEY, OAuth client secrets, or Discord bot tokens. Local embedded Next (STACKUP_EMBEDDED_SERVER=1) reads secrets only from userData .env.
App id: com.stackup.app · product name: StackUp · version: 26.07.18.
Desktop Discord OAuth redirect for embedded / desktop:dev (register in Discord Developer Portal):
http://localhost:17832/api/auth/callback/discord
Unsigned Windows builds may show “Windows protected your PC” (SmartScreen) — this is not a BSOD. StackUp’s builder config already sets:
win.signtoolOptions.publisherName,signingHashAlgorithms: [sha256]signAndEditExecutable: true(version / resource metadata)requestedExecutionLevel: asInvoker
For full SmartScreen trust, add a real Authenticode certificate (do not invent or commit fake certs). Store as GitHub Actions secrets only:
| Secret | Purpose |
|---|---|
CSC_LINK |
Base64 of the .pfx (or path/URL — see electron-builder) |
CSC_KEY_PASSWORD |
Certificate password |
The Windows CI job wires these automatically and enables signing when CSC_LINK is set. Signing is not active until secrets are added. Exact steps: docs/CODE_SIGNING.md. Until then, users may need More info → Run anyway.
Packaged Windows apps check GitHub Releases via electron-updater (publish.provider: github, repo 1tsRajOwO/stackup). Users tap Check for updates in the header or Profile → Desktop. Already latest shows green You’re up to date; real errors (network / download) show red Update failed. After download, Restart to update installs and relaunches.
Mac installs from the public DMG on Releases. In-app auto-update stays off (avoids Gatekeeper / relaunch churn on ad-hoc builds) — grab the next DMG when a new version ships.
- Never commit
.env/.env.local(gitignored) - Use Discord OAuth secrets and Supabase service role only on the hosted server (never in public desktop installers)
- Admin routes gated by
ADMIN_DISCORD_IDS - Repo enables Dependabot, vulnerability alerts, and secret scanning where available — see SECURITY.md
| Script | Description |
|---|---|
npm run dev |
Next.js dev server |
npm run build |
Production build (includes standalone for desktop) |
npm run start |
Start production server |
npm run bot |
Local gateway (Online + /lfg). Or LaunchAgent / Fly — see Online gateway. Buttons use Vercel interactions. |
npm run lint |
ESLint |
npm run desktop:dev |
Electron + Next dev |
npm run desktop:build:mac |
Build Mac desktop app (maintainer / CI artifact only) |
npm run desktop:build:win |
Build Windows .exe (public Release / or use CI) |
npm run desktop:pack |
Pack unpacked desktop app |
- Schema applied in Supabase (incl. rooms + chat + hide_discord)
- Discord OAuth end-to-end
- Card stack Pass / Interested works
- Start LFG shows team code; join by code works
- Hide Discord forces in-app chat
- Room + DM chat poll updates
- Filters (server, language, rank, role) work
- In Match hides from Looking pool
- Admin can close rooms
-
NEXT_PUBLIC_APP_URLset; Pages CTAs point at the app - Discord Portal Terms (
/terms) + Privacy (/privacy) URLs set - Privacy / Terms / Guidelines reviewed for your deployment
- Invite 1–2 friend Discords / Valo servers
Releases use calendar versioning YY.MM.DD (e.g. 26.07.18), tagged v26.07.18.
Desktop artifact filenames may drop zero-padding (26.7.18) — that’s electron-builder, not a different release.
MIT — free and open source forever. See LICENSE.
Core LFG (Discover, rooms, chat) stays free. Optional Stripe premium — if enabled on a deployment — is an add-on only; you never need it for basic stacking.
| Surface | Free host |
|---|---|
| Web app | Vercel (Hobby) |
| Marketing site | GitHub Pages from /docs |
| Discord feedback buttons | Vercel /api/discord/interactions (no always-on bot) |
