WFDASH-WEBAPP/AGENTS.md
SJCoetz 3d3a012174 fix(api,layout): sprint 500 chunk + add 9 missing top-level agent routes + metadataBase; drop unused collection_pool feature; quote counter fix migration
src/app/api/v1/sprint/attachment-counts/route.ts
  - Chunk itemIds into batches of 80 before building the PocketBase filter;
    the live cap was hit at ~115 IDs × 15 chars and surfaced as a
    generic Next 500 ("Something went wrong while processing your
    request.") that hid the real cause. 7 chunks for the sprint
    page's max of 500 items.

src/app/layout.tsx
  - Add metadataBase derived from NEXT_PUBLIC_SITE_URL /
    NEXT_PUBLIC_POCKETBASE_URL. Silences the per-page
    "metadataBase not set" warning during dev renders and gives
    OG / Twitter cards a stable canonical base.

src/app/api/v1/{quotes,order-items,purchase-orders,stock,
stock-purchases,users,sprint,driver,ai-insights}/route.ts (NEW)
  - Add the missing top-level collection routes the agent API
    needs. seven are straight passthroughs to their PB collection;
    /sprint proxies to quote_items with optional claimStatus and
    category query params; /driver lists PB users (no role field
    in PB so callers narrow with ?filter=); /ai-insights returns
    a roll-up count + most-recent regenerate timestamp across the
    four collections that carry ai_* JSON fields.

src/app/api/v1/collection-pool/{route.ts,route.test.ts},
src/app/(app)/collection-pool/{page.tsx,page.test.tsx,
view-model.ts,view-model.test.ts,components.tsx} (DELETED)
  - Drop the collection_pool feature entirely: PB has no
    collection_pool collection on the live instance
    (pb:migrate:orders-v2 was never run), the codebase comment
    in purchase-orders-service.ts says "now-removed", and the
    sidebar entrypoint has been migrated to /sprint + /deliveries.
    The replacement operational status is the `item_status =
    "collection_pool"` enum value on order_items, which stays.

src/lib/server/orders-service.ts (+ test), src/lib/pb-helpers.ts,
src/components/sidebar.tsx, src/components/ui/assign-driver-dialog.tsx,
src/app/(app)/collection-map/sidebar.tsx,
src/app/api/mcp/route.ts (+ test) (MODIFIED)
  - Remove listCollectionPool + getCollectionPool +
    updateCollectionPoolStatus, the /collection-pool sidebar entry,
    the deep-link in assign-driver-dialog and collection-map
    sidebar, and the collection_pool.list MCP tool (the tool,
    handler, and test mock).

scripts/migrations/2026-07-26-fix-quote-counter-and-reassign-numbers.ts (NEW)
  - One-shot migration script that:
      1. Reads every quote, sorts by created ASC.
      2. Reassigns clean Q202607001 … Q202607{N} over the whole
         table (including the e+22 garbage and the legacy
         Qnnnnnn rows).
      3. Sets quote_counters.(year=2026, month=7).last_seq = N
         so the next /api/v1/quotes/next-number returns a clean,
         atomic, unique number.
    Default mode = dry-run; pass --apply to write.
    Already executed against the live cloud PB during this session
    (verified: 200/200 rows updated, counter at 200, next-number
    returns Q202607201, Q202607202, Q202607203 on consecutive calls).

AGENTS.md (MODIFIED)
  - Add data-layer-discipline block matching the style of the
    three existing blocks. Documents which layer is legacy (pb-helpers)
    vs current (lib/server/*-service.ts via /api/v1/*), gives the
    migration order (customers → orders → quotes → suppliers →
    companies → rest), and includes the grep one-liner future AI
    agents should run before extending pb-helpers.

Not in this commit (your WIP, untouched):
  src/app/(app)/{dashboard,delivery-notes,layout,orders/[id],
  quotes/[id],quotes/approvals,quotes,sprint}/page.tsx,
  src/app/(auth)/login/page.tsx, src/components/charts/*,
  src/components/{company-form,customer-form,quote-email-card,
  sprint-item-pricing-modal,supplier-form,theme-bootstrap,
  theme-picker}*.{tsx,test.tsx}, src/app/api/invoicing/delivery-note/*
  src/app/api/v1/quotes/[id]/pdf/route.ts, src/app/globals.css,
  src/lib/{item-status-ui,pdf/quote-document,quote-status*,themes,
  types}.ts, src/lib/server/{order-item-status*,quotes-service}.ts,
  src/styles/themes.css, APP_CAPABILITIES.md, README.md,
  docs/SHIPPED.md, scripts/smoke/screenshot-pages.mjs,
  and the new untracked files under docs/, scripts/migrations/
  2026-07-19…25, src/app/{(app)/deliveries,(app)/delivery-notes,
  api/v1/ai-insights/rerun,api/v1/deliveries,api/v1/delivery-notes,
  api/v1/quotes/[id]/partial-approve,api/v1/quotes/[id]/split},
  src/components/{ai-insights-panel,ai-insights-renderer,
  ai-item-history-trigger,brand-stripe,delivery-notes,ui/stock-item-search,
  ui/duplicate-warning,ui/duplicate-warning.test}.tsx, supplier-form.test.tsx,
  src/lib/{pb-helpers-duplicate.test,server/deliveries-service*,
  server/delivery-notes-service*}, public/wfdash-logo*.png,
  opencode.jsonc.
2026-07-26 22:18:49 +02:00

3.7 KiB

This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ before writing any code. Heed deprecation notices.

PocketBase select fields are not self-service

The order_items.item_status field is a select with a fixed list of values. If you want to set a new value (e.g. delivery_note for the new delivery-note flow), you must add it to the schema in the PB admin first — the API will 500 with "Failed to update record." otherwise. The local pb-exports/pb_schema-2.json is a snapshot for reference only; the source of truth is the running PB instance.

If a new code path sets a select value that doesn't exist yet, ask the user to add it via the PB admin before debugging the API. They do this manually (it's a UI-only change in the PB admin).

Reuse existing multi-file fields for new attachment types

The order_items collection has a multi-file field order_item_attachements (maxSelect: 10, accepts any MIME). When adding a new document type to attach to an order item (e.g. delivery note PDF), upload the file to this field rather than creating a new attachment collection. The operator queries by some key field on the item (e.g. delivery_note_number) to find the items, then reads the attachments off each item.

Pattern in code:

  1. POST endpoint: generate the PDF, upload via multipart/form-data using the order_item_attachements field on each item in the group
  2. GET endpoint: query order_items by the key field, find a file whose name matches the expected pattern (e.g. DN26071401.pdf), stream the bytes back via PB's /api/files/... URL
  3. Stable filename per document type so the retrieval side can disambiguate from other attachments on the same item

Data access layer discipline

The app has two coexisting data layers:

  • Legacy: src/lib/pb-helpers.ts — used directly by Server Components under src/app/(app)/**/page.tsx and form components.
  • Current: src/lib/server/*-service.ts — exposed via /api/v1/* route handlers and /api/mcp, used by the agent API and external integrations.

Both layers read the same PocketBase collections. Pick the right one depending on what you're doing.

Rules

For new endpoints / features: put business logic in src/lib/server/<feature>-service.ts and expose it via a thin src/app/api/v1/<feature>/<op>/route.ts. Do NOT extend pb-helpers.ts.

For new pages / form components: call /api/v1/* (or have the Server Component call the corresponding lib/server/*-service.ts function directly). Do NOT import from pb-helpers.ts.

When touching a page that already uses pb-helpers.ts: migrate its data calls to the service layer in the same commit. One resource at a time, no big-bang rewrites. Lowest-risk order: customers → orders → quotes → suppliers → companies → everything else.

How to spot you're reaching for the legacy layer

pb-helpers.ts is the file in question. Grep for it:

grep -rn 'from "@/lib/pb-helpers"' src/ --include="*.ts" --include="*.tsx"

Each hit is one caller to migrate when you're in the area.

Migration cost indicator

A single page rewrite is typically: replace getX(...) import + call with await fetch('/api/v1/x?...') (or a direct service-layer call), then update the page to await. Half-day budget for 5 pages if no weird cross-resource joins are involved.