Documentation Index
Fetch the complete documentation index at: https://grantmaster.dev/llms.txt
Use this file to discover all available pages before exploring further.
Custom ESLint Rules
13 project-specific ESLint rules enforcing architecture boundaries, design system compliance, and data access patterns. Rule files:.eslint/rules/*.jsConfig:eslint.config.jsDebt baselines:config/eslintDebtBaselines.js
Overview
| Rule | Category | Severity | Auto-fixable |
|---|---|---|---|
no-direct-firestore-import | Architecture | error | No |
no-eventbus-in-components | Architecture | warn | No |
no-derived-state | Architecture | off | No |
no-direct-onsnapshot | Data Access | warn | No |
prefer-typed-firestore-refs | Data Access | warn | No |
require-firestore-limit | Data Access | warn | No |
no-legacy-tailwind-palette | Styling | error | No |
no-raw-button-with-design-system | Design System | error | No |
no-raw-text-input-with-design-system | Design System | error | No |
no-status-badge-bypass | Design System | warn | No |
prefer-empty-state-component | Design Governance | warn | No |
prefer-skeleton-loader-presets | Design Governance | warn | No |
prefer-page-header | Design Governance | warn | No |
error will block CI. Rules set to warn are migration aids and
will be promoted to error once legacy debt is resolved.
Architecture Rules
no-direct-firestore-import
Why: Enforces the service layer architecture. Components, hooks, contexts,
and pages must never import Firebase SDK modules directly — all data access
goes through BaseService subclasses and core abstractions.
Applies to: Files under /components/, /hooks/, /contexts/, /pages/.
Skipped for: /services/, /core/, /utils/, test files.
Flags:
"Direct firebase/firestore imports are not allowed here. Use the service layer (BaseService) instead.""Direct firebase/functions imports are not allowed here. Use callFunction() from @/core/cloudFunctionsService instead.""Direct firebase/storage imports are not allowed here. Use uploadFile()/deleteFile() from @/core/storageService instead."
Note:firebase/authis intentionally excluded —AuthContextwraps it directly.
no-eventbus-in-components
Why: EventBus is infrastructure glue. Components importing the EventBus
singleton create hidden coupling and make rendering unpredictable. Event
emission and subscription belong in services and infrastructure init code.
Applies to: Files under /components/, /pages/.
Skipped for: /services/, /core/, /extensions/, /infrastructure/, /contexts/, test files.
Flags:
"Direct eventBus imports are not allowed in components. Move event emission/subscription to the service layer. See docs/architecture/EVENT_BUS_RULE.md for guidance."
no-derived-state
Why: Detects the React anti-pattern of initializing useState from props or
other state values. This creates a copy that can become stale when the source
changes. The value should be calculated during render or memoized with useMemo.
Severity: Currently off (disabled). Intended to be enabled as warn with
allowedPatterns: ['^initial', '^default'] to permit explicit opt-in names like
initialValue or defaultSelected.
Applies to: React components (PascalCase functions) and custom hooks (use*).
Skipped for: Lazy initializers (arrow/function expressions passed to useState).
Flags:
"useState initialized from props \"{{propName}}\" creates derived state. Consider calculating this value during render or using useMemo instead.""useState initialized from another state variable \"{{stateName}}\" creates derived state. Consider calculating this value during render or using useMemo instead.""useState initialized from destructured prop \"{{propName}}\" creates derived state. Consider calculating this value during render or using useMemo instead."
allowedPatterns option — an array of regex
strings for prop/state names that are permitted to initialize useState.
Data Access Rules
no-direct-onsnapshot
Why: All Firestore real-time subscriptions must go through listenerManager
to benefit from listener pooling (30-50% cost reduction), built-in memory leak
detection, and centralized debug logging.
Applies to: All source files except infrastructure.
Skipped for: listenerManager.ts, useFirestoreListener.ts, firestoreCollections.ts, test files.
Flags:
"Direct onSnapshot import detected. Use listenerManager.subscribe() instead for cost optimization and leak detection. See docs/LISTENER_MANAGER_GUIDE.md for migration examples."
prefer-typed-firestore-refs
Why: The typed helpers in @/core/firestoreCollections (collections.*,
docs.*, queries.*) provide compile-time type safety for collection paths
and document references. Raw collection()/doc() imports bypass this.
Applies to: Files under /services/ only.
Skipped for: /src/core/, /src/tests/.
Flags:
"Prefer typed Firestore refs from \@/core/firestoreCollections` (collections/docs/queries) instead of importing `collection`/`doc` directly.”`
Note: This is intentionally a warn severity as a migration aid.
require-firestore-limit
Why: Unbounded Firestore collection reads can pull entire collections,
causing performance degradation and excessive billing. Every getDocs() call
in service code must include a limit() constraint or an exact document
lookup via where("__name__", "==", ...).
Applies to: Files under /services/.
Skipped for: Test files.
Flags:
"Firestore collection reads in services must include \limit(…)` or an exact `where(“name”, ”==”, …)` lookup.”`
Styling Rules
no-legacy-tailwind-palette
Why: The codebase has migrated to design-system tokens (primary-*) for
primary colors. Files that already use primary-* tokens should not mix in
raw blue-* Tailwind classes, which bypasses theming and dark mode support.
Applies to: Files that already contain primary- in their source text
(indicating they have adopted the design-system palette).
Skipped for: Test files, files that have not yet adopted primary-* tokens.
Flags:
bg-blue-*, text-blue-*, border-blue-*,
from-blue-*, ring-blue-*, to-blue-*, via-blue-* (shades 50-950).
Fix:
"Use design-system \primary-` tokens instead of raw `blue-` Tailwind classes in tokenized files.”`
Debt baseline: Legacy files are exempted via mixedPrimaryBlueDebtFiles in
config/eslintDebtBaselines.js. Remove files from that list as they are migrated.
Design System Rules
no-raw-button-with-design-system
Why: Once a file imports the design-system Button component, any raw
<button> elements in the same file are inconsistent. All interactive buttons
should use the Button component for consistent styling, loading states, and
variant support.
Applies to: Files that import from @/components/ui/button.
Skipped for: Test files, files under /components/ui/ (design system internals).
Flags:
"Use `Button` from `@/components/ui/button` instead of a raw `<button>` in this file."
Debt baseline: Legacy files are exempted via rawButtonDebtFiles in
config/eslintDebtBaselines.js.
no-raw-text-input-with-design-system
Why: Same principle as no-raw-button-with-design-system, but for text-like
inputs. Files that import the design-system Input should not also use raw
<input> elements for text-type fields.
Applies to: Files that import from @/components/ui/input.
Skipped for: Test files, files under /components/ui/.
Detected input types: text, email, password, search, tel, url,
number, date, datetime-local, month, time, week. Non-text types
like checkbox, radio, file, hidden, and range are not flagged.
Flags:
"Use `Input` from `@/components/ui/input` instead of a raw text-like `<input>` in this file."
Debt baseline: Legacy files are exempted via rawTextInputDebtFiles in
config/eslintDebtBaselines.js.
no-status-badge-bypass
Why: The StatusBadge component and mapDomainStatus() helper centralize
status display logic (colors, icons, accessibility). Local helper functions
like getStatusBadge(), getStatusColor(), or statusColor() bypass this
system and lead to inconsistent status rendering.
Applies to: Files that import from @/components/ui/StatusBadge.
Skipped for: Test files.
Detected helper names: getStatusBadge, getStatusColor, statusColor.
Flags:
"Use \StatusBadge` and `mapDomainStatus()` instead of local status helper implementations in this file.”`
Debt baseline: Legacy files are exempted via statusBadgeHelperDebtFiles in
config/eslintDebtBaselines.js.
Design Governance Rules
prefer-empty-state-component
Why: Plain “No data/results found” strings create inconsistent empty-state UX and miss guidance/action affordances. Empty states should be rendered through shared primitives.
Applies to: Feature UI files (non-test).
Flags: Plain empty-state copy in primitive elements like <p>, <div>, <span>, <td>.
Fix:
"Use \EmptyState` or `SurfaceEmptyState` instead of plain “No data/results” text.”`
prefer-skeleton-loader-presets
Why: Ad-hoc animate-pulse markup drifts visually and duplicates loading patterns. Shared skeleton presets keep loading states consistent and easier to maintain.
Applies to: Feature UI files (non-test), excluding skeleton internals.
Flags: animate-pulse class usage in feature code.
Fix:
"Use \SkeletonLoader` presets (or surface skeleton components) instead of ad-hoc `animate-pulse` markup.”`
prefer-page-header
Why: Page-level title blocks should be standardized to ensure consistent hierarchy, spacing, and actions layout.
Applies to: Page-like files (src/pages/**, feature page components, *Page.tsx), excluding design-system internals and tests.
Flags: Manual <h1> usage when a shared page header is not used.
Fix:
"Use `PageHeader` (or `SurfacePageHeader`) for page-level titles instead of manual `<h1>` markup."
How to Disable (When Necessary)
Use inline ESLint disable comments sparingly and always include a justification:- Migration scripts that run once and are not part of the main app
- Design system internals that need low-level access by definition
- Test utilities that mock infrastructure directly
- Third-party integration adapters that bridge external APIs
config/eslintDebtBaselines.js and
create a ticket to migrate it.
Adding New Rules
Custom rules live in.eslint/rules/ and follow the standard ESLint rule
structure:
- Create the rule file in
.eslint/rules/my-rule.js - Import it in
eslint.config.jsand add to thecustomplugin’srulesobject - Set the severity in the main rules block:
'custom/my-rule': 'warn' - If there is existing debt, create a baseline list in
config/eslintDebtBaselines.jsand add a file-level override to disable the rule for those files - Update this document
Maintenance
Update this document when adding, modifying, or removing custom ESLint rules. The debt baseline files inconfig/eslintDebtBaselines.js should shrink over
time as legacy code is migrated — review them periodically and remove entries
for files that have been fixed.