Summary

StatusCountMeaning
EXISTS5Wired or already implemented — integrate now
PARTIAL1Read side works, gaps documented (collections-orders)
GAP23No BE endpoint — hand to backend team
misWired1Wrong URL in RTK slice — real path bug
1. EXISTS 2. PARTIAL 3. GAP → BE 4. misWired 5. Recommended Order

1. EXISTS — Integrate Now

EXISTS scanners Wire MAPPERS["scanners"] in apiAdapter.ts — RTK slice already complete

Endpoints

MethodPathRTK HookCanvas Source
GETcompany/scannersuseGetScannersQueryscannersApi.ts:44-53
POSTcompany/scanneruseCreateScannerMutationscannersApi.ts:55-62
POSTpost-auditor-update-scanneruseUpdateScannerMutationscannersApi.ts:95-102
PUTcompany/scanner/transfer/:iduseTransferScannerMutationscannersApi.ts:103-110
GETget-list-scanner-status-conditionuseGetScannerStatusConditionQueryscannersApi.ts:63-94

Adapter mapper to add to apps/dashboard/src/lib/apiAdapter.ts

// Add imports:
// import scannersApi from "@/features/scanners/services/scannersApi";
// import type { BeScanner } from "@/features/scanners/services/scannersApi";

function mapBeScannerToAdapterRow(s: BeScanner): Record<string, unknown> {
  return {
    id: String(s.id),
    serial_number: s.code,
    model: s.type ?? "",
    name: s.name,
    status: s.status ?? "active",
    status_id: s.status_id,
    condition: s.condition ?? "",
    condition_id: s.condition_id,
    type: s.type ?? "",
    type_id: s.type_id,
    auditor: s.auditor ?? null,
    assigned_date: s.assigned_date ?? null,
    created_at: s.assigned_date ?? "",
  };
}

MAPPERS["scanners"] = {
  fetch: async (filters) => {
    try {
      const result = await store
        .dispatch(
          scannersApi.endpoints.getScanners.initiate({
            page: 1,
            number_per_page: 100,
            sort: "desc",
            sort_by: "assigned_date",
          }),
        )
        .unwrap();
      const rows = (result.data ?? []).map(mapBeScannerToAdapterRow);
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      console.error("MAPPERS[scanners] fetch failed:", error);
      return { data: null, error: error as Error };
    }
  },
};

// scanner_facility_assignments: GAP noop stub stays until BE ships the endpoint.

Caveats

  1. useScanners.ts already bypasses the adapter (calls useGetScannersQuery directly). Mapper covers only residual supabase.from("scanners") call sites (e.g. confidence.ts).
  2. scanner_facility_assignments is a confirmed GAP. Scanners page degrades gracefully: facility column shows "Pending assignment". Confidence score underweights scanner count until BE ships.
  3. "last used by" column uses a mock hash function — no BE audit-log source. Intentional.
  4. No DELETE /company/scanner endpoint exists. Delete button already disabled.
  5. transferScanner takes user_id (numeric), NOT facility_id. "Assign to Facility" dialog intentionally disabled (BE-blocked).
  6. Dashboard scannersApi already uses baseApi.injectEndpoints with 'Scanner' tag. No tagTypes change needed.

BE spec for scanner_facility_assignments (GAP — hand to backend)

Implied columns: id (int), scanner_id (int FK scanners.id), facility_id (int FK facilities.id), created_at (timestamp). Scoped to auth token company.

GET /company/scanner-facility-assignments{ data: Array<{ id, scanner_id, facility_id, created_at }> }
POST /company/scanner-facility-assignments body: { scanner_id: number; facility_id: number }
DELETE /company/scanner-facility-assignments/:id

EXISTS audits MAPPERS["audits"] already wired at apiAdapter.ts:226-254 — one fix noted

Endpoints

MethodPathRTK HookCanvas Source
GETsat-auditsuseGetSatAuditsQueryauditApi.ts:17 (SAT), auditsApi.ts:70 (Dashboard)
POSTsat-audituseSubmitSatAuditMutationauditApi.ts:33 (SAT)
GETuser/audits/:uid/attribute-templateuseGetOrderProcessAuditTemplateQuerycanvas auditApi.ts:301

Adapter mapper (already live — reproduced with fix)

function mapSatAuditToRow(a: SatAuditItem): Record<string, unknown> {
  // Fix: read both purchase_order and po_number (field name varies by BE version)
  const poNumber = (a.reference as any)?.purchase_order
    ?? (a.reference as any)?.po_number
    ?? null;
  return {
    id: String(a.id),
    session_id: a.id,          // no real session join; audit id as stand-in
    ft_id: a.fibretrace_id ?? null,
    blockchain_guid: null,     // no BE source on sat-audits
    result: a.status ?? null,  // "PROCESSED"/"PENDING" - see caveat 4
    notes: poNumber ? `PO: ${poNumber}` : (a.reference as any)?.notes ?? null,
    created_at: a.created_at,
    deleted_at: null,
  };
}

Caveats

  1. Reference field name: dashboard uses po_number, canonical SAT uses purchase_order. The fix above reads both. Verify actual BE response before deploying.
  2. session_id uses audit's own id. Sessions column shows "—" — correct behavior.
  3. blockchain_guid always null. AuditsTable does not render this column.
  4. result maps "PROCESSED"/"PENDING" to Lovable's "pass"/"fail"/"warning" column. Badge color falls through to "warning" style — semantic mismatch until BE exposes pass/fail/warning.
  5. All AdminVerifications mutations already stub-blocked with toast.info(). No call-site RTK migration needed.
  6. Dashboard auditsApi.ts does not yet register 'Audits' tag in baseApi.tagTypes. keepUnusedDataFor: 0 is used instead — works but prevents cross-endpoint cache invalidation.
EXISTS companies-public MAPPERS["companies"] already live — task is DONE

Endpoints

MethodPathRTK HookCanvas Source
GETpublic-companiesuseGetPublicCompaniesQuerycompanyApi.ts:210-217

Adapter mapper (already live at apiAdapter.ts:349-371 — DO NOT re-apply)

function mapPublicCompanyToRow(c: PublicCompanyBeRow): Record<string, unknown> {
  return {
    id: String(c.uid ?? c.id ?? ""),
    name: c.name ?? "",
    confidence_score: null,    // no BE source; Lovable-only field
    confidence_rating: null,   // no BE source; Lovable-only field
    confidence_summary: null,  // no BE source; Lovable-only field
  };
}

Caveats

  1. confidence_score / confidence_rating hard-coded null — BE returns only uid + name. Confidence UI remains empty until BE adds those fields.
  2. No mutations target the companies table. usePartners.ts mutations all target company_partners — separate scope.
  3. Slice (companiesPublicApi.ts) and MAPPERS["companies"] are both already live. No new files or edits required.
EXISTS invoices MAPPERS["invoices"] already present at apiAdapter.ts:322 — no action needed

Endpoints

MethodPathRTK HookCanvas Source
GETcompany/invoicesuseGetCompanyInvoicesQuerybillingApi.ts:15-24
GETcompany/invoices/:codeuseGetInvoiceByCodeQuerybillingApi.ts:26-31

Field mapping (BE → Lovable)

BE fieldLovable fieldNotes
codeinvoice_number
po_numberpurchase_order
total (string)amount (number)Parsed with Number()
created_atissued_at
pdf_urlNo BE source; mapped to ""
notesNo BE source; mapped to ""
companiesNo join on list endpoint; null

Caveats

  1. Already implemented — no new files or edits needed for the fetch path.
  2. companies(name) join: list endpoint does not join company info. Column always shows "—".
  3. pdf_url and notes are Lovable-only — mapped to empty strings.
  4. Admin invoice CRUD mutations already throw not_implemented. No canvas write equivalent.
  5. Verify BE actually returns paginated shape { data: Invoice[]; meta: { total: number } }.
EXISTS notification-prefs SettingsNotifications.tsx fully wired — notification_templates is a GAP

Endpoints (both already in userApi.ts)

MethodPathRTK HookCanvas Source
GETuseruseGetUserQueryuserApi.ts:55-60
PUTv2/useruseUpdateNotificationEmailsMutationuserApi.ts:70-74

notification_templates — BE spec (GAP)

Lovable supabase shape (integrations/supabase/types.ts:755-769):

Row: { id: string, created_at: string, description: string, is_default_enabled: boolean, label: string }

Required operations:
GET /notification-templates — list all, ordered by created_at
POST /notification-templates — create { label, description, is_default_enabled }
PUT /notification-templates/:id — update
DELETE /notification-templates/:id — delete

Do NOT ask BE to build a notification_preferences table — per-user opt-ins must remain in user.notification_emails (existing PUT /api/v2/user pattern).

Caveats

  1. SettingsNotifications.tsx is FULLY WIRED. The apiAdapter no-op stub is a crash-guard only.
  2. notification_templates is a genuine GAP. AdminNotifications.tsx already has TODO comments and toast stubs for all mutations — UI does not crash.
  3. PUT /api/v2/user: VITE_API_URL must be the bare API origin (not ending in /api/v1). Verify .env before shipping.
  4. Canvas gates 3 admin-only OPT_IN keys behind is_company_admin. Dashboard shows all 10 unconditionally. Wire user.is_company_admin if product requires gating.

