@burdenoff/microfe-movethewheels 2026.1010.2 → 2026.1011.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/MoveTheWheelsRoot.js +12 -1
  2. package/dist/MoveTheWheelsRoutes.js +57 -49
  3. package/dist/generated/shipment-status-authority-operations.d.ts +4473 -0
  4. package/dist/generated/tracking-quality-operations.d.ts +4302 -0
  5. package/dist/hooks/useEpod.js +85 -77
  6. package/dist/hooks/useShipmentStatusAuthority.js +127 -0
  7. package/dist/hooks/useTracking.js +15 -14
  8. package/dist/hooks/useTrackingEventQuality.js +60 -0
  9. package/dist/movethewheels/hooks/useEpod.d.ts +23 -0
  10. package/dist/movethewheels/hooks/useShipmentStatusAuthority.d.ts +23 -0
  11. package/dist/movethewheels/hooks/useTrackingEventQuality.d.ts +18 -0
  12. package/dist/movethewheels/pages/ShipmentStatusAuthorityPage.d.ts +1 -0
  13. package/dist/movethewheels/pages/TrackingEventQualityPage.d.ts +1 -0
  14. package/dist/movethewheels/utils/shipmentStatusDraft.d.ts +45 -0
  15. package/dist/node_modules/lucide-react/dist/esm/icons/radio-tower.js +36 -0
  16. package/dist/pages/DeliveryCapturePage.js +181 -116
  17. package/dist/pages/ShipmentStatusAuthorityPage.js +563 -0
  18. package/dist/pages/TrackingEventQualityPage.js +224 -0
  19. package/dist/src/generated/shipment-status-authority-operations.js +86 -0
  20. package/dist/src/generated/tracking-quality-operations.js +44 -0
  21. package/dist/utils/shipmentStatusDraft.js +41 -0
  22. package/package.json +16 -7
  23. package/patches/README.md +25 -0
  24. package/patches/braces@3.0.3.patch +87 -0
  25. package/src/generated/shipment-status-authority-operations.ts +5503 -0
  26. package/src/generated/tracking-quality-operations.ts +5323 -0
  27. package/src/movethewheels/MoveTheWheelsRoot.tsx +14 -0
  28. package/src/movethewheels/MoveTheWheelsRoutes.tsx +4 -0
  29. package/src/movethewheels/hooks/useEpod.ts +47 -12
  30. package/src/movethewheels/hooks/useShipmentStatusAuthority.ts +179 -0
  31. package/src/movethewheels/hooks/useTracking.ts +27 -12
  32. package/src/movethewheels/hooks/useTrackingEventQuality.ts +110 -0
  33. package/src/movethewheels/pages/DeliveryCapturePage.tsx +113 -6
  34. package/src/movethewheels/pages/ShipmentStatusAuthorityPage.tsx +639 -0
  35. package/src/movethewheels/pages/TrackingEventQualityPage.tsx +250 -0
  36. package/src/movethewheels/utils/shipmentStatusDraft.ts +118 -0
  37. package/src/operations/shipment-status-authority/status.graphql +80 -0
  38. package/src/operations/tracking-quality/quality.graphql +15 -0
@@ -0,0 +1,250 @@
1
+ import { useState } from 'react';
2
+ import { RadioTower } from 'lucide-react';
3
+ import { PageLayout } from '../components/PageLayout';
4
+ import { Button } from '../components/ui';
5
+ import { reuseCard, reuseSelect, ReuseState } from '../components/ReuseControls';
6
+ import { useConditionQuery } from '../hooks/useContainerCondition';
7
+ import { useMoveTheWheels } from '../providers/MoveTheWheelsProvider';
8
+ import {
9
+ QualityIntegrationsDocument,
10
+ QualityDeliveriesDocument,
11
+ QualityShipmentDocument,
12
+ trackingOutcomes,
13
+ } from '../hooks/useTrackingEventQuality';
14
+ import type {
15
+ TrackingQualityIntegrationsQuery,
16
+ TrackingQualityDeliveriesQuery,
17
+ TrackingQualityShipmentQuery,
18
+ } from '../../generated/tracking-quality-operations';
19
+
20
+ const LIMIT = 20;
21
+ function Pager({
22
+ offset,
23
+ length,
24
+ busy,
25
+ onChange,
26
+ }: {
27
+ offset: number;
28
+ length: number | undefined;
29
+ busy: boolean;
30
+ onChange: (offset: number) => void;
31
+ }) {
32
+ return (
33
+ <div className="flex flex-wrap items-center gap-3">
34
+ <Button
35
+ variant="outline"
36
+ disabled={busy || offset === 0}
37
+ onClick={() => onChange(Math.max(0, offset - LIMIT))}
38
+ >
39
+ Previous page
40
+ </Button>
41
+ <span>{length === undefined ? 'Page unavailable' : `${length} records on this page`}</span>
42
+ <Button
43
+ variant="outline"
44
+ disabled={busy || length !== LIMIT || offset >= 9980}
45
+ onClick={() => onChange(offset + LIMIT)}
46
+ >
47
+ Next page
48
+ </Button>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default function TrackingEventQualityPage() {
54
+ const { workspaceId, tenantId, currentUser, apiGatewayUrl } = useMoveTheWheels();
55
+ return (
56
+ <PageLayout
57
+ title="Tracking event quality"
58
+ subtitle="Review delivery results before investigating a shipment."
59
+ icon={<RadioTower aria-hidden="true" />}
60
+ >
61
+ <QualityWorkbench key={`${workspaceId}:${tenantId}:${currentUser?.id}:${apiGatewayUrl}`} />
62
+ </PageLayout>
63
+ );
64
+ }
65
+ function QualityWorkbench() {
66
+ const [integrationOffset, setIntegrationOffset] = useState(0);
67
+ const [selected, setSelected] = useState('');
68
+ const [offset, setOffset] = useState(0);
69
+ const [revision, setRevision] = useState(0);
70
+ const [shipmentId, setShipmentId] = useState<string | null>(null);
71
+ const integrations = useConditionQuery<TrackingQualityIntegrationsQuery>(
72
+ QualityIntegrationsDocument,
73
+ { pagination: { limit: LIMIT, offset: integrationOffset } },
74
+ revision
75
+ );
76
+ const deliveries = useConditionQuery<TrackingQualityDeliveriesQuery>(
77
+ QualityDeliveriesDocument,
78
+ selected ? { id: selected, pagination: { limit: LIMIT, offset } } : null,
79
+ revision
80
+ );
81
+ const options = integrations.data?.integrations;
82
+ const records = deliveries.data?.integration?.events;
83
+ return (
84
+ <div className="space-y-6">
85
+ <p>
86
+ Matching event keys and payloads create no additional tracking event. A changed payload with
87
+ the same key is a conflict. Updates without a key can create new events.
88
+ </p>
89
+ <p>
90
+ Each delivery admits at most five updates. An oversized or malformed delivery is rejected
91
+ before updates are recorded. Delivery history can include older records without item
92
+ results.
93
+ </p>
94
+ <section className={reuseCard} aria-label="Integration selection">
95
+ <ReuseState loading={integrations.loading} error={integrations.error} />
96
+ {options?.length === 0 && <p>No integrations on this page.</p>}
97
+ <label className="block">
98
+ Integration
99
+ <select
100
+ className={reuseSelect}
101
+ value={selected}
102
+ disabled={!options || integrations.loading}
103
+ onChange={(event) => {
104
+ setSelected(event.target.value);
105
+ setOffset(0);
106
+ setShipmentId(null);
107
+ }}
108
+ >
109
+ <option value="">Choose an integration</option>
110
+ {options?.map((integration) => (
111
+ <option key={integration.id} value={integration.id}>
112
+ {integration.name} · {integration.provider} · {integration.status}
113
+ </option>
114
+ ))}
115
+ </select>
116
+ </label>
117
+ <Pager
118
+ offset={integrationOffset}
119
+ length={options?.length}
120
+ busy={integrations.loading}
121
+ onChange={(next) => {
122
+ setIntegrationOffset(next);
123
+ setSelected('');
124
+ setOffset(0);
125
+ setShipmentId(null);
126
+ }}
127
+ />
128
+ </section>
129
+ {selected && (
130
+ <section className={reuseCard} aria-label="Tracking delivery history">
131
+ <div className="flex flex-wrap items-center justify-between gap-3">
132
+ <h2 className="text-lg font-semibold">Delivery history</h2>
133
+ <Button
134
+ variant="outline"
135
+ disabled={deliveries.loading}
136
+ onClick={() => setRevision((value) => value + 1)}
137
+ >
138
+ Refresh results
139
+ </Button>
140
+ </div>
141
+ <ReuseState loading={deliveries.loading} error={deliveries.error} />
142
+ {deliveries.data?.integration === null && (
143
+ <p role="status">Integration unavailable in this workspace.</p>
144
+ )}
145
+ {records?.length === 0 && <p>No deliveries on this page.</p>}
146
+ {records?.map((delivery) => {
147
+ const outcomes = trackingOutcomes(delivery.outcomes);
148
+ return (
149
+ <article key={delivery.id} className="space-y-3 rounded-lg border p-4">
150
+ <h3 className="font-semibold">
151
+ {delivery.status} · {new Date(delivery.receivedAt).toLocaleString()}
152
+ </h3>
153
+ {outcomes === null ? (
154
+ <p>Per-update results unavailable for this delivery.</p>
155
+ ) : outcomes.length === 0 ? (
156
+ <p>
157
+ No normalized updates were attempted. This is not a count of raw sender items.
158
+ </p>
159
+ ) : (
160
+ <ol className="space-y-3">
161
+ {outcomes.map((outcome) => (
162
+ <li key={outcome.index} className="break-words rounded-md bg-bg-surface p-3">
163
+ <p>
164
+ Update {outcome.index + 1}: <strong>{outcome.status}</strong>
165
+ </p>
166
+ <p>Event key: {outcome.externalEventId ?? 'Missing — not deduplicated'}</p>
167
+ <p>Shipment reference: {outcome.shipmentReference || 'Not recorded'}</p>
168
+ {outcome.errorCode && (
169
+ <p>Reason: {outcome.errorCode.replaceAll('_', ' ').toLowerCase()}</p>
170
+ )}
171
+ {(outcome.status === 'ACCEPTED' || outcome.status === 'DUPLICATE') && (
172
+ <p>
173
+ Status decision:{' '}
174
+ {outcome.projection?.status === 'APPLIED'
175
+ ? 'Applied to the recorded shipment status'
176
+ : outcome.projection?.status === 'HELD'
177
+ ? `Held — ${outcome.projection.heldReason?.replaceAll('_', ' ').toLowerCase()}`
178
+ : 'No status decision recorded in this legacy result'}
179
+ </p>
180
+ )}
181
+ {outcome.status === 'CONFLICT' && (
182
+ <p>The earlier receipt stays unchanged; this update adds no event.</p>
183
+ )}
184
+ {outcome.shipmentId && (
185
+ <Button
186
+ variant="outline"
187
+ onClick={() => setShipmentId(outcome.shipmentId)}
188
+ >
189
+ View shipment
190
+ </Button>
191
+ )}
192
+ </li>
193
+ ))}
194
+ </ol>
195
+ )}
196
+ </article>
197
+ );
198
+ })}
199
+ <Pager
200
+ offset={offset}
201
+ length={records?.length}
202
+ busy={deliveries.loading}
203
+ onChange={setOffset}
204
+ />
205
+ </section>
206
+ )}
207
+ {shipmentId && (
208
+ <QualityShipment key={shipmentId} id={shipmentId} onClose={() => setShipmentId(null)} />
209
+ )}
210
+ <p className="text-sm text-text-secondary">
211
+ These results describe recorded ingestion, not physical shipment truth. The chosen-source
212
+ clock policy and manual corrections control recorded shipment status. Provider corrections
213
+ and replay need separate reviewed policies.
214
+ </p>
215
+ </div>
216
+ );
217
+ }
218
+
219
+ function QualityShipment({ id, onClose }: { id: string; onClose: () => void }) {
220
+ const query = useConditionQuery<TrackingQualityShipmentQuery>(QualityShipmentDocument, { id });
221
+ return (
222
+ <section className={reuseCard} aria-label="Shipment tracking summary">
223
+ <h2 className="text-lg font-semibold">Shipment tracking summary</h2>
224
+ <Button variant="outline" onClick={onClose}>
225
+ Close shipment summary
226
+ </Button>
227
+ <ReuseState loading={query.loading} error={query.error} />
228
+ {query.data?.shipment === null && <p>Shipment unavailable in this workspace.</p>}
229
+ {query.data?.shipment && (
230
+ <>
231
+ <p>
232
+ {query.data.shipment.shipmentNumber} · {query.data.shipment.status}
233
+ </p>
234
+ <p>Tracking number: {query.data.shipment.trackingNumber ?? 'Not recorded'}</p>
235
+ <p>
236
+ {query.data.shipment.trackingEvents.length} tracking events on the first page (up to
237
+ 20).
238
+ </p>
239
+ <ul>
240
+ {query.data.shipment.trackingEvents.map((event) => (
241
+ <li key={event.id}>
242
+ {event.eventType} · {new Date(event.timestamp).toLocaleString()}
243
+ </li>
244
+ ))}
245
+ </ul>
246
+ </>
247
+ )}
248
+ </section>
249
+ );
250
+ }
@@ -0,0 +1,118 @@
1
+ export interface ShipmentSourceDraft {
2
+ integrationId: string;
3
+ basisTrackingEventId: string;
4
+ maxSourceAgeSeconds: string;
5
+ maxFutureSkewSeconds: string;
6
+ reason: string;
7
+ }
8
+
9
+ export function statusDecisionReason(value: string): string {
10
+ const reason = value.trim();
11
+ if (!reason || reason.length > 1000) throw new Error('Enter a reason of 1–1000 characters.');
12
+ return reason;
13
+ }
14
+
15
+ function explicitSeconds(value: string, minimum: number, maximum: number, label: string): number {
16
+ if (!/^\d+$/.test(value.trim()))
17
+ throw new Error(`${label} must be an explicit whole number of seconds.`);
18
+ const result = Number(value.trim());
19
+ if (!Number.isSafeInteger(result) || result < minimum || result > maximum)
20
+ throw new Error(`${label} must be between ${minimum} and ${maximum} seconds.`);
21
+ return result;
22
+ }
23
+
24
+ export function shipmentSourceInput(draft: ShipmentSourceDraft, expectedStatusRevision: number) {
25
+ if (!draft.integrationId || !draft.basisTrackingEventId)
26
+ throw new Error('Choose one integration and one recorded basis event.');
27
+ return {
28
+ expectedStatusRevision,
29
+ integrationId: draft.integrationId,
30
+ basisTrackingEventId: draft.basisTrackingEventId,
31
+ maxSourceAgeSeconds: explicitSeconds(
32
+ draft.maxSourceAgeSeconds,
33
+ 1,
34
+ 2592000,
35
+ 'Maximum source age'
36
+ ),
37
+ maxFutureSkewSeconds: explicitSeconds(
38
+ draft.maxFutureSkewSeconds,
39
+ 0,
40
+ 86400,
41
+ 'Allowed future skew'
42
+ ),
43
+ reason: statusDecisionReason(draft.reason),
44
+ };
45
+ }
46
+
47
+ interface StatusAcknowledgement {
48
+ id: string;
49
+ status: string;
50
+ statusRevision: number;
51
+ statusAuthority: string;
52
+ statusAuthorityEpoch: number;
53
+ designatedIntegrationId?: string | null;
54
+ statusBasisEventId?: string | null;
55
+ statusBasisSourceTime?: string | null;
56
+ maxSourceAgeSeconds?: number | null;
57
+ maxFutureSkewSeconds?: number | null;
58
+ }
59
+
60
+ export function sourceStatusAcknowledged(
61
+ actual: StatusAcknowledgement | null | undefined,
62
+ expected: {
63
+ id: string;
64
+ epoch: number;
65
+ input: ReturnType<typeof shipmentSourceInput>;
66
+ basis: { id: string; eventType: string; providerEventTime?: string | null } | undefined;
67
+ }
68
+ ): boolean {
69
+ const statuses: Record<string, string> = {
70
+ PICKED_UP: 'PICKED_UP',
71
+ IN_TRANSIT: 'IN_TRANSIT',
72
+ ARRIVED: 'IN_TRANSIT',
73
+ DEPARTED: 'IN_TRANSIT',
74
+ OUT_FOR_DELIVERY: 'IN_TRANSIT',
75
+ DELIVERED: 'DELIVERED',
76
+ FAILED: 'FAILED',
77
+ RETURNED: 'RETURNED',
78
+ };
79
+ const basis = expected.basis;
80
+ const time = basis?.providerEventTime ? Date.parse(basis.providerEventTime) : NaN;
81
+ return (
82
+ !!actual &&
83
+ !!basis &&
84
+ Number.isFinite(time) &&
85
+ actual.id === expected.id &&
86
+ actual.statusRevision === expected.input.expectedStatusRevision + 1 &&
87
+ actual.statusAuthority === 'INTEGRATION_EVENT_TIME' &&
88
+ actual.statusAuthorityEpoch === expected.epoch + 1 &&
89
+ actual.designatedIntegrationId === expected.input.integrationId &&
90
+ actual.statusBasisEventId === basis.id &&
91
+ basis.id === expected.input.basisTrackingEventId &&
92
+ typeof actual.statusBasisSourceTime === 'string' &&
93
+ Date.parse(actual.statusBasisSourceTime) === time &&
94
+ actual.maxSourceAgeSeconds === expected.input.maxSourceAgeSeconds &&
95
+ actual.maxFutureSkewSeconds === expected.input.maxFutureSkewSeconds &&
96
+ typeof statuses[basis.eventType] === 'string' &&
97
+ actual.status === statuses[basis.eventType]
98
+ );
99
+ }
100
+
101
+ export function manualStatusAcknowledged(
102
+ actual: StatusAcknowledgement | null | undefined,
103
+ expected: { id: string; revision: number; epoch: number; status: string }
104
+ ): boolean {
105
+ return (
106
+ !!actual &&
107
+ actual.id === expected.id &&
108
+ actual.statusRevision === expected.revision + 1 &&
109
+ actual.status === expected.status &&
110
+ actual.statusAuthority === 'MANUAL_HOLD' &&
111
+ actual.statusAuthorityEpoch === expected.epoch + 1 &&
112
+ actual.designatedIntegrationId === null &&
113
+ actual.statusBasisEventId === null &&
114
+ actual.statusBasisSourceTime === null &&
115
+ actual.maxSourceAgeSeconds === null &&
116
+ actual.maxFutureSkewSeconds === null
117
+ );
118
+ }
@@ -0,0 +1,80 @@
1
+ fragment ShipmentStatusAuthorityFields on MtwShipment {
2
+ id
3
+ shipmentNumber
4
+ status
5
+ statusRevision
6
+ statusAuthority
7
+ statusAuthorityEpoch
8
+ designatedIntegrationId
9
+ statusBasisEventId
10
+ statusBasisSourceTime
11
+ maxSourceAgeSeconds
12
+ maxFutureSkewSeconds
13
+ }
14
+
15
+ query StatusAuthorityShipments($filter: ShipmentFilterInput, $pagination: PaginationInput!) {
16
+ shipments(filter: $filter, pagination: $pagination) {
17
+ ...ShipmentStatusAuthorityFields
18
+ }
19
+ }
20
+
21
+ query StatusAuthorityDetail($id: ID!, $pagination: PaginationInput!, $evidencePagination: PaginationInput!) {
22
+ shipment(id: $id) {
23
+ ...ShipmentStatusAuthorityFields
24
+ trackingEvents(pagination: $evidencePagination) {
25
+ id
26
+ eventType
27
+ sourceIntegrationId
28
+ sourceTimestampProvenance
29
+ providerEventTime
30
+ serverReceivedAt
31
+ timestamp
32
+ }
33
+ }
34
+ shipmentStatusDecisions(shipmentId: $id, pagination: $pagination) {
35
+ id
36
+ origin
37
+ outcome
38
+ heldReason
39
+ observedRevision
40
+ resultRevision
41
+ priorStatus
42
+ resultStatus
43
+ trackingEventId
44
+ proofId
45
+ reason
46
+ recordedAt
47
+ configuredIntegrationId
48
+ configuredMaxSourceAgeSeconds
49
+ configuredMaxFutureSkewSeconds
50
+ }
51
+ }
52
+
53
+ query StatusAuthoritySources($pagination: PaginationInput!) {
54
+ integrations(pagination: $pagination) {
55
+ id
56
+ name
57
+ provider
58
+ status
59
+ }
60
+ }
61
+
62
+ mutation StatusAuthorityConfigure($id: ID!, $input: ConfigureShipmentStatusSourceInput!) {
63
+ configureShipmentStatusSource(id: $id, input: $input) {
64
+ ...ShipmentStatusAuthorityFields
65
+ }
66
+ }
67
+
68
+ mutation StatusAuthorityManual($id: ID!, $input: UpdateShipmentInput!) {
69
+ updateShipment(id: $id, input: $input) {
70
+ ...ShipmentStatusAuthorityFields
71
+ }
72
+ }
73
+
74
+ query StatusAuthorityOwnedDelivery($deliveryJobId: ID!) {
75
+ myEpodShipmentStatus(deliveryJobId: $deliveryJobId) {
76
+ id
77
+ status
78
+ statusRevision
79
+ }
80
+ }
@@ -0,0 +1,15 @@
1
+ query TrackingQualityIntegrations($pagination: PaginationInput!) {
2
+ integrations(pagination: $pagination) { id name provider status }
3
+ }
4
+ query TrackingQualityDeliveries($id: ID!, $pagination: PaginationInput!) {
5
+ integration(id: $id) {
6
+ id name provider status
7
+ events(pagination: $pagination) { id status receivedAt processedAt outcomes }
8
+ }
9
+ }
10
+ query TrackingQualityShipment($id: ID!) {
11
+ shipment(id: $id) {
12
+ id shipmentNumber status trackingNumber
13
+ trackingEvents(pagination: {limit: 20, offset: 0}) { id eventType timestamp }
14
+ }
15
+ }