From 737e3e3ca86ad2d908165ec756bd6f43f318c6e8 Mon Sep 17 00:00:00 2001 From: OziinG <145884442+OziinG@users.noreply.github.com> Date: Thu, 23 Jul 2026 16:11:07 +0900 Subject: [PATCH 1/2] Preserve live route progress across tracking reconnects Constraint: Reconnect snapshots may lag behind SSE progress already visible in the embedded app. Rejected: Replace the current progress object wholesale | stale snapshots regress the current stage and stop outcomes. Confidence: high Scope-risk: narrow Directive: Keep durable stop outcomes and the newest progress event monotonic during future snapshot merges. Tested: npm test; npm run build; npm run typecheck; npm run check:public-urls; git diff --check Not-tested: Live Shopify embedded reconnect against production traffic --- .../app/features/delivery/route-tracking.js | 44 +++++++++++++++- .../tests/route-tracking-contract.test.mjs | 50 +++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/apps/shopify-app/app/features/delivery/route-tracking.js b/apps/shopify-app/app/features/delivery/route-tracking.js index 98dff86..4e2ea50 100644 --- a/apps/shopify-app/app/features/delivery/route-tracking.js +++ b/apps/shopify-app/app/features/delivery/route-tracking.js @@ -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, @@ -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); diff --git a/apps/shopify-app/tests/route-tracking-contract.test.mjs b/apps/shopify-app/tests/route-tracking-contract.test.mjs index 3755535..ad81cd4 100644 --- a/apps/shopify-app/tests/route-tracking-contract.test.mjs +++ b/apps/shopify-app/tests/route-tracking-contract.test.mjs @@ -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", { From 269ac61984f75cfabfed2e81b9e5b5f128c43d5a Mon Sep 17 00:00:00 2001 From: OziinG <145884442+OziinG@users.noreply.github.com> Date: Thu, 23 Jul 2026 17:28:26 +0900 Subject: [PATCH 2/2] Make order and customer notes distinguishable at a glance Constraint: Existing read-only Shopify queries and scopes already provide both note values. Rejected: Separate visible table columns | the compact headerless note action preserves table width. Confidence: high Scope-risk: narrow Directive: Keep Order Note and Customer Note labels explicit and avoid unlabeled bullet lists. Tested: 373 app tests; React Router type generation; TypeScript; production client and SSR builds. --- .../app/features/orders/orders-page.jsx | 39 ++++++++++++++----- apps/shopify-app/tests/orders-page.test.mjs | 13 +++++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/apps/shopify-app/app/features/orders/orders-page.jsx b/apps/shopify-app/app/features/orders/orders-page.jsx index 1ecceff..769fc4b 100644 --- a/apps/shopify-app/app/features/orders/orders-page.jsx +++ b/apps/shopify-app/app/features/orders/orders-page.jsx @@ -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 = { @@ -5168,12 +5179,20 @@ function OrdersPageContent({ loaderData }) { width: `${Math.round(notePopoverPosition.width)}px`, }} > -
Order Note
-
- +
Notes
+
+ {orderNote ? ( +
+
Order Note
+
{orderNote}
+
+ ) : null} + {customerNote ? ( +
+
Customer Note
+
{customerNote}
+
+ ) : null}
, document.body) : null} diff --git a/apps/shopify-app/tests/orders-page.test.mjs b/apps/shopify-app/tests/orders-page.test.mjs index df97a21..0280a65 100644 --- a/apps/shopify-app/tests/orders-page.test.mjs +++ b/apps/shopify-app/tests/orders-page.test.mjs @@ -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, /NotesOrder NoteCustomer Note/); - assert.match(ordersPageSource, /
  • \{orderNote\}<\/li>/); - assert.match(ordersPageSource, /
  • \{customerNote\}<\/li>/); + assert.match(ordersPageSource, /const noteStackStyle = \{/); + assert.match(ordersPageSource, /const noteLabelStyle = \{/); + assert.match(ordersPageSource, /const noteTextStyle = \{/); + assert.match(ordersPageSource, /\{orderNote \? \([\s\S]*?
    Order Note<\/div>[\s\S]*?
    \{orderNote\}<\/div>/); + assert.match(ordersPageSource, /\{customerNote \? \([\s\S]*?
    Customer Note<\/div>[\s\S]*?
    \{customerNote\}<\/div>/); + assert.doesNotMatch(ordersPageSource, /const noteListStyle = \{/); + assert.doesNotMatch(ordersPageSource, /
      /); }); test("Ordered pill exposes order timing and delivery-cycle sequence on hover", () => {