# Scratchpad: CustomTabs `id` Refactor ## Key Decisions - **Breaking change, no backward compat** — `id` is required, not optional. TypeScript enforces completeness. - **`id` format = kebab-case** — matches existing URL slug conventions. The id IS the slug. - **Single PR** — all 37 files updated together. TypeScript compiler is the safety net (missing `id` = build error). ## Key File Paths ### Core - `packages/ui/src/components/CustomTabs.tsx` — component definition - `packages/ui/src/components/index.ts` — re-exports TabContent, TabGroup, CustomTabs ### Consumers with bidirectional slug maps (Pattern A — delete maps) - `server/src/components/settings/security/SecuritySettingsPage.tsx` - `server/src/components/settings/import-export/ImportExportSettings.tsx` - `server/src/components/settings/general/TicketingSettings.tsx` - `server/src/components/settings/general/NotificationsTab.tsx` - `server/src/components/settings/general/InteractionSettings.tsx` - `server/src/app/msp/settings/sla/page.tsx` - `server/src/app/msp/settings/notifications/page.tsx` - `packages/billing/src/components/billing-dashboard/InvoicingHub.tsx` - `packages/billing/src/components/billing-dashboard/ContractsHub.tsx` - `packages/billing/src/components/credits/CreditsTabs.tsx` - `packages/billing/src/components/settings/billing/BillingSettings.tsx` - `packages/surveys/src/components/SurveySettings.tsx` - `packages/scheduling/src/components/settings/time-entry/TimeEntrySettings.tsx` - `packages/projects/src/components/settings/ProjectSettings.tsx` - `packages/assets/src/components/AssetDetailTabs.tsx` - `packages/client-portal/src/components/settings/ClientPortalSettingsPage.tsx` - `ee/packages/workflows/src/components/user-activities/NotificationsSection.tsx` ### Consumers with findTabLabel helper (Pattern B — delete helper) - `packages/clients/src/components/contacts/ContactDetails.tsx` - `packages/clients/src/components/clients/ClientDetails.tsx` ### Consumers with translated labels (Pattern C — keep t(), add id) - `packages/tickets/src/components/ticket/TicketConversation.tsx` - `packages/client-portal/src/components/notifications/ClientNotificationsList.tsx` ### Consumers with static defaults (Pattern D — just add id) - `server/src/components/settings/extensions/ExtensionManagement.tsx` - `packages/billing/src/components/billing-dashboard/DiscrepancyDetail.tsx` - `packages/billing/src/components/billing-dashboard/CreditReconciliation.tsx` - `packages/billing/src/components/billing-dashboard/CreditManagement.tsx` - `packages/client-portal/src/components/billing/BillingOverview.tsx` - `packages/assets/src/components/AssetDetails.tsx` - `packages/clients/src/components/clients/BillingConfiguration.tsx` ### Dynamic tabs from data (Pattern E — use source data id) - `packages/integrations/src/components/settings/integrations/IntegrationsSettingsPage.tsx` - `packages/integrations/src/components/accounting-mappings/AccountingMappingManager.tsx` ### Complex state management (Pattern F — replace label-based state) - `server/src/components/settings/SettingsPage.tsx` ### Enterprise Edition - `ee/server/src/components/workflow-designer/WorkflowDesigner.tsx` - `ee/packages/workflows/src/components/user-activities/NotificationsSection.tsx` ### UserProfile helper - `server/src/components/settings/profile/UserProfile.tsx` — uses `resolveUserProfileTab()` helper ## Gotchas ### TicketConversation has external API surface The `TicketConversationProps` interface exposes `activeTab: string` and `onTabChange: (tab: string) => void`. After refactor, callers must pass tab IDs not labels. Need to grep for `