@mohasinac/appkit 4.41.11 โ†’ 4.41.13

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.
@@ -141,9 +141,35 @@ export function AdminOrdersView({ children, ...props }) {
141
141
  ? `${title}${extraCount > 0 ? ` +${extraCount} more` : ""}`
142
142
  : `Order ${orderId}`,
143
143
  secondary: [
144
- toStringValue(item.buyerName ?? item.customerName, "Unknown buyer"),
145
- toCurrency(item.totalAmount ?? item.total ?? item.amount),
146
- orderId.slice(0, 14),
144
+ /*
145
+ * ๐Ÿ›‘ `userName` and `totalPrice` FIRST โ€” they are the document's
146
+ * real field names (OrderDocument, and the live response of
147
+ * GET /api/admin/orders).
148
+ *
149
+ * This read `buyerName ?? customerName` and
150
+ * `totalAmount ?? total ?? amount`: five names, not one of which
151
+ * the order document has ever carried. So every row rendered
152
+ * "Unknown buyer ยท -", on all 25 rows, for as long as the view has
153
+ * existed โ€” and the `??` chains made it look like considered
154
+ * defensive coding rather than a guess. A fallback spelled three
155
+ * ways is the tell that nobody checked which one was real.
156
+ *
157
+ * The aliases are GONE rather than demoted to fallbacks: nothing
158
+ * declares them (the client `Order` type has no such fields) and
159
+ * nothing emits them (no adapter renames to them). Keeping a
160
+ * fallback with no producer only preserves the ambiguity that
161
+ * hid the bug.
162
+ */
163
+ toStringValue(item.userName, "Unknown buyer"),
164
+ toCurrency(item.totalPrice),
165
+ /*
166
+ * Not `.slice(0, 14)` โ€” that cut mid-date, so
167
+ * `order-1-20260822-aucwon` and `order-1-20260821-prizedr` both
168
+ * rendered as "order-1-202608". A truncation that destroys
169
+ * uniqueness is worse than a long string, because the id is the
170
+ * one thing on the row an admin searches by.
171
+ */
172
+ orderId,
147
173
  paymentReviewLabel(item),
148
174
  ]
149
175
  .filter(Boolean)
@@ -366,13 +366,37 @@ export function SellerOrdersView({ orderDetailApiBase = SELLER_ENDPOINTS.ORDERS,
366
366
  const loc = item.physicalLocation;
367
367
  return {
368
368
  id: toStringValue(item.id, `order-${index}`),
369
- primary: `Order ${toStringValue(item.id, "-").slice(0, 14)}`,
370
- secondary: toStringValue(item.buyerName ?? item.buyerDisplayName, "Unknown buyer"),
369
+ /*
370
+ * ๐Ÿ›‘ The row says WHAT WAS SOLD, not the id repeated.
371
+ *
372
+ * This was `Order ${id.slice(0, 14)}` โ€” a raw id, truncated
373
+ * mid-date, so two different orders rendered identically. The title
374
+ * was available the whole time: `items[0].productTitle` is
375
+ * denormalised onto every order precisely so a list never needs a
376
+ * second fetch (Root Cause #52), and it is already read three lines
377
+ * below as `itemTitle`.
378
+ */
379
+ primary: (() => {
380
+ const title = typeof firstItem.productTitle === "string" ? firstItem.productTitle : "";
381
+ if (!title)
382
+ return `Order ${toStringValue(item.id, "-")}`;
383
+ const extra = itemsArr.length - 1;
384
+ return extra > 0 ? `${title} +${extra} more` : title;
385
+ })(),
386
+ /*
387
+ * `userName` / `totalPrice` are the document's real field names.
388
+ * `buyerName`/`buyerDisplayName`/`totalAmount`/`total` are four
389
+ * spellings the order document has never carried, so every seller's
390
+ * order list showed "Unknown buyer" and โ‚น0 โ€” see AdminOrdersView for
391
+ * the full writeup. They are removed rather than demoted: nothing
392
+ * declares them and nothing emits them.
393
+ */
394
+ secondary: toStringValue(item.userName, "Unknown buyer"),
371
395
  status: toStringValue(item.status, "PENDING"),
372
396
  updatedAt: toRelativeDate(item.updatedAt ?? item.orderDate ?? item.createdAt),
373
397
  itemCount: itemsArr.length,
374
- totalAmount: Number(item.totalAmount ?? item.total ?? 0),
375
- buyerName: toStringValue(item.buyerName ?? item.buyerDisplayName, "Unknown buyer"),
398
+ totalAmount: Number(item.totalPrice ?? 0),
399
+ buyerName: toStringValue(item.userName, "Unknown buyer"),
376
400
  itemImage: typeof firstItem.image === "string" ? firstItem.image : undefined,
377
401
  itemTitle: typeof firstItem.productTitle === "string" ? firstItem.productTitle : undefined,
378
402
  physicalLocation: loc && typeof loc.zone === "string"
@@ -117,12 +117,12 @@ export const authored = {
117
117
  startPage: "/admin/orders",
118
118
  steps: [
119
119
  "Sign in as admin@letitrip.in / TempPass123!.",
120
- "Open /admin/orders and open the row drawer for order-1-20260721-11joon.",
120
+ "Open /admin/orders and open the row drawer for order-1-20251107-11joon.",
121
121
  "Write down the item titles, the shipping address, the payment method and the status.",
122
122
  "Close the drawer and use that row's Open full page action.",
123
123
  "Read the same four things on the full page.",
124
124
  ],
125
- inputs: { orderId: "order-1-20260721-11joon" },
125
+ inputs: { orderId: "order-1-20251107-11joon" },
126
126
  expectedBehaviour: "The drawer and the full page are two renderings of one record, so they must agree. Where they differ, one of them is reading a field the other is not โ€” and the full page is the bookmarkable one an admin will send to somebody else.",
127
127
  expectedUiState: "Item titles, address, payment method and status are identical in both, and the full page is at a real bookmarkable URL rather than a modal over the list.",
128
128
  expectedData: { drawerAndPageAgree: true },
@@ -42,12 +42,12 @@ export const authored = {
42
42
  startPage: "/user/orders",
43
43
  steps: [
44
44
  "Sign in as karthik.new@gmail.com / TempPass123!, a seeded buyer who owns no seeded orders.",
45
- "Navigate directly to /api/user/orders/order-1-20260721-11joon/invoice โ€” a seeded order belonging to a different persona.",
45
+ "Navigate directly to /api/user/orders/order-1-20251107-11joon/invoice โ€” a seeded order belonging to a different persona.",
46
46
  "Read the response.",
47
- "Now navigate to /user/orders/order-1-20260721-11joon.",
47
+ "Now navigate to /user/orders/order-1-20251107-11joon.",
48
48
  "Read that page.",
49
49
  ],
50
- inputs: { foreignOrderId: "order-1-20260721-11joon" },
50
+ inputs: { foreignOrderId: "order-1-20251107-11joon" },
51
51
  expectedBehaviour: "An order id is short and guessable, so ownership is checked on every hit rather than relied upon to be secret. Answering 403 instead of 404 would confirm that an order with that id exists, which is a membership oracle over the id space.",
52
52
  expectedUiState: "Neither the invoice nor the order page shows any of that order's contents. No item titles, no address, no amounts.",
53
53
  expectedData: { foreignInvoiceServed: false },
@@ -74,14 +74,14 @@ export const authored = {
74
74
  startPage: "/user/orders",
75
75
  steps: [
76
76
  "Sign in as vivaan.kapoor@gmail.com / TempPass123! โ€” this persona owns the seeded DELIVERED orders, and a return can only be requested on one.",
77
- "Open /user/orders and open order-1-20260721-11joon, which is delivered.",
77
+ "Open /user/orders and open order-1-20251107-11joon, which is delivered.",
78
78
  "Use the Return action.",
79
79
  "Give the reason: item not as described.",
80
80
  "Submit the request.",
81
81
  "Reload the order.",
82
82
  "Open /user/returns and look for it.",
83
83
  ],
84
- inputs: { orderId: "order-1-20260721-11joon", reason: "item not as described" },
84
+ inputs: { orderId: "order-1-20251107-11joon", reason: "item not as described" },
85
85
  expectedBehaviour: "A return request moves the order into Return Requested and appears on the returns surface. That status is deliberately an ACTIVE one โ€” it is the seller's next action item, not a closed state โ€” so the order must not disappear from the active list.",
86
86
  expectedUiState: "The order's status reads Return Requested, and the same order is listed under /user/returns.",
87
87
  expectedData: { statusAfterReturn: "return_requested" },
@@ -92,12 +92,12 @@ export const authored = {
92
92
  startPage: "/user/orders",
93
93
  steps: [
94
94
  "Sign in as rehan.sheikh@gmail.com / TempPass123! โ€” this persona owns the seeded SHIPPED order.",
95
- "Open /user/orders and open order-1-20260718-aevnlw.",
95
+ "Open /user/orders and open order-1-20251104-aevnlw.",
96
96
  "Use the Track action.",
97
97
  "Read every step on the timeline and note which carry a date and which do not.",
98
98
  "Compare the dates shown against the order's own placed and shipped dates.",
99
99
  ],
100
- inputs: { orderId: "order-1-20260718-aevnlw" },
100
+ inputs: { orderId: "order-1-20251104-aevnlw" },
101
101
  expectedBehaviour: "Each timeline step shows the date actually recorded for it. A step with no recorded date renders an em-dash rather than a guess โ€” an invented or estimated date is worse than a blank, because it cannot be told apart from a real one.",
102
102
  expectedUiState: "The placed and shipped steps carry dates matching the order. Steps the order has not reached show no date rather than today's.",
103
103
  expectedData: { fabricatedDatesShown: false },
@@ -108,14 +108,14 @@ export const authored = {
108
108
  startPage: "/user/orders",
109
109
  steps: [
110
110
  "Sign in as karthik.new@gmail.com / TempPass123!, a seeded buyer who owns no seeded orders.",
111
- "Navigate to /user/orders/order-1-20260721-11joon/track.",
112
- "Navigate to /user/orders/order-1-20260721-11joon/invoice.",
113
- "Navigate to /user/orders/order-1-20260721-11joon/return.",
114
- "Navigate to /user/orders/order-1-20260721-11joon/cancel.",
115
- "Navigate to /user/orders/order-1-20260721-11joon/payment.",
111
+ "Navigate to /user/orders/order-1-20251107-11joon/track.",
112
+ "Navigate to /user/orders/order-1-20251107-11joon/invoice.",
113
+ "Navigate to /user/orders/order-1-20251107-11joon/return.",
114
+ "Navigate to /user/orders/order-1-20251107-11joon/cancel.",
115
+ "Navigate to /user/orders/order-1-20251107-11joon/payment.",
116
116
  "Read each page in turn.",
117
117
  ],
118
- inputs: { foreignOrderId: "order-1-20260721-11joon" },
118
+ inputs: { foreignOrderId: "order-1-20251107-11joon" },
119
119
  expectedBehaviour: "Ownership is enforced on EVERY subroute, not only on the order page that links to them. A subroute that checks nothing is reachable by typing a URL, and these five carry the address, the amounts and the payment details.",
120
120
  expectedUiState: "All five show a not-found page. None shows an item title, an address, an amount or a tracking number belonging to that order.",
121
121
  expectedData: { subroutesLeakingData: 0 },
@@ -126,11 +126,11 @@ export const authored = {
126
126
  startPage: "/user/orders",
127
127
  steps: [
128
128
  "Open a private window with no session.",
129
- "Navigate to /user/orders/order-1-20260721-11joon/track.",
129
+ "Navigate to /user/orders/order-1-20251107-11joon/track.",
130
130
  "Read the page.",
131
131
  "Navigate to /user/orders and read that page too.",
132
132
  ],
133
- inputs: { foreignOrderId: "order-1-20260721-11joon" },
133
+ inputs: { foreignOrderId: "order-1-20251107-11joon" },
134
134
  expectedBehaviour: "A signed-out visitor is sent to sign in. Being blocked IS the expected result here โ€” the failure to look for is the page rendering its content for a moment before redirecting, which is enough to read and enough for a crawler to capture.",
135
135
  expectedUiState: "The sign-in page, or the orders page in its signed-out state. At no point does any order content appear, not even briefly.",
136
136
  expectedData: { orderContentVisibleToGuest: false },
@@ -448,11 +448,54 @@ const cashOrderVerified = {
448
448
  createdAt: daysAgo(4),
449
449
  updatedAt: daysAgo(2),
450
450
  };
451
+ /*
452
+ * ๐Ÿ›‘ DERIVES `items[]` FROM THE FLAT FIELDS WHEN A FIXTURE OMITS IT.
453
+ *
454
+ * `items[]` is the canonical shape and every orders surface reads
455
+ * `items[0].productTitle` for its row label (Root Cause #52). The generator
456
+ * below was taught to emit it โ€” and that fix was NARROWER THAN THE DEFECT:
457
+ * measured after it shipped, 14 of the 50 seeded orders still had none,
458
+ * because the hand-written fixtures never go through the generator.
459
+ *
460
+ * That is Root Cause #84's shape โ€” a fix scoped to the population you happened
461
+ * to be looking at. Patching those 14 by hand would leave the 15th to whoever
462
+ * writes it next, so the derivation lives here instead, on the path EVERY
463
+ * fixture already takes.
464
+ *
465
+ * It only ever fills a gap: a fixture that declares its own `items[]` (the
466
+ * multi-line ones, where the flat fields cannot express two products) is
467
+ * passed through untouched.
468
+ */
469
+ function withOrderItems(order) {
470
+ if (Array.isArray(order.items) && order.items.length > 0)
471
+ return order;
472
+ if (!order.productId)
473
+ return order;
474
+ const quantity = order.quantity ?? 1;
475
+ const unitPrice = order.unitPrice ?? 0;
476
+ return {
477
+ ...order,
478
+ items: [
479
+ {
480
+ productId: order.productId,
481
+ productTitle: order.productTitle ?? order.productId,
482
+ listingType: "standard",
483
+ quantity,
484
+ unitPrice,
485
+ // Prefer the order's own total: on a single-line order it is the
486
+ // authoritative figure, and re-deriving it would silently drop any
487
+ // fixture that deliberately encodes a locked or negotiated price.
488
+ totalPrice: order.totalPrice ?? unitPrice * quantity,
489
+ },
490
+ ],
491
+ };
492
+ }
451
493
  // Backfills image/imageUrls (see orderItemImage() above) on every fixture
452
494
  // regardless of which shape it uses (multi-item `items[]` vs the legacy
453
495
  // single-item top-level fields), so no seeded order silently renders without
454
496
  // a thumbnail the way real pre-2026-08-20 orders do.
455
- function withOrderImages(order) {
497
+ function withOrderImages(input) {
498
+ const order = withOrderItems(input);
456
499
  return {
457
500
  ...order,
458
501
  imageUrls: order.imageUrls ?? (order.productId ? [orderItemImage(order.productId)] : undefined),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.41.11",
3
+ "version": "4.41.13",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"