Summary
The hosted viewer (app.screenmap.dev) crashes on load for any bundle that contains a node with urlPath: null, which the format contract allows and which the react-navigation provider produces for every screen missing from the linking config.
Uncaught TypeError: Cannot read properties of null (reading 'split')
at Lte (index-DzUlHmgp.js:121:7113) ← matcherFor(urlPath)
at index-DzUlHmgp.js:121:7330
at Array.map (<anonymous>)
at Rte (index-DzUlHmgp.js:121:7310) ← flowResolution(map)
at index-DzUlHmgp.js:185:1223
at Object.useMemo …
Source: apps/visualiser/src/lib/loadBundle.js
function matcherFor(urlPath) {
const re = urlPath
.split('/') // urlPath is null for navigation-only screens
flowResolution() builds a matcher for every node (map.nodes.map((n) => ({ id: n.id, re: matcherFor(n.urlPath) }))), so one URL-less node is enough to take the whole viewer down. The other urlPath uses in the viewer (nodeLabel, the / root lookup in layout.js) are already null-safe.
Reproduction with the repo's own fixture (no external app needed)
cd fixtures/rn-demo-app
node ../../plugins/screenmap/skills/screenmap/scripts/parse-routes.mjs .
mkdir -p .screenmap/out/screens
node ../../plugins/screenmap/skills/screenmap/scripts/pack-map.mjs .
# → "RN Demo-….scrmap" with 5 nodes; Onboarding has "urlPath": null
Drop that bundle on app.screenmap.dev (or apps/visualiser via npm run dev): blank screen + the exception above. The expected-graph.json of that fixture already records "urlPath": null for Onboarding, and docs/scrmap-format.md documents the field as "NULL when the screen has no URL (normal in react-navigation)", so the bundle is conformant; the viewer is the outlier.
Real-world scale: a react-navigation app I mapped (149 screens) has 100 navigation-only screens, so every bundle from it is unloadable in the viewer.
Fix
A URL-less node can never be reached by a deep link, so give it a matcher that matches nothing:
// Route pattern matching: ties flow deep-link URLs back to nodes.
function matcherFor(urlPath) {
+ if (urlPath == null) return /(?!)/
const re = urlPath
.split('/')
Verified locally against the fixture bundle above and the 149-screen bundle: both load, flows resolve via their screen sidecar fields as before. Happy to open a PR with this if you prefer.
Related (smaller)
plugins/screenmap/skills/screenmap/scripts/render-map.mjs (the static HTML fallback) crashes on v2 flow sidecars for a similar reason: flowsSection() does (f.steps ?? []).map(flowStep), but v2 sidecars key steps by YAML index (an object), so .map is not a function. I have a patch that rebuilds the flat step list from the YAML + sidecar; can include it in the same PR.
Summary
The hosted viewer (app.screenmap.dev) crashes on load for any bundle that contains a node with
urlPath: null, which the format contract allows and which the react-navigation provider produces for every screen missing from the linking config.Source:
apps/visualiser/src/lib/loadBundle.jsflowResolution()builds a matcher for every node (map.nodes.map((n) => ({ id: n.id, re: matcherFor(n.urlPath) }))), so one URL-less node is enough to take the whole viewer down. The otherurlPathuses in the viewer (nodeLabel, the/root lookup inlayout.js) are already null-safe.Reproduction with the repo's own fixture (no external app needed)
Drop that bundle on app.screenmap.dev (or
apps/visualiservianpm run dev): blank screen + the exception above. Theexpected-graph.jsonof that fixture already records"urlPath": nullforOnboarding, anddocs/scrmap-format.mddocuments the field as "NULL when the screen has no URL (normal in react-navigation)", so the bundle is conformant; the viewer is the outlier.Real-world scale: a react-navigation app I mapped (149 screens) has 100 navigation-only screens, so every bundle from it is unloadable in the viewer.
Fix
A URL-less node can never be reached by a deep link, so give it a matcher that matches nothing:
// Route pattern matching: ties flow deep-link URLs back to nodes. function matcherFor(urlPath) { + if (urlPath == null) return /(?!)/ const re = urlPath .split('/')Verified locally against the fixture bundle above and the 149-screen bundle: both load, flows resolve via their
screensidecar fields as before. Happy to open a PR with this if you prefer.Related (smaller)
plugins/screenmap/skills/screenmap/scripts/render-map.mjs(the static HTML fallback) crashes on v2 flow sidecars for a similar reason:flowsSection()does(f.steps ?? []).map(flowStep), but v2 sidecars keystepsby YAML index (an object), so.mapis not a function. I have a patch that rebuilds the flat step list from the YAML + sidecar; can include it in the same PR.