feat: gallery + generic blueprint viewer, add Grid blueprint
Refactor the List-specific screen into a data-driven, reusable viewer: - generic Blueprint type + shared LoadState machine + registry - vue-router: / (gallery) and /b/:slug (illustration) - BlueprintViewer renders any blueprint; specimens stay bespoke - add Grid blueprint (6 functions incl. Position) + GridSpecimen
This commit is contained in:
206
src/data/gridBlueprint.ts
Normal file
206
src/data/gridBlueprint.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
// 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<Item>",
|
||||
tagline: "A parameterized, scrollable set of items in a 2D cell layout.",
|
||||
role: "feature",
|
||||
extendsName: "Set<Item>",
|
||||
composesCount: 6,
|
||||
|
||||
sig: {
|
||||
header: "Grid<Item> + 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"],
|
||||
],
|
||||
},
|
||||
|
||||
stateMachine: LOADSTATE_MACHINE,
|
||||
}
|
||||
Reference in New Issue
Block a user