Skip to content

[Bug]: Web app crashes on load — raw NUL bytes in useProjectRunStatuses.ts cause 'literal not terminated' SyntaxError #8475

Description

@TwFlem

What happened?

The web app crashes on every page load with an uncaught syntax error, so the UI never renders (white screen / Next.js error overlay):

Uncaught SyntaxError: '' literal not terminated before end of script
    at (app-pages-browser)/./src/hooks/useProjectRunStatuses.ts (…SettingsDia-16f400.js:11103:1)
    at … EntryNavRail.tsx → EntryShell.tsx → EntryView.tsx → App.tsx → ClientApp → Page

The stack is always the same, it is deterministic (not a flaky dev-server race), and it survives a full tools-dev restart.

Root cause

apps/web/src/hooks/useProjectRunStatuses.ts contains raw NUL bytes (0x00) inside string literals, where the escape \u0000 should be. The byte 0x00 is committed directly into the source (GitHub serves the file as application/octet-stream because of it):

const idsKey = useMemo(() => [...projectIds].sort().join('<0x00>'), [projectIds]);   // line 238
...
const subscription: Subscription = { ids: idsKey.split('<0x00>'), contextRef };      // line 245
...
for (const projectId of idsKey.split('<0x00>')) {                                     // line 253

(<0x00> = a literal NUL byte, not text.)

Modern Chromium/V8 rejects a raw control byte inside a string literal as an unterminated string, which produces '' literal not terminated before end of script. Node's older V8 (e.g. Node 24, V8 13.6) tolerates it, which is why this only surfaces in the browser.

Steps to reproduce

  1. Check out main at 1b47e60 (2026-09-24).
  2. corepack enable && pnpm install
  3. pnpm tools-dev run web (or start web) and open http://127.0.0.1:5175.
  4. Observe the page fails to load with the syntax error above.

Expected behavior

The app should render normally. The join/split separator should be the escape \u0000, not a raw NUL byte.

Proposed fix

Replace the 3 raw 0x00 bytes with the \u0000 escape:

-  const idsKey = useMemo(() => [...projectIds].sort().join('^@'), [projectIds]);
+  const idsKey = useMemo(() => [...projectIds].sort().join('\u0000'), [projectIds]);
-    const subscription: Subscription = { ids: idsKey.split('^@'), contextRef };
+    const subscription: Subscription = { ids: idsKey.split('\u0000'), contextRef };
-    for (const projectId of idsKey.split('^@')) {
+    for (const projectId of idsKey.split('\u0000')) {

This matches the escaping already used elsewhere in the codebase (e.g. join('\u0001'), join('\u0002')).

Environment

  • OpenDesign version: 0.23.1 (commit 1b47e60bd46641469fcd8b69c496c4e3a548bc28)
  • Platform: Linux (Fedora 44)
  • Toolchain: Node v24.21.0, pnpm 10.33.2
  • Web: Next.js 16.2.6 (Webpack dev server)
  • Browser: Chromium-based (Brave) — the error is V8-version-dependent

Verification

Confirming the committed blob contains the raw bytes:

$ git show HEAD:apps/web/src/hooks/useProjectRunStatuses.ts | tr -cd '\000' | wc -c
3

After replacing them with \u0000, the page loads (GET / → 200) with no error.

Activity

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinggood first issueGood for newcomershelp wantedExtra attention is needed

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions