Console navigation slot contract (v1)
The kernel owns the mounted console shell. A module registers both its
generated typed route list and its navigation metadata in the declared slots;
it does not create a ShellRoute or edit the console host/bootstrap.
The ready shell keeps its actual typed home navigator mounted. Pre-contract
URL-only routes still work, and an existing receiving-slot wrapper can compose
beneath the shell until the paired co-bricks migration removes it.
Module declaration
The module's public barrel exports its routes and a
List<ConsoleNavigationItem> get consoleReportsNavigation getter. The DTO is
exported from package:core/core.dart, so modules do not depend on the router
host or its presentation implementation.
List<ConsoleNavigationItem> get consoleReportsNavigation => [
ConsoleNavigationItem(
id: 'reports',
route: const ReportsRoute(), // @TypedGoRoute + generated mixin
icon: LucideIcons.chartColumn,
order: 100,
translationKey: 'reports.navigation.title',
label: (context) => featureTranslations(
context, feature_i18n.AppLocaleUtils.instance,
).navigation.title,
requiredPermissions: const {'reports.read'},
),
];
| Field | Contract |
|---|---|
id |
Stable namespaced lowercase identifier ([a-z][a-z0-9_.-]*), unique across modules; home is reserved |
route |
Concrete generated GoRouteData; its location selects the item and its go(context) performs navigation. No string callback or URL-only destination |
icon |
CoUI IconName (e.g. LucideIcons.chartColumn) |
iconData |
Feature-owned IconSvgData for an additional icon; optional for the bundled chrome set |
order |
Ascending integer; equal orders sort by id, independent of installation order |
translationKey |
Feature-qualified key documenting the owning catalog; nonempty |
label |
Resolver for that key using the shell locale, rebuilt on locale changes |
requiredPermissions |
Immutable capability set; all are required, empty means public |
The label belongs to feature/<d>/console/lib/src/i18n/ (AD-10), not to
shared/i10n or the kernel chrome catalog. All eleven supported locales belong
to the feature. A locale change preserves the router/module state.
The kernel bundles house, layout-dashboard, triangle-alert, chart-column and
shield SVGs. Additional icons supply iconData from feature assets or register
their bundled SVG before router construction. An unbundled icon fails before
mounting instead of making an off-origin Iconify request. Chrome, Widgetbook
and goldens therefore render the same offline SVG data.
Host slot insertion
In feature/console_router/console/lib/src/route/console_routes.dart, import the
module public barrel once and insert:
static List<RouteBase> get routes => [
$consoleStatusRoute,
...consoleReportsRoutes,
// ๐ง AUTO-REGISTER: ์ ๋ผ์ฐํธ๋ฅผ ์ฌ๊ธฐ์ ์ถ๊ฐํ์ธ์
];
static List<ConsoleNavigationItem> get navigation => [
...consoleReportsNavigation,
// ๐ง AUTO-REGISTER(console-navigation): ๋ชจ๋ ๋ด๋น๊ฒ์ด์
์ ์ฌ๊ธฐ์ ์ถ๊ฐํ์ธ์
];
The console-routes and console-navigation slots are declared in slots.yaml.
Registrars check both markers before host writes, add the module dependency to
the router pubspec, preserve the feature-layout marker and keep installation
idempotent. bundle.json and host-integration.json retain their separate AD-1
roles; navigation is a module-owned typed UI contract, not a third assembly
declaration. Duplicate ids/paths and reserved roots fail before mounting.
Permission adapter and four states
The host may pass a ConsoleNavigationController to createConsoleRouter and
owns its disposal. It starts at ConsoleNavigationLoading while resolving
auth, publishes ConsoleNavigationReady(permissions: grants) after success or
ConsoleNavigationError after failure. onRetry is the host's retry callback.
Without an adapter, the kernel grants no capabilities, so only public modules
are accessible. Grants are snapshotted rather than retaining a mutable set.
The same signal feeds the visible menu and the router redirect. Restricted
direct links and permission revocations return to typed ConsoleStatusRoute
before displaying a denied page. Loading/error deny module access. A ready
registry with no visible modules derives the empty state; no separate empty/data
flag can drift from permissions. Server/API authorization remains the host and
feature endpoint contract, independent of this navigation adapter.
| State | Actual mounted UI |
|---|---|
| Loading | Localized loading indicator, no selectable module |
| Data | Ordered destinations and a module-selection guide at normal /console boot |
| Empty | CoUI EmptyState with install/access guidance; distinguishes zero registration from zero access |
| Error | Localized failure state and host-wired retry, no stale granted menu |
Layout and public selectors
The shell uses LayoutBuilder's available width. Expanded side menu anatomy is
256 ร scaling + 24 ร scaling + 640 minimum page width; otherwise the same
destinations become a vertically scrollable top menu. Both menus allow labels
to wrap and keep the page reachable; there is no text-scaling clamp.
Every actual CoUI button has a 48dp ร scaling minimum width/height and grows
with its content. The button key and its public semantics identifier are both
console.nav.<id>; semantics expose the platform role, label, selection and tap.
console.nav.home returns to /console. Layout keys are console.nav.rail and
console.nav.menu; state keys are console.loading/data/empty/error.
Selectors live on the mounted controls, with no offstage instrumentation.
The page navigator has its own semantic container: route BlockSemantics
cannot suppress earlier-painted sibling navigation. Each public link node
exposes its label, selection and actionable typed tap together, matching the
fixed CoUI Link's button: true, link: true contract on native platforms. A
single web adapter uses button: true, link: false, exposing an actionable
button (select by AX name). The fixed web engine prioritizes link over
button: without a URL it is generic, and with a URL it becomes a native anchor
that bypasses the typed action. Native keeps both flags. The declared route
remains item.route.location, and onPressed/typed
item.route.go owns navigation. No native <a> is generated: its default load
can bypass the typed callback, change only the URL or override hash/base routing.
Only the navigation area's automatic scrollbar chrome is disabled: its full-box
percentage semantics sibling can cover links in the release DOM. The underlying
scrollable keeps its wheel, gesture and accessibility scroll actions.
The public node is focusable and forwards its focus action to the same CoUI
button FocusNode, preserving genuine Tab traversal and Enter/Space activation.
The platform role mapping is temporary until coco-de/coui#5374 is resolved.
Verification ownership
- Feature unit/widget/BDD:
feature/console_router/console/test/src/; normal boot, typed navigation, permission grants/revocation/direct-link rejection, locale persistence, deterministic ordering and conflicts. testScaleInvariant: four default combinations (scaling 1.25 ร textScaler 1/1.15/1.3/2), portrait/landscape menu, desktop rail and narrow embedded host. Each checks overflow, silent clipping and a real near-edge pointer tap.- Feature goldens (CI renderer):
console/test/golden/, all four states ร three viewports ร light/dark ร the same four text scales; capture and compare are separate gates. - Actual host journey:
app/console/test/console_slot_navigation_test.dartmountsConsoleApp, boots at/consoleand selects a typed probe module. - Public release browser regression:
app/console/test/release/mounts the same production host with the feature-owned typed fixture. Pages CI runstool/pages/smoke/console_navigation.mjsagainst the release build: desktop and narrow public-name button activation, real DOM hit targets, near-edge pointers and Tab traversal followed by Enter/Space. Each asserts the mounted typed page. - Feature showcase:
feature/console_router/widgetbook, one state dropdown and an actual router fixture;app/app_widgetbookonly assembles its fragment. - Paired bricks/co-bricks changes register console_module/workflow metadata, remove workflow-owned console chrome and prove both install orders in fresh receiving projects. Mounted-host navigation, public release semantics and declared-route coverage are reported as separate evidence.