Skip to content

chore(gitflow): back-merge main → develop (reconcile history, 0 content change) - #52

Merged
mahmutkaya merged 2 commits into
developfrom
chore/reconcile-main-into-develop
Jul 12, 2026
Merged

mahmutkaya merged 2 commits into
developfrom
chore/reconcile-main-into-develop

Conversation

@mahmutkaya

Copy link
Copy Markdown
Contributor

GitFlow reconciliation: back-merge maindevelop (zero content change)

Why: past developmain releases were squash-merged and main was never back-merged into develop, so the branches diverged in history while sharing content. As a result the release PR #50 hits spurious add/add conflicts. This merges main (4167a72) back into develop so main becomes an ancestor of develop again.

Strategy: git merge -s ours maindevelop already contains all of main's content (develop = main + the unreleased #48/#49 work; verified: main is −482/+33 vs develop, i.e. only develop-additions differ). So this records main as merged without changing a single file. The diff of this PR is empty.

Merge method: must be a merge commit (not squash) — squashing would drop the main parent link and defeat the reconciliation. Quality gate (Gemini/Sonar) is N/A here: zero code diff. CI runs and passes trivially.

Companion to #50 (which will then merge cleanly as a merge commit, making develop an ancestor of main → convergence restored, so future releases stop conflicting).

