// The List blueprint, as data. Lifted verbatim from the blueprint-ontology // `list` README: signature, behaviors, invariants, failure modes, performance. // The generic Viewer renders this; ListSpecimen supplies the bespoke visual. import type { Blueprint } from "./blueprint" import { LOADSTATE_MACHINE } from "./loadStateMachine" export const list: Blueprint = { slug: "list", name: "List", tagline: "A scrollable, ordered set of items.", role: "feature", extendsName: "Set", composesCount: 5, sig: { header: "List + Async + Paginated + Pullable + Selectable + Filterable", fields: [ { name: "items", type: "seq Item", note: "List: ordered, loaded subset" }, { name: "loadState", type: "LoadState", note: "Async · Paginated · Pullable" }, { name: "totalCount", type: "Int", note: "Paginated" }, { name: "selectionMode", type: "SelectionMode", note: "Selectable" }, { name: "selected", type: "set Item", note: "Selectable" }, { name: "visible", type: "set Item", note: "Filterable" }, ], types: [ "LoadState = Idle | Loading | Refreshing | LoadingMore | Error", "SelectionMode = None | Single | Multi", ], }, functions: [ { id: "display", name: "Display", fig: "01", caps: ["List", "Async"], verb: "Render items in order; render skeleton placeholders during initial load; handle empty, loading, and error states distinctly.", state: [ ["items", "seq Item"], ["loadState", "LoadState"], ], behaviors: [], invariants: ["#1 items = ∅ is a valid state (empty list, not an error)"], failures: [ "empty vs error conflation — network returns 0 items with 4xx; empty state treated as error, user sees wrong UI", "skeleton / content mismatch — skeleton row dimensions differ from real item; layout shift on Loading → Idle", ], perf: [ "skeleton placeholders render within 100 ms of navigation", "real items replace the skeleton within 300 ms", "skeleton dimensions must match real items to prevent layout shift", ], notes: [], }, { id: "scroll", name: "Scroll", fig: "02", caps: ["List"], verb: "Allow the user to navigate the full item sequence; virtualize off-screen items.", state: [["items", "seq Item"]], behaviors: [], invariants: ["items form a well-formed sequence (no duplicate indices) — l.items.isSeq"], failures: [], perf: [ "60 fps minimum — off-screen items must not be rendered (virtualization required)", "items outside the render window must release their view references (memory)", ], notes: [ "Scroll is navigation, not a mutating behavior — it moves the render window over items, it does not change them.", ], }, { id: "load", name: "Load", fig: "03", caps: ["Async", "Paginated", "Pullable"], verb: "Fetch the initial page; support refresh (restart) and pagination (append).", state: [ ["loadState", "LoadState"], ["totalCount", "Int"], ], behaviors: [ { sig: "refresh()", pre: "loadState = idle", eff: "loadState ← refreshing, reload items from page 1, selected ← ∅", }, { sig: "loadMore()", pre: "#items < totalCount, loadState = idle", eff: "loadState ← loadingMore, append next page to items", }, ], invariants: ["#6 #items ≤ totalCount", "#7 loadState = loadingMore → #items < totalCount"], failures: [ "duplicate load — loadMore() called while loadState = loadingMore; duplicate items appended, items loses uniqueness", "unknown total — streaming source, totalCount never known; use CursorPaginated with a hasMore sentinel instead", ], perf: ["loadMore appends new items without layout shift or scroll-position jump"], notes: [], }, { id: "filter", name: "Filter", fig: "04", caps: ["Filterable"], verb: "Restrict the visible set without mutating the underlying sequence.", state: [["visible", "set Item"]], behaviors: [{ sig: "filter(pred)", pre: "—", eff: "visible ← { x ∈ items | pred(x) }" }], invariants: ["#8 visible ⊆ items — filter never introduces new items"], failures: [ "filter after reload — predicate references old item shape; visible silently empties without error", ], perf: ["result updates within one frame (≤ 16 ms) for in-memory data"], notes: [ "Ephemeral: derives visible from items but does not mutate the sequence. Persisting a filter is a consumer concern.", ], }, { id: "sort", name: "Sort", fig: "05", caps: ["Sortable"], verb: "Reorder the sequence by a comparator without changing identity.", state: [["items", "seq Item"]], behaviors: [{ sig: "sort(cmp)", pre: "—", eff: "reorder items by comparator" }], invariants: ["identity is preserved — sorting changes order, never membership"], failures: [ "sort invalidates cursor (CursorPaginated) — cursor is stale; must reset to NoCursor and reload from page 1", ], perf: [], notes: [ "Ephemeral alongside filter: reorders the displayed sequence without persisting the change.", ], }, { id: "select", name: "Select", fig: "06", caps: ["Selectable"], verb: "Track user intent over a subset of items (none / single / multi).", state: [ ["selectionMode", "SelectionMode"], ["selected", "set Item"], ], behaviors: [ { sig: "select(x)", pre: "x ∈ visible, selectionMode ≠ none", eff: "selected ← selected ∪ {x}", }, { sig: "deselect(x)", pre: "x ∈ selected", eff: "selected ← selected \\ {x}" }, { sig: "selectAll()", pre: "selectionMode = multi", eff: "selected ← visible" }, { sig: "clearSelection()", pre: "—", eff: "selected ← ∅" }, ], invariants: [ "#3 selected ⊆ items", "#4 selectionMode = none → selected = ∅", "#5 selectionMode = single → #selected ≤ 1", ], failures: [ "stale selection — refresh() clears items but not selected; selected ⊄ items, invariant #3 violated", ], perf: [], notes: [], }, { id: "act", name: "Act", fig: "07", caps: ["Swipeable", "Reorderable"], verb: "Expose per-item contextual actions (swipe, long-press) to the consumer.", state: [], behaviors: [ { sig: "swipeAction(x, dir)", pre: "x ∈ visible", eff: "trigger consumer-defined contextual action", }, { sig: "reorder(from, to)", pre: "capability: reorderable = true", eff: "swap positions in items", }, ], invariants: [], failures: [ "reorder on filtered view — reorder operates on visible, not items; position mismatch between displayed and stored order", ], perf: [], notes: ["Open question: swipe direction — left | right only, or also up | down?"], }, ], coreInvariants: [ "items = ∅ is a valid state (empty list, not an error)", "items are identity-unique — no duplicates by key", "elems[items] = members — ordered form stays in sync with the set", ], composition: { caps: [ { id: "core", label: "List : Set", sub: "items : seq Item", core: true }, { id: "async", label: "Async", sub: "loadState" }, { id: "paginated", label: "Paginated", sub: "totalCount" }, { id: "pullable", label: "Pullable", sub: "refresh" }, { id: "filterable", label: "Filterable", sub: "visible" }, { id: "selectable", label: "Selectable", sub: "selected" }, { id: "sortable", label: "Sortable", sub: "cmp" }, { id: "swipeable", label: "Swipeable", sub: "+ Reorderable" }, ], funcs: ["Display", "Scroll", "Load", "Filter", "Sort", "Select", "Act"], links: [ ["core", "Display"], ["core", "Scroll"], ["async", "Display"], ["async", "Load"], ["paginated", "Load"], ["pullable", "Load"], ["filterable", "Filter"], ["selectable", "Select"], ["sortable", "Sort"], ["swipeable", "Act"], ], }, stateMachines: [LOADSTATE_MACHINE], }