Archives openspec change slice3-walk-fixes and syncs its five delta specs (fedwiki-sites, entitlements, operator-panel-navigation, operator-list-scale, ui-quality-gate). - FedWiki site usage is read from active site rows in both quota readers; the reservation counter converges on the rows: raise-only after farm sync and inside the create quota check, exact at boot. The understated production counters repair on the first boot. - The People tile caption excludes the reserved system person through the same query parameter the directory uses. - The operator Domains live-claims list is a governed list: pages of 50, true total, search over root name and organization, a pending/active facet. - New lint rule table-without-list-controls refuses an unpaged page-body table unless it carries a list-scale exempt marker with a reason; six curated or detail tables carry one. Its first run caught the operator FedWiki sites list, which is now governed the same way. - Entitlement-set rule copy: "Per unit", "Multiplied by the quantity purchased or granted."
9.2 KiB
operator-list-scale Specification
Purpose
Cross-cutting contract in the style of empty-state-guidance and form-conventions: how operator lists behave at deployment scale. It governs the lists that grow with the deployment — organizations, grants, people, domain claims, FedWiki sites, and the four billing views (accounts, subscriptions, invoices, payments). Small operator-curated configuration lists (org types, entitlement sets, plan ladders, the products catalog) are exempt and stay flat until they earn controls. It implements the URL shape that operator-panel-navigation reserved ("Browse routes reserve query-parameter real estate for filters").
Requirements
Requirement: Governed lists paginate with a true total count
Each governed operator list SHALL render server-side pages of at most 50 rows with previous/next controls and a current-position indication, and SHALL state the true total row count for the current search and filter ("Showing X–Y of N"), never rendering the whole table at once and never presenting a page's row count as the total. Page state lives in the page query parameter; an out-of-range or malformed page value clamps to a valid page rather than erroring, and the query's offset is computed without overflow for any page value the parameter can carry.
Scenario: A large list pages instead of dumping
- WHEN an operator opens a governed list whose row count exceeds the page size
- THEN at most one page of rows renders, with controls to move between pages
- AND the stated total is the count of all matching rows, not the page's
Scenario: Malformed page values clamp
- WHEN an operator loads a governed list with
?page=9999or?page=abc - THEN the list renders a valid page without error
Scenario: A page number past the 32-bit offset still renders
- WHEN a governed list is loaded with
?page=2147483648or any larger value - THEN the offset SHALL be capped rather than wrapped negative, and the list SHALL render a valid page without a database error
Requirement: List state is URL-addressable and server-rendered
Search, filter, and page state on a governed list SHALL live entirely in that page URL's query parameters (q, capability-appropriate facet names, page) using the reserved shape from operator-panel-navigation, submitted as plain form GETs under the panel's hx-boost; copying the URL SHALL reproduce the exact view, and no list control SHALL require JavaScript beyond the boost that every panel navigation already uses.
Scenario: A filtered view is shareable
- WHEN an operator copies the URL of a searched, filtered, paged list view and opens it in a fresh session
- THEN the same rows, controls, and stated total render
Requirement: Governed lists carry a scoped server-side search
Each governed list SHALL carry one search input that matches case-insensitively against that list's named fields — organizations: name, owner name or email, and the organization's key matched exactly where one is set (ui-vocabulary: a key is a declarative address, so it matches exactly, after IDs and before names, and most organizations carry none); grants: organization and grant target; people: display name and email; billing views: organization name and the customer-facing identifier the view leads with — and a search with no matches SHALL render the no-match state (per empty-state-guidance, distinct from the list's true-empty state) with a one-click way to clear the search.
Scenario: Search narrows the list server-side
- WHEN an operator submits a search term on a governed list
- THEN only matching rows render, the stated total reflects the match count, and the term stays visible in the input and the URL
Scenario: No matches is not empty
- WHEN a search matches nothing on a list that has rows
- THEN the list renders a no-match state naming the term, with a clear-search affordance
- AND not the list's true-empty state
Requirement: Status filters exist where a status vocabulary exists
A governed list whose rows carry a status vocabulary SHALL offer a filter over exactly that vocabulary — grants: the derived Live/Superseded/Inactive states; invoices: their status set plus the derived Overdue state (operator-billing-views); subscriptions: their status set; organizations: org type — composing with search and pagination, with the active filter visibly marked and clearable in one click. Filter values use the reserved facet parameter names.
Scenario: Filters compose with search and paging
- WHEN an operator applies a status filter and a search term together
- THEN rows match both, the total reflects the combination, and paging moves within the filtered set
Scenario: The active filter is visible and clearable
- WHEN a filter is active on a governed list
- THEN the control shows which value is active
- AND one click returns to the unfiltered view
Requirement: Embedded lists can adopt the shared controls
A governed list embedded in a page that hosts more than one (the organization composite's grants ledger and Tier changes) SHALL use the same search, facet, and pager mechanics under namespaced parameter names (a per-list prefix on q, page, and per; the facet parameter is always a full name), and every URL such a list builds — pager links, facet links, page-size links, and the search form's submission — SHALL preserve its sibling lists' state on the shared URL. Embedded lists default to a page size of 10 and offer a picker over the shared option set (10, 25, 50); an unknown size is ignored like an unknown facet, and the size parameter is omitted at the list's default so canonical views keep canonical URLs. Standalone pages keep the unprefixed names and the standard size. The flat "latest 16" cap remains only for embedded lists that have not adopted the controls (Members). An embedded list's controls SHALL re-render only their own panel, never scrolling the page (maintainer 2026-08-24): each control issues a scoped request targeting and selecting the panel's wrapper, pushes the resulting URL so views still deep-link, keeps the plain URL as the no-JS fallback, and refreshes its sibling panels out-of-band from the same response so no panel's links ever carry stale sibling state.
Scenario: Interacting with an embedded list holds the viewport
- WHEN an operator scrolled mid-page switches a tab, searches, resizes, or pages an embedded list
- THEN only that list's panel (and its siblings' control state) re-renders, the scroll position is unchanged, and the address bar carries the combined state of every embedded list on the page
Scenario: Sibling state survives navigation
- WHEN an operator has searched the grants ledger and then pages the Tier changes list on the same organization page
- THEN the Tier changes link carries its own namespaced page parameter and the ledger's search and tab state ride along unchanged
Requirement: The operator Domains live-claims list is governed
The live-claims table on /operator/domains SHALL be a governed list under this contract: server-side pages of at most 50 rows with the true total ("Showing X–Y of N"), page state in page, a scoped server-side search over the claim's root name and the owning organization's name, and a status facet over the live vocabulary (pending, active). The page ordering (pending first, then newest) SHALL apply across the whole result, not per page, so the first page carries the claims that need attention. The page header's count SHALL be the true total for the current search and filter. The placements and history sections on the same page are not governed by this requirement.
Scenario: More claims than a page
- WHEN an operator opens
/operator/domainson a deployment with 120 live claims - THEN 50 rows render with previous/next controls and "Showing 1–50 of 120"
- AND the header count reads 120
Scenario: Search narrows by root name or organization
- WHEN an operator submits
q=poddingon/operator/domains - THEN only claims whose root name or owning organization name contains "podding" render, with the total stated for that search
Scenario: Pending facet
- WHEN an operator selects the
pendingfacet - THEN only pending claims render, and the URL carries the facet so the view is addressable
Requirement: The operator FedWiki sites list is governed
The operator FedWiki sites list SHALL be a governed list under this contract: server-side pages of at most 50 rows with the true total, page state in page, a scoped server-side search over the site's domain, the owning workspace's name and the owning organization's name, and a status facet over the site status vocabulary. It grows one row per site created anywhere in the deployment, which is the property that makes a list governed.
Scenario: More sites than a page
- WHEN an operator opens the FedWiki sites list on a deployment with 101 sites
- THEN 50 rows render with previous/next controls and "Showing 1–50 of 101"
Scenario: Search narrows by domain or organization
- WHEN an operator submits a search token on the FedWiki sites list
- THEN only sites whose domain or owning organization name contains the token render, with the total stated for that search