Commit Graph

9 Commits

Author SHA1 Message Date
Colby Mchenry 1f64e15886 fix(react-router): read links through a route-config object's getHref (#2187)
bulletproof-react writes every link as `<Link to={paths.app.discussion.getHref(id)}>`
and every redirect as `navigate(paths.auth.login.getHref(...))`, so none of its
navigation reached a route. The href reader now follows the config object to
where it is declared (through imports and tsconfig aliases), takes the string or
template the helper returns, keeps a whole-segment `${id}` as a parameter and
drops a hole glued onto a segment (a `?redirectTo=` suffix).

The Link tag pattern also stopped at the `>` of an arrow attribute written
before `to` (`onMouseEnter={() => prefetch(id)}`), which hid those links.

bulletproof-react: 0 -> 11 navigates edges; jira_clone, ecommerce-react,
react-redux-realworld, create-t3-turbo and excalidraw unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 18:17:06 +00:00
Colby Mchenry 37bcd5c0ae fix(react-router): nested, lazy and constant routes; per-app tsconfig aliases (#2165)
- Route tables compose nested paths: a child's `path` is relative to the
  path-bearing routes around it (`children` of a data router, nested
  `<Route>` elements), absolute ones reset. `lazy: () => import('./x')`
  (object and JSX) renders the module's default export (`Component` export
  as fallback) via a claimed `lazy-import:` ref. `path: paths.app.root.path`
  keeps its parts on the node's signature and is named in postExtract from
  the constant's object literal (found through the file's import). The
  route keeps its extracted id; `isReactRouterRoute` accepts it. An element
  wrapped in parentheses or a guard (`<ProtectedRoute>`, `<Suspense>`,
  `*Provider`, `*Guard`) renders the first element inside.
- Import aliases: the tsconfig / jsconfig nearest the importing file that
  declares `paths` is tried before the root's, so each monorepo app resolves
  its own `@/*` / `~/*`.

A/B (edges removed / added): bulletproof-react -43/+367 (0 → 9 named,
linked routes; cross-app imports fixed), react-native-reusables -103/+193,
kit -184/+218, trpc -45/+71 (examples' `~/…` now their own), create-t3-turbo
-1/+13; excalidraw, halo, obytes, realworld byte-identical. #1348's nested
fixtures now expect composed paths (`/dashboard/settings`, `/data/prefs`).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:34:11 +00:00
Colby Mchenry cec041d241 fix(angular): Nx workspaces, barrels, class-constant paths and root-relative navigate (#2133)
From the router sweep:
- angular-spotify had 12 routes all named `/` and no navigation. Its lazy
  routes are `async () => (await import('@ws/home')).HomeModule` through an
  Nx library's `src/index.ts` barrel, which the mount pass never looked
  through; and every lib is its own `src/`, so each routes file got its own
  route table and no template's `routerLink` found the screen it names.
- jira-clone had 3 routes and no navigation: its issue route is
  `issue/:${ProjectConst.IssueId}` (a class `static readonly`), its root
  redirect lives in `app.routes.ts`, which only mounts, and
  `navigate(['project', 'issue', id])` has no leading `/`.

Now: lazy mounts follow `export … from` barrels (and an NgModule's routing
imports); `(await import(x)).M` is read; route constants may be class
statics (strings or objects), enum members, or live behind a barrel, and a
template-literal path takes constant holes; a routes file that only mounts
contributes its redirects at its mount prefix; the route table is keyed by
the `angular.json` / `nx.json` workspace; and `router.navigate` /
`createUrlTree` without `relativeTo` resolve a bare first command from the
root, as Angular does (template `routerLink` stays relative).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 07:06:23 +00:00
Colby Mchenry 4423891e76 fix(react-router): v5 <Redirect to> and styled(Link) wrappers navigate (#2132)
react-boilerplate's header links are `export default styled(Link)`…``,
used as `<HeaderLink to="/features">`; takenote's guards render v5's
`<Redirect to="/" />`. Neither tag was read, so both apps had routes and
no links between their screens.

The link synthesizer now reads `<Redirect to>` (navMethod `redirect`) and
any styled-components / emotion wrapper of `Link` / `NavLink` — declared in
the file, or imported from a file that declares or default-exports one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:55:41 +00:00
Colby Mchenry b62da73550 fix(sveltekit): route names drop (group) folders and parameter matchers (#2131)
A `(group)` directory shares a layout and is never part of the URL, and a
parameter's `=matcher` checks its value. Kept in the name, shadcn-svelte's
`src/routes/(app)/(layout)/blocks/+page.svelte` was `/(app)/(layout)/blocks`,
so no `goto('/blocks')` or `<a href="/blocks">` ever reached it and its
Screens showed no navigation at all.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:50:41 +00:00
Colby Mchenry 54a1698caa fix(vue-router): admin-template route tables, children and layouts; Nuxt routes only in Nuxt (#2130)
vue-element-admin, vue-admin-template and vben had no routes: their tables
are named arrays (`export const constantRoutes = [...]`, `const routes:
RouteRecordRaw[] = [...]`) or per-module objects (`const tableRouter = {…}`)
handed to `new Router(...)`, and most of their screens are `children`.

The Vue Router reader now reads those tables in any file that builds a
router, imports vue-router or lives in a router/ directory; joins children
onto their parent's path (a parent is a screen only when no child claims
its address and it doesn't redirect); binds a lazy view by the FILE it
imports (all of vue-element-admin's are `…/index.vue`), through an alias the
resolver can't follow by the one file in the app with that path; links each
screen to its parents' components as layouts; and takes `this.$router.push`.

Nuxt's file routes move into their own `nuxt` resolver, detected only in a
Nuxt app: halo's plain-Vue console got 30 made-up screens from its `pages/`
folders. A Nuxt app at the repository root now gets its routes, and a
top-level `pages/index.vue` is `/`, not `/index`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 06:46:26 +00:00
Colby Mchenry c3efbbf4e7 feat(angular): a template's event bindings call the component's methods (#2114)
An Angular handler's only caller is its template: `(click)="toggleFavorite()"`,
`(ngSubmit)="submitForm()"`. With no edge, the method showed no callers,
impact stopped at it, and the Steps picture of a screen reached its
handlers only through class containment, with nothing to say what fires
them.

The template pass now reads each `(event)="…"` binding — not
`[(ngModel)]`'s two-way half — and links the component to every method of
its own the statement calls (`save()`, `this.toggle(x)`; not
`closed.emit()` or `form.reset()`), as a `calls` edge
(`synthesizedBy: 'angular-event'`) carrying the binding as
`metadata.trigger`: `{ kind: 'prop', name: '(click)', of: 'button' }`.
The binding is in the template, so it cannot be read back from the source
at the edge's line; Steps now takes a trigger an edge carries before
reading one at the site.

angular-realworld: 21 binding sites → 17 edges (3 repeats of a method and
event, 1 `delete.emit(true)`); Steps for /article/:slug draws
deleteArticle on (click) · <button> → DELETE and back to /, addComment on
(ngSubmit) · <form>, the favorite button's toggle → POST/DELETE or
/register. Ghostfolio 225 edges of 302 sites, ngx-admin 75 of 100 — the
rest are `$event.stopPropagation()`, output emits, assignments and
repeats. Controls (koel, proshop) byte-identical.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:51:30 +00:00
Colby Mchenry 761611c18e feat(angular): routes, navigation and the component tree for Angular apps (#2113)
An Angular app had no route nodes, no navigates edges and no link from a
template to what it renders — the viewer's Steps tab said "No screens or
endpoints" and there was no Screens tab.

Routes (frameworks/angular-router.ts): every `Routes` array — typed
`Routes` / `Route[]`, `RouterModule.forRoot/forChild`, `provideRouter`, a
routes file's `export default [...]` / `as|satisfies Routes` — walked as
objects. `component` binds by `references` (route-roots takes a class as
a named handler); a lazy `loadComponent` binds to the file's `@Component`
class. `children` join their parent's path; a lazily loaded file's routes
are put under the path that loads them in postExtract (following an
NgModule `loadChildren` to the routing module it imports), idempotently
from the in-file path kept in the qualified name. A route with children
is a layout: a screen only where no child claims its address, and linked
from each screen inside it (`references`, `layout: true`). Paths written
as route constants (`internalRoutes.account.path`, through destructured
locals and `const x + '/:id'`) and `$localize` strings are read from the
constant object. `redirectTo` aliases an address to the screen it sends
the user to; a root `**` redirect answers `/` only. A `matcher` route and
a `**` catch-all are not screens.

Navigation: `router.navigate([...])`, `navigateByUrl`, a guard's
`createUrlTree` / `parseUrl` — a command array (non-literal elements are
holes), a static string, a route constant, a component property holding
one, a function constant called with arguments, `.concat(id)`. A
`relativeTo` navigation, a query-only `navigate([])` and a destination
made only of holes (it would match any route of its length) are left
unresolved.

Templates (angular-template-synthesizer.ts): a component's `templateUrl`
file or inline `template:` is read at synthesis time. `<app-foo>` by
element selector (same app first; two candidates = none) is a `calls`
edge from parent to child (`angular-template`), the edge a navigation in
a child component rides to its screen. `routerLink` / `[routerLink]` and
a `routerLink:` field written in the class (a tab bar's config) are
navigates edges. Gated on an `@angular/core` dependency.

Measured:
- angular-realworld: 10 routes all bound; 11/11 navigation sites
  accounted for (10 resolved, 1 query-only); 31/31 routerLink sites
  resolved; 18 renders.
- Ghostfolio: 84 routes all bound, 0 unresolved constant paths; 27
  navigate resolved, 23 correctly not (8 relative, 9 query-only, 2
  computed, 4 aimed at a matcher route); 162 routerLink edges, every one
  checked against its site; 170 renders.
- ngx-admin (NgModule): 55 routes, 47 bound (8 are @nebular/auth's); no
  navigation calls in the source.
- Controls byte-identical vs main (nodes and edges incl. metadata):
  koel, IceCubesApp, spring-petclinic, proshop. Ghostfolio index time at
  parity.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 02:43:58 +00:00
Colby McHenry 6f4887db80 feat(steps): draw all arms of conditional navigations as separate edges
Adds multi-arm navigation support: when a destination is produced by a conditional, every arm is now drawn as its own edge. Introduces helpers (hrefArms, destinationsForHref) and updates framework resolvers and edge creation to emit multiple navigates edges (via alsoTargets) instead of a single one. Also introduces per-app rooted route tables to avoid cross-app crossings, and updates various resolvers (React Router, TanStack Router, Vue Router, SvelteKit, Vue, and SvelteKit’s linker) and the UI to reflect multiple possible destinations. Tests and docs updated to reflect the new behavior, ensuring the Screens tab shows all possible navigation paths from conditional destinations. This makes navigation visualization more accurate for forked destinations.
2026-08-31 11:34:26 -05:00