Skip to content

Register Components for Plugins using Vite#16445

Open
gigincg wants to merge 2 commits into
developfrom
auto_register_vite
Open

Register Components for Plugins using Vite#16445
gigincg wants to merge 2 commits into
developfrom
auto_register_vite

Conversation

@gigincg

@gigincg gigincg commented Jun 8, 2026

Copy link
Copy Markdown
Member

Proposed Changes

  • Eliminate manual Registration of components to the Plugin Registry
  • Use a custom vite plugin to register all exported components
  • Enforce Unique names for all exported components.
  • Cleanup overlapping names in existing components

Tagging: @ohcnetwork/care-fe-code-reviewers

Merge Checklist

  • Add specs that demonstrate the bug or test the new feature.
  • Update product documentation.
  • Ensure that UI text is placed in I18n files.
  • Prepare a screenshot or demo video for the changelog entry and attach it to the issue.
  • Request peer reviews.
  • Complete QA on mobile devices.
  • Complete QA on desktop devices.
  • Add or update Playwright tests for related changes

Summary by CodeRabbit

  • New Features

    • Added build-time automatic component registration to the build pipeline.
    • Added user-facing documentation describing the component override and registration behavior.
  • Refactor

    • Simplified component exports by removing wrapper-based registration; components now export directly.
    • Renamed several components and EmptyState variants for clearer, consistent naming.
    • Patient registration route now uses a dedicated public patient registration component.
  • Chores

    • Added a dev dependency for source-editing utilities.

@gigincg gigincg requested review from a team June 8, 2026 04:03
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

⚠️ Merge Checklist Incomplete

Thank you for your contribution! To help us review your PR efficiently, please complete the merge checklist in your PR description.

Your PR will be reviewed once you have marked the appropriate checklist items.

To update the checklist:

  • Change - [ ] to - [x] for completed items
  • Only check items that are relevant to your PR
  • Leave items unchecked if they don't apply

The checklist helps ensure code quality, testing coverage, and documentation are properly addressed.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds an automated component registration Vite plugin, wires it into vite.config, removes manual register() wrappers from several components, and renames multiple exported components (including EmptyState variants) to more specific names while updating import sites.

Changes

Component Registration Automation and Naming Standardization

