Skip to content

feat: a folder's own URL lands on its first page - #1

Closed
gitKrystan wants to merge 6 commits into
gitkrystan/collectionsfrom
gitkrystan/folder-index-redirect
Closed

feat: a folder's own URL lands on its first page#1
gitKrystan wants to merge 6 commits into
gitkrystan/collectionsfrom
gitkrystan/folder-index-redirect

Conversation

@gitKrystan

@gitKrystan gitKrystan commented Aug 13, 2026

Copy link
Copy Markdown
Owner

Stacked on gitkrystan/collections (universal-ember#366), because it touches Folder — the type that branch renames. Retarget at main once universal-ember#366 lands.

The gap

/Group/sub-folder names a real place in the docs but no document of its own, so visiting one renders the error page. Readers type and link these URLs by hand; on one large internal docs site, 5 of 9 broken links found by a link-crawl were exactly this shape.

Resolution normalizes only the .md extension:

// browser/utils.ts
export function samePagePath(a: string, b: string): boolean {
  return equalsIgnoreCase(a.replace(/\.md$/i, ''), b.replace(/\.md$/i, ''));
}

So /Group/sub-folder/page resolves (extensionless page URLs work), while /Group/sub-folder matches nothing. handlePotentialIndexVisit already redirects a group's own URL to its first page — this applies the same rule one level down.

Approach

Redirect rather than resolve. Rendering the index page at the folder URL would give one page two URLs, and the nav's active state compares currentURL via samePagePath, so the folder URL would highlight nothing. Redirecting also makes folder roots and group roots behave identically.

The target is the folder's first page. Sorting has already hoisted index.md to the top of the folder that holds it, so that is a folder's index page when it has one, and its first ordered page when it doesn't — one rule, both cases, and the same rule group.list[0] encodes at the group root.

Two subtleties, both called out in comments:

  • The new branch runs only after a page lookup misses. An ordinary page visit lands on the wildcard's index too, with the page as the wildcard param, and must be left alone — the invariant the existing comment in handlePotentialIndexVisit describes.
  • That page lookup is manifest-wide, not findByPath. findByPath searches currentGroup, which derives from router.currentURL — during a transition's beforeModel that still names the previous page.

And the mount translation: the wildcard holds only the part of the URL below the mount, so a mount with a path of its own needs its group put back on the front. A scoped mount names it in the binding; an unscoped nested mount takes it as its path (the route above the wildcard); a top-level mount's wildcard already carries it. The unscoped-nested case is the one I got wrong first — /demos/components was looked up as /components.

API

One method on the docs service, landingForFolder(appRelativePath), and two pure helpers in browser/utils.ts (findFolder, firstPageIn). Nothing existing changed shape.

Tests

App Covers
docs-app folder with an index (/authoring/authoring/index), folder without (/development/development/rendering-pages), trailing slash, casing, a page's URL is left alone, a path that is neither still errors
custom-root-url the rootURL-doubling hazard, alongside the existing group-root guard for it; trailing slash; casing; page-not-redirected
multiple-docs-routes a scoped mount (/help/getting-started, mount space ≠ manifest space) and an unscoped nested mount (/demos/components)

Results on this branch:

  • multiple-docs-routes — 18/18 pass
  • virtual-routes-only — 5/5 pass
  • docs-app via the dev server (/tests/?skipAllLinks) — 102 tests, 371 assertions, 0 failed, 5 skipped
  • custom-root-url — 12 pass, 4 fail; markdown-only — 1 pass, 1 fail

Those 5 failures are pre-existing on gitkrystan/collections. I verified by stashing this change, rebuilding, and re-running both apps: identical failures. They all come from the Home group's nav link becoming / instead of /Home, which those apps' nav-active-state and crawl snapshots still expect — worth a fix on universal-ember#366 before it merges.

One caveat: a built testem ci run of docs-app also showed 11 failures, all 120–180s timeouts in <ComponentSignature> / <HelperSignature> / <ModifierSignature> / gitRef. The dev-server run of the same suite is fully green and none of those modules touch routing, so I read them as environmental — but I did not baseline that particular run, since it takes ~20 minutes.

🤖 Generated with Claude Code

renovate Bot and others added 3 commits August 9, 2026 21:33
The entry for the co-located pages links at `rootURL + 'Home'`, where no
page is served: those pages live in the root URL space, under no group
name. The branch meant to handle that tests for a group named 'root', and
the build has named that group 'Home' since 158bf9d, so it never fires.

Two consequences. The link is wrong — a redirect round-trip through
`handlePotentialIndexVisit` where an app wires it on the top-level
wildcard (docs-app), a hard 404 where it does not
(test-apps/multiple-docs-routes). And `@homeName` does nothing, because it
is read only inside that dead branch.

The test app now passes `@homeName` and asserts the label, the root href,
the active state, and that nothing points at /Home. Verified against
main in kolay's own docs-app too.

Found while working on universal-ember#366, which touches this component; split out so it
can land on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The all-links crawl in markdown-only asserted an exact visit order that
included `/Home` seven times — the URL this fix stops linking to. With the
link pointing at the app root, the crawl follows the root's redirect to the
first group instead.

Caught by CI rather than by me: I ran lint and the one test app this diff
touches before pushing, and markdown-only is neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gitKrystan
gitKrystan force-pushed the gitkrystan/collections branch from 8465924 to 9a64466 Compare August 14, 2026 00:42
NullVoxPopuli and others added 3 commits August 13, 2026 22:56
…up-nav-home-link

fix(GroupNav): the Home link is the app root, not /Home
…node-24.x

chore(deps): update node.js to v24.19.0
`/Group/sub-folder` names a real place in the docs but no document of its
own, so visiting one rendered the error page. It now redirects to that
sub-tree's first page — the rule a group's own URL already follows, applied
one level down. These are URLs readers type and link by hand.

Path resolution only ever normalized the `.md` extension (`samePagePath`),
which is why an extensionless *page* URL resolved while a sub-tree's URL
matched nothing at all. The index-page concept already existed for the nav
(`getIndexPage`, and sorting hoists `index.md` to the top of its tree); this
puts it in front of routing, so a tree with an index page lands there and one
without lands on its first ordered page.

Two things worth knowing while reading this:

- The new branch runs only after a page lookup misses. An ordinary page visit
  lands on the wildcard's index too, with the page as the wildcard param, and
  must be left exactly where it is.
- That page lookup is manifest-wide rather than `findByPath`, which searches
  only `currentGroup` — and `currentGroup` derives from `router.currentURL`,
  which still names the *previous* page while a transition is being resolved.

The wildcard holds only the part of the URL below the mount, so a mount with a
path of its own needs its group put back on the front: a scoped mount names it
in the binding, an unscoped nested mount takes it as its path, and a top-level
mount's wildcard already carries it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gitKrystan
gitKrystan force-pushed the gitkrystan/folder-index-redirect branch from 88306e8 to 32b9bd6 Compare August 14, 2026 16:56
@gitKrystan

Copy link
Copy Markdown
Owner Author

Superseded by universal-ember#370 — rebased onto upstream main (this was stacked on the unmerged universal-ember#366) and renamed to the PageTree vocabulary that universal-ember#368 settled on.

@gitKrystan gitKrystan closed this Aug 14, 2026
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.

2 participants