Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ Per-package detail lives in the GitHub release tagged `<npm-name>@<version>`.

## Unreleased

### Added — subject-keyed panels (render-all, predicate-gated)

Implements the counter-proposal from the cat-factory slice-4 triage (`docs/subject-keyed-zones-triage.md`): **panels**, a named region whose module-contributed entries are selected at render time by a runtime **subject**, gated by per-entry `when(subject)` predicates, ordered, and rendered **all-matching** — the render-all, open-contribution sibling of the pick-one component-pairing helpers. Contributions ride the existing module → slot path; no new registration seam, package, or descriptor change. Deliberately _not_ named "zones" (that word already names two pick-one primitives).

- **`@modular-frontend/core`** — `PanelEntry<TSubject>`, `PanelGroupHandle<TSubject>`, `definePanelGroup(slotKey)` (phantom-typed handle, same convention as `defineJourneyHandle`), and `resolvePanels(entries, subject, opts?)`: a pure read-side projection — duplicate-id validation first (throw by default, `onDuplicate: "first-wins" | "last-wins"` to shadow intentionally; runs before the null-subject guard so a registration bug surfaces even while nothing is selected), null subject → empty, `when(subject)` filter, stable `order` sort. The duplicate-id collapse is one shared implementation with `resolveComponentRegistry`, so the two reductions' stances cannot drift. Re-exported by `@modular-react/core` and `@modular-vue/core`.
- **`@modular-react/react`** — `usePanels` (memoized over the slots context + subject, stable empty identity for uncontributed groups), `<PanelsOutlet>` (render-all; subject injected as a `subject` prop **and** via `PanelSubjectContext`; per-panel error boundary; `empty` / `wrap` props; `subjectKey` folds the subject's identity into each panel's key to force remounts across selections), `usePanelSubject` (throws outside an outlet).
- **`@modular-vue/vue`** (re-exported from `@modular-vue/core`) — the same surface as a `computed`-based composable plus `<PanelsOutlet>` with `#empty` / `#wrap` slots and `provide`-based `usePanelSubject`. `usePanels` accepts `MaybeRefOrGetter` for the group, subject, and `onDuplicate`, and tracks **both** runtime slot sources — the reactive computed and the `recalculateSlots()` signal `Ref` — serving whichever produced the more recent evaluation, so dynamic-slot panels update on either path (matching the React host's behavior).
- **`ModuleErrorBoundary`** (both bindings) — new optional `label` prop (default `"Module"`) naming the failing unit in the notice and console message; `<PanelsOutlet>` passes `"Panel"` so a crashed panel isn't mislabeled a module.
- **Docs** — new [`docs/subject-panels.md`](docs/subject-panels.md) guide (entry shape, resolver semantics, both hosts, the Vue reactivity caveat, `when` vs `dynamicSlots`, `subjectKey`, panels-vs-pairing); the compositions README's sibling-primitive comparison extended three → four; cross-links from `reactive-slots-vue.md` and `remote-capability-manifests.md`; the triage doc gains its Resolution section.
- **Housekeeping (Gap D)** — the Vue family peer ranges (`vue`, `vue-core`, `vue-runtime`, `vue-nuxt`, `vue-journeys`, `vue-compositions`, `vue-testing`) widen to admit `@modular-frontend/core@^0.3.0`, aligning lagging `^0.1.0`-only ranges with the engine version already in the workspace.

### Changed — downstream feedback: types, plugins, and the cancel affordance

A second consumer feedback round (Vue + Nuxt this time), triaged critically — some items were already solved and only needed verification or docs; the rest are focused, backward-compatible changes.
Expand Down
41 changes: 21 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,26 +84,27 @@ For the walkthrough of what the scaffold produces (or how to build the workspace

Conceptual documentation for building apps with the framework. Start with a getting-started guide, then dig into the shell patterns once you want to go beyond the defaults.

| Guide | What it covers |
| ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [Getting started with React Router](docs/getting-started-react-router.md) | Scaffold, tour the generated workspace, add modules and stores, turn on the auth guard. |
| [Getting started with TanStack Router](docs/getting-started-tanstack-router.md) | Same walkthrough for the TSR integration, including the `staticData` type augmentation and `beforeLoad` auth guard. |
| [Getting started with Vue Router](docs/getting-started-vue-router.md) | Manual workspace setup for the Vue 3 + vue-router family — modules, the registry, zones via `meta`, stores, and the `beforeEach` auth guard. |
| [Framework-mode (React Router v7)](docs/framework-mode-react-router.md) | `resolveManifest()` integration with `@react-router/dev/vite` — keep file-based `routes.ts`, `+types/route.ts`, HMR, and SSR. |
| [Framework-mode (TanStack Router & Start)](docs/framework-mode-tanstack-router.md) | `resolveManifest()` integration with `@tanstack/router-plugin` and TanStack Start — keep file-based `routeTree.gen.ts`, typed routes, SSR. |
| [Framework-mode (Nuxt 4)](docs/framework-mode-nuxt.md) | `@modular-vue/nuxt` — graft module routes onto Nuxt's vue-router and install the modular contexts on the Nuxt Vue app, via a Nuxt module or `installModularApp` in your own plugin. |
| [Navigation: typed labels, dynamic hrefs, meta](docs/navigation.md) | `NavigationItem<TLabel, TContext, TMeta>` — typed i18n keys, context-aware `to`, app-owned `meta` for permissions/badges. |
| [Shell Patterns (Fundamentals)](docs/shell-patterns.md) | Multi-zone layouts, command palette, module-to-shell communication, headless modules, optional deps, cross-store coordination. |
| [Shell Patterns for React Router](docs/shell-patterns-react-router.md) | Module route shape, route zones via `handle`, `useRouteData` for non-component metadata, auth guards, public shell routes. |
| [Shell Patterns for TanStack Router](docs/shell-patterns-tanstack-router.md) | Module route shape with `createRoute`/`getParentRoute`, route zones via `staticData`, `useRouteData`, `beforeLoad` auth. |
| [Shell Patterns for Vue Router](docs/shell-patterns-vue-router.md) | Router-owning vs framework mode, module route shape, zones and route data via `meta` (typed through `RouteMeta`), `useRouteData`, `beforeEach` auth. |
| [Workspace Patterns](docs/workspace-patterns.md) | Tabbed workspaces, component-only modules, `useActiveZones`, per-session state via `createScopedStore`. |
| [Sibling modules sharing a screen](docs/sibling-modules-shared-screen.md) | One generic screen (e.g. an integration manager) rendered by several sibling modules with per-module config flowing through typed handle (React Router) or staticData (TanStack Router). |
| [Journeys](packages/journeys/README.md) | Typed multi-module workflows with serializable shared state — entry/exit contracts, branch/complete/abort transitions, pluggable persistence. |
| [Journeys in Vue](docs/journeys-vue.md) | Hosting journeys in Vue — `<JourneyHost>`/`<JourneyOutlet>`, lifecycle rules, the modal-mounted (no-URL) recipe, Pinia-backed persistence + the `Store<T>` Pinia adapter, and Nuxt wiring. |
| [Compositions](packages/compositions/README.md) | Multi-module screen layout — arrange modules (and journeys) into named zones on a single screen, with a per-instance scoped store as the orchestration bus. |
| [Catalog](packages/catalog/README.md) | Build-time discovery portal: harvest every `defineModule` / `defineJourney`, surface owner/domain/tags facets, pre-compute the journey↔module cross-reference graph, ship as static HTML. |
| [Remote Capability Manifests](docs/remote-capability-manifests.md) | Drive slots/navigation from backend JSON — `RemoteModuleManifest`, `mergeRemoteManifests`, validation, SSR, and the single-module pattern. |
| Guide | What it covers |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Getting started with React Router](docs/getting-started-react-router.md) | Scaffold, tour the generated workspace, add modules and stores, turn on the auth guard. |
| [Getting started with TanStack Router](docs/getting-started-tanstack-router.md) | Same walkthrough for the TSR integration, including the `staticData` type augmentation and `beforeLoad` auth guard. |
| [Getting started with Vue Router](docs/getting-started-vue-router.md) | Manual workspace setup for the Vue 3 + vue-router family — modules, the registry, zones via `meta`, stores, and the `beforeEach` auth guard. |
| [Framework-mode (React Router v7)](docs/framework-mode-react-router.md) | `resolveManifest()` integration with `@react-router/dev/vite` — keep file-based `routes.ts`, `+types/route.ts`, HMR, and SSR. |
| [Framework-mode (TanStack Router & Start)](docs/framework-mode-tanstack-router.md) | `resolveManifest()` integration with `@tanstack/router-plugin` and TanStack Start — keep file-based `routeTree.gen.ts`, typed routes, SSR. |
| [Framework-mode (Nuxt 4)](docs/framework-mode-nuxt.md) | `@modular-vue/nuxt` — graft module routes onto Nuxt's vue-router and install the modular contexts on the Nuxt Vue app, via a Nuxt module or `installModularApp` in your own plugin. |
| [Navigation: typed labels, dynamic hrefs, meta](docs/navigation.md) | `NavigationItem<TLabel, TContext, TMeta>` — typed i18n keys, context-aware `to`, app-owned `meta` for permissions/badges. |
| [Shell Patterns (Fundamentals)](docs/shell-patterns.md) | Multi-zone layouts, command palette, module-to-shell communication, headless modules, optional deps, cross-store coordination. |
| [Shell Patterns for React Router](docs/shell-patterns-react-router.md) | Module route shape, route zones via `handle`, `useRouteData` for non-component metadata, auth guards, public shell routes. |
| [Shell Patterns for TanStack Router](docs/shell-patterns-tanstack-router.md) | Module route shape with `createRoute`/`getParentRoute`, route zones via `staticData`, `useRouteData`, `beforeLoad` auth. |
| [Shell Patterns for Vue Router](docs/shell-patterns-vue-router.md) | Router-owning vs framework mode, module route shape, zones and route data via `meta` (typed through `RouteMeta`), `useRouteData`, `beforeEach` auth. |
| [Workspace Patterns](docs/workspace-patterns.md) | Tabbed workspaces, component-only modules, `useActiveZones`, per-session state via `createScopedStore`. |
| [Sibling modules sharing a screen](docs/sibling-modules-shared-screen.md) | One generic screen (e.g. an integration manager) rendered by several sibling modules with per-module config flowing through typed handle (React Router) or staticData (TanStack Router). |
| [Journeys](packages/journeys/README.md) | Typed multi-module workflows with serializable shared state — entry/exit contracts, branch/complete/abort transitions, pluggable persistence. |
| [Journeys in Vue](docs/journeys-vue.md) | Hosting journeys in Vue — `<JourneyHost>`/`<JourneyOutlet>`, lifecycle rules, the modal-mounted (no-URL) recipe, Pinia-backed persistence + the `Store<T>` Pinia adapter, and Nuxt wiring. |
| [Compositions](packages/compositions/README.md) | Multi-module screen layout — arrange modules (and journeys) into named zones on a single screen, with a per-instance scoped store as the orchestration bus. |
| [Subject-keyed panels](docs/subject-panels.md) | A named region whose module-contributed panels are selected by a runtime **subject**, gated by per-panel `when(subject)`, ordered, and rendered **all-matching** — the open-contribution, render-all sibling of component pairing. `definePanelGroup` · `resolvePanels` · `usePanels` / `<PanelsOutlet>` (React + Vue). |
| [Catalog](packages/catalog/README.md) | Build-time discovery portal: harvest every `defineModule` / `defineJourney`, surface owner/domain/tags facets, pre-compute the journey↔module cross-reference graph, ship as static HTML. |
| [Remote Capability Manifests](docs/remote-capability-manifests.md) | Drive slots/navigation from backend JSON — `RemoteModuleManifest`, `mergeRemoteManifests`, validation, SSR, and the single-module pattern. |

## What the code looks like

Expand Down
3 changes: 3 additions & 0 deletions docs/reactive-slots-vue.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,6 @@ The signal path is unchanged: `useSlots()` returns the `Ref`, and
- [Navigation](navigation.md) — the navigation manifest and item shape.
- [Remote capability manifests](remote-capability-manifests.md) — backend-driven
slot/nav contributions, which compose with either path.
- [Subject-keyed panels](subject-panels.md) — `usePanels` / `<PanelsOutlet>`
rely on the same reactive-source boundary: a subject-keyed `when(subject)`
predicate only re-runs when the subject is reactive Vue state.
2 changes: 2 additions & 0 deletions docs/remote-capability-manifests.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ Sometimes a manifest entry needs to select **which component** renders it — a

This is the sanctioned shape of "backend data lights up a locally-installed view." It is deliberately narrow: read [Why this isn't the anti-pattern](#why-this-isnt-the-anti-pattern) below before reaching for it, because a sloppy version — a shell-owned `string → component` map used to dodge shipping code — is exactly the [anti-pattern](#anti-patterns-to-avoid) this guide otherwise warns against.

> **Pairing is pick-one; panels are render-all.** `resolveComponentRegistry` / `pairById` select **one** component per id. When a region should instead show **every** contribution that applies to a runtime selection — an inspector rail where several detail panels light up for the selected item, keyed by a `when(subject)` predicate rather than an id — that is the render-all sibling, [subject-keyed panels](subject-panels.md). Same slot substrate, different reduction.

### The shape

**1. Components ship as code and register through the normal module → slot path.** A first-party module contributes `ComponentEntry[]` to a component-registry slot; a consumer deployment contributes its own entries to the _same_ slot via the app's registration seam — no layer fork.
Expand Down
Loading
Loading