RFC-0010: Regionalized "Rail & Drawer" Navigation Architecture
- Status: Implemented
- Author(s): @revant.one
- Created: 2026-04-14
- Supersedes: RFC-0007: ERP Sidebar Architecture
Context
As Framework M moves towards a productized multi-app platform, the previous navigation models were limited by their flat structure and heavy focus on DocTypes. High-density platform applications require a navigation system that can handle hundreds of diverse resources (Reports, Pages, Kiosks) while remaining clean and extensible.
Proposed Architecture
1. The Physical Model: "Rail & Drawer"
Inspired by modern IDEs and high-density SaaS (e.g., Slack, VS Code), we transition from a single sidebar to a dual-layered navigation:
- Primary Rail (The Switcher): A thin leftmost vertical bar containing App Icons (WMS, Finance, Studio, HR). Clicking an icon switches the Contextual Drawer.
- Contextual Drawer (The Tree): A standard-width navigation drawer that displays a hierarchical resource tree for the active App.
2. The Resource Paradigm
Navigation nodes are no longer strictly "DocTypes". The Navigation Manifest supports diverse Actionable Resources:
| Type | Description | Example |
|---|---|---|
DocType | Standard List/Form views | Stock Entry |
Page | Custom React pages | Picking Dashboard |
Report | Grid/Chart views | Inventory Aging |
Kiosk | Simplified terminal UIs | Quick Pick Terminal |
Link | Internal deep-links or external tools (with queries) | /wms/stock?item={{ id }} |
[!TIP] Dynamic Interpolation:
Linkroutes support template placeholders (e.g.,{{ user_id }}or{{ branch_code }}). These are resolved by the Fidelity Port using the activeUserContextor application state to ensure links remain contextual rather than hardcoded.
3. Dual-Layer Contract (Ports & Adapters)
To maintain the "No-Cliff" promise, the navigation is governed by two distinct contracts:
A. The Context Port (Python)
The Logical Source of Truth. This port resolves what the user is entitled to see and where they should go.
- Protocol:
NavigationProtocol. - Contract:
get_navigation_manifest(user_context: UserContext) -> NavigationManifest. - Resolution Hierarchy:
- Entitlement Check: Query the
FeatureEntitlementProtocolto see which apps are active for the tenant. - Rail Discovery (Apps): Query only entitled apps' implementations of
NavigationProtocol. - Policy Filtering: Filter nodes via the
NavigationPolicyEngine(RBAC/ABAC). - Regional Augmentation: Apply decorators from
NavigationDecoratorRegistry. - User Personalization: Final merge with user-pinned or "Frequent" nodes.
- Entitlement Check: Query the
B. The Fidelity Port (React)
The Physical Execution Layer. This port resolves how the manifest is rendered to the user.
- Adapter: Standard
<Navigation />component provided byframework-m-ui. - Rendering Logic: The UI consumes the
NavigationManifestJSON. It maps resource types (DocType, Page, Report) to specific visual treatments. - Escapability: Advanced apps can provide their own React
FidelityAdapterto override the standard navigation rendering while still consuming the systematic Python manifest.
4. Extensibility via Metadata Decorators
Custom apps can augment productized navigation without editing the original codebase:
- Registry:
NavigationDecoratorRegistry. - Mechanism: A custom app can register a decorator that targets a specific
AppID. - Example: An "India Compliance" app adds a
GST Returnspage to theFinanceApp's navigation manifest at runtime.
# Imperative Registration Example
def add_gst_module(manifest: NavigationManifest):
manifest.add_section("Compliance").add_node(type="Page", label="GST Portal")
NavigationDecoratorRegistry.register(target_app="finance", decorator=add_gst_module)
5. Progressive Decomposition (MFE Survival)
To adhere to the progressive-decomposition.md principles (Part 13), the navigation architecture supports architectural decoupling:
- Adapter-Driven Discovery: The
NavigationProtocolremains agnostic to where metadata comes from. In decomposed environments, a specialized RemoteDiscoveryAdapter is injected to fetch manifests fromFRAMEWORK_M_MFE_REMOTESover the network. - Logical Route Stability: All navigation nodes must use logical route strings (e.g.,
/wms/inventory) rather than hardcoded component references. This ensures the Core remains functional regardless of which Adapter (Local vs. Remote) resolves the UI. - Auto-Proxying: Connectivity issues are handled at the Adapter level (triggering
FRAMEWORK_M_MFE_PROXY) rather than leaking into the navigation logic.
6. Policy-Driven Resolution (The "Conditional" Layer)
To handle the challenge of "what to show" conditionally (similar to Landing Resolution), we implement a policy-driven engine that sits between the Manifest and the UI:
6.1 The Visibility Policy
Navigation nodes can declare visibility requirements using a schema that the NavigationProtocol resolves:
{
"label": "GST Returns",
"route": "/finance/gst",
"visibility_policy": {
"roles": ["Accounts Manager"],
"attributes": { "country": "India" },
"feature_flag": "compliance_enabled"
}
}
6.2 Post-Login Handshake
Similar to the resolve_landing API call, the frontend can fetch a "Resolved Navigation" post-authentication. This ensures that the UI doesn't just show a generic navigation and then "flicker" nodes away as permissions load.
- Frontend Cache: The resolved manifest is stored in the App State (Redux/Zustand).
- Hard Refresh: Manifests are re-validated on page refresh or context switch (e.g., switching companies).
6.3 Contextual Auto-Switching
To reduce cognitive load, the navigation should automatically respond to the user's current route:
- Route Tracking: If the user navigates to
/wms/picking, thePrimary Railshould automatically highlight theWMSicon. - Drawer Opening: If the drawer is collapsed, it should expand to reveal the active node's parent section.
7. SaaS Entitlements (The Control Plane)
To support multi-tenant SaaS environments, we add an Entitlement Layer that governs the visibility of entire feature sets before user-level roles are checked.
7.1 The Entitlement Protocol
The SaaS platform provider implements the FeatureEntitlementProtocol:
class FeatureEntitlementProtocol(Protocol):
def is_feature_enabled(self, tenant_id: str, feature_id: str) -> bool:
"""Checks if the tenant's subscription plan allows this feature."""
...
7.2 Entitlement-Aware Nodes
Navigation nodes can optionally declare a feature_id. If the entitlement check fails for that ID, the node (and its children) are automatically pruned from the manifest at the Context Port level.
{
"label": "Advanced Forecasting",
"route": "/finance/forecast",
"feature_id": "finance_ai_pack",
"visibility_policy": { "roles": ["Finance Manager"] }
}
8. Interactive Extensions (Live State & Intents)
To support high-engagement applications (Chat, Fitness, Real-time monitoring), navigation nodes transition from static links to Dynamic Interaction Points.
8.1 Live Badges & Status
Navigation nodes can declare a badge property containing a logical path to a server-side query or a real-time status stream.
- Example:
badge: "wms.queries.get_pending_picks" - Behavior: The Fidelity Port (UI) resolves this path and renders a badge (e.g., "12") or status indicator (e.g., "● Active") next to the node label. Updates are pushed in real-time via the NATS-backed WebSocket stream defined in ADR-0003.
8.2 Hardware & OS Intents
Nodes can be mapped to specialized hardware inputs or OS-level voice commands.
hardware_intent: Maps a node to physical buttons (e.g.,crown_click,sos_button,f1_key).voice_alias: A list of keywords for voice-activated navigation ("Hey Siri, open Store Inventory").
8.3 Interaction Hints
Hints provided to the Fidelity Port to optimize rendering for specialized devices:
interaction_mode:touch_optimized(Kiosks),rotary_scrollable(Watches),dpad_navigable(TVs).
Implementation Strategy
- Phase 1: Update
NavigationManifestschema to includebadge,hardware_intent, andinteraction_mode. - Phase 2: Implement the
FeatureEntitlementProtocolinframework-m-core. - Phase 3: Update the
NavigationPolicyEngineto enforce hierarchy: Entitlement -> Policy -> Decoration. - Phase 4: Implement the Primary Rail & Contextual Drawer components in
framework-m-ui.
Unresolved Questions
- Mobile UX: Should the Rail become a Bottom Tab Bar on mobile?
- State Persistence: Should the "Last Active App" be stored in User Preferences or LocalStorage?
- Server-Side Rendering: How does policy resolution affect initial HTML generation for SEO/speed?