Skip to content

14 — Barrel Files and Public Boundaries

Migrated from root technical docs.

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.


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.


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 boundary
import { activityQueryKeys } from "@legaciti/feature-activity";
// ❌ wrong — deep internal path
import { 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 adminQueryKeys
import { adminQueryKeys } from "@/features/admin";
// ❌ wrong — deep path into admin internals
import { 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 feature
import { myQueryKeys } from "./query-keys";
import { MyCard } from "../components/MyCard";
// ❌ unnecessary — barrel indirection within own feature
import { 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 indiscriminately
export * from "./components";
export * from "./model";
export * from "./hooks";
// ✅ prefer — deliberate named exports
export { 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.


Typical public exports a feature index.ts may expose:

CategoryExamples
Page componentsexported only when consumed from another feature or route
Selected componentse.g. cross-feature reusable panels, not all inner pieces
Query key objectsso sibling features can perform targeted cache invalidation
Query option factoriesso callers can compose loaders without duplicating wiring
Mutation hookswhen reused across feature boundaries
Selected typesDTO shapes, view-model types needed by consumers
Route option factoriesif 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

All feature packages (packages/feature-*/) expose their public API through src/index.ts. All cross-package imports must go through the package name.

PackageNotes on public surface
@legaciti/feature-activityapi, types, formatting, queries, query keys, and reusable activity UI blocks
@legaciti/feature-peopleapi, queries, types, query keys, mutations, and ingestion activity hooks
@legaciti/feature-publicationsapi, 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/)”
FeatureHas index.tsPublic exports
adminyesadminQueryKeys (for admin-users and app shell)
workspace-settingsyesWorkspaceIntegrationsSection, app-owned workspace settings api/query exports
bug-reportsyesBugReportWidget (for RootLayout)
version-syncyesVersionSyncProvider, VersionRefreshBanner, useVersionSync, VersionSyncState
dashboardyesStatsOverview, TrendCharts (for home route)
authnoconsumed only by routes and middleware via direct paths
email-templatesnoconsumed only by its own route
site-toolsnoconsumed only by its own route
integrationsnoconsumed only by its own route
admin-usersnoconsumed only by admin routes

None at this time. All previously identified cross-feature deep imports have been resolved:

  • integrations/pages/IntegrationsPage.tsx previously deep-imported workspace-settings/components/WorkspaceIntegrationsSection. Now uses @/features/workspace-settings via the new public boundary.

  • features/integrations/components/WorkspaceIntegrationsSection.tsx was a stale re-export barrel with no consumers. Removed.

  • features/admin-users/queries.ts and app/shell/admin-workspace-modules-queries.ts previously deep-imported @/features/admin/query-keys. Now use @/features/admin via the new public boundary.