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