Modern applications are often assembled from modules. That is good for ownership, deployment boundaries, and keeping large codebases understandable. It also creates a subtle user-experience failure mode: a page can compile, carry a valid route, pass its local tests, and still be effectively absent because no one can discover it from a real entry point.
This is not primarily a visual-design problem. It is an architecture-contract problem.
Routable is not reachable
Imagine an administrative application with pages spread across two modules. The team already has a test that scans routed pages and checks that each one appears in a navigation catalogue. The test is green, so the navigation surface looks covered.
Later, someone discovers that the scan only walks one module. The second route tree was outside its boundary. Pages there could be added indefinitely without the test knowing they existed.
That is the first important lesson: a completeness check is only as complete as its discovery mechanism.
When the scan is expanded, it finds an entry screen that has documentation and valid follow-on steps, but no link from the application itself. The workflow exists technically. Operationally, it begins with “type this URL.” That is not a usable feature contract.
Define a genuine entry point
The tempting fix is to accept any reference to the route. Perhaps another page links to it. Perhaps it appears on a tab rail. Perhaps a help article mentions it.
Those references are not equivalent.
A help article explains a feature; it is not application navigation. A link on a neighbouring screen can be useful, but it may make discoverability depend on an accidental journey. A tab rail is particularly deceptive: it helps someone move within an area only after they have reached one of its pages. It cannot prove that the area itself has an entrance.
The architectural rule should therefore name the user-facing property you actually want. For example:
Every static administrative workflow page must be represented by a discoverable entry in the primary hub available to its entitled audience.
That sentence is stricter than “the route is mentioned somewhere,” and that is precisely why it is useful.
Test pages, not strings
Route graphs have legitimate shapes that a simple string search will misclassify.
A parameterised detail page is usually reached from a list or search result. Giving every detail route a top-level card would be noisy and misleading. The test can classify routes containing parameters as downstream pages rather than entry pages.
The reverse case also matters. One page may answer on two route aliases for compatibility. Requiring two navigation cards would duplicate the same destination. The unit of the contract is the page, with at least one discoverable canonical route—not every literal route declaration.
These distinctions keep the test strict without making it simplistic.
Make the scan fail honestly
Source-scanning tests have a dangerous failure mode: they can pass after discovering nothing. A changed directory, glob, parser, or build layout can turn the result set into an empty collection, after which every “all pages are covered” assertion becomes vacuously true.
Add a coarse lower bound to the discovered page count. It does not need to encode the exact inventory. It only needs to make a broken scan conspicuous. Pair that with a useful failure message that lists uncovered entry pages and explains the intended remedy.
Exceptions deserve the same discipline. Sometimes an exception is legitimate, but an exemption list easily becomes a hiding place. Store a reason beside each exception, keep the list narrow, and assert that every exception still corresponds to a real uncovered case. Once navigation is fixed, the stale exception should fail the next test run.
That turns exceptions into temporary, reviewable decisions rather than permanent silence.
Align visibility with authorization
Navigation and authorization answer related but different questions.
Navigation visibility asks, “Should this person be offered this destination?” Authorization asks, “May this request proceed?” A hidden card is not a security boundary. Someone can still follow a saved link or construct a request directly.
The useful contract is alignment: the navigation entry should use the same entitlement expressed by the destination policy, while the page or endpoint continues to enforce that policy independently.
This check often exposes ambiguity. If a destination has only broad authentication while its navigation card is restricted to a privileged audience, do not quietly declare the card to be protection. Flag the mismatch for an explicit security decision. Navigation work should reveal authorization uncertainty, not accidentally resolve it.
The trade-off is real
A strict hub contract has costs. The catalogue grows. New pages require deliberate placement and wording. A hub can become crowded if every capability is presented at the same level.
The alternative, however, is not free. A loose rule produces accidental journeys, invisible capabilities, and tests that certify the presence of links without certifying usable discovery.
The practical balance is to keep the entry-point contract strict while allowing the interface to group, search, filter, and progressively disclose destinations. The architecture test should protect reachability; the visual design can protect clarity.
A small checklist
For a modular routed application:
- Inventory every route tree, not only the first one the test was written for.
- Classify static entry pages, parameterised downstream pages, and route aliases.
- Identify the navigation roots users can actually reach.
- Require every entry page to appear from an appropriate root.
- Add a minimum discovery count to catch broken scans.
- Make exemptions explicit, reasoned, narrow, and self-cleaning.
- Align link visibility with destination authorization, while enforcing security at the destination.
The deeper lesson is that architecture includes the path a person can take through the system. If a feature has no honest entrance, its route is an implementation detail—not a delivered capability.
Top comments (0)