Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(site): fix price threshold display and debounce model price lookup - #28298

Merged
DanielleMaywood merged 1 commit into
mainfrom
fix/model-pricing-post-merge-feedback
Aug 19, 2026
Merged

fix(site): fix price threshold display and debounce model price lookup#28298
DanielleMaywood merged 1 commit into
mainfrom
fix/model-pricing-post-merge-feedback

Conversation

@DanielleMaywood

Copy link
Copy Markdown
Contributor

Follow-up to post-merge review feedback on #28290 from @ssncferreira.

< dropped from threshold prices: a tiny positive price formats as a threshold (<$0.0001), but the estimate field sliced the first character assuming a leading $, which dropped the less-than marker and rendered $$0.0001. formatPricePerMillionTokens now returns the display value without a currency symbol plus a belowThreshold flag, so the field renders the < ahead of the $ addon.

Per-keystroke API calls in edit mode: in edit mode the model identifier is a plain input, so each keystroke keyed a new live price query and fired a request. The lookup now runs on a 500ms debounced identifier (existing useDebouncedValue hook), and a pending debounce reads as loading so stale prices are not shown for identifiers the user has typed past. Catalog lookups stay synchronous on the live value.

Fix approaches confirmed with Qwen 3.8 Max before implementation.

Implementation notes for reviewers
  • formatPricePerMillionTokens has exactly one caller (PricingEstimateFields), which previously re-parsed the display string with .slice(1). Returning { belowThreshold, value } removes the string round-trip instead of adding a second parsing helper; unit tests updated to match.
  • Debounce placement is inside PricingEstimateFields so edit mode, duplicate mode, and add-without-catalog all benefit. staleTime could not fix this: each keystroke mints a new query key, so there is never a cached entry to respect.
  • The debouncePending gate is needed because a previously fetched model can be cached: without it, typing back to a cached prefix then continuing would render stale prices with no loading indicator. While pending, the existing skeleton renders.
  • Stories added: CostEstimateBelowThresholdPrice (seeds a 50-micro input price, asserts <$ marker and 0.0001 value) and CostEstimateDebouncesLivePriceLookup (spies on getAIModelPrices, types gpt-5.5 character by character, asserts only the mount lookup and one lookup for the settled value fire).

Generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from d3041d1 to a41ae52 Compare August 19, 2026 11:39
@DanielleMaywood

Copy link
Copy Markdown
Contributor 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: a41ae52ae9

ℹ️ 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 on lines +693 to +696
const debouncePending = debouncedModel !== trimmedModel;
const livePriceLoading =
livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess;
const livePrice = livePricesQuery.data?.[0];
debouncePending ||
(livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess);

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 Gate debounce loading on the live lookup

On deployments without the aibridge entitlement, livePricesQuery is disabled, but changing the model still makes debouncePending set livePriceLoading to true. As a result, selecting or typing a catalog model hides its synchronously available prices behind four loading skeletons for 500 ms even though no request can run. Gate this pending state on the live-price query being enabled so catalog-only deployments continue to update immediately.

AGENTS.md reference: site/AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3621dfb. The debounce-pending state is now gated on the live price query being enabled, so catalog-only deployments keep updating immediately without flashing loading placeholders. Added a story (CostEstimateCatalogNotHiddenByDebounce) asserting catalog prices render with no skeletons when the entitlement is absent.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from a41ae52 to 3621dfb Compare August 19, 2026 12:04
@DanielleMaywood

Copy link
Copy Markdown
Contributor 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: 3621dfbaba

ℹ️ 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 on lines +694 to +695
const debouncePending =
livePriceQueryEnabled && debouncedModel !== trimmedModel;

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 Treat the initial model debounce as loading

On an AI Bridge-entitled add form, or after an empty identifier has settled, typing a known catalog model leaves debouncedModel empty, so livePriceQueryEnabled and therefore debouncePending are false for 500 ms. During that window the catalog price renders even though the deployment price lookup has not run; if the deployment has an override, users briefly see an incorrect estimate before it changes to a skeleton and then the live price. Derive the pending state from the current model's eligibility rather than the old debounced model's query-enabled flag.

AGENTS.md reference: site/AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ec8aeee. The pending state now derives from the current model's lookup eligibility (entitled, provider set, non-empty identifier) rather than the debounced model's, so typing into an empty identifier on an entitled form shows the loading placeholder immediately instead of briefly flashing catalog prices before the lookup runs.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

Comment on lines 696 to +699
const livePriceLoading =
livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess;
const livePrice = livePricesQuery.data?.[0];
debouncePending ||
(livePricesQuery.fetchStatus !== "idle" && !livePricesQuery.isSuccess);
const livePrice = debouncePending ? undefined : livePricesQuery.data?.[0];

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 Hide errors belonging to the previous model

When the current live-price lookup has failed and the user changes to another nonempty model, debouncePending becomes true but the query remains keyed to the old model for 500 ms. Its isError state therefore wins at the return below, showing "Couldn't load pricing" for the newly typed model even though its request has not started. Suppress the old query's error while the debounce is pending so this interval uses the intended loading state.

AGENTS.md reference: site/AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in ec8aeee. The previous model's error state is now suppressed while the debounce is pending, so changing the identifier after a failure shows the loading placeholder instead of "Couldn't load pricing." until the new request fires. Covered by the CostEstimateErrorClearsWhileDebouncePending story.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch 2 times, most recently from ec8aeee to 90afc64 Compare August 19, 2026 12:24
@DanielleMaywood

Copy link
Copy Markdown
Contributor 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: 90afc64194

ℹ️ 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".

});
const debouncePending =
entitledWithProvider &&
trimmedModel !== "" &&

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 Discard stale pricing when the identifier is cleared

On an entitled form with a loaded live price, clearing the model identifier leaves debouncedModel pointing at the old model for 500 ms, but this nonempty guard makes debouncePending false. The old query data or error is therefore rendered against the empty identifier until the debounce settles instead of showing the empty state. Fresh evidence beyond the previously fixed nonempty-model case is this explicit trimmedModel !== "" exclusion.

AGENTS.md reference: site/AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in dc6148b. The staleness check is now symmetric: debouncePending is true whenever the debounced identifier differs from the current one, so clearing the field immediately discards the settled live price (or error) and shows the loading placeholder until the debounce settles, at which point the empty state renders. Covered by the CostEstimateClearingModelDiscardsStalePrices story.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from 90afc64 to dc6148b Compare August 19, 2026 12:35
@DanielleMaywood

Copy link
Copy Markdown
Contributor 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: dc6148bb88

ℹ️ 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".

);
};

const mockGPT5LivePriceBookQueries = [

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Inline each story's query wiring

This pre-wired query array is shared by CostEstimateFromLivePriceBook and CostEstimateClearingModelDiscardsStalePrices, so changing one story's server setup implicitly changes the other. Keep only reusable entity data in a fixture and compose each story's { key, data } query entry inline as required by the frontend story-fixture contract.

AGENTS.md reference: site/AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 449103e. The shared query array is replaced by an entity fixture (MockGPT5PriceBookEntry), and each story composes its own { key, data } query entry inline per the FE9 story-fixture contract.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

Comment on lines +749 to +751
<InputGroupAddon align="inline-start">
{price?.belowThreshold ? "<$" : "$"}
</InputGroupAddon>

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 Expose the threshold marker to assistive technology

For a price below the threshold, <$ is rendered only as a sibling of the read-only input and is not part of that input's accessible name or value. A screen-reader user focusing the field therefore hears an exact value such as Input, 0.0001 rather than “less than $0.0001”; associate the marker with the input, for example through an accessible description, so the estimate retains its meaning.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 449103e. Below-threshold price inputs now carry aria-valuetext="less than $0.0001", so screen readers announce the threshold semantics instead of an exact value. Normal prices rely on the default value announcement (the $ addon is read as part of the group); only the < marker needed the override. Asserted in the CostEstimateBelowThresholdPrice story.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from dc6148b to 449103e Compare August 19, 2026 12:50
@DanielleMaywood

Copy link
Copy Markdown
Contributor 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: 449103e9f1

ℹ️ 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 on lines +544 to +548
{
provider: "openai",
model: "gpt-5",
input_price: 1250000,
output_price: 10000000,
cache_read_price: 125000,
input_price: 50,
output_price: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move the inline price entity into a shared fixture

Fresh evidence after the earlier query-wiring fix is this complete inline AIModelPrice variant, while the new base price entry also remains module-local. Provider, model, and timestamps are consequently duplicated and can drift between pricing stories; define the base as a shared Mock* entity under testHelpers, spread it into a named below-threshold variant, and keep only the { key, data } wiring inline.

AGENTS.md reference: site/AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f65a309. The base entry and below-threshold variant now live in site/src/testHelpers/chatModels.ts as MockGPT5ModelPrice and MockGPT5BelowThresholdModelPrice (spread variant, shared timestamps via MOCK_TIMESTAMP); each story keeps only its { key, data } wiring inline.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

Comment on lines +762 to +765
aria-valuetext={
price?.belowThreshold
? `less than $${price.value}`
: undefined

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 Use a supported description for the threshold value

Fresh evidence after the earlier accessibility fix is that this remains an implicit textbox, for which aria-valuetext is not a supported state, so assistive technology may ignore it and announce the raw value 0.0001; the visible <$ marker is still only a sibling. Associate the threshold wording through a supported mechanism such as aria-describedby and visually hidden descriptive text.

AGENTS.md reference: site/AGENTS.md:L18-L19

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in f65a309. aria-valuetext is dropped; below-threshold inputs now use aria-describedby pointing at visually hidden text ("less than $0.0001 USD per million tokens", rendered only while a threshold price is displayed), which is a supported description mechanism for textboxes. Asserted via toHaveAccessibleDescription in the CostEstimateBelowThresholdPrice story.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from 449103e to f65a309 Compare August 19, 2026 13:08
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: f65a30935c

ℹ️ 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".

@DanielleMaywood
DanielleMaywood marked this pull request as ready for review August 19, 2026 14:25

@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: f65a30935c

ℹ️ 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".

within,
} from "storybook/test";
import { reactRouterParameters } from "storybook-addon-remix-react-router";
import { vi } from "vitest";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace the runtime Vitest dependency in the story

When these stories run in the normal Storybook preview rather than through the Vitest addon, the runtime vi.waitFor and vi.mocked calls have no active Vitest worker and can fail instead of executing the play functions. This also prevents the newly added interaction coverage from serving as usable Storybook documentation. Use the already imported waitFor and assertions from storybook/test so the stories work in both preview and test-runner contexts.

AGENTS.md reference: site/AGENTS.md:L9-L10

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 78452ff. The runtime vitest import is gone; the stories use waitFor and mocked from storybook/test, so the play functions run in the plain Storybook preview as well as under the Vitest addon.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

...aiModelPrices(normalizedProvider, trimmedModel),
enabled:
aibridgeEntitled && normalizedProvider !== "" && trimmedModel !== "",
...aiModelPrices(normalizedProvider, debouncedModel),

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 querying a stale model after a provider change

If an admin edits the identifier and switches providers within the 500 ms debounce window, this query combines the new normalizedProvider with the previous debouncedModel, issuing a lookup for a provider/model pair the form never represented. The pending gate hides the result, but the unnecessary request still defeats the debounce's request-reduction goal; debounce the provider/model pair together or disable the query while the model values differ.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 78452ff. The debounce now wraps the { provider, model } pair as a unit, so the query key only ever represents a settled pair; a provider change within the debounce window counts as pending and never issues a lookup mixing the new provider with the previous identifier.

Reply generated by Coder Agents on behalf of @DanielleMaywood.

Post-merge feedback on #28290:

- A tiny positive price formats as a threshold, but the estimate field
  sliced the first character assuming a leading $\, which dropped the
  less-than marker and rendered $$0.0001. formatPricePerMillionTokens
  now returns the display value without a currency symbol plus a
  belowThreshold flag, so the field renders the marker ahead of the
  dollar addon.
- In edit mode the model identifier is a plain input, so each keystroke
  keyed a new live price query and fired a request. The lookup now runs
  on a 500ms debounced identifier, and a pending debounce reads as
  loading so stale prices are not shown for identifiers the user has
  typed past.
@DanielleMaywood
DanielleMaywood force-pushed the fix/model-pricing-post-merge-feedback branch from f65a309 to 78452ff Compare August 19, 2026 15:34
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

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

Reviewed commit: 78452ff16d

ℹ️ 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".

@ssncferreira ssncferreira left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM :shipit: thank you for the follow-up 🏆

@DanielleMaywood
DanielleMaywood merged commit 29c129e into main Aug 19, 2026
26 checks passed
@DanielleMaywood
DanielleMaywood deleted the fix/model-pricing-post-merge-feedback branch August 19, 2026 19:10
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants