vue-composition-api-architecture-review
Statically review Vue 3 Composition API code — composable extraction quality, reactivity-boundary correctness (ref/reactive/computed usage, destructuring-loses-reactivity pitfalls), and script-setup component organization — against Vue's own documented composable conventions and
Install
npx skills add https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/tree/master/skills/frontend/vue-composition-api-architecture-review
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install vincentchuwaichow-vanguard-frontier-agentic@llmmart
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
Vue Composition API Architecture Review
Purpose
Review Vue 3 composables and <script setup> components for the specific defect classes Vue's own documentation calls out — reactivity lost through destructuring, lifecycle hooks registered outside the synchronous setup window, impure computed() getters, and script-setup components that mix data-fetching, business logic, and presentation with no composable extraction — without re-litigating component prop design, template markup, styling, or SSR/hydration concerns in every response. This skill exists so those adjacent concerns stay out of scope and the review stays focused on the documented reactivity-boundary and composable-extraction catalog.
When to use
Use this skill when the user asks to:
- review a new or refactored composable (a
use*-prefixed function) before merge, - diagnose a report that "reactive state doesn't update in the UI" after destructuring a composable's return value,
- review a
<script setup>component for organization and whether logic should be extracted into a composable, - decide whether a given
computed()or lifecycle-hook usage is correct.
Do not use this skill for:
- Options API-only codebases with no Composition API usage and no stated migration plan — there is no reactivity-boundary or composable-extraction surface to review,
- SSR-specific security concerns (session/auth-token handling inside composables, hydration mismatches) — hand off to
vue-ssr-security-review, - a bug that requires live reproduction (Vue Devtools reactivity inspection, browser profiling) to confirm — static analysis can identify the missing
toRefs()/guard, not prove which specific runtime instance broke.
Context7 Documentation Protocol
- Resolve the Vue library ID with
resolve-library-id(matched result:/vuejs/vue) before labeling any specific pattern as a reactivity-loss bug or a composable-convention violation. /vuejs/vueis Vue's core source-and-test repository, not the prose docs site. Usequery-docsagainst it to corroborate exact runtime behavior (e.g., thecomputed({ get, set })writable-computed shape,reactive()/ref()tracking mechanics) from source and unit tests. It does not reliably surface the narrative guidance in the Composables and Reactivity Fundamentals guides — for that guidance, use theofficial_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based.- Before asserting a specific destructuring pattern loses reactivity, confirm which construct is being destructured: destructuring a
reactive()-returned proxy loses reactivity on primitive-valued properties; destructuring aref()or atoRefs()-wrapped object does not, because each extracted value is itself a ref. Do not apply a blanket "destructuring is unsafe" rule — check the return type first. - Read
package.jsonfirst to confirm Vue 3.x is in use; Composition API and its documented composable conventions (including<script setup>) are Vue 3 features, not Vue 2. - If Context7 is unavailable, fall back to the
official_docsURLs in this skill'smetadata.jsonand label the claimdocumentation-based, unverified against current release.
Lean operating rules
- For every composable in scope, first determine whether its return value is intended to be destructured by consumers. If yes, verify it returns individual refs or a
toRefs()-wrapped reactive object — a plainreactive()object returned for destructuring is the documented reactivity-loss pitfall, not a style preference. - For every composable, verify the
use*naming convention and verify that lifecycle hooks (onMounted,onUnmounted,watch, etc.) and other composables are called synchronously during the composable's/component's initialsetup()/<script setup>execution — not inside an async callback,await-continuation, conditional, loop, or event handler registered after setup has returned. Vue's synchronous-registration requirement is a hard rule, not a lint nicety: a hook registered after anawaitsilently fails to attach to the correct component instance. - For every
computed()in scope, verify the getter is a pure read with no side effects (no state mutation, no async calls, no logging with side effects). A writable computed (computed({ get, set })) is the documented pattern for two-way-bindable derived state — do not flag itssetfunction as an impurity; that is its intended role. - Review
<script setup>components for responsibility mixing: data-fetching, validation/business logic, and presentation-only state all inline with no composable extraction. Flag extraction candidates only when the mixed logic is non-trivial and would reduce duplication or improve testability if extracted — do not flag a component for using one or two localref()s that have no reuse potential. - Do not fabricate a reactivity-loss finding without showing the exact destructuring line and the specific property that would stop updating. A finding that only says "this might lose reactivity" without the concrete assignment is not a valid finding.
- Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only).
- Load only the reference needed for the concern in scope.
References
Load these only when needed:
- Review workflow and findings contract — use for the step-by-step review procedure, the reactivity/composable decision tree, and the required output shape.
- Reactivity boundaries — load only when reviewing
ref()/reactive()/computed()usage, a suspected destructuring-loses-reactivity bug, ortoRef()/toRefs()/unref()interop code. - Composable and script-setup conventions — load only when reviewing composable naming/structure, lifecycle-hook registration timing, or
<script setup>responsibility mixing and extraction candidates.
Response minimum
Return, at minimum:
- the composable(s) and/or component(s), files, and specific call sites in scope,
- ranked findings with file:line evidence, defect category (reactivity-loss, lifecycle-timing, computed-impurity, or extraction candidate), and a concrete fix sketch matching the docs' recommended pattern,
- for every reactivity-loss finding, the exact destructuring line and the property that would stop updating,
- evidence level per finding (
repo evidence,documentation-based, orinference), - verdict (approve / approve-with-notes / block),
- open questions or scope the review could not cover (e.g., "requires Vue Devtools reactivity inspection to confirm at runtime").
Files (vanguard-frontier-agentic)
-
references
-
composable-and-script-setup-conventions.md 6.4 KB
# Composable and Script-Setup Conventions Use this reference only when reviewing composable naming/structure, lifecycle-hook registration timing, or `<script setup>` responsibility mixing and extraction candidates. ## What people get wrong The common bad assumption is: > "As long as the code works when I test it manually, where I call `onMounted()` or another composable inside a composable doesn't matter." That is wrong. Vue's Composition API relies on an internal "current active instance" pointer that is only valid during the synchronous execution of `setup()` (or the top-level, non-awaited body of `<script setup>`). Lifecycle-hook registration functions (`onMounted`, `onUnmounted`, `onUpdated`, etc.) and dependency-injection functions (`provide`, `inject`) read that pointer at call time. If the call happens after an `await`, inside a `setTimeout` callback, inside a conditional branch that runs later, or inside an event-handler function that executes after setup has already returned, the active-instance pointer is no longer set (or points at the wrong instance) and the call either silently no-ops or attaches to the wrong component. This frequently "works" in manual testing because the developer doesn't notice the hook never actually fired, and only surfaces as a subtle bug (a cleanup that never runs, a mount hook that fires late or never) later. ## Officially grounded conventions - **Naming:** composable functions are named with a `use` prefix (`useMouse`, `useFetch`, `useCounter`) by convention — this signals to readers and tooling (including ESLint plugin rules for Composition API) that the function may call other composition APIs (refs, lifecycle hooks, provide/inject) and has rules-of-hooks-like constraints. - **Input/output convention:** composables typically accept plain values, refs, or getters as input (using `unref()`/`toValue()`-style normalization internally) and return an object of refs (or individually-returned refs) so that consumers can destructure safely — see `references/reactivity-boundaries.md` for the mechanics. - **Synchronous-registration rule:** lifecycle hooks and `provide`/`inject` must be called synchronously within the composable's/component's initial setup execution. Async work belongs *inside* the hook's callback body (e.g., `onMounted(async () => { await fetchData() })` is correct; `await fetchData(); onMounted(() => {})` is not, because the `onMounted` call itself has been pushed past the synchronous setup window). - **`<script setup>` is sugar over `setup()`:** every rule that applies to composable calls inside `setup()` applies identically inside the top-level (non-nested, non-async-continuation) body of a `<script setup>` block. ## Non-negotiable design rules 1. **Every lifecycle-hook call and every nested composable call must be reachable synchronously from the top of `setup()`/`<script setup>` execution.** No exceptions for "it's inside a function that always runs during setup anyway" — if that function is itself invoked asynchronously or conditionally, the guarantee breaks. Trace the actual call chain; do not assume synchronicity from proximity in the source file. 2. **A composable's `use*` name must reflect that it may call other Composition APIs.** A plain utility function (no refs, no lifecycle hooks, no other composable calls) does not need the `use` prefix and naming it that way is misleading, not a review-blocking defect — note it, do not escalate it. 3. **Async initialization belongs inside the async operation's own effect (a lifecycle hook body, a `watch`/`watchEffect` callback, or an event handler), never mixed into the composable's own top-level synchronous body in a way that races with hook registration.** If a composable needs to both register a lifecycle hook and kick off an async fetch, register the hook first (synchronously), and start the fetch inside (or after) that synchronous registration completes — not interleaved with it in a way that risks the hook call landing after an early `return`/`await`. ## `<script setup>` organization review Flag a component for composable extraction when it mixes **three or more** of the following concerns inline, with no existing extraction, **and** the mixed logic has genuine reuse or testability value (not just "this file is long"): - data-fetching (an inline `fetch`/HTTP client call plus loading/error state), - validation or business-rule logic (non-trivial conditional/derived logic beyond simple template formatting), - presentation-only local state (toggle flags, form-field bindings) — this one alone is normal and not a concern by itself, - non-trivial event-handling logic (more than a one-line dispatch to a store/composable). When flagging, name the concrete extraction: what the new composable should own (e.g., "extract the fetch + loading/error state into `useOrderHistory(customerId)`"), not a vague "consider extracting some logic." Do not flag a component that has one or two local `ref()`s and a single `fetch` call with no independent business logic — that is normal `<script setup>` usage, not a mixed-concern smell. ## Verification targets When repo evidence is available, verify each finding against: - the actual call stack leading to a hook registration — is `onMounted(...)` reachable synchronously from the top of `setup()`, or is it inside a `.then()`, an `async function` body before any `await`... after any `await`, a `v-if`-guarded code path, or a callback passed to `setTimeout`/an event listener? - whether the same fetch/business logic already appears in another component in the codebase (strengthens an extraction-candidate finding — it demonstrates real duplication, not just theoretical reuse potential). ## When to push back Push back if the user asks to: - "just wrap the whole thing in `nextTick().then()`" to make an async-timing bug with a lifecycle hook go away — that changes when the code runs but does not fix the underlying synchronous-registration violation; the hook registration itself must move, not the work around it, - extract every local `ref()` in a component into a composable regardless of reuse potential — that adds indirection without reducing duplication or improving testability, which contradicts the actual goal of composable extraction, - rename a plain non-reactive utility function to start with `use` "for consistency" — the `use` prefix is a signal about Composition API usage, not a general naming convention, and misapplying it misleads future readers about the function's constraints. -
reactivity-boundaries.md 6.4 KB
# Reactivity Boundaries: ref, reactive, computed, and Interop Use this reference only when reviewing `ref()`/`reactive()`/`computed()` usage, a suspected destructuring-loses-reactivity bug, or `toRef()`/`toRefs()`/`unref()` interop code. ## What people get wrong The common bad assumption is: > "`reactive()` and `ref()` are interchangeable; I can destructure either one and the result stays reactive." That is wrong for `reactive()`. Vue's `reactive()` returns a Proxy that tracks property access *through the proxy itself*. Destructuring a primitive-valued property off that proxy — `const { count } = state` — copies the current primitive value out; the local `count` variable is a plain number/string with no connection back to `state.count`. Further mutations to `state.count` will not update `count`, and Vue's own docs document this as the reason `reactive()`'s usefulness is "limited" for values that need to be passed around or destructured. `ref()` does not have this problem: a `ref` is an object with a `.value` property, so extracting it (or wrapping a `reactive()` object's properties in `toRefs()` first) preserves the reactive connection because you are copying a reference to the ref object, not a snapshot of its value. ## Officially grounded shape - `ref(initialValue)` returns a ref object with a `.value` accessor; reading/writing `.value` is tracked/triggers updates. In `<script setup>` and templates, refs are auto-unwrapped (no `.value` needed in the template). - `reactive(obj)` returns a Proxy wrapping `obj`; property access/mutation through the proxy is tracked. Destructuring a primitive property off the proxy loses the reactive connection for that property. Destructuring a nested object/array property does *not* lose reactivity for further mutations on that nested value, because the extracted reference still points at a proxied (or proxy-wrappable) object — the pitfall is specifically about primitive values. - `toRefs(reactiveObj)` converts every property of a reactive object into an individual ref, returning a plain object of refs. Destructuring the result of `toRefs()` is safe — each destructured value is a ref, not a primitive snapshot. - `toRef(reactiveObj, key)` creates a single ref synced to one property of a reactive object (or normalizes a plain value/getter into a ref-like object) — the documented escape hatch for passing one reactive property into a function that expects a ref, without wrapping the whole object. - `unref(refOrValue)` returns `.value` if the argument is a ref, or the argument itself otherwise — the documented escape hatch for composable code that accepts either refs or plain values as arguments. - `computed(getterOrOptions)` returns a read-only ref by default when given a getter function. Given `{ get, set }`, it returns a writable ref-like computed — the documented pattern for two-way-bindable derived state (e.g., a computed backing a form field that needs to write back to a different source property). ## Non-negotiable design rules 1. **A composable that expects its return value to be destructured by consumers must return refs, not a plain destructurable `reactive()` object.** Return individual refs, or build internal state with `reactive()` and return `toRefs(state)` at the composable's boundary. This is the single most common Composition API defect class and the one most likely to produce a "state doesn't update in the UI" bug report that is expensive to trace back to its source. 2. **`reactive()` is correct, not wrong, when the composable's own internal state is never destructured** — only accessed via dot-notation (`state.count`) inside the composable itself, or passed whole to a template/child without destructuring. Do not flag every `reactive()` call site as a defect; check whether the specific value is actually destructured downstream first. 3. **`computed()` getters must be pure.** No state mutation, no async calls, no network writes inside a getter — Vue may re-run a computed's getter an unpredictable number of times as part of dependency tracking, so any side effect there is nondeterministic and can produce inconsistent state or infinite recomputation loops. A `computed({ get, set })` writable computed's `set` function is not subject to this rule — writing back to source state is its documented purpose. 4. **`toRef()`/`unref()` are documented interop escape hatches, not bugs.** Do not flag their presence as a finding by default — verify instead that they are being used for their documented purpose (normalizing composable arguments, exposing a single reactive property to a child) rather than papering over a reactivity-loss bug found elsewhere. 5. **Shallow variants (`shallowRef`, `shallowReactive`) intentionally do not track nested mutations.** Do not flag a nested-property mutation as "not reactive" without first checking whether the top-level ref/reactive was declared shallow on purpose (e.g., for a large object where deep reactivity is a documented performance cost the team opted out of). ## Verification targets When repo evidence is available, verify each finding against: - the composable's actual return statement — is it `return { ...state }` (reactive spread, still loses reactivity on destructure), `return toRefs(state)` (safe), or `return { countRef, doubledRef }` (safe, individual refs)? - the consumer call site — is it `const { count } = useCounter()` (destructure — check the composable's return shape) or `const counter = useCounter(); counter.count` (property access — reactivity-loss risk does not apply regardless of return shape)? - whether a flagged `computed()` side effect is actually reachable during normal getter evaluation, or only inside the `set` function of a writable computed (not a finding). ## When to push back Push back if the user asks to: - convert a composable's return value from `reactive()`/`toRefs()` to a plain destructured object "for cleaner syntax" — that reintroduces the exact reactivity-loss pitfall this reference exists to catch, - add a `watch()` inside a `computed()` getter to "fix" an impurity finding — that does not make the getter pure, it adds a second reactive side channel; the fix is to move the side effect out of the computed entirely, - flag `shallowRef`/`shallowReactive` usage as a bug without first confirming whether the shallow choice was intentional (e.g., checking for an accompanying comment, a large-list-performance context, or explicit `triggerRef()` calls that indicate deliberate manual reactivity control). -
workflow-and-output.md 6 KB
# Review Workflow and Findings Contract Use this reference for the step-by-step review procedure and the required output shape. Load the other two references only for the specific concern the composable or component under review actually raises. ## Prerequisites - Confirm Vue 3.x is in use (`package.json` — `vue: ^3.x`). Composition API, `<script setup>`, and the composable conventions this skill reviews against are Vue 3 features; a Vue 2 codebase with no stated migration plan is out of scope. - Identify every composable (`use*`-prefixed function) in the diff or review scope, and every component file that consumes one. ## Workflow 1. **Enumerate composables in scope.** For each, read its full body and its return statement. 2. **Classify the return shape.** Does it return a plain object built from `reactive()`, individual `ref()`s, a `toRefs()`-wrapped reactive object, or a mix? This classification drives the reactivity-boundary review — see `references/reactivity-boundaries.md`. 3. **Check consumer call sites.** For each place a composable's return value is consumed, check whether the consumer destructures it. Cross-reference the destructuring pattern against the return-shape classification from step 2. 4. **Check lifecycle-hook and nested-composable call timing.** Verify every `onMounted`/`onUnmounted`/`watch`/`provide`/nested composable call happens synchronously in the initial execution of `setup()` or the `<script setup>` block — not after an `await`, not inside a conditional/loop, not inside an event handler defined during setup and invoked later. 5. **Check `computed()` purity.** Verify every `computed()` getter is a pure read. If a `computed({ get, set })` writable-computed pair is present, verify the `set` function's role (writing back to source state) is not mistaken for an impurity. 6. **Review `<script setup>` organization.** For each component, count distinct concerns handled inline (data-fetching, validation, business-rule logic, presentation-only local state, event handling). Flag extraction candidates per the decision tree below. 7. **Produce ranked findings** using the output contract below. ## Decision tree - Composable returns a plain `reactive()` object **and** is destructured by a consumer → **HIGH** finding (documented reactivity-loss pitfall). Fix: return `toRefs(state)` or return individual refs from the composable instead of a bare reactive object. - Composable calls a lifecycle hook (`onMounted`, etc.) inside an async function, after an `await`, inside a conditional, or inside a callback registered for later invocation → **HIGH** finding (violates Vue's synchronous-registration requirement — the hook silently fails to bind to the intended component instance). Fix: call the hook synchronously in the composable's/component's top-level setup execution; move async work inside the hook's own callback body instead. - `<script setup>` component mixes 3+ distinct concerns (e.g., fetch + validation + layout + event handling) inline with no composable extraction, and the mixed logic has genuine reuse or testability value → **MEDIUM** finding, recommend composable extraction with a concrete split (name the composable, name what it should own). - `computed()` getter performs a side effect (mutates state, fires a network call, logs with side effects) → **MEDIUM-to-HIGH** finding depending on blast radius (see escalation note below). Fix: move the side effect into a `watch`/`watchEffect` or an explicit method; keep the getter pure. - Composable returns a plain `reactive()` object and is **not** destructured (consumed only via property access, e.g., `state.count`) → not a finding. `reactive()` is correct and idiomatic when consumers do not destructure. - `toRef()`/`unref()` used to interop between a prop and a local composable, or to normalize a `ref | value` argument → not a finding; these are the documented escape hatches, not bugs. ## Severity note on computed side effects Treat an impure `computed()` getter as **HIGH** severity when the side effect is a network write, a mutation of state outside the computed's own dependency graph, or a mutation that can re-trigger the same computed's re-evaluation (infinite-loop risk). Otherwise (a benign console log with no state mutation), **MEDIUM** is appropriate — still a documented anti-pattern, since `computed()` getters are expected to be re-run an unpredictable number of times by Vue's reactivity system, but not a data-integrity risk. ## Output contract Every response from this skill must return: 1. **Scope** — the composable(s) and/or component(s), files, and specific call sites reviewed. 2. **Ranked findings** — each with file:line, defect category (reactivity-loss / lifecycle-timing / computed-impurity / extraction-candidate), the concrete evidence (the exact destructuring line, the exact async boundary crossed, or the exact mixed-concern list), and a fix sketch matching Vue's documented pattern. 3. **Evidence level per finding** — `repo evidence`, `documentation-based`, or `inference`. 4. **Verdict** — approve / approve-with-notes / block. 5. **Open questions or out-of-scope items** — e.g., "requires Vue Devtools reactivity inspection to confirm the reported UI-not-updating bug traces to this destructuring site," or "auth-token handling in `useSession` is out of scope — recommend vue-ssr-security-review." ## When to push back Push back if the user asks to: - treat every `reactive()` usage in the codebase as a bug to convert to `ref()` — `reactive()` is correct and idiomatic for state that is never destructured by consumers; a blanket conversion is unnecessary churn, not a fix, - adopt Pinia or Vuex as part of this review's recommendation when neither is already a project dependency — that is a state-management architecture decision outside a composable/reactivity review's scope, - migrate an Options API component to Composition API as a review finding when the PR itself does not already touch Composition API elsewhere — that is a repo-wide migration decision, not a PR-level fix.
-
-
metadata.json 1.5 KB
{ "id": "vue-composition-api-architecture-review", "name": "Vue Composition API Architecture Review", "type": "skill", "provider": "frontend", "harnesses": [ "claude-code", "cursor", "codex", "gemini", "kiro", "other" ], "summary": "Reviews Vue 3 composables and script-setup components for reactivity-boundary correctness (ref/reactive/computed usage, destructuring-loses-reactivity pitfalls, toRef/toRefs/unref interop, lifecycle-hook synchronous-registration timing) and composable extraction quality, grounding claims via Context7 against Vue's own composables and reactivity documentation.", "source_type": "original", "official_docs": [ "https://vuejs.org/guide/extras/composition-api-faq.html", "https://vuejs.org/guide/reusability/composables.html", "https://vuejs.org/api/reactivity-core.html", "https://vuejs.org/guide/essentials/reactivity-fundamentals.html" ], "security_notes": "Static-review-only skill: it reads and greps composable/component source but never executes, builds, or runs application code. No direct security-primitive concern; if a composable is found managing auth tokens, session state, or other credential material, flag it out of scope and recommend the vue-ssr-security-review skill instead of performing credential-handling review here.", "last_verified": "2026-07-02", "path": "skills/frontend/vue-composition-api-architecture-review", "author": "github: VincentChuWaiChow", "version": "0.1.0" } -
SKILL.md 7.1 KB
--- name: vue-composition-api-architecture-review description: Statically review Vue 3 Composition API code — composable extraction quality, reactivity-boundary correctness (ref/reactive/computed usage, destructuring-loses-reactivity pitfalls), and script-setup component organization — against Vue's own documented composable conventions and reactivity fundamentals. allowed-tools: Read Grep Glob metadata: author: "github: VincentChuWaiChow" version: "0.1.0" updated: "2026-07-02" category: architecture --- # Vue Composition API Architecture Review ## Purpose Review Vue 3 composables and `<script setup>` components for the specific defect classes Vue's own documentation calls out — reactivity lost through destructuring, lifecycle hooks registered outside the synchronous setup window, impure `computed()` getters, and script-setup components that mix data-fetching, business logic, and presentation with no composable extraction — without re-litigating component prop design, template markup, styling, or SSR/hydration concerns in every response. This skill exists so those adjacent concerns stay out of scope and the review stays focused on the documented reactivity-boundary and composable-extraction catalog. ## When to use Use this skill when the user asks to: - review a new or refactored composable (a `use*`-prefixed function) before merge, - diagnose a report that "reactive state doesn't update in the UI" after destructuring a composable's return value, - review a `<script setup>` component for organization and whether logic should be extracted into a composable, - decide whether a given `computed()` or lifecycle-hook usage is correct. Do not use this skill for: - Options API-only codebases with no Composition API usage and no stated migration plan — there is no reactivity-boundary or composable-extraction surface to review, - SSR-specific security concerns (session/auth-token handling inside composables, hydration mismatches) — hand off to `vue-ssr-security-review`, - a bug that requires live reproduction (Vue Devtools reactivity inspection, browser profiling) to confirm — static analysis can identify the missing `toRefs()`/guard, not prove which specific runtime instance broke. ## Context7 Documentation Protocol - Resolve the Vue library ID with `resolve-library-id` (matched result: `/vuejs/vue`) before labeling any specific pattern as a reactivity-loss bug or a composable-convention violation. - `/vuejs/vue` is Vue's core source-and-test repository, not the prose docs site. Use `query-docs` against it to corroborate exact runtime behavior (e.g., the `computed({ get, set })` writable-computed shape, `reactive()`/`ref()` tracking mechanics) from source and unit tests. It does not reliably surface the narrative guidance in the Composables and Reactivity Fundamentals guides — for that guidance, use the `official_docs` URLs in this skill's `metadata.json` and label the claim `documentation-based`. - Before asserting a specific destructuring pattern loses reactivity, confirm which construct is being destructured: destructuring a `reactive()`-returned proxy loses reactivity on primitive-valued properties; destructuring a `ref()` or a `toRefs()`-wrapped object does not, because each extracted value is itself a ref. Do not apply a blanket "destructuring is unsafe" rule — check the return type first. - Read `package.json` first to confirm Vue 3.x is in use; Composition API and its documented composable conventions (including `<script setup>`) are Vue 3 features, not Vue 2. - If Context7 is unavailable, 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 - For every composable in scope, first determine whether its return value is intended to be destructured by consumers. If yes, verify it returns individual refs or a `toRefs()`-wrapped reactive object — a plain `reactive()` object returned for destructuring is the documented reactivity-loss pitfall, not a style preference. - For every composable, verify the `use*` naming convention and verify that lifecycle hooks (`onMounted`, `onUnmounted`, `watch`, etc.) and other composables are called synchronously during the composable's/component's initial `setup()`/`<script setup>` execution — not inside an async callback, `await`-continuation, conditional, loop, or event handler registered after setup has returned. Vue's synchronous-registration requirement is a hard rule, not a lint nicety: a hook registered after an `await` silently fails to attach to the correct component instance. - For every `computed()` in scope, verify the getter is a pure read with no side effects (no state mutation, no async calls, no logging with side effects). A writable computed (`computed({ get, set })`) is the documented pattern for two-way-bindable derived state — do not flag its `set` function as an impurity; that is its intended role. - Review `<script setup>` components for responsibility mixing: data-fetching, validation/business logic, and presentation-only state all inline with no composable extraction. Flag extraction candidates only when the mixed logic is non-trivial and would reduce duplication or improve testability if extracted — do not flag a component for using one or two local `ref()`s that have no reuse potential. - Do not fabricate a reactivity-loss finding without showing the exact destructuring line and the specific property that would stop updating. A finding that only says "this might lose reactivity" without the concrete assignment is not a valid finding. - Never execute, build, or run application code as part of this review; this is a static-review skill (Read/Grep/Glob only). - Load only the reference needed for the concern in scope. ## References Load these only when needed: - [Review workflow and findings contract](references/workflow-and-output.md) — use for the step-by-step review procedure, the reactivity/composable decision tree, and the required output shape. - [Reactivity boundaries](references/reactivity-boundaries.md) — load only when reviewing `ref()`/`reactive()`/`computed()` usage, a suspected destructuring-loses-reactivity bug, or `toRef()`/`toRefs()`/`unref()` interop code. - [Composable and script-setup conventions](references/composable-and-script-setup-conventions.md) — load only when reviewing composable naming/structure, lifecycle-hook registration timing, or `<script setup>` responsibility mixing and extraction candidates. ## Response minimum Return, at minimum: - the composable(s) and/or component(s), files, and specific call sites in scope, - ranked findings with file:line evidence, defect category (reactivity-loss, lifecycle-timing, computed-impurity, or extraction candidate), and a concrete fix sketch matching the docs' recommended pattern, - for every reactivity-loss finding, the exact destructuring line and the property that would stop updating, - evidence level per finding (`repo evidence`, `documentation-based`, or `inference`), - verdict (approve / approve-with-notes / block), - open questions or scope the review could not cover (e.g., "requires Vue Devtools reactivity inspection to confirm at runtime").
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.