agentleFS
bbvch-aibbvch-ai/aihub-core/.claude/skills/audit-frontend/SKILL.md

audit-frontend

Skill16 starsChanged 3 days agoApache-2.0View on GitHub

Run a comprehensive frontend code audit of the Nuxt 3 admin interface. Checks Pinia-Colada query/mutation patterns, TypeScript strictness, component architecture, SDK freshness, i18n coverage, and Tailwind usage. Use when user says 'audit the frontend', 'check frontend code quality', 'find unused components', 'composable health check', 'pinia-colada audit', 'typescript errors', or 'frontend code review'. Do NOT use for backend API audits (use scaffold-api-endpoint) or bot UI issues (use bot-framework). Reports issues with file locations and severity levels.

We split this skill into the rules it gives an agent

21 rules. About 0 are common, 2 are distinctive, and 19 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 19

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Run a comprehensive audit of the Nuxt 3 admin interface at packages/web/.

    line 9
  2. Very specific:

    Default scope is all if no argument provided.

    line 12
  3. Very specific:

    Before starting, read packages/web/CLAUDE.md for the full frontend conventions reference.

    line 14
  4. Very specific:

    Check all files in packages/web/composables/ against these rules:

    line 20
  5. Very specific:

    Every exported query composable must use defineQuery(), every mutation must use defineMutation().

    line 24
  6. Very specific:

    Every useQuery must have staleTime: minutesToMilliseconds(N) (from date-fns).

    line 49
  7. Very specific:

    Every SDK function call must include composable: '$fetch' — this is required by the @hey-api/client-nuxt adapter.

    line 61
  8. Very specific:

    If a query uses route.params.*, it must have enabled: useRouteReady('param_name') to prevent firing before the route resolves.

    line 73
  9. Very specific:

    Every mutation should call queryCache.invalidateQueries() — either inside the mutation function or in onSettled.

    line 80
  10. Very specific:

    Prefer onSettled over onSuccess for cache invalidation (runs regardless of success/failure).

    line 81
  11. Very specific:

    Every bg-surface-*, text-surface-*, border-surface-* class should have a dark: variant pair:

    line 188
  12. Very specific:

    Check that every file directly under pages/[tenant]/service/*.vue wraps content in StructuralScreen with a NuxtPage for detail routing.

    line 212
  13. Very specific:

    All composables from composables/ and composables//

    line 258
  14. Distinctive:

    Check if API is accessible:

    line 274
  15. Very specific:

    If accessible, compare OpenAPI spec endpoints with packages/web/sdk/client/sdk.gen.ts:

    line 280
  16. Very specific:

    If not accessible, compare timestamps of SDK files vs latest packages/api/ changes:

    line 284
  17. Very specific:

    Verify SDK is never manually edited — check git status for modifications in sdk/client/.

    line 291
  18. Very specific:

    Read all locale files: packages/web/i18n/locales/{de,en,fr,it}.yaml

    line 297
  19. Very specific:

    Use English as reference — find keys missing in de/fr/it

    line 298
  20. Distinctive:

    Check for empty string values

    line 300
  21. Very specific:

    Verify that surface color tokens use PrimeVue semantic names (surface-50 through surface-950) rather than Tailwind gray/slate/zinc …

    line 330

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Frontend Code Audit

Run a comprehensive audit of the Nuxt 3 admin interface at packages/web/. Scope via $ARGUMENTS: all, composables, typescript, components, pages, sdk, i18n, tailwind.

Default scope is all if no argument provided.

Before starting, read packages/web/CLAUDE.md for the full frontend conventions reference.


Audit 1: Pinia-Colada Composables (scope: composables)

Check all files in packages/web/composables/ against these rules:

1a. defineQuery/defineMutation wrappers (ERROR)

Every exported query composable must use defineQuery(), every mutation must use defineMutation(). Bare useQuery or useMutation in a plain exported function is an anti-pattern — it breaks singleton caching.

# Find bare useQuery not wrapped in defineQuery
grep -rn "export.*=.*(" packages/web/composables/ --include="*.ts" | grep -v defineQuery | grep -v defineMutation

Cross-check: if a file imports useQuery from @pinia/colada but doesn't call defineQuery, flag it.

1b. Query keys must be functions (ERROR)

Keys must be arrow functions key: () => [...], never static arrays key: [...]. Static keys don't react to route or ref changes.

# Find static query keys (array literal without arrow function)
grep -rn "key: \[" packages/web/composables/ --include="*.ts"

Correct: key: () => ['agent-instances', route.params.agent_class] Wrong: key: ['agent-instances', route.params.agent_class]

1c. staleTime must use minutesToMilliseconds (ERROR)

Every useQuery must have staleTime: minutesToMilliseconds(N) (from date-fns). Standard is 5 minutes. Missing staleTime means the global default applies, which may not be intentional.

# Find useQuery calls without staleTime
grep -rn "useQuery" packages/web/composables/ --include="*.ts" -A 5 | grep -v staleTime

Hardcoded milliseconds like staleTime: 300000 are also wrong — use the date-fns helper.

1d. composable: '$fetch' on every SDK call (ERROR)

Every SDK function call must include composable: '$fetch' — this is required by the @hey-api/client-nuxt adapter. Missing it causes the request to use raw fetch instead of Nuxt's $fetch, breaking auth token injection.

# Find SDK calls without composable: '$fetch'
grep -rn "await \(get\|create\|update\|delete\|post\|put\|patch\)" packages/web/composables/ --include="*.ts"

Cross-check each SDK call has composable: '$fetch' in its options object.

1e. Route-dependent queries must use useRouteReady (WARNING)

If a query uses route.params.*, it must have enabled: useRouteReady('param_name') to prevent firing before the route resolves. During route transitions, params can briefly be template placeholders like '{agent_id}'.

Reference implementation: packages/web/composables/useRouteReady.ts

1f. Mutations must invalidate query cache (WARNING)

Every mutation should call queryCache.invalidateQueries() — either inside the mutation function or in onSettled. Prefer onSettled over onSuccess for cache invalidation (runs regardless of success/failure). Mutations should invalidate both broad keys (['agents']) and specific keys (['agents', agentClass, agentId]).

1g. Named exports, not default (WARNING)

Composables must use export const useFoo = defineQuery(...), not export default defineQuery(...). Default exports lose the explicit name and rely on filename inference for auto-import.

# Find default exports in composables
grep -rn "export default" packages/web/composables/ --include="*.ts"

1h. Query key naming conventions (WARNING)

Keys should be kebab-case string arrays: ['agent-classes'], ['agent-instances', agentClass, agentId]. Check for:

  • Snake_case keys (['my_user'] should be ['my-user'])
  • Keys that don't include all reactive inputs used in the query function

Audit 2: TypeScript Strictness (scope: typescript)

2a. Untyped function parameters (ERROR)

Find callback parameters without type annotations (implicit any).

# Find untyped arrow function params in .vue and .ts files
grep -rn "=> {" packages/web/pages/ packages/web/components/ --include="*.vue" --include="*.ts" | grep "(event)" | grep -v ": "

Common offender: const handleClick = (event) => { should be (event: MouseEvent) => {.

2b. Type imports must use import type (WARNING)

SDK types should use import type { FooDto } not import { FooDto }. Regular imports pull in runtime code; type imports are erased at compile time.

# Find non-type imports of SDK types (Dto suffix = type)
grep -rn "import {.*Dto" packages/web/ --include="*.vue" --include="*.ts" | grep -v "import type"

Exception: types re-exported as values (e.g., export type AgentClassDto = AgentClassDtoReadable) are fine.

2c. Route params must be cast (WARNING)

route.params.foo is string | string[]. Always cast to string when passing to SDK functions: route.params.agent_id as string. Flag bare route.params.foo passed directly to functions without cast.

2d. Computed vs function naming (WARNING)

Computed refs named with get prefix (like getEmptyStateTitle = computed(...)) suggest a function but are reactive refs. Either name them as nouns (emptyStateTitle) or make them actual functions.

2e. console.log in production code (WARNING)

grep -rn "console\.\(log\|debug\|info\)" packages/web/ --include="*.vue" --include="*.ts" \
  | grep -v "node_modules" | grep -v "sdk/"

console.error in global error handlers (app.vue) is acceptable. console.log in pages/components is not.


Audit 3: Component Architecture (scope: components)

3a. Unused components (WARNING)

# For each .vue file in components/, derive auto-import name and search for usage
find packages/web/components/ -name "*.vue" | while read f; do
  # Agent/Card.vue → AgentCard
  NAME=$(echo "$f" | sed 's|.*/components/||; s|\.vue$||; s|/||g')
  USAGE=$(grep -rl "$NAME" packages/web/ --include="*.vue" --include="*.ts" | grep -v "$f" | wc -l)
  [ "$USAGE" -eq 0 ] && echo "UNUSED: $f ($NAME)"
