Define the catalog/runtime/integration layering, per-org composite view route, curated landing surface, breadcrumb positional contract, reserved query-param real estate, and single-entry-point grant action affordances. Adds 6 requirements to operator-panel-navigation; the legacy ?tab= SPA contract stays until M7d retires it. Also files an independent tech-debt item for member-console reading products.product_type directly instead of the billing.product_kinds view, per upstream membcons-db Doc 35 Product Kind Taxonomy.
10 KiB
operator-panel-navigation Specification
Purpose
TBD - created by archiving change fix-operator-panel-tabs. Update Purpose after archive.
Requirements
Requirement: Tab state is reflected in the URL
The operator panel SHALL update the URL query parameter ?tab=<tab-id> (using hx-replace-url) whenever a tab is activated, without adding a new browser history entry.
Scenario: Tab click updates URL
- WHEN the operator clicks a tab button
- THEN the URL is updated to include
?tab=<tab-id>without a full page navigation
Scenario: Page reload restores active tab
- WHEN the operator reloads the page with
?tab=<tab-id>in the URL - THEN the panel activates the tab matching
<tab-id>instead of defaulting to tab 1
Scenario: Unknown tab param falls back gracefully
- WHEN the URL contains
?tab=<unknown-id>that does not match any tab - THEN the panel defaults to tab 1 without error
Requirement: Cross-tab data refresh on mutation
The operator panel SHALL refresh dependent tab panes when a mutation in one tab changes data that another tab displays, without requiring a full page reload.
Scenario: Products tab refreshes after entitlement-set mutation
- WHEN an entitlement set is created, edited, or deleted in the Entitlement Sets tab
- THEN the Products tab container re-fetches its partial so the entitlement-set dropdown reflects the updated list
Scenario: Mutation success response triggers refresh
- WHEN the server returns a successful response to a create/edit/delete action in the Entitlement Sets tab
- THEN the response includes an
HX-Trigger: refreshProductsheader - THEN the Products tab pane listens for the
refreshProductsevent and re-fetches/partials/operator/products
Requirement: Tab panes load content on first reveal only (except where cross-dependency requires refresh)
Tab pane partials SHALL be loaded lazily on first reveal. Subsequent tab activations SHALL NOT re-fetch the partial unless a cross-tab refresh event has been triggered.
Scenario: Tab loads on first reveal
- WHEN the operator activates a tab for the first time in a session
- THEN the tab pane fetches its partial from the server
Scenario: Re-activating a tab does not re-fetch
- WHEN the operator switches away from a tab and back again (with no intervening mutation)
- THEN no new HTTP request is made for that tab's partial
Requirement: Operator panel groups capabilities under a three-layer information architecture
The operator panel SHALL organize all capabilities under exactly three top-level groups: Catalog, Runtime, and Integration. Catalog contains configuration entities edited rarely with wide blast radius (org_types, products, entitlement_sets, plan_ladders). Runtime contains per-organization operational surfaces (organizations, grants, billing read-views). Integration contains system-specific surfaces (FedWiki sites today; other integrations later). Capabilities SHALL NOT appear at the operator panel's top level outside one of these three groups.
Scenario: Catalog capabilities are grouped together
- WHEN an operator views the operator panel navigation
- THEN
org_types,products,entitlement_sets, andplan_laddersappear under the Catalog group - THEN none of them appear outside the Catalog group
Scenario: Runtime capabilities are grouped together
- WHEN an operator views the operator panel navigation
- THEN organizations, grants, and billing read-views (accounts, subscriptions, invoices, payments) appear under the Runtime group
- THEN none of them appear outside the Runtime group
Scenario: Integration capabilities are grouped together
- WHEN an operator views the operator panel navigation
- THEN FedWiki sites appears under the Integration group
- THEN future integrations attach to the Integration group rather than a new top-level group
Requirement: Operator panel exposes a curated landing surface at /operator
The operator panel SHALL render a curated landing surface at /operator that highlights hot-path entry points (organization lookup, recent runtime activity, recent billing activity) rather than defaulting to the first capability tab. The landing surface SHALL be the default destination when an operator navigates to /operator without further path or query parameters.
Scenario: Navigating to /operator shows the landing surface
- WHEN an authenticated operator navigates to
/operator - THEN the panel renders the curated landing surface
- THEN the landing surface includes an organization lookup entry point
- THEN the landing surface surfaces recent runtime activity and recent billing activity sections
Scenario: Landing surface is the documented default
- WHEN an operator follows an internal link to
/operatorfrom anywhere in the panel - THEN the landing surface renders instead of an arbitrary capability tab
Requirement: Per-organization composite view is addressable at /operator/organizations/{orgID}
The operator panel SHALL provide a per-organization composite view at the route /operator/organizations/{orgID} that co-locates enrollment state, grant history, billing summary, and grant actions for one organization. The route SHALL be bookmarkable and reload-stable. The composite view SHALL be reachable from the Runtime group's organization browse surface.
Scenario: Per-org composite view is bookmarkable
- WHEN an operator loads
/operator/organizations/{orgID}directly via URL - THEN the page renders the composite view for that organization without requiring prior navigation through the panel
Scenario: Composite view co-locates runtime work
- WHEN an operator views
/operator/organizations/{orgID} - THEN the page surfaces the organization's enrollment state, grant history, and billing summary on the same page
- THEN grant actions (issue, extend, revoke) are reachable from the same page without navigating to a different top-level capability
Requirement: Per-organization composite view is the sole UI entry point for grant actions
The operator panel SHALL expose grant action affordances (issue, extend, revoke) only from the per-organization composite view. The global Grants surface in the Runtime group SHALL be read-only — it presents grants for browse/audit/lookup but exposes no action affordances. Underlying HTTP handlers (e.g. the simple RevokeGrant, the legacy CreateGrant issuance path) MAY remain reachable by direct URL for programmatic callers but SHALL NOT be surfaced as operator UI affordances.
Grant issuance SHALL be exposed as two distinct forms on the per-organization composite view, labeled by intent: one form for plan-typed products (uses IssueGrant; product picker restricted to plan-typed products on the organization's ladder) and one form for non-plan products (uses CreateGrant; product picker restricted to products with product_type ∈ {addon, usage, one_time}).
Grant revocation SHALL use the composite RevokeGrantAndTransition behavior (pool returns to the org-type default and a transition is recorded). The simple RevokeGrant behavior SHALL NOT be exposed as a UI affordance.
Scenario: Per-org view exposes revoke using the composite behavior
- WHEN an operator views
/operator/organizations/{orgID} - THEN each active grant for the organization has a Revoke affordance
- THEN invoking Revoke triggers the revoke-and-transition behavior (pool returns to org-type default; a transition is recorded)
Scenario: Per-org view exposes two issuance forms
- WHEN an operator views
/operator/organizations/{orgID} - THEN the page exposes a "Grant a plan" form whose product picker is restricted to plan-typed products on the organization's ladder
- THEN the page exposes a "Grant a non-plan product" form whose product picker is restricted to products with
product_type ∈ {addon, usage, one_time}
Scenario: Per-org view exposes extension
- WHEN an operator views
/operator/organizations/{orgID} - THEN each active grant has an Extend affordance that updates the grant's expiry
Scenario: Global Grants surface exposes no action affordances
- WHEN an operator views the global Grants surface in the Runtime group
- THEN the grant rows render in a read-only listing
- THEN no Issue, Extend, or Revoke affordance appears on the surface
Requirement: Every operator page declares its IA position for breadcrumb derivation
Every operator panel page SHALL declare its position in the IA as a positional tuple of the form (group, capability) or (group, capability, instance), where group is one of catalog, runtime, or integration, capability is the capability slug (e.g. products, organizations), and instance is present when the page is scoped to one entity (e.g. an organization's composite view). The visual rendering of breadcrumbs is out of scope for this specification.
Scenario: Capability index page declares its IA position
- WHEN an operator loads
/operator/products - THEN the page declares its IA position as
(catalog, products)
Scenario: Instance page declares its IA position
- WHEN an operator loads
/operator/organizations/{orgID} - THEN the page declares its IA position as
(runtime, organizations, {orgID})
Requirement: Browse routes reserve query-parameter real estate for filters
Browse routes under the operator panel (e.g. /operator/organizations, /operator/products, /operator/plan-ladders) SHALL reserve the query parameters ?q=<search> and ?<facet>=<value> (where <facet> is a capability-appropriate field such as org_type, lifecycle_status) as the canonical shape for future filter additions. Browse routes SHALL NOT use these parameter names for unrelated purposes. Implementing visible filter controls is out of scope for this specification; the reservation establishes URL contract only.
Scenario: Reserved parameter names are not reused
- WHEN a new feature is added to a browse route
- THEN the feature SHALL NOT use
q,org_type,lifecycle_status, or other established facet parameter names for any purpose other than search and filtering
Scenario: Unknown filter params do not error
- WHEN an operator loads a browse route with a reserved-but-unimplemented filter param (e.g.
/operator/organizations?q=acme) - THEN the route renders without error
- THEN the unimplemented filter is ignored rather than rejected