Skip to content

🪙 feat: Context Gauge UX, Hover Snapshot, Click Breakdown, Currency, Cost-On-By-Default#13739

Merged
danny-avila merged 6 commits into
devfrom
feat/context-gauge-ux
Jun 14, 2026
Merged

🪙 feat: Context Gauge UX, Hover Snapshot, Click Breakdown, Currency, Cost-On-By-Default#13739
danny-avila merged 6 commits into
devfrom
feat/context-gauge-ux

Conversation

@danny-avila

Copy link
Copy Markdown
Owner

Follow-up UX pass on the context-usage gauge (builds on #13670 / #13734, now on dev). Inspired by Claude Code's status indicator.

What changed

  1. Hover → snapshot, click → breakdown. Hovering the gauge now shows a compact one-line snapshot (Context 341.7k / 1.0M (34%), plus branch cost when enabled) via the existing Tooltip primitive. The full breakdown no longer opens on hover — it opens on click (Ariakit popover) and dismisses on outside-click / Escape / focus-away, standard popover behavior.

  2. Hidden until there's data. The gauge is no longer rendered on a fresh, message-less chat (gated on usedTokens > 0); it eases into view (animate-in fade) once the first tokens land.

  3. contextCost on by default. interface.contextCost now defaults to true (resolved per-field in loadDefaultInterface, so it applies unless an admin explicitly sets false). Pricing exposure + cost display follow.

  4. Configurable display currency (bare-minimum, accurate). New interface.currency: { code, rate } — an ISO-4217 code and a static USD→local multiplier — so non-USD communities (EUR, JPY, CNY, BRL, ZAR, …) can show costs in their currency:

    interface:
      currency:
        code: EUR
        rate: 0.92   # static USD→local multiplier (no live FX)

    formatCost applies usd × rate and formats via a cached, locale-aware Intl.NumberFormat (correct symbol/decimals per currency; falls back to USD on a bad code). Display-only — model prices stay USD server-side. Default (USD, rate 1) is byte-identical to before.

Testing

  • Unit: formatCost currency/rate/fallback (tokens.spec); loadDefaultInterface contextCost-default + currency pass-through (interface.spec, 13 ✓); packages/api tokenConfig + usage gating (79 ✓).
  • E2E (mock): usage.spec updated hover→click; new test asserts gauge absent on a new chat, snapshot tooltip on hover, breakdown on click, and dismissal on Escape + outside-click (5 ✓).
  • Client tsc, eslint, import-sort all clean.

Display-only currency; no server-side pricing/FX changes.

Flip interface.contextCost to default-on (schema default true, resolved per-field
in loadDefaultInterface so it applies unless an admin explicitly sets false).

Add interface.currency { code, rate }: an ISO-4217 code and a static USD→local
multiplier so non-USD communities (EUR, JPY, CNY, BRL, ZAR, …) can show costs in
their currency. Inner fields are required (no nested defaults) to keep zod
input/output identical; loadDefaultInterface passes it through. Display-only —
model prices stay USD server-side.
formatCost(usd, currency?) applies the static rate (usd × rate) and formats via
a cached Intl.NumberFormat keyed by currency code — locale-correct symbol and
per-currency decimals, falling back to USD on a malformed code. The USD default
(code USD, rate 1) is byte-identical to the prior output.
Replace the hover-only HoverCard with: a compact hover snapshot tooltip
("Context 341.7k / 1.0M (34%)" + cost when enabled) via the existing Tooltip
primitive, and a click-opened Ariakit popover for the full breakdown that
dismisses on outside-click/Escape/blur. Gate visibility on usedTokens > 0 so a
fresh, message-less chat shows nothing, with an animate-in fade as the first
tokens land. Thread the display currency into the breakdown + snapshot.
Switch the breakdown specs from hover to click, and add a test that the gauge is
absent on a new chat, surfaces the snapshot tooltip on hover, opens the breakdown
on click, and dismisses on Escape and outside-click.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c183b988c8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread client/src/utils/tokens.ts Outdated
}

export function formatCost(usd: number, currency: CurrencyConfig = DEFAULT_CURRENCY): string {
const amount = usd * currency.rate;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid converting amounts when the currency fallback is USD

When interface.currency.code is malformed but rate is configured, amount is converted with the local rate before currencyFormatter falls back to a USD formatter, so a typo like { code: 'EURO', rate: 0.92 } displays $9.20 for a $10 cost. That presents a non-USD amount with a USD symbol; either validate/reject the code before startup or fall back both the code and rate to USD.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 90874c3. An unsupported/malformed code now falls back to USD and rate 1 (resolved once up front), so { code: "EURO", rate: 0.92 } renders $10.00 for a $10 cost rather than a converted $9.20 under the wrong symbol. Test added.

portal
unmountOnHide
aria-label={localize('com_ui_context_usage')}
className="z-50 rounded-xl border border-border-medium bg-surface-secondary p-3 shadow-lg focus:outline-none"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the click popover above the hover tooltip

After the intended hover-then-click flow, the snapshot tooltip can remain mounted while the breakdown opens; the shared tooltip CSS uses z-index: 150, but this popover is only z-50, and both are positioned above the same gauge. In that scenario the compact tooltip sits on top of the breakdown, so either hide the tooltip while the popover is open or render the popover at a higher stacking level.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 90874c3 — raised the breakdown popover to z-[200] so it always sits above the z-150 hover tooltip on the brief overlap. (The Ariakit tooltip also dismisses on press/blur, but the stacking guarantee covers the hover-then-click window.)

Comment on lines +496 to +497
minimumFractionDigits: minDigits,
maximumFractionDigits: maxDigits,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Preserve zero-decimal currency formatting

When a deployment configures a valid zero-decimal currency such as JPY, forcing these fraction digits overrides Intl's currency defaults: formatCost(5, { code: 'JPY', rate: 1 }) renders ¥5.00, and sub-unit values render with four decimals. That makes costs in currencies without minor units look like they have cents; derive the fraction digits from the selected currency or only apply the extra precision where the currency supports it.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 90874c3. Fraction digits now derive from the currency's own Intl defaults: JPY renders ¥5 (not ¥5.00), and the extra sub-unit precision is applied only to currencies that have minor units. USD output is unchanged. Test added.

Comment thread client/src/utils/tokens.ts Outdated
}

export function formatCost(usd: number, currency: CurrencyConfig = DEFAULT_CURRENCY): string {
const amount = usd * currency.rate;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Guard against incomplete currency overrides

If a DB config override is applied incrementally, for example setting interface.currency.code before interface.currency.rate, this multiplication receives undefined and the UI formats the result as values like €NaN for every cost display. YAML config is schema-checked, but the admin field patch path stores dot-path overrides directly, so formatCost should defensively fall back to the default rate unless currency.rate is a finite number.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 90874c3. formatCost now treats a non-finite or non-positive rate as 1, so a partial override (code set before rate) renders €10.00 instead of €NaN. Test added covering both undefined and NaN rates.

@danny-avila danny-avila changed the title 🪙 feat: Context Gauge UX — Hover Snapshot, Click Breakdown, Currency, Cost-On-By-Default 🪙 feat: Context Gauge UX, Hover Snapshot, Click Breakdown, Currency, Cost-On-By-Default Jun 14, 2026
Address Codex review on the currency display:
- Unsupported currency code now falls back to USD AND rate 1, so a typo like
  { code: 'EURO', rate: 0.92 } no longer shows a converted amount under a $
  symbol (was $9.20 for a $10 cost; now $10.00).
- A non-finite/negative rate (e.g. a partial admin override that set code before
  rate) falls back to rate 1, so a cost never renders as NaN.
- Fraction digits derive from the currency's own defaults, so zero-decimal
  currencies (JPY) render ¥5, not ¥5.00, and extra sub-unit precision applies
  only to currencies that have minor units. USD output is unchanged.
- Raise the click breakdown popover to z-[200] so it always sits above the
  z-150 hover tooltip when both briefly coexist.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 90874c382b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread client/src/utils/tokens.ts Outdated
Comment on lines +496 to +497
new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).resolvedOptions()
.maximumFractionDigits ?? 2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Validate currency codes before trusting Intl

When an admin mistypes a three-letter code such as EUU or configures a common non-ISO code like RMB, Intl.NumberFormat does not throw; it resolves fraction digits and formats with that literal code. Because base is non-null, formatCost then applies the USD→local rate instead of falling back to USD, so users can see a converted amount under an invalid currency label despite the intended fallback. Please validate against supported ISO-4217 codes, or otherwise reject unsupported codes, before applying the rate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in the latest commit. Now validating against Intl.supportedValuesOf("currency") (the ISO-4217 set) rather than relying on Intl.NumberFormat to throw — so a well-formed-but-bogus code like EUU or a non-ISO RMB is rejected and falls back to USD and rate 1, never a converted amount under a bad label. Tests added for both.

Comment thread client/src/utils/tokens.ts Outdated
/** Sub-unit precision only for currencies that have minor units, so
* zero-decimal currencies (e.g. JPY) keep their convention. */
const precise = base > 0 ? base + 2 : 0;
const smallest = base > 0 ? 0.01 : 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Use the currency’s minor unit for tiny amounts

For currencies with three fractional digits such as KWD, BHD, or JOD, hard-coding smallest to 0.01 means small converted costs are rounded up to <KWD 0.010 instead of using the actual minor unit (0.001) or preserving values like 0.005. This contradicts the currency-aware formatting path for non-USD communities and overstates the threshold for any configured three-decimal currency; derive this from base instead of assuming cents for every fractional currency.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in the latest commit. The tiny-amount threshold now derives from the currency's minor unit (10^-fractionDigits): 0.001 for KWD/BHD/JOD, 0.01 for 2-decimal currencies, 1 for zero-decimal — no more hard-coded 0.01. Sub-unit precision also trims to each currency's own scale. Test added covering KWD at 0.005 and 0.0005.

Address Codex review on currency formatting:
- Intl.NumberFormat accepts any well-formed 3-letter code (EUU, RMB) without
  throwing, so the previous construct-based check missed typos/non-ISO codes and
  applied the rate under a bogus label. Validate against Intl.supportedValuesOf
  ('currency') (the ISO-4217 set); unsupported codes fall back to USD + rate 1.
  Codes are normalized to upper-case; graceful fallback if the runtime lacks
  supportedValuesOf.
- The tiny-amount threshold now derives from the currency's minor unit
  (10^-fractionDigits): 0.01 for 2-decimal, 0.001 for 3-decimal (KWD/BHD/JOD),
  1 for zero-decimal — instead of a hard-coded 0.01. Sub-unit precision trims to
  each currency's own scale. USD output unchanged.
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: c288fad161

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila merged commit 7cf2877 into dev Jun 14, 2026
31 checks passed
@danny-avila
danny-avila deleted the feat/context-gauge-ux branch June 14, 2026 17:38
fuuuzzy pushed a commit to fuuuzzy/LibreChat that referenced this pull request Jun 18, 2026
…Cost-On-By-Default (danny-avila#13739)

* 🪙 feat: Default Context Cost On + Configurable Display Currency

Flip interface.contextCost to default-on (schema default true, resolved per-field
in loadDefaultInterface so it applies unless an admin explicitly sets false).

Add interface.currency { code, rate }: an ISO-4217 code and a static USD→local
multiplier so non-USD communities (EUR, JPY, CNY, BRL, ZAR, …) can show costs in
their currency. Inner fields are required (no nested defaults) to keep zod
input/output identical; loadDefaultInterface passes it through. Display-only —
model prices stay USD server-side.

* 🪙 feat: Currency-Aware Context Cost Formatting

formatCost(usd, currency?) applies the static rate (usd × rate) and formats via
a cached Intl.NumberFormat keyed by currency code — locale-correct symbol and
per-currency decimals, falling back to USD on a malformed code. The USD default
(code USD, rate 1) is byte-identical to the prior output.

* 💄 feat: Gauge Hover Snapshot, Click-to-Open Breakdown, Hide Until Data

Replace the hover-only HoverCard with: a compact hover snapshot tooltip
("Context 341.7k / 1.0M (34%)" + cost when enabled) via the existing Tooltip
primitive, and a click-opened Ariakit popover for the full breakdown that
dismisses on outside-click/Escape/blur. Gate visibility on usedTokens > 0 so a
fresh, message-less chat shows nothing, with an animate-in fade as the first
tokens land. Thread the display currency into the breakdown + snapshot.

* 🧪 test: Gauge Interaction + Visibility E2E

Switch the breakdown specs from hover to click, and add a test that the gauge is
absent on a new chat, surfaces the snapshot tooltip on hover, opens the breakdown
on click, and dismisses on Escape and outside-click.

* 🪙 fix: Harden Currency Resolution + Layer Breakdown Above Tooltip

Address Codex review on the currency display:
- Unsupported currency code now falls back to USD AND rate 1, so a typo like
  { code: 'EURO', rate: 0.92 } no longer shows a converted amount under a $
  symbol (was $9.20 for a $10 cost; now $10.00).
- A non-finite/negative rate (e.g. a partial admin override that set code before
  rate) falls back to rate 1, so a cost never renders as NaN.
- Fraction digits derive from the currency's own defaults, so zero-decimal
  currencies (JPY) render ¥5, not ¥5.00, and extra sub-unit precision applies
  only to currencies that have minor units. USD output is unchanged.
- Raise the click breakdown popover to z-[200] so it always sits above the
  z-150 hover tooltip when both briefly coexist.

* 🪙 fix: Validate ISO-4217 Codes + Derive Tiny Threshold from Minor Unit

Address Codex review on currency formatting:
- Intl.NumberFormat accepts any well-formed 3-letter code (EUU, RMB) without
  throwing, so the previous construct-based check missed typos/non-ISO codes and
  applied the rate under a bogus label. Validate against Intl.supportedValuesOf
  ('currency') (the ISO-4217 set); unsupported codes fall back to USD + rate 1.
  Codes are normalized to upper-case; graceful fallback if the runtime lacks
  supportedValuesOf.
- The tiny-amount threshold now derives from the currency's minor unit
  (10^-fractionDigits): 0.01 for 2-decimal, 0.001 for 3-decimal (KWD/BHD/JOD),
  1 for zero-decimal — instead of a hard-coded 0.01. Sub-unit precision trims to
  each currency's own scale. USD output unchanged.
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
…Cost-On-By-Default (danny-avila#13739)

* 🪙 feat: Default Context Cost On + Configurable Display Currency

Flip interface.contextCost to default-on (schema default true, resolved per-field
in loadDefaultInterface so it applies unless an admin explicitly sets false).

Add interface.currency { code, rate }: an ISO-4217 code and a static USD→local
multiplier so non-USD communities (EUR, JPY, CNY, BRL, ZAR, …) can show costs in
their currency. Inner fields are required (no nested defaults) to keep zod
input/output identical; loadDefaultInterface passes it through. Display-only —
model prices stay USD server-side.

* 🪙 feat: Currency-Aware Context Cost Formatting

formatCost(usd, currency?) applies the static rate (usd × rate) and formats via
a cached Intl.NumberFormat keyed by currency code — locale-correct symbol and
per-currency decimals, falling back to USD on a malformed code. The USD default
(code USD, rate 1) is byte-identical to the prior output.

* 💄 feat: Gauge Hover Snapshot, Click-to-Open Breakdown, Hide Until Data

Replace the hover-only HoverCard with: a compact hover snapshot tooltip
("Context 341.7k / 1.0M (34%)" + cost when enabled) via the existing Tooltip
primitive, and a click-opened Ariakit popover for the full breakdown that
dismisses on outside-click/Escape/blur. Gate visibility on usedTokens > 0 so a
fresh, message-less chat shows nothing, with an animate-in fade as the first
tokens land. Thread the display currency into the breakdown + snapshot.

* 🧪 test: Gauge Interaction + Visibility E2E

Switch the breakdown specs from hover to click, and add a test that the gauge is
absent on a new chat, surfaces the snapshot tooltip on hover, opens the breakdown
on click, and dismisses on Escape and outside-click.

* 🪙 fix: Harden Currency Resolution + Layer Breakdown Above Tooltip

Address Codex review on the currency display:
- Unsupported currency code now falls back to USD AND rate 1, so a typo like
  { code: 'EURO', rate: 0.92 } no longer shows a converted amount under a $
  symbol (was $9.20 for a $10 cost; now $10.00).
- A non-finite/negative rate (e.g. a partial admin override that set code before
  rate) falls back to rate 1, so a cost never renders as NaN.
- Fraction digits derive from the currency's own defaults, so zero-decimal
  currencies (JPY) render ¥5, not ¥5.00, and extra sub-unit precision applies
  only to currencies that have minor units. USD output is unchanged.
- Raise the click breakdown popover to z-[200] so it always sits above the
  z-150 hover tooltip when both briefly coexist.

* 🪙 fix: Validate ISO-4217 Codes + Derive Tiny Threshold from Minor Unit

Address Codex review on currency formatting:
- Intl.NumberFormat accepts any well-formed 3-letter code (EUU, RMB) without
  throwing, so the previous construct-based check missed typos/non-ISO codes and
  applied the rate under a bogus label. Validate against Intl.supportedValuesOf
  ('currency') (the ISO-4217 set); unsupported codes fall back to USD + rate 1.
  Codes are normalized to upper-case; graceful fallback if the runtime lacks
  supportedValuesOf.
- The tiny-amount threshold now derives from the currency's minor unit
  (10^-fractionDigits): 0.01 for 2-decimal, 0.001 for 3-decimal (KWD/BHD/JOD),
  1 for zero-decimal — instead of a hard-coded 0.01. Sub-unit precision trims to
  each currency's own scale. USD output unchanged.
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.

1 participant