// The Grid blueprint, as data. Lifted verbatim from the blueprint-ontology // `grid` README. Note: Grid has 6 functions (incl. the Grid-specific `Position`, // no Sort/Act) and shares the composed LoadState machine with List. import type { Blueprint } from "./blueprint" import { LOADSTATE_MACHINE } from "./loadStateMachine" export const grid: Blueprint = { slug: "grid", name: "Grid", tagline: "A parameterized, scrollable set of items in a 2D cell layout.", role: "feature", extendsName: "Set", composesCount: 6, sig: { header: "Grid + Async + Paginated + Pullable + Selectable + Filterable", fields: [ { name: "items", type: "seq Item", note: "row-major; loaded subset of total" }, { name: "columns", type: "Int", note: "number of columns (≥ 1)" }, { name: "totalCount", type: "Int", note: "Paginated" }, { name: "loadState", type: "LoadState", note: "Async · Paginated · Pullable" }, { 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", "row(i) = i / columns col(i) = i mod columns (derived, not stored)", ], }, functions: [ { id: "display", name: "Display", fig: "01", caps: ["Grid", "Async"], verb: "Render items in a 2D cell layout; handle empty, loading, and error states distinctly.", state: [ ["items", "seq Item"], ["loadState", "LoadState"], ], behaviors: [], invariants: [ "#4 items = ∅ is a valid state (empty grid, not an error)", "#8 all cells have equal dimensions", ], failures: [ "empty vs error conflation — server returns 0 items with 4xx; empty state treated as error, user sees wrong UI", "incomplete trailing row — #items mod columns ≠ 0; trailing cells must render as empty placeholders, not crash", "variable-height cell break — items with differing heights; row alignment lost, grid degenerates to a non-uniform layout", ], perf: ["visible rows appear within 300 ms of navigation"], notes: [], }, { id: "scroll", name: "Scroll", fig: "02", caps: ["Grid"], verb: "Allow the user to navigate the full item sequence along one axis; virtualize off-screen rows.", state: [["items", "seq Item"]], behaviors: [], invariants: [], failures: [], perf: [ "60 fps minimum — off-screen rows must not be rendered (row virtualization required)", "rows outside the render window must release their cell view references (memory)", ], notes: [ "Grid scrolls one axis; the row (not the individual cell) is the unit of virtualization.", ], }, { 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: "—", 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 — loaded count never exceeds the server total"], failures: [ "duplicate load — loadMore() called while loadState = loadingMore; duplicate items appended, identity-uniqueness lost", ], perf: ["loadMore appends new rows without layout shift or scroll-position jump"], notes: [], }, { id: "position", name: "Position", fig: "04", caps: ["Grid"], verb: "Derive each item's (row, col) from its index and columns; recompute on resize.", state: [["columns", "Int"]], behaviors: [ { sig: "resize(n)", pre: "n ≥ 1", eff: "columns ← n, reflow layout; scroll position preserved by item", }, ], invariants: [ "#1 columns ≥ 1 — at least one column at all times", "#7 row(i) = i / columns, col(i) = i mod columns — position deterministically derived from index", ], failures: [ "layout thrash on resize — columns changes while items are mid-scroll; visible rows reflow, scroll position jumps to a different item", ], perf: ["column reflow completes within one frame (≤ 16 ms) for in-memory data"], notes: [ "Derived: row(i) = i / columns (integer division), col(i) = i mod columns, rows = ⌈#items / columns⌉.", "Open question: adaptive column count (floor(width / minCellWidth)) is treated as a consumer concern; the model uses a fixed integer.", ], }, { id: "filter", name: "Filter", fig: "05", 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: ["#5 visible ⊆ items — filter is purely restrictive"], failures: [], perf: [], notes: ["Ephemeral: derives visible from items without mutating the sequence."], }, { 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: ["selected ⊆ items — selection is always over loaded items"], failures: [ "stale selection after refresh — refresh() clears items but not selected; selected ⊄ items, invariant violated", ], perf: [], notes: [], }, ], coreInvariants: [ "columns ≥ 1 — at least one column at all times", "members = elems(items) — set and sequence are always in sync", "items are identity-unique within items", "items = ∅ is a valid state (empty grid, not an error)", "all cells have equal dimensions", ], composition: { caps: [ { id: "core", label: "Grid : Set", sub: "items, columns", 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" }, ], funcs: ["Display", "Scroll", "Load", "Position", "Filter", "Select"], links: [ ["core", "Display"], ["core", "Scroll"], ["core", "Position"], ["async", "Display"], ["async", "Load"], ["paginated", "Load"], ["pullable", "Load"], ["filterable", "Filter"], ["selectable", "Select"], ], }, stateMachines: [LOADSTATE_MACHINE], }