Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion apps/shopify-app/app/features/delivery/route-tracking.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ function mergeRouteTrackingSnapshot(currentSnapshot, serverSnapshot) {
const mergedBase = normalizeRouteTrackingSnapshot({
...historyBase,
policy: incomingSnapshot.policy ?? current.policy,
progress: incomingSnapshot.progress,
progress: mergeTrackingProgressSnapshot(current.progress, incomingSnapshot.progress),
roadMatchedPath: getNewestRoadMatchedPath(current.roadMatchedPath, incomingSnapshot.roadMatchedPath),
routePlanId: incomingSnapshot.routePlanId ?? current.routePlanId,
schemaVersion: incomingSnapshot.schemaVersion,
Expand Down Expand Up @@ -317,6 +317,48 @@ function mergeRouteTrackingSnapshot(currentSnapshot, serverSnapshot) {
return mergedSnapshot;
}

function mergeTrackingProgressSnapshot(currentProgress, incomingProgress) {
const currentLatestEvent = currentProgress?.latestEvent ?? null;
const incomingLatestEvent = incomingProgress?.latestEvent ?? null;
const currentLatestTimestamp = getPositionTimestamp(currentLatestEvent);
const incomingLatestTimestamp = getPositionTimestamp(incomingLatestEvent);
const latestEvent = incomingLatestEvent && incomingLatestTimestamp >= currentLatestTimestamp
? incomingLatestEvent
: currentLatestEvent;
const completedStopIds = new Set(currentProgress?.completedStopIds ?? []);
const failedStopIds = new Set(currentProgress?.failedStopIds ?? []);

for (const deliveryStopId of incomingProgress?.completedStopIds ?? []) {
completedStopIds.add(deliveryStopId);
failedStopIds.delete(deliveryStopId);
}
for (const deliveryStopId of incomingProgress?.failedStopIds ?? []) {
failedStopIds.add(deliveryStopId);
completedStopIds.delete(deliveryStopId);
}

if (latestEvent?.deliveryStopId && latestEvent.eventType === "STOP_DELIVERED") {
completedStopIds.add(latestEvent.deliveryStopId);
failedStopIds.delete(latestEvent.deliveryStopId);
}
if (latestEvent?.deliveryStopId && latestEvent.eventType === "STOP_FAILED") {
failedStopIds.add(latestEvent.deliveryStopId);
completedStopIds.delete(latestEvent.deliveryStopId);
}

return {
completedStopIds: [...completedStopIds],
currentStage: latestEvent
? getProgressStage(latestEvent.eventType)
: incomingProgress?.currentStage ?? currentProgress?.currentStage ?? "READY",
currentStopId: latestEvent
? latestEvent.eventType === "STOP_ARRIVED" ? latestEvent.deliveryStopId : null
: incomingProgress?.currentStopId ?? currentProgress?.currentStopId ?? null,
failedStopIds: [...failedStopIds],
latestEvent,
};
}

function mergeTrackingStopArrivals(currentArrivals, incomingArrivals) {
const arrivalsByEventId = new Map();
for (const arrival of currentArrivals) arrivalsByEventId.set(arrival.eventId, arrival);
Expand Down
39 changes: 29 additions & 10 deletions apps/shopify-app/app/features/orders/orders-page.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1273,17 +1273,28 @@ const noteCardStyle = {
padding: "8px 10px",
};

const noteListStyle = {
margin: 0,
paddingLeft: "18px",
const noteStackStyle = {
display: "grid",
gap: "8px",
};

const noteLabelStyle = {
color: "#657080",
fontSize: "10px",
fontWeight: 700,
letterSpacing: "0.06em",
marginBottom: "4px",
textAlign: "left",
textTransform: "uppercase",
};

const noteListItemStyle = {
const noteTextStyle = {
color: "#303030",
fontSize: "12px",
lineHeight: 1.4,
overflowWrap: "anywhere",
textAlign: "left",
whiteSpace: "pre-wrap",
};

const itemPopoverTitleStyle = {
Expand Down Expand Up @@ -5168,12 +5179,20 @@ function OrdersPageContent({ loaderData }) {
width: `${Math.round(notePopoverPosition.width)}px`,
}}
>
<div style={itemPopoverTitleStyle}>Order Note</div>
<div style={noteCardStyle}>
<ul style={noteListStyle}>
{orderNote ? <li style={noteListItemStyle}>{orderNote}</li> : null}
{customerNote ? <li style={noteListItemStyle}>{customerNote}</li> : null}
</ul>
<div style={itemPopoverTitleStyle}>Notes</div>
<div style={noteStackStyle}>
{orderNote ? (
<div style={noteCardStyle}>
<div style={noteLabelStyle}>Order Note</div>
<div style={noteTextStyle}>{orderNote}</div>
</div>
) : null}
{customerNote ? (
<div style={noteCardStyle}>
<div style={noteLabelStyle}>Customer Note</div>
<div style={noteTextStyle}>{customerNote}</div>
</div>
) : null}
</div>
</div>
, document.body) : null}
Expand Down
13 changes: 9 additions & 4 deletions apps/shopify-app/tests/orders-page.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -424,12 +424,17 @@ test("Orders ID stays centered while Note uses a separate headerless column", ()
assert.match(ordersPageSource, /createPortal\([\s\S]*?ref=\{notePopoverRef\}[\s\S]*?notePopoverPosition\.left/);
assert.match(ordersPageSource, /data-order-notes-popover-root="true"/);
assert.match(ordersPageSource, /<s-icon type="note"/);
assert.match(ordersPageSource, />Notes</);
assert.match(ordersPageSource, />Order Note</);
assert.match(ordersPageSource, />Customer Note</);
assert.match(ordersPageSource, /const noteCardStyle = \{/);
assert.match(ordersPageSource, /const noteListStyle = \{/);
assert.match(ordersPageSource, /<ul style=\{noteListStyle\}>/);
assert.match(ordersPageSource, /<li style=\{noteListItemStyle\}>\{orderNote\}<\/li>/);
assert.match(ordersPageSource, /<li style=\{noteListItemStyle\}>\{customerNote\}<\/li>/);
assert.match(ordersPageSource, /const noteStackStyle = \{/);
assert.match(ordersPageSource, /const noteLabelStyle = \{/);
assert.match(ordersPageSource, /const noteTextStyle = \{/);
assert.match(ordersPageSource, /\{orderNote \? \([\s\S]*?<div style=\{noteLabelStyle\}>Order Note<\/div>[\s\S]*?<div style=\{noteTextStyle\}>\{orderNote\}<\/div>/);
assert.match(ordersPageSource, /\{customerNote \? \([\s\S]*?<div style=\{noteLabelStyle\}>Customer Note<\/div>[\s\S]*?<div style=\{noteTextStyle\}>\{customerNote\}<\/div>/);
assert.doesNotMatch(ordersPageSource, /const noteListStyle = \{/);
assert.doesNotMatch(ordersPageSource, /<ul style=\{noteListStyle\}>/);
});

test("Ordered pill exposes order timing and delivery-cycle sequence on hover", () => {
Expand Down
50 changes: 50 additions & 0 deletions apps/shopify-app/tests/route-tracking-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,56 @@ test("tracking progress keeps the current driver stage and completed stop ids",
assert.deepEqual(delivered.progress.completedStopIds, ["stop-1", "stop-2"]);
});

test("reconnect snapshots without latest events cannot regress newer live progress", () => {
const liveSnapshot = normalizeRouteTrackingSnapshot({
policy,
progress: {
completedStopIds: ["stop-live-earlier", "stop-durable-failed"],
currentStage: "DRIVING",
currentStopId: null,
failedStopIds: ["stop-live-failed", "stop-durable-completed"],
latestEvent: {
deliveryStopId: "stop-live-delivered",
driverId: "driver-1",
eventId: "progress-live-delivered",
eventType: "STOP_DELIVERED",
occurredAt: "2026-07-20T04:05:00.000Z",
receivedAt: "2026-07-20T04:05:01.000Z",
routePlanId: "route-1",
},
},
recentPositions: [],
routePlanId: "route-1",
});
const reconnectSnapshot = {
policy,
progress: {
completedStopIds: ["stop-durable-completed"],
currentStage: "READY",
currentStopId: null,
failedStopIds: ["stop-durable-failed"],
latestEvent: null,
},
recentPositions: [],
routePlanId: "route-1",
status: "NO_DATA",
};

const merged = mergeRouteTrackingSnapshot(liveSnapshot, reconnectSnapshot);

assert.equal(merged.progress.currentStage, "DRIVING");
assert.equal(merged.progress.latestEvent.eventId, "progress-live-delivered");
assert.deepEqual(new Set(merged.progress.completedStopIds), new Set([
"stop-durable-completed",
"stop-live-delivered",
"stop-live-earlier",
]));
assert.deepEqual(new Set(merged.progress.failedStopIds), new Set([
"stop-durable-failed",
"stop-live-failed",
]));
});

test("tracking proxy forwards authentication and streams the upstream body without buffering", async () => {
const abortController = new AbortController();
const request = new Request("https://app.test/app/route-tracking/route-1", {
Expand Down