Claude Cursor GitHub Copilot Skill

legacy-jquery-to-modern-framework-review

Inventory the hidden behaviors in a legacy jQuery/Backbone-era codebase — implicit global event delegation, direct DOM mutation outside any render cycle, plugin side effects, ad-hoc accessibility shims, and unsanitized HTML string building — that a mechanical framework port would

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download vincentchuwaichow-vanguard-frontier-agentic-skills_frontend_legacy-jquery-to-modern-framework-review-febe32a.zip · 13 KB
Part of vincentchuwaichow/vanguard-frontier-agentic — 293 skills

Install

skills CLI npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/legacy-jquery-to-modern-framework-review
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
Git git clone https://github.com/VincentChuWaiChow/vanguard-frontier-agentic.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole vincentchuwaichow/vanguard-frontier-agentic collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Legacy jQuery to Modern Framework Review

Purpose

A mechanical 1:1 port of jQuery code into a modern component framework routinely loses behavior that was never written down: event delegation bound at document level, DOM mutations performed by third-party plugins outside any framework render cycle, and accessibility behavior (focus trapping, ARIA attribute toggling) implemented ad hoc in a plugin nobody remembers the internals of. A migration plan that skips this inventory ships a component tree that looks equivalent and silently regresses on events, side effects, or accessibility the day it reaches production. This skill exists to make that inventory explicit — component by component, handler by handler — before a migration plan commits to a strangler boundary.

When to use

Use this skill when the user asks to:

  • inventory jQuery/Backbone-era DOM manipulation and event-binding patterns before a framework migration,
  • identify which jQuery plugins have undocumented side effects (DOM mutation, global state, timers) that must be explicitly reproduced in the replacement,
  • find unsanitized HTML string-building (.html(), string concatenation into innerHTML) that a port must not carry forward unchanged — or worse, upgrade into an unguarded dangerouslySetInnerHTML/v-html,
  • check whether legacy widgets provide accessibility behavior (keyboard support, focus management, ARIA toggling) that the replacement component must match before being declared equivalent.

Do not use this skill for:

  • authoring the replacement framework's component code itself — this skill produces the inventory a migration plan consumes, it does not design the target architecture (pair with frontend-migration-modernization-plan or the target framework's architecture-review skill for that),
  • general DOM XSS/CSP review with no legacy-migration angle — use frontend-dom-xss-csp-review for that,
  • reviewing a codebase that has no jQuery/Backbone-era code at all.

Context7 Documentation Protocol

  • Resolve the target framework's Context7 library ID (resolve-library-id) before citing any claim about how the replacement component's API is expected to behave — e.g. React: /reactjs/react.dev; Vue: /websites/vuejs_guide; Angular: /websites/angular_dev. Read package.json first to confirm the actual target framework and version; do not assume one from the ticket title.
  • For every dangerouslySetInnerHTML/v-html-shaped replacement candidate, ground the security claim in Context7 React docs before writing it: dangerouslySetInnerHTML accepts an untrusted string only if the caller has already sanitized it — passing raw legacy .html() input straight through creates the same "Security Hole with dangerouslySetInnerHTML" pattern the official React docs warn about explicitly (a post.content string with an onerror payload rendered unsanitized). Label this claim documentation-based (Context7: /reactjs/react.dev).
  • For claims about refs, useEffect, and "escape hatch" DOM access as the sanctioned place to reproduce legacy imperative DOM code, ground them in the official React docs' "Manipulating the DOM with Refs" material (refs are an escape hatch for stepping outside React; manually manipulating DOM nodes React also renders causes conflicts, e.g. calling ref.current.remove() outside of setState crashes on the next render). Do not invent a different "sanctioned" location for imperative code.
  • This skill does not by itself certify the new implementation is correct — it inventories legacy behavior and flags what the target implementation must account for. Do not recommend a specific target-framework API as the finished solution without verifying the claim against Context7/official docs first.
  • If Context7 is unavailable for the target framework, fall back to the official_docs URLs in this skill's metadata.json and label the claim documentation-based, unverified against current release.

Lean operating rules

  • Treat every $(document).on('click', '.selector', ...)-style delegated handler as a routing/ownership question, not just an inventory line: in the ported code, which component owns this event, does the DOM structure still exist for delegation to make sense, and is event delegation still needed once the framework's synthetic/native event system replaces manual $(document) binding?
  • Treat every .html(), .append(), .prepend(), or .after() call fed by non-literal data (template strings, concatenation, server response, .val()/.text() of another element) as a security finding, not just a style note. Flag it as an unsanitized-injection candidate for the migration plan's risk register, and flag doubly if the proposed replacement is dangerouslySetInnerHTML/v-html with no sanitizer in between.
  • Do not assume a jQuery UI/plugin widget (datepicker, autocomplete, modal, tabs, slider, tooltip) has zero accessibility behavior just because the markup around it looks bare. Check the plugin's own documentation or bundled source for ARIA attribute toggling, role assignment, and keyboard handlers before declaring an accessibility gap — a missing declaration in the call-site markup does not mean the plugin injects nothing at runtime.
  • Do not classify a jQuery plugin as side-effect-free because its public API looks simple. Grep the plugin source itself (not just call sites) for global variable writes, setInterval/setTimeout registration, direct document/window event binding, and DOM nodes created outside the element the plugin was called on — these are exactly the behaviors a component-scoped framework render cycle will not reproduce automatically.
  • Do not recommend a specific target-framework API as "the" replacement without first grounding the claim via the Context7 Documentation Protocol above.
  • Load references/event-delegation-inventory.md only when cataloguing event-binding patterns.
  • Load references/dom-mutation-and-plugin-side-effects.md only when auditing third-party plugin behavior.
  • Load references/legacy-a11y-shim-audit.md only when checking accessibility parity requirements.

References

Load these only when needed:

  • Event delegation inventory — use to catalogue $(document).on(...)-style global delegation, $.fn custom-event patterns, and Backbone view event maps, and map each handler to an owning component in the target architecture.
  • DOM mutation and plugin side effects — use to find third-party jQuery plugins performing direct DOM mutation, timers, global namespace writes, or singleton state outside any render cycle, and to flag unsanitized HTML string-building sites.
  • Legacy accessibility shim audit — use to verify what keyboard/focus/ARIA behavior existing widgets provide before their replacement is declared equivalent.

Response minimum

Return, at minimum:

  • the inventory of delegated event handlers with proposed component ownership in the target framework,
  • the list of plugins/handlers with undocumented side effects requiring explicit reproduction,
  • unsanitized HTML-construction sites flagged as security findings, with an explicit call-out if the proposed replacement is dangerouslySetInnerHTML/v-html without a sanitizer,
  • an accessibility-parity checklist per replaced widget (keyboard support, focus management, ARIA attributes/roles),
  • evidence level per finding (source-code-verified vs. plugin-docs-based vs. inference), and Context7/official-docs grounding for any target-framework API claim.
Files (vanguard-frontier-agentic)
  • references
    • dom-mutation-and-plugin-side-effects.md 7.1 KB
      # DOM Mutation and Plugin Side Effects
      
      Use this reference when auditing third-party jQuery plugins and ad-hoc DOM-manipulation code for behavior that happens outside any framework render cycle — direct DOM mutation, global state writes, timers, and unsanitized HTML construction.
      
      ## What people get wrong
      
      The common bad assumption is:
      
      > "The plugin has a small public API (`$(el).fancyPlugin({ options })`), so its footprint is small too."
      
      That is false for the overwhelming majority of jQuery-era UI plugins. A plugin's public API surface tells you nothing about:
      
      - how many DOM nodes it creates and where it puts them (often *outside* the element it was called on — appended to `body`, injected as siblings, or moved elsewhere in the tree),
      - whether it registers global listeners (`$(window).resize(...)`, `$(document).on('keydown', ...)`) that persist for the lifetime of the page regardless of whether the "widget" is still visible,
      - whether it holds module-level or `$.fancyPlugin.instances`-style singleton state shared across every instantiation on the page,
      - whether it starts timers (`setInterval` for polling/auto-rotation, `setTimeout` for debounce/animation) that are never cleaned up on the jQuery side either, but happen to not matter in a page-reload-based app the way they will matter in a long-lived SPA.
      
      A framework component's render cycle (mount → update → unmount) has no way to automatically clean up any of this. If the plugin's real behavior is not inventoried, the "equivalent" component will leak listeners, leak timers, or silently stop working the first time the framework unmounts and remounts the component (which jQuery-era pages never did — they reloaded instead).
      
      ## Non-negotiable design rules
      
      ### 1. Read the plugin's source, not just its call sites
      
      A call site like `$('.carousel').slick({ autoplay: true })` gives zero information about side effects. Locate the actual plugin source (vendored file, node_modules, or inlined script) and grep it directly for:
      
      - `document.body.append`/`appendChild`, `$('body').append(`, or any DOM insertion target that is not `this`/the plugin's own root element,
      - `setInterval`, `setTimeout` with no matching `clearInterval`/`clearTimeout` in a documented teardown/`destroy` method,
      - `$(window)`, `$(document)` listener registration,
      - module-level `var`/`let` outside any function scope, or properties attached to the jQuery plugin namespace itself (`$.fn.pluginName.defaults`, a shared cache object) — these persist across every instance and every page-lifecycle event.
      
      ### 2. Distinguish "has a destroy/teardown method" from "is actually called"
      
      Many plugins document a `.pluginName('destroy')` API. Grep the *call sites*, not just the plugin source, for whether teardown is ever invoked. A legacy app that never unmounts widgets (because it never removes DOM nodes without a full page reload) commonly never calls teardown — meaning the migration is the first time this code path's absence becomes an observable bug (memory growth, duplicate global listeners after client-side navigation).
      
      ### 3. Treat every `.html()`, `.append()`, `.prepend()`, `.after()`, `.before()`, `.replaceWith()`, or raw `.innerHTML =` assignment fed by non-literal data as a security finding
      
      "Non-literal" means anything built from a variable, template string, concatenation, `.val()`/`.text()` of another element, a server response, or a URL/query-string value — not a fixed string literal written by the developer. For each match:
      
      - record whether the source is attacker-influenceable (see the taint sources in this skill's sibling security-review skills: URL params, API responses rendering third-party/user content, `postMessage`, storage written by another origin),
      - flag explicitly if the *proposed* replacement is `dangerouslySetInnerHTML` (React) or `v-html` (Vue) — per the Context7-grounded React docs, `dangerouslySetInnerHTML` is a documented "Security Hole" when fed untrusted input directly; a framework migration must not be the moment an existing (bad) pattern becomes a *worse* one by skipping the sanitizer entirely,
      - never assume the plugin already sanitizes its input; verify by reading the plugin's actual string-construction code if the finding is high-severity enough to matter (public-facing, user-generated-content-adjacent).
      
      ### 4. Global state and timers are migration blockers, not migration details
      
      If a plugin's side effects are genuinely global (a single page-wide autoplay ticker, a shared modal-stack z-index counter), the target architecture needs an explicit decision about where that state lives (a store, a context, a singleton service) — it cannot be silently absorbed into "just render the component and it'll work," because the framework component model assumes state is either component-local or explicitly lifted, not implicitly global via a shared jQuery-plugin-namespace object.
      
      ## Minimal safe inventory progression
      
      1. Identify every `$(el).pluginName(...)` call site across the codebase and group by plugin.
      2. For each distinct plugin, locate its actual source (not just its call sites) and grep it for the side-effect categories in rule 1.
      3. For each plugin, check whether a teardown/destroy path exists in the source and whether it is ever invoked at any call site.
      4. Separately, grep the whole codebase (not just plugin source) for `.html(`, `.append(`, `.prepend(`, `.after(`, `.before(`, `.replaceWith(`, and `.innerHTML =` and classify each by literal vs. non-literal input.
      5. Cross-reference: does any plugin call site feed plugin options built from non-literal, potentially-attacker-influenceable data (e.g., a plugin's `content` option populated from an API response)? This is a compound finding — flag it distinctly from a plain `.html()` call, since the plugin's internal handling of that option is opaque without reading its source.
      
      ## Verification targets
      
      - For any plugin flagged as holding global/singleton state, confirm by checking whether multiple instantiations on the same page actually interfere with each other in the current app (evidence: a bug report, a workaround comment in the code, or a code path that explicitly guards against double-initialization) versus being a theoretical risk only.
      - For any `.html()`/`.append()` finding proposed for replacement with `dangerouslySetInnerHTML`/`v-html`, confirm whether a sanitizer (DOMPurify or equivalent) is already present anywhere in the dependency tree before assuming one must be added from scratch.
      
      ## When to push back
      
      Push back if the user asks to:
      
      - port a plugin's call site 1:1 into a `useEffect`/lifecycle-hook wrapper without first reading the plugin's own source for global listeners, timers, or singleton state — this reproduces every leak the plugin had, now inside a component that mounts/unmounts far more often than the legacy page ever did,
      - replace `.html()` with `dangerouslySetInnerHTML`/`v-html` as a mechanical find-and-replace with no sanitizer discussion — that is a downgrade, not a port, whenever the source data is non-literal,
      - mark a plugin "no side effects, safe to wrap" based on reading only its options API or its README, without having actually grepped its source.
      
    • event-delegation-inventory.md 6.8 KB
      # Event Delegation Inventory
      
      Use this reference when cataloguing jQuery/Backbone-era event-binding patterns before a framework migration — specifically `$(document).on(...)`-style global delegation, `$.fn` plugin custom events, and Backbone view `events` maps.
      
      ## What people get wrong
      
      The common bad assumption is:
      
      > "Events are just `addEventListener` with extra syntax. I'll find every `.on(` call, rewrite it as an `onClick` prop, and move on."
      
      That is incomplete, and it is the single most common source of silent regressions in jQuery-to-framework ports. jQuery delegation and framework event handlers solve different problems:
      
      - **Delegation exists because elements come and go.** `$(document).on('click', '.tab', handler)` was written that way *because* `.tab` elements are added/removed dynamically (AJAX-loaded content, plugin-rendered markup) and a direct binding would miss elements that did not exist at bind time. A framework component only needs this pattern if it renders variable, dynamically-appearing children with no stable parent to attach the handler to — which is rare inside a component tree where the framework already re-renders and re-attaches handlers on every update.
      - **The delegation selector is not the same as ownership.** `$(document).on('click', '.modal-close', ...)` is bound once, globally, but the handler's *logic* may belong to a specific feature. Naively porting this to a single global `document.addEventListener` in the new app (instead of scoping it to the owning component) reproduces the original code smell instead of fixing it.
      - **Custom/synthetic events are easy to miss.** jQuery plugins frequently `.trigger('customEventName')` and other code listens with `.on('customEventName', ...)`. Grepping only for DOM event names (`click`, `submit`, `change`) misses this entire category of implicit coupling between unrelated-looking modules.
      - **Backbone view `events` maps use a different delegation model** (`{'click .save': 'onSave'}`), scoped to `this.el`, and typically calling `this.render()` internally — the "component" boundary is the Backbone view, not the DOM node the handler is nominally attached to.
      
      ## Non-negotiable design rules
      
      ### 1. Classify every handler by binding scope, not by event name
      
      For each match, record:
      
      - **Global** (`$(document).on(...)`, `$(window).on(...)`, `$('body').on(...)`) — highest risk; likely couples unrelated features.
      - **Container-scoped** (`$('#widget-container').on(...)`) — bound once to a stable ancestor; probably maps to a single component boundary.
      - **Element-direct** (`$('.button').click(...)` or `.on()` with no delegate selector) — bound at render time; will break silently if the plugin re-renders the DOM node without rebinding (a classic jQuery bug this inventory should surface, not silently port forward).
      - **Backbone view `events` map** — scoped to `this.el`; note the view class name and whether `render()` is called inside the handler.
      
      ### 2. Do not assume the delegated selector's specificity survives the port
      
      A delegated selector like `.on('click', '.item.active', ...)` depends on class-toggling logic living somewhere else in the codebase. Trace where `.active` is added/removed before assuming the target framework's conditional rendering (`className` binding, `:class`, `[class.active]`) is an equivalent replacement — confirm the toggle logic itself is inventoried, not just the handler.
      
      ### 3. Treat custom/synthesized events as first-class inventory items
      
      Grep for `.trigger(`, `.triggerHandler(`, and Backbone's `.trigger(` (models/collections/views all mix in Backbone.Events) alongside the corresponding `.on('eventName', ...)` listeners. Record the event name, the emitting module, and every listening module — this is often the only documentation of a cross-module dependency that exists.
      
      ### 4. Distinguish "will the target framework's re-render make this delegation unnecessary" from "must this delegation pattern be explicitly reproduced"
      
      Framework component trees re-attach handlers on every re-render by design (React re-runs the component function; Vue/Angular re-bind via their own reactivity), which naturally replaces *element-direct* handlers that needed delegation only to survive jQuery's manual DOM churn. But a *genuinely* dynamic, framework-external DOM region (e.g., markup injected by a third-party widget the framework does not control) still needs an explicit delegation strategy — do not assume the framework's reactivity makes all delegation moot.
      
      ## Minimal safe inventory progression
      
      1. Grep for `.on(`, `.bind(`, `.delegate(`, `.live(` (legacy jQuery <1.9 API — flag its presence as an age/version signal), `.click(`, `.trigger(`, `.triggerHandler(`, and Backbone `events:` object literals.
      2. For each match, classify by binding scope (see rule 1) and record the file, the selector/event name, and the handler body's actual side effect (state mutation, network call, DOM mutation, navigation).
      3. Group matches by the DOM region or feature they affect, not by file — a single feature's delegation logic is often split across an initializer file and a separate handler file.
      4. For every custom/triggered event, find both the emitter and every listener before marking the entry complete; an event with only an emitter found (no listener located) is an open item, not a non-issue.
      5. Produce the ownership mapping: each inventoried handler maps to a proposed owning component in the target architecture, or is marked "unresolved — requires product/design input" if no natural owner exists.
      
      ## Verification targets
      
      - Confirm handler classification against actual runtime behavior where feasible (e.g., does the container the handler is bound to ever get replaced/re-rendered by innerHTML replacement elsewhere in the codebase — that would mean an element-direct binding silently stops firing, a bug worth flagging even before migration).
      - Cross-check every `.trigger('name', ...)` call against every `.on('name', ...)` listener; an emitter with zero listeners found in-repo may indicate a listener registered dynamically (e.g., via a plugin option callback) — mark as `inference, needs runtime confirmation` rather than silently dropping it.
      
      ## When to push back
      
      Push back if the user asks to:
      
      - port event handlers file-by-file without first producing the ownership/ scope classification — this guarantees global delegation gets flattened into ad hoc per-component listeners that reintroduce the same "who owns this click" ambiguity the review exists to resolve,
      - skip the custom/triggered-event grep because "we only care about DOM events" — cross-module `.trigger()`/`.on()` coupling is frequently the least-visible and highest-risk-to-drop behavior in the entire codebase,
      - treat "the new framework re-renders automatically, so delegation is a non-issue" as true everywhere, without checking whether any DOM region is still externally/plugin-controlled.
      
    • legacy-a11y-shim-audit.md 7 KB
      # Legacy Accessibility Shim Audit
      
      Use this reference when checking whether a legacy jQuery/Backbone-era widget provides accessibility behavior that the replacement component must explicitly reproduce before being declared equivalent.
      
      ## What people get wrong
      
      The common bad assumption is:
      
      > "The markup for this widget has no `role`, no `aria-*` attributes, and no `tabindex` in the HTML template — so it must have no accessibility behavior, and the replacement doesn't need any either."
      
      That is frequently false, and it is the single most common way a framework migration silently regresses keyboard and screen-reader support. Many jQuery UI-era plugins (jQuery UI's own dialog/tabs/autocomplete/datepicker widgets, and third-party clones of them) inject ARIA attributes, manage `tabindex`, and bind keyboard handlers **at runtime**, via JavaScript, into the DOM the plugin controls — none of it visible in the static HTML template or component markup a reviewer would normally read. Auditing only the markup and concluding "no accessibility behavior found" is a false negative, not a confirmed gap.
      
      Conversely, the opposite mistake also happens: assuming a plugin "must" handle accessibility because it is a mature, popular widget — some plugins (and especially most custom/homegrown ones) genuinely do nothing beyond visual behavior, and the accessibility gap being ported forward is real and already present, not a migration regression.
      
      Both mistakes are resolved the same way: verify, do not assume, in either direction.
      
      ## Officially grounded shape (what the ARIA APG actually specifies)
      
      The W3C ARIA Authoring Practices Guide (APG) documents, per widget pattern (dialog, tabs, combobox/autocomplete, disclosure, menu, slider, etc.), the specific roles, states, properties, and keyboard interactions an accessible implementation of that pattern requires — e.g., a tabs widget needs `role="tablist"`/`role="tab"`/`role="tabpanel"`, `aria-selected`, and arrow-key navigation between tabs with roving `tabindex`; a modal dialog needs `role="dialog"`, `aria-modal="true"`, focus moved into the dialog on open, focus trapped within it, and focus returned to the triggering element on close. Use the APG pattern for the specific widget type as the checklist for what the *replacement* component must implement — regardless of what the legacy plugin did or did not do.
      
      ## Non-negotiable design rules
      
      ### 1. Check the plugin's actual runtime output, not the call-site markup
      
      For jQuery UI and comparable plugins, the accessibility-relevant code lives in the plugin source, in functions that run on initialization and on state change (e.g., open/close, select/deselect). Grep the plugin source for `.attr('role'`, `.attr('aria-`, `.attr('tabindex'`, `setAttribute('aria-`, and keydown/keyup handlers that check `event.which`/`event.key` against arrow keys, `Escape`, `Enter`, `Space`, `Tab`. Their presence means the plugin manages this at runtime; their absence (confirmed by actually reading the source, not by absence in the call-site HTML) means it does not.
      
      ### 2. Separately verify focus management, which is easy to miss even when ARIA attributes are present
      
      A widget can have textbook-correct `role`/`aria-*` attributes and still fail if focus is never moved: a modal that sets `role="dialog"` but never calls `.focus()` on an element inside it when opened, or never restores focus to the trigger on close, is not accessible despite "having ARIA." Grep specifically for `.focus()` calls (or their absence) at the widget's open/close or activate/deactivate transitions, independent of the ARIA-attribute check in rule 1.
      
      ### 3. Map each finding to the specific APG pattern for that widget type, not a generic "is it accessible" judgment
      
      "Accessible" is not a yes/no property; it is per-interaction-pattern. A datepicker's requirements (grid navigation, `aria-live` announcement of the selected date) are different from a dropdown menu's (`aria-expanded`, `aria-haspopup`, arrow-key/Escape handling) or a tooltip's (`aria-describedby`, hover *and* focus triggering, Escape dismissal). Identify which APG pattern the widget corresponds to before building the parity checklist — do not reuse one pattern's checklist for a structurally different widget.
      
      ### 4. Treat "no accessibility behavior found after reading the source" as a real, reportable gap — not a reason to skip the checklist
      
      If the plugin genuinely implements nothing (common for homegrown carousels, custom tooltips, and simple show/hide toggles built directly on top of jQuery with no ARIA/keyboard code at all), report this explicitly as a pre-existing gap the migration inherits, and still produce the APG-pattern-based parity checklist so the replacement component can *close* the gap rather than silently reproduce it. Do not conflate "the legacy version was already inaccessible" with "so the new version doesn't need to be."
      
      ## Minimal safe audit progression
      
      1. Identify each interactive widget being migrated and classify it against the closest W3C ARIA APG pattern (dialog, tabs, tablist, menu, menubar, combobox, listbox, slider, tooltip, disclosure, accordion, carousel, etc.).
      2. For each widget, locate the actual plugin/implementation source and grep for ARIA attribute writes, `tabindex` management, and keyboard event handlers (rule 1).
      3. Separately check focus-management calls at the widget's key state transitions (rule 2).
      4. Build a per-widget parity checklist directly from the matched APG pattern's required roles/states/properties/keyboard interactions (rule 3), marking each item as "present in legacy (verified)", "absent in legacy (confirmed gap)", or "inference — plugin source not fully readable, needs runtime confirmation".
      5. Flag any widget where the legacy implementation provides real accessibility behavior that the currently-planned replacement component does not yet account for — this is the actual migration risk this reference exists to catch.
      
      ## Verification targets
      
      - Where feasible, confirm the plugin's runtime-injected ARIA/keyboard behavior against the plugin's own official documentation or changelog (version-specific — accessibility support has been added/changed across major versions of common plugins) rather than relying solely on reading one version of vendored source.
      - For focus-trap claims, confirm whether the trap covers `Tab` cycling in both directions (forward from the last focusable element wraps to the first, `Shift+Tab` from the first wraps to the last) — a common incomplete implementation traps only one direction.
      
      ## When to push back
      
      Push back if the user asks to:
      
      - skip the accessibility audit for a widget because "the markup has no ARIA attributes so there's nothing to check" — this is exactly the false-negative pattern this reference exists to prevent for runtime-injected behavior,
      - declare a replacement component "equivalent" based on visual/behavioral parity alone, with no APG-pattern-based keyboard/focus/ARIA checklist,
      - treat a confirmed pre-existing accessibility gap in the legacy widget as acceptable to carry forward silently without at least flagging it as a known, inherited gap in the migration output.
      
  • metadata.json 1.4 KB
    {
      "id": "legacy-jquery-to-modern-framework-review",
      "name": "Legacy jQuery to Modern Framework Review",
      "type": "skill",
      "provider": "frontend",
      "harnesses": [
        "claude-code",
        "cursor",
        "codex",
        "gemini",
        "kiro",
        "other"
      ],
      "summary": "Reviews a legacy jQuery/Backbone-era codebase for the specific hidden behaviors (implicit global event delegation, direct DOM mutation outside any render cycle, undocumented plugin side effects, ad-hoc accessibility shims) that a mechanical framework port will silently drop, producing an inventory that a migration plan can actually rely on.",
      "source_type": "original",
      "official_docs": [
        "https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener",
        "https://api.jquery.com/category/events/event-handler-attachment/",
        "https://www.w3.org/WAI/ARIA/apg/",
        "https://react.dev/reference/rules"
      ],
      "security_notes": "Legacy jQuery plugins frequently construct HTML via string concatenation and .html(); flag every such call as a potential unsanitized-injection point that a naive port might carry forward unchanged, or worse, replace with dangerouslySetInnerHTML/v-html without adding sanitization. Static-review-only skill: Read/Grep/Glob, no Bash execution, no network egress, no code mutation.",
      "last_verified": "2026-07-02",
      "path": "skills/frontend/legacy-jquery-to-modern-framework-review",
      "author": "github: VincentChuWaiChow",
      "version": "0.1.0"
    }
    
  • SKILL.md 8 KB
    ---
    name: legacy-jquery-to-modern-framework-review
    description: Inventory the hidden behaviors in a legacy jQuery/Backbone-era codebase — implicit global event delegation, direct DOM mutation outside any render cycle, plugin side effects, ad-hoc accessibility shims, and unsanitized HTML string building — that a mechanical framework port would silently drop or need to explicitly reproduce.
    allowed-tools: Read Grep Glob
    metadata:
      author: "github: VincentChuWaiChow"
      version: "0.1.0"
      updated: "2026-07-02"
      category: architecture
    ---
    
    # Legacy jQuery to Modern Framework Review
    
    ## Purpose
    
    A mechanical 1:1 port of jQuery code into a modern component framework routinely loses behavior that was never written down: event delegation bound at `document` level, DOM mutations performed by third-party plugins outside any framework render cycle, and accessibility behavior (focus trapping, ARIA attribute toggling) implemented ad hoc in a plugin nobody remembers the internals of. A migration plan that skips this inventory ships a component tree that *looks* equivalent and silently regresses on events, side effects, or accessibility the day it reaches production. This skill exists to make that inventory explicit — component by component, handler by handler — before a migration plan commits to a strangler boundary.
    
    ## When to use
    
    Use this skill when the user asks to:
    
    - inventory jQuery/Backbone-era DOM manipulation and event-binding patterns before a framework migration,
    - identify which jQuery plugins have undocumented side effects (DOM mutation, global state, timers) that must be explicitly reproduced in the replacement,
    - find unsanitized HTML string-building (`.html()`, string concatenation into `innerHTML`) that a port must not carry forward unchanged — or worse, upgrade into an unguarded `dangerouslySetInnerHTML`/`v-html`,
    - check whether legacy widgets provide accessibility behavior (keyboard support, focus management, ARIA toggling) that the replacement component must match before being declared equivalent.
    
    Do not use this skill for:
    
    - authoring the replacement framework's component code itself — this skill produces the inventory a migration plan consumes, it does not design the target architecture (pair with `frontend-migration-modernization-plan` or the target framework's architecture-review skill for that),
    - general DOM XSS/CSP review with no legacy-migration angle — use `frontend-dom-xss-csp-review` for that,
    - reviewing a codebase that has no jQuery/Backbone-era code at all.
    
    ## Context7 Documentation Protocol
    
    - Resolve the target framework's Context7 library ID (`resolve-library-id`) before citing any claim about how the replacement component's API is expected to behave — e.g. React: `/reactjs/react.dev`; Vue: `/websites/vuejs_guide`; Angular: `/websites/angular_dev`. Read `package.json` first to confirm the actual target framework and version; do not assume one from the ticket title.
    - For every `dangerouslySetInnerHTML`/`v-html`-shaped replacement candidate, ground the security claim in Context7 React docs before writing it: `dangerouslySetInnerHTML` accepts an untrusted string only if the caller has already sanitized it — passing raw legacy `.html()` input straight through creates the same "Security Hole with dangerouslySetInnerHTML" pattern the official React docs warn about explicitly (a `post.content` string with an `onerror` payload rendered unsanitized). Label this claim `documentation-based (Context7: /reactjs/react.dev)`.
    - For claims about refs, `useEffect`, and "escape hatch" DOM access as the sanctioned place to reproduce legacy imperative DOM code, ground them in the official React docs' "Manipulating the DOM with Refs" material (refs are an escape hatch for stepping outside React; manually manipulating DOM nodes React also renders causes conflicts, e.g. calling `ref.current.remove()` outside of `setState` crashes on the next render). Do not invent a different "sanctioned" location for imperative code.
    - This skill does not by itself certify the new implementation is correct — it inventories legacy behavior and flags what the target implementation must account for. Do not recommend a specific target-framework API as the finished solution without verifying the claim against Context7/official docs first.
    - If Context7 is unavailable for the target framework, fall back to the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based, unverified against current release`.
    
    ## Lean operating rules
    
    - Treat every `$(document).on('click', '.selector', ...)`-style delegated handler as a routing/ownership question, not just an inventory line: in the ported code, which component owns this event, does the DOM structure still exist for delegation to make sense, and is event delegation still needed once the framework's synthetic/native event system replaces manual `$(document)` binding?
    - Treat every `.html()`, `.append()`, `.prepend()`, or `.after()` call fed by non-literal data (template strings, concatenation, server response, `.val()`/`.text()` of another element) as a security finding, not just a style note. Flag it as an unsanitized-injection candidate for the migration plan's risk register, and flag doubly if the proposed replacement is `dangerouslySetInnerHTML`/`v-html` with no sanitizer in between.
    - Do not assume a jQuery UI/plugin widget (datepicker, autocomplete, modal, tabs, slider, tooltip) has zero accessibility behavior just because the markup around it looks bare. Check the plugin's own documentation or bundled source for ARIA attribute toggling, `role` assignment, and keyboard handlers before declaring an accessibility gap — a missing declaration in the call-site markup does not mean the plugin injects nothing at runtime.
    - Do not classify a jQuery plugin as side-effect-free because its public API looks simple. Grep the plugin source itself (not just call sites) for global variable writes, `setInterval`/`setTimeout` registration, direct `document`/`window` event binding, and DOM nodes created outside the element the plugin was called on — these are exactly the behaviors a component-scoped framework render cycle will not reproduce automatically.
    - Do not recommend a specific target-framework API as "the" replacement without first grounding the claim via the Context7 Documentation Protocol above.
    - Load `references/event-delegation-inventory.md` only when cataloguing event-binding patterns.
    - Load `references/dom-mutation-and-plugin-side-effects.md` only when auditing third-party plugin behavior.
    - Load `references/legacy-a11y-shim-audit.md` only when checking accessibility parity requirements.
    
    ## References
    
    Load these only when needed:
    
    - [Event delegation inventory](references/event-delegation-inventory.md) — use to catalogue `$(document).on(...)`-style global delegation, `$.fn` custom-event patterns, and Backbone view event maps, and map each handler to an owning component in the target architecture.
    - [DOM mutation and plugin side effects](references/dom-mutation-and-plugin-side-effects.md) — use to find third-party jQuery plugins performing direct DOM mutation, timers, global namespace writes, or singleton state outside any render cycle, and to flag unsanitized HTML string-building sites.
    - [Legacy accessibility shim audit](references/legacy-a11y-shim-audit.md) — use to verify what keyboard/focus/ARIA behavior existing widgets provide before their replacement is declared equivalent.
    
    ## Response minimum
    
    Return, at minimum:
    
    - the inventory of delegated event handlers with proposed component ownership in the target framework,
    - the list of plugins/handlers with undocumented side effects requiring explicit reproduction,
    - unsanitized HTML-construction sites flagged as security findings, with an explicit call-out if the proposed replacement is `dangerouslySetInnerHTML`/`v-html` without a sanitizer,
    - an accessibility-parity checklist per replaced widget (keyboard support, focus management, ARIA attributes/roles),
    - evidence level per finding (source-code-verified vs. plugin-docs-based vs. inference), and Context7/official-docs grounding for any target-framework API claim.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related