App Main: Frontend
The App Main frontend is a React and TypeScript application built with Vite and Nx. Although it is deployed as a single web application, its code is organized as a composition shell plus several local feature packages and external mod packages.
The host application is responsible for application bootstrap, authentication, routing, global providers, feature visibility, authorization-aware navigation, module integration, and runtime configuration. Feature packages are responsible for API hooks, widgets, utilities, assets, and domain-specific UI composition.
Runtime Shape
Structure
The current web app structure is not a classic components / views / redux / core split. It is
closer to a package-oriented frontend monolith:
apps/main- the runnable host application.apps/main/src/pages- route-level pages and page modules.apps/main/src/widgets- app-shell widgets such as layout and navigation.packages/main- main bounded-context frontend package: API, widgets, components, utils, plugins, assets.packages/medical-record- medical-record frontend package: API, widgets, reports, utils, plugins.packages/common- app-local shared UI helpers, widgets, components, utilities, and assets.packages/chat,packages/notifications,packages/ehr-integration- specialized local feature packages.@mod/*packages - external mod frontend packages consumed by the host.
Dependency Rules
apps/mainmay depend on any local package or mod package because it is the composition root.packages/*/apimay depend on@app/base, generated GraphQL documents, domain types, and low-level query helpers.packages/*/widgetsmay depend on their own package API,@app/ui,@app/base, and stable shared packages.packages/commonshould stay domain-independent.- Pages may import widgets and mod pages, but widgets should not import route pages.
- Feature packages should not import from
apps/main/src/pages. - Cross-package imports should use configured aliases rather than long relative paths.
- Host-specific mod extensions should live in the host integration package, not in the mod package.
Application Shell
The application shell is responsible for composing widgets into pages, bootstrapping global providers, and configuring runtime integrations. It should not contain business logic, direct API requests, or complex widgets. Its code should stay as simple and declarative as possible.
Local Packages
Local packages under packages/* are not independently deployed NPM packages, but they should be
treated as package boundaries inside the web app.
A package may contain:
api- RTK Query endpoints, generated GraphQL documents, and server response types;widgets- feature widgets, forms, lists, details views, mod integration themes;components- smaller package-specific presentational components;utils- hooks and helpers;assets- translations, styles, images, and other static resources;pluginsorreports- specialized extension points for a package.
packages/main is also responsible for host-side mod configuration: host-dependent GraphQL
extensions, global settings, and theme creation. This is the natural place for code that binds mods
to the host without breaking mod boundaries or polluting the application shell.
packages/common is for app-local shared code. It should not become a dumping ground for
domain-specific behavior. If code depends on main, medical-record, billing, schedule, or another
domain concept, keep it in that domain package.
Widgets
Widgets are the main building blocks of the application. Every meaningful feature should be encapsulated in a widget that can be reused across different pages and modules. Widgets should be as autonomous as possible and should not rely on global state or context except through explicit props and hooks. Whenever possible, they should own their data loading, state, mutation callbacks, and workflow-specific behavior.
When designing a widget, think about which props it should accept to remain flexible and reusable.
An ideal widget does not receive data props such as items for a list. Instead, it receives the
identifiers or configuration needed to load the required data through API hooks. This allows the
widget to be used in different contexts without requiring each consumer to understand or duplicate
its internal data-loading logic.
Good widget:
export interface PaymentListWidgetProps {
payerId?: string;
noCreatedBy?: boolean;
}
export function PaymentListWidget({ payerId, noCreatedBy }: PaymentListWidgetProps) {
// ...
}
Bad widget:
export interface PaymentListWidgetProps {
items: PaymentItem[];
noCreatedBy?: boolean;
}
export function PaymentListWidget({ items, noCreatedBy }: PaymentListWidgetProps) {
// ...
}
In our system, widgets are the only UI units that may directly use API hooks to load or mutate data. Other components should remain as presentational as possible and should not call API hooks directly. This avoids mixed responsibilities and unexpected side effects when components are reused. Instead of using API hooks, components should receive all required data and callbacks through props from widgets.
Pages and other widgets may render widgets, but components that are not declared as widgets should not render widgets. This keeps the hierarchy between pages, widgets, and components clear: pages are routes, widgets are reusable features that can be placed on pages, and components are building blocks for widgets.
These rules allow us to create reusable, autonomous widgets that encapsulate their own logic and can be placed in different parts of the application without duplicating data-loading code or business logic. As a result, complex pages can be built as compositions of widgets, where each widget owns a specific part of the workflow and can be reused elsewhere, for example on a patient page, on its own list page, and on a dashboard.
The codebase still contains many Panel suffixes. Treat them as historical naming for widgets:
new code should prefer Widget for plug-and-play feature surfaces and reserve Component for
low-level presentational elements.
Pages and Routing
Pages live in apps/main/src/pages. They are route-level composition units. A page may read URL
parameters, compose widgets, and provide route-specific layout or navigation context.
Pages should stay thin and delegate feature behavior to widgets from packages/* or @mod/*.
Pages are the only layer that should understand where a widget is rendered and how it is connected to application routes. Widgets should not read URL parameters or use routing APIs directly. If a widget needs route-related data, the page should pass it through props. This keeps widgets more autonomous and reusable because they do not depend on a specific location in the route tree. When a widget needs to initiate navigation, it should use callback props or URL generators provided by the page instead of calling the routing API directly.
API
The frontend API layer is built on Redux Toolkit Query. We use RTK Query because it gives us request lifecycle, generated hooks, caching, invalidation, and loading/error state without writing manual Redux actions, reducers, selectors, and effects for every server call.
The application uses a shared baseApi slice from @app/base. Local packages and mod packages
extend it with baseApi.injectEndpoints, so every package can declare its own endpoints while
still sharing one cache and one request pipeline.
The frontend mirrors the backend API style:
- REST is used for command-oriented operations, such as create, update, delete, complete, cancel, or move.
- GraphQL is used for read models, lists, details screens, lookups, and composed response shapes.
GraphQL documents are written as .graphql files and generated against
schema.auto-generated.graphql. Codegen produces typed document constants and response types used
by RTK Query endpoint definitions.
API files are grouped by feature:
usersApi.ts- RTK Query endpoints, exported hooks, cache tags, and request mapping.usersTypes.ts- request interfaces, response models, enums, and feature-specific API types.users.graphql- GraphQL queries and fragments for read operations.
Mod Integration
The host app consumes external mod frontend packages such as @mod/tasks, @mod/assessments,
@mod/billing, @mod/coverages, and @mod/schedule.
The host integrates mods through three mechanisms:
- GraphQL fragment extension and TypeScript module augmentation for host-specific schema fields;
- mod global settings for host-owned hooks and data providers;
@webinex/flexytheme providers for UI replacement and decoration.
Mod integration code is placed in host widget packages, for example @app/main/widgets/Tasks,
@app/main/widgets/Finance, @app/main/widgets/Coverages, and @app/main/widgets/Schedule.
App.tsx wraps the application in the corresponding theme providers so mod widgets render with the
host-specific UI behavior.
Appendix I: Assets, Localization, and Styling
Vite collects localization resources from:
- installed
@app/*and@mod/*packages; - external packages configured in
package.json; apps/main;- local
packages/*.
Translation files should be namespaced by domain and placed close to the package or app surface that owns them.
Global styles belong in app or package assets. Component-specific styles should stay next to the
component as *.module.scss.