fix(v2): create-pod options rendered as plain prose — the reset ate them - #870
Merged
Conversation
Sam, the night before the YC interview: "not usable, no intuition on what is clicked, and no distinction at all." Verified live with a seeded smoke user: both option buttons computed to background transparent, border 0, padding 0 — plain text. The join-policy choice was invisible and the active state painted nothing. Root cause is this codebase's most-documented CSS trap claiming another victim: the global button reset (.v2-root button:not(.MuiButtonBase-root), specificity 0-2-1) beats a bare class (0-1-0) and strips exactly the properties that make an option look like an option. The reaction chips and filter segments already carry the .v2-root button. prefix workaround; the create panel never got it. Compounding it, the intended --active style used a GRAY border (--v2-border-strong) — near-invisible even had it won. Fix, verified by CSS injection on production before committing (before/after screenshots, both selection states toggled): prefixed rules that survive the reset; selected = accent-soft fill + accent border + a 6px accent dot beside the label; hover feedback; 80ms transitions per the design system. Create itself was never broken — smoke user created and deleted a pod end-to-end — the UI was just illegible. Guarded in v2-layout-invariants: the prefix must exist and --active must reference var(--v2-accent). 22/22 invariants, sidebar + create-hook suites green.
lilyshen0722
added a commit
that referenced
this pull request
Aug 6, 2026
…768) (#872) The creation flow's most prominent choice has been inert for every non-admin since it shipped. `createPod` writes `joinPolicy` and hardcodes `type`, while `communityListed` — the flag that decides whether anyone can FIND the pod — was writable only from routes/admin/pods.ts behind adminAuth. Since self-joinable is `community AND open`, a user picking "Open to join" got a pod nobody could discover, and no path existed to fix it. Two halves. **1. POST /api/pods/:id/visibility { tier: 'private' | 'community' }** Owner- or admin-scoped, rate limited to 10/hour (this is the one owner action that exposes content to non-members and to people with no account), audited on every call, and refused for DM kinds. It writes BOTH flags in ONE action, deliberately, rather than mirroring the admin surface's two steps. The admin route separates them because `showcase` (publicRead without listing) is a real curated state it needs to express — but an owner cannot reach that state, so an owner-side two-step would 409 on `listing_requires_public_read` forever with no way to clear it. Writing the pair together is also exactly what ADR-016 invariant 1 (listed => readable) asks of a writer that can prove it. `showcase` stays admin-only: it publishes every future message to anonymous readers. Not exposed to agents. An agent able to publish its own pod could exfiltrate a private room's entire future history in one call. **2. Creation asks for intent, not audience** The two option cards are gone. Every pod is born private; `joinPolicy: 'open'` below the community tier is the DORMANT declaration ("open once listed"), not a choice a stranger should be making about a room with nothing in it yet. Visibility becomes a later, deliberate act on a pod that has content worth disclosing — which is ADR-016's own position. This also retires the selected-state dot from #870: with the cards gone it has nothing to mark, and it was a fourth signal saying what the accent fill, border and text colour already said. Verified against production before writing: ZERO pods sit in an unrepresentable state (listed-but-unreadable), so no migration is required — 235 pods land in exactly the three tiers, and the 18 legacy null joinPolicy rows are already handled by the fail-open predicate. Tests: 15 new on the route (invariant 1 held on promote, showcase refused, non-owner refused, DM kinds refused, audit written, audit failure non-fatal, limiter wired). Frontend suites updated for the simplified form, including a zh-CN case so the removal cannot be done in the component but missed in the catalog. Layout invariant now asserts the option RULES are gone, and documents that anything reintroducing them needs the `.v2-root button.` prefix or it renders as plain prose (#870).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Sam, the night before the YC interview: "not usable, no intuition on what is clicked, and no distinction at all." He was measurably right — verified live with a seeded smoke user, both option buttons computed to
background: transparent, border: 0px, padding: 0. The join-policy choice rendered as two paragraphs of prose. The active state painted nothing.Root cause
This codebase's most-documented CSS trap claiming another victim: the global reset
.v2-root button:not(.MuiButtonBase-root)(specificity 0-2-1) beats a bare class (0-1-0) and strips exactly the properties that make an option look like an option. The reaction chips (#867) and filter segments already carry the.v2-root button.prefix workaround — the create panel never got it. Compounding: the intended--activestyle used a gray border (--v2-border-strong), near-invisible even had it won the cascade.Fix — verified on production before committing
CSS-only. Injected the rules into the live page with a seeded smoke user and screenshotted both selection states before shipping: selected = accent-soft fill + accent border + 6px accent dot beside the label; unselected = plain card with hover feedback; 80ms transitions per the design system.
Also verified the full path while there: create was never broken — smoke user created a pod end-to-end (and deleted it after). The UI was just illegible, which reads as broken.
Guard
v2-layout-invariants: the prefixed rule must exist and--activemust referencevar(--v2-accent)— both jsdom-invisible properties. 22/22 invariants, sidebar + create-hook suites green.Post-interview (#770 remains open): the considered creation flow against ratified ADR-016 — this PR makes the existing panel legible, it does not redesign it.