# WB Listora 1.2.0 - execution plan (autovap v2 manifest)
# Drafted 2026-06-06 from plan/1.2.0-roadmap.md (post-1.1.0 manifests, source-verified).
# Schema: waves[] -> directions[]; each direction carries:
#   have:           what exists today (current 1.1.0 state)
#   need:           what must be built/changed
#   customer_value: why an end customer feels it (usability-first)
# All decisions resolved 2026-06-06 (functionality-first directive). Ready for /autovap-migrate.
# Scope: 1.2.0 = functionality improvement. HivePress migration DEFERRED to 1.3.0 (DEC-3).

plan:
  name: wb-listora-1.2.0
  target_version: 1.2.0
  lockstep:
    - repo: wbcomdesigns/wb-listora
      path: .
    - repo: wbcomdesigns/wb-listora-pro
      path: ../wb-listora-pro
  source_doc: plan/1.2.0-roadmap.md
  created: "2026-06-06"
  release_rules:
    - "Minor release: additive only. No removals (production rule 8/9)."
    - "Every direction updates the manifest + adds journeys/runbook rows in the same PR (self-growth contract)."
    - "Green /wp-plugin-smoke combo before tag; bin/build-release.sh smoke gate enforces."

decisions_resolved:
  - id: DEC-1
    question: "Drop the remaining do_action_deprecated bridge in 1.2.0 (P1-8 plan) or keep until 2.0 (production rule 9: removals are major-only)?"
    resolution: keep-until-2.0
    note: "Default per functionality-first directive 2026-06-06 (keep = zero work beyond updating the stale P1-8 plan note). Owner can override."
  - id: DEC-2
    question: "listora_analytics in Free: build analytics-lite (option a) or document the table as an intentional Pro-only exception (option b)?"
    resolution: analytics-lite
    note: "Owner directive 2026-06-06: focus on functionality improvement. Analytics-lite is the highest-leverage free-tier functional win."
  - id: DEC-3
    question: "HivePress migration in 1.2.0?"
    resolution: deferred
    note: >
      Owner decision 2026-06-06: do HivePress later (1.3.0 candidate);
      1.2.0 focuses on functionality improvement. REQUIRED ripple: the
      customer-facing stub string in Pro class-competitor-migration.php:242
      currently promises 'coming in 1.2.0' - must be reworded in wave 1
      (see W1-D6) so we do not ship a broken promise.

waves:
  # ---------------------------------------------------------------- WAVE 1
  - id: wave-1
    title: "Hygiene + guards (cheap, zero customer risk, do first)"
    directions:
      - id: W1-D1
        repo: wb-listora
        title: "build-release.sh: explicit vendor SDK exclude"
        priority: P3
        effort: S
        have: >
          1.1.0 zip leaked 75 files from the untracked vendor/wbcom-credits-sdk
          submodule leftover; hotfixed with rm -rf before packaging. Canonical
          SDK ships from libs/wbcom-credits-sdk.
        need: >
          Add --exclude='vendor/wbcom-credits-sdk/' to the rsync stage in both
          plugins' bin/build-release.sh as a permanent guard.
        customer_value: >
          Smaller, cleaner plugin zip; no risk of stale duplicate SDK code
          shipping to live sites and confusing future upgrades.
        gates: [zip-content-assert]

      - id: W1-D2
        repo: wb-listora-pro
        title: "Manifest hygiene: remove dropped credit_log table"
        priority: P3
        effort: S
        have: >
          audit/manifest.json tables[] still lists {prefix}credit_log, but the
          table was dropped in the 1.5.0 credit refactor (only DROP-TABLE
          comments remain in class-pro-migrator.php).
        need: >
          Remove the entry; re-validate table count (7 -> 6) in manifest +
          summary; refresh dead-listeners.json (need_published/need_rejected
          are no longer orphans - fire-sites at class-reverse-listings.php:945,1077)
          and drop the stale CLAUDE.md orphan claim.
        customer_value: >
          Indirect: accurate manifests keep every future bug fix targeted,
          which is what keeps regressions off customer sites.
        gates: [manifest-validate]

      - id: W1-D3
        repo: wb-listora
        title: "Trim manifest.summary.json to its 3 KB budget"
        priority: P3
        effort: S
        have: "Summary is ~6.6 KB; verbose refresh_kind/_last_event narratives."
        need: "Restructure the generated block; keep counts + pointers only."
        customer_value: "None direct; faster agent onboarding -> faster fixes."
        gates: [size-budget-3kb]

      - id: W1-D4
        repo: wb-listora-pro
        title: "INV-14: replace 2 raw hex fallbacks in pro-admin.css"
        priority: P2
        effort: S
        have: >
          pro-admin.css:723-724 carry var(--listora-fg-default, #1a1a1a) and
          var(--listora-bg-elevated, #ffffff); 13/14 invariants pass.
        need: "Token-only fallbacks; restore 14/14 architecture invariants."
        customer_value: >
          Admin screens stay legible in dark mode and custom admin color
          schemes instead of flashing hardcoded light-mode colors.
        gates: [architecture-checks]

      - id: W1-D6
        repo: wb-listora-pro
        title: "Reword HivePress stub - remove the 1.2.0 promise (DEC-3 ripple)"
        priority: P0
        effort: S
        have: >
          class-competitor-migration.php:242 tells customers 'HivePress
          migration is not available in this version - coming in 1.2.0'.
          HivePress is now deferred past 1.2.0 (DEC-3).
        need: >
          Change the string to a non-versioned promise ('planned for an
          upcoming release') so 1.2.0 does not ship a broken commitment.
          Keep the Competitor_Detector recognition intact.
        customer_value: >
          Honest product messaging - a customer told 'coming in 1.2.0' who
          then installs 1.2.0 without it loses trust instantly.

      - id: W1-D5
        repo: wb-listora
        title: "Close js_only_activation detector entries (2 remaining)"
        priority: P2
        effort: S
        have: >
          static_analysis.js_only_activation lists 3 UI activations that only
          work with JS; one (settings sections) already has php_fallback: true.
        need: >
          Add server-side fallbacks for the other two or document them with
          php_fallback rationale so the detector reads clean.
        customer_value: >
          Pages still render usable content when JS fails or is blocked -
          fewer 'page looks broken' moments on slow/locked-down devices.
        gates: [manifest-validate]

  # ---------------------------------------------------------------- WAVE 2
  - id: wave-2
    title: "Dedupe + entry-point wiring (three-entry-point rule)"
    depends_on: [wave-1]
    directions:
      - id: W2-D1
        repo: wb-listora
        title: "Consolidate html_to_text onto Email_Body_Formatter"
        priority: P1
        effort: M
        have: >
          Three live copies: Free class-notifications.php:1393 (private), Free
          class-email-body-formatter.php:44 (canonical, exposed as
          wb_listora_email_html_to_text()), Pro class-email-helpers.php:115.
          This was the documented 1.1.0 cleanup that did not land.
        need: >
          Notifications + Pro Email_Helpers delegate to the canonical helper;
          keep Pro's method as a thin deprecated wrapper (additive rule);
          assert via bin/cleanup-duplicate-detect.php so it cannot regress.
        customer_value: >
          Every email (instant notifications, digests, Pro lifecycle emails)
          renders identical clean plain-text - no more subtle formatting
          differences between email types in the same inbox.
        journeys: [regression/email-plaintext-parity]

      - id: W2-D2
        repo: wb-listora-pro
        title: "coupon_usage: wire missing entry points"
        priority: P1
        effort: M
        have: >
          Table written by class-coupons.php only. Admin coupons page exists;
          no REST surface; buyers never see which coupon was applied after
          purchase.
        need: >
          (1) Admin-gated REST read for coupon usage (per-coupon redemption
          list/counts). (2) Show the applied coupon + discount on the credit
          purchase receipt and in the buyer's credit history. (3) Manifest rows
          for both.
        customer_value: >
          Buyers get proof their discount applied (fewer 'my coupon did not
          work' support tickets); owners can audit coupon performance without
          database access.
        journeys: [customer/coupon-redemption-receipt, admin/coupon-usage-rest]

      - id: W2-D3
        repo: wb-listora-pro
        title: "webhook_log: admin REST read + retry"
        priority: P1
        effort: M
        have: >
          Outgoing-webhooks admin list exists (paginated in 1.1.0). Zero REST
          routes cover deliveries; retry only via admin UI.
        need: >
          Admin-gated REST: list deliveries (status, attempts, last error) +
          trigger retry. Manifest rows + journey.
        customer_value: >
          Site owners integrating CRMs/Zapier can diagnose missed webhooks
          from their own tooling instead of clicking through wp-admin, and
          re-fire a failed delivery in one call.
        journeys: [admin/webhook-deliveries-rest]

      - id: W2-D4
        repo: wb-listora
        title: "Customer-facing --sm buttons: 36px -> 40px tap floor"
        priority: P2
        effort: S
        have: >
          AUD-F7 (1.1.0) raised small buttons 32 -> 36px; ux-foundation floor
          is 40px. wppqa still warns on listora-components.css.
        need: >
          Lift --sm tap target to 40px (visual size may stay smaller with
          padding/pseudo-element hit area) or record a documented exception.
          Verify at 390px viewport.
        customer_value: >
          Thumb-sized tap targets on phones - fewer mis-taps on Save/Filter/
          pagination controls where most directory browsing happens.
        journeys: [regression/sm-button-tap-target]

      - id: W2-D5
        repo: wb-listora
        title: "featured_image a11y enforcement (visual_required_no_enforcement)"
        priority: P2
        effort: S
        have: >
          Both manifests flag 1 visual-required field (featured image) with no
          enforcement - listings can render imageless cards with no alt text
          fallback.
        need: >
          Enforce alt text fallback (listing title) on card/detail/map popup
          images and a deterministic placeholder for missing images; clear the
          detector or document it.
        customer_value: >
          Screen-reader users hear which listing an image belongs to; grids
          with missing photos look intentional instead of broken.
        journeys: [regression/card-image-alt-fallback]

  # ------------------------------------------------- DEFERRED (1.3.0 candidate)
  - id: deferred-hivepress
    title: "HivePress migration - DEFERRED past 1.2.0 (DEC-3, owner 2026-06-06)"
    status: deferred
    target: "1.3.0"
    note: "Customer-facing stub string must be reworded in 1.2.0 - see W1-D6."
    directions:
      - id: W3-D1
        repo: wb-listora
        title: "HivePress migrator in Free"
        priority: P0
        effort: L
        have: >
          Customer-facing promise in Pro REST ('coming in 1.2.0',
          class-competitor-migration.php:242). Schema audit exists at
          audit/architecture/competitor-schemas/hivepress.md. Migration_Base +
          Term_Helper + migration-context arg infrastructure all shipped.
        need: >
          includes/import-export/class-hivepress-migrator.php extending
          Migration_Base (detect_source_fields, get_default_mapping, extract_*
          template methods). Listings, categories, locations, images, reviews
          per the schema audit. Fire wb_listora_listing_submitted with
          context: migration. Register in wb_listora_get_migrators().
        customer_value: >
          HivePress site owners can switch to Listora without losing listings,
          reviews, or images - the single biggest adoption blocker for that
          audience, and a promise we already made in the product UI.
        journeys: [admin/migrate-from-hivepress]

      - id: W3-D2
        repo: wb-listora-pro
        title: "Visual Importer wraps HivePress + remove 1.2.0 stub"
        priority: P0
        effort: S
        depends_on: [W3-D1]
        have: "Competitor_Migration REST returns the 'coming in 1.2.0' stub for HivePress."
        need: >
          Remove the stub branch; verify Competitor_Detector recognises
          HivePress installs; Visual Importer mapping UI works against the new
          migrator via wb_listora_get_migrator('hivepress').
        customer_value: >
          Pro buyers get the drag-drop field mapping + preview UX for the
          HivePress switch instead of a CLI-grade import.
        journeys: [admin/visual-import-hivepress]

      - id: W3-D3
        repo: wb-listora
        title: "Docs + store: migrate-from-hivepress guide"
        priority: P1
        effort: S
        depends_on: [W3-D1]
        have: >
          docs/website has migrate-from-{directorist,geodirectory,bdp,listingpro}.md;
          no HivePress guide (correctly absent today).
        need: >
          Add migrate-from-hivepress.md following the existing pattern; sync to
          store docs at release per the store-product-publisher update playbook.
        customer_value: >
          Switchers get a step-by-step path with field-mapping notes instead of
          guessing; reduces pre-sales 'can I migrate from HivePress?' tickets.

  # ---------------------------------------------------------------- WAVE 3
  - id: wave-3
    title: "Free analytics-lite (DEC-2 resolved: build it)"
    depends_on: [wave-2]
    directions:
      - id: W4-D1
        repo: wb-listora
        title: "Listing view counts in Free (three entry points)"
        priority: P1
        effort: M
        have: >
          Free's activator creates listora_analytics; Free has zero read/write/
          UI/REST surface for it (verified - only Pro Analytics + Moderator and
          Pro's retention cron touch it). Dead table on Free-only sites.
        need: >
          Free records listing views (bot-filtered, rate-limited - reuse Pro's
          nonce-gate pattern). Three entry points: (1) frontend - view count on
          the owner dashboard listing rows; (2) backend - views column on the
          admin listings table; (3) API - views field on the listing REST
          response (owner/admin gated). Pro keeps charts, traffic sources,
          retention, exports; Pro's feature supersedes the lite counter when
          active.
        customer_value: >
          Free site owners finally see whether their directory gets traffic -
          'is anyone viewing my listing?' is the first question every listing
          owner asks. Also the natural upsell moment for Pro analytics.
        journeys: [customer/owner-sees-view-count, regression/views-pro-supersedes-lite]

  # ---------------------------------------------------------------- WAVE 4
  - id: wave-4
    title: "Background demo + CSV import"
    depends_on: [wave-2]
    directions:
      - id: W5-D1
        repo: wb-listora
        title: "Demo import on Action Scheduler batches"
        priority: P1
        effort: L
        have: >
          1.1.0 hardened the synchronous import (10s image timeout, gallery
          cap, URL dedupe, skip-on-fail) but its own changelog defers the
          background rearchitecture. AS is bundled in Free; Cron_Scheduler
          helper exists.
        need: >
          Queue demo-pack import as AS batch jobs (listings -> images ->
          index); progress UI in the setup wizard (polled via REST); resumable
          on failure; same pipeline reused for large CSV imports.
        customer_value: >
          First-run experience stops hanging on slow hosts - the setup wizard
          shows live progress instead of a frozen request that times out at
          the worst possible moment (minute one of evaluating the plugin).
        journeys: [admin/demo-import-background-progress, regression/csv-import-large-file]

  # ---------------------------------------------------------------- WAVE 5
  - id: wave-5
    title: "Namespace classification + alias policy (docs-heavy)"
    depends_on: [wave-1]
    directions:
      - id: W6-D1
        repo: wb-listora-pro
        title: "Classify 64 cross-namespace fire-sites (46 unique hooks)"
        priority: P1
        effort: M
        have: >
          Pro fires 64 hooks under Free's wb_listora_* namespace
          (static_analysis.cross_namespace_fires). P1-8 renamed 4 in 1.0.4.
        need: >
          Classify each unique hook: (a) intentional extension of Free's
          public surface - document as such in both manifests; (b) Pro-only
          internal - rename to wb_listora_pro_* WITH a deprecation bridge
          (additive). No removals.
        customer_value: >
          Indirect but real: third-party devs customising a customer's
          directory get a stable, documented hook surface - fewer 'my
          customisation broke after update' incidents.
      - id: W6-D2
        repo: wb-listora-pro
        title: "Deprecated alias policy (DEC-1 resolved: keep until 2.0)"
        priority: P2
        effort: S
        have: >
          One do_action_deprecated bridge remains from P1-8; plan note says
          'drop in 1.2.0' while production rule 9 says removals are
          major-only.
        need: >
          Keep the bridge through 1.x; update the stale P1-8 plan note so the
          two rules stop contradicting. Removal lands in 2.0.
        customer_value: >
          Sites with mu-plugin customisations on the old hook names keep
          working through every 1.x update.

  # ------------------------------------------------------------- UPSTREAM
  - id: upstream
    title: "Out-of-repo dependencies (parallel to any wave)"
    directions:
      - id: U-D1
        repo: wbcom-credits-sdk
        title: "Fix Transaction_Log::maybe_create_table column-upgrade defect"
        priority: P1
        effort: S
        have: >
          maybe_create_table early-returns when the table exists, so schema
          upgrades skip new columns on existing installs. 1.1.0 worked around
          it for payment_intent (9ee59cc).
        need: >
          Upstream fix: version-compare dbDelta path so additive columns land
          on existing installs; re-sync libs/wbcom-credits-sdk in Free.
        customer_value: >
          Paying customers' credit/refund history stays complete across
          updates without manual database surgery.
      - id: U-D2
        repo: wb-listora
        title: "Firefox business-hours verification lane"
        priority: P2
        effort: M
        have: >
          D.business-hours-firefox journey is a manual gate every release
          (Playwright MCP is Chromium-only); flatpickr regression history.
        need: >
          Either accept as a documented per-release manual step (status quo,
          zero cost) or add a Firefox lane (wdio/playwright-firefox CLI) for
          this one journey.
        customer_value: >
          Firefox users keep a working time picker on the submission form -
          the exact regression that shipped twice before the manual gate
          existed.

acceptance:
  per_direction:
    - "Manifest delta in the same PR (counts verified against source)."
    - "Journey + runbook row added per the self-growth contract."
    - "WPCS + PHPStan + architecture invariants green (composer ci)."
    - "Browser-verified including 390px viewport for any UI change."
  per_release:
    - "Green /wp-plugin-smoke combo report matching 1.2.0."
    - "readme.txt changelog: full history, action-prefix format, both repos."
    - "Lockstep: free and pro tag together; cross-linked release bodies."