2. PARTIAL — Integrate Read Part, Flag the Rest

PARTIAL collections-orders Collections wired; sessions are a GAP; verifications partially wrappable

Endpoints

MethodPathRTK HookSourceStatus
GETordersuseGetOrdersQueryorderApi.ts:32 WIRED (collections)
GETorders/:orderCodeuseGetOrderByCodeQueryorderApi.ts:278 EXISTS
GETorders/:orderCode/processesuseGetOrderProcessesQueryprocessesApi.ts:24 BASIS for verifications
GETorders/:orderCode/processes/:processIduseGetOneProcessQueryprocessesApi.ts:29 Needs slice extension

Adapter changes (apply these)

// MAPPERS["collections"] is ALREADY wired (apiAdapter.ts:388-408) — DO NOT replace.

// MAPPERS["verifications"] — wire to GET /orders/:code/processes.
// Only works for filtered calls (.eq("collection_id", orderCode)).
MAPPERS["verifications"] = {
  fetch: async (filters) => {
    try {
      const orderCode = filters.eq?.["collection_id"] as string | undefined;
      if (!orderCode) {
        return { data: [], error: null };
      }
      const result = await store
        .dispatch(
          collectionsApi.endpoints.getOrderProcesses.initiate(orderCode),
        )
        .unwrap();
      const rows = (result.data ?? []).map((p: OrderProcess) =>
        mapProcessToVerification(p, orderCode),
      );
      return { data: applyFilters(rows, filters) as unknown[], error: null };
    } catch (error) {
      console.error("MAPPERS[verifications] fetch error:", error);
      return { data: null, error: error as Error };
    }
  },
};

function mapProcessToVerification(
  p: OrderProcess,
  orderCode: string,
): Record<string, unknown> {
  return {
    id: String(p.id),
    collection_id: orderCode,
    name: (p as any).name ?? `Process ${p.id}`,
    status: (p as any).status != null ? String((p as any).status) : "active",
  };
}

// MAPPERS["sessions"] — GAP stub (must NOT wire to processes; field-set mismatch).
MAPPERS["sessions"] = {
  fetch: async (_filters) => ({ data: [], error: null }),
};

// MAPPERS["session_shares"] — GAP stub.
MAPPERS["session_shares"] = {
  fetch: async () => ({ data: [], error: null }),
};

Slice extension — add getOneProcess to collectionsApi.ts

getOneProcess: builder.query<{ data: OrderProcess }, { orderCode: string; processId: string | number }>({
  query: ({ orderCode, processId }) => ({
    url: `orders/${orderCode}/processes/${processId}`,
    method: 'GET',
  }),
  providesTags: (_result, _error, { orderCode, processId }) => [
    { type: 'Verifications', id: `${orderCode}-process-${processId}` },
  ],
}),

// Add to exports:
export const {
  useGetOrdersQuery,
  useLazyGetOrdersQuery,
  useGetOrderByCodeQuery,
  useLazyGetOrderByCodeQuery,
  useGetOrderProcessesQuery,
  useLazyGetOrderProcessesQuery,
  useGetOneProcessQuery,       // NEW
  useLazyGetOneProcessQuery,   // NEW
} = collectionsApi;

Call-site mutations — replace with BE-pending toast

  • CreateCollectionFlow.tsx:151-160supabase.from('collections').insert(...) — adapter throws. Replace with BE-pending toast until BE confirms POST /orders field mapping (order_name vs name).
  • CreateCollectionFlow.tsx:174-177supabase.from('verifications').update({ collection_id }) — GAP mutation. BE-pending toast.
  • AddVerificationsModal.tsx:137-140 — same GAP mutation. BE-pending toast.

BE spec for gaps

TABLE: sessions

Required columns (from SessionDetail.tsx, CollectionDetail.tsx, useVerifications.ts):

id                   uuid  PK
session_code         text  — human-readable code
conducted_at         timestamptz
fibre_detected       text  — fibre type label
scan_count           int
facility_id          uuid  FK -> facilities.id
verification_id      uuid  FK -> verifications.id
production_record_id uuid  nullable FK
auditor_name         text
pigment_id           uuid  nullable FK -> pigment_ids.id
deleted_at           timestamptz nullable

Nearest BE candidate: GET /orders/:code/processes/:id — field-set mismatch (BE lacks session_code, fibre_detected, pigment_id, facility_id, auditor_name).

Option A: Extend GET /orders/:code/processes/:id response to include a session sub-object with the above fields.
Option B: New endpoint GET /orders/:code/sessions returning the above shape per-row.

TABLE: verifications — BE spec: extend GET /orders/:code/processes items to include { name, status, collection_id (= order_code) }.

Caveats

  1. COLLECTIONS mapper is ALREADY wired and correct. The useCollections hook computes verificationCount/sessionCount/scanCount via sub-queries — these will be 0 until verifications and sessions are wired.
  2. VERIFICATIONS mapper only works for filtered calls. Unfiltered calls (by facility_id) return [].
  3. SESSIONS mapper: ProcessProcesList is missing 6 required fields. A new BE endpoint or response extension is required. SessionDetail.tsx and VerificationPreview.tsx will return empty/null data.
  4. programme_id in CollectionRow mapped to null — fibre_programmes is a Lovable-only table. Consumers resolving fibre_type via programme_id always get empty string.
  5. session_shares: is_shared / shared_with_company_name fields always false/null.

3. GAP — Hand to Backend Team

These 23 tables have no confirmed Fibretrace REST endpoint. Adapter stubs return [] to prevent crashes.

sessions

Lovable concept (grouped scan event within a verification). Nearest BE: /orders/:code/processes/:id but field set (session_code, conducted_at, fibre_detected, pigment_id) has no faithful mapping. Consumers: CollectionDetail.tsx, SessionDetail.tsx, DirectorySearchModal.tsx, AddVerificationsModal.tsx, LinkVerificationModal.tsx, useVerifications.ts, useGuideSignals.ts, ProducerDashboard.tsx. See PARTIAL section for full BE spec.

session_shares

Lovable-only many-to-many join for sharing sessions between companies. Canvas POST /user/audits/:uid/send has different semantics. Consumers: useVerifications.ts, AdminVerifications.tsx.

verifications

Lovable join layer between collections and sessions. Canvas order-process hierarchy does not produce the required field set. Partial wire possible — see PARTIAL section. Consumers: CollectionDetail.tsx, VerificationPreview.tsx, CreateCollectionFlow.tsx, AddVerificationsModal.tsx, useVerifications.ts.

fibre_programmes

Entirely new BE domain. MAPPERS["fibre_programmes"] has a TODO: 'wire to /programs endpoint once BE confirms'. Canvas inventory has zero /programs or /fibre-programmes routes across all 6 inventory files. Consumers: useProgrammes.ts, useEvidenceRequests.ts, useProgrammeHeatmap.ts, FibreCreation.tsx, AdminProgrammes.tsx.

programme_participants • programme_producers • programme_producer_facilities • programme_reservations

Part of the Programmes feature domain. No canvas or legacy endpoint for any of these. Consumers: useProgrammes.ts, AdminProgrammes.tsx.

pigment_ids

Lovable-native fibre pigment tracking concept. No canvas or legacy /pigment-ids endpoint. Consumers: useProgrammes.ts, useProgrammeHeatmap.ts, AdminPigmentIds.tsx, SessionDetail.tsx.

pigment_orders • pigment_company_assignments

Lovable-native pigment ordering and pigment-to-company join. No canvas or legacy endpoints. Consumers: useProgrammes.ts, useProgrammeHeatmap.ts.

production_records

Fibre production / SDU claim flow concept. No canvas or legacy /production-records endpoint. Note: sduApi.ts correctly wires BE SDU device read endpoints (GET /sdu/devices etc. per Nathan spec 2026-05-29) — those are separate from this Lovable supabase table. Consumers: FibreCreation.tsx, LogProductionModal.tsx, useGuideSignals.ts, AdminFibreProduction.tsx.

production_claims

Production claim flow concept. No canvas or legacy /production-claims endpoint. Consumers: FibreCreation.tsx, usePersona.ts, AdminFibreProduction.tsx.

sliver_delivery_units

SDU CRUD (create/update/delete) has no canvas or legacy endpoint. READ side is already wired via sduApi.tsGET /sdu/devices (Nathan spec 2026-05-29) — called directly via hooks, not through this adapter. The supabase.from("sliver_delivery_units") noop remains for Lovable code paths. Consumers: SDU.tsx, AdminSDU.tsx, useGuideSignals.ts.

evidence_requests • evidence_request_shares

Lovable-native evidence pack workflow and share join table. No canvas or legacy /evidence-requests endpoint. Consumers: useEvidenceRequests.ts, EvidencePack.tsx.

facility_licenses

Admin-only facility licensing concept. No canvas or legacy /facility-licenses endpoint. Consumers: AdminFacilities.tsx, AdminLicensing.tsx.

onboarding_tasks

Admin onboarding wizard concept. No canvas or legacy /onboarding-tasks endpoint. Consumers: AdminOnboarding.tsx.

company_details

Admin-only company metadata with direct insert/update. Canvas closest: POST /company-meta but semantics differ (company_code field + direct CRUD vs metadata query). Multiple candidates, no clean 1:1. Consumers: AdminCompanies.tsx, EvidencePack.tsx.

company_users

Admin direct CRUD on users. Canvas GET /users exists (covered by teamApi.ts) but AdminUsers.tsx calls insert/update/delete which map to useSetUserStatusMutation/useDeleteUserMutation/useRestoreUserMutation with different semantics than direct CRUD. Consumers: AdminUsers.tsx, AdminCompanies.tsx.

notification_templates_mutations

Admin insert/update/delete on notification templates. No canvas write endpoint. Read path (GET /service-tab) is separately flagged as fixable. Supabase shape: { id, created_at, description, is_default_enabled, label }. BE spec: GET/POST /notification-templates, PUT/DELETE /notification-templates/:id. Consumers: AdminNotifications.tsx.

scanner_facility_assignments

Lovable-only join table between scanners and facilities. Canvas GET /company/scanners returns count_assigned_scanner in meta but no flat join table endpoint. useScannerAssignments in useScanners.ts calls supabase.from("scanner_facility_assignments") which hits the noop. Consumers: ProducerDashboard.tsx, useScanners.ts.

BE spec: GET /company/scanner-facility-assignments{ data: Array<{ id, scanner_id, facility_id, created_at }> }; POST body { scanner_id, facility_id }; DELETE /:id.

companies_confidence_score

PARTIAL: MAPPERS["companies"] is wired to GET /public-companies (returns uid + name). The confidence_score and confidence_rating fields are Lovable-only and resolve to null — no Fibretrace canvas or legacy endpoint returns these fields. mapPublicCompanyToRow in apiAdapter.ts:349 hardcodes both null. Consumers: useConfidence.ts, useVerifications.ts, useProgrammeHeatmap.ts, AdminCronJobs.tsx.

4. misWired — Real Path Bugs

misWired apps/dashboard/src/services/userApi.ts:97 — POST upload-avatar
FieldValue
Slice fileapps/dashboard/src/services/userApi.ts:97
Bad pathPOST upload-avatar
ConsumerSettingsAccount.tsx:126
EvidenceNot present in any of the 6 canvas inventory files (01-06-*.md); not in legacy storefront/b2c/fim API files; flagged in gap-scan-2026-05-31/canvas-missed.json as 'new endpoint not in scanned 24'. Canvas media upload goes through POST /media (commonApi.ts:60 or processesApi).
Suggested fixPOST /media (commonApi.ts useUploadToS3Mutation) — canvas avatar/logo uploads go through the shared media endpoint. Verify with BE whether /upload-avatar is a real separate endpoint or should route to /media.

5. Recommended Order

  1. Fix misWireduserApi.ts:97 upload-avatar path. 1 file, low risk. Verify with BE first whether /upload-avatar is a real endpoint or should route to /media.
  2. Wire scanners adapter mapperapiAdapter.ts: add MAPPERS["scanners"] block + imports. RTK slice already complete. Unblocks confidence.ts fallback paths.
  3. Wire verifications adapter mapperapiAdapter.ts: add MAPPERS["verifications"] + mapProcessToVerification + session/session_shares stubs. Extend collectionsApi.ts with getOneProcess. Unblocks CollectionDetail.tsx filtered views.
  4. Fix mutation crash sitesCreateCollectionFlow.tsx and AddVerificationsModal.tsx: replace supabase mutation calls with BE-pending toasts so the UI does not crash.
  5. notification_templates read (fixable) — new RTK endpoint for GET /service-tab → wire MAPPERS["notification_templates"] read path. Admin mutations remain BE-blocked.
  6. Hand BE spec to backend — sessions, verifications field extension, scanner_facility_assignments, notification_templates CRUD, fibre_programmes. See Section 3 for full specs.
  7. After each BE endpoint ships — wire the corresponding adapter mapper + RTK slice + remove noop stub. Repeat until all GAPs are resolved.
Note on Lovable-only domains: fibre_programmes, pigment_ids, pigment_orders, pigment_company_assignments, programme_* tables, evidence_requests, evidence_request_shares, facility_licenses, onboarding_tasks, production_records, production_claims are entirely new BE domains with no canvas or legacy equivalent. These require new BE feature work, not just endpoint mapping. Prioritize these in coordination with the product roadmap.