name: "m:klair-shell-migrate-temp" description: "Plan migration of a legacy screen to the new-ui shell system" argument-hint: "[screen-path-or-name]"
Shell Migration Planner
You are a shell migration expert with deep knowledge of the new-ui shell system, filter primitives, dual-mode detection, and data-feeding lifecycle.
User request: $ARGUMENTS
Routing: read the user instruction first
Custom objective: If the user's instruction is something other than a full migration plan (e.g. "which primitive should I use for this filter?", "generate just the route config", "explain the dual-mode pattern", "what's the difference between multiSelect and viewMode?", "how do I feed data bounds?"), use the filter primitive reference, mapping tables, and shell architecture knowledge below to answer directly. You are not bound to the 6-phase migration workflow.
Default workflow (when the user provides a screen name/path for migration, or when no specific instruction is provided): Follow Phases 1–6 below in sequence.
Source of Truth
Read the migration guide first:
features/shell/new-ui-migration/FEATURE.md-- full architecture, filter reference, data-feeding lifecycle, design tokens
Then read the shell implementation files for current filter types:
klair-client/src/shells/DesktopShell/FiltersContext.tsx-- all filter types, interfaces, settersklair-client/src/shells/DesktopShell/ConfigSidebar.tsx-- desktop filter renderingklair-client/src/shells/MobileShell/FilterSheet.tsx-- mobile filter rendering
Canonical Reference
The renewals screen is the gold standard. If you need to understand how a shell-integrated screen works end-to-end, read:
- Route config:
klair-client/src/shells/DesktopShell/routes.tsx(search forrenewals) - Screen:
klair-client/src/screens/RenewalsShell/RenewalsShell.tsx
Migration Philosophy: Dual-Mode
Screens must work at both routes simultaneously. After migration:
- The screen renders at both the shell route (
/new-ui/...) and the legacy route (/...) - The same screen component serves both routes — no duplication
- Use
useFiltersOptional()to detect which mode:- Returns the filters context when inside the shell (
/new-ui/...) - Returns
nullwhen at the legacy route (noFiltersProviderwrapping it)
- Returns the filters context when inside the shell (
- Shell mode (
useFiltersOptional()returns context): hide the inline filter component, read all filter values fromappliedValues.*via the shell sidebar - Legacy mode (
useFiltersOptional()returnsnull): show the inline filter component as-is, read filter values from URL params / local state as before - Keep the legacy filter component file (e.g.,
FilterBar.tsx) — it still renders on legacy routes - Keep the legacy route entry in
App.tsx— do NOT delete it - Shared filtering/sorting logic uses a unified filters object via
useMemothat normalizes from either source
The key pattern:
const shellFilters = useFiltersOptional();
const isShellMode = !!shellFilters;
Then conditionally render the inline FilterBar only when !isShellMode.
Migration Process
Phase 1: Catalogue Legacy Filters
Read the target screen component thoroughly. For each piece of filter UI found:
- Identify every filter control -- dropdowns, date pickers, range sliders, toggles, search inputs, tab selectors, checkboxes, radio groups
- Document each filter in a table:
| Filter | Current UI | Data Source | Behaviour |
|---|---|---|---|
| (name) | (dropdown / toggle / etc.) | (static / API / derived from data) | (multi-select / single / range / etc.) |
- Note where each filter lives -- is it in a sticky header? Inline? A sidebar? URL params?
Phase 2: Map to Shell Primitives
For each catalogued filter, map to the closest shell primitive:
Four generic primitives (prefer these):
| Shell Type | Use For |
|---|---|
dateRange |
Any date range picker, time period selector, predefined period chips |
range |
Any numeric min/max slider or range input |
viewMode |
Any tab/toggle that changes the data perspective or display mode (2+ states) |
multiSelect |
Any dropdown, checkbox group, or list selection -- configure N instances by key |
Legacy types have generic equivalents (don't use legacy for new screens):
| Legacy Type | Replace With |
|---|---|
period |
multiSelect with singleSelect: true for year + quarter |
periodMonth |
multiSelect with singleSelect: true for year + month |
timeRange |
dateRange with dateRangeShowQuickPresets: true |
entityType |
multiSelect |
businessUnit |
multiSelect with { key: 'businessUnits' } |
classes |
multiSelect with { key: 'classes' } |
businessUnitSingle |
multiSelect with singleSelect: true |
classSingle |
multiSelect with singleSelect: true |
multiSelect configuration properties:
| Property | Type | Description |
|---|---|---|
key |
string | Unique ID, used in multiSelectValues[key] |
label |
string | Sidebar display label |
singleSelect |
boolean | Radio behavior (one value only) |
hideSearch |
boolean | Hide search for short option lists |
hideAllToggle |
boolean | Hide "All" checkbox |
showForViewMode |
string or string[] | Conditional visibility based on viewMode |
tooltip |
string | Help text on hover |
singleSelect guidance:
- Default to plain
multiSelect(multi-select). Users benefit from filtering by multiple values (e.g., "High + Medium" impact, "Triage + Assigned" status). - Only use
singleSelect: truewhen explicitly instructed, or when selecting multiple values is genuinely nonsensical (e.g., a time period like "Q1 2025", a view mode toggle, or a mutually exclusive grouping dimension).
Produce a mapping table:
| Legacy Filter | Shell Primitive | Config Key | Notes |
|---|---|---|---|
| (name) | (dateRange / range / viewMode / multiSelect) | (config detail) | (any nuance) |
Phase 3: Gap Analysis
For each filter that does NOT cleanly map to an existing primitive:
- Can it be expressed as multiSelect? Most categorical filters can. multiSelect supports singleSelect mode, conditional visibility, icon, count/percentage metadata.
- Can it be expressed as viewMode? If it switches between display perspectives.
- Can it be expressed as dateRange with presets? If it selects time windows.
- Is it truly unique? Only flag as a gap if NONE of the four primitives work.
Conservative enhancement principle:
- Prefer mapping to existing primitives over proposing new filter types
- If a small, generic extension to an existing primitive solves the problem (e.g., adding a
maxOptionsprop to multiSelect), suggest it as a targeted enhancement - If the extension would be too opinionated or only useful for this one screen, DON'T propose it -- recommend
CustomFilterComponentas a fallback instead - Thread the needle: enhance generically when the enhancement serves multiple future screens; use custom fallback when it's a one-off
Output either:
- "All filters map to existing primitives" (ideal case)
- A list of targeted, generic enhancements needed
- Filters that should use
CustomFilterComponent(genuinely complex cases only)
Phase 4: Generate Route Config
Write the complete route config entry for routes.tsx:
const MyScreen = lazy(() => import('@/features/my-feature-v2/screens/MyScreen'));
// In newUIRoutes array:
{
path: 'my-screen',
title: 'My Screen',
element: <LazyScreen component={MyScreen} />,
filters: ['dateRange', 'viewMode', 'multiSelect'], // only types actually needed
filterDefaults: {
// dateRange config...
// viewMode config...
// multiSelect instances...
},
permissionPath: 'legacy-route-path', // maps to existing permission
}
Phase 5: Screen Refactoring Plan
Describe the changes needed to the screen component:
- Dual-mode detection -- add
useFiltersOptional()import andconst isShellMode = !!shellFiltersat the top of the component - Conditionally hide inline filters -- wrap the existing filter component (e.g.,
<FilterBar>) in{!isShellMode && <FilterBar ... />}. Do NOT delete FilterBar.tsx — it still serves the legacy route. - Unified filter object -- create a
useMemothat normalizes filters from either source into a single object:- Shell mode: read from
shellFilters.appliedValues.multiSelectValues[key],shellFilters.appliedValues.search, etc. - Legacy mode: read from existing URL params / local state (the current code)
- Shell mode: read from
- Feed data (shell mode only) -- when
isShellMode, callsetMultiSelectOptions(),setRangeDataBounds(),setDateRangeDataBounds()after data loads. Guard these calls behindif (shellFilters). - boundsOnly pattern -- on data refresh, call setters with
boundsOnly=trueto preserve user selections - Shell contract (shell mode only) -- when
isShellMode:- Hide internal page title (shell provides it)
- Outer wrapper should be
h-full overflow-y-auto - Replace hardcoded colors with
var(--klair-*)tokens - Use
appliedValuesnotvaluesfor API calls
- Preserve legacy route -- do NOT remove the legacy route entry from
App.tsx. Do NOT delete the inline filter component file.
Phase 6: Desktop + Mobile Verification
Verify both shells will work correctly:
Desktop (ConfigSidebar):
- All filter types declared in
filtersarray render automatically - multiSelect instances render in order declared in
multiSelectFilters showForViewModeconditional visibility works- Apply button workflow (working values -> applied values)
Mobile (FilterSheet):
- FilterSheet mirrors ConfigSidebar automatically for all generic primitives
singleSelect,hideSearch,showForViewMode,iconall supported- Bottom sheet interaction pattern (swipe to dismiss)
- No custom mobile work needed if using only generic primitives
If CustomFilterComponent is used:
- Must handle
isMobileprop for responsive layout - Must work within FilterSheet bottom sheet on mobile
- Document any mobile-specific considerations
Output Format
Present the migration plan as a single document with these sections:
- Filter Catalogue -- table of all legacy filters found
- Primitive Mapping -- how each maps to shell types
- Gap Analysis -- any enhancements needed (or "none")
- Route Config -- complete
routes.tsxentry (copy-paste ready) - Screen Changes -- bulleted list of removals, additions, refactoring steps
- Mobile Parity -- confirmation or specific mobile concerns
- Data Feeding -- which setters to call, when, with what data
Keep the plan actionable and specific. Reference actual function names, file paths, and code patterns from the target screen.