Layer / File(s) Summary
autoRegisterComponents Vite Plugin Implementation
plugins/autoRegisterComponents.ts, vite.config.mts, package.json, docs/care-apps-plugin-overrides.md
New Vite plugin scans TSX under src (excluding overrides), collects exported PascalCase JSX components, validates uniqueness/allowlist, rewrites exports to Base + registration wrappers, injects registration import, emits sourcemaps via MagicString, and is registered in vite.config.mts. Documentation added and magic-string devDependency added.
Remove Manual Component Registration
src/components/Auth/AuthHero.tsx, src/components/Auth/Login.tsx, src/pages/Appointments/BookAppointment/BookAppointmentDetails.tsx, src/pages/Encounters/tabs/overview/quick-actions.tsx
Removes register() wrapper usage and corresponding @/lib/override imports; these modules now export components directly (matching the plugin's auto-registration model).
Empty State Component Naming Standardization
src/components/CareTeam/CareTeamSheet.tsx, src/components/Medicine/MedicationRequestTable/index.tsx, src/components/Patient/Common/EmptyState.tsx, src/pages/Encounters/tabs/overview/summary-panel-details-tab/*, plus import sites
Renames generic EmptyState exports to domain-specific names (MedicationRequestEmptyState, PatientClinicalEmptyState, SummaryPanelEmptyState, CareTeamEmptyState) and updates import sites (often via aliasing) to preserve local identifiers.
Page and Generic Component Renames
src/components/Common/PageHeadTitle.tsx, src/pages/Admin/organizations/components/AdminOrganizationSelector.tsx, src/pages/Facility/settings/healthcareService/HealthcareServiceShow.tsx, src/pages/Facility/settings/locations/LocationSheet.tsx, src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx, src/pages/Patient/index.tsx, src/pages/PublicAppointments/PatientRegistration.tsx
Renames page-level and generic components to context-specific identifiers (e.g., PageHeadTitle, PatientPortalIndex, PublicPatientRegistration, AdminFacilityOrganizationSelector, SettingsHealthcareServiceShow, SettingsLocationSheet, EditFacilityUserRoleSheet).
Organization-Scoped Component Renames
src/pages/Facility/components/UserCard.tsx, src/pages/Facility/settings/locations/components/LocationCard.tsx, src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx, src/pages/Facility/settings/productKnowledge/ProductKnowledgeListComponent.tsx, plus import sites
Renames facility/organization-scoped components (FacilityUserCard, SettingsLocationCard, ActivityDefinitionListContent, ProductKnowledgeListContent) and updates their import sites accordingly.
Printable Component Renames and PrintQuestionnaireResponse Updates
src/components/Facility/ConsultationDetails/PrintAllQuestionnaireResponses.tsx, src/components/Facility/ConsultationDetails/PrintQuestionnaireResponse.tsx
Renames internal exports to PrintableEncounterDetails and PrintableResponseCard and updates PrintQuestionnaireResponse to import them via aliases.
Patient Router Component Wiring Update
src/Routers/PatientRouter.tsx
Uses PublicPatientRegistration (renamed page export) for the /facility/:facilityId/appointments/:staffId/patient-registration route.

Possibly related PRs

  • ohcnetwork/care_fe#16229: Prior work introducing the register-based Override Framework that the plugin and the removed wrappers depend on.

Suggested labels

needs review, needs testing, needs peer review

Suggested reviewers

  • nihal467
  • amjithtitus09
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the key objectives (automatic component registration via Vite plugin, unique name enforcement, cleanup of overlapping names) but the merge checklist items remain unchecked despite significant changes requiring testing and documentation updates. Check off applicable merge checklist items: specify which specs/tests were added, confirm documentation updates, and document any QA completion status before merging.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: implementing a Vite plugin for automatic component registration, which is the primary focus of this PR across all modified files.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto_register_vite

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

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the manual register("ComponentName", BaseComponent) pattern with a custom Vite plugin that automatically injects registration calls for every exported PascalCase TSX component found under src/. Because auto-registration enforces unique component names globally, several components are renamed to resolve conflicts (e.g. EmptyStateMedicationRequestEmptyState, UserCardFacilityUserCard).

  • plugins/autoRegisterComponents.ts — new enforce: \"pre\" Vite plugin that parses each .tsx file with the TypeScript compiler API, identifies exported component functions and arrow-function variables, renames their internal binding to <Name>Base, and appends a register() call. A buildStart hook scans all src/ files and throws on duplicate names.
  • Component renames — 10+ files are renamed for uniqueness; all consuming import sites are updated consistently in the same PR.
  • Manual registrations removed — files like Login.tsx and AuthHero.tsx drop the import { register } + wrapper pattern entirely; the Vite plugin now handles them transparently.

Confidence Score: 4/5

Safe to merge for existing functionality; the three concerns are quality-of-life issues for developers rather than runtime defects.

The plugin logic is sound and the component renames are applied consistently across all import sites. The three flagged issues do not affect production correctness — they affect how legible the transformed output is during debugging and how quickly a developer would notice a naming conflict introduced mid-session.

plugins/autoRegisterComponents.ts warrants a second look on the three points raised; all other files are mechanical renames or boilerplate cleanup and look correct.

Important Files Changed

Filename Overview
plugins/autoRegisterComponents.ts New Vite plugin that automatically injects register() calls into exported TSX components. Three concerns: uniqueName() searches raw source text (matching comments/strings), transform returns map: null breaking source maps, and the uniqueness assertion only runs at buildStart so HMR misses duplicate names introduced during a live dev session.
vite.config.mts Adds autoRegisterComponents() to the plugin list before tailwindcss(). Ordering looks correct (enforce: pre is set inside the plugin itself).
src/components/Auth/Login.tsx Removes manual register() wrapper — the Vite plugin now handles registration automatically via the default export. No logic change.
src/components/Medicine/MedicationRequestTable/index.tsx Renames EmptyState to MedicationRequestEmptyState (and its Props interface) to enforce unique component names. All three import sites were updated in the same PR.
src/pages/PublicAppointments/PatientRegistration.tsx Renames PatientRegistration to PublicPatientRegistration. PatientRouter.tsx is updated consistently.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/empty-state.tsx Renames EmptyState to SummaryPanelEmptyState. All six importing files within the same directory have been updated with the new name aliased back to EmptyState locally.
src/components/Auth/AuthHero.tsx Converts from manual registration pattern (AuthHeroBase + register()) to a direct named export; plugin handles registration automatically.
src/pages/Facility/components/UserCard.tsx Renames UserCard to FacilityUserCard. FacilityDetailsPage.tsx updates its import with an alias to keep local usage unchanged.
src/pages/Patient/index.tsx Renames the default-exported function PatientIndex to PatientPortalIndex for uniqueness. The component is imported by default import in PatientRouter so the local alias is unaffected.

Reviews (1): Last reviewed commit: "Register Components for Plugins using Vi..." | Re-trigger Greptile

Comment on lines +85 to +95
function uniqueName(source: string, preferred: string) {
let name = preferred;
let index = 2;

while (new RegExp(`\\b${name}\\b`).test(source)) {
name = `${preferred}${index}`;
index += 1;
}

return name;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 uniqueName matches names in comments and string literals

new RegExp(\b${name}\b).test(source) searches the raw source text, so a word like LoginBase that appears only in a comment or in a string (e.g. "LoginBase") still counts as a collision. This would cause the internal base name to be unnecessarily promoted to LoginBase2, which is harmless at runtime but could confuse developers reading transformed output or stack traces.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread plugins/autoRegisterComponents.ts Outdated
Comment on lines +493 to +502
const transformed = transformSource(source, normalizedId);
if (!transformed) {
return null;
}

return {
code: transformed,
map: null,
};
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Null source map breaks production debuggability

Returning map: null from the transform means Vite cannot build a correct source-map chain for any transformed TSX file. Subsequent esbuild transforms will generate maps relative to the already-modified code (with renamed identifiers like LoginBase), so breakpoints and stack traces in production builds will point to these generated names rather than the original source. Given that vite.config.mts has sourcemap: true, this affects every component that is auto-registered. Using a library like magic-string or the TypeScript SourceMapper to emit a real source map would preserve debuggability.

Comment on lines +479 to +481
buildStart() {
assertUniqueComponentNames(srcRoot, overrideRoot);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Duplicate-name assertion only runs on cold start, not during HMR

assertUniqueComponentNames is called from buildStart, which Vite invokes once when the server initialises. During a running dev session, if a developer creates a new file (or renames a component) that introduces a duplicate registration key, only the transform hook fires — buildStart is not re-triggered. The second register() call silently replaces the base in the registry (see registerComponent in registry.ts), and no error surfaces until the next full server restart. Adding the same uniqueness check inside the transform hook (or in a handleHotUpdate hook) would catch this immediately.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@plugins/autoRegisterComponents.ts`:
- Around line 375-382: The current transform renames the original declaration
identifier to `${exportName}Base` (baseName) which breaks same-file references
and recursive components; instead keep the original declaration name (do not
replace statement.name) and create a separately named registered alias (e.g.,
registeredName = uniqueName(source, `${exportName}Registered`)) that is appended
as an export which calls registerComponent(originalName). Concretely: stop
performing the edits that replace statement.name with baseName, keep calling
removeExportAndDefaultModifiers(sourceFile, statement, edits) to strip export
modifiers, then push an appended edit that adds `export const <registeredName> =
registerComponent(<exportName>);` (and for default exports append `export
default <registeredName>;`) so the original binding remains intact while
exposing the registered alias.
- Around line 466-504: Add fixture-based tests that exercise the main branches
of the auto-register transform: call transform (via the plugin's transform or
directly transformSource) on representative `.tsx` inputs to cover named
exports, `export default Identifier`, modules with duplicate component names (to
trigger assertUniqueComponentNames behavior), and non-component `.tsx` files
that should remain untouched; create small fixture files for each case, assert
the transformed output (or lack of transformation) and that duplicate-name cases
throw or report as expected, and wire these fixtures into the test harness so
the plugin’s transform pipeline is exercised before removing manual wrappers.

In `@src/pages/PublicAppointments/PatientRegistration.tsx`:
- Line 43: The component PublicPatientRegistration is currently a named export;
change it to be the default export for the page (either by declaring it as
"export default function PublicPatientRegistration(...)" or by keeping the
existing function and adding "export default PublicPatientRegistration" at the
end) so the page file follows the project's default-export convention for pages.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 687134f1-67de-4a52-a38b-3bc46bc87312

📥 Commits

Reviewing files that changed from the base of the PR and between ea4902a and 200199d.

📒 Files selected for processing (38)
  • plugins/autoRegisterComponents.ts
  • src/Routers/PatientRouter.tsx
  • src/components/Auth/AuthHero.tsx
  • src/components/Auth/Login.tsx
  • src/components/CareTeam/CareTeamSheet.tsx
  • src/components/Common/PageHeadTitle.tsx
  • src/components/Facility/ConsultationDetails/PrintAllQuestionnaireResponses.tsx
  • src/components/Facility/ConsultationDetails/PrintQuestionnaireResponse.tsx
  • src/components/Medicine/MedicationAdministration/AdministrationTab.tsx
  • src/components/Medicine/MedicationRequestTable/index.tsx
  • src/components/Patient/Common/EmptyState.tsx
  • src/components/Patient/MedicationStatementList.tsx
  • src/pages/Admin/organizations/components/AdminOrganizationSelector.tsx
  • src/pages/Appointments/BookAppointment/BookAppointmentDetails.tsx
  • src/pages/Encounters/tabs/overview/quick-actions.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/account.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/department-and-team.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/discharge-summary.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/empty-state.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/encounter-tags.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/locations.tsx
  • src/pages/Encounters/tabs/overview/summary-panel-details-tab/manage-care-team.tsx
  • src/pages/Facility/FacilityDetailsPage.tsx
  • src/pages/Facility/components/UserCard.tsx
  • src/pages/Facility/settings/activityDefinition/ActivityDefinitionList.tsx
  • src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx
  • src/pages/Facility/settings/healthcareService/HealthcareServiceShow.tsx
  • src/pages/Facility/settings/locations/LocationSettings.tsx
  • src/pages/Facility/settings/locations/LocationSheet.tsx
  • src/pages/Facility/settings/locations/LocationView.tsx
  • src/pages/Facility/settings/locations/components/LocationCard.tsx
  • src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx
  • src/pages/Facility/settings/productKnowledge/ProductKnowledgeList.tsx
  • src/pages/Facility/settings/productKnowledge/ProductKnowledgeListComponent.tsx
  • src/pages/Patient/History/MedicationHistory.tsx
  • src/pages/Patient/index.tsx
  • src/pages/PublicAppointments/PatientRegistration.tsx
  • vite.config.mts

Comment on lines +375 to +382
const baseName = uniqueName(source, `${exportName}Base`);

removeExportAndDefaultModifiers(sourceFile, statement, edits);
edits.push({
start: statement.name.getStart(sourceFile),
end: statement.name.end,
text: baseName,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve the original component binding when generating the registered export.

Renaming Foo to FooBase changes every same-file reference to Foo. Recursive components stop referring to their own implementation, and any top-level reference that executes before the appended wrapper is initialized will hit the temporal dead zone. Keep the original declaration name and export a separately named registered alias instead.

♻️ Safer rewrite shape
- function FooBase(...) { ... }
- export const Foo = __careRegisterComponent("Foo", FooBase);
+ function Foo(...) { ... }
+ const FooRegistered = __careRegisterComponent("Foo", Foo);
+ export { FooRegistered as Foo };

For default exports, use the same pattern with export default FooRegistered;.

Also applies to: 414-423

🤖 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 `@plugins/autoRegisterComponents.ts` around lines 375 - 382, The current
transform renames the original declaration identifier to `${exportName}Base`
(baseName) which breaks same-file references and recursive components; instead
keep the original declaration name (do not replace statement.name) and create a
separately named registered alias (e.g., registeredName = uniqueName(source,
`${exportName}Registered`)) that is appended as an export which calls
registerComponent(originalName). Concretely: stop performing the edits that
replace statement.name with baseName, keep calling
removeExportAndDefaultModifiers(sourceFile, statement, edits) to strip export
modifiers, then push an appended edit that adds `export const <registeredName> =
registerComponent(<exportName>);` (and for default exports append `export
default <registeredName>;`) so the original binding remains intact while
exposing the registered alias.

Comment thread plugins/autoRegisterComponents.ts Outdated
Comment on lines +466 to +504
export function autoRegisterComponents(): Plugin {
let srcRoot = "";
let overrideRoot = "";

return {
name: "auto-register-components",
enforce: "pre",
configResolved(config) {
srcRoot = `${normalizePath(path.resolve(config.root, "src"))}/`;
overrideRoot = `${normalizePath(
path.resolve(config.root, "src/lib/override"),
)}/`;
},
buildStart() {
assertUniqueComponentNames(srcRoot, overrideRoot);
},
transform(source, id) {
const normalizedId = normalizePath(id.split("?")[0]);

if (
!normalizedId.endsWith(".tsx") ||
!normalizedId.startsWith(srcRoot) ||
normalizedId.startsWith(overrideRoot)
) {
return null;
}

const transformed = transformSource(source, normalizedId);
if (!transformed) {
return null;
}

return {
code: transformed,
map: null,
};
},
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Add fixture coverage for the transform before more manual wrappers are removed.

This plugin now rewrites export semantics for every eligible .tsx module under src, but the PR does not include specs for the main branches: named exports, export default Identifier, duplicate-name failures, and untouched non-component modules. A small fixture-based suite would materially reduce regression risk here.

🤖 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 `@plugins/autoRegisterComponents.ts` around lines 466 - 504, Add fixture-based
tests that exercise the main branches of the auto-register transform: call
transform (via the plugin's transform or directly transformSource) on
representative `.tsx` inputs to cover named exports, `export default
Identifier`, modules with duplicate component names (to trigger
assertUniqueComponentNames behavior), and non-component `.tsx` files that should
remain untouched; create small fixture files for each case, assert the
transformed output (or lack of transformation) and that duplicate-name cases
throw or report as expected, and wire these fixtures into the test harness so
the plugin’s transform pipeline is exercised before removing manual wrappers.

};

export function PatientRegistration(props: PatientRegistrationProps) {
export function PublicPatientRegistration(props: PatientRegistrationProps) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use a default export for this page component.

Line 43 exports this page as a named export, but page files should default-export the page component for consistency.

Proposed fix
-export function PublicPatientRegistration(props: PatientRegistrationProps) {
+export default function PublicPatientRegistration(props: PatientRegistrationProps) {
- import { PublicPatientRegistration } from "`@/pages/PublicAppointments/PatientRegistration`";
+ import PublicPatientRegistration from "`@/pages/PublicAppointments/PatientRegistration`";

As per coding guidelines, src/pages/**/*.{ts,tsx} should "Export page components as default exports".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function PublicPatientRegistration(props: PatientRegistrationProps) {
export default function PublicPatientRegistration(props: PatientRegistrationProps) {
🤖 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 `@src/pages/PublicAppointments/PatientRegistration.tsx` at line 43, The
component PublicPatientRegistration is currently a named export; change it to be
the default export for the page (either by declaring it as "export default
function PublicPatientRegistration(...)" or by keeping the existing function and
adding "export default PublicPatientRegistration" at the end) so the page file
follows the project's default-export convention for pages.

Source: Coding guidelines

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown

🎭 Playwright Test Results

Status: ❌ Failed
Test Shards: 3

Metric Count
Total Tests 321
✅ Passed 320
❌ Failed 1
⏭️ Skipped 0

📊 Detailed results are available in the playwright-final-report artifact.

Run: #9504

Copilot AI review requested due to automatic review settings June 9, 2026 13:28
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying care-preview with  Cloudflare Pages  Cloudflare Pages

Latest commit: 742189d
Status: ✅  Deploy successful!
Preview URL: https://dbde32ce.care-preview-a7w.pages.dev
Branch Preview URL: https://auto-register-vite.care-preview-a7w.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
plugins/autoRegisterComponents.ts (1)

446-454: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Do not rename the original component binding during transform.

Line 450 and Line 492 rename declarations to ...Base, which mutates same-file symbol references and can cause TDZ/runtime failures for references expecting the original exported name. Keep the original declaration name and export a separately named registered alias.

♻️ Safer transform shape
- removeExportAndDefaultModifiers(sourceFile, statement, edits);
- edits.push({
-   start: statement.name.getStart(sourceFile),
-   end: statement.name.end,
-   text: baseName,
-   storeName: true,
- });
- edits.push({
-   start: statement.end,
-   end: statement.end,
-   text: `\nexport const ${exportName} = ${REGISTER_ALIAS}(${JSON.stringify(exportName)}, ${baseName});`,
- });
+ removeExportAndDefaultModifiers(sourceFile, statement, edits);
+ const registeredName = uniqueName(source, `${exportName}Registered`);
+ edits.push({
+   start: statement.end,
+   end: statement.end,
+   text: `\nconst ${registeredName} = ${REGISTER_ALIAS}(${JSON.stringify(exportName)}, ${exportName});\nexport { ${registeredName} as ${exportName} };`,
+ });

Also applies to: 490-496

🤖 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 `@plugins/autoRegisterComponents.ts` around lines 446 - 454, The transform
currently renames the original declaration to `${exportName}Base` by replacing
`statement.name` (via `baseName` from `uniqueName`) which mutates same-file
symbol references and can create TDZ/runtime failures; instead, keep the
original declaration name intact (do not modify `statement.name`) and create a
separately named exported alias/registration using `baseName` (e.g., emit a new
exported const/assignment or export binding that references the original
identifier). Update the code paths that call `uniqueName`,
`removeExportAndDefaultModifiers`, and push into `edits` so they remove
export/default modifiers but do NOT replace `statement.name`; add a new edit
that inserts an exported alias declaration or export statement for `baseName`
referring to the original symbol. Apply the same change to the other occurrence
(the block around lines 490-496) so both places keep the original binding and
export a separately named alias.
🤖 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.

Duplicate comments:
In `@plugins/autoRegisterComponents.ts`:
- Around line 446-454: The transform currently renames the original declaration
to `${exportName}Base` by replacing `statement.name` (via `baseName` from
`uniqueName`) which mutates same-file symbol references and can create
TDZ/runtime failures; instead, keep the original declaration name intact (do not
modify `statement.name`) and create a separately named exported
alias/registration using `baseName` (e.g., emit a new exported const/assignment
or export binding that references the original identifier). Update the code
paths that call `uniqueName`, `removeExportAndDefaultModifiers`, and push into
`edits` so they remove export/default modifiers but do NOT replace
`statement.name`; add a new edit that inserts an exported alias declaration or
export statement for `baseName` referring to the original symbol. Apply the same
change to the other occurrence (the block around lines 490-496) so both places
keep the original binding and export a separately named alias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5fb68afa-a6bb-4e81-b071-c98627077d52

📥 Commits

Reviewing files that changed from the base of the PR and between 200199d and 742189d.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • docs/care-apps-plugin-overrides.md
  • package.json
  • plugins/autoRegisterComponents.ts
  • vite.config.mts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces build-time auto-registration of React components for the CARE plugin override system via a custom Vite plugin, removing the need for manual register() wrappers and enforcing unique exported component names across the codebase.

Changes:

  • Added a custom Vite plugin (autoRegisterComponents) that scans and transforms exported TSX components into override-registered components, with validation for duplicate/unknown component names.
  • Removed manual component registration wrappers and renamed several exported components to ensure global uniqueness.
  • Updated routing/components to align with the renamed exports and clarified public vs patient-portal components.

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vite.config.mts Wires the new auto-registration Vite plugin and adds parsing for an allowlist env var.
src/Routers/PatientRouter.tsx Uses the renamed public patient registration component export.
src/pages/PublicAppointments/PatientRegistration.tsx Renames exported component to PublicPatientRegistration for uniqueness/clarity.
src/pages/Patient/index.tsx Renames the internal component function to a unique portal-specific name.
src/pages/Patient/History/MedicationHistory.tsx Updates import to use the newly renamed medication empty state export.
src/pages/Facility/settings/productKnowledge/ProductKnowledgeListComponent.tsx Renames exported component to a unique *Content name.
src/pages/Facility/settings/productKnowledge/ProductKnowledgeList.tsx Updates usage/import to match renamed product knowledge component.
src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx Renames default-exported sheet component to avoid collisions.
src/pages/Facility/settings/locations/LocationView.tsx Updates import alias to match renamed settings LocationCard export.
src/pages/Facility/settings/locations/LocationSheet.tsx Renames default-exported sheet component for uniqueness.
src/pages/Facility/settings/locations/LocationSettings.tsx Updates import alias to match renamed settings LocationCard export.
src/pages/Facility/settings/locations/components/LocationCard.tsx Renames exported card component to SettingsLocationCard.
src/pages/Facility/settings/healthcareService/HealthcareServiceShow.tsx Renames default export function for uniqueness.
src/pages/Facility/settings/activityDefinition/ActivityDefinitionListComponent.tsx Renames exported component to a unique *Content name.
src/pages/Facility/settings/activityDefinition/ActivityDefinitionList.tsx Updates usage/import to match renamed activity definition component.
src/pages/Facility/FacilityDetailsPage.tsx Updates import alias to match renamed facility user card export.
src/pages/Facility/components/UserCard.tsx Renames exported component to FacilityUserCard to avoid collisions.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/manage-care-team.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/locations.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/encounter-tags.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/empty-state.tsx Renames exported empty state component to SummaryPanelEmptyState.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/discharge-summary.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/department-and-team.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/summary-panel-details-tab/account.tsx Switches to renamed summary-panel empty state export.
src/pages/Encounters/tabs/overview/quick-actions.tsx Removes manual registration wrapper and exports QuickAction directly for auto-registration.
src/pages/Appointments/BookAppointment/BookAppointmentDetails.tsx Removes manual registration wrapper and exports component directly for auto-registration.
src/pages/Admin/organizations/components/AdminOrganizationSelector.tsx Renames default export function for uniqueness.
src/components/Patient/MedicationStatementList.tsx Updates import to use the newly renamed medication empty state export.
src/components/Patient/Common/EmptyState.tsx Renames default export function to avoid name collisions.
src/components/Medicine/MedicationRequestTable/index.tsx Renames exported EmptyState to MedicationRequestEmptyState and updates props interface.
src/components/Medicine/MedicationAdministration/AdministrationTab.tsx Updates import to use the newly renamed medication empty state export.
src/components/Facility/ConsultationDetails/PrintQuestionnaireResponse.tsx Updates named imports to use renamed printable components.
src/components/Facility/ConsultationDetails/PrintAllQuestionnaireResponses.tsx Renames exported printable components for uniqueness and updates usage.
src/components/Common/PageHeadTitle.tsx Renames default export function for uniqueness/consistency.
src/components/CareTeam/CareTeamSheet.tsx Renames local exported empty state component to CareTeamEmptyState.
src/components/Auth/Login.tsx Removes manual registration wrapper and exports component directly for auto-registration.
src/components/Auth/AuthHero.tsx Removes manual registration wrapper and exports component directly for auto-registration.
plugins/autoRegisterComponents.ts Adds the Vite transform + validation logic for automatic component registration.
package.json Adds magic-string dependency for source-preserving transforms and sourcemaps.
package-lock.json Locks the added magic-string dependency.
docs/care-apps-plugin-overrides.md Documents the new build-time registration approach, allowlist env var, and override flow.

Comment on lines +74 to 77
export function QuickAction({
icon,
title,
actionId,

@bodhish bodhish left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the full diff, the runtime register() wrapper, and traced the transform's AST handling against real files (including the scary case — wrapping the src/components/ui Radix/forwardRef library). Overall this is a solid, well-engineered change: enforce: "pre", MagicString hi-res sourcemaps, directive-aware import insertion, and render-time references stay TDZ-safe. The forwardRef catastrophe is avoided because shadcn primitives use export { ... } blocks, which the transform deliberately doesn't touch. Nice.

A few things worth resolving before merge. Inline comments on the specific lines; summary here.

Worth a decision

1. The default registers every component — opposite of stated goal #4. With REACT_MFE_REGISTERED_COMPONENTS unset/empty/*, include is null and every inline-exported PascalCase component (~780 of them) gets wrapped — in dev, CI, and any prod deploy that doesn't set the env var. Goal #4 was "avoid registering every exported component when deployments only need a small override surface," but the default does exactly that. Each wrapper adds a React boundary + 3 useContext reads per rendered instance, which the docs admit "compounds in dense lists or tables" — and this is an EMR with large patient/medication tables. Consider defaulting to an empty allowlist (opt-in). (plugin shouldRegisterComponent)

2. The global-uniqueness gate ignores the allowlist. assertUniqueComponentNames(targets) runs over all discovered components, not just include. So even a deploy that registers 2 components must keep every exported PascalCase component name unique across the whole repo, forever — the permanent tax behind the 38 renames in this PR, inherited by every future contributor. Registry keys only need to be unambiguous among registered components; when include is set, scope the check to include. (plugin buildStart)

Gaps

3. No tests for a 589-line build-critical transform. Zero unit tests for a plugin that rewrites every component in the app, and the "Add specs" merge-checklist item is unchecked. The project already runs vitest (jsdom). This is the highest-leverage ask — cover each branch: export function, single-declarator export const X =, export default / export default function, the multi-declarator + export {}-block skips, allowlist filtering, duplicate detection, and unknown-name rejection. Without it, the edge cases below are unguarded.

4. Export-form coverage is partial and silent. Only three forms are handled (export function, single-declarator export const X =, export default/export default function). export { Foo } blocks, export { X as Y }, multi-declarator export const A = …, B = …, and re-exports are all silently skipped. That's why src/components/ui is safe — but it also means those components can't be overridden, and an allowlisted name that resolves to one of these fails the build with no hint why. Emit a warning when an allowlisted name resolves to an untransformable export form, and document the supported forms. (plugin transformSource)

Notes (low severity)

  • register() isn't ref-forwarding (src/lib/override/register.ts:79function RegisteredComponent(props), not forwardRef). Fine today because every transformed component is a plain function, but it's a latent trap: the first time someone inline-exports a forwardRef/ref-bearing memo component, registration silently drops the ref. Worth skipping forwardRef/memo initializers in isComponentInitializer, or at least a comment.
  • Component.displayName = "X" now lands on the wrapper (e.g. MultiSelect), overwriting register's Registered(MultiSelect). Cosmetic, no crash — registration is inserted before the assignment line.
  • magic-string@^0.27.0 pinned as a direct dep while Vite already ships a newer magic-string — possible duplicate in the tree. Match Vite's version or rely on the transitive one.
  • Doc staleness: the new docs/care-apps-plugin-overrides.md is good, but the JSDoc in src/lib/override/register.ts still teaches the obsolete manual export default register(...) pattern.

Verdict: not a happy-path correctness blocker, but I'd want #1 (default) and #3 (tests) sorted before merge; #2 and #4 are cheap to fix while you're in here.

name: string,
include: ReadonlySet<string> | null | undefined,
) {
return !include || include.has(name);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Default registers every component. With REACT_MFE_REGISTERED_COMPONENTS unset/empty/*, include is null and this returns true for everything — so all ~780 inline-exported components get wrapped by default (dev, CI, and any prod deploy that doesn't set the env var). That's the opposite of goal #4 ("avoid registering every exported component when deployments only need a small override surface"), and each wrapper adds a component boundary + 3 useContext reads per rendered instance, which compounds in the large patient/medication tables. Consider defaulting to an empty allowlist (opt-in) so the override cost is only paid where wanted.

},
buildStart() {
const targets = collectRegisteredComponentTargets(srcRoot, overrideRoot);
assertUniqueComponentNames(targets);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Uniqueness gate ignores the allowlist. This runs over all discovered components, not just include. So a deploy that registers 2 components still must keep every exported PascalCase component name globally unique forever — the permanent tax behind the 38 renames in this PR, inherited by every future contributor (name a new component EmptyState anywhere → build fails). Registry keys only need to be unambiguous among registered components; when include is set, scope the uniqueness check to include.

if (
isExported &&
ts.isVariableStatement(statement) &&
statement.declarationList.declarations.length === 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Only 3 export forms are handled here (export function, single-declarator export const X =, export default/export default function). export { Foo } blocks, export { X as Y }, multi-declarator export const A = …, B = …, and re-exports are silently skipped — which is why src/components/ui is safe, but also means those components can't be overridden, and an allowlisted name that resolves to one of these fails the build with no hint why. Worth a warning when an allowlisted name resolves to an untransformable export form, plus a doc note on supported forms.

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.

4 participants