Context-Layered Usecase and Recipe Profile
The existing six-layer structure remains the internal runtime architecture. This profile adds the public boundary needed to connect that runtime to a design-system-based product UI.
Positioning
Product Scope
└─ Provider + Handler Registry
└─ Recipe
├─ Astryx primitives
└─ Usecase Facade
└─ Context-Layered Runtime
├─ contexts
├─ business
├─ handlers
├─ actions
└─ hooksContext-Layered Architecture is the umbrella name. Usecase Boundary, Facade, and Recipe are boundaries within that architecture, not a replacement architecture.
Why the profile exists
The original six layers explain how Context-Action executes a workflow, but they do not define the public UI boundary clearly enough:
actionsandhookscan accidentally become an undocumented public APIviewscan mix pure rendering with product-level composition- an Astryx primitive can become coupled to domain state or
context-action
This profile makes the ownership explicit:
| Boundary | Owns | Must not own |
|---|---|---|
| Domain business | Pure validation, calculation, state transitions | React, stores, UI wording |
| Runtime | Contexts, handlers, dispatch, subscriptions | Astryx components |
| Facade | Stable commands and view model | Layout and visual policy |
| Recipe | Astryx composition and prop mapping | Domain rules and raw dispatch |
| Product scope | Provider, registry, route, external data | Individual handler details |
Canonical feature structure
Existing features can keep the six-layer folders and add the two public boundaries:
access-request/
├── contexts/
├── business/
├── handlers/
├── actions/
├── hooks/
├── facade/
│ └── useAccessRequestFacade.ts
├── recipes/
│ └── AccessRequestRecipe.tsx
├── views/
└── AccessRequestPage.tsxFor new features, grouping runtime implementation is also valid:
feature/
├── contract/
├── domain/
├── runtime/
│ ├── contexts/
│ ├── handlers/
│ ├── actions/
│ └── hooks/
├── facade/
├── recipe/
└── scope/Use the first form when preserving an existing feature. Use the second form for a new feature with a clear package boundary.
Facade convention
The facade is the only public React-facing API for a feature runtime.
const vm = useAccessRequestFacade();
vm.workflow.phase;
vm.canSubmit;
vm.commands.changeReason(value);
vm.commands.submit();Rules:
- expose nouns for state and verbs for commands
- do not expose handler IDs, store managers, or raw
dispatch - derive values such as
isOpen,isBusy, andcanSubmitin the facade - keep async result, abort, retry, and error normalization inside the facade
Recommended names:
AccessRequestProvider
AccessRequestHandlerRegistry
useAccessRequestFacade
AccessRequestRecipeRecipe convention
Recipes compose Astryx-style primitives and map the facade view model to controlled props.
function AccessRequestRecipe() {
const vm = useAccessRequestFacade();
return (
<Drawer
isOpen={vm.workflow.resourceId != null}
onClose={vm.commands.close}
>
<Textarea
value={vm.workflow.reason}
onChange={vm.commands.changeReason}
/>
<Button
isLoading={vm.isBusy}
isDisabled={!vm.canSubmit}
onClick={vm.commands.submit}
/>
</Drawer>
);
}Recipe rules:
- import the design-system primitives and the feature facade
- preserve controlled props such as
isOpen,value,isLoading, andstatus - keep focus, ARIA, keyboard, and intrinsic interaction behavior in primitives
- never import
context-actiondirectly in a primitive - never implement validation or business transitions in JSX
Ref-mount observation usecase
Framework pattern demos often need to observe a DOM boundary without turning the DOM node itself into application state. Use the ref context for registration and mount lifecycle, a store for derived observations, and actions for user intent:
Ref mount lifecycle → action intent → Handler Registry → pure state transition → Store subscriptionThe useRefMountState demo is the reference recipe for this case:
contexts/owns the action, store, and ref contracts.business/contains pure transitions such as incrementing render counts.handlers/is the only place that registersuse*ActionHandlercalls.actions/exposes semantic commands such asresetRenderCounts.- views read
useRefMountStateanduseStoreValue; they do not mutate stores.
Mount callbacks may perform narrowly scoped DOM synchronization, but they must not become a second state-management channel. If the observation is rendered, dispatch an intent and keep the rendered value in a Store Context. Compose the domain as Action → Store → Ref → Handler Registry → View.
Priority pipeline usecase
An ordered pipeline is a useful usecase when validation, policy, business work, and observability must run under one action. Keep the order explicit in one Handler Registry:
authenticate → 100 validation → 95 security → 90 rate limit
→ 80 business → 30 analytics → 10 auditEach registration should have a stable handler ID and a documented priority. Blocking stages use the controller to abort before later stages, while later stages record their result through a Store Context rather than closing over view-local React state. This makes the execution trace observable and keeps priority configuration reviewable without reading the page component.
Action and handler naming
Usecase actions describe intent, not storage mutation:
selectResource
changeReason
submitRequest
cancelRequest
retryRequest
resetRequestHandler IDs describe the feature, action, and stage:
access-request.submit.validation
access-request.submit.policy
access-request.submit.request
access-request.submit.auditSuggested priority bands:
| Priority | Stage | Blocking |
|---|---|---|
| 100 | Contract and input validation | yes |
| 80 | Policy and permission checks | yes |
| 50 | Business operation or request | yes |
| 20 | View synchronization | normally no |
| 10 | Audit and telemetry | no |
Priority-word execution usecase
The ActionGuard priority-word demo is a compact usecase for an ordered, user-built command sequence:
registerWord → executeRegistered → ordered Registry run → status + result Storescomponents/priority/business/priority-demo-rules.tsowns duplicate checks, priority sorting, status transitions, and result composition as pure rules.contexts/PriorityDemoContexts.tsxdefines the Action and Store contracts; registration state, execution state, and the visible result are observable.actions/usePriorityDemoActions.tsexposesregisterWord,executeRegistered, andclearwithout leaking Store mutation to the view.handlers/PriorityDemoHandlerRegistry.tsxowns the ordered async run, per-step delay, cancellation token, and cleanup.
Use this recipe for command palettes, approval steps, staged workflows, or any demo where users register work in one order but the system executes it by an explicit priority. Keep the execution trace in Stores so the view can show registered, executing, and completed states without owning the pipeline.
Memoization comparison usecase
The memoization demo compares stable and intentionally re-registered handlers without mixing the comparison mechanics into the widgets:
control command → ComparisonHandlerRegistry → memoized/non-memoized Stores
→ render metrics + data status Viewbusiness/comparison-rules.tsowns counter, calculation, heavy-data, and memory-data transitions as pure functions.handlers/ComparisonHandlerRegistry.tsxowns both action lanes and the performance-control handlers. The stable lane usesuseCallback; the comparison lane intentionally recreates its handler references inside the same Registry boundary.actions/useComparisonActions.tsexposes stable commands to widgets, whileuseComparisonViewStateremains read-only presentation state.- The old component and handler hooks remain compatibility re-exports/status hooks, so existing links do not reintroduce direct registrations.
Use this recipe when measuring handler identity, registration churn, or the effect of lazy Store reads. Keep the contrast explicit in the Registry and do not let a legacy comparison path become a second architectural standard.
Generic object-context synchronization usecase
The generic object-context factory is a useful usecase when one domain needs object lifecycle commands, a manager-owned runtime, and reactive metadata without coupling views to the manager implementation:
semantic command → ObjectContextHandlerRegistry
→ ObjectContextManager.dispatch → typed Stores → viewcreateObjectContextHookscreates the Action/Store/Manager contracts and keeps its public hooks compatible.handlers/ObjectContextHandlerRegistry.tsxregisters lifecycle, selection, focus, and cleanup actions, then synchronizes metadata into Stores.ObjectContextManagerowns domain lifecycle behavior; its publicdispatchbridge keeps the internalActionRegisterprivate.
Use this recipe for dashboards, editors, or catalogs that manage many typed entities and need independent subscriptions for object lists, selection, focus, and cleanup state.
High-frequency pointer tracking usecase
Mouse tracking is a useful usecase when high-frequency input, derived metrics, and direct DOM feedback must coexist. Keep the event boundary semantic and split the data by update frequency:
pointer event → updatePosition / recordClick → Handler Registry
→ position + movement + activity Stores → metric subscriptionsThe enhanced context-store mouse recipe demonstrates this boundary:
contexts/owns Store, Action, and Ref contracts.- the ViewModel hook computes handler implementations and injects Store access; it does not register actions itself.
handlers/EnhancedContextStoreHandlerRegistry.tsxis the only registration point for the five mouse actions.- high-frequency position and movement data stay in separate stores from derived metrics and performance counters.
- the View subscribes to the metrics it renders and leaves DOM/canvas control to the Ref-aware control hooks.
The lower-level context-store variant is useful when the example should make the Store boundaries visible:
context-store-pattern/context/MouseEventsContext.tsxdefines the typed Action and Store contracts plus derived calculation helpers.context-store-pattern/handlers/MouseEventsHandlerRegistry.tsxowns the six event registrations and updates the split stores.context-store-pattern/providers/MouseEventsProvider.tsxkeeps Provider composition outside the context definition, so containers only dispatch semantic pointer actions.
Use this recipe when the feature needs observable state without forcing every pointer event through a large page-level render tree. Keep the action payload small, cap retained paths/history, and document which derived metrics are updated eagerly versus throttled.
Abortable filtered search usecase
Search is a useful usecase when query intent, filter transitions, async work, and result selection must remain independently observable:
query/filter command → performSearch → abort-aware Registry → results + metricsThe advanced search recipe applies the same boundaries:
business/owns relevance scoring, filtering, history, and metric updates as deterministic functions.actions/exposesperformSearch, filter commands, andselectResultwithout exposing Store mutation to the View.- the Registry owns the async search lifecycle and checks the pipeline signal before committing results.
- query, filters, results, selection, loading, history, and metrics are separate Store concerns so each consumer subscribes only to what it renders.
Use this recipe when a new query should supersede stale work or when filtering must be measured independently from result rendering. Keep cancellation and error policy in the handler boundary, not in input JSX.
API request gate usecase
API request management is a useful usecase when duplicate-request blocking, rate limiting, simulated transport work, and outcome metrics must remain observable independently:
request command → blocking policy → rate-limit policy → transport
→ outcome Registry → request history + metrics StoresThe API Blocking recipe applies the same boundaries:
business/api-blocking-rules.tsowns rate-window normalization, blocking decisions, request-record transitions, and metric calculations as pure functions.actions/useApiBlockingActions.tsexposesmakeApiCall, configuration, and history commands without exposing raw Store mutation to the View.handlers/ApiBlockingHandlerRegistry.tsxowns the simulated transport, blocking timer, rate-limit counter, and success/blocked/error result actions.apiCalls, gate state, rate-limit state, and metrics remain separate Stores; each View subscribes only to the state it renders.- Every request receives a
callId, so asynchronous outcomes update exactly one pending record instead of matching by endpoint or timestamp.
Use this recipe for double-submit protection, client-side request gates, or small circuit-breaker demonstrations. Keep policy decisions pure, put timers and transport effects in the Registry, and keep the route responsible only for provider composition and feature scope.
Virtualized scroll usecase
Infinite scroll is a useful usecase when high-frequency position updates, virtualized rendering, asynchronous page loading, and DOM animation need clear ownership:
scroll event → position/virtualization commands → Handler Registry
→ scroll/content/metrics Stores → virtualized ViewThe Scroll recipe applies the following boundaries:
business/scroll-rules.tsowns content generation, velocity derivation, virtualization windows, and page-load metric transitions.contexts/ScrollContexts.tsxdefines Action, Store, and Ref contracts; the Ref Context exposes the scroll container without putting the DOM node in a Store.actions/useScrollActions.tsexposes semantic commands such assmoothScrollTo,loadMoreContent, andresetScroll.handlers/ScrollHandlerRegistry.tsxowns handler registration, the async loading lifecycle, requestAnimationFrame animation, and the 60fps auto-scroll loop.- The View subscribes to scroll data, content, loading, virtualization, and metrics while keeping event handlers limited to payload calculation and command dispatch.
Use this recipe for large content surfaces where only the visible window should render. Keep the content window bounded by virtualization, keep DOM feedback in the Ref boundary, and keep page-loading state transitions observable in Stores rather than local component state.
Timing strategy comparison usecase
Throttle comparison is a useful usecase when normal, throttle, debounce, leading-edge, and trailing-edge input strategies must be compared against the same processing and metrics pipeline:
input event → strategy scheduler → processEvent → event log + metrics Store
→ benchmark / auto-test actionsThe Throttle recipe applies these boundaries:
business/throttle-rules.tsowns metric transitions, bounded event history, configuration defaults, and benchmark history as pure functions.actions/useThrottleActions.tsexposesinputEvent, configuration, and test commands; the View never chooses a scheduler or dispatchesprocessEvent.handlers/ThrottleHandlerRegistry.tsxowns the five timing schedulers, timeout cleanup, simulated processing, benchmark loops, and auto-test timer.inputEventis the semantic boundary. It fans out to the five strategy schedulers, whileprocessEventremains the single result/metrics boundary.
Use this recipe when comparing interaction policies or validating a timing configuration. Keep scheduler state in refs owned by the Registry, clean up all timers on unmount, and feed every strategy into the same result transition so the comparison remains meaningful.
Design-system boundary
The recipe is the integration point for Astryx-like conventions:
- neutral canvas and surface roles
- semantic accent and muted selected state
- subtle elevation rather than decorative gradients
- visible focus ring and keyboard-safe controls
- status text paired with a semantic state, never color alone
- controlled/uncontrolled behavior only for intrinsic component interaction
The Live Code Editor's Usecase boundary example is the reference implementation for this profile. It demonstrates the complete path in one browser surface:
Contract → Runtime → Facade → Recipe → Activity / ResultFor a model-driven variant of the same boundary, use the Tool-Calling Web Studio Convention. It adds the tool registry, approval policy, provider-neutral model loop, workspace repository, and preview acknowledgement to the runtime while keeping the facade/view boundary intact.
Verification gates
Every profile implementation should verify:
- domain functions independently
- handler ordering, blocking, abort, and result collection
- facade derivation and command stability
- recipe controlled props, focus, and status states
- browser happy path and invalid path
- no direct runtime import from design-system primitives
Start with: