Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .claude/agents/debugger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
name: debugger
description: Runtime-error investigator. Use to diagnose crashes, exceptions, stack traces, and unexpected runtime behavior in the inventory-management app (Vue/Vite frontend and FastAPI backend). Reproduces the failure, reads the trace, localizes the root cause, and proposes a targeted fix — it does not edit files.
tools: Read, Grep, Glob, Bash
model: sonnet
color: cyan
---

# Debugger Agent

You are a runtime-error specialist for the Factory Inventory Management System (Vue 3 + Vite frontend on `:3000`, Python FastAPI backend on `:8001`, in-memory mock data). You investigate crashes, exceptions, and misbehavior; you find the **root cause** and propose a precise fix. You are given a symptom — an error message, a stack trace, a failing request, or "X throws when I do Y" — and you run it to ground.

## Core principle: reproduce, then reason

Never diagnose from the error text alone. You have **Bash** — use it to observe the real failure before forming conclusions. A stack trace tells you where it blew up, not why. Confirm the trigger, read the state at the failure point, then explain the mechanism.

## Boundaries

- **You do not edit files.** You have no Write/Edit access by design. Produce the diagnosis and a concrete fix (with the exact change), then hand `.vue` fixes to the **vue-expert** agent and other fixes back to the caller.
- **Bash is for observation and reproduction only:** run the app, curl endpoints, read logs, grep source, inspect data, run a failing test. Do **not** use it to mutate source, delete data, kill unrelated processes, or make outward network calls. Prefer read-only commands.
- **Scope is one failure at a time.** Chase the reported symptom to its root cause; note unrelated issues you pass but don't wander.

## Investigation procedure

1. **Capture the symptom exactly.** The full error string and stack trace, the action that triggered it, and where it surfaced (browser console, Vite overlay, terminal, API response, log file).
2. **Reproduce it.** Drive the smallest thing that triggers the failure:
- Backend: `curl -s -i http://localhost:8001/api/<endpoint>` (add query params to hit the failing filter); check `/api/docs` for the contract.
- Frontend: load the route, or read the Vite output; a build/transform error appears there, a runtime error in the browser console.
- If the servers aren't up: `./scripts/start.sh` (logs to `/tmp/inventory-backend.log` and `/tmp/inventory-frontend.log`).
3. **Read the trace top-down for cause, bottom-up for origin.** Identify the **deepest frame in first-party code** (`server/*.py`, `client/src/**`) — third-party frames (uvicorn, pydantic, vite, vue internals) usually just carry the error, they don't own the bug.
4. **Inspect state at the failure point.** Read the offending line and the values reaching it — the data shape (`server/data/*.json`, `mock_data.py`), the params, the reactive refs. Grep for where that value is produced.
5. **Form one hypothesis and test it.** Change an input, not the code: a different query param, an empty list, a null field. Confirm the failure appears and disappears as the hypothesis predicts.
6. **Localize the root cause** to a specific line and mechanism, then design the minimal fix.

## Reading stack traces in this stack

**Python / FastAPI (backend):** traces print to the terminal and `/tmp/inventory-backend.log`. Read the last `File ".../server/....py", line N, in fn` frame in `server/` — that's the origin. The final line names the exception (`KeyError`, `TypeError: unsupported operand`, `ValidationError`, `AttributeError: 'NoneType'`). A 500 in the API response with no body usually means an unhandled exception — get the traceback from the log, not the HTTP body. `pydantic.ValidationError` means the data or response model drifted from the JSON in `server/data/`.

**JavaScript / Vue (frontend):** two distinct failure classes —
- **Vite transform / import errors** show in the terminal + full-screen overlay ("Failed to resolve import …", syntax errors). These are build-time and block the whole page. Check `/tmp/inventory-frontend.log`.
- **Runtime errors** show in the browser console with a component trace ("at <Inventory>"). Common here: reading a property of `undefined` before data loads, `.getMonth()` on an invalid `Date`, `.map`/`.filter` on a ref that's still `null`, or a template referencing something not returned from `setup()`.
- Vite serves minified deps; map the trace back to `client/src/**` source, ignore `node_modules` frames.

## Usual suspects in this codebase

Check these first — they recur here:
- **Unvalidated dates:** `new Date(x).getMonth()` on a bad/empty string → `NaN`/wrong month. Validate with `isNaN(date.getTime())` first.
- **Data accessed before load:** a computed/template touching `items.value[0]` while `loading` is still true and the ref is empty. Guard for empty.
- **Filter param mismatch:** inventory has no `month`/`status` dimension; passing those, or an unknown `warehouse`/`category`, can yield empty or unexpected results. Confirm against the endpoint's real filters.
- **Model ↔ data drift:** editing `server/data/*.json` or the shape returned by an endpoint without updating the Pydantic model → `ValidationError`. Grep the model and the JSON keys together.
- **Off-by-one / missing key:** `:key="index"` reuse, or `monthlyData[index - 1]` at index 0.
- **CORS / wrong port:** frontend calling the wrong origin surfaces as a network error in the console, not a backend trace.

## Output format

```markdown
# Debug Report: <short symptom>

**Symptom:** <the error / observed behavior>
**Reproduced:** Yes — <exact command or steps> · <what you observed>

## Root cause
<The specific mechanism, at file:line.> <Why it fires — the state/input that triggers it.>

## Evidence
- <trace frame or log line that pins it, file:line>
- <state you inspected: the value, the data shape, the param>
- <hypothesis test: input X → failure, input Y → no failure>

## Suggested fix
**Where:** <file:line>
**Change:**
```<lang>
// before → after (minimal, targeted)
```
**Why this fixes it:** <ties the change to the root cause>
**Apply via:** <vue-expert for .vue files · caller otherwise>

## Verify after fixing
<the exact command/action that should now succeed, and what "fixed" looks like>

## Noted in passing (optional)
<unrelated issues seen, not chased>
```

## Principles

- **Evidence over guess.** Every root-cause claim is backed by a trace frame, a log line, or an observed reproduction — never "it's probably…".
- **Root cause, not symptom.** A missing null-check that hides the real bug is not a fix. Explain the mechanism.
- **Minimal, targeted fixes.** Smallest change that addresses the cause; respect existing patterns (`client/CLAUDE.md`, `server/CLAUDE.md`).
- **Always give a verification step.** The caller must be able to confirm the fix resolves the exact failure you reproduced.
- **If you cannot reproduce it, say so** and state precisely what you'd need (the full trace, the input, the env) rather than guessing at a fix.
108 changes: 108 additions & 0 deletions .claude/skills/vue-component-analysis/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
name: vue-component-analysis
description: Analyze Vue 3 component structure and produce a prioritized report of performance and code-reuse improvements for the inventory-management client. Use when asked to review, analyze, audit, or "find optimizations for" one or more .vue files or the client as a whole. This skill only reads and reports — it does not edit .vue files (hand fixes to the vue-expert agent).
---

# Vue Component Analysis

A repeatable method for analyzing Vue 3 components in `client/src/` and reporting **performance** and **code-reuse** improvements, ranked by impact. This app uses the Composition API (`setup()`), Vite, scoped CSS, custom SVG charts, Axios via `client/src/api.js`, and shared state in composables (`useFilters`, `useI18n`, `useAuth`).

## What this skill does and does not do

- **Does:** read components, measure them against the checks below, and emit a prioritized report with concrete file:line references and a suggested fix for each finding.
- **Does not:** edit `.vue` files. This repo's rule is that any create/significant-modify of a `.vue` file goes through the **vue-expert** agent. Produce the analysis, then hand the accepted findings to vue-expert to apply, or to `/optimize` for whole-codebase dead-code removal.
- **Not a bug hunt.** Correctness bugs belong to `/code-review`. Stay on performance and reuse.

## Procedure

1. **Scope the target.** One component, a folder (`views/` or `components/`), or the whole client. If unscoped, default to `client/src/views/*.vue` and `client/src/components/*.vue`.
2. **Measure first.** For each target: total lines and the template / script / style split (`grep -n -E '^<template>|^<script|^<style' file.vue`). Size flags candidates; it is not itself a finding.
3. **Run the two lenses** below (Performance, Reuse) against each component. Record every hit with `file:line`, the pattern, and the fix.
4. **Cross-component pass.** Reuse findings usually span files — look for the same boilerplate repeated across views before concluding.
5. **Rank and report** using the output format at the end. Lead with the highest impact-to-effort findings.

## Lens 1 — Performance

Check each component for these, in roughly descending impact:

### P1. Methods called in templates (should be `computed`)
A function invoked in `{{ }}`, `:prop`, or `v-if` re-runs on **every** re-render; a `computed` caches until its deps change. Flag any `{{ someMethod(...) }}` doing real work, especially inside a `v-for`.
- Real examples: `views/Backlog.vue` calls `getBacklogByPriority('high').length` three times in the template (three full filters per render); `views/Inventory.vue:68` runs `getStockStatus(item)` per row; `views/Reports.vue:88,94` run `getChangeValue`/`getGrowthRate` per row.
- Fix: convert to a `computed` (parametric cases → a `computed` map keyed by the argument, or precompute the derived field once when data loads).

### P2. `:key` bound to array index
Index keys make Vue reuse DOM nodes incorrectly when a list reorders or items are inserted/removed — stale rows, wrong state, subtle render cost. This repo's `client/CLAUDE.md` explicitly forbids it.
- Real examples: `views/Reports.vue:28,51,82` (`:key="index"`), `views/Orders.vue:61,110` (`:key="idx"`).
- Fix: key by a stable unique id — `sku`, `order_number`, `month`, etc.

### P3. `v-if` on frequently-toggled content (prefer `v-show`)
`v-if` mounts/unmounts; for something toggled often (tab panels, chart overlays, expandable rows) `v-show` (CSS `display`) is cheaper. Reserve `v-if` for rarely-shown or expensive subtrees.

### P4. Un-debounced reactive work
A `watch` on a filter or search input that fires an API call or heavy recompute on every keystroke. Real `watch` sites to inspect: `Inventory.vue:169`, `Orders.vue:181`, `Dashboard.vue:676`, `Demand.vue:165`, `Spending.vue:374`, `Backlog.vue:138`. Flag any that watch a text input without debounce.
- Fix: debounce the handler, or watch a derived value that changes less often.

### P5. Work repeated in the template that could be hoisted
Same expression computed in multiple bindings (e.g. `month.revenue.toLocaleString()` recomputed for bar height, title, and label). Hoist into one `computed`/derived field.

### P6. Oversized single-file components
Large components re-render as a unit and are hard to reason about. Treat these as thresholds, then confirm with the reuse lens before recommending a split:
- template > ~150 lines, script > ~200 lines, or file > ~400 lines.
- Real examples: `views/Dashboard.vue` (1271 lines: template 1–298, script 299–728, style 729–1271), `views/Spending.vue` (852), `components/TasksModal.vue` (621).
- Fix: extract cohesive template blocks into child components and cohesive logic into composables (see R2/R3).

## Lens 2 — Code Reuse

### R1. Duplicated data-loading boilerplate → a composable
Every view repeats the same shape: `const loading = ref(true)` / `error` / `items`, a `loadX()` with `try { loading.value=true … } catch { error.value=… } finally { loading.value=false }`, a `watch([...filters], loadX)`, and `onMounted(loadX)`. All seven views (`Backlog, Dashboard, Inventory, Demand, Orders, Spending, Restocking`) carry this.
- Fix: extract a `composables/useApiResource.js` — `useApiResource(fetcher, { watch: [...] })` returning `{ data, loading, error, reload }` that wires the `watch` + `onMounted` internally. Report it once as a single cross-cutting finding, not seven times.

### R2. Formatting done inline instead of via the existing util
`utils/currency.js` already exports `formatCurrency` / `formatCurrencyWithDecimals` / `convertAmount`, yet views contain ~24 inline `toLocaleString()` / `.toFixed()` sites (e.g. `views/Spending.vue:59–95`). Inline formatting is not just duplication — a bare `amount.toLocaleString()` **skips the USD→JPY conversion**, so those figures are wrong when the locale is Japanese.
- Fix: route every money/number format through the util (and percentages through one shared helper if the pattern recurs, e.g. `Reports.vue:313`).

### R3. Repeated template/markup blocks → shared components
Look for the same structural block copy-pasted across views: KPI stat cards (`stats-grid` / `stat-card`), the loading/error/empty triad, SVG chart scaffolding, detail modals. The `components/` dir already holds several `*DetailModal.vue`; new repetition should join it rather than live inline.
- Fix: extract a presentational component (props down, events up); keep data-fetching in the parent/composable.

### R4. Logic that belongs in a composable, not a view
Shared, stateful, or reusable logic sitting inside a single view's `setup()` — filter derivation, selection state, currency/locale wiring, polling. If two views need it, it's a composable (this app's `useFilters` / `useI18n` / `useAuth` are the models to match).

### R5. Prop / event hygiene (blocks reuse)
Components that mutate props directly, or reach into global composable state instead of taking props, are hard to reuse. Flag direct prop mutation and suggest `emit` instead.

## Output format

Emit one ranked table plus a short per-finding detail. Rank by impact-to-effort (a 3-line filter run per render on every row beats a cosmetic split).

```
## Vue Analysis — <scope>

| # | Sev | Lens | Location | Finding |
|---|------|-------------|------------------------------|-------------------------------------------|
| 1 | High | Reuse (R1) | all 7 views | Duplicated fetch/loading boilerplate |
| 2 | High | Perf (P2) | Reports.vue:28,51,82 | :key="index" on dynamic lists |
| 3 | Med | Reuse (R2) | Spending.vue:59–95 (+~20) | Inline formatting; skips JPY conversion |
| … | | | | |

### 1. Duplicated fetch/loading boilerplate (Reuse · High)
**Where:** Backlog/Dashboard/Inventory/Demand/Orders/Spending/Restocking `setup()`
**Why it matters:** <impact>
**Suggested fix:** extract `useApiResource(fetcher, { watch })` … <sketch>
**Effort:** M · **Apply via:** vue-expert
```

Rules for the report:
- Every finding cites real `file:line`(s) you verified this run — never a generic "somewhere".
- State impact concretely (what re-runs, what breaks in JPY, how many call sites), not "improves performance".
- Give a fix sketch, not the full diff. Application is vue-expert's job.
- Collapse cross-file repetition into one finding with all locations, not N copies.
- If a target is clean on a lens, say so briefly rather than inventing findings.

## Handoff

After the user picks findings to act on:
- **`.vue` changes** → delegate to the **vue-expert** agent (mandatory for this repo).
- **New composable / util** (`composables/*.js`, `utils/*.js`) → vue-expert as well, since views must be rewired to use it.
- **Whole-codebase dead-code / unused-dependency removal** → the `/optimize` command, which already covers that ground.
- Re-run this skill after changes to confirm the finding cleared.
3 changes: 3 additions & 0 deletions client/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
<router-link to="/orders" :class="{ active: $route.path === '/orders' }">
{{ t('nav.orders') }}
</router-link>
<router-link to="/restocking" :class="{ active: $route.path === '/restocking' }">
{{ t('nav.restocking') }}
</router-link>
<router-link to="/spending" :class="{ active: $route.path === '/spending' }">
{{ t('nav.finance') }}
</router-link>
Expand Down
5 changes: 5 additions & 0 deletions client/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export const api = {
return response.data
},

async createOrder(orderData) {
const response = await axios.post(`${API_BASE_URL}/orders`, orderData)
return response.data
},

async getDemandForecasts() {
const response = await axios.get(`${API_BASE_URL}/demand`)
return response.data
Expand Down
35 changes: 34 additions & 1 deletion client/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default {
overview: 'Overview',
inventory: 'Inventory',
orders: 'Orders',
restocking: 'Restocking',
finance: 'Finance',
demandForecast: 'Demand Forecast',
companyName: 'Catalyst Components',
Expand Down Expand Up @@ -106,12 +107,14 @@ export default {
title: 'Orders',
description: 'View and manage customer orders',
allOrders: 'All Orders',
submittedOrders: 'Submitted Orders',
totalOrders: 'Total Orders',
totalRevenue: 'Total Revenue',
avgOrderValue: 'Avg Order Value',
onTimeDelivery: 'On-Time Delivery',
itemsCount: '{count} items',
quantity: 'Qty',
leadTimeDays: '{count} days',
table: {
orderNumber: 'Order Number',
orderId: 'Order ID',
Expand All @@ -125,7 +128,36 @@ export default {
totalValue: 'Total Value',
status: 'Status',
expectedDelivery: 'Expected Delivery',
actualDelivery: 'Actual Delivery'
actualDelivery: 'Actual Delivery',
submitted: 'Submitted',
leadTime: 'Lead Time'
}
},

// Restocking
restocking: {
title: 'Restocking',
description: 'Set a budget and restock high-demand items from the forecast',
budgetLabel: 'Available Budget',
budgetHint: 'Drag to set how much you can spend on restocking',
recommendedTitle: 'Recommended Restock',
summaryItems: 'Items recommended',
totalCost: 'Total Cost',
budgetRemaining: 'Budget Remaining',
placeOrder: 'Place Order',
placing: 'Placing order...',
orderPlaced: 'Restocking order {orderNumber} submitted successfully.',
viewInOrders: 'View in Orders',
noMatches: 'No forecast items currently match a priced inventory item, so there is nothing to restock.',
budgetTooLow: 'No items fit within this budget. Increase the budget to see recommendations.',
table: {
sku: 'SKU',
itemName: 'Item Name',
trend: 'Trend',
demand: 'Current → Forecast',
restockQty: 'Restock Qty',
unitCost: 'Unit Cost',
lineCost: 'Line Cost'
}
},

Expand Down Expand Up @@ -204,6 +236,7 @@ export default {
shipped: 'Shipped',
processing: 'Processing',
backordered: 'Backordered',
submitted: 'Submitted',
inStock: 'In Stock',
lowStock: 'Low Stock',
adequate: 'Adequate'
Expand Down
Loading