feat(vue): add useReactiveSlots — Vue-reactive slot evaluation - #88
Conversation
Adds `useReactiveSlots()` to `@modular-vue/vue`: the resolved slot manifest as a Vue `computed`, re-evaluated automatically when the reactive state its `dynamicSlots` factories / `slotFilter` read changes — no `recalculateSlots()` call required. It rebuilds the deps snapshot inside a tracked `computed`, so a factory/filter that reads a reactive source live (a service object with getters over refs, a reactive service, a reactive store proxy) makes it a tracked dependency. This is the Vue-idiomatic counterpart to the existing framework-neutral signal path (`useSlots()` + `useRecalculateSlots()`), which stays unchanged. The two coexist and are chosen per source: reactive when the gating inputs are reactive Vue state the host owns (RBAC permissions, availability flags); signal for non-reactive/external sources, transactional recompute, or event-driven invalidation. The runtime provides a new `reactiveSlotsConfigKey` (base slots + factories + filter) alongside the existing slots context, in both the plugin and framework-mode component install forms. Driven by the cat-factory nav/command-manifest adoption (the production consumer exercising the layer-extends consumer story). Full tradeoffs + the host-owned RBAC-gating shape: docs/reactive-slots-vue.md. React source for intent: packages/react/src/slots-context.tsx (signal-only; Vue adds the reactive path on top). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 43 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds ChangesReactive Vue slots
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant VueComponent
participant useReactiveSlots
participant ModularRuntime
participant evaluateDynamicSlots
VueComponent->>useReactiveSlots: read computed slot manifest
useReactiveSlots->>ModularRuntime: inject slot configuration and dependencies
useReactiveSlots->>evaluateDynamicSlots: evaluate factories and filter
evaluateDynamicSlots-->>useReactiveSlots: filtered slots
useReactiveSlots-->>VueComponent: computed slot value
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
- Fix oxfmt formatting that failed the Lint CI job (reactive-slots-vue.md, framework-mode-nuxt.md, reactive-slots.test.ts). - Docs: clarify the "value-gated downstream" note (useReactiveSlots returns a plain computed producing a fresh manifest per recompute; value-gating pays off one level down on stable derived values), and add a "cost scales per consumer" caveat with the share-once-high-in-the-tree remedy. - Tests: cover the reactiveService getSnapshot() reactive-tracking path so the buildDepsSnapshot-through-getSnapshot boundary is locked in alongside the existing plain-service and slotFilter cases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Py3ZFw7u2Qnw9N4X9MRsBi
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@docs/reactive-slots-vue.md`:
- Around line 130-133: Rewrite the reactive-slots explanation around the gates
getter, slotFilter, and useReactiveSlots computed to state that gates is a plain
service passed by reference and dependency tracking occurs when its getter reads
reactive state during computed evaluation. Remove the suggestion that an
attached dependency snapshot changes or must be refreshed or replaced, while
preserving the point that consumers of slots.value.nav recompute without
recalculateSlots().
🪄 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: 32557f56-2ad5-4395-b5c4-3e0219b8b614
📒 Files selected for processing (10)
docs/framework-mode-nuxt.mddocs/reactive-slots-vue.mddocs/vue-support-tracker.mdpackages/vue-runtime/src/index.tspackages/vue-runtime/src/providers.tspackages/vue-runtime/src/reactive-slots.test.tspackages/vue/README.mdpackages/vue/src/index.tspackages/vue/src/slots-context.test.tspackages/vue/src/slots-context.ts
Make the reactive path mirror the signal path's architecture: the runtime resolves the manifest once at install into a single shared `computed` and provides it via `reactiveSlotsKey`; `useReactiveSlots()` becomes a thin reader over that one source. Previously each `useReactiveSlots()` call built its own `computed`, so N consumers re-evaluated the factories/filter N times per change; now evaluation happens once per change regardless of consumer count, and every consumer sees the same manifest object. - slots-context.ts: replace `reactiveSlotsConfigKey`/`ReactiveSlotsConfig` with `reactiveSlotsKey` (holds the resolved `ComputedRef`) and a runtime-facing `resolveReactiveSlots(input)` building block; `useReactiveSlots` injects and returns the shared computed. Drops the composable's dependency on `sharedDependenciesKey`. - providers.ts: build the source once per install — inside a detached `effectScope` stopped on `app.onUnmount` (plugin form) or the ModularProviders `setup` scope (framework-mode) — so the computed's effect is disposed with the app, matching how the signal subscription is disposed. - Tests: cover `resolveReactiveSlots` evaluation directly (reactive factory, reactive slotFilter, reactiveService getSnapshot tracking) and assert `useReactiveSlots` hands every consumer the same shared computed instance. - Docs: describe the single-shared-source model; address review feedback on the RBAC section — `gates` is a plain service passed by reference, tracked because its getters read reactive state inside the computed, not via a snapshot swap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Py3ZFw7u2Qnw9N4X9MRsBi
What
Adds
useReactiveSlots()to@modular-vue/vue: the resolved slot manifest as a Vuecomputed, re-evaluated automatically whenever the reactive state itsdynamicSlotsfactories /slotFilterread changes — norecalculateSlots()call. It rebuilds the deps snapshot inside a trackedcomputed, so a factory/filter that reads a reactive source live (a service object with getters over refs, a reactive service, a reactive store proxy) becomes a tracked dependency of the computed. Lazy, cached, fine-grained.This is the Vue-idiomatic counterpart to the existing framework-neutral signal path (
useSlots()+useRecalculateSlots()), which is left untouched. Additive: the two coexist and are chosen per source.Why
The React binding has only the signal path (React has no ambient reactivity to track). In Vue, gating logic whose inputs are reactive state (RBAC permissions, connection-availability flags, feature toggles) would otherwise need a
recalculateSlots()at every mutation site — easy to forget, and coarse (rebuilds the whole manifest). Acomputedgives the same producer-driven invalidation automatically, tracking only the state that changed.Driven by the cat-factory nav/command-manifest adoption — the production consumer exercising the layer-extends story (
docs/consumer-feedback-production-app.mdrole).When to use which (documented in full)
useReactiveSlotsStore/zustand snapshot or external subscribe/getSnapshotTradeoffs, the "is it over-eager? no" analysis, and the host-owned RBAC-gating shape live in
docs/reactive-slots-vue.md(linked fromframework-mode-nuxt.md, the@modular-vue/vueREADME, and the vue-support-tracker).Changes
@modular-vue/vue:useReactiveSlots,reactiveSlotsConfigKey,ReactiveSlotsConfig(inslots-context.ts), exported.@modular-vue/runtime: providers installreactiveSlotsConfigKey(base slots + factories + filter) in both plugin and framework-mode component forms; re-exported.slots-context.test.ts(reactive recompute with no signal; reactiveslotFilter) + an end-to-endreactive-slots.test.ts(resolve → install → reactive filter recomputes).docs/reactive-slots-vue.md; links added; tracker updated (partially advances D3 Pinia interop).Vue-impact note
Vue-only addition on top of the shared engine; no
@modular-frontend/*change. React parity unaffected (React keeps signal-only by design).React source for intent:
packages/react/src/slots-context.tsx.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
useReactiveSlots()for automatic Vue-reactive slot updates when permissions, availability, or other reactive inputs change.Documentation
Tests