Skip to main content
Version: next

Theme customization audit rules

Audit rules for the theme layer of a Front-Commerce project — override surface, extension points, error boundaries, and design system usage.

Theme customization is the most frequent source of findings in real Front-Commerce audits. An oversized or incorrectly wired override surface is invisible day to day, but it turns every version upgrade into a manual merge and silently disables extension points. These rules check that your app/theme folder (and any extension theme folder) stays minimal, correctly structured, and resilient.

AUDIT-THEME-01 — Minimize the override surface

Severity: important — Detection: static

Rule: No file in app/theme (or an extension theme folder) is an identical or near-identical copy of its original in the reference theme.

Why: Every overridden file is frozen at the version it was copied from: it stops receiving upstream bug fixes, accessibility improvements, and security patches, and it must be manually re-diffed on every Front-Commerce upgrade. Real audits regularly find 10+ files overridden in pure duplication — upgrade debt with zero functional value. Deleting an identical copy is a free win.

How to check: This check needs the installed packages — run pnpm install (or the project's package manager) first if node_modules is absent. Then diff every overridden file against its original in node_modules/@front-commerce/theme-chocolatine/:

cd app/theme
find . -type f | while read -r f; do
orig=$(find ../../node_modules/@front-commerce/theme-chocolatine \
-path "*/theme/${f#./}" 2>/dev/null | head -1)
if [ -n "$orig" ]; then
if diff -q "$f" "$orig" >/dev/null 2>&1; then
echo "IDENTICAL: $f"
else
echo "CHANGED ($(diff "$f" "$orig" | grep -c '^[<>]') lines): $f"
fi
else
echo "NEW: $f"
fi
done | sort

Any IDENTICAL file is a violation: delete it. Files reported as CHANGED with fewer than ~5 changed lines are near-duplicates: review whether the change can be achieved by wrapping the original component (import it from @front-commerce/theme/...) or through an existing extension point instead. Report the total override count and the identical/near-identical count in the audit.

AUDIT-THEME-02 — Extend core logic instead of replacing it

Severity: important — Detection: manual

Rule: No core loader, resolver map, or GraphQL module is copied and overridden in full when an additional GraphQL module with a targeted resolver (or a loader decorating the original) achieves the same result.

Why: A wholesale copy of a core loader or resolver silently drops internal behavior the rest of the framework relies on. Real audit case: a fully overridden CartLoader lost the __originalData bookkeeping, which broke dynamic routes. A full copy also freezes hundreds of lines of core logic at one version, compounding the upgrade debt of AUDIT-THEME-01 at the data layer, where regressions are hardest to notice.

How to check:

  1. List custom GraphQL modules and their intent:

    grep -rn "createGraphQLModule\|createGraphQLRuntime" extensions/ app/ --include="*.ts"
  2. In each runtime.ts, inspect contextEnhancer: a returned loader whose name shadows a core loader (Cart, Product, Customer, ...) and whose implementation is a copied class rather than a wrapper around loaders.<Name> is a violation.

  3. Search for copied core sources: pick distinctive lines from suspect files and grep for them in node_modules/@front-commerce/*/. A file that is mostly identical to a core loader/resolver is a violation.

  4. For each violation, verify the lighter alternative was possible: a module declaring the owning core module in dependencies and redefining only the target fields in resolvers (see the "Change a resolver behavior" guide), or a loader that delegates to the original instance.

AUDIT-THEME-03 — Import theme files through the theme/ alias

Severity: important — Detection: static

Rule: Files under a theme folder import other theme files through the theme/ alias, never through relative paths that cross component directories (../OtherComponent/...).

Why: The override mechanism resolves the theme/ alias against a chain that puts app/theme ahead of every package's theme. A relative import bypasses that chain and resolves directly on disk, so any override of the imported file is silently ignored. The breach is invisible in the importing project and typically surfaces later as "my override doesn't apply in some places".

How to check:

# Relative imports climbing out of the current component directory
grep -rn --include="*.tsx" --include="*.ts" --include="*.jsx" \
-E "from ['\"]\.\./" app/theme extensions/*/theme

Each match that resolves to another component, hook, or stylesheet under a theme tree is a violation: rewrite it as theme/<path from theme root>. Same-directory imports of a component's own private files (./Foo.scss) are acceptable in an integrator project; imports reaching a sibling component directory are not. Also check .scss files for relative @use/@import of another component's stylesheet.

AUDIT-THEME-04 — Keep overrides namespaced and one component per directory

Severity: minor — Detection: static

Rule: Custom theme code lives in feature-namespaced PascalCase directories (theme/modules/StoreLocator/), with one component per file and its .scss, .gql, stories, and tests colocated in the component's own subdirectory.

Why: Files dropped at the root of theme/components/ or theme/modules/ collide with files from other extensions overriding the same path. Multiple components packed into one file cannot be overridden (or reused) individually, and scattered .scss/.gql siblings make it impossible to tell which files belong to which component when upgrading.

How to check:

  • Flag loose files at shared roots:

    find app/theme/components app/theme/modules -maxdepth 1 -type f
  • Flag directories mixing several components with their assets side by side:

    # More than one .tsx/.jsx per directory alongside .scss/.stories files
    find app/theme -type d | while read -r d; do
    n=$(find "$d" -maxdepth 1 \( -name "*.tsx" -o -name "*.jsx" \) | wc -l)
    [ "$n" -gt 1 ] && echo "$n components: $d"
    done
  • Flag files exporting more than one React component (grep -c "^export const [A-Z]\|^export function [A-Z]" per file).

  • Flag kebab-case or lowercase module directories under theme/modules/.

AUDIT-THEME-05 — Export an ErrorBoundary from the root and every overridden layout

Severity: important — Detection: static

Rule: app/root.tsx exports Front-Commerce's RootErrorBoundary (or a project-specific equivalent), and every overridden layout route re-exports an ErrorBoundary that preserves the layout (typically wrapping LayoutErrorBoundary).

Why: Without a root boundary, any unhandled error renders the framework's bare fallback instead of a branded, internationalized error page. When an overridden layout (for example _main.tsx) drops the ErrorBoundary export, errors in any nested route escape to the root boundary and the user loses the header, footer, and navigation — a real audit finding on an overridden _main.tsx.

How to check:

# Root boundary present?
grep -n "RootErrorBoundary\|export const ErrorBoundary\|export function ErrorBoundary" app/root.tsx

# Every overridden layout re-exports one
for f in app/routes/_*.tsx; do
grep -L "ErrorBoundary" "$f"
done

A layout route file (an app/routes/ file that renders Outlet and wraps it in a layout) without an ErrorBoundary export is a violation. Also verify that custom error pages use internationalized titles rather than hardcoded strings (grep -rn "appErrorPages" app/theme).

AUDIT-THEME-06 — Keep the PWA manifest valid and branded

Severity: minor — Detection: static

Rule: The PWA configuration (app/config/pwa.ts or the pwa key of front-commerce.config.ts) declares icons that exist on disk, and its appName, themeColor, and description reflect the brand instead of the skeleton defaults.

Why: A manifest icon pointing to a missing file (real audit case: icon: "assets/icon.png" with no such file) produces a broken install prompt and a 404 fetched on every page load. Skeleton defaults ("Front-Commerce", #fbb03b) shipped to production look unfinished on the user's home screen.

How to check:

  1. Locate the config: grep -rn "pwa" front-commerce.config.ts app/config/.

  2. For each icon, maskableIcon, and offline fallback path, verify the file exists (paths resolve from the project root, for example public/favicon.svg):

    node -e "const c = require('./app/config/pwa'); /* or read the file */"
    ls -l public/favicon.svg public/images/Logo.svg # adapt to declared paths
  3. Flag skeleton leftovers:

    grep -n "Front-Commerce\|fbb03b\|My e-commerce application" app/config/pwa.ts
  4. Verify icons are at least 512x512 (or SVG), as required for install prompts.

AUDIT-THEME-07 — Reuse design system components instead of recoding them

Severity: minor — Detection: static

Rule: Custom components reuse the theme's design system — theme/components/atoms/Icon for icons, PriceVariant for prices, existing form atoms (Input, Select, Checkbox, Button, ...) for controls — instead of inline SVG, hand-rolled price formatting, or raw form elements.

Why: The theme atoms are internationalization-aware, accessible (labels, focus, error rendering), and consistent with the rest of the store. Hand-rolled duplicates drift visually, lose tax/currency formatting rules for prices, and duplicate accessibility work. Inline SVG bypasses the icon registry, so icons can no longer be swapped centrally.

How to check:

# Inline SVG in custom components
grep -rn "<svg" app/theme extensions/*/theme --include="*.tsx" --include="*.jsx"

# Hand-rolled price display (rendering amounts without PriceVariant/Price)
grep -rn "toFixed(2)\|Intl.NumberFormat" app/theme --include="*.tsx"

# Raw form controls where an atom exists
grep -rn "<input\|<select\|<textarea" app/theme --include="*.tsx" --include="*.jsx"

Each match is a candidate violation; confirm no theme atom covers the need (ls node_modules/@front-commerce/theme-chocolatine/*/theme/components/atoms) before reporting. A raw control inside an overridden copy of a reference-theme file that already used a raw control is not a finding.

AUDIT-THEME-08 — Colocate GraphQL fragments and type components from generated types

Severity: important — Detection: static

Rule: Each theme component that consumes GraphQL data owns a colocated <ComponentName>Fragment.gql containing only the fields it renders, and its props are typed with the generated types from ~/graphql/graphql — never a hand-written TypeScript shape duplicating a GraphQL type.

Why: The fragment is the component's data contract: when it lives elsewhere (or fields are stuffed into a page-level query), overriding the component no longer controls what is fetched, and unused fields accumulate in every response. Hand-written prop types silently drift from the schema; codegen types fail the build instead when the schema changes.

How to check:

# Components rendering GraphQL data without a sibling fragment
find app/theme -name "*Fragment.gql" | sed 's/Fragment.gql//' > /tmp/frags
grep -rln "Fragment" app/theme --include="*.tsx" | while read -r f; do
ls "${f%.tsx}Fragment.gql" >/dev/null 2>&1 || echo "no colocated fragment: $f"
done

# Hand-written GraphQL shapes instead of generated types
grep -rn "interface.*Props" app/theme --include="*.tsx" -A 5 | grep -E ": \{ (id|sku|name|price)"
grep -rLn "~/graphql/graphql" $(grep -rl "Fragment.gql" app/theme --include="*.tsx" 2>/dev/null)

Violations: a fragment declared in a shared/pages-level file while a single component consumes it; a fragment with fields no component in its subtree renders; a component with a colocated fragment whose props are typed manually instead of with <Name>FragmentFragment from ~/graphql/graphql.