Park remaining local WIP on main. TUI archived, CLI/kernel edits, glass. Sort later. Broken is fine.

This commit is contained in:
2026-08-24 14:07:23 -05:00
parent f753506416
commit abb26a26ab
278 changed files with 4326 additions and 1067 deletions
+228
View File
@@ -0,0 +1,228 @@
# Fishbone: black TUI, split starts, leftover OpenCode
Fix **bottom to top**. Do not start at “blank screen” or a new `script/dev.ts`. Each layer can look like the same black window.
Date: 2026-08-24
Repo: `neuron`
Kit: sibling `neuron-tui`
Symptom: iTerm looks empty after launching the CLI, or the CLI dies before that with a loud error.
---
## Unproven (do not skip)
We have **not** shown that a high-contrast cell landed in **your** iTerm **alternate screen** on the current command.
iTerm “session contents” (AppleScript) is usually the **main** buffer. The TUI draws on the **alt** buffer. Empty contents is not proof of a blank app.
Until a bright first frame or a dump of the renderer buffer, “always blank” is several failures wearing one coat.
---
## Core lizard (cyclomatic)
Tool: **lizard** (CCN = cyclomatic complexity). Default warn is **15**. Run 2026-08-24 on `@neuron-tui/core` + native Zig.
**The JS presentation host is too big for lizard to see.** `renderer.ts` is 5130 lines. Lizard reports avg CCN **1.9** and **0 warnings**. That is a lie: it swallowed most of `CliRenderer` into `resolveModes` (595 lines). `zig.ts` is 6706 lines and lizard found **5 functions**. If a metric cannot split the file, the file is the problem.
Hand count of the JS hot path (if/else/switch + `&&`/`||`): `loop` ~32 branches / 122 nloc; `requestRender` ~10; `setupTerminal` ~14; `constructor` ~39. Not 1.9.
**Native is where lizard can see, and one function owns present:**
| CCN | nloc | fn | file |
|---|---|---|---|
| **189** | 394 | `prepareRenderFrameWithWriter` | `native/src/renderer.zig` |
| **104** | 240 | `checkEnvironmentOverrides` | `native/src/terminal.zig` |
| 58 | 107 | `processCapabilityResponse` | `terminal.zig` |
| 57 | 109 | `writeSnapshotCommit` | `renderer.zig` |
| 47 | 68 | `writeKittyImages` | `renderer.zig` |
`prepareRenderFrameWithWriter` is **12×** the warn line. It is the function that turns the JS buffer into TTY bytes. Skip lives next door: `prepareFrame` tryLock miss → `finishSkippedFrame` **clears** the buffer JS just drew. `renderer-output.zig` looks simple (avg CCN 2.6). The skip is small. The present is not.
JS outliers that are real parsers, not the blank path: `Markdown.createInitialStyledText` CCN **366** (lizard may have eaten the class), `toNativeAudioStreamFormat` 101, `parseAlign` 89, `parseKeypress` 84, `parsePositiveDecimalPrefix` 77.
**Fix from this list first:** split/log `prepareRenderFrameWithWriter`; do not wipe `nextRenderBuffer` on skip; do not run `checkEnvironmentOverrides` + alt-screen + first frame on the same mutex; split `CliRenderer` until lizard can name `loop` / `requestRender` / `setupTerminal`.
---
## Bottom → top (fix in this order)
### 0. Hold one test still
This chat mixed runs: Cursor aborted `serve`, AppleScript 0×0 windows, fake `script/dev.ts`, OpenTUI then `neuron-tui`, port 4096 leftovers. “Still blank” compared **different stacks**.
**Fix:** one command, real iTerm (not Cursor job), leave the engine alone, do not AppleScript-scrape alt-screen.
Current command (from any folder, including `~`):
```bash
bun --cwd /Users/will/Development/neuron-technologies/neuron/packages/cli --conditions=browser src/index.ts
```
`--cwd packages/cli` is required so `packages/cli/bunfig.toml` preloads `@neuron-tui/solid/preload`. That cwd also makes `process.cwd()` = `packages/cli` (the “project” the engine/TUI think you opened). That is a later layer.
---
### 1. Core: native library (`neuron-tui`)
**What:** Zig renderer. On darwin arm64 JS does `import("@neuron-tui/core-darwin-arm64")`.
**Fact:** that name **404s on npm**. OpenTUIs `@opentui/core-darwin-arm64` **is** published. Rename did not ship binaries.
**Error (loud):** `Failed to initialize OpenTUI render library: Cannot find package '@neuron-tui/core-darwin-arm64'` — leftover **OpenTUI** string in **your** core.
**Done locally:** workspace package `neuron-tui/packages/core-darwin-arm64` copies `packages/native/lib/aarch64-macos/libopentui.dylib` (built ~Aug 22). CLI/TUI depend on it.
**Still open:** JS is today; dylib is another day. ABI mismatch → black, **no** JS throw. Rebuild native from current `neuron-tui` when fixing core for real. Other platforms still 404 (linux/win optional deps).
---
### 2. Core: Mac thread vs Linux, skip vs fail
**What:** `CliRenderer` constructor sets `useThread: true` except **Linux** (forced off; comment: thread **crashes**). You are on Mac.
**Two output machines:**
| Path | What happens on native `render` status `SKIPPED` (1) |
|---|---|
| Tests / custom stdout | `NativeSpanFeed` → wait `feed.idle()` → retry |
| iTerm `process.stdout` + thread | **No feed.** `stdout.write` hijacked to `lib.writeOut`. Skip → `"backpressured"`, **no** `console.error` |
**Alt-screen** is `lib.setupTerminal(..., alternate-screen)` — wipe is **native**, before Solid has a tree.
**Fix direction:** `useThread: false` on darwin until skip is visible; or log skip; or use feed on TTY; rebuild native; prove `lib.render` returns presented not skipped.
---
### 3. Core: idle one-shot vs live loop
**What:** `@neuron-tui/solid` `render()` does `engine.attach` (timeline) + mount. Tests call `renderer.start()`. The **app does not**.
Paint is `requestRender()` → one `activateFrame` if `_isRunning` is false.
`activateFrame` **no-ops** if `updateScheduled` is already false.
`internalStart()` **clears** `updateScheduled` so queued one-shots die when the live loop starts. Idle shots and the continuous loop **cancel each other by design**.
**Fix direction:** `renderer.start()` (or `requestLive`) after mount so skipped frame 0 is not the only chance. Do not “fix” this by adding another wrapper script.
---
### 4. Core: queries and pixels on the same hijack
`setupTerminal` returns immediately. It has already:
1. switched alt-screen
2. turned on capability probes (`privateCapabilityRepliesActive`)
3. written those probes through **the same** `lib.writeOut` as frames
iTerm replies on stdin. Frame 0 is `process.nextTick`. Probes and first paint **contend**. Skip on frame 0 is “queue full,” not “JS forgot to draw.” The 5s timer is **stop asking**, not **dont draw yet**.
Constructor also replaces **global** `requestAnimationFrame`. If the renderer is idle, rAF UI sleeps too.
**Fix direction:** dont probe on the presentation path; or dont hijack stdout until after first present; or `useThread: false` so `stdout.write` is real TTY again.
---
### 5. Errors after core owns stdout can vanish
Loud errors you pasted (`Failed to start server`, `react`, missing dylib) happened **before** hijack.
Bootstrap (`sync.tsx`) uses `throwOnError: true` and `exit(e)` **after** the TUI is up. Those logs can go into Zig. **No `ERROR (#1)` + black ≠ no error.**
**Fix direction:** log bootstrap failures to a file, or restore `realStdoutWrite` for `console.error`.
---
### 6. `--conditions=browser` is a pun
**Needed:** `solid-js` client build (`dist/solid.js`), not `dist/server.js`.
**Also:** Bun treats it as “browser app” → default JSX factory **React**.
`--cwd` **repo** skips `packages/cli/bunfig.toml` → no `@neuron-tui/solid/preload``Cannot find package 'react'`. Root bunfig preload failed: `@opentui/solid/preload` not installed at root (and that is not your kit).
**Fix direction:** keep CLI bunfig preload; do not put kit preload on repo root; or pass `--jsx-import-source @neuron-tui/solid` explicitly; split “solid client resolve” from “browser JSX.”
---
### 7. One `--cwd`, three jobs
| Job | Wants |
|---|---|
| bunfig / Solid preload | `packages/cli` |
| `process.cwd()` = project for engine/TUI | neuron repo (or the users real project) |
| Finding `src/index.ts` | a path that exists from **this** shell (`~` vs repo) |
`--cwd packages/cli` from `~` → ENOENT (`packages/cli` is relative).
`--cwd` repo → JSX React.
Old `script/dev.ts` climbed **twice** (`URL ..` then `dirname`) → `neuron-technologies/` not `neuron/`. Deleted; do not bring it back.
**Fix direction:** absolute CLI path + set process cwd to the repo **after** bun loads bunfig, or `bun --cwd cli` plus `NEURON_CWD` / engine `cwd` override. Never one relative `--cwd` for all three.
---
### 8. Two bosses for “is the engine up”
CLI daemon: alive = `~/.neuron/server.json` **and** `/global/health`.
A live `bun ./src/serve.ts` on **4096** with **no** file = “no server.” Child spawn uses `stdio: ignore`. Wait → `Failed to start server`.
Daemon `start()` now probes default `http://127.0.0.1:4096` if registration is missing.
**Fix direction:** one authority (health on the port, or always `--register`). Never hide child stderr. Dont spawn a second listener on 4096.
---
### 9. Painted empty ≠ unpainted
`packages/tui` is still OpenCodes **client** (many HTTP routes, plugins, home layout). Neuron answers a lot with **empties** so the client wont invent data.
Home: two `flexGrow={1}` spacers, 7-row logo, prompt. Wordmark PNG is cream on **black**; app clear `#0a0a0a`. `StartupLoading` only appears if `ready()` is false after 500ms; App `ready` starts **true****no spinner**.
A **successful** frame can look dead.
Settings `~/.local/state/opencode/kv.json` used to **hide the whole tree** until a flock lock finished (`createSimpleContext` + `Show when ready`). That gate was removed; file is optional. **Do not treat kv as the remaining blank.**
Logo `protocol="auto"` (Kitty/iTerm handshake) could hang with no ASCII fallback. Now `protocol="blocks"` on the same PNG. Contrast still terrible.
**Fix direction:** first frame **high contrast** (e.g. yellow “neuron” on black) **before** logo/image; `renderer.start()`; then put the wordmark back. Do not use alt-screen scrape as pass/fail.
---
### 10. Product vs kit (do not confuse)
| Piece | What it is |
|---|---|
| `neuron-tui` | Your **kit** (Zig + Solid reconciler). Not the Neuron chat app. |
| `packages/tui` | OpenCode **screens** now importing `@neuron-tui/*`. |
| `packages/cli` | Launcher + daemon; still `@opencode-ai/cli`. |
| `packages/neuron` | Engine HTTP on 4096. |
| `script/dev.ts` | Fake one-command runner. **Deleted. Do not restore.** |
Root `package.json` workspaces include sibling `../neuron-tui/packages/{core,solid,keymap,react,core-darwin-arm64}`. Nested workspace + npm 404s on other `core-*` platforms remain.
---
## Loud errors already explained (not “blank”)
| Paste | Cause |
|---|---|
| `Could not change directory to "packages/cli"` | Relative `--cwd` from `~` |
| `Failed to start server` | Daemon vs leftover 4096 / no `server.json` |
| `Cannot find package 'react'` | JSX default React; CLI bunfig not loaded |
| `preload not found "@opentui/solid/preload"` | Root bunfig; not your kit |
| `Cannot find package '@neuron-tui/core-darwin-arm64'` | Native never published; now local package |
---
## Not the users fault
Did not delete `packages/cli`. Did not need React. Did not “fail to use the TS build.” Bun/`package.json` already was that. Did not turn on a blank-screen mode.
---
## Suggested first patches (still bottom-up)
1. Rebuild `neuron-tui` native for this machine; keep `core-darwin-arm64` in workspace.
2. In `packages/tui` `createCliRenderer`: `useThread: false` until skip is logged.
3. After Solid `render()`, `renderer.start()`.
4. First paint: high-contrast text, not the black wordmark.
5. Then: one cwd story, engine health-only, bootstrap errors to a file.
When those five are true in **one** iTerm run, re-judge “blank.” Not before.