Skip to main content
← Back to list
01Issue
BugShippedSwamp ClubPublic
Assigneeskeeb

Relationships

#1657 Profile Edit trigger is a fire-and-forget island: no aria-expanded, and a silent no-op if the editor island does not hydrate

Opened by keeb · 8/14/2026· Shipped 8/15/2026

Problem

The profile "Edit" trigger is islands/ProfileEditButton.tsx, an island whose entire job is to globalThis.dispatchEvent(new CustomEvent("swamp:toggle-profile-editor")). The thing that reacts is islands/ProfileEditor.tsx — a different island, mounted ~600 lines further down routes/u/[username].tsx (trigger at line 868, editor at line 1463).

The trigger holds no state of its own and renders no aria-expanded. So if the listener is not there for any reason — the second island failing to hydrate, its chunk failing to load, a throw anywhere in its mount — the button is a no-op that reports nothing. Not a console error, not a disabled state, not a visual change. The operative clicks and the page does nothing, forever.

Two things make it worse:

  • The editor sits at the bottom of a long dossier, so the trigger has to smooth-scroll to it after a 50ms setTimeout. Even on the happy path the feedback is deferred and off-screen at the moment of the click.
  • aria-expanded is missing on a control that expands a region, which is a WCAG 4.1.2 gap independent of the reliability point.

Why file it

This is the shape behind #1473 ("Profile edit does nothing on Safari"). That issue was closed as not reproducible — a real WebKit 26.4 engine drives both surfaces correctly, the shipped bundle is syntax-clean for Safari 14+, and the scroll fires — so the reported cause was not found. But the structure remains: any silent hydration failure in ProfileEditor presents as exactly that bug report, with nothing in the console to go on. Worth removing on its own merits rather than waiting for it to be someone's cause.

Proposed solution

Collapse the bridge. Make the trigger and the editor one island so the open/closed state is local and cannot be lost in transit, and give the trigger aria-expanded. Fresh renders islands SSR-first, so the trigger stays in the initial HTML either way.

If they must stay separate for layout reasons, the trigger should at minimum own its own expanded signal, render aria-expanded from it, and the editor should read that shared signal rather than a fire-and-forget window event.

Alternatives

  • Keep the event bridge but have the editor acknowledge (a second event back), and have the trigger surface a failure if no acknowledgement arrives. More machinery than the problem deserves.
  • Leave as-is and rely on client-side error reporting to catch the next occurrence. Doesn't fix the silence, only observes it.

Environment

swamp-club.com, routes/u/[username].tsx, islands/ProfileEditButton.tsx, islands/ProfileEditor.tsx.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 8 MOREREVIEW+ 2 MOREPR_LINKED

Shipped

8/15/2026, 1:02:14 AM

No activity in this phase yet.

03Sludge Pulse
keeb assigned keeb8/14/2026, 10:32:30 PM
keeb unassigned keeb8/14/2026, 11:49:40 PM
keeb assigned keeb8/14/2026, 11:50:28 PM

Sign in to post a ripple.