SuperAPI AI Directory

defensive-data-design

Coding & development

View repository

Updated 10-16 07:02

About this skill

Defensive data design

Safe defaults for code that can lose someone's work or leave them stuck. Apply them as checks, not intentions.

Errors the user sees

  • Say the cause, not just the symptom. Where the failure carries a usable message, show it (and its code if that helps); fall back to your own copy when it does not. A catch that receives an error and shows a fixed string is a defect.
  • Rejection shapes from apiFetch vary — see the pitfall in the root AGENTS.md before reading a cause from one.
  • REST validation failures carry per-field reasons in error.data.params. The top-level message only names the field.
  • Strip tags with __unstableStripHTML from @wordpress/dom rather than discarding a message that contains HTML.
  • Never swallow. An empty catch is acceptable only when the failure is expected and the fallback is correct — say which failure, in a comment.
  • Show enough of a failure to act on, and make it copyable where there is room. Keep the user's content out of the payload, and the current URL too: it can carry a nonce.
  • Read the copy guide's Error Messaging section before writing the string. Plain words, name the cause, offer a next step.

Mutations and destructive actions

Prefer reversible changes, and make both the change and the way back visible. An irreversible one should be harder to trigger.

  • Before a consequential mutation, show what it targets, who will see the result, and the state it leaves behind. Default to draft, private or reversible; make public or permanent the deliberate choice.
  • Moving something to the trash should be easy; deleting it should be hard. If both sit in the same menu behind the same confirmation, the confirmation is doing no work.
  • Confirm dialogs echo what is affected: the item's title, or the count for bulk actions.
  • Irreversible confirms use isDestructive on the confirm button and say so in the label ("Delete permanently", not "Delete"). Follow the destructive actions pattern.
  • Announce a mutation that succeeds, and offer Undo when the prior value is in scope. Capture that value explicitly rather than popping the undo stack, which would revert unrelated edits too.
  • Snackbar renders one action only; more logs a warning and truncates to actions[0] (packages/components/src/snackbar/index.tsx). Undo or another button, not both.
  • A snackbar dismisses itself six seconds after appearing, so use explicitDismiss or a different notice for anything the user must finish reading. The copy guide covers how long the text itself can be.
  • When a mutation fails, leave the user able to try again: keep their input, restore consistent state, release any control left busy, and make a retry safe.
  • saveEntityRecord and deleteEntityRecord add nothing to the undo stack, so anything already persisted needs its own recovery path. editEntityRecord records an undo level unless called with undoIgnore.

Legibility

  • Use the space you have: show the value itself rather than a label that flattens it, instead of making someone open a panel to find out.
  • Make sure a label reflects reality, including any qualifier it would otherwise hide. For example:
    • "Published" on a password-protected post reads as publicly readable when it is not.
    • A template reads as the theme's when it carries local edits that belong to the site.

Data you did not create

Guard where untrusted data enters — a REST response, post meta, theme.json, a filtered editor setting, a storage read — not at every read downstream. Block attributes are only partly covered: packages/blocks/src/api/parser/get-block-attributes.ts substitutes the declared default when a parsed value fails its type, but the blocks.getBlockAttributes filter runs after that check and updateBlockAttributes does not check at all, so a wrongly typed value still reaches save functions and PHP.

  • JSON.parse at a boundary goes in a try/catch, and the result is shape-checked (Array.isArray, is_array) before it is mapped or iterated: valid JSON of the wrong shape is the common case, not malformed JSON.
  • In PHP the failure mode depends on what you touch, and a fatal during rendering is a white screen for every visitor:
    • Warns and yields null: foreach over a non-iterable; an offset on null, an int, a bool or a float. Bad data flows on quietly.
    • Fatal: a string offset on a string ($str['slug']); an array or object used as an offset; an object treated as an array. Guard these first.
  • Check where a throw lands. React error boundaries catch render-phase errors only — a throw in a registry.subscribe callback, an async click handler or a promise chain escapes them and can silently drop the user's edit.