Put a versions literal in front of the version detail route

What this does

Puts a versions/ literal in front of the version detail route, so it reads /:id/:artifactId/versions/:versionId rather than /:id/:artifactId/:versionId.

Follow-up to this thread on !253776 (merged), where @rchanila preferred option 1 — literals for both the manifest and the version segment — over leaving the two asymmetric.

Why

A bare :versionId claimed every unreserved third segment under an artifact. That is why manifests needed a literal to escape it while a version did not have one, and reading the two routes side by side the asymmetry looked like an oversight rather than a decision. It also meant a bare parameter was sitting on every segment a later step might want to hang there.

Both routes lead with a literal now, so neither can claim the other's segment and the order between them stays free.

What a bare segment does now

This is the question on the thread. Before:

Path Resolved to
…/manifests/sha256:abc manifest detail
…/manifests version detail, versionId: 'manifests' → that page's own not-found state
…/3.2.1 version detail

After:

Path Resolved to
…/manifests/sha256:abc manifest detail
…/versions/3.2.1 version detail
…/manifests Page not found (catch-all route)
…/versions Page not found (catch-all route)
…/3.2.1 Page not found (catch-all route)

So yes — Page not found, and via the real not-found route rather than a page rendering its own empty state. All five cases are in the resolution table in index_spec.js, driven from the path rather than by name, since a push by name cannot fail path matching.

The only version-detail link in the app is the version list's row (versions_table.vue), and it pushes by route name, so it picks the new path up on its own. The breadcrumb trail derives its hrefs from the resolved route. I swept every router-link, :to, and $router.push in the feature to confirm nothing builds a version path by hand, and checked that nothing outside the SPA deep-links into one.

A test pins that resolving by name lands on the prefixed path, since that is what actually decides the URL every caller gets.

Verification

  • 1854 Jest tests pass across the artifact registry suite. Two suites (repository_detail_spec.js, version_list_spec.js) fail to run locally on missing generated GraphQL fixtures — pre-existing and unrelated; CI generates them.
  • ESLint and Prettier clean.
  • Not driven in a browser. The change is entirely route matching, and the layer that decides the URL is pinned directly by the name-resolution and breadcrumb-href tests.

Breaking change, and why it is fine here

Deep links to the old shape stop resolving. Nothing outside the SPA builds one, and the surface is pre-GA behind the organization stage flags.

Stacked

Targets zcuddy-s06p2-step1-manifest-detail-page (!253776 (merged)), so review that one first. Retarget to master once it merges.

Merge request reports

Loading
Loading