Commit Graph

11 Commits

Author SHA1 Message Date
josh c6fb839005 feat(part-models): detail page with fleet insights
CI / Playwright (smoke) (push) Has been cancelled
CI / Lint · Typecheck · Test · Build (push) Successful in 46s
CI / Build & push images (push) Successful in 1m6s
Adds /part-models/:id mirroring host/part detail pattern: KPIs for
units, spend, avg price, failure counts, and FMs implicating the
model, a state-breakdown bar chart, and the parts-of-this-model
table. New GET /part-models/:id/insights aggregates via part.groupBy
+ aggregate and repair/fm counts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 14:51:39 -04:00
josh 13e3444258 fix(locations): size card to content instead of full viewport
CI / Lint · Typecheck · Test · Build (push) Successful in 47s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m4s
The page forced the card to fill the viewport via h-[calc(100vh-...)],
leaving awkward empty space when few bins were present. Drop the fixed
height so the card sizes to its tallest column and let the page scroll
naturally if the bin grid overflows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 14:38:34 -04:00
josh 0b29e706b0 feat(hosts): generate unique 8-digit asset ID
CI / Lint · Typecheck · Test · Build (push) Successful in 1m2s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m21s
Add a Generate button to the host create dialog that fetches a random
8-digit asset ID from the new GET /hosts/generate-asset-id endpoint.
The service retries against the unique index so the returned ID is
guaranteed unused. Edit mode hides the button.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 14:30:24 -04:00
josh 95e501a9c8 fix(hosts): tweak detail page copy and summary style
CI / Lint · Typecheck · Test · Build (push) Successful in 55s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 8s
- Render State/Stack as plain text in the summary (badges still in header).
- Show FM UUID instead of problem text in the timeline entries.
- Rename PART_ARRIVED label to "Part deployed".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 14:11:13 -04:00
josh b0e9c5d1d0 feat: host detail page + FM host context
CI / Lint · Typecheck · Test · Build (push) Successful in 44s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m5s
Add /hosts/:id detail page with unified timeline (HostEvents + FMs + Repairs
+ part arrivals/departures) and a deployed-parts table. Hosts list rows now
link to the page. FM list + detail surface inline State/Stack badges next
to the asset ID, with the asset ID linking to the host page.

HostEvent audit model added; create/update in the hosts service now diff
and log state, stack, and field changes the same way parts.ts does.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 14:04:07 -04:00
josh 60255f20bb feat: laundry-list polish pass
CI / Lint · Typecheck · Test · Build (push) Successful in 44s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 59s
Seven bundled improvements:
- PartModel combobox on Add Part + Log Repair (known MPN auto-fills;
  unknown reveals manufacturer picker for catalog upsert).
- Host lifecycle: state (DEPLOYED/DEGRADED/TESTING) and stack
  (PRODUCTION/VETTING) fields, driven by external clients via the API.
- Locations page redesigned as a 2-pane tree + bin grid with breadcrumb.
- PENDING_REPAIR custody state: tech takes a SPARE into custody for a
  future swap; resolves to DEPLOYED via Repair or back to SPARE via a
  bin-required drop-off.
- Move Category from Part to PartModel; seed common categories
  (GPU/RAM/SSD/HDD/NIC/CPU/PSU/MOBO). Parts table gets a Category
  column and filter sourced from the model.
- Fix Deployed Value 100x bug on the Dashboard (price is stored as
  dollars, not cents).
- PartModels table shows "No" instead of "--" when destroyOnFail=false.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 13:36:11 -04:00
josh 3d77f2846d feat: split Repairs into FM, Repair, and Custody workflows
CI / Lint · Typecheck · Test · Build (push) Successful in 44s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m0s
The old Repairs module had grown ticketing-system features (status lifecycle,
comments, assignee, notes) that duplicate what the external ticketing tool
already owns. Vector only needs to track whether maintenance is open or closed.

- Rename RepairJob -> Fm (OPEN/CLOSED only), drop RepairComment, assignee, notes
- New Repair table: persistent log of physical part swaps, with ingest on
  unknown broken MPN via partModels.upsertByMpn
- New custody model: PENDING_DROP_IN_CUSTODY / PENDING_DESTRUCTION_IN_CUSTODY
  states + Part.custodianId, with a "My Custody" page for drop-off
- PartModel.destroyOnFail routes broken parts to the destruction path
- Host lookup on /fms and /repairs accepts hostId XOR assetId
- Wire the dormant webhook emitter: fm.opened, fm.closed, repair.logged
- Single fresh Prisma migration (dev DB was wiped, no backfill)

Tests: 60 passing (custody transitions in parts.test.ts; new fms.test.ts,
repairs.test.ts, custody.test.ts covering happy paths, validation failures,
webhook emissions, and ingest-on-unknown-MPN).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 12:22:56 -04:00
josh 6690d8a5dd feat(parts): couple state and location (host vs bin)
CI / Lint · Typecheck · Test · Build (push) Successful in 45s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m23s
DEPLOYED parts live on a host; every other state lives in a bin (or
unassigned). Previously binId and hostId were independent nullable
fields with no validation, so the Edit Part dialog could leave a
DEPLOYED part with only a bin and no host — which silently dropped
it from the repair problem-part picker.

- Service: resolveLocation() helper enforces the invariant on create
  and update. On a state transition, update auto-clears the stale
  relation and emits LOCATION_CHANGED for the cleared side.
- Zod: CreatePartRequest.superRefine rejects mismatched state/location
  up front; UpdatePartRequest rejects both-fields-set.
- Web: PartFormDialog swaps a single Location field between Host
  combobox (DEPLOYED) and Bin combobox (others); switching State
  clears the opposite field. Parts list + detail render host first,
  then bin path, then Unassigned.
- Tests: 9 new cases covering the invariant including the no-op guard
  so an unrelated PATCH on a DEPLOYED part doesn't touch hostId/binId.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:43:02 -04:00
josh 0f952d6c1b feat: rework EOL, repairs, and hosts for real workflow
CI / Lint · Typecheck · Test · Build (push) Successful in 48s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Successful in 1m1s
Four domain-model changes driven by exercising the deployed 2.0 build:

- EOL moves from manufacturer to MPN via new PartModel catalog table,
  so alerts fire on the thing that actually ages.
- Repairs re-home to Host (required hostId + problem text) with an
  optional RepairJobPart join for affected parts; drop Part.replacementPartId.
- New /repairs/:id detail page with editable problem, part list, and
  a RepairComment thread (REPAIR_COMMENTED events fan out to each
  problem part's timeline).
- Host.assetId (required, unique) surfaces prominently on the repair
  page so techs can confirm they're touching the right box.

Single destructive migration reshapes existing dev data. All 7 packages
typecheck clean; 30 API tests pass (9 new covering host membership,
upsertByMpn idempotency + race, assetId 409, comment userId stamping).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-17 10:17:29 -04:00
josh acf6fc1103 feat(deploy): containerize api + web for single-host docker-compose
CI / Lint · Typecheck · Test · Build (push) Failing after 43s
CI / Playwright (smoke) (push) Has been skipped
CI / Build & push images (push) Has been skipped
- apps/api/Dockerfile: multi-stage build, runs prisma migrate deploy on
  boot. Workspace package.json "main/exports" rewritten to dist so Node
  ESM resolves compiled JS at runtime.
- apps/web/Dockerfile + nginx.conf: static build served by nginx with
  SPA fallback, gzip, cache-bust on hashed assets, and /api reverse
  proxy to the internal api service.
- docker-compose.yml: production-oriented stack — api (SQLite on a
  named volume), web (exposes WEB_PORT), redis (for the upcoming
  worker). Postgres dropped since schema still targets SQLite.
- .dockerignore: keep build context lean.
- ci: add docker job gated on push-to-main that builds and pushes both
  images to ${{ vars.REGISTRY_URL }} using ${{ secrets.REGISTRY_TOKEN }}.
  Tags :latest + :${github.sha}.
2026-04-16 21:10:04 -04:00
josh 7c0d422228 chore: initial Vector 2.0 monorepo
CI / Lint · Typecheck · Test · Build (push) Failing after 5m41s
CI / Playwright (smoke) (push) Has been skipped
Ground-up TypeScript rewrite of the Vector hardware parts inventory
system. Ships the full roadmap (Phases 0-8) in one initial commit:

- pnpm + Turbo monorepo: apps/{api,web,e2e}, packages/{db,shared,ui,config}
- Express 5 + Prisma 5 + zod validation + JWT w/ refresh-token rotation
- React 19 + Vite + shadcn/ui + TanStack Query/Table + nuqs URL state
- Repair/RMA, tags, bulk ops, saved views, CSV audit export
- Analytics dashboard on Recharts + EOL tracking
- Signed webhook subscriptions (HMAC-SHA256) with in-process emitter
- Vitest unit tests (shared schemas, api services/helpers) + Playwright skeleton
- Gitea Actions CI (lint, typecheck, test+coverage, build) + Renovate

Deferred follow-ups: Postgres cutover (data-migration script ready),
BullMQ worker for webhook delivery, @react-pdf PDF export, CSV import wizard.
2026-04-16 20:52:32 -04:00