Skip to content

fix(ci): require a default export before treating a file as a route entry - #7028

Open
waleedlatif1 wants to merge 2 commits into
stagingfrom
fix-boundary-entry-discriminator
Open

fix(ci): require a default export before treating a file as a route entry#7028
waleedlatif1 wants to merge 2 commits into
stagingfrom
fix-boundary-entry-discriminator

Conversation

@waleedlatif1

Copy link
Copy Markdown
Collaborator

Follow-up to #7026. Both points were raised in review there and I merged before reading them, so they land here.

The filename was never the right test

#7026 added error.tsx to the entry filenames, and with it picked up [workspaceId]/components/error/error.tsx — named like a boundary, and not one:

export interface ErrorBoundaryProps {  }
export function ErrorShell() {  }
export function ErrorState() {  }

Named exports only. It is the shared helper the thirteen real boundaries render, and Next would reject it as a boundary for having no default export.

Counting it inflated the coverage number and — the part that would have bitten — recorded a shared component in the graph-weight baseline as though it were a route.

Every convention-composed entry must default-export the thing Next renders, so that is the discriminator now.

Entry count 60 → 59. All thirteen real error.tsx boundaries still walk; only the helper drops out.

Completing the enumeration

Also adds template.tsx and default.tsx. Neither exists under app/workspace today, so this changes nothing right now — but the list claims to cover what Next composes, and leaving two out makes that claim false the day someone adds one. That is the same failure the surrounding TSDoc already warns about: "A hardcoded list goes stale silently."

Verification

✓ tool registry stays out of 59 workspace page/layout graphs
error.tsx entries walked: 13
components/error/error.tsx: not present

Credit

The non-route entry was Cursor's; the missing template.tsx/default.tsx was Greptile's. Both were correct.

…ntry

Follow-up to #7026, which added `error.tsx` to the entry filenames and with it
picked up `[workspaceId]/components/error/error.tsx` — named like a boundary,
and not one. It exports `ErrorShell` and `ErrorState` for the thirteen real
boundaries to use; Next would reject it as a boundary for having no default
export. Counting it inflated the coverage number and would have recorded a
shared component in the graph-weight baseline as though it were a route.

The filename was never the right test. Every convention-composed entry must
default-export the thing Next renders, so that is the discriminator now. Entry
count goes 60 → 59, and all thirteen real `error.tsx` boundaries still walk.

Also adds `template.tsx` and `default.tsx`. Neither exists under
`app/workspace` today, so this changes nothing now — but the enumeration claims
to cover what Next composes, and leaving two out makes that claim false the day
someone adds one.

Both raised in review on #7026 (Cursor and Greptile respectively); I merged
before reading them, so this lands separately.
@vercel

vercel Bot commented Aug 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Aug 24, 2026 3:06am

Request Review

@cursor

cursor Bot commented Aug 24, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
CI-only change to how route entries are enumerated; no runtime, auth, or data-path impact. The regex could miss unusual default-export forms, which would under-count entries rather than ship bad code.

Overview
Tightens how check-tool-registry-boundary.ts discovers Next.js workspace entries so a filename match is no longer enough.

collectEntries now also requires a default export (export default or export { … as default}). That drops the shared [workspaceId]/components/error/error.tsx helper (named like a boundary, no default) from the walk and graph-weight baseline, while the real error.tsx boundaries stay. Entry count goes 60 → 59.

Also enumerates template.tsx and default.tsx so the convention list matches what Next composes. Neither exists under app/workspace today.

Reviewed by Cursor Bugbot for commit c01be22. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c01be22. Configure here.

Comment thread scripts/check-tool-registry-boundary.ts Outdated
* graph-weight baseline as though it were a route.
*/
const DEFAULT_EXPORT_RE =
/(?:^|\n)\s*export\s+default\b|(?:^|\n)\s*export\s*\{[^}]*\bas\s+default\b/

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default-export check misses re-exports

Low Severity

DEFAULT_EXPORT_RE only recognizes export default and export { … as default }, so a valid Next entry written as export { default } from '…' fails hasDefaultExport and is dropped from the walk. That form already appears under app/workspace, and a route using it would silently skip the registry-boundary and graph-weight checks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c01be22. Configure here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — fixed in 376e373.

