Blog

All posts
John Damask · 2026-04-27
devlogfrontendgalleryfeatures

A small UX cleanup that produced an outsized discoverability win. The home-page carousel had a single quiet "Browse Gallery" link that pointed at the public gallery, and the post-upload success state showed users a bare URL labeled "View your page (private)" -- leaving signed-in users with no obvious path back to their library and no breadcrumb to the place where their freshly-generated paper lives.

The two surfaces that changed

The carousel header now exposes both galleries side-by-side: PUBLIC GALLERY is always visible, and MY GALLERY appears for signed-in users only. Both use the existing accent style. The auth-gated reveal piggybacks on the same auth-state listener that drives the upload form vs sign-in CTA toggle, so signing in or out flips the link without a reload. The roughly 50 to 200 millisecond flicker on hard reload before auth state resolves is identical to what the upload gate already pays.

The success state for private jobs swapped its bare URL for Job completed! View it now. as the primary anchor, plus a hint reading You can also find it in My Gallery. that links straight to /my-gallery.html. Public-job behavior is unchanged -- those still display the URL as the link text since for public jobs the URL itself is the share artifact.

The styling iteration

First pass shipped a longer heading -- Recent translations from public gallery -- wrapped in a custom anchor element with its own CSS rule for color inheritance. It worked. But on review, the visual hierarchy felt off: the long muted-grey heading on the left didn't read as a peer to the bright accent My Gallery link on the right, even though they were now functionally symmetric (both nav links to a gallery page). Trimmed it to Public Gallery and reused the existing accent styling for both. The two header items now read as a matched pair, and a couple of CSS rules got deleted as dead code in the same pass.

Scope discipline

Frontend-only -- one HTML file is the entire diff. No backend, Lambda, CloudFormation, or DynamoDB touched. No new endpoints, no new copy elsewhere, no top-nav refactor (other pages have their own headers and the footer already exposes "My Gallery"). The plan called out search and privacy controls on the generated page itself as out-of-scope for this issue, and they stayed that way.

Test deploy and a thumbnail red herring

Test verification almost derailed on what looked like a regression: 22 thumbnail 403 Forbiddens in the My Gallery network panel right after the CloudFront invalidation. Nothing in the diff touched bucket policy, CloudFront config, signed-cookie issuance, or my-gallery.html -- so the failure mode was decoupled from the change that exposed it.

I tracked it to the existing CloudFront signed-cookie path for pages/users/*. Those private thumbnails require per-user CloudFront-Policy, CloudFront-Signature, and CloudFront-Key-Pair-Id cookies that the auth flow sets on sign-in, scoped 30 days to the user's path. The cookies must have expired or hadn't been re-set since the previous CloudFront edge cache cleared. A re-sign-in restored them and thumbnails came back. I recorded it as a benign cookie expiry and moved on.

(That turned out to be wrong. The next post is the diagnosis.)

PR squash-merged to main, prod-deployed via the frontend-only deploy command the same day. Five beads tasks, all closed.