Files
sase.tr/MANIFEST-FN-403.md
Fusion 713ef987a0 feat(FN-403): Phase-1 parts panel selector & route manifest for FN-368
Commits merged:
- feat(FN-403): Phase-1 parts panel selector & route manifest for FN-368

Files changed:
MANIFEST-FN-403.md | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)

Fusion-Task-Id: FN-403
2026-05-16 11:24:00 +03:00

165 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
`<PartsPanel>` is always mounted via `<SchemaViewer>`. 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/<vehicleId>/categories/<categoryId>
(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
<button type="button"> that contains a <Copy> lucide icon (no Faro hook, no aria-label),
role: button (no accessible name; icon-only),
expected on click: e.stopPropagation() → navigator.clipboard.writeText(part.oemCode) →
setCopiedId(part.id) flips the icon from <Copy> to <Check className="text-green-500"> for 1500 ms →
api.post('/analytics/oem-copy', {oemCode, partId, vehicleId, categoryId}) (errors swallowed) →
capture('oem_code_copied', {oem_code, part_id, vehicle_id, category_id}) PostHog event,
mobile hit-target: ≈18×18 px (p-0.5 + size-3.5) — FLAG <44 px
(file: apps/web/src/components/schema/parts-panel.tsx, line: 198 for <button>, 200-204 for onClick, 205-210 for icon swap, 56-79 for copyOemCode handler)
- Empty-state escape (FN-368) — selector: getByRole('button', { name: 'Geri don' }),
role: button (accessible name "Geri don", variant="outline"),
expected on click: window.history.back() — only renders when parts.length === 0,
mobile hit-target: ≈40 px (Button default sm h-9 ≈ 36 px; with outline border + py-2 effective ≈40 px) — borderline, document
(file: apps/web/src/components/schema/parts-panel.tsx, line: 120 for <Button onClick>, 121 for label, 117 for parts.length===0 branch guard)
Loading state: getByText('Yukleniyor...') (no diacritics) + 8 sibling <Skeleton className="h-10 w-full">
keyed s0..s7. Branch entered when isLoading prop is true. Note: <SchemaViewer>'s page-level
skeleton at schema-viewer.tsx:72-86 usually fires before PartsPanel's own loading branch.
(file: apps/web/src/components/schema/parts-panel.tsx, line: 96 for "Yukleniyor...", 16 for SKELETON_ROW_KEYS, 99-103 for Skeleton map, 89 for `if (isLoading)` guard)
Empty state: getByText('Bu kategori icin parca bulunamadi.') (no diacritics) + "Geri don" button.
Branch entered when parts.length === 0 after isLoading clears.
(file: apps/web/src/components/schema/parts-panel.tsx, line: 119 for text, 121 for button label, 117 for parts.length===0 guard)
Feedback affordance (FN-368 fix): NONE — failures bubble to SchemaViewer skeleton at schema-viewer.tsx:72-86.
FN-368 (commit 0ce68a7) shipped (a) aria-disabled="true" + stripped handlers on unavailable rows,
(b) "Yukleniyor..." loading branch + 8 skeleton rows, (c) "Geri don" empty-state escape. No toast,
no inline error UI. `use-parts` fetch failures bubble up to <SchemaViewer>'s page-level
`if (isLoading)` skeleton block. `grep -n toast apps/web/src/components/schema/*.tsx
apps/web/src/hooks/use-parts.ts` → 0 matches; PartsPanel has no toast call site.
(file: apps/web/src/components/schema/schema-viewer.tsx, line: 72 for `if (isLoading)`, 73-85 for skeleton return, 86 for closing brace)
data-testid introduced by FN-368: NONE (sase/web convention — see project memory
`frontend-testability-hook-convention`).
(verified: `grep -n data-testid apps/web/src/components/schema/parts-panel.tsx` → 0 matches at HEAD d684e7f)
```
---
## Route 2 — catalog drill-down (brand → model → categories)
```
Route: /dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId
(file: apps/web/src/routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx, line: 36)
Live example URL: https://sase.tr/dashboard/catalog/<brand>/<modelId>/categories/<categoryId>
[optional search params: ?body=&engine=&gearbox=&mgp=]
(slug discovery: start at https://sase.tr/dashboard/catalog, drill brand →
model → category)
Container: same — getByRole('heading', { name: 'Parcalar' })
(file: apps/web/src/components/schema/parts-panel.tsx, line: 95 / 112)
Interactive elements: identical to Route 1 (same <SchemaViewer><PartsPanel> children).
Mount site in this route: SchemaViewer rendered at line 225 inside `<Suspense fallback={<SchemaViewerFallback />}>`.
(file: apps/web/src/routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx, line: 225)
Loading state: same — see Route 1 block; this route additionally renders a route-level
<SchemaViewerFallback /> while the lazy chunk loads (line 224 Suspense + line 121 also).
(file: apps/web/src/routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx, line: 224 for Suspense, 121 for in-page fallback usage)
Empty state: same as Route 1.
Feedback affordance (FN-368 fix): NONE — failures bubble to SchemaViewer skeleton at schema-viewer.tsx:72-86.
data-testid introduced by FN-368: NONE (sase/web convention — see project memory
`frontend-testability-hook-convention`).
```
---
## Route 3 — emex catalog group leaf
```
Route: /dashboard/catalog_/emex/$catalogCode_/$vehicleId_/groups/$groupId
(file: apps/web/src/routes/dashboard/catalog_/emex/$catalogCode_/$vehicleId_/groups/$groupId.tsx, line: 17)
Live example URL: https://sase.tr/dashboard/catalog/emex/<catalogCode>/<vehicleId>/groups/<groupId>
(slug discovery: start at https://sase.tr/dashboard/catalog, pick an
emex-backed brand → model → vehicle → group)
Container: same — getByRole('heading', { name: 'Parcalar' })
(file: apps/web/src/components/schema/parts-panel.tsx, line: 95 / 112)
Interactive elements: identical to Route 1.
Mount site in this route: SchemaViewer rendered at line 83.
(file: apps/web/src/routes/dashboard/catalog_/emex/$catalogCode_/$vehicleId_/groups/$groupId.tsx, line: 83)
Loading state: same as Route 1.
Empty state: same as Route 1.
Feedback affordance (FN-368 fix): NONE — failures bubble to SchemaViewer skeleton at schema-viewer.tsx:72-86.
data-testid introduced by FN-368: NONE (sase/web convention — see project memory
`frontend-testability-hook-convention`).
```
---
## Route 4 — pcat catalog group leaf
```
Route: /dashboard/catalog_/pcat/$catalogId_/$modelId_/$carId_/groups/$groupId
(file: apps/web/src/routes/dashboard/catalog_/pcat/$catalogId_/$modelId_/$carId_/groups/$groupId.tsx, line: 17)
Live example URL: https://sase.tr/dashboard/catalog/pcat/<catalogId>/<modelId>/<carId>/groups/<groupId>
(slug discovery: start at https://sase.tr/dashboard/catalog, pick a
pcat-backed brand → model → car → group)
Container: same — getByRole('heading', { name: 'Parcalar' })
(file: apps/web/src/components/schema/parts-panel.tsx, line: 95 / 112)
Interactive elements: identical to Route 1.
Mount site in this route: SchemaViewer rendered at line 100.
(file: apps/web/src/routes/dashboard/catalog_/pcat/$catalogId_/$modelId_/$carId_/groups/$groupId.tsx, line: 100)
Loading state: same as Route 1.
Empty state: same as Route 1.
Feedback affordance (FN-368 fix): NONE — failures bubble to SchemaViewer skeleton at schema-viewer.tsx:72-86.
data-testid introduced by FN-368: NONE (sase/web convention — see project memory
`frontend-testability-hook-convention`).
```
---
## Drift vs FN-400 baseline
FN-400 captured selectors against `main @ 08dbde7`. Re-verifying on current worktree HEAD `d684e7f`:
- All `parts-panel.tsx` line numbers unchanged (146 select-part attr, 147 aria-disabled, 96 "Yukleniyor...", 119 empty-state text, 121 "Geri don" button).
- `schema-viewer.tsx` skeleton block: FN-400 noted `:72-86`; the `if (isLoading)` branch on current worktree returns through line 94 (block opens at `:72` and the closing brace is at `:94`). Use **`schema-viewer.tsx:72-86`** going forward.
- All four route files (mount sites in step 3 of PROMPT.md) confirmed present and mounting `<SchemaViewer>`.
## Cross-references
- Project memory: `frontend-testability-hook-convention` (sase/web no-`data-testid` convention).
- Project memory `MEMORY.md` (≈ lines 257268 of root index): `Canonical FN-367/FN-368 selectors` block — kept in sync via the same `fn_memory_append` call that wrote the compact version of this manifest.
- Sibling: `.fusion/tasks/FN-400/MANIFEST.md` (FN-367 / VIN-decode half).
- Consumer: FN-393 (Playwright verification — to be re-specified against this manifest).
- Source commit for the fix: `0ce68a7``feat(FN-368): parts panel loading state, inert unavailable rows, escape hatch (gitea #10)`.