Background
`fix/avatar-use-client` (#79) marked the whole published bundle as `"use client"` via a Rollup banner. Avatar uses `createContext` at module top level (D14) and would otherwise crash Next.js RSC build-time analysis. The banner is the same convention shadcn / MUI v5 / Chakra v2 ship with — pragmatic and unblocks adoption.
Trade-off accepted at the time: atoms with no hooks (`Card`, `Separator`, `Badge`, `Input`, `Textarea`) lose the ability to render inside a Server Component. No current consumer (Alexandria) renders them server-side, so the cost is theoretical today.
What this issue tracks
Migrating to real per-component RSC granularity — same shape MUI v6, Chakra v3, Mantine 7 ship today.
Goal state
- `dist/Avatar.js`, `dist/Card.js`, `dist/Button.js`, … one file per atom (multi-entry Vite build).
- Each source file with hooks (`Avatar.tsx`, `Button.tsx`, `IconButton.tsx`, `Spinner.tsx`) carries its own `"use client"` directive — preserved per file by `rollup-plugin-preserve-directives` (or built-in once Rollup 5 stabilises preservation for library mode).
- Stateless atoms (`Card.tsx`, `Separator.tsx`, `Badge.tsx`, `Input.tsx`, `Textarea.tsx`) ship without the directive → consumable from Server Components.
- `package.json` exports map keeps the public surface identical (`import { Avatar } from "@code-sherpas/pharos-react"` still works) but allows deep imports (`import { Card } from "@code-sherpas/pharos-react/Card"`) for finer tree-shake.
Steps (rough sketch)
- Refactor `vite.config.ts`: `build.lib.entry` → multi-entry map, `rollupOptions.output.preserveModules: true` or per-component manual config.
- Add `rollup-plugin-preserve-directives` (or equivalent).
- Drop the global `banner: "'use client';"` from rollupOptions.
- Add `"use client"` to the source of every atom that uses React hooks at module scope or inside the component body that triggers RSC evaluation (Avatar — already has it; Button / IconButton / Spinner — verify, add if needed).
- Update `package.json` `exports` map to expose per-component subpaths.
- Verify dist contains the per-file directives (`head -1 dist/Card.js` → no banner; `head -1 dist/Avatar.js` → `"use client"`).
- Add a regression check to `tests/dist-types-smoke.ts` or a new script that asserts directive presence per file.
- Test in Alexandria worktree: confirm a Server Component can import `Card` from Pharos and `next build` succeeds.
Acceptance
- Every atom with hooks keeps its current behaviour but no longer pulls the whole library client-side.
- Every atom without hooks is importable from a Server Component in a Next.js App Router project and `next build` completes.
- Bundle size for consumers shrinks (no Avatar / Button code in bundles that only use Card).
- Public API surface unchanged at the named-export level; deep imports work additively.
Priority / when
Phase 6 / structural refactor. Not blocking any current adoption. Open it as soon as a real consumer demands a server-rendered atom (rendering performance, edge metadata, etc.) or once the atom catalogue passes ~15 and the bundle-size delta becomes measurable.
References
Background
`fix/avatar-use-client` (#79) marked the whole published bundle as `"use client"` via a Rollup banner. Avatar uses `createContext` at module top level (D14) and would otherwise crash Next.js RSC build-time analysis. The banner is the same convention shadcn / MUI v5 / Chakra v2 ship with — pragmatic and unblocks adoption.
Trade-off accepted at the time: atoms with no hooks (`Card`, `Separator`, `Badge`, `Input`, `Textarea`) lose the ability to render inside a Server Component. No current consumer (Alexandria) renders them server-side, so the cost is theoretical today.
What this issue tracks
Migrating to real per-component RSC granularity — same shape MUI v6, Chakra v3, Mantine 7 ship today.
Goal state
Steps (rough sketch)
Acceptance
Priority / when
Phase 6 / structural refactor. Not blocking any current adoption. Open it as soon as a real consumer demands a server-rendered atom (rendering performance, edge metadata, etc.) or once the atom catalogue passes ~15 and the bundle-size delta becomes measurable.
References