Skip to content

refactor(core): extract framework-neutral @modular-frontend/core - #54

Merged
kibertoad merged 3 commits into
mainfrom
feat/frontend-core-extraction
Jul 5, 2026
Merged

refactor(core): extract framework-neutral @modular-frontend/core#54
kibertoad merged 3 commits into
mainfrom
feat/frontend-core-extraction

Conversation

@kibertoad

@kibertoad kibertoad commented Jul 5, 2026

Copy link
Copy Markdown
Owner

First step of the Vue-support initiative: split the framework-neutral guts of @modular-react/core into a new @modular-frontend/core package so a future Vue binding can reuse them.

What changed

  • New packages/frontend-core (@modular-frontend/core): all the current core logic and types, with no UI-framework dependency. The only React coupling was type-level (React.ComponentType / React.ReactNode); it becomes a two-line neutral seam (UiComponent / UiNode) in ui-types.ts. @types/react is dropped from the package.
  • @modular-react/core becomes a thin facade that re-exports the neutral surface (export * from "@modular-frontend/core"). All 11 existing consumers keep importing from @modular-react/core unchanged; name and version are unchanged.
  • Runtime error prefixes and package-name doc comments in the moved files updated to @modular-frontend/core.
  • Adds a project CLAUDE.md documenting the repo-wide oxfmt workflow.

The UiComponent / UiNode seam

The neutral core never renders, calls, or inspects a component (defineModule is identity; validation only checks id / requires / entry-exit shape), so components are carried as opaque values. Instead of a threaded generic, two aliases capture the seam:

export type UiComponent<P = any> = ((props: P) => any) | (new (props: P) => any);
export type UiNode = any;

UiComponent is "callable or constructable with props P", which admits React's full ComponentType (function and class components) and stays usable as a JSX element type, while keeping function-component props checked against ModuleEntryProps. UiNode is loose (any) so a binding can pass nodes straight into its framework's render slots (e.g. a React <Suspense fallback>). A future @modular-vue/core narrows both aliases to Vue's types.

Verification

  • Full workspace typecheck (108 tasks) and build (31 tasks) pass.
  • The neutral package's 270 tests pass.
  • The only failing test is the pre-existing Windows EPERM flake in @tanstack-react-modules/cli (.test-output cleanup), unrelated to this change.

Summary by CodeRabbit

  • New Features

    • Added a new framework-neutral core package with shared UI, navigation, validation, store, and journey APIs.
    • Added build and type-support setup for the new package.
  • Documentation

    • Added and refreshed documentation for package usage, conventions, and architecture.
    • Reworked several docs tables for consistency and readability.
  • Bug Fixes

    • Updated user-facing error and warning messages to use the new package name consistently.

First step of the Vue-support initiative: split the framework-neutral guts
of @modular-react/core into a new @modular-frontend/core package so a future
Vue binding can reuse them.

- New packages/frontend-core (@modular-frontend/core): all the current core
  logic and types, with no UI-framework dependency. React's ComponentType /
  ReactNode references become a two-line neutral seam (UiComponent / UiNode)
  in ui-types.ts. @types/react is dropped from the package.
- @modular-react/core becomes a thin facade that re-exports the neutral
  surface (export * from "@modular-frontend/core"), so all existing consumers
  keep importing from @modular-react/core unchanged. Name and version are
  unchanged.
- UiComponent is "callable or constructable with props P", which admits
  React's full ComponentType (function and class) and stays usable as a JSX
  element type; a Vue binding can narrow it. UiNode is loose (any) so bindings
  can pass nodes straight into their framework's render slots.
- Runtime error prefixes and package-name doc comments in the moved files
  updated to @modular-frontend/core.
- Add project CLAUDE.md documenting the repo-wide oxfmt workflow.

Full workspace typecheck (108 tasks) and build (31 tasks) pass; the neutral
package's 270 tests pass. The only failing test is the pre-existing Windows
EPERM flake in @tanstack-react-modules/cli, unrelated to this change.
@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kibertoad, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a94ca74f-78e0-4ee5-b527-6ae977cb60ee

📥 Commits

Reviewing files that changed from the base of the PR and between 0eee88b and f1ffe3d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • CLAUDE.md
  • packages/core/package.json
  • packages/frontend-core/README.md
📝 Walkthrough

Walkthrough

This PR extracts a new framework-neutral package, @modular-frontend/core, from @modular-react/core. It introduces UiComponent/UiNode type seams replacing React types across contracts, moves the full public API into the new package's barrel export, and converts packages/core into a thin React re-export facade. Documentation, build configs, and error message prefixes are updated accordingly.

Changes

Core Extraction and Facade Refactor

Layer / File(s) Summary
UI type seam
packages/frontend-core/src/ui-types.ts
Adds UiComponent<P> and UiNode as framework-neutral stand-ins for React component/node types.
Replace React types across contracts
packages/frontend-core/src/types.ts, runtime-types.ts, plugin.ts, runtime-mount.ts, entry-exit.test-d.ts
Updates ModuleDescriptor, ModuleEntry, RegistryPlugin, RuntimeMountAdapter, ZoneMap(Of), NavigationItem.icon, LazyEntryComponent, and related type tests to use UiComponent/UiNode instead of React.ComponentType/ReactNode.
Public export barrel
packages/frontend-core/src/index.ts
New file re-exporting the full public API surface: types, entry/exit helpers, store, detection, module/slots, navigation, route-data, env, remote manifests, validation, runtime types, plugin API, journey contracts, semver, and runtime-mount adapter.
Error message namespace updates
navigation.ts, remote-manifest.ts, semver.ts, validation.ts, runtime-types.ts, dev-env.ts
Renames error/warning message prefixes and doc comments from [@modular-react/core] to [@modular-frontend/core].
Package config, build, and docs
packages/frontend-core/package.json, tsconfig.json, vite.config.ts, vitest.config.ts, README.md
Adds new package manifest, build/typecheck configs (Vite library mode with dts plugin, Vitest typecheck), and README documenting installation, exports, and API semantics.
packages/core rewired as facade
packages/core/src/index.ts, package.json, vite.config.ts, README.md
Replaces explicit named exports with export * from "@modular-frontend/core", adds it as a workspace dependency, marks it external in the Vite build, updates test script and description, and rewrites README.
Repo conventions and tracker doc formatting
CLAUDE.md, docs/vue-port-analysis.md, docs/vue-support-tracker.md
Adds lint/format conventions doc and reformats existing markdown tables without content changes.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Possibly related PRs

Suggested labels: minor

Suggested reviewers: diogomiguel, casamitjana

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: extracting a framework-neutral core package.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/frontend-core-extraction

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/frontend-core/src/plugin.ts (1)

58-61: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale "React" reference in a now framework-neutral doc comment.

The providers field's docstring still says "Contribute React providers to the provider stack. Applied after user providers; first element is outermost." despite the type itself being migrated to UiComponent<{ children: UiNode }>. This is inconsistent with the PR's goal of removing framework-specific language from this package.

✏️ Suggested doc fix
   /**
-   * Contribute React providers to the provider stack. Applied after user
+   * Contribute providers to the provider stack. Applied after user
    * providers; first element is outermost.
    */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/frontend-core/src/plugin.ts` around lines 58 - 61, The doc comment
on the providers field still uses framework-specific “React providers” language,
which is now inconsistent with the UiComponent/UiNode API in plugin.ts. Update
the providers documentation to describe framework-neutral UI
providers/components instead, keeping the existing ordering note about user
providers and outermost placement while removing the React reference.
packages/frontend-core/src/runtime-mount.ts (1)

37-40: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Stale "React" reference in a now framework-neutral doc comment.

The Outlet field's docstring still says "React component that mounts a running instance by id." even though Outlet is now typed with UiComponent. Same inconsistency as in plugin.ts's providers doc.

✏️ Suggested doc fix
   /**
-   * React component that mounts a running instance by id. The optional
+   * Component that mounts a running instance by id. The optional
    * `loadingFallback` propagates through `Suspense` boundaries inside
    * the embedded runtime (e.g. while a lazy step chunk loads).
    */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/frontend-core/src/runtime-mount.ts` around lines 37 - 40, The doc
comment for the Outlet field is stale and still refers to a “React component”
even though it is now framework-neutral and typed as UiComponent. Update the
comment in runtime-mount.ts to describe Outlet in generic terms, and keep the
loadingFallback/Suspense wording aligned with the existing behavior; use Outlet
and UiComponent as the locating symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Around line 15-17: The fenced example in CLAUDE.md is missing a language tag,
which can trip markdown linting. Update the markdown fence around the oxfmt/pnpm
lint:fix example to use the appropriate bash language identifier so the
documented command block is recognized correctly.

In `@packages/frontend-core/README.md`:
- Around line 29-38: The README’s “UiComponent seam” example does not match the
package’s exported UiComponent/UiNode contract, which can mislead consumers
copying the snippet. Update the documentation in the UiComponent/UiNode section
so the example reflects the actual aliases exported by frontend-core, keeping
the wording aligned with the real component and node types used by descriptors
like component, zones, entry-point component, and NavigationItem.icon.

