Skip to content

Field constraints are invisible until the server rejects the save, and autosave then retries the doomed request indefinitely #2417

Description

@virafb

Description

A collection field can declare validation constraints (maxLength, min, minItems, pattern). These are enforced server-side by the generated Zod schema, but nothing in the admin surfaces them to the person typing. The editor's first and only signal is a failed save.

Since #1838 that failure message is at least specific — the toast reads excerpt: Too big: expected string to have <=160 characters rather than the old generic "Invalid request data", which is a real improvement. But it arrives after the fact, names the field by slug rather than by its label, uses validator-internal phrasing, and — the substantive part of this report — autosave keeps retrying a request that can never succeed.

In one editing session on 0.32.0 we recorded 443 failed PUTs to the same entry over 23.4 minutes, a median of 3.0 seconds apart (~19/min), every one a 400 with the identical body. Each produced its own toast. The author had added a few words to a summary field capped at 160 characters; nothing in the UI had indicated a cap existed.

Steps to reproduce

  1. Declare a text field with validation: { maxLength: 160 } in a collection.
  2. Open an entry in the admin and type past 160 characters in that field.
  3. Observe: no counter, no maxlength, no help text — nothing marks the limit while typing.
  4. Wait. Autosave fires, fails 400, and toasts excerpt: Too big: expected string to have <=160 characters.
  5. Leave the tab open. Autosave retries every ~3 seconds indefinitely, toasting each time, with no back-off and no terminal state.

Observed

  • The constraint is unknowable from the UI until it is violated.
  • The message identifies the field by slug (excerpt), not by the label the editor actually sees ("Summary").
  • The message is the raw validator string ("Too big: expected string to have <=160 characters") rather than a sentence aimed at an author.
  • Autosave treats a permanent 400 as retryable. 443 attempts, 23.4 minutes, no escalation, no stop, no back-off.

Expected

  • Constraints declared in the schema are visible while editing — at minimum a character counter or maxlength on length-limited fields, and help text derived from validation rather than hand-written into the label.
  • Validation errors name the field by its label.
  • Autosave treats a 4xx other than 429 as terminal: stop retrying, keep the buffer dirty, surface the problem once, and retry only when the content changes again.

Why this matters beyond one field

We audited our own schema after hitting this: 16 of 17 declared constraints never reach the editor's screen. The single exception is the field in this report, and only because a developer had previously hand-typed "max 160 chars" into its label — after an earlier incident. That workaround is unchecked: change maxLength to 200 and the label still says 160.

The options constraints are effectively surfaced, because a select renders the allowed set. Everything expressed as a number or a pattern — maxLength, min, minItems, pattern — is invisible. A non-technical author cannot discover any of it except by tripping it.

Related

Environment

  • emdash 0.32.0
  • Astro node adapter, PostgreSQL 18 adapter
  • Reproduced against a production-shaped content set; server responses captured from load-balancer access logs, so the retry counts above are measured rather than estimated.

Notes on possible fixes

Roughly in increasing order of effort:

  1. Stop the retry storm. Classify non-429 4xx as terminal in the autosave client. This alone removes 442 of the 443 requests and the toast pile.
  2. Use the label in the message. The field descriptor already carries both slug and label.
  3. Render the constraint. Pass validation through to the field renderer: maxlength plus a live counter for length-limited strings, min/max on numerics, and generated help text. This is the fix that would have prevented the situation entirely.

Happy to open a PR for (1) and (2) if that would be useful — they look self-contained, and (1) is the one that turns a papercut into a non-event.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions