Commits merged: - feat(FN-400): Phase-1 selector & route manifest for FN-367/FN-368 Files changed: MANIFEST.md | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) Fusion-Task-Id: FN-400
8.9 KiB
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=textboxwith placeholder"Şase numarasını girin (17 karakter)"(search.tsx:334). - No
data-testid, nodata-faro-user-action-nameon the input. Use the placeholder orrole=textbox.
Submit / decode trigger
text="Şase Çöz"button (type=submit) — file:apps/web/src/routes/dashboard/search.tsx:399-409.- Stable accessor:
role=buttonwith name"Şase Çöz". - Form submit handler:
handleSearch(search.tsx:204). - No
data-faro-user-action-nameon 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 lines414and415).- 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
errordoes 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 atsearch.tsx:443. - Handler:
handleRetry(search.tsx:189), which fires PostHog eventvin_decode_retry_clicked(search.tsx:193).
data-testid hooks added by FN-367
- NOT PRESENT IN CURRENT CODE. FN-367 did not add
data-testidattributes. 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 fileapps/web/src/routes/__tests__/dashboard-search.test.tsxqueries 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 <SchemaViewer>, 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 <PartsPanel> 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=headingwith name"Parcalar". - No dedicated container
data-testidordata-faro-user-action-nameon the wrapper<div>. 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:
classNameincludesopacity-40on 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×
<Skeleton>elements rendered fromSKELETON_ROW_KEYS(parts-panel.tsx:18, mapped at:101-103). - Stable accessor: scope to panel by
role=heading name=Parcalar, then assert siblingtext="Yukleniyor...". - Introduced by FN-368 (the entire
if (isLoading)block atparts-panel.tsx:89-106is new in commit0ce68a7).
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(<Button … onClick={() => window.history.back()}>Geri don</Button>). - Stable accessor for escape hatch:
role=buttonname"Geri don". - No dedicated error state. The panel does not render a server-error branch — upstream
use-partsfailures bubble to the page-level loading skeleton inSchemaViewer(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-testidattributes. 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)
[data-faro-user-action-name="…"]— preferred where present.getByRole(role, { name })—role=alert,role=button,role=heading,role=textbox.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.