done

3b. Props/emits typing pattern (WARNING)

Components should use:

  • withDefaults(defineProps<{...}>(), {...}) for props with defaults
  • defineEmits<{ eventName: [argType] }>() for typed emits (Vue 3.3+ named tuple form)
  • defineProps<{...}>() for props without defaults

Flag components using the options API syntax (props: { ... }) or untyped defineEmits(['click']).

3c. v-model bridging pattern (INFO)

For v-model on custom components, check for the standard pattern:

const visible = computed({
  get: () => props.modelValue,
  set: (value) => emit('update:modelValue', value),
})

Flag cases where modelValue prop exists but the writable computed bridge is missing.

3d. Dark mode completeness (WARNING)

Every bg-surface-*, text-surface-*, border-surface-* class should have a dark: variant pair:

  • Light: bg-surface-50/100/200, Dark: dark:bg-surface-800/900/950
  • Light: text-surface-500/600/700, Dark: dark:text-surface-300/400
  • Light: border-surface-200/300, Dark: dark:border-surface-700/800
# Find surface classes without dark: companion on the same element
grep -rn "bg-surface-\(50\|100\|200\)" packages/web/components/ --include="*.vue" \
  | grep -v "dark:bg-surface"

Audit 4: Page Architecture (scope: pages)

4a. StructuralScreen/Column pattern (ERROR)

List pages must follow:

StructuralScreen > StructuralColumn + NuxtPage

Check that every file directly under pages/[tenant]/service/*.vue wraps content in StructuralScreen with a NuxtPage for detail routing.

grep -rL "StructuralScreen" packages/web/pages/\[tenant\]/service/*.vue

4b. tenantPath for all navigation (ERROR)

All router.push(), navigateTo(), and NuxtLink :to must use useTenantPath() (it wraps useLocalePath() and adds the [tenant] segment). Bare paths like router.push('/service/agents') break locale and tenant prefixing.

# Find bare path navigation without tenantPath/localePath
grep -rn "router\.push\|navigateTo" packages/web/pages/ packages/web/components/ --include="*.vue" \
  | grep -v tenantPath | grep -v localePath | grep -v "//.*router"

Also check <NuxtLink :to=" attributes — they should wrap paths in tenantPath(). Sysadmin pages (/tenants/...) are not tenant-scoped and use useLocalePath().

4c. NavItem isActive must be a closure (ERROR)

In hub pages (e.g., [agent_class]-[agent_id].vue), NavItem.isActive must be a function returning boolean, not a boolean value. The SelectButton calls isActive() dynamically.

// Correct: closure
isActive: () => route.path.startsWith(tenantPath(subPath('overview')))
// Wrong: evaluated once
isActive: route.path.startsWith(tenantPath(subPath('overview')))

4d. Detail pages use close-route (WARNING)

Leaf pages (inside [param]/) should pass close-route to StructuralColumn for the back button. The path should be bare (not wrapped in tenantPath — StructuralColumn applies it internally).

4e. Unnecessary explicit imports (WARNING)

In <script setup>, these are auto-imported and must NOT be explicitly imported:

  • Vue: ref, computed, watch, onMounted, nextTick
  • Vue Router: useRoute, useRouter, navigateTo
  • i18n: useI18n
  • PrimeVue: useConfirm, useToast
  • All composables from composables/ and composables/**/
# Find unnecessary imports in pages and components
grep -rn "import.*from 'vue'" packages/web/pages/ packages/web/components/ --include="*.vue"
grep -rn "import.*from 'vue-router'" packages/web/pages/ --include="*.vue"
grep -rn "import.*from 'vue-i18n'" packages/web/pages/ --include="*.vue"
grep -rn "import.*from 'primevue/use" packages/web/pages/ --include="*.vue"

Exception: useLocalePath from '#i18n' DOES need explicit import (useTenantPath is auto-imported).


Audit 5: SDK Freshness (scope: sdk)

  1. Check if API is accessible:
curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/api/v1/active/openapi.json
  1. If accessible, compare OpenAPI spec endpoints with packages/web/sdk/client/sdk.gen.ts:

    • Report: new endpoints missing from SDK, removed endpoints still in SDK
  2. If not accessible, compare timestamps of SDK files vs latest packages/api/ changes:
stat -c %Y packages/web/sdk/client/sdk.gen.ts
git log -1 --format=%ct packages/api/
  1. Verify SDK is never manually edited — check git status for modifications in sdk/client/.

Audit 6: i18n Coverage (scope: i18n)

  1. Read all locale files: packages/web/i18n/locales/{de,en,fr,it}.yaml
  2. Use English as reference — find keys missing in de/fr/it
  3. Find keys in de/fr/it that don't exist in en (orphaned)
  4. Check for empty string values
  5. Scan .vue files for $t('...') and t('...') calls — find keys used in code but missing from en.yaml
  6. Report coverage per language as a percentage

Note: some keys are dynamically constructed with template literals — flag them as unverifiable, not missing.


Audit 7: Tailwind and Styling (scope: tailwind)

7a. Style blocks should be minimal (WARNING)

<style> blocks are acceptable only for PrimeVue deep overrides (:deep(.p-panel-header)). Flag large style blocks (more than 10 lines) or style blocks not using :deep() — they should use Tailwind classes instead.

# Find style blocks and count lines
grep -rn "<style" packages/web/components/ packages/web/pages/ --include="*.vue"

7b. Inline styles (WARNING)

grep -rn 'style="' packages/web/components/ packages/web/pages/ --include="*.vue" | grep -v ":style"

Static style="" should be Tailwind classes. Dynamic :style for computed values is acceptable.

7c. PrimeVue surface tokens (INFO)

Verify that surface color tokens use PrimeVue semantic names (surface-50 through surface-950) rather than Tailwind gray/slate/zinc (gray-100, slate-200). PrimeVue surface tokens respect the theme.

grep -rn "text-\(gray\|slate\|zinc\|neutral\|stone\)-" packages/web/ --include="*.vue"
grep -rn "bg-\(gray\|slate\|zinc\|neutral\|stone\)-" packages/web/ --include="*.vue"

Report Format

Present findings as a summary table:

Audit AreaStatusIssues FoundSeverity
Composablespass/warn/failCountHigh
TypeScriptpass/warn/failCountHigh
Componentspass/warnCountMedium
Pagespass/warn/failCountHigh
SDK Freshnesspass/warn/failCountMedium
i18n Coveragepass/warn/failCount + %Medium
Tailwind/Stylingpass/warnCountLow

Then list each issue grouped by audit area with: file path, line number, issue description, suggested fix.

Examples

  • /audit-frontend all -- Run all 7 audits and produce summary table
  • /audit-frontend composables -- Deep check on Pinia-Colada patterns (defineQuery, keys, staleTime, cache)
  • /audit-frontend typescript -- Find type errors, implicit any, missing type imports
  • /audit-frontend pages -- Check page architecture, tenantPath usage, NavItem patterns
  • /audit-frontend i18n -- Cross-check translation keys across de/en/fr/it locales

Troubleshooting

IssueResolution
API unreachable for SDK checkSkip SDK freshness audit, report as "skipped (API unreachable)"
Too many unused componentsVerify auto-import naming: Agent/Card.vue becomes AgentCard
False positive on i18n keysSome keys are dynamically constructed — check for template literals
Composable uses bare useQueryFlag as ERROR — should be wrapped in defineQuery for singleton caching
useLocalePath flagged as unnecessaryThis IS required — it must be imported from '#i18n'

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.