Skip to content

[quantity-formatting] Optional unitsProvider, startup race fix, missing peers, exported prop types - #1828

Open
hl662 wants to merge 11 commits into
nam/qf-2-format-fixesfrom
nam/qf-3-tech-debt
Open

hl662 wants to merge 11 commits into
nam/qf-2-format-fixesfrom
nam/qf-3-tech-debt

Conversation

@hl662

@hl662 hl662 commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

The package had cleanup items from the April refactor backlog that affect consumers:

  • Concurrent startup() calls could race.
  • Public types referenced packages that weren't declared as peer dependencies.
  • Every consumer had to pass a unitsProvider and track changes to it themselves.
  • Several errors were silently swallowed.

This PR fixes those, makes unitsProvider optional (it defaults to IModelApp.quantityFormatter.unitsProvider), and turns on unused-code checks. Minor release.

Stacked on #1827.

What

Asset Why it exists
QuantityFormatting.startup / terminate Concurrent startup() calls registered the namespace twice. Calls made while startup runs now share it, a failed startup can be retried, and reading localization before startup throws a clear error.
unitsProvider optional on QuantityFormatPanel / FormatPanel / FormatSample Most apps pass IModelApp.quantityFormatter.unitsProvider and re-subscribe to onUnitsProviderChanged by hand. The components now do that by default; an explicit provider still wins. Supported core stays at ^5.3.
internal/useUnitsProvider.ts (new) The single place that resolves the default provider and follows app-level changes, via useSyncExternalStore.
Peer dependencies @itwin/core-common, @itwin/ecschema-metadata (^5.3.0) Public types already import from both, so consumers without them failed type-checking.
QuantityFormatPanel Reloads the persistence unit only when the unit name changes rather than on every edit, logs lookup failures, and documents when onFormatChange fires.
FormatSample Logs formatter creation failures instead of silently showing nothing.
FormatSetSelector Fallback keys for unnamed sets used the filtered index, so selection jumped to another set while searching. Now uses the original index.
FormatSetPanel Starts from the provided format set instead of empty values that are then filled in by an effect.
tsconfig.json noUnusedLocals / noUnusedParameters Dead handlers and imports had piled up unnoticed. The flags keep them out.
test-viewer quantity-formatting components Drop their two copies of the units-provider subscription and rely on the new default.
Changesets Minor entries for the package contract changes and the optional unitsProvider.

Tests

New and updated test coverage
Test What it covers
QuantityFormatting.test.ts — concurrent startup The namespace is registered once when startup() is called in parallel.
QuantityFormatting.test.ts — localization before startup Throws a descriptive error instead of an undefined access.
QuantityFormatting.test.ts — retry after failure A failed namespace registration doesn't block later startups.
useUnitsProvider.test.ts — default / follows app / explicit wins / explicit removed Default provider resolution, updates via onUnitsProviderChanged, and no stale default after an explicit provider is removed.
FormatSetSelector.test.tsx — stable keys while filtering Selecting an unnamed set during search keeps it highlighted.
QuantityFormatPanel.test.tsx — persistence unit reload / logging No reload on unrelated edits; lookup failures are logged.
QuantityFormatPanel.test.tsx — prop change resets edits Changing formatDefinition discards pending edits without saving.

Validation

tsc, pnpm lint, pnpm test (213/213), pnpm build, extract-api, check-extractions, the test-viewer type-check, and the learning-snippets quantity-formatting tests all pass.


Nambot 🤖 (powered by claude-opus-5.5)

hl662 added 4 commits October 7, 2026 19:38
…, exported prop types, error logging, stale keys

- QuantityFormatting.startup shares one in-flight promise; localization getter throws a descriptive error before startup
- Add @itwin/core-common and @itwin/ecschema-metadata peers (public types already depend on them)
- Export component prop types and document onFormatChange timing
- Log formatter/persistence-unit failures instead of swallowing them
- FormatSetSelector keys no longer shift while searching; FormatSetPanel initializes from props
- Persistence unit reload keyed on unit name; remove dead code and unused imports; enable noUnusedLocals
- Guard QuantityFormatting startup completions with a generation so terminate() invalidates in-flight work; document first-call-wins localization
- Tests: deferred startup race, FormatSetSelector stable keys while filtering, persistence-unit reload/logging
…elApp.quantityFormatter.unitsProvider

The components follow onUnitsProviderChanged when the prop is omitted, so test-viewer drops its two copies of that wiring.
@hl662
hl662 requested review from a team as code owners October 8, 2026 03:00
@hl662
hl662 added this pull request to stack #1829 October 8, 2026 03:01
Copilot AI balanced review requested due to automatic review settings October 8, 2026 13:55

Copilot AI 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.

🟡 Changes recommended

A startup completing after termination can leave its localization namespace registered indefinitely.

1 open finding
What changed in this PR

Improves quantity-formatting initialization, provider defaults, public types, diagnostics, and cleanup.

Changes:

  • Makes unit providers optional and reactive to application-level changes.
  • Prevents concurrent startup races and exports component prop types.
  • Adds peer dependencies, diagnostics, tests, and unused-code checks.
File Description
.changeset/​@itwin-quantity-formatting-react-package-contract.md Records package contract changes.
.changeset/​@itwin-quantity-formatting-react-optional-units-provider.md Records optional provider support.
apps/​test-viewer/​src/​components/​quantity-formatting/​FormatPropertyContextMenuItem.tsx Uses the default units provider.
apps/​test-viewer/​src/​components/​quantity-formatting/​FormatTabPanel.tsx Removes explicit provider plumbing.
apps/​test-viewer/​src/​components/​quantity-formatting/​QuantityFormatButton.tsx Removes duplicated provider subscription.
packages/​quantity-formatting/​README.md Documents optional providers.
packages/​quantity-formatting/​api/​quantity-formatting-react.api.md Updates extracted API declarations.
packages/​quantity-formatting/​api/​quantity-formatting-react.exports.csv Adds exported prop types.
packages/​quantity-formatting/​package.json Adds missing peer dependencies.
packages/​quantity-formatting/​src/​QuantityFormatting.ts Coordinates startup and localization state.
packages/​quantity-formatting/​src/​components/​quantityformat/​FormatPanel.tsx Supports the default provider and exports props.
packages/​quantity-formatting/​src/​components/​quantityformat/​FormatSample.tsx Supports defaults and logs formatter failures.
packages/​quantity-formatting/​src/​components/​quantityformat/​FormatSelector.tsx Exports selector props.
packages/​quantity-formatting/​src/​components/​quantityformat/​FormatSetPanel.tsx Initializes supplied values and exports props.
packages/​quantity-formatting/​src/​components/​quantityformat/​FormatSetSelector.tsx Stabilizes fallback keys and exports props.
packages/​quantity-formatting/​src/​components/​quantityformat/​QuantityFormatPanel.tsx Resolves providers and optimizes unit loading.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​FractionDash.tsx Removes an unused handler.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​StationBaseFactor.tsx Removes an unnecessary type escape.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​misc/​DecimalPrecision.tsx Removes an unused import.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​misc/​DecimalSeparator.tsx Removes an unused import.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​misc/​FractionPrecision.tsx Removes an unused import.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​misc/​SignOption.tsx Removes an unused import.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​misc/​StationSizeSelector.tsx Removes an unused import.
packages/​quantity-formatting/​src/​components/​quantityformat/​internal/​useUnitsProvider.ts Adds provider resolution and subscription.
packages/​quantity-formatting/​src/​quantity-formatting-react.ts Exports public prop types.
packages/​quantity-formatting/​src/​test/​QuantityFormatting.test.ts Tests startup lifecycle behavior.
packages/​quantity-formatting/​src/​test/​_Setup.test.ts Removes an unused test import.
packages/​quantity-formatting/​src/​test/​quantityformat/​FormatSample.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​FormatSetSelector.test.tsx Tests stable filtered selection.
packages/​quantity-formatting/​src/​test/​quantityformat/​QuantityFormatPanel.test.tsx Tests reset, loading, and logging behavior.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​DecimalSeparator.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​FormatPrecision.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​FractionDash.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​KeepDecimalPoint.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​KeepSingleZero.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​ShowTrailingZeros.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​SignOption.test.tsx Removes unused test imports.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​StationBaseFactor.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​ThousandsSeparator.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​ZeroEmpty.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​internal/​useUnitsProvider.test.ts Tests provider resolution and updates.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​AzimuthPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​BearingPanel.test.tsx Removes unused test imports.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​DecimalPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​FractionalPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​RatioPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​ScientificPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​src/​test/​quantityformat/​panels/​StationPanel.test.tsx Removes an unused import.
packages/​quantity-formatting/​tsconfig.json Enables unused-code checks.

🧠 Review effort: Balanced


Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.

Comment thread packages/quantity-formatting/src/QuantityFormatting.ts Outdated
hl662 added 2 commits October 8, 2026 10:18
…ration that completes after terminate

Keeps the registration when a newer startup reuses the same localization instance.
@grigasp

grigasp commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Component prop types weren't exported

Why should they be exported?

Comment thread packages/quantity-formatting/src/QuantityFormatting.ts Outdated
hl662 added 2 commits October 9, 2026 11:20
…ternalStore for the default units provider

- startup shares one in-flight initialization and allows a retry after failure; drop the generation guard and
  late-registration cleanup
- useUnitsProvider reads the app provider through useSyncExternalStore, so removing an explicit provider never
  renders a stale default
@hl662

hl662 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Why should they be exported?

They were already part of the public signatures (FormatSelector: React.FC, etc.), but consumers couldn’t import them by name. API Extractor listed them as non-exported. So exporting them matches property-grid, which does exports its component props types. I could revert it, but I figured it could help?

@grigasp

grigasp commented Oct 12, 2026

Copy link
Copy Markdown
Member

They were already part of the public signatures (FormatSelector: React.FC, etc.), but consumers couldn’t import them by name

Consumers can import components, which is what's generally used directly. Component props are relatively rarely used directly, so, IMO, there's no point in exporting them. And if consumers do need props, React provides an utility for that - type MyComponentProps = React.ComponentProps<typeof MyComponent>.

API Extractor listed them as non-exported

Not sure what you mean. The extract-api has two outputs:

  • .exports.csv lists all APIs exported through the barrel. This is what consumers get when importing from barrel.
  • .api.md lists all APIs accessible through barrel (directly or indirectly). This is what we consider public API surface.

The APIs listed in the two outputs don't have to be the same.

Exporting them matches property-grid, which does exports its component props types

Property grid is not the best example, we're going to clean it up with the next major release.

Not exporting component props has been a pattern for all new-generation packages, though, including StrataKit packages (example), AppUI (example), Presentation (example).

I figured it could help?

FWIW, I think it does more harm than good. When you import from barrel, you expect to get APIs that you're likely to use. Exporting every publicly accessible API through it pollutes barrel exports in a way that makes it impossible to know what you're supposed to use directly.

hl662 added 2 commits October 13, 2026 09:48
Consumers can use React.ComponentProps<typeof Component>; newer iTwin packages keep props types out of the barrel.
@hl662

hl662 commented Oct 13, 2026

Copy link
Copy Markdown
Contributor Author

FWIW, I think it does more harm than good. When you import from barrel, you expect to get APIs that you're likely to use. Exporting every publicly accessible API through it pollutes barrel exports in a way that makes it impossible to know what you're supposed to use directly.

Gotcha, I've reverted the props exports from the PR now

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.

3 participants