Known problems, ranked by what they cost a user or a new engineer. Each one has a reproduction or a file reference. Nothing here is a hunch.
Product defects live in promotion/PROMOTION_LOG.md with full reproductions;
this page is the engineering view and includes things that ledger does not
track.
runtime/nodeAgentChatAdapter.ts has no try/catch, and no component in the
tree is a React error boundary. A throw inside any rendered child unmounts the
whole app to a blank page with no message and no way back except reload.
This is not theoretical: it is exactly how defect D1 presented — two
Sigma: Container has no width. errors took the entire app down, and React said
so in the console ("Consider adding an error boundary to your tree"). D1's cause
was fixed; the missing boundary was not.
Where a fix goes: a boundary in components/NodeAgentDemoApp.tsx and a catch
in the adapter's generator that yields a failed-state message. Scored as
condition 5 FAIL in promotion/PRODUCT_GOAL.md.
Reproduction: at 1440x900 send a question, wait until
.na-tool[data-running="true"] appears, then enumerate every <button> on the
page. There is exactly one — Send — and it is disabled. A user whose run
misbehaves can only reload, discarding the thread.
The adapter does receive an abortSignal and checks it between steps
(nodeAgentChatAdapter.ts:98), so the runtime half already exists; what is
missing is a control wired to it. Scored as condition 2 FAIL.
useLocalRuntime holds one thread. Any thread-list affordance is a no-op. Do
not add one without changing the runtime underneath it.
A tool renders only if the toolName in components/toolUIs.tsx exactly matches
the name the adapter emits in runtime/nodeAgentChatAdapter.ts. A typo produces
no error — the card simply does not appear, and the run still reports ok.
Two files must agree, with nothing enforcing it. Adding a fifth step is where
this will bite.
npm audit --omit=dev used to report a high-severity advisory in nanoid
(GHSA-28wg-ghj8-5hjv, transitive via @assistant-ui/react), and it was the one
stage that failed npm run check from a clean checkout. package.json now
carries "nanoid@^5.0.0": "5.1.16" in overrides, next to the existing
esbuild and ws pins: the patched version is semver-compatible with what
@assistant-ui asks for, and the range key leaves postcss's CommonJS
nanoid@3 alone (forcing ESM 5.x on it would break the Vite build).
Plain npm audit, without --omit=dev, still reports 8 advisories including
that same nanoid one — it also covers <3.3.16, and vite → postcss pulls
nanoid@3.3.12. Every one of those is dev-only and none reaches the shipped
bundle, which is why the gate scopes itself to production dependencies.
The pin is the concern. It is our number, not the upstream's, so when
@assistant-ui bumps its own nanoid this override can silently hold the tree
back. Delete it, run npm install && npm audit --omit=dev, and if that is clean
the pin has done its job and should go.
npm run doctor (→ scripts/nodeagent-cli.ts) checks nine repo paths.
nodeagent doctor (→ bin/nodeagent.mjs) checks four packaged template files.
Both are legitimate — one serves a contributor, one a consumer of the published
package — but sharing a name means the answer to "did doctor pass?" depends on
which one you ran. Renaming touches the published CLI surface, so it was
recorded rather than changed.
Reaching a script through npm run x -- --flag value inside another npm run
inside a third is not reliable on Windows: the --dir <path> argument was
silently lost and the scaffolder exited 0 having written nothing. This cost
the repo a permanently failing npm run check that nobody noticed, because
check had never been run end to end. Both scaffold smokes now spawn
bin/nodeagent.mjs directly. Do not add a new smoke that reaches a tool
through a nested npm run … --.
index.html:11 loads Manrope and JetBrains Mono from fonts.googleapis.com.
On a restricted network the woff2 intermittently 404s (2 of 5 runs during the
promotion baseline) and the page falls back to system-ui. Self-hosting the two
fonts removes the app's only unconditional outbound request.
At 1440x900 after a run, node labels draw on top of each other ("NodeAgent",
"acme-dd", "Cash-runway sensitivity" collide) and the right-most label clips at
the canvas edge. Evidence: promotion/evidence/desktop-1440-run.png. A reader
cannot tell which node is which without dragging.
All four steps complete before the first card renders. The adapter paces them
out with await tick(360). Consequences: the abort signal only takes effect
between steps, and per-step latency is not real. This is a deliberate demo
simplification, but it is invisible from the UI, so it is written down here and
in docs/START_HERE.md Step 6.
Vite warns on every build. The bulk is the WebGL graph renderer and its
graphology/sigma dependencies. Code-splitting the rail behind a dynamic
import is the obvious move; it was not done because it changes load behaviour
and belongs with a measurement, not with a structural pass.
jscpd reports 24 clones / 287 duplicated lines, mostly parseArgs, writeJson
and the report shape shared by nodeagent-chat-ui-scaffold-smoke.ts and
nodeagent-local-dashboard-scaffold-smoke.ts. Extracting a helper trades ~90
duplicated lines for a new shared module imported by two dev scripts — roughly
neutral in concepts, so it was left.
It buckets by format, so a .mjs file is never compared against a .ts one.
The largest duplication this repo ever had — ~180 lines shared between
bin/nodeagent.mjs and scripts/nodeagent-cli.ts — was invisible to it. Do not
read a low jscpd score as an absence of duplication here.
No axe, no Lighthouse, no contrast check, no screen-reader pass. Keyboard
navigation was driven manually at 1440 only. Conditions 6, 7 and 8 in
promotion/PRODUCT_GOAL.md are UNVERIFIED, which is not the same as passing.
convex/schema.ts is never connected by the demo. The aws-dynamodb,
postgres and cloudflare adapter directories contain READMEs and schemas
only — no running code. scripts/nodeagent-cli.ts marks each as runnable or
blueprint; trust that list.