# FN-400 — Phase-1 Selector & Route Manifest **Scope:** sase.tr web app (`apps/web`), current `main` (commit `08dbde7`). **Source of truth for FN-367:** commit `e274e02` (`feat(FN-367): add inline retry affordance after VIN decode failure`). **Source of truth for FN-368:** commit `0ce68a7` (`feat(FN-368): parts panel loading state, inert unavailable rows, escape hatch`). **Convention note:** This codebase does **not** use `data-testid`. Testability hooks are `data-faro-user-action-name="…"` plus semantic ARIA (`role`, `aria-live`, `aria-disabled`). All selectors below were re-verified against the shipped tree. --- ## FN-367 — VIN decode failure → rage clicks ### Route(s) - `css=/dashboard/search` — file: `apps/web/src/routes/dashboard/search.tsx:46` (`createFileRoute("/dashboard/search")`). VIN decode is triggered exclusively from this route. Successful decode navigates to `/dashboard/vehicles/$id` (`search.tsx:179`). ### Input selector (VIN field) - `css=input[maxlength="17"]` — file: `apps/web/src/routes/dashboard/search.tsx:333-358`. - Stable accessor (preferred): `role=textbox` with placeholder `"Şase numarasını girin (17 karakter)"` (`search.tsx:334`). - **No `data-testid`, no `data-faro-user-action-name` on the input.** Use the placeholder or `role=textbox`. ### Submit / decode trigger - `text="Şase Çöz"` button (type=submit) — file: `apps/web/src/routes/dashboard/search.tsx:399-409`. - Stable accessor: `role=button` with name `"Şase Çöz"`. - Form submit handler: `handleSearch` (`search.tsx:204`). - **No `data-faro-user-action-name` on the submit button.** ### Error / failure-state selector (the element a user would rage-click) - `role="alert"` + `aria-live="assertive"` on the error banner — file: `apps/web/src/routes/dashboard/search.tsx:412-471` (attributes at lines `414` and `415`). - Title text inside the banner: `"Şase çözümlenemedi"` (`search.tsx:421`). - Introduced/touched by FN-367 (commit `e274e02`). Confirmed present in current tree. ### Retry affordance selector (introduced by FN-367) - `data-faro-user-action-name="vin-decode-retry"` — file: `apps/web/src/routes/dashboard/search.tsx:446`. - Button text: `"Tekrar Dene"` (`search.tsx:453`). - Rendered conditionally: only when `error` does **not** contain the substrings `"abone olun"` (subscription wall) or `"tanınamadı"` (unknown VIN — that branch shows a different "report to admin" CTA instead). See guard at `search.tsx:443`. - Handler: `handleRetry` (`search.tsx:189`), which fires PostHog event `vin_decode_retry_clicked` (`search.tsx:193`). ### data-testid hooks added by FN-367 - **NOT PRESENT IN CURRENT CODE.** FN-367 did not add `data-testid` attributes. The verification-relevant hooks it added are: (1) `data-faro-user-action-name="vin-decode-retry"` on the retry button (`search.tsx:446`), (2) `role="alert"` + `aria-live="assertive"` on the error banner (`search.tsx:414-415`). The vitest test file `apps/web/src/routes/__tests__/dashboard-search.test.tsx` queries by role/name, not by testid — Playwright should follow the same pattern. ### Other useful hooks on this route - `data-faro-user-action-name="fill-example-vin"` — example-VIN helper button (`search.tsx:390`). - `data-faro-user-action-name="select-history-vin"` — history-row buttons (`search.tsx:560`). --- ## FN-368 — Parts panel → rage clicks → abandonment ### Route(s) The parts panel is rendered indirectly via ``, which is mounted on four category-level routes (all four are in scope; the "vehicle detail page" referenced in the spec is the first): - `css=/dashboard/vehicles/$id/categories/$categoryId` — file: `apps/web/src/routes/dashboard/vehicles_/$id/categories_/$categoryId.tsx:157-158`. **Primary post-VIN-decode parts surface.** - `css=/dashboard/catalog/$brandName/$modelId/categories/$categoryId` — file: `apps/web/src/routes/dashboard/catalog_/$brandName_/$modelId/categories_/$categoryId.tsx:224-225`. - `css=/dashboard/catalog/emex/$catalogCode/$vehicleId/groups/$groupId` — file: `apps/web/src/routes/dashboard/catalog_/emex/$catalogCode_/$vehicleId_/groups/$groupId.tsx:83`. - `css=/dashboard/catalog/pcat/$catalogId/$modelId/$carId/groups/$groupId` — file: `apps/web/src/routes/dashboard/catalog_/pcat/$catalogId_/$modelId_/$carId_/groups/$groupId.tsx:100`. `SchemaViewer` then renders `` at `apps/web/src/components/schema/schema-viewer.tsx:100` (no-schema fallback) and `:168` (split-pane right column). ### Parts panel container selector - `text="Parcalar"` — the heading inside the container header — file: `apps/web/src/components/schema/parts-panel.tsx:95` (loading state) and `:112` (loaded state). - Stable accessor: `role=heading` with name `"Parcalar"`. - **No dedicated container `data-testid` or `data-faro-user-action-name` on the wrapper `
`.** Use the heading text to scope locators. ### Part category / part item selector (the elements users were rage-clicking pre-fix) - `data-faro-user-action-name="select-part"` on each part row — file: `apps/web/src/components/schema/parts-panel.tsx:146`. - Available rows: `role="button"` + `tabIndex={0}` (`parts-panel.tsx:148-149`). - Unavailable rows (the inert, post-fix state that visually replaces the rage-clicked target): `aria-disabled="true"` + `tabIndex={-1}` + stripped click/keydown handlers (`parts-panel.tsx:147-149`, with handler-strip logic at `:166-181`). - Visual hint: `className` includes `opacity-40` on unavailable rows (`parts-panel.tsx:158`). ### Loading / skeleton state selector - `text="Yukleniyor..."` (note: no diacritics in source) inside the panel header during load — file: `apps/web/src/components/schema/parts-panel.tsx:96`. - Skeleton rows are 8× `` elements rendered from `SKELETON_ROW_KEYS` (`parts-panel.tsx:18`, mapped at `:101-103`). - Stable accessor: scope to panel by `role=heading name=Parcalar`, then assert sibling `text="Yukleniyor..."`. - Introduced by FN-368 (the entire `if (isLoading)` block at `parts-panel.tsx:89-106` is new in commit `0ce68a7`). ### Empty / error state selector - Empty state: `text="Bu kategori icin parca bulunamadi."` — file: `apps/web/src/components/schema/parts-panel.tsx:119`. - Escape-hatch button (introduced by FN-368): `text="Geri don"` — file: `apps/web/src/components/schema/parts-panel.tsx:121` (``). - Stable accessor for escape hatch: `role=button` name `"Geri don"`. - **No dedicated error state.** The panel does not render a server-error branch — upstream `use-parts` failures bubble to the page-level loading skeleton in `SchemaViewer` (`schema-viewer.tsx:69-83`). Flag for verification author: if FN-368 testing needs an explicit error UI, it does not exist in current code. ### data-testid hooks added by FN-368 - **NOT PRESENT IN CURRENT CODE.** FN-368 did not add `data-testid` attributes. The verification-relevant hooks it added are: - `data-faro-user-action-name="select-part"` on every row (`parts-panel.tsx:146`). - `aria-disabled="true"` on unavailable rows (`parts-panel.tsx:147`). - `role="button"` + `tabIndex={0}` on available rows; `tabIndex={-1}` on unavailable rows (`parts-panel.tsx:148-149`). - Loading-state branch with `"Yukleniyor..."` text + skeleton rows (`parts-panel.tsx:89-106`). - "Geri don" escape-hatch button in empty state (`parts-panel.tsx:120-123`). The companion vitest spec `apps/web/src/components/schema/__tests__/parts-panel.test.tsx` (added by FN-368) queries by `role` / accessible-name / text — Playwright should match that pattern. --- ## Drift check | Selector | In commit | In current `main` | Status | |---|---|---|---| | `role="alert"` + `aria-live="assertive"` on VIN error banner | `e274e02` `search.tsx` | `search.tsx:414-415` | ✅ unchanged | | `data-faro-user-action-name="vin-decode-retry"` | `e274e02` `search.tsx` | `search.tsx:446` | ✅ unchanged | | `"Tekrar Dene"` button text | `e274e02` | `search.tsx:453` | ✅ unchanged | | `data-faro-user-action-name="select-part"` | `0ce68a7` `parts-panel.tsx` | `parts-panel.tsx:146` | ✅ unchanged | | `aria-disabled="true"` on unavailable rows | `0ce68a7` | `parts-panel.tsx:147` | ✅ unchanged | | `"Yukleniyor..."` loading text | `0ce68a7` | `parts-panel.tsx:96` | ✅ unchanged | | `"Geri don"` empty-state escape hatch | `0ce68a7` | `parts-panel.tsx:121` | ✅ unchanged | No drift between the FN-367 / FN-368 PRs and current shipped code on `main` as of `08dbde7`. --- ## Recommended Playwright locator priority (per project convention) 1. `[data-faro-user-action-name="…"]` — preferred where present. 2. `getByRole(role, { name })` — `role=alert`, `role=button`, `role=heading`, `role=textbox`. 3. `getByText("…")` / `getByPlaceholder("…")` — last resort for elements without (1) or (2). Do **not** request new `data-testid` attributes unless a target has none of the above; that would force a code change purely for testability and conflicts with the established convention.