The regex required as default, so export { default } from './page' fell through and the entry would have dropped out of the walk entirely, skipping both the registry gate and the graph-weight ratchet. Silent omission is the dangerous direction for this discriminator to fail in, since the whole point is deciding what gets checked.

One scoping note on "already appears under app/workspace": the form does appear, but in a barrel (logs/components/dashboard/index.ts), not in a file named page.tsx/layout.tsx/error.tsx/etc. collectEntries only tests entry filenames, so no entry uses it today — the gap was latent, not live. Fixing it anyway.

Four forms now count: export default …, export { default } from, export { default, … } from, and export { X as default }.

export { default as X } deliberately still does not count — it re-exports another module's default under a name and leaves this module without one. Verified all ten variants including that distinction:

ok  DEFAULT     export default function Page() {}
ok  DEFAULT     export { default } from './page'
ok  DEFAULT     export { default, Foo } from './page'
ok  DEFAULT     export { Foo as default }
ok  no-default  export { default as Dashboard } from './dashboard'
ok  no-default  export { ErrorShell, ErrorState }
ok  no-default  export const defaultValue = 1

Guard still reports 59 entries with all 13 real error.tsx boundaries walked.

@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR narrows workspace route-entry discovery to convention files with detected default exports and expands the recognized Next.js filenames to include template.tsx and default.tsx.

  • Excludes named-export-only helpers from route graph checks.
  • Adds future coverage for template and parallel-route fallback entries.
  • Uses source-text matching to detect default exports, with one valid re-export form currently unrecognized.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking gap in recognizing valid default re-export syntax.

Current workspace entries use forms recognized by the new filter, but a future route using a bare default re-export would be omitted from both boundary and graph-weight checks.

Files Needing Attention: scripts/check-tool-registry-boundary.ts

Important Files Changed

Filename Overview
scripts/check-tool-registry-boundary.ts Improves route-entry enumeration, but the new default-export regex silently excludes entries using export { default } from ....

Reviews (1): Last reviewed commit: "fix(ci): require a default export before..." | Re-trigger Greptile

Comment thread scripts/check-tool-registry-boundary.ts Outdated
Comment on lines +90 to +91
const DEFAULT_EXPORT_RE =
/(?:^|\n)\s*export\s+default\b|(?:^|\n)\s*export\s*\{[^}]*\bas\s+default\b/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Default re-export syntax is missed

A workspace entry using the valid export { default } from './page' syntax is excluded by this regex, so collectEntries silently omits it from both registry-boundary checking and graph-weight baseline ratcheting.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct — fixed in 376e373.

The regex required as default, so export { default } from './page' fell through and the entry would have dropped out of the walk entirely, skipping both the registry gate and the graph-weight ratchet. Silent omission is the dangerous direction for this discriminator to fail in, since the whole point is deciding what gets checked.

One scoping note on "already appears under app/workspace": the form does appear, but in a barrel (logs/components/dashboard/index.ts), not in a file named page.tsx/layout.tsx/error.tsx/etc. collectEntries only tests entry filenames, so no entry uses it today — the gap was latent, not live. Fixing it anyway.

Four forms now count: export default …, export { default } from, export { default, … } from, and export { X as default }.

export { default as X } deliberately still does not count — it re-exports another module's default under a name and leaves this module without one. Verified all ten variants including that distinction:

ok  DEFAULT     export default function Page() {}
ok  DEFAULT     export { default } from './page'
ok  DEFAULT     export { default, Foo } from './page'
ok  DEFAULT     export { Foo as default }
ok  no-default  export { default as Dashboard } from './dashboard'
ok  no-default  export { ErrorShell, ErrorState }
ok  no-default  export const defaultValue = 1

Guard still reports 59 entries with all 13 real error.tsx boundaries walked.

`export { default } from './page'` is a valid Next entry and the regex required
`as default`, so such an entry would have dropped out of the walk and skipped
both the registry gate and the graph-weight ratchet — silently, which is the
dangerous direction for a discriminator to fail in.

Latent rather than live: the form appears once under `app/workspace`, in a
barrel, not in an entry filename.

Four forms now count — `export default …`, `export { default } from`,
`export { default, … } from`, and `export { X as default }`.
`export { default as X }` still does not: it re-exports another module's default
under a name and leaves this one without one. Verified all ten variants,
including that last distinction.

Raised by both Cursor and Greptile on #7028.
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