Skip to content

feat(spa): editable Range fields — two-input editor wired to [lower, upper] write shape (#242)#538

Merged
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/range-spa-editor-242
May 28, 2026
Merged

feat(spa): editable Range fields — two-input editor wired to [lower, upper] write shape (#242)#538
MartinCastroAlvarez merged 1 commit into
mainfrom
feat/range-spa-editor-242

Conversation

@MartinCastroAlvarez
Copy link
Copy Markdown
Owner

Summary

Closes #242. The backend already serializes ranges as the documented {subtype, value: {lower, upper, bounds}} envelope (#141) and just learned to accept [lower, upper] on the write path (#533). This PR pairs that with the SPA-side editor, matching the JSON / Duration / Array editors that already shipped under #242.

Tier 4 (frontend only — no backend/wire-contract change).

What

  • @dar/api contract.ts — adds the previously-missing 'range' to FieldType. It's already documented in docs/api-contract.md §range types and already emitted by field_type_for for every postgres range internal-type; the TS mirror was stale.
  • @dar/form FieldInput — a new range branch renders two text inputs (lower / upper) bound to the [lower, upper] write shape _range_endpoints accepts. Empty side = unbounded. Subtype-aware formatting is the user's job (Django's MultiValueField sub-fields validate each side and surface a bad value as a normal field error — same contract as the json / duration / array editors).
  • Three seeders unwrap the read envelope into the [lower, upper] pair where form state is built: CreatePage, DetailPage (initialValueFor), RelatedAddModal (seedValue). Empty / missing envelope → ['', ''] so a new object starts with two blank inputs.

Design notes

  • No new descriptor metadata for subtype — the user knows what range they're filling in (the field label), and the backend validates per-side. Matches the deliberately-thin pattern of the other Form: structured field editors — JSONField / ArrayField / range / DurationField are read-only #242 editors (no client-side schema duplication).
  • a11y: the lower input carries the row id; the upper input has its own aria-label="<field> upper bound" so a screen reader announces both sides distinctly.
  • The three-site seeder duplication is a known smell that pre-dates this PR (json + array already triplicated); folding them into a shared seedValue is a clean follow-up that isn't in scope here.

Verification (matches CI gate #506)

  • pnpm -r typecheck clean (13 pkgs) · pnpm lint clean (eslint --max-warnings 0 + stylelint + dark-mode coverage) · pnpm -r build ok.
  • pnpm test142 passed (22 files), incl. two new FieldInput cases: a non-empty value renders both inputs and emits [newLower, oldUpper] / [oldLower, newUpper] independently; null renders two empty inputs.

Role / autonomy

Author / Architect. Tier 4 (frontend only) → agent-mergeable per autonomy-policy.md.

Closes #242

🤖 Generated with Claude Code

…er, upper] write shape (#242)

Closes **#242**. The backend already serializes ranges as the
`{subtype, value: {lower, upper, bounds}}` envelope (#141) and now
accepts `[lower, upper]` on the write path (#533) — this PR pairs that
with the SPA-side editor, matching the JSON / Duration / Array editors
that already shipped.

- @dar/api `contract.ts`: add the previously-missing `'range'` to
  `FieldType` so the closed vocabulary matches what the backend has been
  emitting (it's documented in `docs/api-contract.md` §field types; the
  TS mirror was stale). No wire change.
- @dar/form `FieldInput`: a `range` branch renders two text inputs
  (lower / upper) bound to the `[lower, upper]` write shape
  `_range_endpoints` accepts. An empty side = unbounded. Subtype-aware
  formatting is the user's job — Django's `MultiValueField` sub-fields
  validate each side and surface a bad value as a normal field error,
  same contract as the JSON / Duration / Array editors.
- Seeders unwrap the read envelope into the `[lower, upper]` pair in
  the three sites that prepare form state: `CreatePage`, `DetailPage`'s
  `initialValueFor`, and `RelatedAddModal`'s `seedValue`. Empty / missing
  envelopes seed to `['', '']` so new objects start with two blank
  inputs instead of an unsupported shape.

Tests: two new `FieldInput` cases — non-empty value renders both inputs
and emits `[newLower, oldUpper]` / `[oldLower, newUpper]` independently;
a `null` value renders two empty inputs. Full vitest **142 passed**;
typecheck + ESLint (--max-warnings 0) + stylelint + dark-mode guard
clean; `pnpm build` ok.

Closes #242

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MartinCastroAlvarez MartinCastroAlvarez merged commit 743473e into main May 28, 2026
5 checks passed
@MartinCastroAlvarez MartinCastroAlvarez deleted the feat/range-spa-editor-242 branch May 28, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Form: structured field editors — JSONField / ArrayField / range / DurationField are read-only

2 participants