---

Outside diff comments:
In `@packages/frontend-core/src/plugin.ts`:
- Around line 58-61: The doc comment on the providers field still uses
framework-specific “React providers” language, which is now inconsistent with
the UiComponent/UiNode API in plugin.ts. Update the providers documentation to
describe framework-neutral UI providers/components instead, keeping the existing
ordering note about user providers and outermost placement while removing the
React reference.

In `@packages/frontend-core/src/runtime-mount.ts`:
- Around line 37-40: The doc comment for the Outlet field is stale and still
refers to a “React component” even though it is now framework-neutral and typed
as UiComponent. Update the comment in runtime-mount.ts to describe Outlet in
generic terms, and keep the loadingFallback/Suspense wording aligned with the
existing behavior; use Outlet and UiComponent as the locating symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d287142-087e-46dd-8ff5-ea820b0764a1

📥 Commits

Reviewing files that changed from the base of the PR and between c22050b and 0eee88b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (53)
  • CLAUDE.md
  • docs/vue-port-analysis.md
  • docs/vue-support-tracker.md
  • packages/core/README.md
  • packages/core/package.json
  • packages/core/src/index.ts
  • packages/core/vite.config.ts
  • packages/frontend-core/README.md
  • packages/frontend-core/package.json
  • packages/frontend-core/src/any-module-descriptor.test.ts
  • packages/frontend-core/src/catalog-meta.ts
  • packages/frontend-core/src/define-module.ts
  • packages/frontend-core/src/define-slots.ts
  • packages/frontend-core/src/define.test.ts
  • packages/frontend-core/src/detection.test.ts
  • packages/frontend-core/src/detection.ts
  • packages/frontend-core/src/dev-env.test.ts
  • packages/frontend-core/src/dev-env.ts
  • packages/frontend-core/src/entry-exit.test-d.ts
  • packages/frontend-core/src/entry-exit.test.ts
  • packages/frontend-core/src/entry-exit.ts
  • packages/frontend-core/src/index.ts
  • packages/frontend-core/src/journey-contracts.ts
  • packages/frontend-core/src/lazy-module.ts
  • packages/frontend-core/src/navigation-item-base.test.ts
  • packages/frontend-core/src/navigation.test.ts
  • packages/frontend-core/src/navigation.ts
  • packages/frontend-core/src/plugin.ts
  • packages/frontend-core/src/remote-manifest.test.ts
  • packages/frontend-core/src/remote-manifest.ts
  • packages/frontend-core/src/route-data-warn.test.ts
  • packages/frontend-core/src/route-data-warn.ts
  • packages/frontend-core/src/route-data.test.ts
  • packages/frontend-core/src/route-data.ts
  • packages/frontend-core/src/runtime-mount.ts
  • packages/frontend-core/src/runtime-types.test.ts
  • packages/frontend-core/src/runtime-types.ts
  • packages/frontend-core/src/semver.test.ts
  • packages/frontend-core/src/semver.ts
  • packages/frontend-core/src/slots.test.ts
  • packages/frontend-core/src/slots.ts
  • packages/frontend-core/src/step-spec.test-d.ts
  • packages/frontend-core/src/store-compat.test.ts
  • packages/frontend-core/src/store-primitives.test.ts
  • packages/frontend-core/src/store.test.ts
  • packages/frontend-core/src/store.ts
  • packages/frontend-core/src/types.ts
  • packages/frontend-core/src/ui-types.ts
  • packages/frontend-core/src/validation.test.ts
  • packages/frontend-core/src/validation.ts
  • packages/frontend-core/tsconfig.json
  • packages/frontend-core/vite.config.ts
  • packages/frontend-core/vitest.config.ts

Comment thread CLAUDE.md Outdated
Comment thread packages/frontend-core/README.md Outdated
…ypes/react

The frontend-core README documented an earlier single-arm draft of the seam:
`UiComponent<P> = (props: P) => any` and `UiNode = unknown`. The actual
ui-types.ts is `((props: P) => any) | (new (props: P) => any)` and
`UiNode = any`. Update the snippet and prose to match, including the construct
arm (class components) and the rationale for `any` over `unknown`.

The @modular-react/core facade is now only `export * from "@modular-frontend/core"`
with no React type usage, so its @types/react devDependency and optional peer
were dead weight. Remove both; they can layer back when a real React refinement
of the UiComponent seam lands here.
markdownlint MD040 flags fenced blocks without a language. The oxfmt example
block is a shell command, so tag it `bash`.
@kibertoad
kibertoad merged commit 7249c1e into main Jul 5, 2026
14 checks passed
@kibertoad
kibertoad deleted the feat/frontend-core-extraction branch July 5, 2026 20:14
kibertoad added a commit that referenced this pull request Jul 5, 2026
…eys-engine (#55)

* refactor(journeys): extract framework-neutral @modular-frontend/journeys-engine

PR-02 of the Vue-support initiative. Split the framework-neutral guts of
@modular-react/journeys into a new @modular-frontend/journeys-engine package so
a future Vue binding can reuse them, mirroring the @modular-frontend/core
extraction from #54.

- New packages/journeys-engine (@modular-frontend/journeys-engine): the runtime,
  validation, persistence, authoring helpers (defineJourney/defineTransition/
  selectModule), handles, simulate-journey, the test harness, and the full type
  surface, plus their non-React tests. Depends only on @modular-frontend/core
  (happy-dom is a dev dep for the storage-backed persistence tests). Two entries:
  index + /testing.
- @modular-react/journeys keeps its React files (outlet, module-tab, provider,
  plugin, instance-hooks, use-journey-state, use-wait-for-exit, mount-adapter)
  and re-exports the engine, so its public export surface is unchanged. A thin
  testing.ts re-exports the engine's /testing entry, keeping the existing
  @modular-react/journeys/testing import path working.

Two forced deviations from the tracker's PR-02 plan:

- mount-adapter.ts stays in the binding, not the engine: createJourneyMountAdapter
  supplies Outlet: JourneyOutlet (a React component), so it is binding-specific
  glue over the neutral RuntimeMountAdapter seam rather than engine logic.
- JourneyNavContribution.icon used the React.ComponentType namespace; it moves to
  the neutral UiComponent seam, matching how NavigationItem.icon was neutralized
  in #54. Source-compatible for authors (a React component still satisfies
  UiComponent).

Error-message prefixes stay [@modular-react/journeys] / [@modular-react/journeys/
testing] on purpose: they name the package users import and point at real import
paths, so the moved tests pass unmodified.

Verification: engine 346 tests (26 files) + journeys 72 tests (8 files) = the
pre-split total; @modular-react/compositions (the createJourneyMountAdapter
consumer) 115 tests pass; full workspace typecheck (110 tasks) and pnpm lint
clean. The only failing tests are the pre-existing Windows EPERM .test-output
flake in the two router CLIs, unrelated to this change.

* docs(journeys-engine): drop mount-adapter from the engine README

createJourneyMountAdapter stays in the @modular-react/journeys binding (it
supplies the React JourneyOutlet), so the engine does not ship it. Listing it
under "What's included" advertised an export this package does not provide.

* docs(journeys): address PR review comments on the engine extraction

- vue-support-tracker: soften the shared-engines header so it no longer reads
  as if compositions-engine is already extracted, and add the #55 reference to
  the PR-02 status-board row.
- journeys/index.ts: point the getInternals note at @modular-frontend/journeys-engine;
  runtime.ts no longer lives in this package.
- journeys-engine/testing.ts: extract the repeated record + registration +
  loading guard shared by fireExit/goBack/goForward into one activeRecordAndReg
  helper.
kibertoad added a commit that referenced this pull request Jul 6, 2026
…ompositions-engine (#56)

PR-03 of the Vue-support initiative. Splits the framework-neutral guts of
@modular-react/compositions into a new @modular-frontend/compositions-engine
package so a future Vue binding can reuse them, mirroring the journeys-engine
extraction (#55) and the frontend-core extraction (#54).

What moves: the pure files (runtime.ts, stores.ts, validation.ts,
define-composition.ts, types.ts) plus their non-.tsx tests. The engine depends
only on @modular-frontend/core; happy-dom is a dev dep for the test
environment. @modular-react/compositions keeps its React files (outlet.tsx,
provider.tsx, plugin.tsx, hooks.ts) and all .tsx tests, and re-exports the
engine so its public export surface is unchanged.

One React reference had to be neutralized, matching #54/PR-02:
CompositionZoneDescriptor.fallback used React.ComponentType; it moves to the
neutral UiComponent seam from @modular-frontend/core. Source-compatible for
authors (a React component still satisfies UiComponent). Error-message
prefixes stay [@modular-react/compositions] on purpose — they name the package
users import.

Test counts preserved: 52 in the engine + 63 in the binding = the pre-split
total of 115. Full workspace typecheck and lint clean.


Claude-Session: https://claude.ai/code/session_01Sf7deW3iRQaaSjZuj3akTA

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant