Skip to content

fix: surface controlled-value problems instead of silently degrading (M7)#25

Merged
dogmar merged 1 commit into
mainfrom
fix/m7-controlled-degradation
Jun 16, 2026
Merged

fix: surface controlled-value problems instead of silently degrading (M7)#25
dogmar merged 1 commit into
mainfrom
fix/m7-controlled-degradation

Conversation

@dogmar

@dogmar dogmar commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Audit item M7. CalendarProvider had two controlled-component footguns that failed silently.

1. Malformed value silently went uncontrolled

In range/multiple mode, a non-null value that wasn't the right shape (range: not a { start, end }; multiple: not an array) fell through to undefinedcontrolledDates === undefinedisControlled === false. The component quietly self-managed, so the value prop looked ignored.

→ Now emits console.warn("[DatePicker] …") (graceful uncontrolled fallback kept, but loud).

2. Out-of-bounds controlled value fired onValueChange(null) on mount

The single-mode cleanup effect cleared any committed date outside min/max via onValueChange(null)even when controlled, so a controlled component spontaneously told the parent to clear on mount (controlled-semantics violation + render-loop risk).

→ When controlled, it now warns and leaves the value alone (the parent owns it). Uncontrolled auto-clear is unchanged.

Warn-once (from adversarial review)

A review pass flagged that both warnings would re-fire every render when the parent inlines value/onValueChange (the effects' deps change identity). Both are now ref-guarded to warn once per episode.

Tests (TDD, RED→GREEN)

  • malformed range/multiple → warns once
  • out-of-bounds controlled → warns once across re-renders, never fires onValueChange
  • out-of-bounds uncontrolled → still clears + fires (regression guard)
  • Full suite 443/443 · lint/typecheck clean · package build OK (attw clean)

🤖 Generated with Claude Code

…(M7)

CalendarProvider had two controlled-component footguns:

1. A controlled `value` with the wrong shape for the selection mode
   (range: not a { start, end }; multiple: not an array) fell through to
   `undefined` and the component silently became uncontrolled — the prop
   looked ignored. Now it warns: console.warn("[DatePicker] …").

2. An out-of-bounds controlled `value` fired onValueChange(null) on mount —
   a controlled component spontaneously clearing the parent's value (and a
   render-loop risk). When controlled it now warns and leaves the value
   alone (the parent owns it); the uncontrolled auto-clear is unchanged.

Both warnings are ref-guarded to fire once per episode, not every render
(the effects re-run whenever the parent inlines `value` / `onValueChange`).

Tests: malformed range/multiple warn once; out-of-bounds controlled warns
once across re-renders and never fires onValueChange; out-of-bounds
uncontrolled still clears + fires (regression guard).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@netlify

netlify Bot commented Jun 16, 2026

Copy link
Copy Markdown

Deploy Preview for colander-cal canceled.

Name Link
🔨 Latest commit e868c4b
🔍 Latest deploy log https://app.netlify.com/projects/colander-cal/deploys/6a3095ad4165670008ef60f8

@dogmar dogmar merged commit 7c977a0 into main Jun 16, 2026
7 checks passed
@dogmar dogmar deleted the fix/m7-controlled-degradation branch June 16, 2026 00:33
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.1.0-alpha.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant