Blueprint.stateMachine becomes stateMachines[], the viewer renders each machine in its own panel, and StateMachine draws self-loops (a === b) for recurring transitions that don't change state. Existing blueprints move to the array form unchanged.
231 lines
8.4 KiB
TypeScript
231 lines
8.4 KiB
TypeScript
// 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<Item>",
|
||
tagline: "A scrollable, ordered set of items.",
|
||
role: "feature",
|
||
extendsName: "Set<Item>",
|
||
composesCount: 5,
|
||
|
||
sig: {
|
||
header: "List<Item> + 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],
|
||
}
|