Summary
| Status | Count | Meaning |
|---|---|---|
| EXISTS | 5 | Wired or already implemented — integrate now |
| PARTIAL | 1 | Read side works, gaps documented (collections-orders) |
| GAP | 23 | No BE endpoint — hand to backend team |
| misWired | 1 | Wrong URL in RTK slice — real path bug |
1. EXISTS — Integrate Now
EXISTS scanners Wire MAPPERS["scanners"] in apiAdapter.ts — RTK slice already complete
Endpoints
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | company/scanners | useGetScannersQuery | scannersApi.ts:44-53 |
| POST | company/scanner | useCreateScannerMutation | scannersApi.ts:55-62 |
| POST | post-auditor-update-scanner | useUpdateScannerMutation | scannersApi.ts:95-102 |
| PUT | company/scanner/transfer/:id | useTransferScannerMutation | scannersApi.ts:103-110 |
| GET | get-list-scanner-status-condition | useGetScannerStatusConditionQuery | scannersApi.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
useScanners.tsalready bypasses the adapter (callsuseGetScannersQuerydirectly). Mapper covers only residualsupabase.from("scanners")call sites (e.g.confidence.ts).scanner_facility_assignmentsis a confirmed GAP. Scanners page degrades gracefully: facility column shows "Pending assignment". Confidence score underweights scanner count until BE ships.- "last used by" column uses a mock hash function — no BE audit-log source. Intentional.
- No
DELETE /company/scannerendpoint exists. Delete button already disabled. transferScannertakesuser_id(numeric), NOTfacility_id. "Assign to Facility" dialog intentionally disabled (BE-blocked).- Dashboard scannersApi already uses
baseApi.injectEndpointswith 'Scanner' tag. NotagTypeschange 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
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | sat-audits | useGetSatAuditsQuery | auditApi.ts:17 (SAT), auditsApi.ts:70 (Dashboard) |
| POST | sat-audit | useSubmitSatAuditMutation | auditApi.ts:33 (SAT) |
| GET | user/audits/:uid/attribute-template | useGetOrderProcessAuditTemplateQuery | canvas 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
- Reference field name: dashboard uses
po_number, canonical SAT usespurchase_order. The fix above reads both. Verify actual BE response before deploying. session_iduses audit's ownid. Sessions column shows "—" — correct behavior.blockchain_guidalways null. AuditsTable does not render this column.resultmaps "PROCESSED"/"PENDING" to Lovable's "pass"/"fail"/"warning" column. Badge color falls through to "warning" style — semantic mismatch until BE exposes pass/fail/warning.- All AdminVerifications mutations already stub-blocked with
toast.info(). No call-site RTK migration needed. - Dashboard
auditsApi.tsdoes not yet register 'Audits' tag inbaseApi.tagTypes.keepUnusedDataFor: 0is used instead — works but prevents cross-endpoint cache invalidation.
EXISTS companies-public MAPPERS["companies"] already live — task is DONE
Endpoints
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | public-companies | useGetPublicCompaniesQuery | companyApi.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
confidence_score/confidence_ratinghard-coded null — BE returns onlyuid+name. Confidence UI remains empty until BE adds those fields.- No mutations target the
companiestable.usePartners.tsmutations all targetcompany_partners— separate scope. - Slice (
companiesPublicApi.ts) andMAPPERS["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
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | company/invoices | useGetCompanyInvoicesQuery | billingApi.ts:15-24 |
| GET | company/invoices/:code | useGetInvoiceByCodeQuery | billingApi.ts:26-31 |
Field mapping (BE → Lovable)
| BE field | Lovable field | Notes |
|---|---|---|
code | invoice_number | |
po_number | purchase_order | |
total (string) | amount (number) | Parsed with Number() |
created_at | issued_at | |
| — | pdf_url | No BE source; mapped to "" |
| — | notes | No BE source; mapped to "" |
| — | companies | No join on list endpoint; null |
Caveats
- Already implemented — no new files or edits needed for the fetch path.
companies(name)join: list endpoint does not join company info. Column always shows "—".pdf_urlandnotesare Lovable-only — mapped to empty strings.- Admin invoice CRUD mutations already throw
not_implemented. No canvas write equivalent. - 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)
| Method | Path | RTK Hook | Canvas Source |
|---|---|---|---|
| GET | user | useGetUserQuery | userApi.ts:55-60 |
| PUT | v2/user | useUpdateNotificationEmailsMutation | userApi.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
SettingsNotifications.tsxis FULLY WIRED. The apiAdapter no-op stub is a crash-guard only.notification_templatesis a genuine GAP.AdminNotifications.tsxalready has TODO comments and toast stubs for all mutations — UI does not crash.- PUT
/api/v2/user:VITE_API_URLmust be the bare API origin (not ending in/api/v1). Verify.envbefore shipping. - Canvas gates 3 admin-only OPT_IN keys behind
is_company_admin. Dashboard shows all 10 unconditionally. Wireuser.is_company_adminif product requires gating.
2. PARTIAL — Integrate Read Part, Flag the Rest
PARTIAL collections-orders Collections wired; sessions are a GAP; verifications partially wrappable
Endpoints
| Method | Path | RTK Hook | Source | Status |
|---|---|---|---|---|
| GET | orders | useGetOrdersQuery | orderApi.ts:32 |
WIRED (collections) |
| GET | orders/:orderCode | useGetOrderByCodeQuery | orderApi.ts:278 |
EXISTS |
| GET | orders/:orderCode/processes | useGetOrderProcessesQuery | processesApi.ts:24 |
BASIS for verifications |
| GET | orders/:orderCode/processes/:processId | useGetOneProcessQuery | processesApi.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-160 —
supabase.from('collections').insert(...)— adapter throws. Replace with BE-pending toast until BE confirms POST /orders field mapping (order_namevsname). - CreateCollectionFlow.tsx:174-177 —
supabase.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
- COLLECTIONS mapper is ALREADY wired and correct. The
useCollectionshook computes verificationCount/sessionCount/scanCount via sub-queries — these will be 0 until verifications and sessions are wired. - VERIFICATIONS mapper only works for filtered calls. Unfiltered calls (by facility_id) return
[]. - SESSIONS mapper:
ProcessProcesListis missing 6 required fields. A new BE endpoint or response extension is required.SessionDetail.tsxandVerificationPreview.tsxwill return empty/null data. programme_idin CollectionRow mapped to null —fibre_programmesis a Lovable-only table. Consumers resolvingfibre_typeviaprogramme_idalways get empty string.session_shares:is_shared/shared_with_company_namefields 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.ts → GET /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
| Field | Value |
|---|---|
| Slice file | apps/dashboard/src/services/userApi.ts:97 |
| Bad path | POST upload-avatar |
| Consumer | SettingsAccount.tsx:126 |
| Evidence | Not 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 fix | POST /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
- Fix misWired —
userApi.ts:97upload-avatar path. 1 file, low risk. Verify with BE first whether/upload-avataris a real endpoint or should route to/media. - Wire scanners adapter mapper —
apiAdapter.ts: addMAPPERS["scanners"]block + imports. RTK slice already complete. Unblocksconfidence.tsfallback paths. - Wire verifications adapter mapper —
apiAdapter.ts: addMAPPERS["verifications"]+mapProcessToVerification+ session/session_shares stubs. ExtendcollectionsApi.tswithgetOneProcess. Unblocks CollectionDetail.tsx filtered views. - Fix mutation crash sites —
CreateCollectionFlow.tsxandAddVerificationsModal.tsx: replace supabase mutation calls with BE-pending toasts so the UI does not crash. - notification_templates read (fixable) — new RTK endpoint for
GET /service-tab→ wireMAPPERS["notification_templates"]read path. Admin mutations remain BE-blocked. - Hand BE spec to backend — sessions, verifications field extension, scanner_facility_assignments, notification_templates CRUD, fibre_programmes. See Section 3 for full specs.
- After each BE endpoint ships — wire the corresponding adapter mapper + RTK slice + remove noop stub. Repeat until all GAPs are resolved.