FullStack VC v0.0.8 — Three-Bucket Stack Editor, Stacks on Turso, Google OAuth as Peer Provider
First tagged release of the FullStack VC site, accumulating three substantial feature drops since v0.0.5. The polling system that captured zero participant votes on April 29 now sits behind three peer identity providers (Google, GitHub, LinkedIn) with a link-while-logged-in flow so any provider works from any session — VCs with Workspace emails finally have a one-click path through the auth gate. Stack edits moved off the GitHub-App-commits-on-every-save pattern that the polling blueprint v2 §8.1 explicitly retired for time-sensitive data — they now persist to Astro DB on Turso in sub-second time, with a sync script that materializes back to public markdown on the operator's cadence. And the stack editor itself, which had only ever surfaced the Current bucket in its UI, is now a three-bucket drag-drop canvas with toggleable columns, cross-bucket data reshape, optimistic UI with Cmd/Ctrl+Z undo, and a mobile-friendly fallback. Six days before the May 27 Monthly All-Hands.
Why Care?
The Agentic VC Dojo's first live session on April 29, 2026 prepped seven interactive polls and captured zero participant votes despite a fully-working OAuth gate and a working Svelte poll UI. The diagnosis after the fact: the only sign-in providers were GitHub and LinkedIn. Most VC operators don't have GitHub accounts (developer demographic), and very few think to sign in with LinkedIn — they browse to it. Every fund operator has a Google Workspace email. Adding Google as a third peer provider isn't a "nice to have" for the May 27 follow-up session — it's the dominant entry point that wasn't there.
That diagnosis cascaded into the rest of this release. If identity needed Google, the stack editor needed to be reachable from any provider session (not just GitHub). If the editor was reachable, it needed to be worth the trip — which meant exposing Aspirational and Archived in the UI for the first time, and getting saves off the 30-90 second GitHub-App-commits-then-Vercel-rebuilds loop they'd been on. The three features in this release are independent technical choices, but they share one purpose: make participation in the May 27 session easier than ignoring it.
What's New Since v0.0.5
| Surface | v0.0.5 | v0.0.8 |
| Identity providers | GitHub + LinkedIn | GitHub + LinkedIn + Google (all peer) |
| Cross-provider linking | Auto on email match only | Link-while-logged-in — clicking a second provider attaches it to the active session's User row instead of minting a duplicate |
| Header auth indicator | "Log in" / partial / fully-linked | Tri-state with N/3 count surfaced, sign-out + stack-counts CTA in the tooltip |
| Stack storage | participants/<handle>.md via GitHub App commits | Astro DB on Turso, with pnpm sync:stacks materializing back to markdown |
| Stack save latency | 30–90s (commit + Vercel rebuild) | Sub-second |
| Stack editor UI | Single-column Current-only | Three-bucket drag-drop canvas with pill toggles, cross-bucket reshape, undo |
| Owner Edit affordances on public profile | None | Edit chips on head card + each tier header (Current / Aspirational / Moved on), revealed client-side via /api/me |
/people/<handle>/stack/edit access | GitHub session only | Any provider session as long as the User row's github_handle matches the URL |
Key Features
1. Google OAuth as a peer identity provider
Effective v0.0.6. Google joins GitHub and LinkedIn as a first-class identity provider — no privileged status, just another way through the gate.
Why all three, not just Google: each provider tells us something different about the same person, and the value compounds when more than one is linked.
| Provider | What it tells us |
| GitHub | Building behavior — repos, languages, commit cadence. Required for the stack-write GitHub App bot. |
| Professional identity — firm, role, work history. Highest fidelity for roster + people-page cross-referencing. | |
| Google Workspace | Domain affiliation (hd claim), verified firm email, lowest-friction sign-in for fund operators. Future hooks for calendar / drive integrations. |
The header tooltip now nudges with this framing: "We get different signal from each — connect more for richer Dojo discovery." Three states: red dot (0/3), yellow (1/3 or 2/3 with count visible), green (3/3 — "fully triangulated").
Email-verified guard: the Google callback refuses to mint a session if email_verified === false. Defense in depth for the email-as-canonical-key invariant the User row depends on.
2. Link-while-logged-in flow across all three providers
OAuth is stateless from the provider's perspective. The original v0.0.5 callbacks would replace the active session cookie on every sign-in — meaning a user signed in via GitHub who clicked "Continue with Google" got a fresh User row with only google_sub populated, instead of attaching Google to their existing identity.
This release adds linkProviderToExistingUser() to the user-record layer. All three callbacks now check for a valid session cookie before overwriting it; if the active session is from a different provider, the new provider's sub gets attached to the existing User row's github_handle / linkedin_sub / google_sub column. Name + avatar are preserved (the user is already operating under that identity); the new email gets unioned into User.emails[].
Logged as outcome: 'provider_linked' in AuthEvent so the audit trail distinguishes a fresh signup from a link.
3. Stacks migrated to Turso with DB→markdown materialization
The polling blueprint v2 §8.1 had already retired the GitHub-App-commits-on-every-save pattern for time-sensitive data, citing commit-history pollution, PEM brittleness, latency, and concurrent-edit fragility. Stacks are editorial cadence, not real-time — they technically escape the ban. But the costs apply regardless of write frequency.
The architecture, generalizing the polling system's §9 materialization motion:
Authoritative store: Astro DB on Turso, one flat
Stacktable with abucketdiscriminator column ('current' | 'aspirational' | 'abandoned') and bucket-specific nullable fields (notes/intent/reason). Unique index on(user_id, bucket, tool)— cross-bucket moves are a single UPDATE.Read path: public
/people/<handle>stays SSG and renders from materialized markdown. The editor reads Turso directly (SSR) so in-progress edits are visible immediately./api/mestack counts also come from Turso so the header tooltip reflects unsynced edits.Write path:
/api/stack/saveis now an atomic per-bucket DELETE+INSERT against Turso. Sub-second. No commit. No deploy trigger. The endpoint accepts each bucket independently — present field replaces that bucket; absent field preserves it. (Important for the bridge period before the new editor shipped; not a concern after this release since the new editor always sends all three.)Sync:
pnpm sync:stacks(orsync:stacks:remote) reads Turso, rewrites the three stack arrays in each participant markdown's frontmatter, preserves everything else byte-for-byte. The operator commits the result with amaterialize(stacks): snapshot from Turso <ISO>convention so materialization commits are visually distinct from editorial commits ingit log— solving the pollution problem we'd had since v0.0.5.
Operational footprint: new pnpm scripts migrate:stacks (one-time backfill) and sync:stacks (Turso → markdown, on demand). New scripts under scripts/ named with the _ prefix convention so they're collated with the other ops scripts. Schema push (pnpm astro db push --remote) needed once after deploy — Vercel's astro build --remote doesn't push schema (lesson learned the hard way during the Google OAuth deploy).
4. Multi-column drag-drop stack editor
The pre-release editor surfaced only the Current bucket. Aspirational and Abandoned existed in the data model and on the public profile, but the editor had no UI for them — you had to know to hand-edit the markdown frontmatter. Predictably, most participant stacks had a fleshed-out Current list and near-empty other buckets, not because people weren't trying tools and moving on from them, but because capturing that wasn't possible.
The redesigned StackBuilder.svelte (now Svelte 5 runes; the old Svelte 4 syntax is gone) makes the three buckets first-class:
Pill toggles for which buckets are visible. At-least-one invariant — clicking the last active pill is a no-op. Single-pill = single column at max-w 36rem; two-pill = side-by-side at max-w 60rem; three-pill snaps to two-up of the most-recently-toggled pair plus a "more →" peek of the third. State persists in
localStorageper handle.Drag-and-drop via
svelte-dnd-action(~6KB gzipped, keyboard-accessible out of the box). Within a column, drag reorders position. Across columns (only meaningful in two-up mode), drag transforms the data shape —notes→intentwhen demoting Current to Aspirational,intent→reasonwhen archiving an Aspiring tool, and so on. Same canonical mapping via areshape()helper instack-types.ts.Inline reshape form below the dropped card: "Moved to Aspirational · the previous note became: [text] [Keep] [Edit] [Clear]". Auto-dismisses after 5s on the keep path. Nothing silent.
Optimistic UI with a dirty banner that surfaces the undo count. Cmd/Ctrl+Z undoes the last drag or reshape edit (LIFO depth 10, ignores keystrokes inside text fields so browser-native undo still works there).
Mobile fallback at ≤768px: drag handles hidden; each card gets ↑/↓ buttons for within-column reorder and a Move button that opens a modal with target-bucket radio options. Same reshape pipeline as drag — the modal pathway goes through the same code as the drag finalize handler.
Accessibility: SR-only live region announces every cross-bucket move ("Moved claude-code from Current to Aspiring").
svelte-dnd-actionprovides keyboard drag (Space to grab, arrows to move, Space to drop).prefers-reduced-motioncollapses layout transitions.
5. Owner-Edit affordances on the public profile
Four small chips, hidden by default, revealed client-side when /api/me reports the viewer's handle matches the page:
Edit profile → on the participant head card
Edit → on each of the three section headers (Current stack / Aspirational / Moved on)
Works on the SSG-prerendered /people/<handle> page without sacrificing prerendering — same client-side reveal pattern the header auth indicator uses. Visitors who aren't the owner see nothing.
Bug Fixes Worth Naming
Three round-tripping fixes surfaced during end-to-end testing on production:
Edit gate at
/people/<handle>/stack/edithad hardcodedsession.provider === 'github'after Google + link-while-logged-in landed. A Google-signed-in owner withgithub_handleon their User row was bouncing back to the public profile. Fixed: look up the User row by whichever provider sub the active session has, then check the row'sgithub_handleagainst the URL.Save endpoint had the same bug independently — gate at
/api/stack/savewas rejecting non-GitHub sessions even when the linked User row matched. Fixed with the same by-provider-sub lookup pattern.Linked-accounts panel on edit page was doing a duplicate lookup hardcoded to
User.github_handle = session.subject, which fails for Google sessions (subject is the Google sub, not a github handle). Fixed: reuseownerRowfrom the gate.
Pattern recorded for the future: anywhere we have a github-handle-vs-session-subject check, the right form is by-provider-sub lookup → check row.github_handle.
Operational Hand-Off
For anyone pulling this release into a similar Astro Knots site:
Schema push to production Turso (Vercel's
astro build --remotedoes NOT push schema):pnpm astro db push --remoteBackfill stacks if there's existing markdown:
DRY_RUN=true pnpm migrate:stacks:remotefor a preview, then drop the flag to run for realGoogle Cloud Console redirect URI must include
https://<domain>/api/auth/google/callbackexactly. No wildcards — preview deployments need to be added individually or skipped (recommended for May-27-style time-pressure)Env vars on Vercel:
GOOGLE_OAUTH_CLIENT_ID+GOOGLE_OAUTH_CLIENT_SECRET(in addition to the existing GitHub + LinkedIn pairs)First sync after deploy:
HANDLE=<your-handle> pnpm sync:stacks:remoteto confirm the round-trip produces clean markdown. Commit withmaterialize(stacks): snapshot from Turso <ISO>so the convention propagates.
What's Next
Two planning docs land alongside this release in context-v/tasks/:
Pre-create-and-Fuzzy-Bind-Users-from-External-Rosters.md— the next track on the identity layer. Zoom export → Turso User rows with emails populated → fuzzy match to public participant markdown profiles via a confidence-scored bridge (GitHub handle exact, LinkedIn URL exact, name+kauffman-class fuzz, firm match). The "claim this profile?" affordance lives on/me; an admin triage view at/admin/unbound-usershandles the long tail. The privacy boundary stays clean: emails in Turso, public profiles in markdown, singlehandlecolumn on User as the bridge.Redesign-Stack-Builder-with-Multi-Column-Drag-Drop.md— the plan implemented in this release. Kept in the repo as living context so future contributors can see the design rationale behind the editor architecture.
For the May 27 Monthly All-Hands specifically: the three open polls are already live in production, presenters wired (Mike Moradi + Raeed Zainuddin on Claude Team for admin/audit/fundraise; Toby Rush on Obsidian 2nd Brain + OpenClaw), RSVP link wired. The polling system has the auth surface area it needed. If the conversion math is right, the next session won't repeat April 29.