Skip to content

feat: retitle chat settings and add a Usage tab - #1298

Merged
sakibsadmanshajib merged 8 commits into
mainfrom
feat/chat-settings-usage-tab
Aug 29, 2026
Merged

sakibsadmanshajib merged 8 commits into
mainfrom
feat/chat-settings-usage-tab

Conversation

@sakibsadmanshajib

@sakibsadmanshajib sakibsadmanshajib commented Aug 28, 2026 •

Copy link
Copy Markdown
Owner

Summary

A parity re-score of Hive's chat surface against Claude Desktop scored settings at 0.30 credit (criterion weight 9). Two concrete findings: the General tab's section header was the literal upstream string "WebUI Settings" (stock Open WebUI branding), and there was no consumption/credit surface anywhere in Settings.

  • Renamed the "WebUI Settings" header to "Chat Preferences".
  • Added a dedicated Usage tab, clustered next to Account (mirroring the Claude Desktop reference's General/Account/Usage grouping). Shows credit balance and today's usage, both routed through the same honesty-invariant formatter already used by the composer credit banner (vendor/open-webui/src/lib/hive/credits.ts, itself a faithful port of apps/web-console/lib/format/model-pricing.ts). A real balance, zero included, always renders as a dollar figure, never as the bare integer a prior defect once showed a customer directly ("9,789,478,244 remaining").
  • Deliberately does not replicate the reference's session/weekly quota bars with reset timers: Hive bills prepaid credits with no reset window (D-046, D-031 in .wolf/decisions.md), so a countdown here would be fabricated data on a screen whose whole job is telling the truth about money.

Scope discipline

Did not touch apps/web-console (parallel work in flight there). Did not do a full audit/removal pass over every remaining stock-OWUI toggle in Interface.svelte/Audio.svelte/DataControls.svelte (traced enough to confirm these are functional, backend-agnostic UI preferences, consistent with the prior P0 settings-declutter wave already having removed the genuinely nonfunctional items).

A finding along the way, investigated and corrected in place

Traced the tools (Integrations: direct OpenAPI tool server + Open Terminal registration) settings tab: it has no entry in SettingsModal.svelte's allSettings array, so its rail button and content-pane branches are unreachable. Filed that as issue #1258 with an initial framing that treated it as an open decision (restore vs. delete) — then corrected it on the same issue after reading Dockerfile.open-webui's own comment: the descriptor's absence from vendored source is the documented, intentional fix for issue #771 (direct tool servers bypass Hive's gateway metering, billing and sanitization; every tenant OWNER holds the OWUI admin role per #748, so the feature-gate branch never actually gated anyone), with Caddyfile.owui already blocking the server-side half (#770). No open design question remains; #1258 is now a low-priority "delete the dead template branches" tidiness item.

Review round two, and why the first green was not evidence

An independent review mutated this branch three ways at once, a Svelte parse error in the Usage component, the two money figures transposed, and the rail button click handler emptied, and the repo gate still reported 16 files, 208 tests, 13 of 13 components compiled, exit 0. Nothing in this repository could tell a working version of the component from a broken one, because scripts/test-owui-hive-frontend.sh compiles lib/hive only and the component had been added under lib/components, where the script treats copied files as text fixtures.

What changed:

  • The component moved to vendor/open-webui/src/lib/hive/SettingsUsage.svelte, where the compile guard already looks and where every other Hive authored component lives.
  • The gate gained a real render capability. It installs the vite svelte plugin at the version pinned in the vendored lockfile and writes a vitest config into its scratch tree, so a test can import a component and assert what it renders. Server side rendering through svelte/server, so no jsdom has to be added to the vendored lockfile and the identical test runs in the image build's in place run.
  • A failed refresh no longer wipes a good balance or advances the last updated stamp. That policy lives in credits.ts as refreshCreditSnapshot, executable in a test rather than only readable in a diff.
  • Both money labels now name their scope, Organization credit balance and Organization usage today. The figures are tenant scope, per the deliberately TENANT balance note in apps/control-plane/internal/ledger/chat_balance.go, and a bare "Used today" printed the whole organization's spend under a personal label.
  • The Usage tab is hidden entirely where the deployment has no credits surface, which is the silent absence posture deploy/docker/owui-patches/hive_credits.py documents. The modal's availability probe hands its balance straight to the panel, so the tab does not fetch the same figure twice.
  • The retitle no longer silently drops a translated string. The new key and every new money label are in en-US and translated in bn-BD, the first market, with two tests holding it. General tab search keywords carry the new title as well as the old one.

Verification

Mutation results, each run through make test-owui-frontend on the current head:

mutation result
{$i18n.t('Usage'} in the component RED, CompileError: SettingsUsage.svelte:100:56 Unexpected token, exit 2
the two money figures transposed RED, 3 failed assertions, exit 2
usage rail button click handler emptied RED, 1 failed assertion, exit 2
unmutated tree GREEN, 16 files, 221 tests, 14 of 14 components compiled, exit 0

The component count moving from 13 to 14 is the direct evidence that this file is now inside the compile set.

  • In place run, inside the real image build stage: npm run test:frontend -- --run against the vendored node_modules reported 16 files and 221 tests passed, so the render assertions work in both the scratch tree and the tree the deploy builds from.
  • Image built from this branch with the shipped deploy/docker/Dockerfile.open-webui, container run locally, and the populated Usage tab captured: Organization credit balance $12.50, Organization usage today $0.34, top up link, last updated stamp. The one credits response is fulfilled by Playwright, so no database is needed; issue shared .env's SUPABASE_URL/SUPABASE_DB_URL point at a deleted Supabase Cloud project, blocking every local agent stack #1297 does not block this after all. A third capture shows the same build with that endpoint answering its documented 404 and the Usage entry absent from the rail.
  • Capture log, including the exact route stub and the read-back DOM text: docs/proof/chat-settings-usage-tab-2026-08-29/capture.log.md.
  • Review streams re-run on the fixed diff: CodeRabbit CLI ran and returned two findings, both addressed (probe races and a duplicate fetch for a figure the modal already held). Antigravity gemini-3.1-pro-high ran and returned two, one valid and fixed (an unbounded slice in the keywords test), one a false positive (set -eu is already the first thing in the container payload, which the mutation runs above demonstrate empirically).

Buglog entry

To be appended to .wolf/buglog.jsonl on main in a separate buglog-only pull request once this merges.

{"date":"2026-08-29","title":"Pre-merge frontend gate compiled lib/hive only, so a Hive component added under lib/components was compiled and rendered by nothing","error_message":"make test-owui-frontend reported 16 files, 208 tests, 13/13 components compiled, exit 0 on a tree carrying a Svelte parse error, two transposed money figures and an inert settings tab","root_cause":"scripts/test-owui-hive-frontend.sh copies upstream components into its scratch tree as text fixtures and runs the svelte compile pass over lib/hive only, so a Hive authored component placed under lib/components/chat/Settings was read as text and never compiled, and no test rendered it; the image build that would have caught the parse error runs after merge in deploy-demo-box.yml","fix":"moved the component to vendor/open-webui/src/lib/hive/SettingsUsage.svelte where the compile guard already looks, taught the gate to install the lockfile pinned vite svelte plugin and write a vitest config so tests can import and server side render components, and re-ran the three mutations to confirm each one turns the suite red","tags":["ci","test-gate","open-webui","svelte","frontend","money-surface"]}

Test plan

  • make test-owui-frontend on the unmutated tree, 221 tests, 14 of 14 components compiled
  • Each of the three review mutations re-run against the gate, all red
  • npm run test:frontend -- --run in place, inside the image build, 221 tests
  • Image built from this branch and run as a container
  • Populated Usage tab and enterprise absence captured and posted to this PR
  • Adversarial review re-run on the fixed diff, CodeRabbit CLI and Antigravity, findings addressed

🤖 Generated with Claude Code

sakibsadmanshajib and others added 2 commits August 28, 2026 17:22
…abricated resets)

Parity re-score scored settings at 0.30/9: the right pane was literally
titled "WebUI Settings" (stock Open WebUI branding) and there was no
consumption or credit surface anywhere in Settings.

Renames the General tab's "WebUI Settings" header to "Chat Preferences",
and adds a dedicated Usage tab clustered next to Account (mirroring the
Claude Desktop reference's General/Account/Usage grouping), showing
credit balance and today's usage.

Usage.svelte reuses vendor/open-webui/src/lib/hive/credits.ts end to end
(fetch, format, low/empty state) rather than re-deriving any of it, so
this tab and the composer credit banner can never disagree about what a
balance means. formatUsdFromCredits carries the same honesty invariant as
apps/web-console/lib/format/model-pricing.ts: a real balance, zero
included, always renders as a dollar figure, never as the bare integer a
prior defect once showed a customer ("9,789,478,244 remaining").

Deliberately does not replicate the reference's session/weekly quota bars
with reset timers: Hive bills prepaid credits with no reset window
(D-046, D-031), so a countdown here would be fabricated data on a screen
whose job is telling the truth about money.

Traced the 'tools' (Integrations, direct OpenAPI tool server + Open
Terminal registration) settings tab: it has no entry in the allSettings
array that drives the tab rail, so its button/content branches are
unreachable. Initially filed that as an open decision (issue #1258), then
corrected on the same issue after reading Dockerfile.open-webui's own
comment: the descriptor's absence from vendored source is the documented,
intentional fix for #771 (direct tool servers bypass Hive's gateway
metering, billing and sanitization; every tenant OWNER holds the OWUI
admin role per #748, so the feature-gate branch never actually gated
anyone), with Caddyfile.owui already blocking the server-side half (#770).
No open design question remains; #1258 is retitled to a low-priority
"delete the now-dead template branches" tidiness item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Text log backing the screenshots posted to this PR via
scripts/post-pr-visual-proof.sh, per D-042 and npm run lint:proof-tokens
(which scans only docs/proof/). Documents the sign-in path used (a
throwaway local signup, not the shared QA fixture), and the pre-existing
environment blocker (dead Supabase Cloud project reference in the shared
.env) that limited the Usage tab capture to its honest no-data fallback
state rather than a populated balance.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 33 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 109b4392-63b9-43e5-9cdc-0dc642e59b80

📥 Commits

Reviewing files that changed from the base of the PR and between 1ed098e and a94ccae.

📒 Files selected for processing (11)
  • docs/proof/chat-settings-usage-tab-2026-08-28/capture.log.md
  • docs/proof/chat-settings-usage-tab-2026-08-29/capture.log.md
  • scripts/test-owui-hive-frontend.sh
  • vendor/open-webui/src/lib/components/chat/Settings/General.svelte
  • vendor/open-webui/src/lib/components/chat/SettingsModal.svelte
  • vendor/open-webui/src/lib/hive/SettingsUsage.svelte
  • vendor/open-webui/src/lib/hive/credits.test.ts
  • vendor/open-webui/src/lib/hive/credits.ts
  • vendor/open-webui/src/lib/hive/settings-usage-tab.test.ts
  • vendor/open-webui/src/lib/i18n/locales/bn-BD/translation.json
  • vendor/open-webui/src/lib/i18n/locales/en-US/translation.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Visual proof

General tab retitled to 'Chat Preferences' (was literal 'WebUI Settings'); new Usage tab clustered next to Account, showing the honest no-data fallback since the local verification stack's shared .env points at a deleted Supabase project (issue #1297).

pr1298-20260828212539-6441-proof-general-chat-preferences.png

pr1298-20260828212545-18458-proof-usage-tab.png

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Adversarial review (pipeline mode)

CodeRabbit CLI: SKIPPED. coderabbit review --agent --committed --base main returned a rate-limit error twice (11-13 min apart): "You've used all 3 included reviews currently available... this Git provider account isn't linked to an assigned seat for the selected organization." That reads as an org/seat issue rather than a pure per-minute cooldown, so I'm not treating a later clean run as a substitute for this one; flagging it explicitly rather than letting the absence read as a pass.

Plain adversarial pass (my own re-read of the diff against the actual PR description, git diff origin/main...HEAD, five files, 435 insertions / 41 deletions):

  • SettingsModal.svelte: the new usage tab-rail button mirrors the existing general button's exact class/state logic (aria-selected, highContrastMode branches), and aria-controls="tab-usage" correctly matches Usage.svelte's root id="tab-usage". The account object was moved (not duplicated) — confirmed only one id: 'account' entry exists post-diff.
  • Usage.svelte: no raw credit integer ever reaches the template outside a formatUsdFromCredits(...) call (checked by hand, and pinned by settings-usage-tab.test.ts). The loading/balance state machine covers all four reachable states (initial loading, loaded-with-data, loaded-null, refresh-in-flight with the button disabled to prevent a double-fetch race). Component unmount mid-fetch (closing the modal or switching tabs while the balance request is in flight) is a normal Svelte no-op, not a crash.
  • No console.log in the added/changed lines (one pre-existing console.log at SettingsModal.svelte:519 predates this diff, in saveSettings, untouched here).
  • No new i18n keys need a locale file edit: this file's existing convention is key-equals-English-text with per-locale overrides layered on top (see the neighboring "Help us translate Hive Chat!" string), which the new "Chat Preferences" / "Usage" / etc. strings already follow.

No findings. I could not run the two agent-dispatched streams (ecc:code-review, plus any diff-triggered domain specialist) myself: this builder session has Read/Write/Edit/Bash only, no Skill or Agent dispatch tool, which is a structural capability gap rather than something to route around. Flagging it here and in my report to whoever dispatched this task, per the project's Gate Compliance policy, rather than skipping it silently.

… scratch tree

Repo policy lints (tenant + audit) failed CI: settings-usage-tab.test.ts
reads General.svelte and Usage.svelte as text fixtures (same pattern as
the existing settings-declutter guard), but scripts/test-owui-hive-frontend.sh
only mirrors an explicit allowlist of component files into its scratch
tree for speed (avoiding a full frontend npm install), and neither file
was on that list. ENOENT on both, 7/208 tests failed.

Verified locally by reproducing the script's own mirror step and running
vitest against the resulting tree with the real node_modules (host npm
network access in this sandbox is unreliable for the script's own docker
invocation, confirmed separately as a pre-existing sandbox constraint, not
a regression from this fix).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Comment thread scripts/test-owui-hive-frontend.sh Outdated
Comment thread vendor/open-webui/src/lib/hive/SettingsUsage.svelte
Comment thread vendor/open-webui/src/lib/hive/settings-usage-tab.test.ts Outdated
Comment thread vendor/open-webui/src/lib/hive/SettingsUsage.svelte
Comment thread vendor/open-webui/src/lib/components/chat/Settings/Usage.svelte Outdated
Comment thread vendor/open-webui/src/lib/hive/SettingsUsage.svelte
Comment thread vendor/open-webui/src/lib/components/chat/Settings/General.svelte
Comment thread vendor/open-webui/src/lib/components/chat/SettingsModal.svelte
@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Independent review, stage 6. Verdict: NO MERGE.

Read the pushed diff at 6ebe5ba, not the description. Checks are green and there were zero threads, but the author self-review is not verification under this repo's contract, and the green is load bearing in a way that does not hold up.

The headline

I mutated the PR head and re-ran the repo's own gate. Three mutations at once:

  1. A hard Svelte parse error in Usage.svelte ({$i18n.t('Usage'}).
  2. The two money figures swapped, so "Credit balance" rendered today's spend and "Used today" rendered the remaining balance.
  3. The Usage rail button's click handler emptied, making the tab inert.

make test-owui-frontend on that tree:

Test Files  16 passed (16)
     Tests  208 passed (208)
13/13 components compiled

Exit 0. Byte for byte the verification the PR body reports. A component that does not parse, with its two money numbers transposed and its tab dead, is indistinguishable from this PR under every check that runs before merge.

Root cause is scope, not effort. scripts/test-owui-hive-frontend.sh compiles lib/hive only, and this component was added at lib/components/chat/Settings/, where the script treats copied files as text fixtures. The 13 compiled components are the 13 .svelte files under lib/hive. Dockerfile.open-webui is referenced only by deploy-demo-box.yml, which runs after merge, and Docker build smoke (fork PRs only) is skipping here. So nothing pre-merge compiles the one new component in this PR, which is the 2026-08-23 incident that motivated the compile pass, replayed.

Findings

Blocking:

  1. scripts/test-owui-hive-frontend.sh:52 — new component registered as a text fixture, so no CI job compiles it.
  2. Usage.svelte:87-96 — the balance and the daily total can be transposed with all 10 new tests green; nothing anywhere renders this component.
  3. settings-usage-tab.test.ts:47-51 — the test explicitly named for the dead-tab regression cannot detect a dead tab.

Should fix before merge:

  1. Usage.svelte:46-51 — lastUpdated stamped on failed fetches, and a transient failure wipes a good balance off the screen.
  2. Usage.svelte:94 — "Used today" is the tenant's spend labelled as if it were the signed-in user's.

Worth fixing:

  1. Usage.svelte:66-69 — a permanent dead tab on Enterprise, inverting the silent-absence posture hive_credits.py documents and CreditsBanner.svelte implements.
  2. General.svelte:61-67 — the retitle drops a translated string in 62 locales including bn-BD, and none of the new money labels exist in any locale file.
  3. SettingsModal.svelte:81 — search keywords not updated for the retitle.

Checked and clear

Worth recording, because these were the risks this surface carried going in and the PR handles them correctly.

  • Data exposure: clean. The tab consumes /api/v1/hive/credits/balance, which returns exactly three fields, two integers plus a top_up_url read from deployment env. Nothing from usage_events crosses the boundary: no internal_metadata, no customer_tags, no provider_request_id, no provider identity. The key-name denylist weakness in RedactMetadata is not reachable from this surface, because this surface never touches that table.
  • Tenant isolation: enforced at the database layer, not in the UI. The browser never names an account. Open WebUI's get_verified_user resolves the principal server side, the email rides in a POST body over the internal-token gate rather than in a URL, and ResolveAccountIDForEmail does the identity to account resolution in Postgres. Nothing in the request influences which identity is resolved.
  • Issue /v1/messages always reports usage input_tokens 0 and output_tokens 0 #1329 (/v1/messages reporting zero tokens): not this tab's source. "Used today" sums credit_ledger_entries where entry_type = 'usage_charge' (repository.go:496-502), that is money actually posted, not the reported usage block. If /v1/messages always reports usage input_tokens 0 and output_tokens 0 #1329 causes Anthropic-protocol traffic to be billed at zero, this tab under-reports, but the balance under-reports identically and consistently. The tab introduces no new fabrication path. The residual product risk is that it makes the billing bug customer-visible, which argues for fixing /v1/messages always reports usage input_tokens 0 and output_tokens 0 #1329 sooner, not for changing this tab.
  • Issue Console shows credits as a bare integer on one page and dollars on another #1332 (one quantity, three presentations): this adopts the settled one. Both figures route through formatUsdFromCredits, the same dollar rendering as the console's API keys table ($0.000662). It does not add a third presentation, and it does not repeat the dashboard's bare grouped integer. That was the right call.
  • Prototype-chain reachability: none. No map is indexed by a query-string or search value anywhere in the diff. setFilteredSettings uses Array.filter and Array.includes (SettingsModal.svelte:492-505), so ?x=toString and ?x=constructor have nothing to reach.

Visual proof

Required, since this changes a live UI surface, and satisfied for only half the change. The General retitle is genuinely proven. The Usage tab capture shows "Usage isn't available on this deployment.", which docs/proof/chat-settings-usage-tab-2026-08-28/capture.log.md states plainly and honestly. But the claimed behavior is "Shows credit balance and today's usage", and no screenshot, no test and no CI job has ever rendered that state.

The environment blocker (#1297, dead Supabase Cloud pointers) is real and does not prevent this. A Playwright page.route('**/api/v1/hive/credits/balance', ...) fulfilling a JSON body renders the populated tab against the same locally built container with no database at all, which also happens to be the cheapest way to catch the transposition in finding 2.

Review streams

  • Plain adversarial pass: ran. Findings 1 to 8 above, with 1, 2 and 3 established by mutation against the repo's own gate rather than by reading.
  • Antigravity (gemini-3.1-pro-high, high effort): ran. Independently produced findings 4 and 5, plus the two test-strengthening notes folded into finding 3. It also flagged the usage_today_credits fallback-to-zero coercion in credits.ts:92; I am not posting that one, because that line is not in this diff and the suggested remedy (throwing from inside a ternary) is worse than the behavior it replaces.
  • CodeRabbit CLI: SKIPPED. Two attempts, both rate_limit: "You've used all 3 included reviews currently available", 11 minute wait advertised, third attempt still pending at time of writing and will be reported separately if it returns anything. Note that the repo's own CodeRabbit check reports pass on this PR while its detail line reads "Review rate limited", so that green is not evidence of a clean CodeRabbit pass.

What would flip this to MERGE

Findings 1, 2 and 3 are one change in practice: move Usage.svelte under vendor/open-webui/src/lib/hive/, which is where all 13 other Hive-authored components live, so the compile guard covers it, then add a render assertion for the two rows and the selectedTab = 'usage' pin. Findings 4 and 5 are a four-line edit and a label. Findings 6 to 8 can be follow-up issues if you would rather not grow the diff, provided 6 is stated as a decision rather than left implicit.

The feature itself is the right feature, the reuse of credits.ts is exactly right, and declining to fabricate a reset timer is the correct call for a prepaid model. None of that is in question. What is in question is that nothing in this repository can currently tell a working version of this component from a broken one.

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Correction: the CodeRabbit stream did run

My review summary above marked CodeRabbit as SKIPPED after three rate-limited attempts. A fourth attempt, made a few minutes later, went through. Correcting the record rather than leaving a wrong stream status standing.

{"type":"complete","status":"review_completed","findings":0,
 "reviewedFiles":["docs/proof/chat-settings-usage-tab-2026-08-28/capture.log.md",
   "scripts/test-owui-hive-frontend.sh",
   "vendor/open-webui/src/lib/components/chat/Settings/General.svelte",
   "vendor/open-webui/src/lib/components/chat/Settings/Usage.svelte",
   "vendor/open-webui/src/lib/components/chat/SettingsModal.svelte",
   "vendor/open-webui/src/lib/hive/settings-usage-tab.test.ts"]}

CodeRabbit CLI: RAN, all six changed files reviewed, zero findings.

Attempt history, for the next agent who hits this: attempt 1 failed on an invalid flag (--plain is not an option in this CLI version, plain text is the default output mode). Attempts 2 and 3 returned errorType: rate_limit with "You've used all 3 included reviews currently available", advertising an 11 minute and then a 1 minute wait. Attempt 4, after roughly 90 seconds more, ran clean. Worth retrying past the third refusal.

This does not move the verdict. Zero findings from a diff reviewer is the expected result for findings 1 through 3, which are not visible in the diff text at all: they are properties of which files the CI compile guard covers and of what the assertions fail to pin, both of which need the repo's gate to actually be run and mutated. Findings 4 through 8 it simply did not raise; the Antigravity stream independently raised 4 and 5, so they are not artefacts of a single reviewer.

The verdict stands at NO MERGE on findings 1, 2 and 3.

Independent review mutated this branch three ways at once, a Svelte parse
error in the Usage component, the two money figures transposed, and the rail
button click handler emptied, and the repo gate still reported 16 files, 208
tests, 13 of 13 components compiled, exit 0. Nothing in this repository could
tell a working version of the component from a broken one.

Root cause was scope. scripts/test-owui-hive-frontend.sh compiles lib/hive
only, and the component had been added under lib/components, where the script
treats copied files as text fixtures. The image build that would have caught
the parse error runs after merge.

The component now lives at lib/hive/SettingsUsage.svelte, where the compile
guard already looks and where every other Hive authored component lives, and
SettingsModal imports it the way Chat.svelte imports CreditsBanner. The gate
grew a real render capability: it installs the vite svelte plugin at the
lockfile pinned version and writes a vitest config, so a test can import a
component and assert what it renders. Server side rendering, so no jsdom has
to be added to the vendored lockfile and the same test runs identically in the
image build's in place run.

Mutation results after the change, each run through make test-owui-frontend:

  parse error in SettingsUsage.svelte   RED, CompileError, exit 2
  two money figures transposed          RED, 3 failed assertions, exit 2
  rail button click handler emptied     RED, 1 failed assertion, exit 2
  unmutated tree                        GREEN, 221 tests, 14 of 14 compiled

Also fixed, from the same review:

Failed refresh no longer wipes a good balance or advances the last updated
stamp. That policy moved into credits.ts as refreshCreditSnapshot so it is
executable in a test rather than only readable in a diff, with four cases
covering it.

Both figures are tenant scope, so both labels now say so. The schema has no
per user allowance at all, see the deliberately TENANT balance note in
apps/control-plane/internal/ledger/chat_balance.go, so the old bare "Used
today" printed the whole organization's spend under a personal label.

The Usage tab is hidden where the deployment has no credits surface.
Enterprise deployments never wire the chat container's credits proxy and it
fails closed with a 404; silent absence is that posture's documented behavior,
and a tab permanently stuck on a not available sentence inverted it.

The rename drops a string that was translated in bn-BD, so the new key and
every new money label are added to en-US and translated in bn-BD, the first
market. Remaining locales fall back to English, as every other untranslated
key in this fork already does. General tab search keywords carry the new title
as well as the old one.
Comment thread vendor/open-webui/src/lib/hive/settings-usage-tab.test.ts Fixed
…m it

Code scanning flagged the single pass tag stripping regular expression in the
new render helper as incomplete multi character sanitization. Nothing here
sanitizes anything, the input is a string this same test just rendered, but
the pattern was also unnecessary: the span holds a formatted currency string
with no nested elements.

Slicing between the span boundaries and comparing the result whole is stronger
for this test's purpose. A tag or comment appearing inside the value is a real
change in what the slot renders, and now fails the comparison instead of being
quietly removed from it.
The first capture on this branch showed the tab in its no-data fallback,
which is not the behaviour the PR claims. This one renders the populated
state against a container built from this branch, with the single credits
response fulfilled by Playwright rather than a database, and adds the
enterprise case where the endpoint answers its documented 404 and the tab is
absent from the rail entirely.
@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Visual proof

Usage tab populated, the state the first capture missed. Organization credit balance 12.50 dollars and organization usage today 0.34 dollars, rendered by a container built from this branch with Playwright fulfilling the one credits response. Third image is the same build with that endpoint answering its documented 404, where the Usage entry is absent from the rail entirely. Log: docs/proof/chat-settings-usage-tab-2026-08-29/capture.log.md

pr1298-20260829030904-16597-proof-usage-populated.png

pr1298-20260829030907-3869-proof-general-chat-preferences.png

pr1298-20260829030910-12516-proof-usage-absent-enterprise.png

Second review round, from CodeRabbit CLI and Antigravity on the fixed diff.

The settings modal stays mounted for the whole session and probed credits on
every open, so two opens in quick succession could land out of order and an
older answer could overwrite a newer one. One probe is in flight at a time
now.

The probe's answer is also kept rather than discarded. It travels to the Usage
panel as its starting snapshot, so opening the tab shows the balance the modal
already fetched, stamped with the time that fetch happened, instead of firing
a second request for the same figure and rendering a spinner over data the
modal is holding. The panel takes one `initial` snapshot prop in place of the
previous balance and loading pair, and still fetches for itself when handed
nothing.

The probe goes through `refreshCreditSnapshot` for the same reason the panel
does: a failed re-probe keeps the last known good balance and its original
timestamp rather than blanking a figure already on screen.

Also from Antigravity: the settings keywords test sliced between two
`indexOf` results without checking the second. A missing end marker returns
-1, and `slice(start, -1)` would have quietly widened the assertion to most of
the file, letting any other tab's descriptor satisfy it. Both boundaries are
asserted now.

Its other finding, that the container payload lacks `set -e` and so swallows a
vitest failure, is a false positive: `set -eu` is the first thing in that
payload, and the three mutation runs below exit 2 on assertion failures alone.

Mutation re-run after these changes, through `make test-owui-frontend`:

  parse error in SettingsUsage.svelte   RED, CompileError at 100:56, exit 2
  two money figures transposed          RED, 3 failed assertions, exit 2
  rail button click handler emptied     RED, 1 failed assertion, exit 2
  unmutated tree                        GREEN, 221 tests, 14 of 14 compiled
The panel now renders from the snapshot the settings modal already fetched
rather than from its own mount request, so the capture was retaken against
that head instead of reused from the previous one.
@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Visual proof

Recaptured against 338b699, the head that answers the second review round, where the settings modal hands its probe balance to the panel. Usage tab populated: organization credit balance 12.50 dollars, organization usage today 0.34 dollars, top up link, last updated stamp. Second image is the retitled General tab with Usage in the rail. Third is the same build with the credits endpoint answering its documented 404, where the Usage entry is absent entirely. Log: docs/proof/chat-settings-usage-tab-2026-08-29/capture.log.md

pr1298-20260829033131-5515-proof-usage-populated.png

pr1298-20260829033134-23328-proof-general-chat-preferences.png

pr1298-20260829033136-28835-proof-usage-absent-enterprise.png

@sakibsadmanshajib

Copy link
Copy Markdown
Owner Author

Review streams re-run on the fixed diff

All eight review threads plus the code scanning thread are answered and resolved. Streams re-run against the head that carries the fixes.

CodeRabbit CLI, round one (coderabbit review --agent --committed --base main): ran, two findings, both major, both on SettingsModal.svelte and both landed.

  1. The credits availability probe could race itself. This modal stays mounted for the whole session and probed on every open, so two opens in quick succession could land out of order and an older answer could overwrite a newer one. One probe is in flight at a time now.
  2. The probe's answer was thrown away and the panel then fetched the same figure again. The probe's snapshot now travels to the panel as its starting value, so the tab shows the balance the modal already holds, stamped with the time that fetch actually happened, and makes no second request.

CodeRabbit CLI, round two (after those fixes): ran, one finding, minor, rebutted.

It asks that the selectedTab === 'usage' render branch also require creditsAvailable, to stop the panel rendering or fetching where credits are unavailable. The fetch half cannot happen: the panel only fetches when handed no snapshot, and the modal always hands it one, so on a credits-less deployment it renders the honest not-available sentence and issues no request. The render half is already self-correcting: setFilteredSettings resets selectedTab when the current tab is not in the available set, and probeCredits calls it, so the only way to see that panel there is a deep link that opens settings directly on usage, for the moment before the probe answers. Nothing in this repository does that today. Adding a second gate to the render branch would trade a brief honest sentence for a brief blank pane.

Antigravity (agy, gemini-3.1-pro-high, high effort): ran, two findings, one landed and one false positive.

Landed: the settings keywords test sliced between two indexOf results without checking the second. A missing end marker returns -1 and slice(start, -1) would have quietly widened the assertion to most of the file, so any other tab's descriptor could have satisfied it. Both boundaries are asserted now, the same shape the dead-tab test already used.

False positive: it reported that the container payload in scripts/test-owui-hive-frontend.sh lacks set -e and therefore swallows a vitest failure. set -eu is the first thing in that payload, and the mutation runs demonstrate it empirically: two of the three mutations fail only on assertions, never on compilation, and both exit 2.

It found nothing to change in the money surface, the enterprise gating, the refresh policy, the localisation, or in whether the new tests can fail for the defects they name.

Plain adversarial pass: covered by the independent review this round answers, plus the mutation runs, which are the part that actually established the original findings.

@sakibsadmanshajib
sakibsadmanshajib merged commit f88e4da into main Aug 29, 2026
29 checks passed
@sakibsadmanshajib
sakibsadmanshajib deleted the feat/chat-settings-usage-tab branch August 29, 2026 03:38
sakibsadmanshajib added a commit that referenced this pull request Aug 29, 2026
…#1364)

## What this changes

Repo-local agent tooling only. No product code, no workflows, no runtime
behavior. Linked to no issue.

### 1. Four dead skills deleted

`.claude/skills/debug-issue.md`, `explore-codebase.md`,
`refactor-safely.md` and `review-changes.md` all instruct the agent to
call a `code-review-graph` MCP server: `get_minimal_context`,
`semantic_search_nodes`, `query_graph`, `get_flow`, `list_flows`,
`detect_changes`, `get_impact_radius`, `get_affected_flows`,
`get_architecture_overview`, `list_communities`, `find_large_functions`,
`refactor_tool`, `apply_refactor_tool`.

That server does not exist here. Verified in this worktree before
deleting:

- No `.mcp.json` at the repository root, and `find . -name .mcp.json`
returns nothing anywhere in the tree.
- No `graphify-out/` directory in the checkout.
- Outside those four files, the string `code-review-graph` appears only
in `.gitignore` and a historical note in `.wolf/cerebrum.md`.
- The tool names themselves (`get_minimal_context`,
`semantic_search_nodes`, `query_graph`, `get_impact_radius`) appear in
those four files and nowhere else.

An agent invoking one of these is told to call tools that are absent,
and the likely failure is improvisation rather than a clean error. Their
shared "at most 5 tool calls, at most 800 output tokens" budget is
meaningless without the graph it was written for.

**`review-changes` is deleted rather than rewritten**, and that was a
judgment call worth stating. Rewriting it around `git diff` and `gh pr
diff` leaves a file whose remaining content is a pointer to
`prove-test-load-bearing.md` and `pr-ci-status.md`, both of which
already carry front matter descriptions that route agents to themselves,
plus a restatement of the review sequence that
`.claude/rules/orchestrator.md` stage 6 already owns and that the global
`adversarial-pr-review` skill defines in full. A third copy of that
sequence is a third thing to keep in sync, and the two skills it would
point at are already discoverable. The two genuinely repo-specific
lessons that a rewritten `review-changes` would have carried are folded
into `prove-test-load-bearing.md` instead, where the surrounding
material is about exactly that.

### 2. Patterns added

**Assert on the wire, not on the struct**
(`prove-test-load-bearing.md`). For anything crossing an HTTP or SSE
boundary, assert on serialized bytes: `httptest.ResponseRecorder.Body`,
the SSE frame text, or a decoded `map[string]any`. A struct assertion is
blind to a missing `json:"..."` tag, a custom `MarshalJSON`, an
`omitempty` erasing a legitimate zero, and any downstream rewrite. Names
this repository's three body-rewriting layers, since a struct assertion
upstream of any of them cannot see what actually goes out:

- `injectMemoryBlock`, `apps/edge-api/internal/chat/memory.go`
- the Anthropic request translator,
`apps/edge-api/internal/anthropic/translate_request.go`
- `buildMessagesFromInput`,
`apps/edge-api/internal/inference/responses.go`

Worked example is issue #1329 / PR #1334: `StreamUsage` carried
`omitempty` on every field, so `message_start` serialized as
`"usage":{}` with no required member at all. The struct was correct; the
serialization was not.
`apps/edge-api/internal/anthropic/usage_wire_test.go` is the guard that
can see it, and its own header states the same lesson.

**The gate's scope is not the gate's name**
(`prove-test-load-bearing.md`). A gate can be green because it never
examined the thing. Two shapes, both from real merges:

- A scope narrower than its name.
`scripts/test-owui-hive-frontend.sh:175` runs `node
owui-hive-svelte-compile-check.mjs lib/hive`, so coverage stops at that
one directory. Three simultaneous mutations including a hard parse error
left the suite reporting `16 passed / 208 passed / 13/13 components
compiled`, exit 0 (PR #1298). Every count was true; none of them counted
the broken file.
- A gate nothing ever runs. `.claude/hooks/hooks.selfcheck.js` had real
cases and no caller, which is how a secrets scanner blind to every
MultiEdit survived (issues #1333, #1339). PR #1337 wired it into
`ci.yml:705`.

The check to actually run: name the file you changed, read the gate's
invocation rather than its title, find the path or glob argument, and
confirm your file is inside it. Negative control offered: break the file
on purpose and rerun the gate.

**A CONFLICTING PR runs no CI at all while looking green**
(`pr-ci-status.md`, new cause 0, ahead of the existing three because it
invalidates the evidence they are diagnosed from). Read `mergeable`
before `mergeStateStatus`. `mergeable: CONFLICTING` means GitHub cannot
build `refs/pull/N/merge`, so no `pull_request` workflow run is created
at all, so `statusCheckRollup` is empty or stale and the page reads as
"no problems found" rather than "not evaluated". Observed on PR #1336:
while CONFLICTING it showed only CodeQL, GitGuardian and CodeRabbit,
which scans as green at a glance; the full suite ran only after a
rebase. Notes `.wolf/buglog.jsonl` as the recurring cause on this repo,
since GitHub's server-side merge ignores `merge=union` (issue #873). The
waiting-for-CI section gains one line: an empty `statusCheckRollup` is
ambiguous between "not started" and "cannot start", so read `mergeable`
first.

**`mergeStateStatus: UNKNOWN` means retry, not fail**
(`pr-ci-status.md`, new section ahead of the BLOCKED material). UNKNOWN
means GitHub has not finished computing the merge commit, and it appears
right after a push, a base update, or a force-push. Poll again after a
few seconds. Treating it as failure produced two false NOT-MERGED
reports in one session, on PRs that had in fact merged.

### 3. Two corrections

- `memory-tools.md` listed claude-mem as a live option with an
`mcp__plugin_claude-mem_mcp-search__*` detection hint and a `mem-search`
instruction. claude-mem was retired 2026-06-12 and survives only as a
read-only sqlite archive at `~/.claude-mem/claude-mem.db`. The row now
says so and tells the agent not to look for those tools.
`memory-layers.md` line 71 carried the same stale `mem-search` pointer
and is corrected the same way.
- `worktree-compose-stack.md` gains one paragraph:
`scripts/set-compose-project-name.sh:103-107` writes
`deploy/docker/.env` unconditionally but the repository-root `.env` only
if that file already exists, so the script has to be re-run with
`--check` after an `.env` is created or copied, not only once at
worktree creation. A worktree namespaced before its `.env` was copied in
carries the namespace on one file and the default `hive` project name on
the other, which is a container collision waiting to happen.

### 4. New skill: `owui-fork-edits.md`

Nothing in `.claude/skills/` encoded the Open WebUI patch discipline,
which is expensive to rediscover. Covers:

- The chat image builds only the frontend from `vendor/open-webui` and
replaces `/app/build`; the backend comes from a pinned upstream image,
so a backend edit under the vendored tree is inert and produces no
error.
- A version assertion in the final stage fails the build when the
vendored tree and the pinned backend disagree.
- Backend changes go through `deploy/docker/owui-patches/`, and every
patch asserts its own effect: an anchor that does not match exactly once
prints what it expected and exits 1, and Python splices are `ast.parse`d
before writing.
- `Caddyfile.owui` is a third layer that can 404 a route independently
of both others.
- `dump_bundle_excerpts.py` plus `pinned-bundle-excerpts.json` is how
the literal bundle assertions stay honest, since PR CI never builds the
image.

It also retires one stale claim that several patch comments still assert
in their justifying prose: "every tenant OWNER holds the Open WebUI
admin role" is no longer true. `owui-patches/tenant_role_from_db.py`
lines 17 to 30 revoked that mapping, and OWUI `admin` now requires an
ACTIVE `owner` row in `public.account_memberships` on an account with
`is_platform_admin = true`. A tenant OWNER is an ordinary OWUI `user`.
The patches remain correct defence; only the prose is stale.

### 5. `CLAUDE.md`

The paragraph enumerating project-level skills is replaced with a
pointer to the directory. That list had gone stale in both directions:
it named four skills that had been dead for months, and omitted six that
existed (`memory-layers`, `pr-ci-status`, `pr-visual-proof`,
`prove-test-load-bearing`, `verify-deploy-happened`,
`worktree-compose-stack`).

## Verification

- Absence of the MCP server confirmed by direct search in this worktree
before deleting anything, as listed above.
- Every factual claim added was read out of the file it cites:
`scripts/set-compose-project-name.sh`,
`deploy/docker/owui-patches/tenant_role_from_db.py`,
`dump_bundle_excerpts.py`, `apply_credits_patch.py`,
`deploy/docker/Dockerfile.open-webui`, `deploy/docker/Caddyfile.owui`,
`apps/edge-api/internal/anthropic/types.go`, `usage_wire_test.go`,
`scripts/owui-hive-svelte-compile-check.mjs`,
`scripts/test-owui-hive-frontend.sh`, `.github/workflows/ci.yml`.
- `node .claude/hooks/hooks.selfcheck.js`: 61/61 passed.
- Every `.wolf/decisions.md` id cited (D-036, D-040, D-044, D-052)
exists and is live; none of the revoked or retired entries are cited.

## Test plan

- [ ] CI green. `.claude/*` is on the inert-path allowlist in `ci.yml`,
so the required checks report green without their heavy steps;
`CLAUDE.md` is not, so the full suite runs on that file.
- [ ] `node .claude/hooks/hooks.selfcheck.js` still passes.
- [ ] No product code, workflow, or runtime behavior touched.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01WyEwUxZCArdn1ZUDkTvuQ1

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
sakibsadmanshajib added a commit that referenced this pull request Aug 29, 2026
## Summary

This is the batched buglog follow-up for the pull requests merged to
`main` on 2026-08-29. Its diff is `.wolf/buglog.jsonl` and nothing else.

Per `.claude/rules/openwolf.md`, every fixed bug, error, failed test or
failed build must be logged, but the line may never be appended on a fix
branch. `merge=union` in `.gitattributes` resolves concurrent appends
locally and is ignored by GitHub's server side merge, so two branches
that both appended land in hard conflict there. An unmergeable pull
request gets no `refs/pull/N/merge`, no `pull_request` run and therefore
zero checks, and the required status gate then blocks the merge for a
reason the page never states (issue #873). Each fix accordingly carried
its entry in its own pull request body, and this pull request copies
them onto `main` in one batch, which the protocol explicitly prefers
over one pull request per entry.

## Scope examined

Fifty nine pull requests merged to `main` on 2026-08-29. Forty eight of
them carried at least one entry, for eighty two entries in total. Thirty
two of those were already on `main` and are skipped, leaving fifty
appended here from thirty four pull requests.

The largest block of skips comes from #1342, the equivalent batch for
the 2026-08-28 merges, which merged earlier the same day and already
landed thirty six entries covering #1257, #1268, #1276, #1277, #1287,
#1292, #1293, #1294, #1296, #1301, #1303, #1305, #1313, #1335 and #1337.

## What landed

Fifty entries appended, one JSON object per line, append only. The 232
pre-existing lines are byte identical to `origin/main` (verified by
hashing the first 232 lines of the result against the base file). Every
line in the resulting file parses as JSON and carries `error_message`,
`root_cause`, `fix` and `tags`.

| Source | Entries |
|---|---|
| #1083 | 2 |
| #1277 | 1 |
| #1278 | 1 |
| #1298 | 1 |
| #1334 | 1 |
| #1336 | 3 |
| #1343 | 1 |
| #1346 | 1 |
| #1351 | 1 |
| #1365 | 2 |
| #1368 | 1 |
| #1369 | 1 |
| #1371 | 3 |
| #1375 | 3 |
| #1376 | 1 |
| #1378 | 1 |
| #1379 | 2 |
| #1388 | 5 |
| #1389 | 3 |
| #1390 | 2 |
| #1393 | 1 |
| #1394 | 1 |
| #1410 | 1 |
| #1417 | 1 |
| #1421 | 1 |
| #1423 | 1 |
| #1424 | 1 |
| #1426 | 1 |
| #1429 | 1 |
| #1431 | 1 |
| #1433 | 1 |
| #1434 | 1 |
| #1436 | 1 |
| #1439 | 1 |

Entries are copied verbatim from their source pull request bodies.
Nothing was rewritten, no field was invented, and no field was added. No
JSON needed repair: all eighty two extracted entries parsed on the first
attempt and all four required fields were present on every one.

## Merged pull requests that carried no entry

Eleven of the fifty nine. Recorded here because the gap is itself the
useful signal.

| Pull request | Title | Assessment |
|---|---|---|
| #1013 | chore(deps): bump the go-minor-patch group across 1 directory
with 4 updates | Dependabot bump, no defect fixed, no entry expected |
| #1015 | chore(deps): bump the go-minor-patch group across 1 directory
with 6 updates | Dependabot bump, no entry expected |
| #1016 | chore(deps): bump golang from 1.26-alpine to 1.27-alpine in
/deploy/docker | Dependabot bump, no entry expected |
| #1218 | chore(deps): bump postcss from 8.5.19 to 8.5.26 in
/apps/desktop | Dependabot bump, no entry expected |
| #1219 | chore(deps): bump golang.org/x/crypto from 0.41.0 to 0.52.0 in
/apps/control-plane | Dependabot bump, no entry expected |
| #1342 | chore: batch buglog entries for the 2026-08-28 merges | The
previous batch pull request itself, correctly carries no entry of its
own |
| #1364 | chore: remove four dead skills and record the patterns that
cost time | Protocol gap. The body records patterns that cost time,
which is the shape of a buglog entry, but none was written as one |
| #1383 | test: retire stale expected-failure markers, restore the ones
that are true (#1381, #1382, #1324) | Protocol gap. Stale `it.fails`
markers reading as red is a real defect that was fixed here and should
have carried an entry |
| #1384 | docs: correct D-047, hive-auto reverted to variable pricing
(D-059) | Decision ledger correction, arguably a documentation defect,
no entry written |
| #1387 | chore(deps): bump next from 15.5.23 to 16.3.3 in
/apps/agent-console | Dependabot bump, no entry expected |
| #1398 | docs: rescue the 2026-08-25 parity captures and add the
2026-08-29 QA matrix evidence | Documentation and evidence rescue, no
entry written |

Six of the eleven are Dependabot bumps and one is the previous batch, so
the genuine protocol gaps are #1364, #1383, #1384 and #1398. Of those,
#1383 is the one worth a follow-up: it fixed a real defect class (a
stale expected-failure marker reads as a red "Expect test to fail" and
gets dismissed as pre-existing) and left no record.

## Entries skipped as already present

Thirty two. Thirty of them matched an entry already on `main` on
`error_message`, `id` or `fix`. Two more from #1278 are semantic
duplicates that an exact match would have missed, and were skipped after
reading the landed entries they duplicate:

- #1278's `streaming content_block_start omits text field` entry is
covered by the consolidated
`bug-2026-08-28-anthropic-sdk-wire-conformance` entry landed from #1296,
whose root cause names the same `omitempty` on
`StreamContentBlock.Text`.
- #1278's `GET /v1/models leaked an upstream provider name` entry is
covered by `BUG-1284`, landed from #1300, which names the same
`public.model_aliases.summary` publication path.

#1278's third entry, on `top_k` forwarding producing a 400, is not
covered anywhere on `main` and is appended here. #1342 recorded #1278 as
fully "merged into #1296", which was accurate for two of its three
entries.

## Note on entry quality

One appended entry is thin: #1277's parity re-score record carries
`error_message` of `n/a` and a root cause of "console had no
privacy/data-policy surface at all". It is a parity gap record rather
than a defect record. It is included exactly as written rather than
embellished, per the protocol's preference for the author's own words.

## Test plan

- [x] Branch cut fresh from `origin/main`, diff is `.wolf/buglog.jsonl`
and nothing else
- [x] First 232 lines byte identical to the base file (md5 match)
- [x] All 282 resulting lines parse as JSON and carry `error_message`,
`root_cause`, `fix` and `tags`
- [x] No `.wolf/` telemetry (`anatomy.md`, `memory.md`,
`token-ledger.json`, `hooks/_session.json`, `buglog.json`) in the commit
- [ ] The six required checks report green via the inert path allowlist
in `.github/workflows/ci.yml`

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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