Skip to content

feat: add no-empty-screen-component rule (blank mobile preview / orphan return-null root route)#72

Open
mrpopo573 wants to merge 1 commit into
mainfrom
captainron/no-empty-root-screen
Open

feat: add no-empty-screen-component rule (blank mobile preview / orphan return-null root route)#72
mrpopo573 wants to merge 1 commit into
mainfrom
captainron/no-empty-root-screen

Conversation

@mrpopo573

Copy link
Copy Markdown

What

Adds a new laint rule, no-empty-screen-component, that flags a default-exported screen/route component whose body renders nothing: return null, return undefined, an empty return;, or an empty fragment (<></>). A concise-body arrow (() => null) and an identifier default export resolving to such a function are covered too.

Why

Recurring class of blank mobile previews at first boot. The generation agent restructures a mobile app's routes into a route group (for example moving the home UI into (tabs)/index.tsx) but leaves the top-level entry src/app/index.tsx as a stub:

export default function Index() {
  return null;
}

Route / resolves to that top-level index.tsx, renders null, and the app boots to a white screen in both the web preview and Expo Go. The user then burns several retry prompts before it self-resolves.

Evidence (CloudWatch worker logs, us-east-2, 7 day window): the co-occurring Metro metro-virtual/unresolved/throw-*.js bundle error fired 497 times across 97 distinct sandboxes, trending up day over day, with a shared virtual-module signature. This rule catches the static, single-file half of that failure (the return null root route) before it ships.

The rule is intentionally single-file decidable (laint rules receive AST plus source, no filename) and scoped to keep false positives near zero: it only fires on a default-exported PascalCase or anonymous component whose entire body is one empty return. Anything with a conditional early return (loading gate, auth guard, +not-found), any real JSX (including childless elements like <View />), or a non-default helper that returns null is left alone.

Scope

  • src/rules/no-empty-screen-component.ts: the rule + meta (severity error, platforms expo + web, category React / JSX).
  • tests/no-empty-screen-component.test.ts: 14 cases, both fire and no-fire, including the loading-gate and childless-element non-fire guards.
  • src/rules/index.ts and README.md: regenerated by npm run sync (registry entry, README rule table row, rule count 55 to 56). Generated files, not hand-edited.

Testing

  • npm test: full suite green (476/476) after npm run build.
  • npm run lint, npm run format:check, npm run sync:check, npm run knip: clean for the added files (the pre-existing cli-init lint error and cli/init.ts security warnings are untouched and unrelated).
  • End to end via the built CLI: the gift-card stub index.tsx reports the violation and exits 1; a real Gift Cards screen is silent and exits 0.

Linked context

Investigated from build ed1cca00-a8c6-48ec-afd6-ed5961cb5e49 (V2 native, from-scratch). Internal notes only; no customer PII or raw log lines.

Flags a default-exported screen/route component whose body renders nothing
(return null / return undefined / empty return / empty fragment). This is the
orphan-stub signature behind a recurring class of blank mobile previews: the
generation agent moves real UI into a route group (e.g. (tabs)/index.tsx) but
leaves the top-level index.tsx as 'export default function Index(){ return null; }',
so the '/' route renders nothing and the app boots to a white screen in both the
web preview and Expo Go.

Scoped to be single-file decidable and low false-positive: only fires on a
default-exported PascalCase (or anonymous) component whose entire body is a single
empty return, resolving identifier default exports to their in-scope declaration.
Conditional early returns, loading/auth gates, real JSX (including childless
elements like <View />), and non-default helpers are left alone.
@jpulec jpulec removed their request for review July 6, 2026 21:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant