Tour the Atmosphere. Switch clients, share universal links, browse any PDS.

Deferred review follow-ups: error states, record not-found, a11y modals, perf (#57) master

* UX: surface UFOs API outages instead of rendering them as empty data The UFOs client swallowed request failures into empty results, so an API outage rendered as authoritative "No lexicons matched in this window." / "No lexicons found." — the error branches in TrendingLexicons and BrowseAllLexicons were unreachable because the client never signalled failure. fetchCollections now returns a `failed` flag distinguishing a real failure (null response) from a genuinely empty result. fetchRanking throws on it so the trending leaderboard shows its existing "Couldn't reach the UFOs API" state, and BrowseAllLexicons shows the same instead of a false empty state. Existing callers ignore the added field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DF1WX7iG9AL7FVXVbmmPNo * UX: tell visitors when a shared record can't be previewed Universal-link record and post pages rendered the client picker unconditionally even when the record fetch returned null, so a visitor following a link to a deleted or unavailable record saw the full "choose a client" picker with no indication the preview was missing. Add a notice above the picker on both the generic record route and the post route. The fetch returns null for both a deleted record and a transient host failure, so the copy covers both honestly; the picker still renders since its client links work if the record exists elsewhere or the host recovers. Verified: notice shows for a nonexistent rkey, and valid records (recordData present) never trigger it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DF1WX7iG9AL7FVXVbmmPNo * a11y: keep collapsed compact-header panels out of the tab order + add Escape On every universal-link page the compact header's nav and search panels are hidden only with scaleY(0)/opacity/pointer-events, so keyboard and screen-reader users tabbed through invisible nav links, the theme toggle, sign-in controls, and the search input. Mark both panels `inert` when collapsed (React 19) to remove them from the tab order and accessibility tree, and add an Escape handler so the open nav menu can be dismissed by keyboard (previously only an outside mouse click closed it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DF1WX7iG9AL7FVXVbmmPNo * a11y: shared accessible JsonModal for the margin "view raw data" dialogs All seven margin.* previews carried their own copy of a plain fixed <div> overlay with no dialog semantics, no Escape, and no focus management — a screen reader heard nothing on open and keyboard focus stayed on the covered page. Replace the seven copies with one <JsonModal> built on the native <dialog> element via showModal(), which provides role=dialog + aria-modal, a focus trap, Escape-to-close, focus moved into the dialog on open, and focus restored to the trigger on close. Verified end-to-end against a live at.margin.annotation record: the dialog opens labeled, focus lands inside it, and Escape closes it; the visual (centered card, blurred backdrop, JSON pane) is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DF1WX7iG9AL7FVXVbmmPNo * perf: cap concurrent handle lookups on the PDS explorer A PDS page renders up to ~50 repo rows, each firing a describeRepo handle lookup on mount — so opening one hit the PDS with ~50 simultaneous requests (rate-limit / hammer risk). Route the lookups through a small semaphore (6 concurrent); rows still resolve their handle as slots free up, at a fraction of the burst. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DF1WX7iG9AL7FVXVbmmPNo --------- Co-authored-by: Claude <noreply@anthropic.com>


Author dame Co-author Claude Fable 5 Committer GitHub Date Commit e0dbaa25 Parent b720e5c7