Skip to content

[FEATURE]: Compact one-line display for the sidebar Context block #53200

Open

Description

@Nowaker

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

The session sidebar's Context block takes four lines for three numbers:

Context
175,019 tokens
18% used
$1,215.20 spent

On a short terminal, or with many MCP/LSP/todo entries, those lines push the rest of the sidebar down, and the block can't be collapsed the way MCP, LSP, and Modified Files can.

Proposal:

  • A tui.json option, sidebar.context: "expanded" (default, today's layout) or "compact".
  • compact puts everything on one line and uses the most detailed form that fits the sidebar width:
    • 175,019 tokens · 18% used · $1,215.20 spent
    • 175,019 · 18% · $1,215.20
    • 175K · 18% · $1,215
  • Add the same ▼/▶ arrow the other sidebar sections use. Clicking it switches between the two modes. There is also a command palette entry ("Compact/Expand sidebar context", bindable as sidebar_context_toggle). The runtime choice is persisted in kv, like other TUI toggles.

The default stays exactly as it renders today, apart from the new arrow.

Activity

  1. github-actions commented on Oct 4, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    For keybind-related issues, please also check our pinned keybinds documentation: #4997

  2. opencodejr commented on Oct 27, 2026

    @opencodejr

    Thanks for the detailed proposal! The request is an optional one-line layout for the sidebar Context block that uses shorter forms as the sidebar narrows, plus a ▼/▶ toggle and a palette command, with the choice remembered. Maintainers will consider it. I see you've already opened #54966 for it.

    Related issues:

    Until then, you can get this now with a CLI plugin. The Context block is itself the built-in plugin opencode.sidebar.context, so you can turn it off and drop in a replacement. I've type-checked this plugin but haven't run it in a real TUI yet.

    1. Turn off the built-in block in ~/.config/opencode/cli.json:
    {
      "plugins": ["-opencode.sidebar.context"]
    }
    1. Save the plugin as ~/.config/opencode/plugins/sidebar-context-compact/tui.tsx:
    /** @jsxImportSource @opentui/solid */
    import { Plugin } from "@opencode/plugin/tui"
    import type { BoxRenderable } from "@opentui/core"
    import { createMemo, createSignal, Show } from "solid-js"
    
    const money = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
    const whole = new Intl.NumberFormat("en-US", { style: "currency", currency: "USD", maximumFractionDigits: 0 })
    const short = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 0 })
    
    function SidebarContext(props: { context: Plugin.Context; sessionID: string }) {
      const ctx = props.context
      const theme = ctx.theme
      const [view, update] = ctx.storage.store("view", { initial: { compact: ctx.options.mode === "compact" } })
      const [width, setWidth] = createSignal(40)
      const session = createMemo(() => ctx.data.session.get(props.sessionID))
      const cost = createMemo(() => ctx.data.session.cost(props.sessionID))
      const usage = createMemo(() => {
        const boundary = session()?.revert?.messageID
        const messages: any[] = ctx.data.session.message.list(props.sessionID) ?? []
        const end = boundary ? messages.findIndex((m) => m.id === boundary) : -1
        const last = (end >= 0 ? messages.slice(0, end) : messages).findLast((m) => m.role === "assistant" && m.tokens)
        if (!last) return
        const t = last.tokens
        const tokens = t.input + t.output + t.reasoning + t.cache.read + t.cache.write
        if (tokens <= 0) return
        const model = ctx.data.location.model
          .list(session()?.location)
          ?.find((m) => m.providerID === last.model?.providerID && m.id === last.model?.id)
        return { tokens, percent: model?.limit.context ? Math.round((tokens / model.limit.context) * 100) : undefined }
      })
    
      // Pick the most detailed one-line form that fits the sidebar width.
      const line = createMemo(() => {
        const u = usage()
        const c = cost()
        const pct = u?.percent !== undefined
        const forms = [
          [u && `${u.tokens.toLocaleString()} tokens`, pct && `${u!.percent}% used`, c > 0 && `${money.format(c)} spent`],
          [u && u.tokens.toLocaleString(), pct && `${u!.percent}%`, c > 0 && money.format(c)],
          [u && short.format(u.tokens), pct && `${u!.percent}%`, c > 0 && whole.format(c)],
        ].map((parts) => parts.filter(Boolean).join(" · "))
        return forms.find((f) => f.length <= width()) ?? forms[forms.length - 1]
      })
    
      const toggle = () =>
        void update((draft) => {
          draft.compact = !draft.compact
        })
    
      ctx.keymap.layer(() => ({
        mode: "global",
        commands: [
          {
            id: "sidebar_context_toggle",
            title: view.compact ? "Expand sidebar context" : "Compact sidebar context",
            group: "Session",
            palette: true,
            run: toggle,
          },
        ],
      }))
    
      return (
        <Show when={usage() || cost() > 0}>
          <box
            onSizeChange={function (this: BoxRenderable) {
              setWidth(this.width)
            }}
          >
            <box flexDirection="row" gap={1} onMouseDown={toggle}>
              <text fg={theme.text.base}>{view.compact ? "▶" : "▼"}</text>
              <text fg={theme.text.base}>
                <b>Context</b>
              </text>
            </box>
            <Show
              when={!view.compact}
              fallback={
                <text fg={theme.text.muted} wrapMode="none" truncate>
                  {line()}
                </text>
              }
            >
              <Show when={usage()}>
                {(u) => (
                  <>
                    <text fg={theme.text.muted}>{u().tokens.toLocaleString()} tokens</text>
                    <Show when={u().percent !== undefined}>
                      <text fg={theme.text.muted}>{u().percent}% used</text>
                    </Show>
                  </>
                )}
              </Show>
              <Show when={cost() > 0}>
                <text fg={theme.text.muted}>{money.format(cost())} spent</text>
              </Show>
            </Show>
          </box>
        </Show>
      )
    }
    
    export default Plugin.define({
      id: "sidebar-context-compact",
      setup(context) {
        context.ui.slot({
          append: "sidebar.content",
          render: (props) => <SidebarContext context={context} sessionID={props.sessionID} />,
        })
      },
    })

    Clicking ▼/▶ Context switches between the layouts, and the choice is saved in plugin storage. The palette also gets Compact/Expand sidebar context, under the command ID sidebar_context_toggle, which you can bind in keybinds. To start in compact mode, load the plugin through the object form with "options": { "mode": "compact" }.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions