Skip to content

feat: landing pages, storefront UX, and Yalidine integration hardening - #102

Merged
bighadj22 merged 4 commits into
mainfrom
feat/landing-pages-yalidine-hardening
Sep 8, 2026
Merged

feat: landing pages, storefront UX, and Yalidine integration hardening#102
bighadj22 merged 4 commits into
mainfrom
feat/landing-pages-yalidine-hardening

Conversation

@bighadj22

Copy link
Copy Markdown
Owner

What

Four clean commits, each self-consistent:

Commit Scope
feat(landing-pages) (75 files) One-product marketing pages end-to-end: D1 migrations 0019/0020, management API + scopes, public store endpoint with view counter + order attribution, presign folders, dashboard (list/create/Studio/compare, i18n ×3), store-domain setting, 7 MCP tools
feat(theme01) Storefront UX: real favicon fix (old link 404'd), marketing mode (no header/footer on LPs), sticky Order-Now CTA hidden when the form is in view, CLS-free image rendering (intrinsic dimensions + parallel SSR fetches + preload)
feat(yalidine) (55 files) Integration hardening, every decision test-proven against the official docs AND the live API (272 real history events)
docs README claims (code-verified), CHANGELOG incl. the Known Limitation, brand logo

Yalidine highlights

  • Geo-name resolution — migration 0021 + sync-geo endpoint + 3-phase matcher (exact → normalized → distance-1, wilaya-scoped). Our seed spellings differ from Yalidine's for 389/1551 communes (Ain Arnat vs Aïn Arnat) — ~25% of dispatches were being rejected at the carrier. Dashboard button included; other carriers untouched.
  • Status mapper rewritten to the documented 36-status enum: real return statuses (Retourné au vendeur, Echèc livraison, …) now map to returned instead of leaving orders stuck at out_for_delivery; phantom statuses deleted; transit statuses are deliberate no-ops (the old map could regress dispatched orders); drift-guard test fails CI if the enum changes.
  • Webhook HMAC-SHA256 verification (was a TODO): proven byte-identical to PHP hash_hmac via a Node reference-implementation suite; 400 on tampered/missing signatures; fail-open + dashboard warning when no secret. Also fixes replayed-delivery 500s (Drizzle cause-chain duplicate detection — retries previously risked carrier-side webhook auto-disable).
  • Dispatch UX (carrier-agnostic): deliveryType override persisted on success, wilaya-scoped desk picking (cross-wilaya fallback removed), commune pre-selection, explicit no-desk-in-wilaya resolution state.
  • Webhook tooling: events read API + dashboard panels; Yalidine setup card + ZR Express register/unregister & mapping editor (previously dead code); adapter error extraction now surfaces the carrier's real messages.

⚠️ Known limitation (documented in CHANGELOG)

Yalidine's Cloudflare zone blocks all Cloudflare-Worker-originated traffic (403 error code: 1106) — proven pre-auth, whole-zone, fetch AND raw sockets, token-independent. Outbound calls (dispatch, syncs, tracking pulls) fail from the production Worker until either (a) Yalidine allowlists Workers traffic (evidence email drafted), or (b) the ready-made egress relay (cod-server/scripts/yalidine-egress-proxy.ts, Deno Deploy) is deployed and wired via company-notes keys. Inbound webhooks are unaffected — live-proven with real events (HMAC-verified, deduplicated, correctly mapped).

Verification

  • cod-server: 128 files / 1832 tests + typecheck (36 new Yalidine tests incl. real-D1 e2e suites; 10 landing-pages e2e suites)
  • dashboard: 142 tests + typecheck + production build; i18n parity ×3
  • theme01: astro check + 10 tests + validators
  • Live-verified on production: CRC echo, tampered-signature 400, RBAC, events endpoint with real carrier events, migrations applied (remote D1)

Deployed versions already running: server 6c6fcca9, dashboard 6043f414, storefront ead35ad9.

…tribution, MCP tools

- D1 migrations 0019 (landing_pages + images + orders.landing_page_id) and
  0020 (image intrinsic dimensions for CLS-free rendering); Drizzle tables
  + mock-db fixtures
- Management API /api/landing-pages (defineRoute, LANDING_PAGES_READ/MANAGE
  scopes, slug rules, delete-with-orders guard, image stack endpoints with
  R2-first delete + reference counting, duplicate shares R2 objects)
- Public store endpoint GET /store/landing-pages/:slug (published-only,
  atomic deferred view counter) + order attribution via landingPageSlug
  (best-effort, never blocks an order)
- presign folder param (products | landing) with route+handler allowlists
- Dashboard: feature module (13 API fns), nav leaf, /landing-pages list +
  create flow + Studio (uploads, reorder, live preview, spacing sliders,
  publish) + A/B compare screen; i18n ar/en/fr (+ guard)
- Store Settings: custom domain field (landing page share URLs prefer it)
- 7 MCP tools wired into the registry with scope gating
- theme core: fetchLandingPageBySlug client + placeOrder schema field +
  OrderForm landingPageSlug hidden input (the /lp/[slug] page itself lands
  with the theme UX commit)
@bighadj22
bighadj22 force-pushed the feat/landing-pages-yalidine-hardening branch from 738514d to 6792bb8 Compare September 8, 2026 09:42
…A, CLS-free landing pages

- favicon: serve the real /favicon.ico (the old link pointed at a
  nonexistent favicon.svg — every page 404'd its icon)
- StoreLayout marketing mode: no header/nav AND no footer on landing pages
- /lp/[slug]: parallel SSR fetches (halved TTFB), multi-image preload,
  intrinsic width/height from the 0020 columns reserve every image's slot
  (zero form-jumping layout shift), soft placeholder background
- sticky Order Now CTA fixed to the bottom while the form is out of view,
  hidden via IntersectionObserver the moment it enters the viewport;
  localized (orderCta), RTL-safe, anchor-based (works without JS), hidden
  when out of stock
- wrangler deploy script passes the production COD_SERVER_URL override
… dispatch UX, webhook tooling

Fixes the three production breakages found by auditing our adapter against
the official docs AND the live API (272 real history events across 20 real
parcels; every decision below is backed by tests, not assumptions):

Carrier geo-name resolution (~25% of dispatches were rejected):
- migration 0021: carrier_wilayas + carrier_communes per-carrier exact-name
  maps; POST /api/delivery-companies/:id/sync-geo builds them by matching
  our reference rows to the carrier's own lists (exact → normalized →
  distance-1 variant scoped to the same wilaya), reporting unmapped rows
- Yalidine dispatches resolve wilaya/commune through the map (our seed
  spellings differ for 389/1551 communes — 'Ain Arnat' vs 'Aïn Arnat');
  NOEST/ZR/EcoTrack untouched; dashboard 'Sync Delivery Zones' button

Status mapper rewritten against the documented 36-status enum:
- real return statuses (Retourné au vendeur, Echèc livraison, Colis
  abandonné, ...) now map to 'returned' — orders no longer stick at
  out_for_delivery forever; phantom statuses removed; transit statuses are
  deliberate no-ops (the old map could REGRESS a dispatched order via
  Ramassé/En préparation); late Tentative échouée events can't touch
  terminal orders; exhaustive drift-guard test fails CI on enum changes

Webhook HMAC-SHA256 verification (was a TODO accepting unsigned events):
- X-Yalidine-Signature over raw body, hex digest, constant-time compare,
  proven byte-identical to PHP hash_hmac via a Node reference-implementation
  test; 400 on tampered/missing signatures; fail-open + warning when no
  secret stored (dashboard shows the unverified state)
- fixes replayed-delivery 500s (Drizzle wraps D1 errors — duplicate
  detection now walks the cause chain; retries previously risked the
  carrier auto-disabling the webhook)

Dispatch UX (merchant-side stop desk, carrier-agnostic):
- deliveryType override on dispatch (home ⇄ stop desk) persisted with the
  tracking number; wilaya-scoped desk picking (cross-wilaya fallback
  removed); commune desk pre-selection; explicit no-desk-in-wilaya state
  with switch hint; Yalidine weight field enabled per the official contract

Webhook tooling:
- GET /api/delivery-companies/:id/webhook/events — outcome-filtered,
  paginated event log (ok/ignored/unmapped/error) with joined order links
- dashboard: Yalidine setup card (URL + copy, secret save, guided steps),
  ZR Express register/unregister + custom status-mapping editor (both were
  dead API code), events panel for every webhook-capable carrier
- adapter: unified request() extracts the carrier's real error bodies
  (nested error.message + flat message + raw-body snippet) and getStopDesks
  no longer swallows failures (a dead credential used to report 'synced 0
  desks, success')

Egress workaround (see KNOWN LIMITATION in CHANGELOG):
- Yalidine's Cloudflare zone blocks ALL Cloudflare-Worker-originated
  traffic (403 error 1106 — proven pre-auth, whole zone, fetch AND raw
  sockets); scripts/yalidine-egress-proxy.ts is a ready Deno Deploy relay
  (secret-authenticated, allowlisted to api.yalidine.app) and the adapter
  routes through it when proxy_base_url/proxy_secret are set in company
  notes — inert otherwise; .agents/skills/yalidine-integration-skill-main/
  vendors the official reference docs we validated against
…n limitation, brand logo

- README: +3 code-verified Delivery Engine claims (carrier zone-name sync,
  dispatch-time delivery-type switching, webhook event log); Yalidine
  Hardening + Landing Pages entries in Recently Shipped
- CHANGELOG [Unreleased]: Added/Fixed for landing pages, theme UX, and the
  Yalidine hardening; Known Limitations section documents the Yalidine
  Cloudflare-Worker egress block (403 error 1106) with impact and both
  workarounds
- replaces the banner with the CodFlow logo asset
@bighadj22
bighadj22 force-pushed the feat/landing-pages-yalidine-hardening branch from 6792bb8 to cbb83f2 Compare September 8, 2026 09:44
@bighadj22
bighadj22 merged commit 6242395 into main Sep 8, 2026
3 checks passed
@bighadj22
bighadj22 deleted the feat/landing-pages-yalidine-hardening branch September 8, 2026 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant