Skip to content

feat(use-react): a link addressed by route name, and stone dev starts again - #271

Open
evens-stone wants to merge 1 commit into
mainfrom
fix/react-dev-mcp-dev-and-alias
Open

feat(use-react): a link addressed by route name, and stone dev starts again#271
evens-stone wants to merge 1 commit into
mainfrom
fix/react-dev-mcp-dev-and-alias

Conversation

@evens-stone

Copy link
Copy Markdown
Contributor

Two of the four reported bugs, verified and fixed. The other two are reported below rather than fixed, with the measurements that say why.

stone dev has been dead for React applications

Reproduced on the published starter, not read from the code:

npx stone dev
  import { runDevServer } from '@stone-js/cli'
  SyntaxError: The requested module '@stone-js/cli' does not provide an export named 'runDevServer'

The generated entry imported the runner from @stone-js/cli. Both runners moved into this package when it took over the React build; the template kept naming the package it had left. It now imports from @stone-js/use-react/cli, which is where they are.

One correction to the report: it has been broken since the build moved (0.8.11), not since 0.8.19. Which is worse, and says something: no test starts a dev server, and the generated file is only ever executed by one.

The two tests covering that template asserted the import as a string, so they stayed green right through the move. They check the specifier and the name against the module now, so moving or renaming a runner fails in the suite instead of at the first stone dev somebody runs.

StoneLink takes a route name

<StoneLink name='notes.show' params={{ id: note.id }}>Read it</StoneLink>
<StoneLink name='notes.index' query={{ page: 2 }} hash='top'>Page 2</StoneLink>

Writing a path in a component makes the component own what the router owns: the day the path changes, every link that spelt it out is wrong and nothing says so. Everything router.generate() accepts is a prop (params, query, hash, protocol, withDomain), and the generation is the router's own, so a link and a redirect built from the same name cannot disagree. A name nobody declared warns and renders inert instead of producing a broken address that looks fine.

The resolution lives in @stone-js/use-react-core, as useLink — an anchor is a browser element and a route name is not, so a React Native component calls the same hook and renders whatever it likes:

const { href, isCurrent, navigate } = useLink({ name: 'notes.show', params: { id } })

That split is also what fixes a bug nobody had reported: the selected class compared the generated /notes/42 against the current route's pattern /notes/:id, which are never equal, so a link to any parameterised route was never highlighted. A named link is compared by name and by the parameters it named; one naming no parameter is current for every value of them, which is what a nav highlight wants.

href and to keep working, to still accepting a path or NavigateOptions. The props are a union, so the compiler asks for one of the three.

What I did not fix, and why

SJ-68, i18n as a silent no-op in the browser: not reproduced. The stated mechanism is that blueprint.set(i18nBlueprint) from a @Configuration does nothing. Measured on the published @stone-js/config: it deep-merges correctly, providers and kernel.middleware concatenated, everything else intact, stone.i18n present. And @Configuration.configure() runs during setup, before the kernel reads providers. So something else is at work, most likely the configuration class not reaching the client bundle's module list, which would explain SSR translating while hydration does not. It needs the app: tell me whether that file is in the client bundle's modules and I will take it from there. Their own fix, @I18n() on the Application, is the sanctioned activation path anyway.

SJ-69, a middleware alias resolving itself: already diagnosed, not silent. Measured: a middleware aliased auth whose constructor asks for auth gets ContainerError: Circular dependency detected: SignedIn → SignedIn. The framework names it. Renaming the alias was the right fix.

What I did find next door is real but is not what was reported, and I reverted it rather than ship it: two different bindings can claim the same alias and the second silently wins (measured). Overwriting is asserted deliberately by the existing suite, and forbidding it would break an application overriding a first-party service. In a primitive everything depends on, that is your call, not mine.

SJ-67, @stone-js/mcp-dev in the browser bundle: real, and my fix was wrong, so it is not here. Only two files touch node:*, and the build config already claims to exclude browser stubs that do not exist. I wrote them, and measured the result: the browser bundle still carried node:fs, because three other modules import those two files and rollup follows the graph whatever the input glob says. Excluding inputs is not enough; it needs a resolution override, designed and bundled-tested. Shipping a browser condition that still imports node:fs would be a false promise, so I reverted.

Also: eight security advisories

The lockfile audit refused, on both lockfiles, without a dependency having changed here. browserslist is the one that matters, a direct dependency of @stone-js/use-react with two high advisories; its range already allowed the patch. fast-uri, qs and @xmldom/xmldom are transitive and pinned through overrides in the lockfile that owns each, since a nested lockfile does not inherit the root's.

Verification

pnpm run verify green across the workspace. @stone-js/use-react-core 119 tests with link.ts at 100% on all four gauges, @stone-js/use-react 272 tests.

🤖 Generated with Claude Code

…ts again

Two things a browser found that no test could.

`stone dev` has been dead for React applications since the build moved into this
package: the generated entry imported `runDevServer` from `@stone-js/cli`, which
stopped exporting it, and the application died on a SyntaxError before running a
line. Reproduced on the published starter. The two tests covering that template
asserted the import as a string, which is why they stayed green through the move;
they check the specifier and the name against the module now.

And `StoneLink` takes a route name. Writing a path in a component makes the
component own what the router owns: the day the path changes, every link that spelt
it out is wrong and nothing says so. `name` plus everything `router.generate()`
accepts, generated by the router itself, so a link and a redirect built from the
same name cannot disagree. `href` and `to` keep working.

The resolution lives in `@stone-js/use-react-core` as `useLink`, because an anchor
is a browser element and a route name is not: a React Native component calls the
same hook and renders what it likes. That is also why a parameterised link is
highlighted correctly for the first time: the selected class compared a generated
`/notes/42` against the pattern `/notes/:id`, which are never equal.

Also eight security advisories published since the last release, which the lockfile
audit refused without a dependency having changed here: `browserslist` refreshed
(direct, two high), and `fast-uri`, `qs` and `@xmldom/xmldom` pinned through
overrides in the lockfile that owns each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant