chore(gitflow): back-merge main → develop (reconcile history, 0 content change) - #52
Merged
Merged
Conversation
…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>
|
Note Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported. |
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



GitFlow reconciliation: back-merge
main→develop(zero content change)Why: past
develop→mainreleases were squash-merged andmainwas never back-merged intodevelop, so the branches diverged in history while sharing content. As a result the release PR #50 hits spurious add/add conflicts. This mergesmain(4167a72) back intodevelopsomainbecomes an ancestor ofdevelopagain.Strategy:
git merge -s ours main—developalready contains all of main's content (develop = main + the unreleased #48/#49 work; verified:mainis−482/+33vsdevelop, i.e. only develop-additions differ). So this recordsmainas 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
mainparent 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
developan ancestor ofmain→ convergence restored, so future releases stop conflicting).