navigable folder structure
why
two readers use the tree in opposite directions:
- human: descends from the top, looking for the owning surface.
- agent: greps from the bottom, looking for a named capability.
serve both: top compact — few wrappers between entry and decision; bottom granular — one responsibility per leaf, names stay an inventory. getting either wrong shows up as open-file archaeology or duplicate implementations nobody can find.
rule
high-level ownership compact, low-level foundations granular: fewest meaningful files to the owning surface, then one focused file per independently changing implementation detail.
high-level files
at the top of a package, route, domain, or feature: smallest file count that still makes ownership obvious. one owner file may compose its direct surface, state its public contract, point to its lower-level parts. no wrapper-only splits just to make folders look symmetrical.
foundational files
near stable foundations — schemas, mappers, routes, repositories, adapters, policies, UI primitives — split by exact responsibility. each file answers one navigation question immediately: what does it map, validate, persist, render, or integrate?
decision test
- split when: a part has independently understandable behavior, a distinct owner, or a different reason to change.
- keep together when: a split would create a pass-through wrapper, a forwarding export, or another file the reader must open before reaching real behavior.
| observed shape | navigation decision |
|---|---|
| one route's visible composition | keep it in the route owner; do not hide it in a one-use wrapper. |
| a single concept with one authored leaf | denest to the parent as one file. |
| one concept with schema, mapper, and operation that change independently | nest under the concept with focused leaves and a pure barrel. |
| several unrelated helpers in one generic file | split by actual responsibility and give each leaf a searchable name. |
| a shared technical boundary used by domains | use the responsibility role folder, then domain nesting only where needed. |
verification
- run
treeor list the relevant role folder before and after a structural change. - high-level reader: finds the composing owner with minimal clicks.
- agent: identifies foundation capabilities from leaf names alone.
- search for forwarding wrappers and one-file folders after the move.
good and bad
bad
feature/index.ts // implementation wrapper hiding compositionPageShell.tsx // one-use shellmappers/utils.ts // several unrelated mappers in one generic file
wrapper-heavy top, generic foundation name — extra hops both ways:
- human opens
index.ts, thenPageShell.tsx, thenmappers/utils.tsbefore anything real. - agent searching a mapper finds a generic bucket, must read it to learn what exists.
- cost repeats every change; capabilities quietly duplicate because nobody can tell they already exist.
good
feature/featurePage.tsx # compact owner: composes the visible surfacemappers/ # foundational role foldermapBillingCharge.tsreadWorkflowStep.tsprocessSnapshotSchema.ts
- one compact owner composes the route; the role folder exposes every capability by responsibility-named leaves.
- human reaches the decision through one file; agent inventories foundations from names alone; each leaf changes for exactly one reason.
- tree stays navigable both directions without reorganization: growth adds leaves, not thicker wrappers.
do not
- optimize directory symmetry over direct navigation.
- keep large mixed foundation files because their callers happen to be close.
- create
indeximplementation wrappers or single-use page shells to hide a shallow composition. a pure folder barrel follows the nesting structure rule. - hide available behavior behind generic filenames such as
utils,helpers,common, ortypes.