fix: support versioned Mobbin catalog sync - #2315
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Contributor
|
Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews. |
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 7 files
Confidence score: 5/5
- Safe to merge after the addressed issues were fixed.
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
charlietlamb
force-pushed
the
charlie/mobbin-catalog-minimal
branch
from
July 20, 2026 19:17
0a750f4 to
5611d87
Compare
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.
Minimal Mobbin catalog changes only.
Excluded the CLI deletion-check changes; they are unrelated to the versioned import path.
Summary by cubic
Adds support for explicit, versioned Mobbin catalog sync, including renames via
new_plan_id. Preserves provided versions, enforces sequential versioning (even across renames), and orders updates by dependencies to keep license links consistent.New Features
new_plan_idwhen present): fresh plans must start at 1; subsequent versions must be latest + 1; process earlier versions and dependencies first across renames.Bug Fixes
Written for commit 5611d87. Summary will update on new commits.
Greptile Summary
This PR extends the Mobbin catalog sync path to correctly handle batches of plans that carry explicit version numbers, fixing a sort-order bug and adding sequential-version validation so the system can reliably create v1, v2, … in a single API call.
sortCatalogPlansByDependenciesnow treatsversion N-1as an implicit dependency ofversion N, ensuring lower versions are always written before higher ones in the same batch (the old order placedchild@2beforechild@1).validateCatalogPlanVersionTargetsenforces that new versioned plans must be exactlylatestVersion + 1, rejecting gaps (e.g. jumping straight to v2 on a fresh plan); validation fires on both the preview and the update routes.upsertPlansnow detects when a versioned plan doesn't yet exist at the requested version and routes throughupdateProduct({ force_version: true })instead ofcreateProduct, preserving matched Stripe plan prices from the existing latest version.previewUpdateCatalogdefensively wrapsproduct.entitlementsaccess with?? []to tolerate products that arrive without an entitlements array during catalog preview.Confidence Score: 4/5
Safe to merge; changes are self-contained to the catalog versioned-import path and are backed by both a new integration test and an updated unit test.
The core logic in validateCatalogPlanVersionTargets and sortCatalogPlansByDependencies is sound: the topological sort's new implicit v→(v-1) edge guarantees lower versions are processed first, and the sequential-version check correctly simulates already-created versions within the same batch. The upsertPlans versioned path correctly falls back from createProduct to updateProduct(force_version:true) when the plan_id already exists at another version. The only mild concern is that the new !current && latest branch in upsertPlans issues a second DB query per new versioned plan, which is acceptable but worth monitoring under large catalog syncs.
updateCatalog.ts — the new versioned-plan branch omits propagateToVariants; this is likely intentional for catalog sync but worth confirming if update_variant_ids is ever used alongside explicit version numbers in the Mobbin import.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Client participant previewUpdateCatalog participant preflightCatalogPlans participant validateCatalogPlanVersionTargets participant sortCatalogPlansByDependencies participant upsertPlans participant ProductService Client->>previewUpdateCatalog: POST /catalog.preview_update previewUpdateCatalog->>preflightCatalogPlans: activePlans, previews preflightCatalogPlans->>sortCatalogPlansByDependencies: cycle detection (v1 implicit dep of v2) preflightCatalogPlans->>validateCatalogPlanVersionTargets: plans, persistedProducts Note over validateCatalogPlanVersionTargets: enforces version = latestVersion+1 validateCatalogPlanVersionTargets-->>preflightCatalogPlans: OK or RecaseError(400) preflightCatalogPlans-->>previewUpdateCatalog: license previews attached previewUpdateCatalog-->>Client: CatalogPreviewUpdateResponse Client->>previewUpdateCatalog: POST /catalog.update previewUpdateCatalog->>preflightCatalogPlans: preflight (validation runs again) previewUpdateCatalog->>upsertPlans: sorted activePlans loop each plan in dependency order upsertPlans->>ProductService: "getFull(plan_id, version) -> current" alt version specified AND current is null upsertPlans->>ProductService: "getFull(plan_id, no version) -> latest" alt latest exists upsertPlans->>ProductService: updateProduct(force_version:true) else brand new plan upsertPlans->>ProductService: createProduct end else current exists upsertPlans->>ProductService: updateProduct(version, ...) end end upsertPlans-->>Client: CatalogUpdateResponse%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Client participant previewUpdateCatalog participant preflightCatalogPlans participant validateCatalogPlanVersionTargets participant sortCatalogPlansByDependencies participant upsertPlans participant ProductService Client->>previewUpdateCatalog: POST /catalog.preview_update previewUpdateCatalog->>preflightCatalogPlans: activePlans, previews preflightCatalogPlans->>sortCatalogPlansByDependencies: cycle detection (v1 implicit dep of v2) preflightCatalogPlans->>validateCatalogPlanVersionTargets: plans, persistedProducts Note over validateCatalogPlanVersionTargets: enforces version = latestVersion+1 validateCatalogPlanVersionTargets-->>preflightCatalogPlans: OK or RecaseError(400) preflightCatalogPlans-->>previewUpdateCatalog: license previews attached previewUpdateCatalog-->>Client: CatalogPreviewUpdateResponse Client->>previewUpdateCatalog: POST /catalog.update previewUpdateCatalog->>preflightCatalogPlans: preflight (validation runs again) previewUpdateCatalog->>upsertPlans: sorted activePlans loop each plan in dependency order upsertPlans->>ProductService: "getFull(plan_id, version) -> current" alt version specified AND current is null upsertPlans->>ProductService: "getFull(plan_id, no version) -> latest" alt latest exists upsertPlans->>ProductService: updateProduct(force_version:true) else brand new plan upsertPlans->>ProductService: createProduct end else current exists upsertPlans->>ProductService: updateProduct(version, ...) end end upsertPlans-->>Client: CatalogUpdateResponseReviews (1): Last reviewed commit: "fix(catalog): tolerate missing product e..." | Re-trigger Greptile