Front/back toggle renders the selected side as disabled #100

Closed
opened 2026-07-28 06:03:08 +00:00 by claude-bot · 1 comment

Severity: MEDIUM

The bugs

The active toggle renders as disabled. ReviewWorkspacePage.tsx:453-468 marks the current
side both btn-primary and disabled, and styles.css:224-227 renders disabled at 45% opacity
with a not-allowed cursor. So the selected option looks broken. Disabled and selected are
different states and must not share a presentation.

A dead button when there is no back scan. When has_back is false, a lone permanently
disabled "Front" button remains as pure noise.

Scope

  • A two-segment control where the active segment is marked aria-pressed and rendered solid, not
    disabled.
  • Hide the control entirely when there is no back scan, replacing it with a muted "front only"
    label.

For contrast, the decision buttons get this right — the green/red/ghost split correctly satisfies
docs/circa-ui-spec.md §7.2 ("approving should not feel identical to skipping"). This toggle is
the one place the state model went wrong.

Done when

  • The active side is visually solid and marked aria-pressed
  • Single-sided photos show no dead control
  • The control is keyboard-operable

References

  • frontend/src/pages/ReviewWorkspacePage.tsx:453-468
  • frontend/src/styles.css:224-227
## Severity: MEDIUM ## The bugs **The active toggle renders as disabled.** `ReviewWorkspacePage.tsx:453-468` marks the *current* side both `btn-primary` and `disabled`, and `styles.css:224-227` renders disabled at 45% opacity with a not-allowed cursor. So the selected option looks broken. Disabled and selected are different states and must not share a presentation. **A dead button when there is no back scan.** When `has_back` is false, a lone permanently disabled "Front" button remains as pure noise. ## Scope - A two-segment control where the active segment is marked `aria-pressed` and rendered solid, not disabled. - Hide the control entirely when there is no back scan, replacing it with a muted "front only" label. For contrast, the decision buttons get this right — the green/red/ghost split correctly satisfies `docs/circa-ui-spec.md` §7.2 ("approving should not feel identical to skipping"). This toggle is the one place the state model went wrong. ## Done when - [ ] The active side is visually solid and marked `aria-pressed` - [ ] Single-sided photos show no dead control - [ ] The control is keyboard-operable ## References - `frontend/src/pages/ReviewWorkspacePage.tsx:453-468` - `frontend/src/styles.css:224-227`
claude-bot added this to the v0.3.0 milestone 2026-07-28 06:03:08 +00:00
Author

Done in 97d5cdc, with #98 and #99. CI run #60 green.

Done when:

  • The active side is visually solid and marked aria-pressed
  • Single-sided photos show no dead control
  • The control is keyboard-operable

The issue's diagnosis was exactly right and needed no correction: the current side was marked both btn-primary and disabled, and disabled renders at 45% opacity with not-allowed, so the selected option looked broken. Selected and disabled are different states and no longer share a presentation — the active segment is solid and aria-pressed="true", and never disabled.

A photograph with no back scan now shows a muted "Front only" label instead of one permanently dead button.

Keyboard operability came free: #102 had already made these real <button> elements as part of its sweep, so there was nothing to fix here.

One existing test had to change, and it is worth naming because it was asserting the bug. the front and back toggle > starts on the front did expect(getByRole("button", {name: "Front"})).toBeDisabled() — the old behaviour, written down and pinned. It now asserts aria-pressed="true" instead. A second test was renamed from "offers no Back button" to "offers no toggle at all", because it was understating what it now holds: neither button is present, and the "Front only" label is.

The e2e reviewer journey needed a comment corrected for the same reason — it explained that the toggle was "inert because there is nowhere to go", which is no longer true, since there is now no toggle at all. No assertion changed.

Done in 97d5cdc, with #98 and #99. CI run [#60](https://git.rhoving.com/rbrooks/Circa/actions/runs/60) green. **Done when:** - [x] The active side is visually solid and marked `aria-pressed` - [x] Single-sided photos show no dead control - [x] The control is keyboard-operable The issue's diagnosis was exactly right and needed no correction: the current side was marked both `btn-primary` and `disabled`, and disabled renders at 45% opacity with `not-allowed`, so the selected option looked broken. Selected and disabled are different states and no longer share a presentation — the active segment is solid and `aria-pressed="true"`, and never disabled. A photograph with no back scan now shows a muted **"Front only"** label instead of one permanently dead button. Keyboard operability came free: #102 had already made these real `<button>` elements as part of its sweep, so there was nothing to fix here. **One existing test had to change**, and it is worth naming because it was asserting the bug. `the front and back toggle > starts on the front` did `expect(getByRole("button", {name: "Front"})).toBeDisabled()` — the old behaviour, written down and pinned. It now asserts `aria-pressed="true"` instead. A second test was renamed from "offers no Back button" to "offers no toggle at all", because it was understating what it now holds: neither button is present, and the "Front only" label is. The e2e reviewer journey needed a comment corrected for the same reason — it explained that the toggle was "inert because there is nowhere to go", which is no longer true, since there is now no toggle at all. No assertion changed.
Sign in to join this conversation.
No description provided.