Mosaik

🧠 Composable, Scalable, and Fully Hybrid

✅ TL;DR — Mosaik’s UI Composition

  • Slots → define where things render (flexible layout points).
  • Components → handle how things look (purely visual, themed, tiny).
  • Blocks → handle what they do (business logic + orchestration).
  • Modules → bundle related blocks into plug-and-play feature sets.
  • Services → manage high-level systems and app-wide APIs.

Result: Clean separation of structure, logic, style, and orchestration — simple, reusable, testable UIs that stay flexible as you grow.


🎯 Slots

Slots are “mounting points” that any child UI element can target.
Think of them as named insertion points:

  • A Desktop might expose header, footer, sidebar, and content slots.
  • A Navigation block can render itself into the sidebar slot automatically.
  • It doesn’t matter which theme or component you mount — if it targets the slot, it appears there.
  • Changing layout or business logic is easy — your building blocks stay the same.

Why it matters:
Slots decouple structure from content. You don’t hardcode where things go — you declare where they can go.


❓ Why Render Slots on the Server?

In many slot systems (like in React or Vue), slot contents are defined at runtime via props or context. But that breaks SSR — because the server has no idea what’s going to be inserted where.

To support server-rendered slots, we must know all slot contents ahead of time.

That’s why in Mosaik, we precompute slots on the server before rendering layouts or blocks.


✅ Solution: getDesktopSlots and Server-Side Slot Definitions

We define slots via slot definition functions — e.g. getDesktopSlots().
These return a plain object like:

{
  sidebar: <Navigation />,
  header: <AppHeader />,
  content: <MainView />,
}

These definitions are resolved on the server, so slots can be rendered fully server-side — but remain just as dynamic and composable as client-rendered ones.

This powers our entire layout composition system while keeping it SSR-first.


⚙️ Components

Components are your low-level, themed, purely visual pieces.
They’re single-purpose, tiny, and easy to reason about:

  • Example: SidebarFooter might just be a <div> with padding and children.
  • Each themed version lives in themes/{themeName}/<ComponentName>.tsx and is dynamically imported, so unused themes are tree-shakable.
  • Most components wrap simple Tailwind markup.

Key:
Keep components purely visual, no business logic. One concern, one file.

⚠️ Warning:
Do not render blocks or modules in themes. If you need to render them, pass them as children (this shifts the responsibility to the parent and keeps the themed component pure). Themed components may render other themed components. They just need to be pure (no effects, no async) so they can run on both, the server and client. Rendering async components from a client context throws an error. A component must be isomorphic.


🧩 Blocks

Blocks wire up business logic to your UI.
They use themed components internally — but add orchestration:

  • Example: A Navigation block figures out which routes to render.
  • The Navigation block mounts into the sidebar slot of your Desktop.
  • If you swap the theme, the look changes — but the logic stays the same.
  • If you tweak business logic, you update the block — not every component.

Key:
Blocks = logic + orchestration. Components = visual only.
One block can wrap many themed components — all still slot-aware.


🗂️ Modules

Modules bundle multiple blocks into reusable, pluggable units:

  • Example: An ItemList module that wires up filtering, pagination, and actions.
  • Exposes clear actions and slots for subcomponents.
  • Keeps related logic together, but still composable with other modules.

Key:
Modules solve bigger problems than blocks alone — they’re the “feature” layer.


💡 Hybrid Components

Sometimes you need to render something on the server but hydrate with client-side logic — e.g., to access a context, handle interactivity, or sync state.

We solve this using hybrid wrappers:

🔧 Server Wrapper

// @/blocks/hybrid/Sidebar.tsx
export const Sidebar = createHybridComponent("Sidebar", SidebarClient);

🧠 Client Wrapper

// @/blocks/hybrid/SidebarClient.tsx
export const SidebarClient = createHydratableComponent((props) => {
  const SidebarContent = useThemedComponent("SidebarContent");
  const { state } = useAppState();
  const [hydrated, setHydrated] = useState(false);

  useEffect(() => setHydrated(true), []);

  if (!hydrated || !SidebarContent) return props.Component;
  return <SidebarContent {...props} state={state} />;
});

These use simple helpers to keep things ergonomic and consistent:

  • createHybridComponent(name, ClientCmp) → resolves themed server component and passes it to the client for hydration.
  • createHydratableComponent(Component) → wraps a client component that hydrates only after mount to avoid flicker.

Why it matters:
This lets you prerender anything for SEO, but still hydrate custom client logic exactly when and where you need it.


🛰️ Services

Services are the top-level orchestration layer.
They expose APIs, state, and slots to your app’s highest level:

  • Example: A DesktopService handles window management — opening, minimizing, docking.
  • Multiple modules and blocks can plug into a service.
  • Provides clear Actions (OPEN_WINDOW, DOCK_WINDOW), state, and context.

🔁 Actions and Effects

Mosaik handles state like Redux or Elm — with immutable global state, actions to trigger changes, and effects to handle side-effects.

  • Actions describe what happened (pure data).
  • Effects describe what should happen (side-effects like navigation, fetching).

We use action generators and effect generators to keep intent readable.

We also render declarative effects with <Effects> and effect components like:

<AutoCollapseSidebarOnMobile />

This effect will automatically trigger COLLAPSE_SIDEBAR on small screens. It’s colocated with the Sidebar visually — and declarative.

Key:

  • Services coordinate systems
  • Modules coordinate features
  • Blocks coordinate logic for one unit
  • Components handle presentation
  • Actions modify state
  • Effects handle side effects
v0.0.1