The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(plans): harvest and record more opus plans

Signed-off-by: Xe Iaso <me@xeiaso.net>

Xe Iaso 64d5cb12 ff89f87b

+232
+165
docs/superpowers/plans/2026-03-24-sponsor-panel-patreon.md
··· 1 + # Plan: Add Patreon OAuth Login to Sponsor Panel 2 + 3 + ## Context 4 + 5 + The sponsor-panel (`cmd/sponsor-panel/`) currently only supports GitHub OAuth login. Patreon patrons have no way to access sponsor benefits (Discord invite, team invitations, logo submissions). This change adds Patreon as a second authentication provider so patrons get feature parity with GitHub Sponsors. 6 + 7 + **Design decisions:** 8 + 9 + - User-token based verification (no saasproxy dependency) 10 + - Separate identities (no account linking between GitHub and Patreon) 11 + - Patreon API v2 via direct HTTP calls; `patreon-go` library used only for OAuth URL constants 12 + - OAuth scopes: `identity`, `identity[email]`, `campaigns.members` 13 + - Team invite: Patreon $50+ users see the same card and input a GitHub username 14 + 15 + --- 16 + 17 + ## Step 1: Database Migration 18 + 19 + **File:** `cmd/sponsor-panel/migrations.go` 20 + 21 + Add `migration002` constant with idempotent DDL: 22 + 23 + - `ALTER TABLE users ALTER COLUMN github_id DROP NOT NULL` 24 + - `ADD COLUMN IF NOT EXISTS patreon_id TEXT UNIQUE` 25 + - `ADD COLUMN IF NOT EXISTS provider TEXT NOT NULL DEFAULT 'github'` 26 + - Drop `users_login_key` unique constraint, replace with `UNIQUE INDEX (provider, login)` 27 + - Add `idx_users_patreon_id` index 28 + 29 + Update `runMigrations()` to execute `migration002` after `migrationSchema`. 30 + 31 + --- 32 + 33 + ## Step 2: Update User Model 34 + 35 + **File:** `cmd/sponsor-panel/models.go` 36 + 37 + - Change `User.GitHubID` from `int64` to `*int64` (nullable) 38 + - Add `PatreonID *string` and `Provider string` fields 39 + - Update all `SELECT`/`Scan` calls in `getUserByID`, `upsertUser` to include new columns 40 + - Add `upsertPatreonUser(ctx, pool, user)` — same pattern as `upsertUser` but upserts by `patreon_id`, sets `provider='patreon'` 41 + 42 + --- 43 + 44 + ## Step 3: Add Patreon OAuth Config 45 + 46 + **File:** `cmd/sponsor-panel/main.go` 47 + 48 + New flags (all optional — service still works GitHub-only if omitted): 49 + 50 + - `--patreon-client-id` 51 + - `--patreon-client-secret` 52 + - `--patreon-redirect-url` 53 + - `--patreon-campaign-id` (to match pledge against) 54 + 55 + Add to `Server` struct: 56 + 57 + ```go 58 + patreonOAuth *oauth2.Config // nil if not configured 59 + patreonCampaignID string 60 + ``` 61 + 62 + In `main()`, conditionally create `oauth2.Config` using `patreon.AuthorizationURL` and `patreon.AccessTokenURL` from `gopkg.in/mxpv/patreon-go.v1`. 63 + 64 + Register new routes: 65 + 66 + ``` 67 + /login/patreon → server.patreonLoginHandler 68 + /callback/patreon → server.patreonCallbackHandler 69 + ``` 70 + 71 + --- 72 + 73 + ## Step 4: Patreon OAuth Handlers (new file) 74 + 75 + **File:** `cmd/sponsor-panel/patreon_oauth.go` (new) 76 + 77 + ### `patreonLoginHandler` 78 + 79 + Mirrors `loginHandler` in `oauth.go`: generate state, set CSRF cookie, redirect to `s.patreonOAuth.AuthCodeURL(state)`. Returns 404 if `s.patreonOAuth == nil`. 80 + 81 + ### `patreonCallbackHandler` 82 + 83 + 1. Validate state cookie (same CSRF pattern as GitHub) 84 + 2. Exchange code via `s.patreonOAuth.Exchange(ctx, code)` 85 + 3. Call Patreon API v2 identity endpoint: 86 + ``` 87 + GET https://www.patreon.com/api/oauth2/v2/identity 88 + ?include=memberships.campaign 89 + &fields[user]=full_name,vanity,email,image_url 90 + &fields[member]=patron_status,currently_entitled_amount_cents 91 + ``` 92 + 4. Parse JSON:API response to extract user identity and membership data 93 + 5. Find membership matching `s.patreonCampaignID` 94 + 6. Build `SponsorshipData` JSON: `{is_active, monthly_amount_cents, tier_name}` 95 + 7. Call `upsertPatreonUser()` with `provider="patreon"`, login = vanity or full_name 96 + 8. Create session (same `user_id` in gorilla/sessions cookie) 97 + 9. Redirect to `/` 98 + 99 + ### Response types (define in same file): 100 + 101 + - `patreonIdentityResponse` — JSON:API envelope with user data + included memberships 102 + - `patreonMember` — membership attributes (patron_status, currently_entitled_amount_cents) + campaign relationship 103 + 104 + --- 105 + 106 + ## Step 5: Update Login Template 107 + 108 + **File:** `cmd/sponsor-panel/templates/login.templ` 109 + 110 + Change signature to `templ Login(patreonEnabled bool)`. Add a "Login with Patreon" button (with Patreon SVG icon) conditionally rendered when `patreonEnabled` is true, linking to `/login/patreon`. 111 + 112 + --- 113 + 114 + ## Step 6: Update Dashboard for Provider Awareness 115 + 116 + **File:** `cmd/sponsor-panel/templates/dashboard.templ` 117 + 118 + - Add `Provider string` to `UserProps` 119 + - In `SponsorshipCard`, when user is not a sponsor: show Patreon link for `provider == "patreon"`, GitHub Sponsors link otherwise 120 + 121 + **File:** `cmd/sponsor-panel/dashboard.go` 122 + 123 + - `loginPageHandler`: pass `s.patreonOAuth != nil` to `templates.Login()` 124 + - `dashboardHandler`: set `UserProps.Provider` from `user.Provider` 125 + 126 + --- 127 + 128 + ## Step 7: Generate & Build 129 + 130 + 1. `go tool templ generate` (regenerate `*_templ.go` files) 131 + 2. `go build ./cmd/sponsor-panel/` 132 + 3. `npm test` (`go test ./...`) 133 + 134 + --- 135 + 136 + ## Files Modified 137 + 138 + | File | Change | 139 + | --------------------------------------------- | ----------------------------------------------- | 140 + | `cmd/sponsor-panel/migrations.go` | Add migration002 | 141 + | `cmd/sponsor-panel/models.go` | Update User struct, add upsertPatreonUser | 142 + | `cmd/sponsor-panel/main.go` | Add flags, Server fields, routes | 143 + | `cmd/sponsor-panel/patreon_oauth.go` | **New** — Patreon login/callback handlers | 144 + | `cmd/sponsor-panel/oauth.go` | No changes (existing GitHub flow untouched) | 145 + | `cmd/sponsor-panel/dashboard.go` | Pass patreonEnabled and Provider | 146 + | `cmd/sponsor-panel/templates/login.templ` | Add Patreon button | 147 + | `cmd/sponsor-panel/templates/dashboard.templ` | Add Provider to props, conditional sponsor link | 148 + 149 + ## Existing Code to Reuse 150 + 151 + - `generateState()` in `oauth.go:20` — reuse for Patreon CSRF state 152 + - `SponsorshipData` struct in `models.go:27` — same JSON format for both providers 153 + - `User.IsSponsorAtTier()` in `models.go:35` — works provider-agnostically 154 + - Session management via `getSessionUser()` in `oauth.go:634` — no changes needed 155 + - `patreon.AuthorizationURL` / `patreon.AccessTokenURL` from `gopkg.in/mxpv/patreon-go.v1` 156 + - All feature handlers (`inviteHandler`, `logoHandler`) — work unchanged since they only check `IsSponsorAtTier()` 157 + 158 + ## Verification 159 + 160 + 1. **Build**: `go build ./cmd/sponsor-panel/` compiles without errors 161 + 2. **Tests**: `npm test` passes 162 + 3. **GitHub flow unchanged**: Login with GitHub still works identically 163 + 4. **Patreon login**: With Patreon OAuth credentials set, clicking "Login with Patreon" redirects to Patreon, callback creates user with `provider=patreon` 164 + 5. **Tier gating**: A Patreon user pledging $50+/month sees team invite card; $1+ sees logo submission and Discord 165 + 6. **No Patreon config**: When Patreon flags are omitted, login page only shows GitHub button, `/login/patreon` returns 404
+67
docs/superpowers/plans/2026-03-25-sponsor-card-homepage.md
··· 1 + # Replace GitHub Sponsor iframe with Styled Sponsor Card 2 + 3 + ## Context 4 + 5 + The homepage (`lume/src/index.jsx` line 40) currently embeds a GitHub Sponsors iframe. This should be replaced with a custom-styled card linking to Patreon, GitHub Sponsors, and the Sponsor Panel (sponsors.xeiaso.net). The card design should echo the sponsor-panel app's warm Gruvbox aesthetic. 6 + 7 + ## File to modify 8 + 9 + - `lume/src/index.jsx` — the only file that needs changes 10 + 11 + ## Design reference (from `cmd/sponsor-panel`) 12 + 13 + - Cards: `rounded-2xl`, border, surface bg, `overflow-hidden`, subtle shadow 14 + - Gradient accent line: 2px at top, orange-to-pink for warm variant 15 + - Card titles: `font-serif`, semibold 16 + - Buttons: `rounded-xl`, colored bg, hover lift (`hover:-translate-y-px`), white text 17 + 18 + ## Plan 19 + 20 + ### 1. Add small icon components before the default export 21 + 22 + Copy SVG paths from `lume/src/donate.jsx` (GitHubIcon, PatreonIcon) scaled to 20x20. Add a star icon for the Sponsor Panel link. 23 + 24 + ### 2. Add `SponsorCard` component 25 + 26 + Structure: 27 + 28 + ``` 29 + <div> — card container (rounded-2xl, border, bg-bg-2, dark:bg-bgDark-2, shadow-sm, overflow-hidden, max-w-xl, mx-auto, my-6) 30 + <div> — gradient accent line (2px, orange→purple, with dark mode variant via dark:hidden/hidden dark:block) 31 + <div> — content area (px-6 py-5 text-center) 32 + <h3> — "Support My Work" with heart SVG icon 33 + <p> — brief description (text-sm, muted color) 34 + <div> — flex row of 3 button-links (flex-wrap, gap-3, centered) 35 + <a> Patreon — bg-orange-light / dark:bg-orangeDark-light 36 + <a> GitHub Sponsors — bg-fg-0 / dark:bg-purpleDark-light 37 + <a> Sponsor Panel — bg-purple-light / dark:bg-blueDark-light 38 + ``` 39 + 40 + ### 3. Replace the iframe (line 40) with `<SponsorCard />` 41 + 42 + ## Key implementation details 43 + 44 + **Gradient accent line:** Use two `<div>`s with `dark:hidden` / `hidden dark:block` to swap light/dark gradients, since inline `style` can't respond to `prefers-color-scheme`: 45 + 46 + - Light: `linear-gradient(90deg, #d65d0e, #b16286)` (orange→purple) 47 + - Dark: `linear-gradient(90deg, #fe8019, #d3869b)` (bright orange→pink) 48 + 49 + **Button link style overrides:** The site's base `<a>` styles (in `lume/src/styles.css` line 61-63 and `hack.css` line 65-71) apply link colors, underline, border-bottom, and visited styles. Each button `<a>` needs: 50 + 51 + - `no-underline border-0` to remove text decoration and bottom border 52 + - `text-white hover:text-white hover:bg-[color]` to override link/hover colors 53 + - `visited:text-white visited:hover:text-white visited:hover:bg-[color]` to override visited states 54 + 55 + **Color tokens available** (from `lume/tailwind.config.js`): 56 + 57 + - `bg-orange-light`, `dark:bg-orangeDark-light`, `hover:bg-orange-dark` 58 + - `bg-purple-light`, `dark:bg-blueDark-light`, `hover:bg-purple-dark` 59 + - `bg-fg-0`, `dark:bg-purpleDark-light`, `hover:bg-fg-1` 60 + - `text-fg-0`, `dark:text-fgDark-1`, `text-fg-3`, `dark:text-fgDark-3` 61 + 62 + ## Verification 63 + 64 + 1. Run `npm run dev` and check the homepage in both light and dark mode 65 + 2. Verify all three links open correctly in new tabs 66 + 3. Confirm the card is responsive (shrinks gracefully on mobile) 67 + 4. Check that button hover states work (lift effect, color change, no link underline artifacts)