14 — Barrel Files and Public Boundaries
Section titled “14 — Barrel Files and Public Boundaries”Generated: 2026-04-22 Status: Active — canonical as of this date.
This document defines the rules for barrel files (index.ts / index.tsx)
and public boundary patterns in this monorepo. It applies to extracted feature
packages (packages/feature-*/), app-local features
(apps/dashboard/src/features/*/), platform packages, and shared code.
Why this matters
Section titled “Why this matters”A barrel file is a module that re-exports symbols from other files. Used well, barrels create stable, intentional public APIs. Used carelessly, they:
- hide ownership by making internal files look public
- enable accidental cross-feature coupling through private paths
- make it hard to know “what is the intended public surface?”
- create multiple valid import paths for the same thing
The goal is: every cross-feature or cross-package import goes through a deliberate, narrow public boundary, not a deep internal path.
Canonical rules
Section titled “Canonical rules”1. Package root barrels are valid public boundaries
Section titled “1. Package root barrels are valid public boundaries”Every packages/* package has a root src/index.ts that defines its complete
public API. External consumers must import only from the package name:
// ✅ correct — package public boundaryimport { activityQueryKeys } from "@legaciti/feature-activity";
// ❌ wrong — deep internal pathimport { activityQueryKeys } from "@legaciti/feature-activity/src/query-keys";2. App-local feature root index.ts is required when cross-feature consumption exists
Section titled “2. App-local feature root index.ts is required when cross-feature consumption exists”If an app-local feature (src/features/{name}/) is consumed by any other
feature or app-shell code, it must have a root index.ts that explicitly
exports only its intended public surface.
Features with no cross-feature consumers do not need an index.ts.
// ✅ correct — admin feature has index.ts exporting adminQueryKeysimport { adminQueryKeys } from "@/features/admin";
// ❌ wrong — deep path into admin internalsimport { adminQueryKeys } from "@/features/admin/query-keys";3. Routes may import from feature page barrels and query factories directly
Section titled “3. Routes may import from feature page barrels and query factories directly”Route files are the primary consumers of feature pages. They may import:
- the feature’s page barrel (
@/features/{name}/pages) - query option factories (
@/features/{name}/queries)
This is the expected usage pattern.
Routes and app/ must not import individual page files directly. Dashboard
route-facing features should expose a pages/index.ts barrel so consumers use
the stable folder boundary instead of a specific page file.
4. Same-feature internal imports need no barrel indirection
Section titled “4. Same-feature internal imports need no barrel indirection”Within a single feature, direct relative imports are preferred:
// ✅ correct — within the same featureimport { myQueryKeys } from "./query-keys";import { MyCard } from "../components/MyCard";
// ❌ unnecessary — barrel indirection within own featureimport { myQueryKeys } from ".";5. Folder-level convenience barrels are allowed when they reduce noise
Section titled “5. Folder-level convenience barrels are allowed when they reduce noise”An index.ts inside a subfolder (e.g. components/index.ts) may exist to
group a small set of related exports used across the same feature. It should
not re-export unrelated internals.
Example — auth/components/index.ts groups the four auth UI primitives used
by all auth pages. This is acceptable.
6. Do not create catch-all barrels that expose unrelated internals
Section titled “6. Do not create catch-all barrels that expose unrelated internals”A barrel that blindly re-exports everything from a folder hides ownership and couples consumers to implementation details. Prefer named, explicit exports.
// ❌ avoid — exposes all internals indiscriminatelyexport * from "./components";export * from "./model";export * from "./hooks";
// ✅ prefer — deliberate named exportsexport { MyPublicComponent } from "./components/MyPublicComponent";export { myPublicQueryKeys } from "./query-keys";7. Stale re-export wrappers must be removed
Section titled “7. Stale re-export wrappers must be removed”If a file only re-exports something from another feature’s internal path, it is a stale compatibility barrel. Remove it rather than maintaining a second import path for the same thing.
Feature public surface guidance
Section titled “Feature public surface guidance”Typical public exports a feature index.ts may expose:
| Category | Examples |
|---|---|
| Page components | exported only when consumed from another feature or route |
| Selected components | e.g. cross-feature reusable panels, not all inner pieces |
| Query key objects | so sibling features can perform targeted cache invalidation |
| Query option factories | so callers can compose loaders without duplicating wiring |
| Mutation hooks | when reused across feature boundaries |
| Selected types | DTO shapes, view-model types needed by consumers |
| Route option factories | if the feature owns its route wiring |
Keep internal implementation files private by default. Do not export:
- internal helper modules
- private page sections or inner panels
- implementation-specific hooks with no external use
- model-level internal utilities
Current public boundaries in this repo
Section titled “Current public boundaries in this repo”Package feature boundaries
Section titled “Package feature boundaries”All feature packages (packages/feature-*/) expose their public API through
src/index.ts. All cross-package imports must go through the package name.
| Package | Notes on public surface |
|---|---|
@legaciti/feature-activity | api, types, formatting, queries, query keys, and reusable activity UI blocks |
@legaciti/feature-people | api, queries, types, query keys, mutations, and ingestion activity hooks |
@legaciti/feature-publications | api, queries, types, query keys, search params, mutations, and ingestion/activity hooks |
App-local feature boundaries (src/features/)
Section titled “App-local feature boundaries (src/features/)”| Feature | Has index.ts | Public exports |
|---|---|---|
admin | yes | adminQueryKeys (for admin-users and app shell) |
workspace-settings | yes | WorkspaceIntegrationsSection, app-owned workspace settings api/query exports |
bug-reports | yes | BugReportWidget (for RootLayout) |
version-sync | yes | VersionSyncProvider, VersionRefreshBanner, useVersionSync, VersionSyncState |
dashboard | yes | StatsOverview, TrendCharts (for home route) |
auth | no | consumed only by routes and middleware via direct paths |
email-templates | no | consumed only by its own route |
site-tools | no | consumed only by its own route |
integrations | no | consumed only by its own route |
admin-users | no | consumed only by admin routes |
Exceptions and temporary notes
Section titled “Exceptions and temporary notes”None at this time. All previously identified cross-feature deep imports have been resolved:
-
integrations/pages/IntegrationsPage.tsxpreviously deep-importedworkspace-settings/components/WorkspaceIntegrationsSection. Now uses@/features/workspace-settingsvia the new public boundary. -
features/integrations/components/WorkspaceIntegrationsSection.tsxwas a stale re-export barrel with no consumers. Removed. -
features/admin-users/queries.tsandapp/shell/admin-workspace-modules-queries.tspreviously deep-imported@/features/admin/query-keys. Now use@/features/adminvia the new public boundary.