Skip to content

fix(v2): create-pod options rendered as plain prose — the reset ate them - #870

Merged
lilyshen0722 merged 1 commit into
mainfrom
fix/create-pod-option-visibility
Aug 6, 2026
Merged

fix(v2): create-pod options rendered as plain prose — the reset ate them#870
lilyshen0722 merged 1 commit into
mainfrom
fix/create-pod-option-visibility

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

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 --active style 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 --active must reference var(--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.

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
lilyshen0722 merged commit d557d45 into main Aug 6, 2026
11 checks passed
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant