Skip to content

Make Remix host-agnostic without owning navigation or a Material shell #82

Description

@leoafarias

Problem

Remix is intended to be a composable design system, but the dashboard, examples, and much of the test harness currently make MaterialApp + Scaffold look like a runtime requirement. That creates two problems:

  1. Consumers cannot tell which infrastructure Remix actually requires when it is embedded in a MaterialApp, CupertinoApp, WidgetsApp, router-based application, or another design system.
  2. App-shell behavior can accidentally mask overlay bugs. The dashboard toast inserts a raw OverlayEntry; that entry is built under the target Overlay, not under its trigger's subtree, so it loses the route-local DefaultTextStyle. Under MaterialApp, the remaining app-level style is intentionally the red/yellow warning style, which caused the observed toast regression.

The current implementation confirms that Remix core does not use MaterialApp, Scaffold, Drawer, ScaffoldMessenger, or ThemeData. Those are dashboard concerns today. However, packages/remix/lib still has 28 material.dart imports for convenience constants/default icons and enables uses-material-design, while packages/dashboard/lib has 23 Material imports.

Relevant code:

Findings

Navigation and overlays are separate requirements

WidgetsApp can run without a Navigator when it is given only a builder. In that configuration, the application must provide an Overlay if it uses portal-based UI. Scaffold does not provide that overlay; a Navigator commonly does.

The Remix overlay families currently have two distinct contracts:

  • Menu, select, popover, and tooltip flow through Naked UI's RawMenuAnchor, which uses Flutter OverlayPortal. OverlayPortal preserves all inherited widgets from the trigger subtree and needs an Overlay, but not a Navigator or Scaffold.
  • showRemixDialog flows through showNakedDialog and pushes a RawDialogRoute; it intentionally requires a caller-owned Navigator. Naked UI captures Flutter InheritedThemes, while Remix also replays MixScope and FortalTheme because those scopes are not InheritedThemes.

A temporary Flutter 3.44 widget test validated that WidgetsApp.builder + FortalScope + a standalone Overlay, with no Navigator, can open RemixPopover and retain both Fortal and Mix scopes. The same test confirmed that route dialogs have no navigator in that configuration and that a raw OverlayEntry sees the host-level text style rather than a trigger-local one.

Flutter references:

Direction

Candidate Decision Reason
Required RemixApp Do not add Remix must not own or replace an application's navigator/router, localization, shortcuts, restoration, or platform app configuration.
Optional RemixApp convenience wrapper Defer It would largely mirror the broad WidgetsApp API before Remix has a second concrete use case. Consumers can compose Remix through an app builder or around a subtree.
General RemixScaffold Do not add Scaffold combines unrelated Material page concerns. Remix does not yet have a generalized app bar, drawer, FAB, snackbar, or bottom-sheet contract, and none is required for portals.
Focused RemixOverlayHost (proposed name) Add A small opt-in host solves the concrete no-Navigator case by owning only an Overlay, without introducing routes or back-stack behavior.

FortalScope remains the navigation-independent design-system root. It must work around an entire app, inside an existing app's builder, or around an embedded subtree.

Proposed navigation-free usage (API sketch, not current code):

WidgetsApp(
  color: const Color(0xFF000000),
  builder: (context, child) => FortalScope(
    child: RemixOverlayHost(
      child: MyEmbeddedExperience(),
    ),
  ),
)

Applications that already provide an overlay through their navigator/router do not need this host. Route-based APIs such as showRemixDialog continue to use the caller's navigator; Remix must not silently create one.

Scope

  • Add a focused, opt-in overlay host (name subject to API review) that:
    • owns an Overlay and base content entry;
    • does not create a Navigator, Router, routes, or back-button behavior;
    • updates and disposes its entry correctly;
    • supports nesting intentionally and documents nearest/root-overlay behavior.
  • Replace the dashboard toast's raw OverlayEntry approach with OverlayPortal or the shared host/layer mechanism so live DefaultTextStyle, Fortal, Mix, directionality, localization, and accessibility context are preserved.
  • Migrate the dashboard app shell from MaterialApp to WidgetsApp. The dashboard may explicitly own a navigator because it demonstrates route dialogs; that navigator is an application choice, not a Remix requirement.
  • Replace the dashboard Scaffold/Drawer with a dashboard-private responsive shell. Do not promote it to Remix until there is a second reusable page-shell use case and an explicit API contract.
  • Remove the remaining Material-only constants/defaults from Remix core:
    • replace Colors.* with tokens or framework Color constants;
    • replace Material Icons.* defaults with Remix-owned icons/paths;
    • switch imports to widgets.dart, painting.dart, foundation.dart, services.dart, or dart:ui as appropriate;
    • remove uses-material-design when the icon-font dependency is gone.
  • Update examples and test helpers so Material is an interoperability example, not the only demonstrated host.
  • Document the host capability matrix: plain subtree, overlay-only host, caller-provided navigator, and router-based app.

Acceptance criteria

  • Remix widgets render inside existing Material, Cupertino, Widgets, router, and embedded-subtree hosts without Remix taking ownership of navigation.
  • A WidgetsApp.builder test with the opt-in overlay host contains no Navigator and verifies menu/select/popover/tooltip behavior plus live Fortal/Mix inheritance.
  • Dialog tests run with a caller-provided Navigator under WidgetsApp, and showRemixDialog clearly documents/asserts that requirement.
  • Toast/global transient UI no longer displays Flutter's fallback warning text style and updates when the active Fortal theme changes.
  • Dashboard responsive sidebar behavior, modal dismissal, focus restoration, Escape/back handling, semantics, and compact/wide layouts remain covered without Scaffold or Drawer.
  • packages/remix/lib has no package:flutter/material.dart imports and the Remix package no longer enables uses-material-design.
  • Public documentation does not present MaterialApp/Scaffold as a Remix prerequisite.
  • No public RemixApp or catch-all RemixScaffold is introduced as part of this work.

Non-goals

  • Defining a Remix router or navigation stack.
  • Reimplementing route dialogs on an overlay-only host.
  • Adding generalized app bar, drawer, FAB, snackbar, or bottom-sheet APIs before their component contracts exist.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions