# FN-368 Parts Panel Selector Manifest Source: FN-403 recon on worktree branch `fusion/fn-403` rebased onto `origin/dev`, HEAD `d684e7f` (fix(pl24): pass Ford VIN BOM detail URL through fetchP4Page baseUrl prefix). `apps/web/src/components/schema/parts-panel.tsx` content is **unchanged** from the FN-368 ship commit `0ce68a7` (feat(FN-368): parts panel loading state, inert unavailable rows, escape hatch). Sibling to `.fusion/tasks/FN-400/MANIFEST.md` (FN-367 / VIN-decode half). Conventions: see project memory `frontend-testability-hook-convention` — `apps/web` does **not** use `data-testid`; hook priority is (1) `data-faro-user-action-name`, (2) ARIA role + accessible name, (3) visible text. `` is always mounted via ``. SchemaViewer mounts PartsPanel at two sites: - `apps/web/src/components/schema/schema-viewer.tsx:100` — no-schema fallback branch (`if (!schemaPic)`, e.g. PL24 bomOverviewlist pages). - `apps/web/src/components/schema/schema-viewer.tsx:168` — split-pane right column (default). `grep -n data-testid apps/web/src/components/schema/parts-panel.tsx` → **0 matches**. --- ## Route 1 — primary post-VIN-decode flow ``` Route: /dashboard/vehicles_/$id/categories_/$categoryId (file: apps/web/src/routes/dashboard/vehicles_/$id/categories_/$categoryId.tsx, line: 44) Live example URL: https://sase.tr/dashboard/vehicles//categories/ (slug discovery: go to https://sase.tr/dashboard/search, paste a known-good VIN, the decoded landing page links each category leaf via this route) Container: getByRole('heading', { name: 'Parcalar' }) → closest div.flex.h-full.flex-col.overflow-hidden (file: apps/web/src/components/schema/parts-panel.tsx, line: 95 for loading-state heading inside div at line 94; line 112 for normal-state heading inside div at line 110) Interactive elements: - Parts row, available — selector: [data-faro-user-action-name="select-part"][role="button"], role: button (Turkish accessible name is part.name + optional remark/modelCodes), expected on click: useSchemaStore.setSelectedGroup(group) toggles selection, scrolls the matched row into view via row.scrollIntoView({ behavior: "smooth", block: "center" }), triggers hotspot highlight on the schema image, mobile hit-target: ≈36 px (td has px-3 py-2, text-sm) — FLAG <44 px (file: apps/web/src/components/schema/parts-panel.tsx, line: 146 for data-faro attr, 148 for role, 149 for tabIndex, 85-88 for scrollIntoView, 174-179 for onClick) - Parts row, unavailable (FN-368 inert, MUST NOT be rage-clickable) — selector: [data-faro-user-action-name="select-part"][aria-disabled="true"], role: (no role attribute — omitted when unavailable), tabIndex={-1}, expected on click: NOTHING — onClick / onKeyDown / onMouseEnter / onMouseLeave are all undefined for unavailable rows, mobile hit-target: ≈36 px (still rendered, opacity-40) — N/A for rage-click verification (inert) (file: apps/web/src/components/schema/parts-panel.tsx, line: 146 for data-faro attr, 147 for aria-disabled, 148-149 for role/tabIndex, 158 for opacity-40 class, 171-187 for stripped handlers) - OEM copy button — selector (scope to a parts row first): getByRole('button') matching the inline