Skip to content

feat(opencode): quota pacing — burn-rate projection on the TUI sidebar#74

Open
iceteaSA wants to merge 18 commits into
cortexkit:mainfrom
iceteaSA:feat/quota-pacing
Open

feat(opencode): quota pacing — burn-rate projection on the TUI sidebar#74
iceteaSA wants to merge 18 commits into
cortexkit:mainfrom
iceteaSA:feat/quota-pacing

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Codex-style pacing for the 5h/7d quota windows in the TUI sidebar.

Stacked on #73 (feat/tui-preferences) — review the last 4 commits only (85ebae8..d0dc5d8). Merging #73 first reduces this diff to the pacing feature.

  • Math (sidebar-state.ts, pure + tested): computeQuotaPacing(window, windowMs, now)
    • deltaPercent = used % − even-burn pace % (positive = deficit, |Δ|<1 = on-pace)
    • runsOutAt = burn-rate projection; null when the window lasts until reset
    • null guards: missing/invalid resetsAt, <5 min or <1 % of window elapsed (noise), stale windows
  • Bar segments (variant C): normal fill → headroom (under pace, green) / overshoot (over pace, red) → empty. On-pace bars render exactly as before.
  • Sublines: off-pace windows add a muted row — reserve 12% · lasts / deficit 10% · out in 2d 7h (deficit in warn tone).
  • Collapsed view: summary text turns red when any active-account window is in deficit.
  • Preference: sections.pacing (default true) gates segments, sublines, and the tint; hot-reloads.
  • formatResetIn is now day-aware (6d12h instead of 156h22m).

Testing

  • 9 new computeQuotaPacing tests (reserve/deficit/on-pace, runout boundary at reset, zero-usage, noise guards, invalid input) — includes a case derived from real Codex bar values
  • sections.pacing resolution tests
  • Full suite: 444 pass / 1 pre-existing failure (provider.models cost test, unrelated)
  • typecheck, lint, build all clean

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds even-burn quota pacing to the TUI sidebar with run-rate projection, plus a live-reloading tui-preferences.jsonc that persists collapse and customizes layout, sections, and appearance. Bars show reserve/deficit segments, off-pace rows show projected runout, and the collapsed header tints warn (never red) when the active account is over pace.

  • New Features

    • Quota pacing: computeQuotaPacing(window, windowMs, now) returns pacePercent, deltaPercent, state (reserve | deficit | on-pace), and runsOutAt | null, with noise/stale guards; formatResetIn is day-aware.
    • Sidebar UI: bars add pace segments ( reserve, deficit); off-pace rows show a subline with reserve/deficit and projected “out in …”; collapsed summary uses warn tone on active-window deficit; zero-length segments are dropped; gate via sections.pacing (default true).
    • TUI preferences: reads tui-preferences.jsonc (env OPENCODE_TUI_PREFERENCES_FILE or config dirs), hot-reloads, and writes atomically while preserving comments. Persists collapse across restarts and survives slot remounts. Controls layout (forceToTop, order), behavior (startCollapsed, rememberCollapsed, pollMs, refreshDebounceMs), header (label, showVersion), sections (quota, fallbackAccounts, routing, cache, health, pacing), and appearance (barWidth, bar chars, warnThreshold, errorThreshold). Shared helpers in @cortexkit/opencode-anthropic-auth/tui-prefs: readTuiPreferencesFile, resolveAnthropicAuthPrefs, computeEffectiveOrder (honors forceToTop), watchTuiPreferences (debounced, content-verified), queueTuiPreferenceUpdate (atomic writes).
  • Migration

    • To restore per-session collapse, set "rememberCollapsed": false in tui-preferences.jsonc.

Written for commit e6325a6. Summary will update on new commits.

Review in cubic

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@socket-security

socket-security Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedjsonc-parser@​3.3.110010010086100

View full report

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant User as TUI User
    participant TuiPlugin as TUI Plugin (process lifetime)
    participant SidebarUI as Sidebar UI Component
    participant PrefsFile as tui-preferences.jsonc
    participant PrefsModule as tui-preferences.ts
    participant SidebarState as sidebar-state.ts
    participant NodeFS as Node.js FS

    Note over TuiPlugin,SidebarUI: Startup & Preferences Loading

    TuiPlugin->>PrefsModule: readTuiPreferencesFile()
    PrefsModule->>NodeFS: read file from config dir/env
    NodeFS-->>PrefsModule: raw JSONC text
    PrefsModule->>PrefsModule: parse with jsonc-parser (tolerant)
    PrefsModule-->>TuiPlugin: Record<string, unknown>
    TuiPlugin->>PrefsModule: resolveAnthropicAuthPrefs(root)
    PrefsModule->>PrefsModule: validate/clamp per key, fallback defaults
    PrefsModule-->>TuiPlugin: AnthropicAuthTuiPrefs
    TuiPlugin->>TuiPlugin: createSidebarController(prefs)
    Note over TuiPlugin: createSignal for prefs & collapsed

    TuiPlugin->>PrefsModule: computeEffectiveOrder(root, 'anthropic-auth', 160)
    alt forceToTop=true
        PrefsModule->>PrefsModule: indexOf key → FORCE_TOP_BASE + index
    else manual order
        PrefsModule->>PrefsModule: clamp(-10000..10000)
    end
    PrefsModule-->>TuiPlugin: effective order number

    TuiPlugin->>PrefsModule: watchTuiPreferences(onChange)
    PrefsModule->>NodeFS: fs.watch(dirname(file))
    Note over PrefsModule: debounce 150ms, filter by filename

    Note over SidebarUI,PrefsModule: Hot-Reload Cycle (on file edit)

    User->>NodeFS: edits tui-preferences.jsonc
    NodeFS-->>PrefsModule: watch event fires
    PrefsModule->>PrefsModule: debounce timer
    PrefsModule->>PrefsModule: re-read & re-parse (tolerant)
    PrefsModule-->>TuiPlugin: new Record<string, unknown>
    TuiPlugin->>PrefsModule: resolveAnthropicAuthPrefs(newRoot)
    PrefsModule-->>TuiPlugin: new validated prefs
    TuiPlugin->>TuiPlugin: setPrefs(newPrefs) → signal update
    TuiPlugin-->>SidebarUI: signal re-renders

    Note over SidebarUI,SidebarState: Quota State Polling

    loop every pollMs (default 1500ms)
        TuiPlugin->>SidebarState: getSidebarState()
        SidebarState->>NodeFS: read state from file system
        NodeFS-->>SidebarState: raw state
        SidebarState-->>TuiPlugin: SidebarState
        TuiPlugin-->>SidebarUI: signal update
    end

    Note over SidebarUI: NEW: Pacing Computation (per render)

    SidebarUI->>SidebarState: computeQuotaPacing(window, FIVE_HOUR_MS, Date.now())
    SidebarState->>SidebarState: validate resetsAt, elapsed guards
    alt elapsed < 5 min OR elapsed < 1% of window
        SidebarState-->>SidebarUI: null
    else elapsed >= windowMs (stale/reset)
        SidebarState-->>SidebarUI: null
    else valid
        SidebarState->>SidebarState: pacePercent = elapsed/windowMs * 100
        SidebarState->>SidebarState: deltaPercent = used% - pacePercent
        alt |delta| < 1%
            SidebarState-->>SidebarUI: state='on-pace', runsOutAt=null
        else delta > 0
            SidebarState->>SidebarState: project runout: start + (elapsed*100)/used
            alt runout < resetsAt
                SidebarState-->>SidebarUI: state='deficit', runsOutAt=ISO timestamp
            else
                SidebarState-->>SidebarUI: state='deficit', runsOutAt=null
            end
        else delta < 0
            SidebarState-->>SidebarUI: state='reserve', runsOutAt=null
        end
    end

    Note over SidebarUI: Render Bar with Pacing Segments

    SidebarUI->>SidebarUI: quotaBarSegments(usedPct, appearance, pacing)
    alt no pacing data
        SidebarUI->>SidebarUI: plain bar: filled + empty
    else pacing present
        alt usedCells > paceCells (deficit)
            SidebarUI->>SidebarUI: fill lo, ▓ overshoot segment (err-tone), empty
        else paceCells > usedCells (reserve)
            SidebarUI->>SidebarUI: fill lo, ▒ headroom segment (ok-tone), empty
        else equal
            SidebarUI->>SidebarUI: plain bar (no change)
        end
    end

    alt pacing state !== 'on-pace'
        SidebarUI->>SidebarUI: render subline with delta % and runout
        alt deficit
            SidebarUI->>SidebarUI: "deficit X% · out in {formatResetIn(runsOutAt)}"
            Note over SidebarUI: warn-tone text
        else reserve
            SidebarUI->>SidebarUI: "reserve X% · lasts"
            Note over SidebarUI: muted-tone text
        end
    end

    Note over SidebarUI: Collapsed Summary Tinting

    alt any active-account window has deficit state
        SidebarUI->>SidebarUI: collapsed summary turns red
    else
        SidebarUI->>SidebarUI: normal color
    end

    Note over SidebarUI,PrefsFile: Persist Collapse State

    User->>SidebarUI: clicks header to collapse/expand
    SidebarUI->>TuiPlugin: toggleCollapsed()
    TuiPlugin->>TuiPlugin: setCollapsed(!current)
    TuiPlugin->>PrefsModule: queueTuiPreferenceUpdate('anthropic-auth', ['collapsed'], newValue)
    PrefsModule->>PrefsModule: read current file
    PrefsModule->>PrefsModule: jsonc-parser modify (preserve comments)
    PrefsModule->>NodeFS: atomic write (temp + rename)
    Note over PrefsModule: serialized on promise chain
