Skip to content

TuiHitRow renders undefined (evaluating 'props.trend.color') — sidebar slot silently unmounts via ErrorBoundary #5

Description

@yukishirataco

Environment

Item Value
opencode 1.18.10 (linux x64, bun runtime)
opencode-cache-hit 0.6.3 (npm @latest)
@opencode-ai/plugin SDK 1.18.10
@opentui/solid 0.4.5
OS linux x64
TUI config ~/.config/opencode/tui.jsonc"plugin": ["opencode-cache-hit@latest", ...]

Summary

On opencode 1.18.10, the cache-hit sidebar panel never appears. tui() is invoked, api.slots.register({ order: 56, slots: { sidebar_content } }) runs successfully, but sidebar_content throws during first render and opencode's per-slot <ErrorBoundary> (v1.17.0+, see README "Update" note) silently unmounts the slot — no crash screen, no stderr, no opencode.log entry. Only an in-process try/catch around the render call surfaces the error:

sidebar_content RENDER ERROR: undefined is not an object (evaluating 'props.trend.color')
TypeError: undefined is not an object (evaluating 'props.trend.color')
    at TuiHitRow (dist/index.js:470)
    at MainSessionView (dist/index.js:1647)
    at CacheHitSidebar (dist/index.js:2087)
    ...

Root cause

src/tui-panel/components.tsx:172TuiHitRow:

export function TuiHitRow(props: {
  label: string
  bar: string
  pct: string
  barColor: string
  textColor: string
  trend?: { text: string; color: string }   // optional
}) {
  return (
    <text>
      ...
      <Show when={props.trend}>
        <span style={{ fg: props.trend!.color }}> {props.trend!.text}</span>   // ← line 186
      </Show>
    </text>
  )
}

src/main-session-view.tsx:44-46 passes trend conditionally:

trend={
  m.perCall().hasTrend ? { text: m.trendLabel(), color: m.trendFg() } : undefined
}

When m.perCall().hasTrend is false, trend === undefined. The <Show when={props.trend}> is supposed to guard, but this relies on SolidJS's lazy children evaluation produced by babel-preset-solid. The published package ships raw .tsx sources (exports["./tui"]: "./index.tsx"), and opencode loads them with bun's generic JSX transform (jsxDEV), which eagerly evaluates the children object — so props.trend!.color runs before Show's when guard, throwing on undefined.

This is structurally the same class of bug README.md warns about (issue #3undefined is not an object (evaluating 'config.providers')), just at a different site.

Reproduction

  1. opencode 1.18.10 + tui.jsonc: "plugin": ["opencode-cache-hit@latest"]
  2. opencode -s <any-existing-session> (session with prior assistant turns so metrics exist)
  3. Sidebar shows other plugins' slots but cache-hit panel is absent
  4. Wrap the sidebar_content render in try/catch writing to a file → captures the props.trend.color TypeError above

Evidence that the slot is registered but unmounted

Injecting appendFileSync probes into the compiled entry:

[03:07:58.047Z] dist/index.js module evaluated, jsxDEV=function
[03:07:58.060Z] tui() CALLED, api.slots=function            ← register succeeded
[03:11:03.897Z] sidebar_content CALLED, session_id=ses_...  ← render invoked
[03:11:03.902Z] sidebar_content RENDER ERROR: undefined is not an object (evaluating 'props.trend.color')

Note: opencode routes console.error to the TTY, not fd2, so 2>file captures nothing — the ErrorBoundary fully swallows the throw.

Suggested fixes (any one suffices)

  1. Defensive access in components.tsx:186:
    <Show when={props.trend}>
      <span style={{ fg: props.trend?.color }}> {props.trend?.text}</span>
    </Show>
  2. Ship a compiled bundle built with babel-preset-solid (lazy children) instead of raw TSX, and point exports["./tui"] at ./dist/tui.js — matching opencode-quota / oh-my-openagent layout. Also add "main" and consider "oc-plugin": ["tui"].
  3. Skip the ! assertion and rely on the Show guard with a SolidJS-aware build.

Workaround (verified)

Compile locally with bun build then patch the eager-evaluation site:

cd ~/.cache/opencode/packages/opencode-cache-hit@latest/node_modules/opencode-cache-hit
bun build ./index.tsx --outdir ./dist --target node --format esm \
  --external solid-js --external @opencode-ai/plugin --external @opencode-ai/sdk \
  --external @opentui/core --external @opentui/solid
# then edit dist/index.js: props.trend!.color → props.trend?.color (2 sites)
# and add to package.json: "main":"./dist/index.js",
#   "exports": { ".": "./dist/index.js", "./tui": "./dist/index.js", "./tui-panel": "./src/tui-panel/index.ts" }

After this, the panel renders correctly.

Additional notes

  • @latest is pinned to first-resolved version and never re-pulled (README "Update" section). Survivors of an older broken cache see this even after upstream fixes.
  • opencode's slot resolver uses j.get(name)?.at(-1)?.render (last-registered wins for same-name slots). If opencode-cache-hit and opencode-quota both register sidebar_content, only the last-registered renders. Order plugins in tui.jsonc accordingly when both ship a sidebar panel.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions