{
 "id": "optimistic-update",
 "name": "Optimistic update",
 "purpose": "A to-do list that shows a change the moment it's made, then quietly puts it back with a reason and a retry if the save fails.",
 "use_when": [
  "for quick, likely-to-work changes such as ticking a task, liking or starring, where waiting for the server would feel slow",
  "the change is small, reversible and almost always succeeds",
  "the server answer takes long enough to feel slow (over about 100 ms)"
 ],
 "avoid_when": [
  "payments, sends and deletes: wait for the server and show inline-spinner",
  "changes that other people see at once: confirm before showing them as done"
 ],
 "type": "states",
 "style": "quiet",
 "width": "medium",
 "tags": {
  "audience": "work",
  "industries": [
   "any"
  ],
  "keywords": [
   "optimistic",
   "rollback",
   "undo",
   "checkbox",
   "save error"
  ]
 },
 "root": ".odl-optimistic-update",
 "parts": [
  {
   "name": "tasks",
   "selector": ".odl-optimistic-update [data-od-list=\"tasks\"]",
   "what": "the tasks, one item each",
   "slot": "list"
  },
  {
   "name": "tasks.done",
   "selector": ".odl-optimistic-update [data-od-list=\"tasks\"] [data-od-input=\"done\"]",
   "what": "the done",
   "slot": "input"
  },
  {
   "name": "tasks.task",
   "selector": ".odl-optimistic-update [data-od-list=\"tasks\"] [data-od-slot=\"task\"]",
   "what": "the task, as in “Send Q4 invoices to Gorsedown Print”",
   "slot": "text"
  },
  {
   "name": "tasks.retry",
   "selector": ".odl-optimistic-update [data-od-list=\"tasks\"] [data-od-action=\"retry\"]",
   "what": "the Try again action",
   "slot": "action"
  }
 ],
 "data": {
  "type": "object",
  "properties": {
   "tasks": {
    "type": "array",
    "minItems": 1,
    "maxItems": 6,
    "items": {
     "type": "object",
     "properties": {
      "done": {
       "type": "boolean",
       "part": "tasks.done"
      },
      "task": {
       "type": "string",
       "maxLength": 56,
       "part": "tasks.task"
      }
     },
     "required": [
      "task"
     ]
    },
    "part": "tasks"
   }
  },
  "required": [
   "tasks"
  ]
 },
 "states": {
  "shown": [
   "saving",
   "saved",
   "rolled back",
   "retry",
   "hover",
   "focus"
  ],
  "edge": {
   "empty": {
    "status": "handled",
    "evidence": "data-od-list",
    "how": "the Smart UI renderer puts one plain 'Nothing to show yet.' line in an empty list"
   },
   "loading": {
    "status": "shown",
    "how": "A pending change is shown at once and dimmed until the save answers.",
    "evidence": "is-pending"
   },
   "error": {
    "status": "shown",
    "how": "A failed save rolls the checkbox back and says so in its row, with Try again.",
    "evidence": "odl-err"
   },
   "disabled": {
    "status": "not_built",
    "how": "No control here is ever disabled; each one always does something."
   },
   "overflow": {
    "status": "handled",
    "how": "Long task names wrap inside their row.",
    "evidence": "text-wrap"
   },
   "long_text": {
    "status": "handled",
    "how": "Titles balance and body text wraps at word boundaries inside a capped measure.",
    "evidence": "text-wrap"
   },
   "missing_image": {
    "status": "none",
    "how": "the part has no photo"
   }
  }
 },
 "tokens": [
  "--ds-border-width-default",
  "--ds-color-bg-action-primary",
  "--ds-color-bg-action-primary-hover",
  "--ds-color-bg-action-primary-pressed",
  "--ds-color-bg-neutral-subtle",
  "--ds-color-border-default",
  "--ds-color-border-focus",
  "--ds-color-border-separator",
  "--ds-color-border-subtle",
  "--ds-color-surface-raised",
  "--ds-color-text-danger",
  "--ds-color-text-link",
  "--ds-color-text-on-action",
  "--ds-color-text-primary",
  "--ds-color-text-secondary",
  "--ds-focus-ring-offset",
  "--ds-focus-ring-width",
  "--ds-font-weight-heading",
  "--ds-font-weight-label",
  "--ds-icon-stroke-md",
  "--ds-motion-duration-short",
  "--ds-motion-easing-standard",
  "--ds-opacity-disabled-container",
  "--ds-opacity-disabled-content",
  "--ds-radius-container",
  "--ds-radius-control",
  "--ds-size-control-md",
  "--ds-size-icon-md",
  "--ds-size-icon-sm",
  "--ds-size-row-md",
  "--ds-size-target-min",
  "--ds-space-inline-md",
  "--ds-space-inline-sm",
  "--ds-space-inline-xs",
  "--ds-space-inset-lg",
  "--ds-space-inset-md",
  "--ds-space-inset-sm",
  "--ds-space-inset-xl",
  "--ds-space-stack-md",
  "--ds-space-stack-sm",
  "--ds-space-stack-xs",
  "--ds-state-error-text",
  "--ds-text-body-md",
  "--ds-text-body-sm",
  "--ds-text-label-lg",
  "--ds-text-title-md"
 ],
 "a11y": {
  "summary": "Each task is a real checkbox; the polite status reads each change, and a failed save is read with what happened; the error line sits in the row it belongs to and its Try again button is a real button.",
  "roles": [
   "list",
   "region",
   "status"
  ],
  "keyboard": [
   "Tab moves through the checkboxes; Space ticks one",
   "After a failed save, Tab reaches Try again in that row"
  ],
  "controls": {
   "button": 3,
   "input": 3,
   "link": 0,
   "select": 0,
   "textarea": 0,
   "summary": 0
  },
  "focus_visible": true,
  "reduced_motion": true,
  "min_target": true
 },
 "files": {
  "html": "components/optimistic-update/component.html",
  "bytes": 8778,
  "script": true
 },
 "source": null,
 "photos": 0,
 "urls": {
  "html": "https://opendesigner.dev/library/optimistic-update.html",
  "json": "https://opendesigner.dev/library/optimistic-update.json",
  "page": "https://opendesigner.dev/library/optimistic-update/"
 },
 "get": {
  "curl": "curl -fsSLo optimistic-update.html https://opendesigner.dev/library/optimistic-update.html",
  "opendesigner": "python3 path/to/skills/opendesigner/scripts/library.py add optimistic-update --from https://opendesigner.dev",
  "prompt": "Add the OpenDesigner component optimistic-update to this project: https://opendesigner.dev/library/optimistic-update.html. Keep its markup and states; map its --ds-* variables to my tokens."
 }
}