Loading

Re-trigger cubic

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@ualtinok

Copy link
Copy Markdown
Contributor

Thanks for the pacing work. I tested the stack against current main; this is currently blocked by the same tui-preferences watcher bug in #73. After resolving only the local import conflicts, the focused test suite fails in watchTuiPreferences > ignores sibling files that share the preferences name as a prefix (Expected: 0, Received: 1). Since #74 is stacked on #73, please fix/rebase #73 first.

Separate product note for this PR: default-on pacing changes the collapsed quota summary color to red for an even-burn-rate deficit. That is an interpretation/projection, not actual quota exhaustion or a hard route failure. I would prefer either keeping sections.pacing default-off, or using a less alarming tone/label unless the copy clearly frames it as pacing rather than quota danger.

iceteaSA added 18 commits June 12, 2026 19:21
Some platforms misattribute sibling-file renames to the real prefs
filename, defeating the name-based pre-filter. After debounce, re-read
the file and compare against last-seen content before invoking the
callback. Last-seen is seeded async at watch start with a null guard
to avoid races.
@iceteaSA iceteaSA force-pushed the feat/quota-pacing branch from c66e349 to e6325a6 Compare June 12, 2026 17:36

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iceteaSA has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@iceteaSA

Copy link
Copy Markdown
Contributor Author

Rebased onto the updated #73 (watcher fix included underneath).

Pacing tone (e6325a6): agreed — a pace deficit is a projection, not exhaustion. The collapsed tint no longer paints red: a deficit now only bumps the usage tone to warn at most (ok/mutedwarn; an existing usage-driven warn/err is left untouched, so a true 95%-used red still reads red). README copy updated to call it a pacing projection explicitly. sections.pacing stays default-on with the softer tint — happy to flip the default instead if you'd still prefer that.

Focused suites: 54/54 on the rebased stack.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants