Skip to content

tui: compact number formatter shows "1000.0K" instead of "1.0M" between 999,950 and 999,999 #55256

Closed

Description

@Hotragn

Version: OpenCode 2.0.26 (checked against v2 @ 4617210)

Description
Locale.number formats values from 999,950 to 999,999 as 1000.0K. The >= 1000000 check runs before rounding, so these values go to the K branch, where toFixed(1) rounds them up to 1000.0.

Evidence (v2 source)
packages/tui/src/util/locale.ts:19-26: num >= 1000000 → M; otherwise (num / 1000).toFixed(1) + "K".

Repro
Locale.number(999_950) returns "1000.0K"; the expected value is "1.0M". In the UI, this shows up in the token/context counters when a session is about 1M tokens in.

Fix: #48969

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report! I reproduced this on v2.0.28 (the latest release), and it still happens on the latest dev (43bb7ba).

    Steps: a simulated model reports 999,950 prompt tokens in its usage, and after the reply the status bar context counter shows 1000.0K (781%) instead of 1.0M. The 781% is only because the simulated model has a small context limit. The recording and screenshot below show it, and the script below reproduces it.

    The cause is the one you describe: number() in packages/tui/src/util/locale.ts checks >= 1000000 before rounding, so values from 999,950 to 999,999 round up to 1000.0K. Your open PR #48969 targets this. The earlier report, #33947, was closed automatically for inactivity.

    recording.mp4

    Screenshot

    Reproduction script (drive.ts)
    // Repro for #55256: context counter shows "1000.0K" instead of "1.0M"
    // The simulated model reports 999,950 prompt tokens in its usage chunk.
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        tui: { recording: true },
      },
      ({ ui, llm }) =>
        Effect.gen(function* () {
          yield* ui.screenshot("before")
          yield* llm.queue(
            Llm.text("Done."),
            Llm.raw({
              choices: [],
              usage: { prompt_tokens: 999_950, completion_tokens: 0, total_tokens: 999_950 },
            }),
            Llm.finish("stop"),
          )
          yield* ui.submit("hello")
          yield* ui.waitFor("Done.")
          yield* Effect.sleep("1500 millis")
          const frame = yield* ui.capture()
          yield* Effect.log(JSON.stringify(frame).match(/[\d.]+[KM]\b[^"\\]{0,12}/g))
          yield* ui.screenshot("after")
          yield* Effect.sleep("1 second")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. argszero commented on Oct 15, 2026

    @argszero
    Contributor

    I'd like to work on this.

    Checked first that it is still live: packages/tui/src/util/locale.ts on today's dev (b47c6802da) still has the num >= 1000000 test running before rounding, and the two earlier attempts at it are both closed without being merged — #47523 and #48969 (the latter as a duplicate of the former) were closed by the periodic stale-PR cleanup rather than on merit, so nothing is carrying this.

    Intended fix, on the dev side of number():

    • decide the unit from the value after rounding to one decimal, not before, so 999,950–999,999 roll over to 1.0M instead of 1000.0K;
    • keep 1000.0K impossible in general — the same off-by-rounding exists one unit down, where 999,950 is not the only boundary that can round up into the next suffix;
    • pin the boundaries in the existing locale test (999, 999,950, 999,999, 1,000,000 and the neighbours) so the rounding rule is asserted rather than the single reported value.

    I will keep it to packages/tui/src/util/locale.ts and its test.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions