Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
215 lines
19 KiB
Markdown
215 lines
19 KiB
Markdown
# PRD -- MSP i18n: Contracts Sub-batch
|
|
|
|
- Slug: `2026-04-09-msp-i18n-contracts`
|
|
- Date: `2026-04-09`
|
|
- Status: Draft
|
|
|
|
## Summary
|
|
|
|
Extract all hardcoded English strings from the 38 contract management components, create the `msp/contracts` namespace, wire `useTranslation()`, and generate translations for 7 languages plus 2 pseudo-locales. This is the largest sub-batch in the MSP i18n effort, covering contract detail views, list/tab views, creation wizards, template wizards, pricing schedules, contract lines, service pickers, and quick-start guides.
|
|
|
|
## Problem
|
|
|
|
The entire contract management UI -- detail pages, create/edit wizards, template authoring, contract line management, pricing schedules, client assignment editing, invoices, documents, and quick-start guides -- renders English-only text regardless of the user's locale preference. This affects ~14,000 LOC across 38 component files spanning three directory levels. Users who operate in non-English locales see a jarring mix of translated and untranslated UI when navigating from already-translated areas of the MSP portal into contract management.
|
|
|
|
## Goals
|
|
|
|
1. Create `server/public/locales/en/msp/contracts.json` with all extracted keys.
|
|
2. Wire all 38 component files with `useTranslation('msp/contracts')`.
|
|
3. Replace hardcoded `new Intl.NumberFormat('en-US', ...)` calls with `useFormatters()` where applicable.
|
|
4. Generate translations for 7 languages (de, en, es, fr, it, nl, pl) plus 2 pseudo-locales (xx, yy) -- 9 locale files total.
|
|
5. Register `msp/contracts` in `ROUTE_NAMESPACES` for `/msp/billing`.
|
|
6. Pass `validate-translations.cjs` with 0 errors across all 9 locales.
|
|
7. Zero regressions with the `msp-i18n-enabled` feature flag OFF.
|
|
|
|
## Non-goals
|
|
|
|
- Translating server-side contract actions or API responses.
|
|
- Translating `@alga-psa/types` interface constants (e.g., status enums in shared types).
|
|
- Refactoring component architecture or splitting large components.
|
|
- Translating currency option labels from `@alga-psa/core` (shared constant, separate effort).
|
|
|
|
## In scope: shared enum label migration (carried over from contract-lines batch)
|
|
|
|
The contract-lines batch (2026-04-09-msp-i18n-contract-lines, shipped 2026-04-14) adopted a localized option-hook pattern for shared billing enums and migrated every in-scope call site. See [`.ai/translation/enum-labels-pattern.md`](../../../../.ai/translation/enum-labels-pattern.md) for the full recipe and [the contract-lines SCRATCHPAD follow-up note](../2026-04-09-msp-i18n-contract-lines/SCRATCHPAD.md#follow-up-enum-labels-from-alga-psabillingconstantsbilling) for the decision trail.
|
|
|
|
**The contract-lines batch left the deprecated `BILLING_FREQUENCY_*`, `CONTRACT_LINE_TYPE_*`, and `PLAN_TYPE_*` exports in `packages/billing/src/constants/billing.ts` in place as `@deprecated` backwards-compat shims specifically so this batch can clean them up.** Every import of those constants in this batch's file list must be migrated to the published hooks from `@alga-psa/billing/hooks/useBillingEnumOptions`:
|
|
|
|
| Hook | Replaces |
|
|
|---|---|
|
|
| `useBillingFrequencyOptions()` | `BILLING_FREQUENCY_OPTIONS` in `<CustomSelect options={...}>` |
|
|
| `useFormatBillingFrequency()` | `BILLING_FREQUENCY_DISPLAY[value]` in table `render` callbacks and inline label lookups |
|
|
| `useContractLineTypeOptions()` | `PLAN_TYPE_OPTIONS` / `CONTRACT_LINE_TYPE_OPTIONS` in `<CustomSelect>` and filter dropdowns |
|
|
| `useFormatContractLineType()` | `PLAN_TYPE_DISPLAY[value]` / `CONTRACT_LINE_TYPE_DISPLAY[value]` in table renderers |
|
|
|
|
**Call sites this batch owns** (enumerated during the contract-lines audit on 2026-04-14):
|
|
|
|
| File | Line(s) | Current import | Target hook |
|
|
|---|---|---|---|
|
|
| `ContractForm.tsx` | 142 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `ContractDialog.tsx` | 588 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `ContractDialog.tsx` | 773 | `CONTRACT_LINE_TYPE_DISPLAY` (option-builder) | `useContractLineTypeOptions` |
|
|
| `ContractDetail.tsx` | 1510 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `ContractTemplateDetail.tsx` | 709, 790 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `wizard-steps/ContractBasicsStep.tsx` | 260 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `wizard-steps/ReviewContractStep.tsx` | 22 (import), 148 | `BILLING_FREQUENCY_OPTIONS` (lookup by value) | `useBillingFrequencyOptions` + `.find(opt => opt.value === ...)` |
|
|
| `wizard-steps/ReviewContractStep.tsx` | 303, 387, 429 | `BILLING_FREQUENCY_DISPLAY[...]` (override display in three review blocks) | `useFormatBillingFrequency` |
|
|
| `template-wizard/steps/TemplateContractBasicsStep.tsx` | 81 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `AddContractLinesDialog.tsx` | 392 | `CONTRACT_LINE_TYPE_DISPLAY` (option-builder) | `useContractLineTypeOptions` |
|
|
| `CreateCustomContractLineDialog.tsx` | 854 | `BILLING_FREQUENCY_OPTIONS` | `useBillingFrequencyOptions` |
|
|
| `BillingFrequencyOverrideSelect.tsx` | 24, 27, 72 | `BILLING_FREQUENCY_OPTIONS` + `BILLING_FREQUENCY_DISPLAY` | `useBillingFrequencyOptions` + `useFormatBillingFrequency` |
|
|
|
|
Line numbers are as of 2026-04-14; expect small drift if this batch runs later. Use the audit greps in [`.ai/translation/enum-labels-pattern.md`](../../../../.ai/translation/enum-labels-pattern.md#finding-latent-gaps) to recover exact positions.
|
|
|
|
**`contractsTabs.ts` (Templates / Client Contracts / Drafts):** The `CONTRACT_SUBTAB_LABELS` constant is the same anti-pattern but has a wrinkle — it is used both as display text and as an identifier in `CONTRACT_LABEL_TO_SUBTAB`. Migrate by (a) keeping `CONTRACT_SUBTAB_LABELS` as value-to-value mapping for the identifier lookup, (b) adding enum keys under `msp/contracts.json` `enums.contractSubtab.*` (or `common.tabs.*` in the namespace), and (c) adding a small `useContractSubtabLabels()` helper inside `Contracts.tsx` / `ClientContractsTab.tsx` consumers that maps `ContractSubTab` values to localized labels via `t()`. Do NOT add `useTranslation` to `contractsTabs.ts` itself (it is not a React file).
|
|
|
|
### Cross-package cleanup: `ClientServiceOverlapMatrix` + `getPlanTypeDisplayAsync`
|
|
|
|
One more latent consumer lives **outside this batch's normal file list** but belongs in the same cleanup PR that removes the deprecated constants. The already-shipped `msp-i18n-clients` batch wired `ClientServiceOverlapMatrix.tsx` to `useTranslation('msp/clients')`, but its contract-line-type badge still renders English because the label arrives via a server-action RPC with a hardcoded English map:
|
|
|
|
| File | Line | Issue |
|
|
|---|---|---|
|
|
| `packages/clients/src/lib/billingHelpers.ts` | 366 | `getPlanTypeDisplayAsync` is a `withAuthCheck` server action that returns `{ Fixed: 'Fixed', Hourly: 'Hourly', Usage: 'Usage Based' }`. No tenant/session dependency — it has no business being a server action. |
|
|
| `packages/clients/src/components/clients/ClientServiceOverlapMatrix.tsx` | 41-46 | `useEffect` loads `planTypeDisplay` state from `getPlanTypeDisplayAsync()` and renders it in the type badge. |
|
|
|
|
**Migration:**
|
|
1. Delete `getPlanTypeDisplayAsync` from `billingHelpers.ts` (verify no other consumers: `rg -n 'getPlanTypeDisplayAsync' packages/ server/ ee/`).
|
|
2. In `ClientServiceOverlapMatrix.tsx`: remove the import, the `planTypeDisplay` state, the `useEffect`, and the `setPlanTypeDisplay` call. Import `useFormatContractLineType` from `@alga-psa/billing/hooks/useBillingEnumOptions`, add `const formatContractLineType = useFormatContractLineType();` at the top of the component body, and replace the `planTypeDisplay[value]` lookup with `formatContractLineType(value)`.
|
|
3. Verify `features/billing` is loaded on whichever route renders the overlap matrix. If that route currently only loads `msp/clients`, add `features/billing` to its `ROUTE_NAMESPACES` entry in `packages/core/src/lib/i18n/config.ts`.
|
|
4. Run `node scripts/validate-translations.cjs` and confirm `rg -n 'getPlanTypeDisplayAsync'` returns no matches.
|
|
|
|
No new translation keys are needed — `enums.contractLineType.*` already exists in `features/billing.json` across all 9 locales from the contract-lines batch. This cleanup is ~30 LOC across 2 files and should land in the same PR that removes the deprecated `*_DISPLAY` / `*_OPTIONS` exports from `packages/billing/src/constants/billing.ts` (see acceptance criterion 14).
|
|
|
|
## File Inventory
|
|
|
|
All files are under `packages/billing/src/components/billing-dashboard/contracts/`.
|
|
|
|
### Main contract components (25 files)
|
|
|
|
| # | File | LOC | Est. Strings | Category |
|
|
|---|------|-----|-------------|----------|
|
|
| 1 | `ContractDetail.tsx` | 2,330 | ~300-400 | Full detail view: overview tab, edit form, assignment editing, renewal, PO, invoices, documents, quick actions, confirmation dialogs |
|
|
| 2 | `ContractDialog.tsx` | 1,386 | ~150-200 | Legacy create/edit dialog: form fields, preset picker, rate overrides |
|
|
| 3 | `ContractTemplateDetail.tsx` | 1,318 | ~150-200 | Template detail view: metadata, lines, services, scheduling |
|
|
| 4 | `CreateCustomContractLineDialog.tsx` | 1,024 | ~100-140 | Custom line creation: type picker, service config, bucket overlay |
|
|
| 5 | `ContractLines.tsx` | 1,027 | ~100-140 | Contract lines list: expand/collapse, service configs, inline editing, bucket overlays |
|
|
| 6 | `AddContractLinesDialog.tsx` | 919 | ~80-120 | Add lines dialog: search, filter, preset selection, rate overrides |
|
|
| 7 | `Contracts.tsx` | 862 | ~80-120 | Main list view: sub-tabs (templates/client/drafts), search, row actions, wizard triggers |
|
|
| 8 | `ClientContractsTab.tsx` | 822 | ~80-110 | Client contracts list: columns, status badges, search, actions, terminate dialog |
|
|
| 9 | `ContractWizard.tsx` | 805 | ~40-60 | Client contract wizard shell: step definitions, validation, save/draft, confirmation dialogs |
|
|
| 10 | `ContractOverview.tsx` | 351 | ~40-55 | Contract overview card: stats, line cards, service list, empty states |
|
|
| 11 | `PricingScheduleDialog.tsx` | 302 | ~35-50 | Pricing schedule create/edit dialog |
|
|
| 12 | `PricingSchedules.tsx` | 287 | ~30-45 | Pricing schedules list: columns, actions, empty state |
|
|
| 13 | `TemplatesTab.tsx` | 247 | ~30-40 | Templates list: columns, status badges, search, actions |
|
|
| 14 | `ServiceCatalogPicker.tsx` | 225 | ~20-30 | Service catalog picker: search, filter, selection |
|
|
| 15 | `ContractForm.tsx` | 197 | ~25-35 | Simple contract edit form: name, description, status, frequency, currency |
|
|
| 16 | `QuickStartGuide.tsx` | 195 | ~35-50 | Quick start guide: 3-step walkthrough, best practices |
|
|
| 17 | `BucketOverlayFields.tsx` | 187 | ~15-25 | Bucket overlay config: included units, overage rate, rollover |
|
|
| 18 | `ContractLineEditDialog.tsx` | 172 | ~20-25 | Contract line edit dialog: rate, billing timing |
|
|
| 19 | `ContractHeader.tsx` | 163 | ~20-30 | Contract header bar: status badge, stats row, PO alert |
|
|
| 20 | `ContractDetailSwitcher.tsx` | 156 | ~8-12 | Router switcher: loading, error, contract type detection |
|
|
| 21 | `ContractLineRateDialog.tsx` | 99 | ~8-12 | Rate dialog (contract lines) |
|
|
| 22 | `ContractPlanRateDialog.tsx` | 95 | ~8-12 | Rate dialog (plans) |
|
|
| 23 | `BillingFrequencyOverrideSelect.tsx` | 78 | ~8-12 | Billing frequency override select |
|
|
| 24 | `ServicePicker.tsx` | 56 | ~5-8 | Service search/select wrapper |
|
|
| 25 | `contractsTabs.ts` | 27 | 0 | Pure constants -- translate at point of use |
|
|
|
|
### Wizard steps (6 files)
|
|
|
|
| # | File | LOC | Est. Strings | Category |
|
|
|---|------|-----|-------------|----------|
|
|
| 26 | `wizard-steps/ContractBasicsStep.tsx` | 741 | ~80-120 | Client/template picker, dates, renewal, PO, billing config |
|
|
| 27 | `wizard-steps/UsageBasedServicesStep.tsx` | 466 | ~50-70 | Usage service picker, unit rate, bucket overlay |
|
|
| 28 | `wizard-steps/HourlyServicesStep.tsx` | 322 | ~35-50 | Hourly service picker, rate, minimum/rounding, bucket overlay |
|
|
| 29 | `wizard-steps/FixedFeeServicesStep.tsx` | 307 | ~30-45 | Fixed fee service picker, base rate, proration |
|
|
| 30 | `wizard-steps/ReviewContractStep.tsx` | 466 | ~60-80 | Full contract review: basics, services by type, PO, summary |
|
|
| 31 | `wizard-steps/ProductsStep.tsx` | 242 | ~25-35 | Product service picker, quantity, rate |
|
|
|
|
### Template wizard (8 files)
|
|
|
|
| # | File | LOC | Est. Strings | Category |
|
|
|---|------|-----|-------------|----------|
|
|
| 32 | `template-wizard/TemplateWizard.tsx` | 383 | ~30-40 | Template wizard shell: steps, validation, save |
|
|
| 33 | `template-wizard/steps/TemplateFixedFeeServicesStep.tsx` | 303 | ~30-40 | Template fixed fee services |
|
|
| 34 | `template-wizard/steps/TemplateHourlyServicesStep.tsx` | 252 | ~25-35 | Template hourly services |
|
|
| 35 | `template-wizard/steps/TemplateReviewContractStep.tsx` | 235 | ~30-40 | Template review step |
|
|
| 36 | `template-wizard/steps/TemplateUsageBasedServicesStep.tsx` | 229 | ~25-35 | Template usage-based services |
|
|
| 37 | `template-wizard/TemplateServicePreviewSection.tsx` | 170 | ~15-20 | Service preview with remove confirmation |
|
|
| 38 | `template-wizard/steps/TemplateProductsStep.tsx` | 168 | ~15-20 | Template products step |
|
|
| 39 | `template-wizard/steps/TemplateContractBasicsStep.tsx` | 97 | ~12-18 | Template basics: name, notes, billing frequency |
|
|
|
|
| | **Total** | **~14,100** | **~1,400-2,200** | |
|
|
|
|
> String estimates use ~0.10-0.15 strings/LOC. Previous batches showed ~0.15 strings/LOC overestimates by 1.5-2x. The realistic target is ~800-1,200 unique keys.
|
|
|
|
## Namespace Structure
|
|
|
|
```
|
|
msp/contracts.json
|
|
common.* -- Shared labels reused across many components (Cancel, Save, Delete, Edit, Back, Saving..., etc.)
|
|
status.* -- Contract status labels (Active, Draft, Terminated, Expired) and assignment status labels
|
|
renewal.* -- Renewal mode labels, notice period, decision due, tenant defaults
|
|
billing.* -- Billing frequency labels, timing (arrears/advance), cadence owner
|
|
po.* -- PO required, PO number, PO amount labels
|
|
contractDetail.* -- ContractDetail.tsx: tabs, cards, edit form, quick actions, confirmation dialogs
|
|
contractHeader.* -- ContractHeader.tsx: stats row, PO alert
|
|
contractOverview.* -- ContractOverview.tsx: stats, line cards, empty states
|
|
contractDialog.* -- ContractDialog.tsx: form fields, preset picker
|
|
contractForm.* -- ContractForm.tsx: simple edit form
|
|
contractLines.* -- ContractLines.tsx: line list, expand/collapse, inline editing, bucket configs
|
|
contractLineEdit.* -- ContractLineEditDialog.tsx: rate, timing
|
|
contractLineRate.* -- ContractLineRateDialog.tsx, ContractPlanRateDialog.tsx
|
|
addLines.* -- AddContractLinesDialog.tsx: search, filter, preset selection
|
|
createCustomLine.* -- CreateCustomContractLineDialog.tsx: type picker, service config
|
|
pricingSchedules.* -- PricingSchedules.tsx + PricingScheduleDialog.tsx
|
|
contractsList.* -- Contracts.tsx: tabs, search, row actions
|
|
clientContracts.* -- ClientContractsTab.tsx: columns, search, terminate
|
|
templatesTab.* -- TemplatesTab.tsx: columns, search, actions
|
|
detailSwitcher.* -- ContractDetailSwitcher.tsx: loading, error states
|
|
templateDetail.* -- ContractTemplateDetail.tsx: metadata, lines, services
|
|
quickStart.* -- QuickStartGuide.tsx: steps, best practices
|
|
servicePicker.* -- ServicePicker.tsx, ServiceCatalogPicker.tsx
|
|
bucketOverlay.* -- BucketOverlayFields.tsx: included units, overage, rollover
|
|
frequencyOverride.* -- BillingFrequencyOverrideSelect.tsx
|
|
wizard.* -- ContractWizard.tsx: step labels, validation, save/draft dialogs
|
|
wizardBasics.* -- ContractBasicsStep.tsx: client picker, dates, renewal, PO
|
|
wizardFixed.* -- FixedFeeServicesStep.tsx: service picker, rate, proration
|
|
wizardProducts.* -- ProductsStep.tsx: product picker, quantity
|
|
wizardHourly.* -- HourlyServicesStep.tsx: hourly rate, minimum, rounding
|
|
wizardUsage.* -- UsageBasedServicesStep.tsx: unit rate, measure
|
|
wizardReview.* -- ReviewContractStep.tsx: all sections summary
|
|
templateWizard.* -- TemplateWizard.tsx: step labels, validation
|
|
templateBasics.* -- TemplateContractBasicsStep.tsx: name, notes, frequency
|
|
templateFixed.* -- TemplateFixedFeeServicesStep.tsx
|
|
templateProducts.* -- TemplateProductsStep.tsx
|
|
templateHourly.* -- TemplateHourlyServicesStep.tsx
|
|
templateUsage.* -- TemplateUsageBasedServicesStep.tsx
|
|
templateReview.* -- TemplateReviewContractStep.tsx
|
|
templatePreview.* -- TemplateServicePreviewSection.tsx
|
|
```
|
|
|
|
## ROUTE_NAMESPACES Changes
|
|
|
|
The `/msp/billing` route entry should add `msp/contracts`:
|
|
|
|
```typescript
|
|
'/msp/billing': ['common', 'msp/core', 'features/billing', 'msp/reports', 'msp/contracts'],
|
|
```
|
|
|
|
## Acceptance Criteria
|
|
|
|
1. `server/public/locales/en/msp/contracts.json` exists and contains all extracted keys.
|
|
2. All 37 UI component files (excluding `contractsTabs.ts`) import `useTranslation` from `@alga-psa/ui/lib/i18n/client` and use `t('key', { defaultValue: '...' })` for all user-visible strings.
|
|
3. Currency and date formatting uses `useFormatters()` where applicable, replacing hardcoded `new Intl.NumberFormat('en-US', ...)` calls in ContractDetail.tsx, ContractOverview.tsx, ReviewContractStep.tsx, and related components.
|
|
4. All 9 locale files exist: `{de,en,es,fr,it,nl,pl,xx,yy}/msp/contracts.json`.
|
|
5. `validate-translations.cjs` passes with 0 errors and 0 warnings for `msp/contracts` across all 9 locales.
|
|
6. Italian translations use correct accents (verified by accent audit).
|
|
7. Pseudo-locale `xx` shows `11111` patterns for visual QA.
|
|
8. `ROUTE_NAMESPACES` in `packages/core/src/lib/i18n/config.ts` includes `msp/contracts` in the `/msp/billing` entry.
|
|
9. `npm run build` succeeds with no TypeScript errors.
|
|
10. No hardcoded English strings remain in the 37 wired component files (verified by grep for bare string literals in JSX).
|
|
11. Interpolation variables (e.g., `{{count}}`, `{{name}}`) are used for dynamic values in pluralized or parameterized strings rather than template literals.
|
|
12. `contractsTabs.ts` string constants are translated at their consumption point via a `useContractSubtabLabels()` helper in `Contracts.tsx` / `ClientContractsTab.tsx`, not in the constant definition file. The `CONTRACT_LABEL_TO_SUBTAB` value-to-value mapping stays intact for identifier lookup.
|
|
13. All call sites in the "Shared enum label migration" table above import hooks from `@alga-psa/billing/hooks/useBillingEnumOptions` and use the result in `<CustomSelect>` / `render` callbacks. Zero imports of `BILLING_FREQUENCY_OPTIONS`, `BILLING_FREQUENCY_DISPLAY`, `PLAN_TYPE_OPTIONS`, `PLAN_TYPE_DISPLAY`, or `CONTRACT_LINE_TYPE_DISPLAY` remain in the files owned by this batch (verify with `rg -n '_DISPLAY|_OPTIONS' packages/billing/src/components/billing-dashboard/contracts/`).
|
|
14. After this batch merges, the `@deprecated` aliases in `packages/billing/src/constants/billing.ts` should be removed (leaving only `BILLING_FREQUENCY_VALUES`, `CONTRACT_LINE_TYPE_VALUES`, `*_LABEL_DEFAULTS`, and the TypeScript types). If any consumer outside the billing package still imports them, flag it and migrate in a follow-up — do not leave stale deprecated exports indefinitely.
|