feat(share-web): /me + /@handle polish — modals, SWR header, row redesign#375
Merged
Conversation
…sign
Recovers polish work designed + implemented on 2026-06-04 that never
landed on main. Ten items, scoped to share-web only (no backend or
desktop changes).
Header SWR + cache:
- New `lib/me-cache.ts` — versioned localStorage cache of {name, avatar_url}
- New `lib/auth-cache.ts` — module-level memo so cross-page Header mounts
share one fetchMe; invalidated on signout + 401
- `Header` default `auth='auto'` paints from cache first, revalidates in
background; pages drop their hardcoded `auth="out"` props
- Wordmark href "/" → "https://spool.pro/" so the logo click goes to the
landing site in both dev and prod
Account surface:
- `DeleteAccountModal` replaces the inline confirm/scheduled state
machine; opened from a quiet "Delete account" link on the right
edge of the "Signed in as …" row
- `onScheduled(at)` callback lifts state to the parent so the deletion-
pending banner appears immediately after Yes, not on the next refresh
- Banner copy now carries an inline "Cancel deletion" button that opens
the same modal in cancel mode
- Re-entrancy lock (useRef) on confirm-unpublish so a double-tap on a
slow device can't fire two parallel revokes
ShareRow + Unpublish flow:
- New row layout: bold single-line title (ellipsis + hover tooltip),
meta line with a visibility ICON (`globe` for profile-listed, `link-2`
for unlisted; tooltip on hover, `cursor: help`) + published date +
optional expires + revoked = small red UNPUBLISHED pill
- Whole-row click navigates to the reader — title + meta wrap in a
single `<a>` so hovering the visibility icon shows its `title=`
naturally (no `::after` overlay to block tooltips)
- Hover state: row bg → `--card-2`, title → accent
- Actions sit outside the anchor as siblings (copy + eye-off only —
the external-link Open icon dropped; title is the open affordance)
- `UnpublishConfirmModal` reuses the same `.sw-modal-*` shell — shows
the target row + 410-Gone warning before committing
Profile (public /@handle) rows:
- Simple title + mono date layout — same anchor wrap, no actions
Utilities:
- `relativeDate(ts, now)` + `Number.isFinite` guards on humanDate /
humanDateTime / relativeDate so NaN doesn't surface as "Invalid Date"
- `.sw-section-label` drops `text-transform: uppercase` per the
sentence-case rule; mono + tracking kept
- Reader paper shadow tokens (light + dark variants)
- `ModalShell` shared by Delete + Unpublish modals (ESC + backdrop
close, gated by busy)
Tests:
- `dates.test.ts` (8) — humanDate guards, relativeDate buckets (Today /
Yesterday / Nd ago / >7 days fallback / future / NaN)
- `me-cache.test.ts` (7) — round-trip, version mismatch invalidates,
malformed JSON survives, clear works, localStorage-unavailable
fallback doesn't throw
- `api-cache.test.ts` (5) — fetchMe 200 writes cache + 401 clears it
AND drops the auth memo, 403 leaves cache alone (deletion-pending
surface still needs identity), signOut clears both layers even on
network failure
44 → 70 tests; tsc clean.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
Polish pass on the share-web
/meand/@handlesurfaces. Scoped topackages/share-web/only; no backend or desktop changes.Changes
Header SWR + cache
lib/me-cache.ts— versionedlocalStoragecache of{name, avatar_url}so first paint shows the real avatar instead of a 100ms blank while/api/meround-tripslib/auth-cache.ts— module-level memo so cross-pageHeadermounts share onefetchMecall; invalidated onsignOutand on a 401 responseHeaderdefault changes fromauth='out'toauth='auto'— paints from cache, revalidates in the background;Reader.tsx,Profile.tsx,Tombstone.tsx, and theMeloading / error states drop the hardcodedauth="out"propWordmarkhref/→https://spool.pro/so logo-click lands on the landing site in both dev and prodAccount surface
DeleteAccountModalreplaces the inlineidle / confirming / scheduled / cancellingstate machine; opened from a small "Delete account" link in the "Signed in as …" footer rowonScheduled(at)lifts state to the parent so the deletion-pending banner appears immediately after the user confirms — previously the banner waited for the next reloaduseRefre-entrancy lock onconfirmUnpublishso a double-tap on a slow device cannot fire two parallel revoke callsShareRow + Unpublish flow
title=hover tooltip), meta line with a visibility icon (globeforprofile-listed,link-2forunlisted;title=tooltip,cursor: help) + published date + optional expires + revoked = small redUnpublishedpill<a class="sw-share-link">so hovering the visibility icon shows itstitle=naturally without an overlay blocking events--card-2, title → accentUnpublishConfirmModaluses the same.sw-modal-*shell; renders the target row plus a 410-Gone warning before committingProfile (public /@handle) rows
Utilities
relativeDate(ts, now)+Number.isFiniteguards onhumanDate/humanDateTime/relativeDatesoNaNdoes not surface asInvalid Date.sw-section-labeldropstext-transform: uppercase, keeps mono + tracking.reader-papergets shadow tokens (light + dark)ModalShellshared by both confirm modals —Escand backdrop click close, gated bybusyTest plan
pnpm --filter @spool/share-web typecheck— cleanpnpm --filter @spool/share-web exec vitest run— 70 / 70dates.test.ts(8):humanDate/humanDateTimeguards,relativeDatebuckets (Today / Yesterday / Nd ago / 7+ days fallback / future / NaN)me-cache.test.ts(7): round-trip, version-mismatch invalidation, malformed-JSON tolerance,clearCachedMe,localStorage-unavailable fallbackapi-cache.test.ts(5):fetchMe200 writes cache, 401 clears cache + drops the auth memo, 403 leaves cache intact,signOutclears both layers even when the network call failsFollow-ups
/termsand/privacyare placeholder pages — real content neededShares → Publishedtab rows want the same polishSubmitter: @graydawnc
🤖 Generated with Claude Code