[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71PMWE559YAWC8QWCD96CSD":3,"skill-same:01M71PMWE559YAWC8QWCD96CSD":89},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":14,"repositoryHtmlUrl":59,"repositoryHomepageUrl":60,"ownerHtmlUrl":61,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":62,"install":63,"resources":76},"01M71PMWE559YAWC8QWCD96CSD","selector-drift-recovery",null,"Bulk-regenerate broken test selectors after a UI refactor or redesign. Detects drift between old and new DOM with an aria-snapshot diff, maps old locators to new equivalents using role-first + region scoping, validates against the new build, and produces a single PR with grouped per-file selector updates and per-change evidence. Assumes Playwright >= 1.50 (trace viewer DOM-snapshot panel, getByRole filtering, ariaSnapshot). Use when: \"UI refactor broke tests,\" \"redesign broke tests,\" \"bulk update selectors,\" \"regenerate selectors after refactor,\" \"selector drift,\" \"fix N broken tests after redesign.\" Not for: healing one flaky test at runtime — use test-reliability. Not for: writing a new test suite from scratch — use playwright-automation. Not for: re-recording tests after a framework switch (Selenium to Playwright) — use test-migration. Related: test-reliability, playwright-automation, test-migration, ci-cd-integration, visual-testing.","pattanation\u002FNorvik","pattanation","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235419268?v=4","01M71M5THHYYB15ME8R2E4FCZG","Patta","01M71M5TJM1NDM992M8PDKYR2K",0,"MIT","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fselector-drift-recovery\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"selector","recovery","selector drift recovery","drift","redesign","change","maps","updates","when","writing","new","selenium","ci","fix","testing","re","reliability","broken","bulk","recording","2026-11-10 13:02:02",false,"---\nname: selector-drift-recovery\ndescription: >-\n  Bulk-regenerate broken test selectors after a UI refactor or redesign. Detects\n  drift between old and new DOM with an aria-snapshot diff, maps old locators to\n  new equivalents using role-first + region scoping, validates against the new\n  build, and produces a single PR with grouped per-file selector updates and\n  per-change evidence. Assumes Playwright >= 1.50 (trace viewer DOM-snapshot\n  panel, getByRole filtering, ariaSnapshot). Use when: \"UI refactor broke tests,\"\n  \"redesign broke tests,\" \"bulk update selectors,\" \"regenerate selectors after\n  refactor,\" \"selector drift,\" \"fix N broken tests after redesign.\" Not for:\n  healing one flaky test at runtime — use test-reliability. Not for: writing a new\n  test suite from scratch — use playwright-automation. Not for: re-recording tests\n  after a framework switch (Selenium to Playwright) — use test-migration.\n  Related: test-reliability, playwright-automation, test-migration, ci-cd-integration, visual-testing.\nlicense: MIT\nmetadata:\n  author: kindlmann\n  version: \"2.0\"\n  category: automation\n---\n\n\u003Cobjective>\nA redesign shipped and 23 tests now fail with `TimeoutError: locator.* exceeded` — the DOM moved, not the product. This skill closes that maintenance loop: it diffs the old DOM against the new one, regenerates the broken selectors role-first against the new build, validates the whole suite, and ships the diff as one reviewable PR with a confidence score and screenshot per change. The trigger is an event (a refactor merged), not a flake. The output is a PR a human signs off on — not a silent runtime auto-heal.\n\nThis is the bulk, offline, batch counterpart to `test-reliability`. They share the multi-attribute locator + confidence scoring primitives but run in opposite directions: `test-reliability` heals one selector at runtime behind a guarded threshold; `selector-drift-recovery` regenerates N selectors offline against the new DOM and bundles them into a PR. If you are doing the second workflow inside `test-reliability`, switch here.\n\u003C\u002Fobjective>\n\n## Quick Route\n\n| Situation | Go to |\n|-----------|-------|\n| One broken test, not a refactor | Stop — use `test-reliability` instead |\n| 200+ broken selectors across many files | Split by area first (one PR per page\u002Fdir), then Phase 1 |\n| Refactor changed flows\u002Fsemantics, not just structure | Stop — rewrite from specs with `playwright-automation` |\n| Framework switch (Selenium → Playwright) | Stop — use `test-migration`, not drift recovery |\n| No old-DOM reference exists anywhere | Capture one (Phase 1) or scope down — without it this is \"rewrite tests\" |\n| Have old + new DOM, ready to map | Phase 1 → 6 below |\n\n---\n\n## Discovery Questions\n\nCheck `.agents\u002Fqa-project-context.md` first — if it exists, use it and skip anything answered there. It identifies your E2E framework, selector strategy, and known fragile areas. Then:\n\n1. **What triggered the drift?** A planned refactor (Storybook can show the new DOM before merge), a shipped redesign (new DOM is in main), a dependency upgrade, or a Tailwind\u002FCSS migration? The trigger decides whether you run pre-emptively or react to CI failures.\n2. **What is the blast radius?** A single component, a page, or the whole app? Single component: scope recovery to the test files that touch it. Global: budget half a day to a day, and decide which tests should be rewritten rather than re-selected.\n3. **What is your current selector strategy?** If selectors are mostly `data-testid` and the refactor preserved testids, recovery is trivial. If they are CSS-class or XPath based, expect 30–60% to need a new *strategy*, not just a new locator.\n4. **Is there a passing baseline?** You need the old DOM *somewhere*: a previous CI trace artifact, a deployed staging build, a Storybook story, or git history of the components. No old-DOM reference means this degrades to \"rewrite tests.\"\n5. **Are the broken locators inline or wrapped in a Page Object?** The JSON reporter's `error.location` points at the *failing line*. For inline locators that is the locator itself; for POM-wrapped locators it points at the POM helper, not the test. Know this before you trust the auto-extracted line numbers (see Failure Modes).\n6. **Who reviews the resulting PR?** Confidence-scored updates need a human signoff. Decide upfront whether the PR goes to the test author, the engineer who did the refactor, or the QA lead.\n\n---\n\n## Core Principles\n\n1. **Recovery is event-driven, not failure-driven.** Run this when a refactor is planned or just shipped, not when one test goes flaky. One broken test → `test-reliability`. Ten or more from the same event → this skill.\n\n2. **Old DOM, new DOM, mapped pair.** The whole skill rests on a snapshot before the refactor and one after. Everything else is bookkeeping around that pair. Capture both as **aria snapshots** (`await page.locator('body').ariaSnapshot()`), not raw HTML — a role-tree diff is exactly the signal role-first recovery needs and ignores the cosmetic churn (class renames, wrapper divs) that a raw-HTML diff drowns in. If you cannot produce both snapshots, fix that first.\n\n3. **Role-first replacement, every time.** Even when the old test used CSS, the regenerated selector should prefer `getByRole` + accessible name, then `getByLabel` for form fields, then `getByTestId` when the refactor added one. The recovery PR is your chance to ratchet the average selector stability score up (0–5 rubric below, shared with `test-reliability`).\n\n4. **Disambiguate by region scoping, not layout selectors.** When role+name is ambiguous (two \"Submit\" buttons), narrow with `getByRole('region', { name }).getByRole('button', …)` or `getByRole(...).filter({ hasText })`. Do **not** reach for `:near()` \u002F `:right-of()` — see the Avoid note. A score-3 candidate is one where region scoping has been *applied* and the locator now matches exactly one element.\n\n5. **One PR, grouped by file, with per-change evidence.** Reviewers cannot eyeball 47 selector changes spread across 30 commits. Bundle into one PR, group hunks by test file, attach a confidence score + DOM screenshot per change.\n\n6. **The suite must pass before merge, and dead tests get deleted.** A regenerated selector that doesn't run is a worse version of the original problem; the skill ends with green CI, not a generated diff. And if the refactor removed a feature, prune its tests — do not regenerate selectors for elements that no longer exist.\n\n> **Avoid:** Playwright layout selectors `:near()`, `:right-of()`, `:left-of()`, `:above()`, `:below()` as disambiguators — officially deprecated and \"may be removed,\" because a 1px layout shift changes the match (Playwright docs, 2026). They also contradict the role-first thesis. Use region scoping \u002F `getByRole().filter()` instead.\n\n---\n\n## Workflow\n\nSix phases, each gated by a check before the next.\n\n### Phase 1 — Snapshot the old DOM\n\nYou need a snapshot of every page or component the affected tests touch, in its pre-refactor state. **Sources, in preference order:**\n\n1. **Last green CI trace artifact.** Most teams save Playwright traces on failure (`trace: 'on-first-retry'`). Download the green-run artifact, open a trace with `npx playwright show-trace traces\u002Fcheckout.zip`, select an action, and read the per-action **DOM snapshot panel** for each surface. (The viewer no longer has a \"Copy HTML at this step\" menu item; you read the snapshot panel or, for a programmatic dump, replay with `page.content()` \u002F `ariaSnapshot()`.)\n2. **Storybook at a pre-refactor commit.** `git checkout \u003CPRE_REFACTOR_SHA>`, start Storybook, and dump each story with a tiny `page.content()` \u002F `ariaSnapshot()` script.\n3. **A staging build still on the old version.** Navigate the same flows and snapshot.\n4. **Git history of the components.** Reconstructable but the most expensive — render in isolation.\n\nOutput: `.drift-recovery\u002Fold\u002F\u003Cpage-or-component>.aria.yml` (and `.html` if you also need raw markup) per affected unit.\n\n**Gate:** You can answer \"what did this page look like when the tests last passed?\" from a snapshot file, not from memory.\n\n### Phase 2 — Snapshot the new DOM\n\nRun the same surfaces in the post-refactor build — a Vercel\u002FNetlify preview deploy is ideal, or a local dev server \u002F the PR branch in CI. Wait for hydration (`await page.waitForLoadState('networkidle')`) before snapshotting, or SSR pages give you the pre-hydration tree and you miss client-rendered elements.\n\nOutput: `.drift-recovery\u002Fnew\u002F\u003Cpage-or-component>.aria.yml` matching the old set.\n\n**Gate:** Every old snapshot has a matching new one. If a route 404s in the new build, that flow was deleted — mark its tests for the deletion pile in Phase 6.\n\n### Phase 3 — Identify broken selectors and infer intent\n\nFor each test file, run against the new build with the JSON reporter, then parse it. Capture, per failure: **file, line, old locator string, error type (timeout vs assertion), and inferred intent**. Group the results by test file.\n\n- **Error classification:** a drift failure is `TimeoutError: locator.* exceeded`. Distinguish it from an assertion failure (`expect(...).toBe`) so you don't try to re-select a locator that resolved fine but failed a value check.\n- **Inferred intent is mandatory and not in the reporter.** Read the surrounding test code — what action is taken on the locator, what assertion follows — and store a short intent string (\"submit the order\", \"read the order total\"). The candidate generator keys off intent, so this step is load-bearing, not commentary.\n- **Page route is also not in the reporter.** Map each locator to the snapshot it should resolve against (which `.drift-recovery\u002Fnew\u002F*.aria.yml`) so Phase 4 can load the right new DOM.\n\nThe result is a per-file table:\n\n| Test file | Line | Old locator | Error type | Page route | Inferred intent |\n|---|---|---|---|---|---|\n| `tests\u002Fcheckout.spec.ts` | 42 | `getByTestId('submit-btn')` | timeout | `\u002Fcheckout` | Submit the order |\n| `tests\u002Fcheckout.spec.ts` | 87 | `locator('.summary > h2')` | timeout | `\u002Fcheckout` | Read the order total |\n\nSee `references\u002Frecovery-scripts.md` for `identify-drift.ts`, which produces exactly these rows (with the intent\u002Froute fields populated, not stubbed).\n\n**Gate:** Every broken locator has an inferred intent and a page route. If you cannot infer intent, ask the test author or read the original PR — do not guess.\n\n### Phase 4 — Generate replacement candidates\n\nFor each row, generate candidates against the **new** DOM snapshot and score each on the 0–5 rubric (shared with `test-reliability`). Strategy ladder, best first:\n\n1. **New `data-testid`** added by the refactor team — the most stable choice they made. Score 5.\n2. **`getByRole` + accessible name, unique on the page.** Score 4.\n3. **`getByLabel` for a form field**, when the intent is an input and a label exists. Score 4 (use over a bare role when the field has no name otherwise).\n4. **Role + name, region-scoped to a single match.** If role+name alone returns >1 element, wrap it — `getByRole('region', { name }).getByRole(role, { name })` or `.filter({ hasText })` — and confirm the scoped locator now matches exactly one. Only score 3 **after** scoping makes it unambiguous.\n5. **Visible text only** (`getByText`). Score 2 — fragile to copy changes.\n6. **CSS class on the changed structure.** Score 1 — usually still broken.\n7. **No safe replacement.** Score 0 — flag for human.\n\n| Score | Replacement strategy | Auto-apply? |\n|---|---|---|\n| 5 | New `data-testid` exists | yes |\n| 4 | `getByRole` + accessible name (or `getByLabel`), unique on page | yes |\n| 3 | `getByRole` + name, region-scoped to exactly one match | yes |\n| 2 | Visible-text-only | no |\n| 1 | CSS class on changed structure | no |\n| 0 | No safe replacement found | no — flag for human |\n\nA candidate is **score 3 only if scoping already resolved it to a single element**. A still-ambiguous multi-match (`count > 1`, \"needs scoping\") is not a 3 — it is unfinished, and must not be auto-applied.\n\nOutput: `.drift-recovery\u002Fcandidates.json` with `{ file, line, oldLocator, selector, score, rationale, screenshotPath }` per change. See `references\u002Frecovery-scripts.md` for `generate-candidates.ts`.\n\n**Gate:** Every row has a candidate scored ≥ 3 (and confirmed unique), or is flagged for human review. Score-0\u002F1\u002F2 are never auto-applied.\n\n### Phase 5 — Apply, validate, iterate\n\n1. Apply the score-≥3 replacements to a feature branch. Replace by `(file, line)`, not a content-wide string replace — the reporter's locator string is a *rendered* form (`locator('.summary > h2')`) that rarely matches the source expression verbatim, and a naive `String.replace` hits only the first occurrence and collides on identical locators. Edit the specific line; set `applied: true` on each candidate you actually wrote.\n2. Run the **full affected suite**, not just the previously-failing tests — a new selector can match an unintended element and break a previously-passing test.\n3. Per test: **passed** → keep the replacement. **failed** → revert that one line, mark the test for human review.\n4. Emit a summary: N recovered automatically, M flagged.\n\n**Gate:** Recovered tests pass the suite. Flagged tests are clearly marked, not silently included.\n\n### Phase 6 — Ship the PR\n\nThe PR is the deliverable. **Title:** `chore(tests): selector recovery after \u003Crefactor description>`. **Body** (generated from `candidates.json`, filtering on `applied`):\n\n```markdown\n## Trigger\n\u003CLink to the refactor PR \u002F describe the redesign>\n\n## Summary\n- N test files updated   - M selectors changed\n- K tests deleted (feature removed)   - L tests flagged for manual review\n\n## Per-file changes\n\u003CFor each file: a table of line, old, new, score, screenshot URL>\n\n## Flagged for review\n\u003CTests where no candidate scored >= 3, with the inferred intent>\n\n## How to review\n- Check each screenshot: does `new` point at the element `old` pointed at?\n- For score-3 candidates, verify the region scope is meaningful in the new design.\n- For flagged tests, decide: rewrite, delete, or accept a manual selector update.\n```\n\nAttach screenshots inline via your team's CI artifact URL pattern. See `references\u002Frecovery-scripts.md` for `apply-recovery.ts` (line-anchored) and `build-pr-body.ts`.\n\n**Gate:** PR is reviewable in one sitting. Too large → split by area (one PR per page \u002F component \u002F test directory).\n\n---\n\n## Anti-Patterns\n\n1. **Auto-applying score-0, -1, or -2 candidates.** A score-2 is \"we found *some* element.\" That is gambling, not recovery — and it is the usual cause of a suite-wide stability score *dropping* after a recovery. Auto-apply only score ≥ 3.\n\n2. **Calling an ambiguous multi-match \"score 3.\"** If `getByRole(...)` returns >1 element it is not a 3 until region scoping narrows it to exactly one. Scoring it 3 and auto-applying ships a locator that resolves to the wrong element.\n\n3. **Skipping the screenshots.** A score-4 candidate can still point at the wrong element when the page has two regions with the same role + name. The per-change screenshot is the only check that catches semantic drift; confidence scores alone do not.\n\n4. **Content-wide string replace instead of line-anchored edits.** `content.replace(oldLocator, …)` hits the first occurrence only, collides on duplicate locators, and silently no-ops when the reporter's rendered string differs from the source expression. Edit the specific `(file, line)`.\n\n5. **Trusting auto-extracted line numbers for POM-wrapped locators.** The JSON reporter's `error.location` points at the failing line, which for a Page Object is the helper, not the test. Re-read the locator from the trace action or grep the POM source before applying.\n\n6. **Recovering tests for deleted features.** The refactor may have removed flows. Map deleted routes in Phase 2 and prune those tests — do not regenerate selectors for elements that no longer exist.\n\n7. **Auto-merging the recovery PR in CI.** The PR *is* the artifact; the whole point is a human eyeballing the per-change evidence. Auto-merge once tests pass defeats the purpose — a green suite with a selector pointing at the wrong-but-present element passes and erodes trust. Require a reviewer.\n\n8. **Treating the PR as urgent.** A failed suite feels urgent; a *correctly* recovered one is what matters. Time pressure produces score-2 replacements that quietly degrade the suite.\n\n---\n\n## Failure Modes\n\n| Symptom | Likely cause | Fix or check |\n|---|---|---|\n| `identify-drift.ts` finds 0 failures despite red CI | Suite errored before producing the JSON report, or you parsed the wrong file | `jq '.stats' .drift-recovery\u002Fresults.json`; confirm `--reporter=json` redirected to the file |\n| Extracted line points at a POM file, not the test | Locator is wrapped in a Page Object | Read the locator from the trace action, or grep the POM source for the rendered string |\n| `generate-candidates.ts` reads `undefined` for intent\u002Froute | Phase 3 output missing the inferred-intent \u002F page-route fields | Populate them in Phase 3 — they are not in the reporter; the generator cannot infer them |\n| New-DOM snapshot is missing client-rendered elements | Snapshotted before hydration | Add `await page.waitForLoadState('networkidle')` before `ariaSnapshot()` |\n| Average stability score dropped after recovery | Score-2\u002FCSS candidates auto-applied | Revert candidates with score \u003C 3 in `candidates.json`; only role\u002Flabel\u002Ftestid should land (see scorer below) |\n\n---\n\n## Verification\n\nRun these on the recovery branch before opening the PR, smallest first:\n\n```bash\n# 1. No applied candidate is below the stability floor (machine-checkable proxy for \"ratcheted up\")\nnode references\u002Fscore-candidates.mjs .drift-recovery\u002Fcandidates.json\n# prints average score + count of applied rows with score \u003C 3 — that count MUST be 0\n\n# 2. The recovered suite is green against the new build\nPLAYWRIGHT_TEST_BASE_URL=$PREVIEW_URL npx playwright test --reporter=json \\\n  | jq '.stats.unexpected'        # must be 0 (flagged tests excluded via grep\u002Fskip)\n\n# 3. The PR exists with the evidence body\ngh pr view --json title,body -q '.title'   # contains \"selector recovery\"\n```\n\n`references\u002Fscore-candidates.mjs` reads `candidates.json`, prints the average applied score and the count of `applied && score \u003C 3` rows; a non-zero count means a low-confidence selector leaked in. Step 1 passing + step 2 returning `0` is the proof the recovery worked.\n\n---\n\n## Done When\n\n- Every in-scope test passes on the new build, is flagged for human review with a clear reason, or is deleted because its feature is gone.\n- `references\u002Fscore-candidates.mjs candidates.json` reports **0** applied candidates with score \u003C 3.\n- The PR is open (`gh pr view` succeeds) with per-change evidence: confidence score and screenshot per change, grouped by file.\n- `npx playwright test --reporter=json | jq '.stats.unexpected'` returns `0` on the recovery branch.\n- A short note is added to `.agents\u002Fqa-project-context.md` describing the refactor and any new test patterns introduced.\n\n## Reference Files (in `references\u002F`)\n\n- **recovery-scripts.md** — the full playbook with corrected, runnable scripts: aria-snapshot capture, `identify-drift.ts` (populates intent + route), `generate-candidates.ts` (region-scoping ladder, true score-3), line-anchored `apply-recovery.ts`, and `build-pr-body.ts`. Includes the Cypress-adaptation note.\n- **score-candidates.mjs** — tiny stability scorer; prints the average applied score and the count of applied rows below score 3. Used by Verification and Done When.\n\n## Related Skills\n\n- **test-reliability** — runtime per-test healing. Use for one flaky test, not a refactor-driven mass update. Shares the 0–5 stability rubric.\n- **playwright-automation** — writing new tests from scratch. Use when the refactor removed enough features that tests should be rewritten, not patched.\n- **test-migration** — switching frameworks (Selenium → Playwright). A migration re-records tests; it is not selector drift, even though both touch many tests at once.\n- **visual-testing** — had this run on every PR, the refactor's visual diff would have flagged before merge. Recovery is the fallback when that coverage is missing.\n- **ci-cd-integration** — wires the recovery PR's validation step into CI.\n","\u003Cobjective>\nA redesign shipped and 23 tests now fail with `TimeoutError: locator.* exceeded` — the DOM moved, not the product. This skill closes that maintenance loop: it diffs the old DOM against the new one, regenerates the broken selectors role-first against the new build, validates the whole suite, and ships the diff as one reviewable PR with a confidence score and screenshot per change. The trigger is an event (a refactor merged), not a flake. The output is a PR a human signs off on — not a silent runtime auto-heal.\n\nThis is the bulk, offline, batch counterpart to `test-reliability`. They share the multi-attribute locator + confidence scoring primitives but run in opposite directions: `test-reliability` heals one selector at runtime behind a guarded threshold; `selector-drift-recovery` regenerates N selectors offline against the new DOM and bundles them into a PR. If you are doing the second workflow inside `test-reliability`, switch here.\n\u003C\u002Fobjective>\n\n## Quick Route\n\n| Situation | Go to |\n|-----------|-------|\n| One broken test, not a refactor | Stop — use `test-reliability` instead |\n| 200+ broken selectors across many files | Split by area first (one PR per page\u002Fdir), then Phase 1 |\n| Refactor changed flows\u002Fsemantics, not just structure | Stop — rewrite from specs with `playwright-automation` |\n| Framework switch (Selenium → Playwright) | Stop — use `test-migration`, not drift recovery |\n| No old-DOM reference exists anywhere | Capture one (Phase 1) or scope down — without it this is \"rewrite tests\" |\n| Have old + new DOM, ready to map | Phase 1 → 6 below |\n\n---\n\n## Discovery Questions\n\nCheck `.agents\u002Fqa-project-context.md` first — if it exists, use it and skip anything answered there. It identifies your E2E framework, selector strategy, and known fragile areas. Then:\n\n1. **What triggered the drift?** A planned refactor (Storybook can show the new DOM before merge), a shipped redesign (new DOM is in main), a dependency upgrade, or a Tailwind\u002FCSS migration? The trigger decides whether you run pre-emptively or react to CI failures.\n2. **What is the blast radius?** A single component, a page, or the whole app? Single component: scope recovery to the test files that touch it. Global: budget half a day to a day, and decide which tests should be rewritten rather than re-selected.\n3. **What is your current selector strategy?** If selectors are mostly `data-testid` and the refactor preserved testids, recovery is trivial. If they are CSS-class or XPath based, expect 30–60% to need a new *strategy*, not just a new locator.\n4. **Is there a passing baseline?** You need the old DOM *somewhere*: a previous CI trace artifact, a deployed staging build, a Storybook story, or git history of the components. No old-DOM reference means this degrades to \"rewrite tests.\"\n5. **Are the broken locators inline or wrapped in a Page Object?** The JSON reporter's `error.location` points at the *failing line*. For inline locators that is the locator itself; for POM-wrapped locators it points at the POM helper, not the test. Know this before you trust the auto-extracted line numbers (see Failure Modes).\n6. **Who reviews the resulting PR?** Confidence-scored updates need a human signoff. Decide upfront whether the PR goes to the test author, the engineer who did the refactor, or the QA lead.\n\n---\n\n## Core Principles\n\n1. **Recovery is event-driven, not failure-driven.** Run this when a refactor is planned or just shipped, not when one test goes flaky. One broken test → `test-reliability`. Ten or more from the same event → this skill.\n\n2. **Old DOM, new DOM, mapped pair.** The whole skill rests on a snapshot before the refactor and one after. Everything else is bookkeeping around that pair. Capture both as **aria snapshots** (`await page.locator('body').ariaSnapshot()`), not raw HTML — a role-tree diff is exactly the signal role-first recovery needs and ignores the cosmetic churn (class renames, wrapper divs) that a raw-HTML diff drowns in. If you cannot produce both snapshots, fix that first.\n\n3. **Role-first replacement, every time.** Even when the old test used CSS, the regenerated selector should prefer `getByRole` + accessible name, then `getByLabel` for form fields, then `getByTestId` when the refactor added one. The recovery PR is your chance to ratchet the average selector stability score up (0–5 rubric below, shared with `test-reliability`).\n\n4. **Disambiguate by region scoping, not layout selectors.** When role+name is ambiguous (two \"Submit\" buttons), narrow with `getByRole('region', { name }).getByRole('button', …)` or `getByRole(...).filter({ hasText })`. Do **not** reach for `:near()` \u002F `:right-of()` — see the Avoid note. A score-3 candidate is one where region scoping has been *applied* and the locator now matches exactly one element.\n\n5. **One PR, grouped by file, with per-change evidence.** Reviewers cannot eyeball 47 selector changes spread across 30 commits. Bundle into one PR, group hunks by test file, attach a confidence score + DOM screenshot per change.\n\n6. **The suite must pass before merge, and dead tests get deleted.** A regenerated selector that doesn't run is a worse version of the original problem; the skill ends with green CI, not a generated diff. And if the refactor removed a feature, prune its tests — do not regenerate selectors for elements that no longer exist.\n\n> **Avoid:** Playwright layout selectors `:near()`, `:right-of()`, `:left-of()`, `:above()`, `:below()` as disambiguators — officially deprecated and \"may be removed,\" because a 1px layout shift changes the match (Playwright docs, 2026). They also contradict the role-first thesis. Use region scoping \u002F `getByRole().filter()` instead.\n\n---\n\n## Workflow\n\nSix phases, each gated by a check before the next.\n\n### Phase 1 — Snapshot the old DOM\n\nYou need a snapshot of every page or component the affected tests touch, in its pre-refactor state. **Sources, in preference order:**\n\n1. **Last green CI trace artifact.** Most teams save Playwright traces on failure (`trace: 'on-first-retry'`). Download the green-run artifact, open a trace with `npx playwright show-trace traces\u002Fcheckout.zip`, select an action, and read the per-action **DOM snapshot panel** for each surface. (The viewer no longer has a \"Copy HTML at this step\" menu item; you read the snapshot panel or, for a programmatic dump, replay with `page.content()` \u002F `ariaSnapshot()`.)\n2. **Storybook at a pre-refactor commit.** `git checkout \u003CPRE_REFACTOR_SHA>`, start Storybook, and dump each story with a tiny `page.content()` \u002F `ariaSnapshot()` script.\n3. **A staging build still on the old version.** Navigate the same flows and snapshot.\n4. **Git history of the components.** Reconstructable but the most expensive — render in isolation.\n\nOutput: `.drift-recovery\u002Fold\u002F\u003Cpage-or-component>.aria.yml` (and `.html` if you also need raw markup) per affected unit.\n\n**Gate:** You can answer \"what did this page look like when the tests last passed?\" from a snapshot file, not from memory.\n\n### Phase 2 — Snapshot the new DOM\n\nRun the same surfaces in the post-refactor build — a Vercel\u002FNetlify preview deploy is ideal, or a local dev server \u002F the PR branch in CI. Wait for hydration (`await page.waitForLoadState('networkidle')`) before snapshotting, or SSR pages give you the pre-hydration tree and you miss client-rendered elements.\n\nOutput: `.drift-recovery\u002Fnew\u002F\u003Cpage-or-component>.aria.yml` matching the old set.\n\n**Gate:** Every old snapshot has a matching new one. If a route 404s in the new build, that flow was deleted — mark its tests for the deletion pile in Phase 6.\n\n### Phase 3 — Identify broken selectors and infer intent\n\nFor each test file, run against the new build with the JSON reporter, then parse it. Capture, per failure: **file, line, old locator string, error type (timeout vs assertion), and inferred intent**. Group the results by test file.\n\n- **Error classification:** a drift failure is `TimeoutError: locator.* exceeded`. Distinguish it from an assertion failure (`expect(...).toBe`) so you don't try to re-select a locator that resolved fine but failed a value check.\n- **Inferred intent is mandatory and not in the reporter.** Read the surrounding test code — what action is taken on the locator, what assertion follows — and store a short intent string (\"submit the order\", \"read the order total\"). The candidate generator keys off intent, so this step is load-bearing, not commentary.\n- **Page route is also not in the reporter.** Map each locator to the snapshot it should resolve against (which `.drift-recovery\u002Fnew\u002F*.aria.yml`) so Phase 4 can load the right new DOM.\n\nThe result is a per-file table:\n\n| Test file | Line | Old locator | Error type | Page route | Inferred intent |\n|---|---|---|---|---|---|\n| `tests\u002Fcheckout.spec.ts` | 42 | `getByTestId('submit-btn')` | timeout | `\u002Fcheckout` | Submit the order |\n| `tests\u002Fcheckout.spec.ts` | 87 | `locator('.summary > h2')` | timeout | `\u002Fcheckout` | Read the order total |\n\nSee `references\u002Frecovery-scripts.md` for `identify-drift.ts`, which produces exactly these rows (with the intent\u002Froute fields populated, not stubbed).\n\n**Gate:** Every broken locator has an inferred intent and a page route. If you cannot infer intent, ask the test author or read the original PR — do not guess.\n\n### Phase 4 — Generate replacement candidates\n\nFor each row, generate candidates against the **new** DOM snapshot and score each on the 0–5 rubric (shared with `test-reliability`). Strategy ladder, best first:\n\n1. **New `data-testid`** added by the refactor team — the most stable choice they made. Score 5.\n2. **`getByRole` + accessible name, unique on the page.** Score 4.\n3. **`getByLabel` for a form field**, when the intent is an input and a label exists. Score 4 (use over a bare role when the field has no name otherwise).\n4. **Role + name, region-scoped to a single match.** If role+name alone returns >1 element, wrap it — `getByRole('region', { name }).getByRole(role, { name })` or `.filter({ hasText })` — and confirm the scoped locator now matches exactly one. Only score 3 **after** scoping makes it unambiguous.\n5. **Visible text only** (`getByText`). Score 2 — fragile to copy changes.\n6. **CSS class on the changed structure.** Score 1 — usually still broken.\n7. **No safe replacement.** Score 0 — flag for human.\n\n| Score | Replacement strategy | Auto-apply? |\n|---|---|---|\n| 5 | New `data-testid` exists | yes |\n| 4 | `getByRole` + accessible name (or `getByLabel`), unique on page | yes |\n| 3 | `getByRole` + name, region-scoped to exactly one match | yes |\n| 2 | Visible-text-only | no |\n| 1 | CSS class on changed structure | no |\n| 0 | No safe replacement found | no — flag for human |\n\nA candidate is **score 3 only if scoping already resolved it to a single element**. A still-ambiguous multi-match (`count > 1`, \"needs scoping\") is not a 3 — it is unfinished, and must not be auto-applied.\n\nOutput: `.drift-recovery\u002Fcandidates.json` with `{ file, line, oldLocator, selector, score, rationale, screenshotPath }` per change. See `references\u002Frecovery-scripts.md` for `generate-candidates.ts`.\n\n**Gate:** Every row has a candidate scored ≥ 3 (and confirmed unique), or is flagged for human review. Score-0\u002F1\u002F2 are never auto-applied.\n\n### Phase 5 — Apply, validate, iterate\n\n1. Apply the score-≥3 replacements to a feature branch. Replace by `(file, line)`, not a content-wide string replace — the reporter's locator string is a *rendered* form (`locator('.summary > h2')`) that rarely matches the source expression verbatim, and a naive `String.replace` hits only the first occurrence and collides on identical locators. Edit the specific line; set `applied: true` on each candidate you actually wrote.\n2. Run the **full affected suite**, not just the previously-failing tests — a new selector can match an unintended element and break a previously-passing test.\n3. Per test: **passed** → keep the replacement. **failed** → revert that one line, mark the test for human review.\n4. Emit a summary: N recovered automatically, M flagged.\n\n**Gate:** Recovered tests pass the suite. Flagged tests are clearly marked, not silently included.\n\n### Phase 6 — Ship the PR\n\nThe PR is the deliverable. **Title:** `chore(tests): selector recovery after \u003Crefactor description>`. **Body** (generated from `candidates.json`, filtering on `applied`):\n\n```markdown\n## Trigger\n\u003CLink to the refactor PR \u002F describe the redesign>\n\n## Summary\n- N test files updated   - M selectors changed\n- K tests deleted (feature removed)   - L tests flagged for manual review\n\n## Per-file changes\n\u003CFor each file: a table of line, old, new, score, screenshot URL>\n\n## Flagged for review\n\u003CTests where no candidate scored >= 3, with the inferred intent>\n\n## How to review\n- Check each screenshot: does `new` point at the element `old` pointed at?\n- For score-3 candidates, verify the region scope is meaningful in the new design.\n- For flagged tests, decide: rewrite, delete, or accept a manual selector update.\n```\n\nAttach screenshots inline via your team's CI artifact URL pattern. See `references\u002Frecovery-scripts.md` for `apply-recovery.ts` (line-anchored) and `build-pr-body.ts`.\n\n**Gate:** PR is reviewable in one sitting. Too large → split by area (one PR per page \u002F component \u002F test directory).\n\n---\n\n## Anti-Patterns\n\n1. **Auto-applying score-0, -1, or -2 candidates.** A score-2 is \"we found *some* element.\" That is gambling, not recovery — and it is the usual cause of a suite-wide stability score *dropping* after a recovery. Auto-apply only score ≥ 3.\n\n2. **Calling an ambiguous multi-match \"score 3.\"** If `getByRole(...)` returns >1 element it is not a 3 until region scoping narrows it to exactly one. Scoring it 3 and auto-applying ships a locator that resolves to the wrong element.\n\n3. **Skipping the screenshots.** A score-4 candidate can still point at the wrong element when the page has two regions with the same role + name. The per-change screenshot is the only check that catches semantic drift; confidence scores alone do not.\n\n4. **Content-wide string replace instead of line-anchored edits.** `content.replace(oldLocator, …)` hits the first occurrence only, collides on duplicate locators, and silently no-ops when the reporter's rendered string differs from the source expression. Edit the specific `(file, line)`.\n\n5. **Trusting auto-extracted line numbers for POM-wrapped locators.** The JSON reporter's `error.location` points at the failing line, which for a Page Object is the helper, not the test. Re-read the locator from the trace action or grep the POM source before applying.\n\n6. **Recovering tests for deleted features.** The refactor may have removed flows. Map deleted routes in Phase 2 and prune those tests — do not regenerate selectors for elements that no longer exist.\n\n7. **Auto-merging the recovery PR in CI.** The PR *is* the artifact; the whole point is a human eyeballing the per-change evidence. Auto-merge once tests pass defeats the purpose — a green suite with a selector pointing at the wrong-but-present element passes and erodes trust. Require a reviewer.\n\n8. **Treating the PR as urgent.** A failed suite feels urgent; a *correctly* recovered one is what matters. Time pressure produces score-2 replacements that quietly degrade the suite.\n\n---\n\n## Failure Modes\n\n| Symptom | Likely cause | Fix or check |\n|---|---|---|\n| `identify-drift.ts` finds 0 failures despite red CI | Suite errored before producing the JSON report, or you parsed the wrong file | `jq '.stats' .drift-recovery\u002Fresults.json`; confirm `--reporter=json` redirected to the file |\n| Extracted line points at a POM file, not the test | Locator is wrapped in a Page Object | Read the locator from the trace action, or grep the POM source for the rendered string |\n| `generate-candidates.ts` reads `undefined` for intent\u002Froute | Phase 3 output missing the inferred-intent \u002F page-route fields | Populate them in Phase 3 — they are not in the reporter; the generator cannot infer them |\n| New-DOM snapshot is missing client-rendered elements | Snapshotted before hydration | Add `await page.waitForLoadState('networkidle')` before `ariaSnapshot()` |\n| Average stability score dropped after recovery | Score-2\u002FCSS candidates auto-applied | Revert candidates with score \u003C 3 in `candidates.json`; only role\u002Flabel\u002Ftestid should land (see scorer below) |\n\n---\n\n## Verification\n\nRun these on the recovery branch before opening the PR, smallest first:\n\n```bash\n# 1. No applied candidate is below the stability floor (machine-checkable proxy for \"ratcheted up\")\nnode references\u002Fscore-candidates.mjs .drift-recovery\u002Fcandidates.json\n# prints average score + count of applied rows with score \u003C 3 — that count MUST be 0\n\n# 2. The recovered suite is green against the new build\nPLAYWRIGHT_TEST_BASE_URL=$PREVIEW_URL npx playwright test --reporter=json \\\n  | jq '.stats.unexpected'        # must be 0 (flagged tests excluded via grep\u002Fskip)\n\n# 3. The PR exists with the evidence body\ngh pr view --json title,body -q '.title'   # contains \"selector recovery\"\n```\n\n`references\u002Fscore-candidates.mjs` reads `candidates.json`, prints the average applied score and the count of `applied && score \u003C 3` rows; a non-zero count means a low-confidence selector leaked in. Step 1 passing + step 2 returning `0` is the proof the recovery worked.\n\n---\n\n## Done When\n\n- Every in-scope test passes on the new build, is flagged for human review with a clear reason, or is deleted because its feature is gone.\n- `references\u002Fscore-candidates.mjs candidates.json` reports **0** applied candidates with score \u003C 3.\n- The PR is open (`gh pr view` succeeds) with per-change evidence: confidence score and screenshot per change, grouped by file.\n- `npx playwright test --reporter=json | jq '.stats.unexpected'` returns `0` on the recovery branch.\n- A short note is added to `.agents\u002Fqa-project-context.md` describing the refactor and any new test patterns introduced.\n\n## Reference Files (in `references\u002F`)\n\n- **recovery-scripts.md** — the full playbook with corrected, runnable scripts: aria-snapshot capture, `identify-drift.ts` (populates intent + route), `generate-candidates.ts` (region-scoping ladder, true score-3), line-anchored `apply-recovery.ts`, and `build-pr-body.ts`. Includes the Cypress-adaptation note.\n- **score-candidates.mjs** — tiny stability scorer; prints the average applied score and the count of applied rows below score 3. Used by Verification and Done When.\n\n## Related Skills\n\n- **test-reliability** — runtime per-test healing. Use for one flaky test, not a refactor-driven mass update. Shares the 0–5 stability rubric.\n- **playwright-automation** — writing new tests from scratch. Use when the refactor removed enough features that tests should be rewritten, not patched.\n- **test-migration** — switching frameworks (Selenium → Playwright). A migration re-records tests; it is not selector drift, even though both touch many tests at once.\n- **visual-testing** — had this run on every PR, the refactor's visual diff would have flagged before merge. Recovery is the fallback when that coverage is missing.\n- **ci-cd-integration** — wires the recovery PR's validation step into CI.\n",{"name":5,"license":15,"metadata":52,"description":7},{"author":53,"version":54,"category":55},"kindlmann","2.0","automation","claude\u002Finstall-gsd-build-foQdV","d86cd127f527124b5fd0fd0bc679a4d79b7d01d7",".agents\u002Fskills\u002Fselector-drift-recovery","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik","https:\u002F\u002Fnorvik-orpin.vercel.app","https:\u002F\u002Fgithub.com\u002Fpattanation","2026-07-17 11:17:04",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add pattanation\u002FNorvik --skill selector-drift-recovery",{"tool":69,"command":70},"bunx","bunx skills add pattanation\u002FNorvik --skill selector-drift-recovery",{"tool":72,"command":73},"pnpm","pnpm dlx skills add pattanation\u002FNorvik --skill selector-drift-recovery","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fselector-drift-recovery\u002FSKILL.md",true,[77,83],{"path":78,"resourceType":79,"resourceTypeDesc":80,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Frecovery-scripts.md","REFERENCE","参考文档",15950,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fselector-drift-recovery\u002Freferences\u002Frecovery-scripts.md",{"path":84,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":87,"sourceHtmlUrl":88},"references\u002Fscore-candidates.mjs","SCRIPT","脚本",1411,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fselector-drift-recovery\u002Freferences\u002Fscore-candidates.mjs",[90,119,145,168,190,205],{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":95,"sourceHtmlUrl":96,"categories":97,"keywords":102,"publishedAt":47},"01M71QDV4DM8T19HEHWATEF1K4","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fwebapp-testing\u002FSKILL.md",[98,99],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[103,41,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118],"webapp","webapp testing","for","applications","local","frontend","debugging","toolkit","logs","browser","with","playwright","web","behavior","ui","screenshots",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":94,"licenseFileUrl":123,"sourceHtmlUrl":124,"categories":125,"keywords":127,"publishedAt":47},"01M71QDH2GGMXGA9105C9W1N7B","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fweb-artifacts-builder\u002FSKILL.md",[126],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[128,129,130,115,131,132,133,105,134,135,108,136,137,138,139,140,141,142,143,144],"builder","artifacts builder","artifacts","web artifacts builder","single-file","css","Tailwind","ui components","single","use","file","components","react","multi","state","routing","state management",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":149,"sourceHtmlUrl":150,"categories":151,"keywords":153,"publishedAt":47},"01M71QD9T2XC3EWMQNN9ACNWQ1","vueuse-functions","Apply VueUse composables where appropriate to build concise, maintainable Vue.js \u002F Nuxt features.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvueuse-functions\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvueuse-functions\u002FSKILL.md",[152],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[154,155,156,157,158,159,160,161,162,163,164,165,166,167],"functions","vueuse","vueuse functions","project","nuxt","apply","github","concise","build","https","features","vue","js","composables",{"skillNo":169,"name":170,"nameZh":6,"description":171,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":172,"sourceHtmlUrl":173,"categories":174,"keywords":177,"publishedAt":47},"01M71QD51HWSGYMCMB7RW7PGK8","vue-testing-best-practices","Use for Vue.js testing. Covers Vitest, Vue Test Utils, component testing, mocking, testing patterns, and Playwright for E2E testing.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-testing-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-testing-best-practices\u002FSKILL.md",[175,176],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[178,41,179,165,180,114,181,182,183,160,184,105,185,186,187,137,188,166,189],"vue testing best practices","testing best practices","best practices","test","testing patterns","component","vitest","mocking","e2e","utils","patterns","e2e testing",{"skillNo":191,"name":192,"nameZh":6,"description":193,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":194,"sourceHtmlUrl":195,"categories":196,"keywords":198,"publishedAt":47},"01M71QD358H2DJ9THJRBP77T3T","vue-router-best-practices","Vue Router 4 patterns, navigation guards, route params, and route-component lifecycle interactions.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-router-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-router-best-practices\u002FSKILL.md",[197],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[199,165,200,180,201,183,202,160,188,203,204],"router","vue router best practices","route","guards","navigation","lifecycle",{"skillNo":206,"name":207,"nameZh":6,"description":208,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":209,"sourceHtmlUrl":210,"categories":211,"keywords":213,"publishedAt":47},"01M71QCYPNWDE2MQZ1TF3ED5S4","vue-best-practices","MUST be used for Vue.js tasks. Strongly recommends Composition API with `\u003Cscript setup>` and TypeScript as the standard approach. Covers Vue 3, SSR, Volar, vue-tsc. Load for any Vue, .vue files, Vue Router, Pinia, or Vite with Vue work. ALWAYS use Composition API unless the project explicitly requires Options API.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-best-practices\u002FSKILL.md",[212],{"categoryNo":100,"categoryName":101,"taxonomyType":21,"taxonomyTypeDesc":22},[214,165,180,215,157,216,217,218,199,219,220,221,222,223,224,105,225,137,226,227],"vue best practices","Composition","files","tasks","api","pinia","work","options","ssr","typescript","as","vite","load","standard"]