Skip to content

docs: triage the cat-factory slice-4 subject-keyed zones request - #93

Merged
kibertoad merged 1 commit into
mainfrom
claude/vue-subject-keyed-zones-q2989h
Jul 19, 2026
Merged

docs: triage the cat-factory slice-4 subject-keyed zones request#93
kibertoad merged 1 commit into
mainfrom
claude/vue-subject-keyed-zones-q2989h

Conversation

@kibertoad

@kibertoad kibertoad commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Accept the substance (subject-keyed, render-all, module-contributed detail
panels), redirect the shape: no new @modular-vue/zones package, no third
'zone' semantic, no new descriptor seam (defineModule zones collides with
the shipped ModuleDescriptor.zones field). Counter-proposal: a pure
subject resolver over slot entries in @modular-frontend/core plus thin
hosts in both bindings, mirroring the slice-2 pairing shape. Corrects the
stale peer-range picture (engine is at 0.3.0; compositions/testing still
peer ^0.1.0).

Co-Authored-By: Claude Fable 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_012BPCt9jQArsRrVqygKXShC

Summary by CodeRabbit

  • Documentation
    • Added guidance for triaging subject-keyed UI regions and evaluating proposed API designs.
    • Documented an engine-neutral panels approach, including filtering, ordering, duplicate handling, and framework-specific integration concepts.
    • Clarified proposed Vue-facing usage patterns and cross-framework alignment.
    • Recorded removed proposal elements, reactivity considerations, version alignment, acceptance criteria, and maintainer decisions.

Accept the substance (subject-keyed, render-all, module-contributed detail
panels), redirect the shape: no new @modular-vue/zones package, no third
'zone' semantic, no new descriptor seam (defineModule zones collides with
the shipped ModuleDescriptor.zones field). Counter-proposal: a pure
subject resolver over slot entries in @modular-frontend/core plus thin
hosts in both bindings, mirroring the slice-2 pairing shape. Corrects the
stale peer-range picture (engine is at 0.3.0; compositions/testing still
peer ^0.1.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BPCt9jQArsRrVqygKXShC
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a triage document that evaluates subject-keyed zones, specifies an engine-neutral panels replacement with Vue and React parity, updates peer-version guidance, outlines documentation work, and maps the revised design to acceptance criteria.

Changes

Subject-Keyed Panels Triage

Layer / File(s) Summary
Proposal assessment
docs/subject-keyed-zones-triage.md
Documents the requested behavior, rejects the zones naming and descriptor shape, and identifies the slots-based, engine-first alternative.
Panels design and binding plan
docs/subject-keyed-zones-triage.md
Defines typed panel entries, resolvePanels, Vue host APIs, React parity, omitted API elements, and predicate reactivity constraints.
Compatibility and acceptance alignment
docs/subject-keyed-zones-triage.md
Updates peer-version guidance, outlines documentation work, maps acceptance criteria to panels, and records reuse and maintainer decisions.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 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 describes the documentation triage for the cat-factory slice-4 subject-keyed zones request.
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 claude/vue-subject-keyed-zones-q2989h

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: 4

🤖 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/subject-keyed-zones-triage.md`:
- Around line 168-172: Update the PanelsOutlet documentation to explicitly
define the subject-key contract: state its accepted type, require a stable
unique value per subject, and specify whether the component supplies a default
when subject-key is omitted. Explain that keys must not be unstable or reused
across different subjects.
- Around line 146-160: Clarify the resolvePanels contract around ordering and
duplicate validation: define where entries with missing order appear relative to
ordered entries, and specify whether duplicate IDs are detected across all
entries or only after when(subject) filtering. Ensure both bindings implement
the same deterministic rules.
- Around line 246-259: The acceptance-criteria table in “What cat-factory's §6
acceptance criteria become” incorrectly marks unshipped implementation-dependent
work as “Met.” Update those dispositions to use “Proposed,” “Planned,” or “Not
yet implemented” unless the referenced engine, Vue, React, and documentation
changes are already shipped, while preserving “Met” only for verified existing
behavior.
- Around line 218-230: Update peer dependency ranges across the entire
`@modular-vue` package family, including `@modular-vue/compositions` and
`@modular-vue/testing`, to admit `@modular-frontend/core` versions ^0.1.0, ^0.2.0,
and ^0.3.0. Ensure the new panels also peer against a range containing the
engine version that provides resolvePanels.
🪄 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: d81baef2-f517-4af1-b952-d9d367a16828

📥 Commits

Reviewing files that changed from the base of the PR and between 6b28220 and a1516e4.

📒 Files selected for processing (1)
  • docs/subject-keyed-zones-triage.md

Comment on lines +146 to +160
readonly when?: (subject: TSubject) => boolean; // absent = always
readonly order?: number; // ascending; ties keep contribution order
readonly props?: Record<string, unknown>; // merged with { subject }
}

/** Phantom-typed handle, same convention as defineJourneyHandle /
* defineCompositionHandle: carries TSubject + the slot key. */
export function definePanelGroup<TSubject>(slotKey: string): PanelGroupHandle<TSubject>;

/** Pure: filter by when(subject), stable-sort by order; null/undefined
* subject → empty; duplicate ids throw (align with resolveComponentRegistry). */
export function resolvePanels<TSubject>(
entries: readonly PanelEntry<TSubject>[],
subject: TSubject | null | undefined,
): readonly PanelEntry<TSubject>[];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Specify deterministic ordering and duplicate-ID semantics.

The contract says entries are sorted by optional order, but does not define where missing orders go or whether duplicate IDs are checked before or after predicate filtering. State these rules explicitly so both bindings produce identical results.

🤖 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 `@docs/subject-keyed-zones-triage.md` around lines 146 - 160, Clarify the
resolvePanels contract around ordering and duplicate validation: define where
entries with missing order appear relative to ordered entries, and specify
whether duplicate IDs are detected across all entries or only after
when(subject) filtering. Ensure both bindings implement the same deterministic
rules.

Comment on lines +168 to +172
- `<PanelsOutlet :group :subject :subject-key>` — renders every resolved entry with
`subject` as a prop **and** via `provide` (`usePanelSubject()` for nested content),
keyed by `entry.id + subjectKey(subject)`, each entry wrapped in
`ModuleErrorBoundary`; `#empty` slot for the no-match case. (`#wrap` per-entry chrome:
accepted, it is cheap and genuinely useful for collapsible shells.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the subject-key contract explicit.

Define its type, uniqueness requirements, and whether <PanelsOutlet> provides a default. Unstable or non-unique keys can reuse panel state across different subjects or cause unnecessary remounts.

🤖 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 `@docs/subject-keyed-zones-triage.md` around lines 168 - 172, Update the
PanelsOutlet documentation to explicitly define the subject-key contract: state
its accepted type, require a stable unique value per subject, and specify
whether the component supplies a default when subject-key is omitted. Explain
that keys must not be unstable or reused across different subjects.

Comment on lines +218 to +230
- The widen it asks for **already shipped**: `@modular-vue/{vue,core,runtime,nuxt,journeys}`
peer `@modular-frontend/core@^0.1.0 || ^0.2.0` today.
- Meanwhile `@modular-frontend/core@0.3.0` published on 2026-07-19, so those same ranges
now exclude the _current_ engine again, and `@modular-vue/compositions` +
`@modular-vue/testing` were never widened past `^0.1.0`.

Accepted as housekeeping, generalized: widen the whole Vue family (including
compositions and testing this time) to include `^0.3.0` in the next release train, and
have the new panels surface peer against the range that includes the engine version that
ships `resolvePanels`. A follow-up worth considering separately: this drift has now
recurred three times (React family in the changelog's peer-range entries, Vue slice 2,
now 0.3.0) — a `check:publish`-style script asserting every workspace peer range admits
every workspace sibling's current version would end the category.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f 'package.json' packages -x rg -n -C2 \
  '"`@modular-frontend/core`"|"`@modular-vue/`(compositions|testing)"' {}

pnpm view `@modular-frontend/core`@0.3.0 time --json

Repository: kibertoad/modular-react

Length of output: 4490


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pnpm view time =="
pnpm view `@modular-frontend/core`@0.3.0 time --json || true

echo
echo "== npm view time =="
npm view `@modular-frontend/core`@0.3.0 time --json || true

echo
echo "== package manifests for relevant workspaces =="
for f in $(git ls-files 'packages/**/package.json'); do
  if rg -q '"`@modular-frontend/core`"|"\`@modular-vue/`(compositions|testing|vue|core|runtime|nuxt|journeys)"' "$f"; then
    echo "--- $f"
    rg -n -C1 '"`@modular-frontend/core`"|"`@modular-vue/`(compositions|testing|vue|core|runtime|nuxt|journeys)"' "$f"
  fi
done

Repository: kibertoad/modular-react

Length of output: 7241


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' /tmp/coderabbit-shell-logs/shell-output-vTGnjG

Repository: kibertoad/modular-react

Length of output: 4490


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pnpm view time =="
pnpm view `@modular-frontend/core`@0.3.0 time --json || true

echo
echo "== npm view time =="
npm view `@modular-frontend/core`@0.3.0 time --json || true

echo
echo "== relevant package manifests =="
for f in $(git ls-files 'packages/**/package.json'); do
  if rg -q '"`@modular-frontend/core`"|"\`@modular-vue/`(compositions|testing|vue|core|runtime|nuxt|journeys)"' "$f"; then
    echo "--- $f"
    rg -n -C1 '"`@modular-frontend/core`"|"`@modular-vue/`(compositions|testing|vue|core|runtime|nuxt|journeys)"' "$f"
  fi
done

Repository: kibertoad/modular-react

Length of output: 7226


🌐 Web query:

npm registry publication time for @modular-frontend/core 0.3.0 July 2026

💡 Result:

There is no publicly available npm package named @modular-frontend/core, nor is there evidence of a 0.3.0 version for such a package being published in July 2026. While the name @modular-frontend/core appears in technical discussions as a router-neutral core library within the context of the kibertoad/modular-react repository, documentation and commit history indicate that these packages are maintained within a monorepo and are not published to the public npm registry [1][2]. Specifically, recent development activity within that repository has focused on adding support for Vue and Angular, and contributors have noted that these modular-frontend packages remain unpublished [2]. Other packages with similar names exist on the npm registry, such as mod-arch-core (published June 3, 2026) [3] and @modjs/core (published August 25, 2024) [4], but these are distinct projects unrelated to the @modular-frontend namespace.

Citations:


Widen the Vue peer ranges to include @modular-frontend/core@0.3.0
@modular-frontend/core@0.3.0 was published on 2026-07-19, but @modular-vue/compositions and @modular-vue/testing still cap at ^0.1.0, and the rest of the Vue family still stops at ^0.2.0, so the current engine is excluded again.

🤖 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 `@docs/subject-keyed-zones-triage.md` around lines 218 - 230, Update peer
dependency ranges across the entire `@modular-vue` package family, including
`@modular-vue/compositions` and `@modular-vue/testing`, to admit
`@modular-frontend/core` versions ^0.1.0, ^0.2.0, and ^0.3.0. Ensure the new
panels also peer against a range containing the engine version that provides
resolvePanels.

Comment on lines +246 to +259
## What cat-factory's §6 acceptance criteria become

| Their criterion | Disposition |
| ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `@modular-vue/zones` published; `defineZone`/`ZoneOutlet`/`useZone`/`useZoneSubject` importable | **Changed**: import `definePanelGroup`/`resolvePanels` (engine, via `@modular-vue/core`) and `usePanels`/`<PanelsOutlet>`/`usePanelSubject` (`@modular-vue/vue`). No new package. |
| Render-all, ordered, sectioned, no router | **Met**, with sections replaced by per-region group ids. |
| Subject-reactive, incl. mutable run-state predicates | **Met**, with the documented reactive-source boundary (their subject already qualifies). |
| Render-all distinct from `pairById` | **Met**; documented side by side. |
| Consumer-extensible via module registration, no host edit | **Met** — it is the existing module → slot path. |
| Headless `/testing` resolver | **Met** by `resolvePanels` being a pure engine export; no subpath needed. |
| `installModularApp` provides the zone manifest, typed, opt-in | **Obsolete** — the slots context Nuxt already provides is the manifest; nothing to thread. |
| Peer alignment | **Superseded** by the corrected picture above (family-wide widen to `^0.3.0`, incl. compositions/testing). |
| Docs | **Met** per the docs plan. |
| Additive, no breaking changes | **Met** — and only met _because_ the `defineModule({ zones })` seam was rejected; as requested it was breaking. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not label future work as “Met.”

This PR documents a proposed design and a future documentation plan, but the table marks implementation-dependent criteria as met. Use “Proposed,” “Planned,” or “Not yet implemented” unless the corresponding engine, Vue, React, and documentation changes are already shipped.

🤖 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 `@docs/subject-keyed-zones-triage.md` around lines 246 - 259, The
acceptance-criteria table in “What cat-factory's §6 acceptance criteria become”
incorrectly marks unshipped implementation-dependent work as “Met.” Update those
dispositions to use “Proposed,” “Planned,” or “Not yet implemented” unless the
referenced engine, Vue, React, and documentation changes are already shipped,
while preserving “Met” only for verified existing behavior.

@kibertoad
kibertoad merged commit c359af6 into main Jul 19, 2026
16 of 17 checks passed
@kibertoad
kibertoad deleted the claude/vue-subject-keyed-zones-q2989h branch July 19, 2026 09:20
kibertoad added a commit that referenced this pull request Jul 19, 2026
* feat(frontend-core,react,vue): subject-keyed panels (render-all, predicate-gated)

Implements the counter-proposal from the cat-factory slice-4 triage (#93):
a render-all, subject-keyed, open-contribution panel surface — the sibling
of the pick-one component-pairing helpers — landed engine-first with both
bindings in the same train, under the name "panels" (not "zones").

Engine (@modular-frontend/core):
- PanelEntry<TSubject>, PanelGroupHandle<TSubject>, definePanelGroup
- resolvePanels: pure resolver (null subject -> empty; when(subject) filter;
  stable order sort; duplicate-id throw with first-wins/last-wins escape
  hatch mirroring resolveComponentRegistry)
- Re-exported by @modular-react/core (export *) and @modular-vue/core

Vue (@modular-vue/vue, re-exported from @modular-vue/core):
- usePanels (computed over the slots source + subject; prefers the reactive
  slots source), <PanelsOutlet> (render-all, subject injected as prop +
  provide, per-panel ModuleErrorBoundary, #empty / #wrap slots),
  usePanelSubject

React (@modular-react/react):
- usePanels, <PanelsOutlet>, usePanelSubject / PanelSubjectContext — same
  surface with useMemo + context

Contributions ride the existing module -> slot path; no descriptor change,
no new package, no registry plugin, no /testing subpath, no Nuxt manifest
threading — all subsumed per the triage.

Docs:
- docs/subject-panels.md guide (entry shape, resolver, both hosts, the
  inspector recipe, the Vue reactivity caveat)
- compositions README "pick by problem shape" table extended 3 -> 4
  primitives; cross-links from reactive-slots-vue.md and
  remote-capability-manifests.md; triage doc marked resolved

Housekeeping (Gap D): widen the Vue family peer ranges (incl. compositions
and testing) to admit @modular-frontend/core@^0.3.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QULkEZq9fop9povfXdMecL

* refactor(panels): address review findings — validation order, dual slots source, shared dedup, boundary labels

Code-review + architecture follow-ups on the subject-keyed panels PR:

- resolvePanels validates duplicate ids BEFORE the null-subject guard, so a
  registration bug surfaces deterministically on first resolve — including the
  usual initial state where nothing is selected yet. New engine tests lock it.
- The duplicate-id collapse is now one shared implementation
  (collapseEntriesById in component-registry.ts) behind both
  resolveComponentRegistry and resolvePanels, so the two reductions' duplicate
  stance cannot drift; the redundant post-filter copy in resolvePanels is gone.
- Vue usePanels tracks BOTH runtime slot sources — the reactive computed and
  the recalculateSlots() signal Ref — serving whichever produced the more
  recent evaluation. Panels contributed via dynamicSlots now update on either
  path, matching the React host (whose single context carries both). Covered
  by a both-directions test.
- Vue usePanels accepts MaybeRefOrGetter for group and onDuplicate, resolved
  inside the computed; <PanelsOutlet> passes getters, so its props are live
  after mount instead of frozen at setup (tested via a post-mount onDuplicate
  flip).
- ModuleErrorBoundary (both bindings) gains an optional `label` prop (default
  "Module"); <PanelsOutlet> passes "Panel" so a crashed panel isn't mislabeled
  a module.
- React usePanels uses a module-level empty-entries constant so the memo stays
  stable for uncontributed groups.
- New host tests in both bindings: subjectKey remount semantics (state kept
  without it, remounted with it) and onDuplicate forwarding.
- Docs: resolver semantics reordered; new "when vs dynamicSlots" section;
  props.subject override documented; Vue caveat now covers the contribution
  side; CHANGELOG gains the missing Unreleased entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5fr1q1oVcmjg9s1zibMXP

* docs(panels): documentation coverage for subject-keyed panels

Close the doc-discoverability and API-reference gaps around the panels
feature:

- README.md: add a "Subject-keyed panels" row to the documentation index so
  the guide is discoverable alongside the sibling primitives.
- packages/frontend-core/README.md: document the engine exports
  (definePanelGroup, resolvePanels, PanelEntry, PanelGroupHandle).
- packages/react/README.md: document the React host (usePanels, PanelsOutlet,
  usePanelSubject, PanelSubjectContext).
- packages/vue/README.md: document the Vue host (usePanels, PanelsOutlet,
  usePanelSubject, panelSubjectKey), with the reactivity-caveat pointer.
- docs/subject-panels.md: add a concrete end-to-end walkthrough tying the
  scattered fragments into one inspector-rail scenario — subject type, group
  handle, first-party + consumer contributions, and a trace of exactly which
  panels render (ordered, filtered) across selections, including the
  open-contribution case where a consumer panel appears with no host edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV2ACUkMCvCgquryU3P5Cb

* test(examples): runnable subject-keyed panels example + e2e (React Router)

Add examples/react-router/inspector-panels — a self-contained pnpm workspace
demonstrating the panels feature end to end, matching the sibling examples'
conventions (app-shared / modules / shell + Playwright e2e):

- app-shared: the BoardBlock subject type, a sample board, and the shared
  definePanelGroup handle.
- modules/inspector-core (first-party): an always-on `identity` panel plus a
  `frontend-config` panel gated to frame-level frontend blocks; demonstrates
  both the injected `subject` prop and the `usePanelSubject` context reader.
- modules/acme-extras (consumer): a namespaced `acme:security-report` panel for
  its own `acme-secure` block type, added with no edit to the host — the
  open-contribution property.
- shell: registry wiring plus the board host and the <PanelsOutlet> inspector
  rail (subjectKey / empty / wrap), and a smoke.spec.ts that asserts the exact
  filtered/ordered render across selections, the empty state, and re-resolution.

Verified: typecheck, vite build, and the Playwright e2e (5/5) all pass. Listed
in examples/README.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NV2ACUkMCvCgquryU3P5Cb

* docs(panels): address review — correct React definePanelGroup import, fix README fragment, clarify subjectKey string form

- docs/subject-panels.md: import definePanelGroup from @modular-react/core
  (the /react host layer does not re-export it); both examples
- packages/vue/README.md: complete the sentence fragment in the panels bullet
- panels docstrings (React + Vue) and docs: clarify that subjectKey is re-read
  each render, so the string form is a caller-computed discriminator (a literal
  constant never drives remounts), while the function form derives it from the
  subject

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDhW7GZMkb4pSCwKVch4tC

---------

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants