CSP was blocking PostHog scripts/connections (t.sase.tr) and the theme-FOUC
inline script in index.html. Faro /collect/ requests were aborting because
the bare-metal nginx route disappeared during the Coolify migration.
- Add https://t.sase.tr to scriptSrc + connectSrc
- Add sha256 hash for the theme-FOUC inline script in index.html
- Exclude /collect/* from the /api global prefix
- Add FaroCollectController that forwards POST /collect/:id to
faro-collector-prod-eu-west-2.grafana.net (overridable via FARO_UPSTREAM)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gitea (git.semih.ai) is the canonical remote. Coolify pulls from Gitea
and triggers builds via Gitea webhooks. Deployment notifications come
from Coolify's built-in Telegram integration (team-level, configured
2026-05-14).
What used to be done by GH Actions:
- deploy.yml (push main → SSH → PM2 → bare-metal sase.tr)
- sync-dev-to-gitea.yml (mirror github/dev → gitea/dev)
What replaces it:
- Push to gitea/main → Coolify webhook → sparkling-snake (production env, prod-iskelet ready)
- Push to gitea/dev → Coolify webhook → good-gerenuk (staging env, dev.sase.tr)
- Both deploys notified via Coolify → Telegram (chat 7840804807)
qa-gate.yml retained for now (PR test gating); will revisit when Gitea
Actions / Drone are wired or removed if obsolete.
The post-P0 subscription audit listed Sections 4 (trust copy aria-labels),
5 (payment badge SVGs), and 9 (cancel-save dialog width) as outstanding
P1 polish items. All three have shipped to dev:
- Section 4 → FN-294 / 7036b18
- Section 5 → FN-296 / e5201b2
- Section 9 → 1dfeb13 (manual; FN-291/295 attempts both failed/duplicated)
Designer's heartbeat kept reading the stale status table and re-spawning
duplicate tasks (FN-291 → FN-296 → FN-326 chain for Section 5 alone).
Mark them DONE with commit refs and add an explicit "do not create new
tasks" note so heartbeats stop looping on it.
Fusion is allowed to ship to dev.sase.tr (staging) only — the user owns
the dev→main GitHub merge that promotes to production. Heartbeat-created
tasks kept hallucinating prod deploys / monitoring (FN-291 / 315 / 323 /
324 / 311 etc.), and an entire e2e suite targeting live sase.tr was merged
under FN-320 with hardcoded test creds.
Changes:
- Add "Project-wide rule: Fusion ships to dev.sase.tr only" block to all
7 agent personas (ceo/cto/cpo/designer/qa/backend-eng/frontend-eng).
- Add /AGENTS.md at project root as the canonical rule (loaded once
pi-markdown-workflows is reinstalled in the container).
- Remove production-targeted e2e suite under apps/web/e2e/ (auth.setup.ts,
deployment-audit.cjs, p0-subscription-cro.spec.ts, p0-subscription-cro-verify.cjs,
playwright.config.ts, screenshots/).
- Remove docs/product/funnel-monitoring-p0-cro-post-deploy-2026-05.md
(prod-monitoring deliverable, out-of-scope).
- Subscription downgrade-offer dialog: avoid mobile-viewport overflow by
capping width at calc(100vw - 2rem) below sm breakpoint
(formerly attempted in failed/duplicate FN-291/295).
A user who picked a plan + brands, clicked the checkout button to
create the pending subscription, then closed the tab without paying
came back to a frozen "Mevcut Plan · Bekliyor" card with no payment
button and no way to edit. Status was "pending", which my conditional
treated as an active subscription and routed away from the stepper.
Frontend
- hasActiveSub narrowed to active|cancelled only. Pending users now
see the stepper again.
- New effect pre-fills selectedPlanKey, selectedBrandIds,
billingPeriod from the pending row on first render and jumps
straight to the payment step.
- New PendingPaymentBanner at the top of the stepper with
"Odemeye devam et" (scrolls + sets step=payment) and "Vazgec"
(calls cancel-pending then resets selection).
Backend
- subscriptions.service.create() auto-expires existing pending
rows for the user before inserting a new one — abandoned checkouts
no longer accumulate and a fresh attempt with a different plan
doesn't 409.
- New PATCH /subscriptions/cancel-pending + service method for
the banner's explicit cancel action.
i18n: subscription.pendingBanner.* (tr/en).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The 0004 migration file shipped in 6a0b36a but _journal.json did not
include its entry, so the hash-based migrate runner saw "0 applied,
4 already applied" on the previous deploy. Append idx 4 so the next
deploy picks it up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Subscription/checkout flow rewritten end-to-end. The plan-card "Devam Et"
button silently wiped brand selection on re-click and the actual proceed
button lived offscreen — fixed by collapsing plan/brands/payment/confirm
into a single vertical stepper with one sticky CTA.
Backend
- Stripe Hosted Checkout (`/payments/stripe/checkout`) + webhook
(`/payments/stripe/webhook`, raw body) replacing the stubbed Iyzico
module. Webhook activates subscription on `checkout.session.completed`,
expires the pending subscription on cancel/expire so users can retry.
- New `bank_accounts` table — multiple rows, single `is_active` enforced
by a partial unique index. Admin CRUD under `/admin/bank-accounts`
with multipart QR upload to MinIO; atomic `/activate` swap in a
transaction; `GET /payments/bank-info` returns the active row.
- `payments` gains `stripe_session_id`, `stripe_payment_intent_id`,
`bank_account_id`. EFT flow now reads the active bank account at
payment time and stores the FK for reconciliation.
- Env: `IYZICO_*` removed, `STRIPE_*` added (validated by zod schema).
- `main.ts` `rawBody: true` for Stripe signature verification.
- Drizzle 0003 snapshot id collision fixed (VIEW-only migration shared
prevId with 0002, blocking new generates).
Frontend
- `/dashboard/subscription` rewritten as a 4-step vertical stepper with
step-aware sticky bottom CTA; plan re-selection is idempotent and
preserves brand state. `/dashboard/subscription/pay` deleted; Stripe
returns to the same page via `?stripe=success|cancelled` and the UI
polls `/subscriptions/me` until the webhook activates the row.
- New components: `bank-transfer-card.tsx` (DB-driven IBAN + Kolay Adres
+ uploaded QR image + receipt upload) and `stripe-checkout-button.tsx`.
- Active subscription view, trial onboarding/urgency banner, downgrade
and cancel dialogs preserved.
- TR/EN i18n: new `subscription.steps.*`, `subscription.stickyCta.*`,
`payment.stripe.*`, `payment.bank.*`; provider label updated.
PostHog: `method: "iyzico"` → `"stripe"`; new events `iban_copied`,
`kolay_adres_copied`, `qr_viewed`, `eft_initiated`,
`stripe_redirect_returned`.
Deploy runs `db:migrate` which applies 0004_hot_quicksilver
(additive: new table + nullable columns; safe to apply on prod).
Operator must add `STRIPE_SECRET_KEY`, `STRIPE_PUBLISHABLE_KEY`,
`STRIPE_WEBHOOK_SECRET` to env and create the first
`bank_accounts` row via the admin endpoint before the bank tab works.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useEffect and useCallback were declared after three `return null` guards,
so when a user's subscription transitioned into the trial+≤7-days window
React saw more hooks than the previous render and crashed with #310.
Hoisted all hooks above the single guard return; render and dismiss
behavior unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pnpm/action-setup@v4 conflicts when both workflow `version: 10` and
package.json `"packageManager": "pnpm@10.29.3"` are set. Drop the
workflow pin and let action-setup read packageManager.
Unblocks the QA gate workflow on PRs (was failing in setup before
tests ran, e.g. run 25784218896).