Summary
Upgrading an app from stx 0.2.231 → 0.2.369 makes <script server> relative imports resolve to the wrong directory. The modules exist; the specifiers are the ones the app has always used. stx then replaces the whole component with a visible red error banner, and the build still reports success — so this ships.
In our app, 13 of 46 built pages came out as a banner with their content gone, including every signed-in dashboard page and the public homepage. The banner prints an absolute filesystem path to the visitor.
stx: a <script server> imports a module that does not resolve, so this component rendered nothing.
/…/status/resources/views/layouts/default.stx
ResolveMessage: Cannot find module '/…/hq-apps/app/lib/display'
from '/…/status/node_modules/@stacksjs/stx/dist/chunk-wph1s6mm.js'Note the resolved path: hq-apps/app/lib/display. The app root is hq-apps/status/, and the file is at hq-apps/status/app/lib/display.ts. It landed one level above the app root.
What changed
Same checkout, same source, only the dependency versions differ (@stacksjs/stx, bun-plugin-stx, @stacksjs/components all moved together):
| pages with the error banner | visual:validate | |
|---|---|---|
| 0.2.231 | 0 / 46 | passes |
| 0.2.369 | 13 / 46 | fails |
Evidence
Every failing page, with the specifier it used and where 0.2.369 resolved it. Paths are shortened at hq-apps/; the app root is hq-apps/status/.
| view (dir depth under app root) | specifier | correct target | 0.2.369 resolved to |
|---|---|---|---|
resources/views/dashboard/settings/billing.stx (4) | ../../../../app/lib/teamContext | status/app/lib/teamContext | hq-apps/app/lib/teamContext |
resources/views/dashboard/settings/cloud.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/settings/notifications.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/settings/security.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/settings/team.stx (4) | ../../../../app/lib/display | status/app/lib/display | hq-apps/app/lib/display |
resources/views/dashboard/monitors/index.stx (4) | ../../../../app/lib/display | same | hq-apps/app/lib/display |
resources/views/dashboard/monitors/new.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/incidents/index.stx (4) | ../../../../app/lib/display | same | hq-apps/app/lib/display |
resources/views/dashboard/servers/index.stx (4) | ../../../../app/lib/display | same | hq-apps/app/lib/display |
resources/views/dashboard/maintenance/index.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/status-pages/index.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/dashboard/status-reports/index.stx (4) | ../../../../app/lib/teamContext | same | hq-apps/app/lib/teamContext |
resources/views/index.stx (2) | ../../../app/lib/authRequest | — see below | hq-apps/app/lib/authRequest |
And the pages at the same import style that do still resolve correctly at 0.2.369:
| view (dir depth) | specifier | result |
|---|---|---|
resources/views/dashboard/index.stx (3) | ../../../app/lib/display | resolves |
resources/views/dashboard/onboarding.stx (3) | ../../../app/lib/teamContext | resolves |
So a specifier with four ../ from a four-deep view directory lands one level too high, while a specifier with three ../ from a three-deep directory is fine. I could not pin the mechanism down further from outside — compileTemplate() returns serverScriptContent unevaluated, so the resolution happens in a later stage I could not isolate into a standalone repro.
Two separate behaviour changes may be tangled here, because resources/views/index.stx is a different case from the rest: its specifier has one more ../ than its own depth warrants, so page-relative resolution should send it above the app root. It resolved fine at 0.2.231 and fails at 0.2.369. That suggests 0.2.231 tolerated excess ../ (anchoring at the app root) and 0.2.369 does not — worth deciding deliberately either way, since the stricter behaviour is arguably correct for that one page and is a breaking change for anyone relying on the old leniency.
Why this is worse than a resolution bug
- The build reports success.
Total: 45 pages / Success: 45 / Failed: 0, while 13 of them contain nothing but the banner. A page carrying the error is a rendered page as far as the builder is concerned. - The error is user-facing. It renders into the served HTML, in monospace, including an absolute filesystem path from the build machine.
- Nothing in the log names an import. What actually caught it for us was an unrelated responsive gate: the banner's long unwrapped path overflows a 390px viewport, so it surfaced as
/dashboard/monitors at mobile: 176px horizontal overflow. Without that check we would have deployed it.
A hard build failure naming the file and the specifier would be strictly better than a banner — related to #2004, which made the same point about unresolved components. The same reasoning applies to unresolved modules.
Reproduction
In an app whose views sit four levels under the app root and import a local module with a matching ../../../../ specifier from <script server>:
app/lib/display.ts # exists
resources/views/dashboard/monitors/index.stx # <script server> imports '../../../../app/lib/display'bun add @stacksjs/stx@0.2.231 bun-plugin-stx@0.2.231→ build, page rendersbun add @stacksjs/stx@0.2.369 bun-plugin-stx@0.2.369→ build, page is a banner readingCannot find module '<one level above app root>/app/lib/display'
Environment
@stacksjs/stx0.2.369 (broken) / 0.2.231 (good)bun-plugin-stx0.2.369,@stacksjs/components0.2.369stacks0.75.37, Bun 1.4.3-canary.1, macOS (also reproduced onubuntu-latestin CI)- Registered via
bunfig.toml→[serve.static] plugins = [ "bun-plugin-stx" ]