fix(web): ship avatar-picker CSS inline so a stale styles.css can't break it (BUNYIP-408) #400

Merged
longjacksonle merged 1 commit from fix/BUNYIP-408-avatar-picker-inline-css into main 2026-07-28 22:34:04 +02:00

The bug (bug.jpg)

On the deployed Settings page the avatar control was a disaster: a page-filling green circle with the initial floating in the middle, the raw "Choose File / No file chosen" native input showing, both the no-JS "Upload" and the enhanced "Change photo" buttons visible, and a "selected file is empty" error.

Root cause

The component's structural CSS lived in the Tailwind-built assets/styles.css - a separately-built, browser-cached file. The running app served fresh SSR HTML + inline JS but a stale cached stylesheet with none of the .avatar-picker* rules. With those rules missing, the absolute inset-0 fallback initial escaped its (now unsized, unpositioned) container and filled the card, the input clip never applied, and the enhancement-hiding never applied. (Confirming how easy this class is to hit: the three utilities the markup leaned on - sr-only, select-none, and a novel to-teal-600 - are not even in the built stylesheet, because Tailwind purges unused classes.)

Fix

Move every structural .avatar-picker* / .avatar-slot__img rule out of input.css/styles.css and into an inline <style> in the SSR document head (avatar_picker::AVATAR_PICKER_CSS), exactly like the app's existing inline theme/toast scripts. Inline CSS ships with the always-fresh HTML, is never separately cached, and needs no Tailwind rebuild - the stale-stylesheet failure mode is gone. input.css and assets/styles.css are reverted to their pre-picker state, so this branch no longer touches the built stylesheet at all.

Hardening so a missing stylesheet can never be catastrophic again: the letter fallback sizes with h-full w-full inside an inline-CSS-sized circle instead of absolute inset-0, and the markup drops the purged sr-only / select-none utilities (accessible name via aria-label, non-select via inline CSS).

Verification

  • Headless render of every state (empty, photo set, uploading, drag-over, no-JS): the circle stays a circle, the native input is clipped, and enhanced vs no-JS affordances show correctly.
  • just check-container green: fmt + clippy -D warnings + all test binaries (116 web tests).

🤖 Generated with Claude Code

## The bug (bug.jpg) On the deployed Settings page the avatar control was a disaster: a page-filling green circle with the initial floating in the middle, the raw "Choose File / No file chosen" native input showing, both the no-JS "Upload" and the enhanced "Change photo" buttons visible, and a "selected file is empty" error. ## Root cause The component's structural CSS lived in the Tailwind-built `assets/styles.css` - a separately-built, browser-cached file. The running app served fresh SSR HTML + inline JS but a **stale cached stylesheet** with none of the `.avatar-picker*` rules. With those rules missing, the `absolute inset-0` fallback initial escaped its (now unsized, unpositioned) container and filled the card, the input clip never applied, and the enhancement-hiding never applied. (Confirming how easy this class is to hit: the three utilities the markup leaned on - `sr-only`, `select-none`, and a novel `to-teal-600` - are not even in the built stylesheet, because Tailwind purges unused classes.) ## Fix Move every structural `.avatar-picker*` / `.avatar-slot__img` rule **out of `input.css`/`styles.css` and into an inline `<style>` in the SSR document head** (`avatar_picker::AVATAR_PICKER_CSS`), exactly like the app's existing inline theme/toast scripts. Inline CSS ships with the always-fresh HTML, is never separately cached, and needs no Tailwind rebuild - the stale-stylesheet failure mode is gone. `input.css` and `assets/styles.css` are reverted to their pre-picker state, so this branch no longer touches the built stylesheet at all. Hardening so a missing stylesheet can never be catastrophic again: the letter fallback sizes with `h-full w-full` inside an inline-CSS-sized circle instead of `absolute inset-0`, and the markup drops the purged `sr-only` / `select-none` utilities (accessible name via `aria-label`, non-select via inline CSS). ## Verification - Headless render of every state (empty, photo set, uploading, drag-over, no-JS): the circle stays a circle, the native input is clipped, and enhanced vs no-JS affordances show correctly. - `just check-container` green: fmt + clippy `-D warnings` + all test binaries (116 web tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(web): ship avatar-picker CSS inline so a stale styles.css can't break it (BUNYIP-408)
All checks were successful
E2E / Playwright against deployment (pull_request) Successful in 1m4s
Check / fmt + clippy + build + tests (pull_request) Successful in 4m51s
Create release / Create release from merged PR (pull_request) Has been skipped
a93dd056a9
Bug report (bug.jpg): on the running Settings page the avatar control was a disaster - a page-filling green circle with the initial floating in the middle, the raw "Choose File / No file chosen" native input showing, both the no-JS "Upload" and the enhanced "Change photo" buttons visible, and a "selected file is empty" error.

Root cause: the component's structural CSS lived in the Tailwind-built `assets/styles.css`, which is a separately-built, browser-cached file. The running app served fresh SSR HTML + inline JS but a STALE cached stylesheet with none of the `.avatar-picker*` rules. With those rules missing the `absolute inset-0` fallback initial escaped its (now unsized, unpositioned) container and filled the whole card, the input clip did not apply, and the enhancement-hiding did not apply. Confirmed the same three utilities the markup leaned on (`sr-only`, `select-none`, a novel `to-teal-600`) are not even in the built stylesheet, so Tailwind purging makes this class of failure easy to hit.

Fix: move every structural `.avatar-picker*` / `.avatar-slot__img` rule OUT of `input.css`/`styles.css` and INTO an inline `<style>` in the SSR document head (`avatar_picker::AVATAR_PICKER_CSS`), exactly like the app's inline theme/toast scripts. Inline CSS ships with the always-fresh HTML, is never separately cached, and needs no Tailwind rebuild, so the stale-stylesheet failure mode is gone. `input.css` and `assets/styles.css` are reverted to main (net-zero change), so this branch no longer touches the built stylesheet at all.

Hardening so a missing stylesheet can never be catastrophic again: the letter fallback now sizes with `h-full w-full` inside an inline-CSS-sized circle instead of `absolute inset-0`, and the markup avoids the purged `sr-only` / `select-none` utilities (accessible name via `aria-label`, non-select via inline CSS). Verified with a headless render of every state (empty, photo set, uploading, drag-over, no-JS): the circle stays a circle, the native input is clipped, and enhanced vs no-JS affordances show correctly.

`just check-container` green (fmt + clippy -D warnings + all test binaries, 116 web tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5dcYueNHByRnWJDYoDX1W
longjacksonle deleted branch fix/BUNYIP-408-avatar-picker-inline-css 2026-07-28 22:34:04 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/bunyip!400
No description provided.