Background
To display the full area tree as secondary info beneath work item and household item titles across many views, we need a reusable shared component. The component must support a default (full path) variant and a compact (left-truncated with middle ellipsis on root) variant for narrow contexts, with the full path always available via tooltip. Additionally, the existing SearchPicker must gain an optional secondary-line slot so pickers can render the breadcrumb under the primary label without duplicating layout logic.
Per the shared-component policy, UX designer produces a visual spec before implementation. Translator adds non-English strings for the new areas.noArea key.
Scope
client/src/components/AreaBreadcrumb/* (new directory) — component, CSS module, index export
client/src/components/SearchPicker/SearchPicker.tsx — add optional renderSecondary?: (item) => ReactNode prop
client/src/i18n/en/areas.json — add (or reuse namespace for) areas.noArea → \"No area\"
client/src/i18n/glossary.json — add domain term if applicable (translator-owned)
Acceptance Criteria
Scenario 1: Happy path — full path rendering (default variant)
- Given an
AreaSummary with ancestors: [House, Basement] and name Bathroom
- When
<AreaBreadcrumb area={summary} /> is rendered (default variant)
- Then the rendered text reads
House › Basement › Bathroom using the › separator (U+203A, single spaces)
- And text uses
--color-text-muted and .textSmall style tokens (no hardcoded colors)
Scenario 2: Null-area fallback
- Given
area is null
- When
<AreaBreadcrumb area={null} /> is rendered
- Then the component renders the translated
areas.noArea placeholder (\"No area\" in English)
- And the component does not crash or render an empty separator
Scenario 3: Compact variant — truncation and tooltip
- Given an
AreaSummary with ancestors [Property, House, Floor 1, Kitchen Area] and name Pantry
- When
<AreaBreadcrumb area={summary} variant=\"compact\" /> is rendered in a narrow container
- Then the visible text left-truncates with a middle ellipsis on the root segment, e.g.,
… › Kitchen Area › Pantry
- And hovering/tapping the breadcrumb surfaces a tooltip containing the full path
Property › House › Floor 1 › Kitchen Area › Pantry
Scenario 4: SearchPicker renderSecondary slot
- Given a
SearchPicker instance with renderSecondary={(item) => <AreaBreadcrumb area={item.area} variant=\"compact\" />}
- When the picker renders an item list
- Then each item shows the primary label on line 1 and the breadcrumb on line 2, muted and single-line with overflow ellipsis
- And when
renderSecondary is omitted, the picker renders as before (single-line label) with no regression
Dependencies
None. This item unblocks issues 3, 4, and 5.
Out of scope
- Wiring the component into any page, list, or picker (covered by issues 3, 4, 5)
- Backend changes to
AreaSummary (covered by issue 1)
- German translations (handled by translator agent in parallel, per i18n convention)
- Changes to
SearchPicker behaviors other than the new optional renderSecondary slot
Background
To display the full area tree as secondary info beneath work item and household item titles across many views, we need a reusable shared component. The component must support a default (full path) variant and a compact (left-truncated with middle ellipsis on root) variant for narrow contexts, with the full path always available via tooltip. Additionally, the existing
SearchPickermust gain an optional secondary-line slot so pickers can render the breadcrumb under the primary label without duplicating layout logic.Per the shared-component policy, UX designer produces a visual spec before implementation. Translator adds non-English strings for the new
areas.noAreakey.Scope
client/src/components/AreaBreadcrumb/*(new directory) — component, CSS module, index exportclient/src/components/SearchPicker/SearchPicker.tsx— add optionalrenderSecondary?: (item) => ReactNodepropclient/src/i18n/en/areas.json— add (or reuse namespace for)areas.noArea→\"No area\"client/src/i18n/glossary.json— add domain term if applicable (translator-owned)Acceptance Criteria
Scenario 1: Happy path — full path rendering (default variant)
AreaSummarywithancestors: [House, Basement]and nameBathroom<AreaBreadcrumb area={summary} />is rendered (default variant)House › Basement › Bathroomusing the›separator (U+203A, single spaces)--color-text-mutedand.textSmallstyle tokens (no hardcoded colors)Scenario 2: Null-area fallback
areaisnull<AreaBreadcrumb area={null} />is renderedareas.noAreaplaceholder (\"No area\"in English)Scenario 3: Compact variant — truncation and tooltip
AreaSummarywith ancestors[Property, House, Floor 1, Kitchen Area]and namePantry<AreaBreadcrumb area={summary} variant=\"compact\" />is rendered in a narrow container… › Kitchen Area › PantryProperty › House › Floor 1 › Kitchen Area › PantryScenario 4: SearchPicker renderSecondary slot
SearchPickerinstance withrenderSecondary={(item) => <AreaBreadcrumb area={item.area} variant=\"compact\" />}renderSecondaryis omitted, the picker renders as before (single-line label) with no regressionDependencies
None. This item unblocks issues 3, 4, and 5.
Out of scope
AreaSummary(covered by issue 1)SearchPickerbehaviors other than the new optionalrenderSecondaryslot