@mohasinac/appkit 4.35.4 → 4.37.0

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 (31) hide show
  1. package/dist/_internal/server/features/site-settings/adapters.d.ts +2 -2
  2. package/dist/_internal/server/features/site-settings/adapters.js +25 -1
  3. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +73 -6
  4. package/dist/_internal/shared/features/site-settings/types.d.ts +12 -0
  5. package/dist/features/tester/schemas/firestore.d.ts +29 -0
  6. package/dist/features/tester/seed-data/_money-flows.js +13 -5
  7. package/dist/features/tester/seed-data/authored/_types.d.ts +24 -0
  8. package/dist/features/tester/seed-data/authored/account-auth__profile-settings.js +2 -0
  9. package/dist/features/tester/seed-data/authored/account-auth__signup-login.js +22 -0
  10. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.d.ts +2 -0
  11. package/dist/features/tester/seed-data/authored/admin__bug-hunter-rewards.js +106 -0
  12. package/dist/features/tester/seed-data/authored/admin__site-system.js +2 -0
  13. package/dist/features/tester/seed-data/authored/buying__my-orders.js +2 -0
  14. package/dist/features/tester/seed-data/authored/buying__offers.js +2 -0
  15. package/dist/features/tester/seed-data/authored/content-discovery__notifications.js +2 -0
  16. package/dist/features/tester/seed-data/authored/content-discovery__search.js +48 -19
  17. package/dist/features/tester/seed-data/authored/index.js +6 -0
  18. package/dist/features/tester/seed-data/authored/public-pages__auth-error-pages.js +4 -0
  19. package/dist/features/tester/seed-data/authored/selling__listing-a-product.js +0 -16
  20. package/dist/features/tester/seed-data/authored/selling__product-upload-details.d.ts +2 -0
  21. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +150 -0
  22. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.d.ts +2 -0
  23. package/dist/features/tester/seed-data/authored/selling__quick-add-minimum-details.js +154 -0
  24. package/dist/features/tester/seed-data/authored/selling__seller-orders.js +2 -0
  25. package/dist/features/tester/seed-data/index.d.ts +1 -0
  26. package/dist/features/tester/seed-data/index.js +1 -0
  27. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +111 -6
  28. package/dist/features/tester/seed-data/tester-responses-seed-data.d.ts +2 -0
  29. package/dist/features/tester/seed-data/tester-responses-seed-data.js +95 -0
  30. package/package.json +1 -1
  31. package/scripts/seed-cli.mjs +797 -752
@@ -62,6 +62,25 @@ function group(groupKey, groupLabel, pages, opts) {
62
62
  expectedUiState: c.expectedUiState ?? authored?.expectedUiState,
63
63
  expectedData: c.expectedData ?? authored?.expectedData,
64
64
  endResult: c.endResult ?? authored?.endResult,
65
+ /*
66
+ * 🛑 This mapping is HAND-PICKED, so a field the overlay carries but
67
+ * nobody lists here dies right at this line — Root Cause #38, which the
68
+ * header of this very file warns about.
69
+ *
70
+ * `needsReview`/`reviewNote` were exactly that, from the day they were
71
+ * added until 2026-09-13: `fetch-cases.mjs` reads them off the Firestore
72
+ * item, SKILL.md documents them, and `record-verdicts.mjs` renders
73
+ * "the case's own author flagged it for review" in the report — but
74
+ * nothing ever put them ON the document, so the flag was permanently
75
+ * false and that report line had never printed once. Four hops correct,
76
+ * one hop missing, no error anywhere.
77
+ *
78
+ * When adding a field to AuthoredCase, add it HERE in the same commit.
79
+ */
80
+ needsReview: c.needsReview ?? authored?.needsReview,
81
+ reviewNote: c.reviewNote ?? authored?.reviewNote,
82
+ requiresHumanChannel: c.requiresHumanChannel ?? authored?.requiresHumanChannel,
83
+ humanChannelReason: c.humanChannelReason ?? authored?.humanChannelReason,
65
84
  // Every case gets a link: its own href, or the page's default —
66
85
  // guarantees "Go test this ->" always has somewhere real to send
67
86
  // the tester, even for cases nobody bothered to link individually.
@@ -2107,12 +2126,24 @@ const rawTesterChecklistItems = [
2107
2126
  description: "Fixed 2026-08-21 — a fresh upload can briefly 404 while the Firestore doc and Storage object finish propagating; the thumbnail now retries with backoff instead of latching a permanent broken-image placeholder. Try uploading 2-3 images back-to-back on a slower connection to see the retry in action.",
2108
2127
  href: "/store/products/new",
2109
2128
  },
2110
- {
2111
- key: "media-upload-images-capped-at-5",
2112
- label: "The product gallery upload UI caps at 5 images (not 10) and its label reads \"up to 5\"",
2113
- description: "Fixed 2026-08-21 — the UI previously advertised/allowed up to 10 images while the server schema only ever accepted 5, so a 6th+ image silently failed validation with no clear reason.",
2114
- href: "/store/products/new",
2115
- },
2129
+ /*
2130
+ * REMOVED 2026-09-11 — `media-upload-images-capped-at-5`.
2131
+ *
2132
+ * It asserted the gallery caps at "5 images (not 10)". The cap was
2133
+ * raised to 10 (`PRODUCT_MAX_IMAGES`, shared/media/limits.ts:37) and the
2134
+ * `selling/media-limits` page asserts the opposite in its own label:
2135
+ * "The 10th gallery image uploads — the 5th used to fail with a 400".
2136
+ *
2137
+ * So this case documented the OLD behaviour as the expectation. It
2138
+ * would fail against correct code, and anyone "fixing" the product to
2139
+ * satisfy it would re-break ten-image support. Its id encoded the wrong
2140
+ * number too, so correcting the label alone would still mislead.
2141
+ *
2142
+ * The invariant it really tested — the stated limit equals the enforced
2143
+ * limit — survives in `media-eleventh-image-refused-client-side`, which
2144
+ * requires the refusal to name the limit ("You can upload up to 10
2145
+ * images."). Nothing was lost.
2146
+ */
2116
2147
  {
2117
2148
  key: "media-upload-video-duration",
2118
2149
  label: "Attaching a directly-uploaded video file during listing captures its duration automatically and saves without a validation error; attaching a YouTube or external video URL also saves fine without needing a duration",
@@ -2605,6 +2636,80 @@ const rawTesterChecklistItems = [
2605
2636
  },
2606
2637
  ],
2607
2638
  },
2639
+ {
2640
+ pageKey: "product-upload-details",
2641
+ pageLabel: "Product media — crop, refusals, ordering and the video path",
2642
+ href: "/store/products/new",
2643
+ cases: [
2644
+ {
2645
+ key: "main-image-crop-applies",
2646
+ label: "The main-image crop is applied to what gets UPLOADED, not just previewed",
2647
+ description: "BEFORE: a cropper that shows a selection and stores the original is worse than none — the seller believes they framed the photo and the marketplace shows something else. AFTER: crop to the left half, confirm, and the stored thumbnail shows the left half.",
2648
+ },
2649
+ {
2650
+ key: "oversize-image-refused-client-side",
2651
+ label: "An image over 10 MB is refused BEFORE any bytes leave the browser",
2652
+ description: "BEFORE: a 12 MB upload that travels, finalises and then fails validation has taken a minute to say what the form knew when the file was picked. AFTER: immediate refusal reading the size limit, and no /api/media/sign request at all.",
2653
+ },
2654
+ {
2655
+ key: "disallowed-type-refused",
2656
+ label: "An SVG is refused, and the field still works immediately afterwards",
2657
+ description: "SVG is outside the allowed image types on purpose — a document format that can carry script, served from our own origin. Refusing one file must not wedge the control: the next PNG has to upload without a reload.",
2658
+ },
2659
+ {
2660
+ key: "gallery-order-persists",
2661
+ label: "Re-ordering gallery images survives Publish and re-opening the editor",
2662
+ description: "Gallery order is the seller decision about which angle a buyer sees second, so it is stored rather than merely displayed. Order that holds in the form and resets on reload means the array is rebuilt from upload time instead of from the arrangement.",
2663
+ },
2664
+ {
2665
+ key: "main-image-does-not-collide-with-gallery",
2666
+ label: "The main image and the first gallery image do not overwrite each other",
2667
+ description: "Filenames are content-derived with no timestamp, so two uploads computing the same index write the same storage path and the second silently replaces the first — the seller watches one of their two pictures vanish. Main image is index 1; the gallery starts at 2.",
2668
+ },
2669
+ {
2670
+ key: "video-upload-succeeds",
2671
+ label: "A video uploaded from the dedicated video panel saves and plays",
2672
+ description: "BEFORE: /api/media/sign returned 500 because the panel omitted the category from the upload context and the filename generator threw on it — every seller video upload from this panel failed while the gallery path worked. AFTER: sign returns 200, a poster frame and duration appear, and the published page plays it.",
2673
+ },
2674
+ ],
2675
+ },
2676
+ {
2677
+ pageKey: "quick-add-minimum-details",
2678
+ pageLabel: "Quick add — the short form, and what WhatsApp shows",
2679
+ href: "/store/products/new",
2680
+ cases: [
2681
+ {
2682
+ key: "quick-form-is-the-default",
2683
+ label: "Creating an ordinary product opens the SHORT form, with the full form one click away",
2684
+ description: "Most listings need six fields and the sectioned form asks for thirty. /store/products/new opens Quick add; Show all fields (advanced) reaches the rest. Non-standard types open the sectioned form directly because they carry fields the short form has no room for.",
2685
+ },
2686
+ {
2687
+ key: "publish-with-the-minimum",
2688
+ label: "The short form alone can publish a real, buyable listing",
2689
+ description: "If it cannot, it is a decoy: the seller fills it, is refused, and has to learn the long form anyway — having done the work twice.",
2690
+ },
2691
+ {
2692
+ key: "whatsapp-shows-the-same-fields",
2693
+ label: "Everything WhatsApp shows was already asked for by the short form",
2694
+ description: "The catalog push sends title, price, image, condition and stock. That is the quick form field set, which is why the short form is the right shape — a seller who fills only it has supplied everything a WhatsApp buyer will see.",
2695
+ },
2696
+ {
2697
+ key: "advanced-flip-keeps-values",
2698
+ label: "Switching to the full form keeps everything already typed",
2699
+ description: "Losing values on the way to the full form punishes the seller for outgrowing the short one, and teaches them to start with the long form every time — which defeats the point of having a short one.",
2700
+ },
2701
+ {
2702
+ key: "condition-options-are-all-saveable",
2703
+ label: "Every Condition option the form offers can actually be saved",
2704
+ description: "BEFORE: the dropdown lists Like New, Good and Fair while the server accepts only new/used/refurbished/broken, so picking the option that honestly describes the item is refused with nothing on screen saying which options are real. AFTER: every listed option publishes.",
2705
+ },
2706
+ {
2707
+ key: "description-requirement-is-honest",
2708
+ label: "The Description field label agrees with what the save actually requires",
2709
+ description: "BEFORE: the field reads Brief description (optional) and is never validated client-side, while the server demands 20+ characters — so a field marked optional is refused after the seller commits, as a toast, with no error on the field. AFTER: label and behaviour agree and a short entry is caught inline.",
2710
+ },
2711
+ ],
2712
+ },
2608
2713
  {
2609
2714
  pageKey: "media-limits",
2610
2715
  pageLabel: "Media \u2014 ten images and one video, on every listing type",
@@ -0,0 +1,2 @@
1
+ import type { TesterChecklistResponseDocument } from "../schemas/firestore";
2
+ export declare const testerResponsesSeedData: Partial<TesterChecklistResponseDocument>[];
@@ -0,0 +1,95 @@
1
+ /*
2
+ * WHY: Three `admin/bug-hunter-rewards` cases need a tester submission that
3
+ * ALREADY EXISTS before the tester arrives — you cannot test "confirm this
4
+ * reported bug" or "reopen this confirmed case" against an empty inbox.
5
+ *
6
+ * `testerChecklistResponses` is DERIVED tier, so a run wipes it at setup
7
+ * and on a fresh project there are genuinely zero submissions. That is
8
+ * correct — stale verdicts from a previous run must not leak into the next
9
+ * one — but it left those three cases permanently unrunnable. Measured
10
+ * 2026-09-11: 0 pass / 0 fail / 5 blocked on that page, with both
11
+ * calibration controls answered correctly, so the batch was trustworthy and
12
+ * the emptiness was real.
13
+ *
14
+ * The fix is NOT to stop wiping the collection. It is to restore these
15
+ * fixtures after the wipe — `SEED_TRANSACTIONAL` in
16
+ * tester/scripts/lib/collections.mjs is a RESTORE list, orthogonal to the
17
+ * delete tiers, so a collection can be both wiped and re-seeded.
18
+ *
19
+ * WHAT: Two response documents against the two demo-fixture checklist items that
20
+ * already live in the catalogue.
21
+ *
22
+ * 🛑 THE DOC ID IS DETERMINISTIC: `${testerId}__${checklistItemId}`.
23
+ * That is the same id the live answer path computes, and it is what makes an
24
+ * answer an UPSERT rather than a duplicate — `set({merge:true})`, never
25
+ * `add()`. Generating a random id here would (a) create a second, competing
26
+ * response for the same (tester, case) pair the moment a real tester answers,
27
+ * and (b) break idempotency, so every `appkit-seed load` would pile up
28
+ * another copy — Root Cause #25 exactly.
29
+ *
30
+ * @tag domain:tester
31
+ * @tag layer:seed
32
+ * @tag access:server-only
33
+ * @tag consumers:appkit/scripts/seed-cli.mjs, seed/manifest.ts
34
+ * @tag sideEffects:none
35
+ */
36
+ /** The seeded QA persona that the demo fixtures credit as "Mock User 18". */
37
+ const TESTER_ID = "user-tester-qa";
38
+ const TESTER_NAME = "Mock User 18";
39
+ /** Same shape the answer route computes — see the callout above. */
40
+ const responseId = (testerId, checklistItemId) => `${testerId}__${checklistItemId}`;
41
+ const DEMO_V1 = "checklist-admin-bug-hunter-rewards-demo-fixture";
42
+ const DEMO_V2 = "checklist-admin-bug-hunter-rewards-demo-fixture-v2";
43
+ const seededAt = new Date("2026-08-18T09:30:00.000Z");
44
+ export const testerResponsesSeedData = [
45
+ /*
46
+ * The ALREADY-CONFIRMED submission. The v1 catalogue item carries
47
+ * bugConfirmed:true / bugHunterId:"user-tester-qa" / bugHunterName:"Mock User
48
+ * 18", and until now there was no response row behind that credit — so
49
+ * /admin/tester-feedback showed "No feedback submitted yet" while the
50
+ * leaderboard showed a confirmed bug. This is the missing half.
51
+ *
52
+ * Backs: confirm-bug-idempotent (needs a row that is already confirmed) and
53
+ * reopen-case (needs the v1 credit to be visible from a real submission).
54
+ *
55
+ * `status: "reviewed"` because an admin has already acted on it.
56
+ */
57
+ {
58
+ id: responseId(TESTER_ID, DEMO_V1),
59
+ testerId: TESTER_ID,
60
+ testerDisplayName: TESTER_NAME,
61
+ checklistItemId: DEMO_V1,
62
+ groupKey: "admin",
63
+ pageKey: "bug-hunter-rewards",
64
+ phase: 1,
65
+ answer: "no",
66
+ comment: "Seed fixture — the reported bug behind the v1 demo case. Already confirmed and credited, then reopened as v2. Exists so an admin can see what a confirmed submission looks like without waiting for a real tester to file one.",
67
+ status: "reviewed",
68
+ createdAt: seededAt,
69
+ updatedAt: seededAt,
70
+ },
71
+ /*
72
+ * The UNCONFIRMED submission, against the active v2 retest. This is the row
73
+ * "Mark as Bug" is meant to be tried on end-to-end.
74
+ *
75
+ * 🛑 status MUST stay "new" and the case MUST stay un-confirmed, or the
76
+ * confirm-bug case has nothing left to do — it would open an already-confirmed
77
+ * row and correctly report that it could not perform its own steps. A fixture
78
+ * that pre-completes the thing under test is worse than no fixture, because it
79
+ * looks like coverage.
80
+ */
81
+ {
82
+ id: responseId(TESTER_ID, DEMO_V2),
83
+ testerId: TESTER_ID,
84
+ testerDisplayName: TESTER_NAME,
85
+ checklistItemId: DEMO_V2,
86
+ groupKey: "admin",
87
+ pageKey: "bug-hunter-rewards",
88
+ phase: 1,
89
+ answer: "no",
90
+ comment: "Seed fixture — a fresh 'No' on the reopened v2 case, deliberately NOT yet confirmed. This is the row to try Mark as Bug against.",
91
+ status: "new",
92
+ createdAt: seededAt,
93
+ updatedAt: seededAt,
94
+ },
95
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.35.4",
3
+ "version": "4.37.0",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"