mahmutkaya and others added 2 commits July 12, 2026 01:05
…nt (#42/#43) + GitFlow docs (#46)

* docs(claude): rewrite §8 to GitFlow (#40)

* docs(claude): rewrite §8 to GitFlow (develop = integration, main = releases)

Also add a no-commit-to-branch pre-commit guard mirroring the server-side
main-develop ruleset (commit-stage only; keeps the review-gate pre-push symlink).

* ci: run CI on PRs to develop (GitFlow) — unblocks develop-based PRs

* ci+docs: run CI on PRs to develop (GitFlow) + branch guard + §8 (#41)

* docs(claude): rewrite §8 to GitFlow (develop = integration, main = releases)

Also add a no-commit-to-branch pre-commit guard mirroring the server-side
main-develop ruleset (commit-stage only; keeps the review-gate pre-push symlink).

* ci: run CI on PRs to develop (GitFlow) — unblocks develop-based PRs

* feat(aeo): content engine — changelog, RUMI case study, GloriaFood comparison (sofra #6) (#42)

AEO plan §2 slice: three localized public marketing pages under [locale],
in all six locales, with per-page hreflang/canonical metadata + JSON-LD.

- /changelog — dated what's-new entries (data-driven via
  components/changelog-data.ts + changelog.* messages; WebPage JSON-LD
  with dateModified = newest entry)
- /case/rumi — RUMI Geneva case study, verifiable facts only (live since
  2026-06-29, 10 guest languages, EU infra); the quantitative stats
  section is deliberately omitted (owner-owed), with an honest
  "where are the numbers?" note instead. Article JSON-LD.
- /compare/gloriafood — honest, table-driven Sofra-vs-GloriaFood page
  with a visible "last checked" date; every GloriaFood claim verified
  2026-07-10 against their public pages (URLs in
  components/compare-gloriafood-data.ts and rendered as a visible
  Sources section). Conservative wording for anything unverified
  (website/app add-on prices described qualitatively only).

Cross-cutting:
- lib/seo.ts: shared SITE_URL + pageAlternates/marketingPageMetadata
  (layout refactored to use it); unit-tested and added to the coverage
  floor scope
- components/JsonLdScript.tsx: shared hardened JSON-LD renderer
  (JsonLd.tsx refactored onto it)
- Header/Footer anchors switched to locale-aware /#anchor links so the
  nav works from subpages; footer links the three new pages
- sitemap.xml now lists all 4 routes x 6 locales; llms.txt mentions the
  new pages
- messages: +71 keys per locale, parity green across en/fr/de/nl/tr/ar

Validation: typecheck, eslint, next build (all routes SSG x6 locales),
vitest 89 passed + coverage floor, i18n parity, check-single-file --all,
runtime smoke (200 + JSON-LD + hreflang + RTL verified on next start).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(aeo): PR #42 Gemini/Sonar follow-ups — sitemap timestamp hoist, JsonLdScript cleanups, Readonly props (#43)

Post-merge triage of the Gemini review + open Sonar issues on #42 (the PR
merged before the async bot review landed — process gap fixed separately):

- app/sitemap.ts: one new Date() hoisted out of the flatMap/map (Gemini
  MEDIUM) — also keeps lastModified consistent across all 24 entries.
- components/JsonLdScript.tsx: stable keys (the serialized JSON-LD string,
  not the array index — S6479), replaceAll + String.raw for the "<" escape
  (S7781/S7780), Readonly props (S6759). Escaping behavior unchanged.
- Readonly props on the three content pages (S6759).

Declined with rationale (replied on the PR): switching the header /login
link to LocaleLink — the control plane (login/dashboard/admin) is
deliberately NOT locale-prefixed (sofra CLAUDE.md); LocaleLink would
mis-route it. The independent pre-push review verified this distinction.

typecheck clean · eslint 0 warnings · next build 43/43 SSG · vitest 89/89 ·
message parity 6 locales · file checker green.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* docs: fix stale PR-template branch line for GitFlow (target develop, not main) (#44)

The Standard checklist still said 'Branch off main, PR to main' — stale
since the 2026-07-10 GitFlow reinstatement (CLAUDE.md §8: feature/fix/
chore/docs PRs target develop; only the develop → main release PR is a
release).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* feat(billing): partner onboarding + self-serve monthly subscription (#45)

* feat(billing): partner onboarding + self-serve monthly subscription

Let an admin onboard a referred partner as the reseller payer for a tenant and
hand them a pre-approval link; the partner sets a password, sees a welcome panel
(go-live date + monthly amount), and starts an auto-monthly Mollie subscription
themselves. First concrete case: RUMI (tenant #1, brought in via a partner).

Reuses the existing invite -> set-password -> ACTIVE flow and the Mollie
subscription engine (customer -> PENDING plan -> first-payment checkout ->
webhook activation, all unchanged). The gap this fills:

- Admin "Onboard a partner" action (requireAdmin): creates/reuses the PARTNER
  user + the tenant Client (linked by tenantSlug) + a PENDING plan with NO
  Mollie customer yet, mints the invite link, returns it for manual sharing.
- Partner welcome hero on /dashboard + a /dashboard/billing page; nav branches
  by derived state (has-billing -> Plan; commissions/no-billing -> Ledger) so a
  reseller and a commission partner each see the right surface.
- Partner-scoped startPaymentAction (requirePartner + client-ownership check +
  rate-limit) creates the Mollie customer + first-payment checkout on demand and
  redirects to Mollie server-side (progressively enhanced).

Schema: TenantBilling.mollieCustomerId is now nullable (a plan can be defined
before its first payment; kept @unique) + a display-only liveSince. Handwritten
migration; the existing nullable call sites are adjusted.

Money-safety (live Mollie key):
- Refuse a second first payment once one is paid (the mandate-lag window where
  the sub still reads PENDING) -- server guard + the UI shows "processing", never
  a second pay button.
- Idempotent mollieCustomerId claim so concurrent starts can't create two
  customers and strand a paid payment.

EUR throughout; the commission ledger is untouched (it belongs to commission-type
partners -- a separate, valid partner type).

Out of scope (follow-ups): public direct-owner self-serve signup (OWNER role +
payerUserId); commission automation.

Verify: tsc, eslint, 103 vitest, i18n parity (500 x6), file-length, next build,
and scripts/e2e-local.mjs end-to-end against a throwaway postgres (migration
applies clean; onboard -> invite -> welcome -> billing all pass). Billing QA is on
the test key only; the first live charge stays the owner's deliberate action.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: put lib/billing-display.ts under the coverage floor

The plan-state helper carries the double-charge guard (paid first payment ->
'processing', never a second pay button); pin it at the floor like the other
pure lib modules. 100% stmts / 98% branches locally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(billing): bound the partner billing-payment reads (review gate)

The dashboard welcome hero + startFirstPayment pulled a tenant's full
BillingPayment history unpaginated in request paths. planState + the
paid/reuse guards only need FIRST payments, so scope the include to
sequenceType:first + take:20 — bounded and semantically exact (recurring
history never enters these paths). Billing page keeps take:10 for its
history display.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(billing): address Gemini + Sonar review on #45

Gemini:
- onboarding: reject an impossible liveSince (e.g. 2026-02-31 passes the
  format regex but is not a real day) BEFORE any DB write — was a Prisma 500
  + orphan user/client risk.
- start-payment: rate-limit by user.id, not IP (authenticated action → no NAT
  collisions, no proxy-header dependency, nothing to spoof).

Sonar:
- billing page: extract the plan-status nested ternary into an if/else helper (S3358 x2).
- StartPaymentButton: Readonly props (S6759).
- start-payment: type-guard formData billingId instead of String(... ?? "") (S6551).

typecheck/eslint/103 vitest+coverage/file-length/build green; e2e-local all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(onboarding): extract helpers to cut cognitive complexity (Sonar S3776)

onboardPartnerAction tipped over Sonar's cognitive-complexity threshold after
the liveSince guard. Extract resolvePartnerUser / resolveTenantClient /
emailOnboardInvite — the main action is now mostly linear guard-and-go, same
behavior. typecheck/eslint/build green; e2e-local all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* fix(onboarding): robust liveSince validation + orphan-user pre-check (#46 review) (#47)

* fix(onboarding): robust liveSince date validation + orphan-user pre-check (#46 Gemini)

Two medium comments from the release-PR review:
- liveSince: centralize calendar validation in onboardSchema via .refine().
  Note: Gemini's suggested isNaN check does NOT catch the bug it cites —
  new Date('2026-02-31T00:00:00Z') doesn't return Invalid Date, it silently
  ROLLS OVER to Mar 3. Round-trip instead (reconstruct + compare), with a NaN
  guard because the refine still runs when the regex failed (e.g. '').
- onboarding: pre-check tenant-slug ownership BEFORE resolvePartnerUser, so a
  slug held by a different partner can't leave a freshly-created user orphaned.

Unit tests pin both impossible-date forms (Feb-31 rollover + month-13 NaN).
tsc/eslint/104 vitest+coverage(100/98)/build green; e2e-local 24/24 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(onboarding): case-insensitive email compare in the slug-owner pre-check (#47 Gemini)

Emails are case-insensitive; compare accordingly. All current paths store
lowercased email, but don't rely on that in the ownership guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@sonarqubecloud

Copy link
Copy Markdown

@mahmutkaya
mahmutkaya merged commit 6f53b10 into develop Jul 12, 2026
16 checks passed
@mahmutkaya
mahmutkaya deleted the chore/reconcile-main-into-develop branch July 12, 2026 01:10
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