diff --git a/knowledge/_claude-context/context.md b/knowledge/_claude-context/context.md index 9f537c7a8..edfc7b97a 100644 --- a/knowledge/_claude-context/context.md +++ b/knowledge/_claude-context/context.md @@ -1,7 +1,7 @@ # OpenIAP Project Context > **Auto-generated for Claude Code** -> Last updated: 2026-08-11T08:49:51.526Z +> Last updated: 2026-08-12T06:58:01.676Z > > Usage: `claude --context knowledge/_claude-context/context.md` @@ -2545,8 +2545,10 @@ from OpenIAP 3.0, `react-native-iap` 16.0.0, `expo-iap` 5.0.0, redirects may name removed surfaces when they clearly describe history or route readers to the replacement. - The public migration catalog remains - `/docs/updates/deprecations`. It records the removed-to-replacement mapping - and the exact package major boundary. + `/docs/updates/migration` (published at `/docs/updates/deprecations` until + 2026-08; that path still redirects). It is organized as one section per + coordinated major train, and each train records the removed-to-replacement + mapping and the exact package major boundary. - A future deprecation must be introduced through the canonical GraphQL deprecation directives or an explicit package-local notice, name one future major boundary, include a canonical replacement, and update the migration diff --git a/knowledge/internal/07-docs-consistency.md b/knowledge/internal/07-docs-consistency.md index b0ee17038..9cc62936f 100644 --- a/knowledge/internal/07-docs-consistency.md +++ b/knowledge/internal/07-docs-consistency.md @@ -287,8 +287,10 @@ from OpenIAP 3.0, `react-native-iap` 16.0.0, `expo-iap` 5.0.0, redirects may name removed surfaces when they clearly describe history or route readers to the replacement. - The public migration catalog remains - `/docs/updates/deprecations`. It records the removed-to-replacement mapping - and the exact package major boundary. + `/docs/updates/migration` (published at `/docs/updates/deprecations` until + 2026-08; that path still redirects). It is organized as one section per + coordinated major train, and each train records the removed-to-replacement + mapping and the exact package major boundary. - A future deprecation must be introduced through the canonical GraphQL deprecation directives or an explicit package-local notice, name one future major boundary, include a canonical replacement, and update the migration diff --git a/libraries/expo-iap/README.md b/libraries/expo-iap/README.md index b0996c59f..facc3cae1 100644 --- a/libraries/expo-iap/README.md +++ b/libraries/expo-iap/README.md @@ -9,7 +9,7 @@ Expo IAP is a powerful in-app purchase solution for Expo and React Native applic If you're shipping an app with expo-iap, we’d love to hear about it—please share your product and feedback in [expo-iap Q&A Discussions](https://github.com/hyodotdev/openiap/discussions/categories/expo-iap). Community stories help us keep improving the ecosystem. -Open IAP +Open IAP @@ -64,7 +64,7 @@ For detailed usage examples and error handling, see the [documentation](https:// ## Powered by OpenIAP -OpenIAP +OpenIAP Expo IAP conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides: diff --git a/libraries/expo-iap/example/amazon.sdktester.json b/libraries/expo-iap/example/amazon.sdktester.json index 43cd5e901..cd536dfc8 100644 --- a/libraries/expo-iap/example/amazon.sdktester.json +++ b/libraries/expo-iap/example/amazon.sdktester.json @@ -4,28 +4,28 @@ "price": 0.99, "title": "10 Bulbs", "description": "A small pack of bulbs for testing consumable purchases", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.30bulbs": { "itemType": "CONSUMABLE", "price": 1.99, "title": "30 Bulbs", "description": "A larger pack of bulbs for testing consumable purchases", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.certified": { "itemType": "ENTITLED", "price": 4.99, "title": "Certified", "description": "A non-consumable entitlement for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.premium": { "itemType": "SUBSCRIPTION", "price": 4.99, "title": "Premium Monthly", "description": "Monthly premium access for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png", + "smallIconUrl": "https://openiap.dev/logo.webp", "subscriptionBase": "dev.hyo.martie.premium.base", "subscriptionParent": "dev.hyo.martie.premium.parent", "term": "Monthly" @@ -35,7 +35,7 @@ "price": 49.99, "title": "Premium Yearly", "description": "Yearly premium access for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png", + "smallIconUrl": "https://openiap.dev/logo.webp", "subscriptionBase": "dev.hyo.martie.premium.base", "subscriptionParent": "dev.hyo.martie.premium.parent", "term": "Yearly" diff --git a/libraries/flutter_inapp_purchase/README.md b/libraries/flutter_inapp_purchase/README.md index 9756fd395..e5a0ead59 100644 --- a/libraries/flutter_inapp_purchase/README.md +++ b/libraries/flutter_inapp_purchase/README.md @@ -7,7 +7,7 @@ A comprehensive Flutter plugin for implementing in-app purchases that conforms to the [Open IAP specification](https://openiap.dev) -Open IAP +Open IAP @@ -103,7 +103,7 @@ final sameIap = FlutterInappPurchase.instance; // Same instance ## Powered by OpenIAP -OpenIAP +OpenIAP flutter_inapp_purchase conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides: diff --git a/libraries/godot-iap/README.md b/libraries/godot-iap/README.md index 051ea982b..a25f35738 100644 --- a/libraries/godot-iap/README.md +++ b/libraries/godot-iap/README.md @@ -13,7 +13,7 @@ A comprehensive in-app purchase plugin for Godot 4.x that conforms to the Open IAP +Open IAP @@ -80,7 +80,7 @@ See the [Quick Start Guide](https://openiap.dev/docs/setup/godot) for complete c ## Powered by OpenIAP -OpenIAP +OpenIAP godot-iap conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides: diff --git a/libraries/kmp-iap/README.md b/libraries/kmp-iap/README.md index faf3a7e89..b82e6beeb 100644 --- a/libraries/kmp-iap/README.md +++ b/libraries/kmp-iap/README.md @@ -1,7 +1,7 @@ # kmp-iap
- kmp-iap logo + kmp-iap logo Maven Central KMP CI @@ -10,7 +10,7 @@ A comprehensive Kotlin Multiplatform library for in-app purchases on Android and iOS platforms that conforms to the Open IAP specification - Open IAP + Open IAP
## 📚 Documentation @@ -127,7 +127,7 @@ kmpIAP.finishTransaction( ## Powered by OpenIAP -OpenIAP +OpenIAP kmp-iap conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides: diff --git a/libraries/kmp-iap/images/create_release_and_tag.png b/libraries/kmp-iap/images/create_release_and_tag.png deleted file mode 100644 index 737d45e73..000000000 Binary files a/libraries/kmp-iap/images/create_release_and_tag.png and /dev/null differ diff --git a/libraries/kmp-iap/images/create_release_and_tag.webp b/libraries/kmp-iap/images/create_release_and_tag.webp new file mode 100644 index 000000000..1b8eab1c5 Binary files /dev/null and b/libraries/kmp-iap/images/create_release_and_tag.webp differ diff --git a/libraries/kmp-iap/images/draft_release.png b/libraries/kmp-iap/images/draft_release.png deleted file mode 100644 index 1687c9782..000000000 Binary files a/libraries/kmp-iap/images/draft_release.png and /dev/null differ diff --git a/libraries/kmp-iap/images/draft_release.webp b/libraries/kmp-iap/images/draft_release.webp new file mode 100644 index 000000000..58cdd29fd Binary files /dev/null and b/libraries/kmp-iap/images/draft_release.webp differ diff --git a/libraries/kmp-iap/images/github_releases.png b/libraries/kmp-iap/images/github_releases.png deleted file mode 100644 index 64a917593..000000000 Binary files a/libraries/kmp-iap/images/github_releases.png and /dev/null differ diff --git a/libraries/kmp-iap/images/github_releases.webp b/libraries/kmp-iap/images/github_releases.webp new file mode 100644 index 000000000..4bcc14c82 Binary files /dev/null and b/libraries/kmp-iap/images/github_releases.webp differ diff --git a/libraries/kmp-iap/images/github_secrets.png b/libraries/kmp-iap/images/github_secrets.png deleted file mode 100644 index 0118ec8f1..000000000 Binary files a/libraries/kmp-iap/images/github_secrets.png and /dev/null differ diff --git a/libraries/kmp-iap/images/github_secrets.webp b/libraries/kmp-iap/images/github_secrets.webp new file mode 100644 index 000000000..1af6e744a Binary files /dev/null and b/libraries/kmp-iap/images/github_secrets.webp differ diff --git a/libraries/kmp-iap/images/published_on_maven_central.png b/libraries/kmp-iap/images/published_on_maven_central.png deleted file mode 100644 index 088ac0a0b..000000000 Binary files a/libraries/kmp-iap/images/published_on_maven_central.png and /dev/null differ diff --git a/libraries/kmp-iap/images/published_on_maven_central.webp b/libraries/kmp-iap/images/published_on_maven_central.webp new file mode 100644 index 000000000..9579cd0b5 Binary files /dev/null and b/libraries/kmp-iap/images/published_on_maven_central.webp differ diff --git a/libraries/kmp-iap/images/release_settings.png b/libraries/kmp-iap/images/release_settings.png deleted file mode 100644 index 83976074b..000000000 Binary files a/libraries/kmp-iap/images/release_settings.png and /dev/null differ diff --git a/libraries/kmp-iap/images/release_settings.webp b/libraries/kmp-iap/images/release_settings.webp new file mode 100644 index 000000000..42a212228 Binary files /dev/null and b/libraries/kmp-iap/images/release_settings.webp differ diff --git a/libraries/react-native-iap/README.md b/libraries/react-native-iap/README.md index 66fd22f13..607b9b609 100644 --- a/libraries/react-native-iap/README.md +++ b/libraries/react-native-iap/README.md @@ -7,7 +7,7 @@ **React Native IAP** is a high-performance in-app purchase library using Nitro Modules that **conforms to the [Open IAP specification](https://openiap.dev)**. It provides a unified API for handling in-app purchases across iOS and Android platforms with comprehensive error handling and modern TypeScript support. -Open IAP +Open IAP ## 📚 Documentation @@ -137,7 +137,7 @@ Quick links: ## Powered by OpenIAP -OpenIAP +OpenIAP React Native IAP conforms to the **[OpenIAP specification](https://openiap.dev)** — an open, vendor-neutral interoperability standard for in-app purchases. OpenIAP provides: diff --git a/libraries/react-native-iap/example/amazon.sdktester.json b/libraries/react-native-iap/example/amazon.sdktester.json index 43cd5e901..cd536dfc8 100644 --- a/libraries/react-native-iap/example/amazon.sdktester.json +++ b/libraries/react-native-iap/example/amazon.sdktester.json @@ -4,28 +4,28 @@ "price": 0.99, "title": "10 Bulbs", "description": "A small pack of bulbs for testing consumable purchases", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.30bulbs": { "itemType": "CONSUMABLE", "price": 1.99, "title": "30 Bulbs", "description": "A larger pack of bulbs for testing consumable purchases", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.certified": { "itemType": "ENTITLED", "price": 4.99, "title": "Certified", "description": "A non-consumable entitlement for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png" + "smallIconUrl": "https://openiap.dev/logo.webp" }, "dev.hyo.martie.premium": { "itemType": "SUBSCRIPTION", "price": 4.99, "title": "Premium Monthly", "description": "Monthly premium access for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png", + "smallIconUrl": "https://openiap.dev/logo.webp", "subscriptionBase": "dev.hyo.martie.premium.base", "subscriptionParent": "dev.hyo.martie.premium.parent", "term": "Monthly" @@ -35,7 +35,7 @@ "price": 49.99, "title": "Premium Yearly", "description": "Yearly premium access for OpenIAP example testing", - "smallIconUrl": "https://openiap.dev/img/logo.png", + "smallIconUrl": "https://openiap.dev/logo.webp", "subscriptionBase": "dev.hyo.martie.premium.base", "subscriptionParent": "dev.hyo.martie.premium.parent", "term": "Yearly" diff --git a/logo.png b/logo.png index 6dd62592b..da177bd60 100644 Binary files a/logo.png and b/logo.png differ diff --git a/logo.webp b/logo.webp new file mode 100644 index 000000000..10d21cc33 Binary files /dev/null and b/logo.webp differ diff --git a/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.png b/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.png deleted file mode 100644 index 5a81a5f40..000000000 Binary files a/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.png and /dev/null differ diff --git a/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.webp b/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.webp new file mode 100644 index 000000000..dae6a5fa8 Binary files /dev/null and b/packages/docs/public/docs/images/openiap-mcp-iphone-purchase.webp differ diff --git a/packages/docs/public/llms-full.txt b/packages/docs/public/llms-full.txt index 15bfa1f54..40f517ab7 100644 --- a/packages/docs/public/llms-full.txt +++ b/packages/docs/public/llms-full.txt @@ -2245,7 +2245,7 @@ assuming DAU implies safe request volume. replacement, generated warnings where supported, migration documentation, and executable absence checks at the removal boundary. Patch and minor releases must not remove them early. -- See https://openiap.dev/docs/updates/deprecations for the complete mapping. +- See https://openiap.dev/docs/updates/migration for the complete mapping. --- diff --git a/packages/docs/public/llms.txt b/packages/docs/public/llms.txt index eb56fa2db..3f8c6fc63 100644 --- a/packages/docs/public/llms.txt +++ b/packages/docs/public/llms.txt @@ -84,7 +84,7 @@ Current NuGet package version: 2.3.0 replacement, generated warnings where supported, migration documentation, and executable absence checks at the removal boundary. Patch and minor releases must not remove them early. -- See https://openiap.dev/docs/updates/deprecations for the complete mapping. +- See https://openiap.dev/docs/updates/migration for the complete mapping. ## Core APIs diff --git a/packages/docs/public/logos/android.webp b/packages/docs/public/logos/android.webp new file mode 100644 index 000000000..e2bde900c Binary files /dev/null and b/packages/docs/public/logos/android.webp differ diff --git a/packages/docs/public/logos/apple.webp b/packages/docs/public/logos/apple.webp new file mode 100644 index 000000000..30fa54d6f Binary files /dev/null and b/packages/docs/public/logos/apple.webp differ diff --git a/packages/docs/public/logos/expo-iap.webp b/packages/docs/public/logos/expo-iap.webp new file mode 100644 index 000000000..ca092afcb Binary files /dev/null and b/packages/docs/public/logos/expo-iap.webp differ diff --git a/packages/docs/public/logos/expo.webp b/packages/docs/public/logos/expo.webp new file mode 100644 index 000000000..e1f7014be Binary files /dev/null and b/packages/docs/public/logos/expo.webp differ diff --git a/packages/docs/public/logos/flutter.webp b/packages/docs/public/logos/flutter.webp new file mode 100644 index 000000000..8fc9151c9 Binary files /dev/null and b/packages/docs/public/logos/flutter.webp differ diff --git a/packages/docs/public/logos/flutter_inapp_purchase.webp b/packages/docs/public/logos/flutter_inapp_purchase.webp new file mode 100644 index 000000000..eb949644e Binary files /dev/null and b/packages/docs/public/logos/flutter_inapp_purchase.webp differ diff --git a/packages/docs/public/logos/godot-iap.webp b/packages/docs/public/logos/godot-iap.webp new file mode 100644 index 000000000..456917b52 Binary files /dev/null and b/packages/docs/public/logos/godot-iap.webp differ diff --git a/packages/docs/public/logos/godot.webp b/packages/docs/public/logos/godot.webp new file mode 100644 index 000000000..88c30da77 Binary files /dev/null and b/packages/docs/public/logos/godot.webp differ diff --git a/packages/docs/public/logos/horizonos.webp b/packages/docs/public/logos/horizonos.webp new file mode 100644 index 000000000..a59001a73 Binary files /dev/null and b/packages/docs/public/logos/horizonos.webp differ diff --git a/packages/docs/public/logos/kmp-iap.webp b/packages/docs/public/logos/kmp-iap.webp new file mode 100644 index 000000000..e7ba73c49 Binary files /dev/null and b/packages/docs/public/logos/kmp-iap.webp differ diff --git a/packages/docs/public/logos/kmp.webp b/packages/docs/public/logos/kmp.webp new file mode 100644 index 000000000..89142a305 Binary files /dev/null and b/packages/docs/public/logos/kmp.webp differ diff --git a/packages/docs/public/logos/maui-iap.webp b/packages/docs/public/logos/maui-iap.webp new file mode 100644 index 000000000..40fb31e7f Binary files /dev/null and b/packages/docs/public/logos/maui-iap.webp differ diff --git a/packages/docs/public/logos/openiap-apple.webp b/packages/docs/public/logos/openiap-apple.webp new file mode 100644 index 000000000..020f6be73 Binary files /dev/null and b/packages/docs/public/logos/openiap-apple.webp differ diff --git a/packages/docs/public/logos/openiap-google.webp b/packages/docs/public/logos/openiap-google.webp new file mode 100644 index 000000000..49f0df6f6 Binary files /dev/null and b/packages/docs/public/logos/openiap-google.webp differ diff --git a/packages/docs/public/logos/openiap-gql.webp b/packages/docs/public/logos/openiap-gql.webp new file mode 100644 index 000000000..1530de356 Binary files /dev/null and b/packages/docs/public/logos/openiap-gql.webp differ diff --git a/packages/docs/public/logos/openiap.webp b/packages/docs/public/logos/openiap.webp new file mode 100644 index 000000000..8cb27a248 Binary files /dev/null and b/packages/docs/public/logos/openiap.webp differ diff --git a/packages/docs/public/logos/react-native-iap.webp b/packages/docs/public/logos/react-native-iap.webp new file mode 100644 index 000000000..2d5ab731a Binary files /dev/null and b/packages/docs/public/logos/react-native-iap.webp differ diff --git a/packages/docs/public/logos/react.webp b/packages/docs/public/logos/react.webp new file mode 100644 index 000000000..726873c82 Binary files /dev/null and b/packages/docs/public/logos/react.webp differ diff --git a/packages/docs/public/sitemap.xml b/packages/docs/public/sitemap.xml index eee34ad34..e7360ff30 100644 --- a/packages/docs/public/sitemap.xml +++ b/packages/docs/public/sitemap.xml @@ -158,7 +158,7 @@ 0.7 - https://openiap.dev/docs/updates/deprecations + https://openiap.dev/docs/updates/migration 2026-07-24 monthly 0.8 diff --git a/packages/docs/scripts/render-mcp-demo-video.mjs b/packages/docs/scripts/render-mcp-demo-video.mjs index eec1e9482..de089dbb5 100644 --- a/packages/docs/scripts/render-mcp-demo-video.mjs +++ b/packages/docs/scripts/render-mcp-demo-video.mjs @@ -31,9 +31,9 @@ const outputPath = resolve( ); const purchaseCapturePath = resolve( scriptDir, - '../public/docs/images/openiap-mcp-iphone-purchase.png' + '../public/docs/images/openiap-mcp-iphone-purchase.webp' ); -const purchaseCaptureDataUri = `data:image/png;base64,${readFileSync( +const purchaseCaptureDataUri = `data:image/webp;base64,${readFileSync( purchaseCapturePath ).toString('base64')}`; diff --git a/packages/docs/src/components/EcosystemDiagram.tsx b/packages/docs/src/components/EcosystemDiagram.tsx new file mode 100644 index 000000000..5a566ae04 --- /dev/null +++ b/packages/docs/src/components/EcosystemDiagram.tsx @@ -0,0 +1,289 @@ +import { Link } from 'react-router-dom'; +import { LIBRARIES, type FrameworkLibraryName } from '../lib/images'; +import { OPENIAP_VERSIONS } from '../lib/versioning'; +import '../styles/ecosystem-diagram.css'; + +// ============================================================================= +// Ecosystem Diagram Data +// ============================================================================= +// Framework libraries are deliberately NOT listed in this file. Membership, +// order, display name, version, setup path and the fallback framework mark all +// come from LIBRARIES in src/lib/images.ts (SSOT, see CONVENTION.md "Framework +// Listings"). Adding a library there makes it show up here with no edit below. +// +// Artwork lives in public/logos as <= 256px .webp files. To swap an image, drop +// the new file in and repoint the matching entry. +// ============================================================================= + +const GITHUB = 'https://github.com/hyodotdev/openiap'; +const GITHUB_TREE = `${GITHUB}/tree/main`; + +/** Rendered when a library ships no OpenIAP package artwork of its own. */ +const FALLBACK_PACKAGE_ICON = '/logos/openiap.webp'; + +/** Package artwork per framework library. Missing key -> FALLBACK_PACKAGE_ICON. */ +const PACKAGE_ICONS: Partial> = { + 'expo-iap': '/logos/expo-iap.webp', + 'react-native-iap': '/logos/react-native-iap.webp', + flutter_inapp_purchase: '/logos/flutter_inapp_purchase.webp', + 'kmp-iap': '/logos/kmp-iap.webp', + 'maui-iap': '/logos/maui-iap.webp', + 'godot-iap': '/logos/godot-iap.webp', +}; + +/** Target framework mark. Missing key -> lib.image from LIBRARY_IMAGES. */ +const FRAMEWORK_MARKS: Partial> = { + 'expo-iap': '/logos/expo.webp', + 'react-native-iap': '/logos/react.webp', + flutter_inapp_purchase: '/logos/flutter.webp', + 'kmp-iap': '/logos/kmp.webp', + 'godot-iap': '/logos/godot.webp', + // maui-iap falls back to /frameworks/maui.webp. +}; + +/** Flat black artwork - has to flip to white in dark mode. */ +const BLACK_INK_ART = new Set(['/logos/apple.webp', '/logos/expo.webp']); + +/** Flat white artwork - has to flip to black in light mode. */ +const WHITE_INK_ART = new Set(['/logos/horizonos.webp']); + +/** + * Marks whose glyph is narrower than the square box it is fitted into, so they + * read smaller than a full-bleed neighbour even at the same height. Apple's + * mark renders 18.5px wide next to Horizon OS's 22px ring; this scales the box + * so the drawn widths match. + */ +const COMPACT_MARKS = new Set(['/logos/apple.webp']); + +/** Artwork that already carries its own opaque square background. */ +const SQUARE_ART = new Set([ + '/logos/openiap-gql.webp', + '/logos/expo-iap.webp', + '/logos/react-native-iap.webp', + '/logos/flutter_inapp_purchase.webp', + '/logos/kmp-iap.webp', + '/frameworks/maui.webp', +]); + +interface DiagramMark { + src: string; + label: string; +} + +interface DiagramNode { + id: string; + name: string; + note: string; + icon: string; + href: string; + marks?: DiagramMark[]; +} + +const SPEC_NODES: DiagramNode[] = [ + { + id: 'openiap', + name: 'openiap', + note: `The specification · ${OPENIAP_VERSIONS.spec}`, + icon: '/logos/openiap.webp', + href: GITHUB, + }, + { + id: 'openiap-gql', + name: 'openiap-gql', + note: 'GraphQL schema · type SSOT', + icon: '/logos/openiap-gql.webp', + href: `${GITHUB_TREE}/packages/gql`, + }, +]; + +const CORE_NODES: DiagramNode[] = [ + { + id: 'openiap-google', + name: 'openiap-google', + note: `Kotlin · Play Billing · ${OPENIAP_VERSIONS.google}`, + icon: '/logos/openiap-google.webp', + href: `${GITHUB_TREE}/packages/google`, + marks: [ + { src: '/logos/android.webp', label: 'Android' }, + { src: '/logos/horizonos.webp', label: 'Horizon OS' }, + ], + }, + { + id: 'openiap-apple', + name: 'openiap-apple', + note: `Swift · StoreKit 2 · ${OPENIAP_VERSIONS.apple}`, + icon: '/logos/openiap-apple.webp', + href: `${GITHUB_TREE}/packages/apple`, + marks: [{ src: '/logos/apple.webp', label: 'iOS, macOS and tvOS' }], + }, +]; + +function artClass(src: string, base: string): string { + const classes = [base]; + + if (SQUARE_ART.has(src)) { + classes.push('eco-art--square'); + } + + if (BLACK_INK_ART.has(src)) { + classes.push('eco-art--black-ink'); + } + + if (WHITE_INK_ART.has(src)) { + classes.push('eco-art--white-ink'); + } + + if (COMPACT_MARKS.has(src)) { + classes.push('eco-art--compact'); + } + + return classes.join(' '); +} + +function NodeCard({ + node, + className, +}: { + node: DiagramNode; + className?: string; +}) { + return ( + + + + {node.name} + {node.note} + + {node.marks ? ( + + {node.marks.map((mark) => ( + {mark.label} + ))} + + ) : null} + + ); +} + +function Rail({ + variant, + label, +}: { + variant: 'a' | 'b' | 'bypass'; + label: string; +}) { + return ( + + ); +} + +function EcosystemDiagram() { + return ( +
+
+ {/* display: contents - the two spec nodes are grid items of .eco-grid + so the gutter arrow can sit on openiap-gql's own row. */} +
+

+ Spec +

+ +
+ + + +
+

+ Core +

+
+ {CORE_NODES.map((node) => ( + + ))} +
+
+ + + + +
+

+ Libraries +

+ + types from openiap-gql + +
+ {LIBRARIES.map((lib) => { + const icon = PACKAGE_ICONS[lib.name] ?? FALLBACK_PACKAGE_ICON; + const mark = FRAMEWORK_MARKS[lib.name] ?? lib.image; + + return ( + + + + {lib.displayName} + {lib.description} + + + {lib.version} + {lib.frameworkName} + + + ); + })} + + and more … join the ecosystem + +
+
+
+ +
+ openiap-gql generates the type system for the core + native packages and for every framework library, and the core + packages are bundled into each library. Select any node to open its + project. +
+
+ ); +} + +export default EcosystemDiagram; diff --git a/packages/docs/src/lib/searchData.ts b/packages/docs/src/lib/searchData.ts index 8e3144d4d..174090052 100644 --- a/packages/docs/src/lib/searchData.ts +++ b/packages/docs/src/lib/searchData.ts @@ -896,7 +896,7 @@ export const apiData: ApiItem[] = [ }, { id: 'active-subscription-android', - title: 'ActiveSubscriptionAndroid', + title: 'ActiveSubscription (Android fields)', category: 'Types (Android)', description: 'Android active subscription: autoRenewingAndroid, basePlanIdAndroid, purchaseTokenAndroid', @@ -950,12 +950,12 @@ export const apiData: ApiItem[] = [ path: '/docs/errors', }, { - id: 'deprecations-migration', - title: 'Deprecations & 3.0 Migration', + id: 'migration', + title: 'Migration', category: 'Documentation', description: - 'Deprecated API removal schedule and replacements for OpenIAP 3.0 and each framework library next major', - path: '/docs/updates/deprecations', + 'Per-major migration trains: removal schedule and canonical replacements for OpenIAP and each framework library', + path: '/docs/updates/migration', }, { id: 'store-setup', diff --git a/packages/docs/src/pages/docs/ecosystem.tsx b/packages/docs/src/pages/docs/ecosystem.tsx index 61e5f56f5..1538bf3ac 100644 --- a/packages/docs/src/pages/docs/ecosystem.tsx +++ b/packages/docs/src/pages/docs/ecosystem.tsx @@ -1,11 +1,10 @@ -import { useState } from 'react'; import Callout from '../../components/Callout'; +import EcosystemDiagram from '../../components/EcosystemDiagram'; import SEO from '../../components/SEO'; import { useScrollToHash } from '../../hooks/useScrollToHash'; function Ecosystem() { useScrollToHash(); - const [isModalOpen, setIsModalOpen] = useState(false); return (
@@ -22,48 +21,7 @@ function Ecosystem() { hyo@hyo.dev.

-
- OpenIAP Ecosystem setIsModalOpen(true)} - /> -
- - {isModalOpen && ( -
setIsModalOpen(false)} - > - OpenIAP Ecosystem -
- )} +

Core

diff --git a/packages/docs/src/pages/docs/index.tsx b/packages/docs/src/pages/docs/index.tsx index bf24c4475..3b7a88295 100644 --- a/packages/docs/src/pages/docs/index.tsx +++ b/packages/docs/src/pages/docs/index.tsx @@ -5,6 +5,7 @@ import type { PointerEvent as ReactPointerEvent, } from 'react'; import { createPortal } from 'react-dom'; +import { Bookmark } from 'lucide-react'; import { Route, Routes, @@ -122,7 +123,7 @@ import KmpSetup from './setup/kmp'; import MauiSetup from './setup/maui'; import Example from './example'; import Announcements from './updates/announcements'; -import Deprecations from './updates/deprecations'; +import Migration from './updates/migration'; import Releases from './updates/releases'; import Versions from './updates/versions'; import AIAssistants from './guides/ai-assistants'; @@ -147,10 +148,17 @@ function NavigatePreservingHash({ to }: { to: string }) { } const SIDEBAR_WIDTH_STORAGE_KEY = 'openiap-docs-sidebar-width-v2'; +const SIDEBAR_COLLAPSED_STORAGE_KEY = 'openiap-docs-sidebar-collapsed-v1'; const SIDEBAR_DEFAULT_WIDTH = 340; const SIDEBAR_MIN_WIDTH = 300; const SIDEBAR_MAX_WIDTH = 480; const SIDEBAR_KEYBOARD_STEP = 16; +// Drag this far past the minimum and the sidebar snaps shut instead of +// refusing to move: below ~300px the nav labels start truncating, so hiding it +// outright is more useful than an unreadably narrow column. +const SIDEBAR_COLLAPSE_SLACK = 60; +// Pointer travel that separates "clicked the handle" from "dragged the handle". +const SIDEBAR_DRAG_THRESHOLD = 4; function clampSidebarWidth(width: number) { return Math.min( @@ -172,14 +180,31 @@ function readSavedSidebarWidth() { : SIDEBAR_DEFAULT_WIDTH; } +function readSavedSidebarCollapsed() { + if (typeof window === 'undefined') { + return false; + } + + return window.localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === 'true'; +} + function Docs() { const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); const [sidebarWidth, setSidebarWidth] = useState(readSavedSidebarWidth); + const [isSidebarCollapsed, setIsSidebarCollapsed] = useState( + readSavedSidebarCollapsed + ); const [isResizingSidebar, setIsResizingSidebar] = useState(false); const [isSidebarScrolling, setIsSidebarScrolling] = useState(false); const sidebarRef = useRef(null); const sidebarScrollTimeoutRef = useRef(null); + const dragRef = useRef<{ + startX: number; + moved: boolean; + startedCollapsed: boolean; + reopened: boolean; + } | null>(null); const closeSidebar = () => setIsSidebarOpen(false); @@ -199,6 +224,13 @@ function Docs() { ); }, [sidebarWidth]); + useEffect(() => { + window.localStorage.setItem( + SIDEBAR_COLLAPSED_STORAGE_KEY, + String(isSidebarCollapsed) + ); + }, [isSidebarCollapsed]); + useEffect(() => { return () => { if (sidebarScrollTimeoutRef.current !== null) { @@ -218,11 +250,62 @@ function Docs() { document.body.style.userSelect = 'none'; const handlePointerMove = (event: PointerEvent) => { + const drag = dragRef.current; + + if (!drag) { + return; + } + + if ( + !drag.moved && + Math.abs(event.clientX - drag.startX) < SIDEBAR_DRAG_THRESHOLD + ) { + return; + } + + drag.moved = true; + const sidebarLeft = sidebarRef.current?.getBoundingClientRect().left ?? 0; - setSidebarWidth(clampSidebarWidth(event.clientX - sidebarLeft)); + const nextWidth = event.clientX - sidebarLeft; + + if (drag.startedCollapsed) { + // Collapsed, the sidebar is 0 wide and the handle sits on its edge, so + // nextWidth is only ~25px however the pointer moves. Falling through to + // the collapse branch below would make any small jitter a no-op and + // strand the user - above 768px this handle is the only way back. Only + // a deliberate drag out past the minimum reopens; anything shorter is + // left to stopResizing, which treats the release as a toggle. + if (nextWidth >= SIDEBAR_MIN_WIDTH - SIDEBAR_COLLAPSE_SLACK) { + drag.reopened = true; + setIsSidebarCollapsed(false); + setSidebarWidth(clampSidebarWidth(nextWidth)); + } + + return; + } + + if (nextWidth < SIDEBAR_MIN_WIDTH - SIDEBAR_COLLAPSE_SLACK) { + // Dragged past the minimum: snap shut and end the drag. Clear the drag + // so a trailing pointerup cannot toggle it straight back open. + dragRef.current = null; + setIsSidebarCollapsed(true); + setIsResizingSidebar(false); + return; + } + + setSidebarWidth(clampSidebarWidth(nextWidth)); }; const stopResizing = () => { + const drag = dragRef.current; + + // Toggle on a plain click, and also when a gesture that started collapsed + // never travelled far enough to count as a reopen drag. + if (drag && !drag.reopened && (!drag.moved || drag.startedCollapsed)) { + setIsSidebarCollapsed((collapsed) => !collapsed); + } + + dragRef.current = null; setIsResizingSidebar(false); }; @@ -239,12 +322,18 @@ function Docs() { }; }, [isResizingSidebar]); - const startSidebarResize = (event: ReactPointerEvent) => { + const startSidebarResize = (event: ReactPointerEvent) => { if (window.innerWidth <= 768) { return; } event.preventDefault(); + dragRef.current = { + startX: event.clientX, + moved: false, + startedCollapsed: isSidebarCollapsed, + reopened: false, + }; setIsResizingSidebar(true); }; @@ -262,8 +351,32 @@ function Docs() { }; const handleSidebarResizerKeyDown = ( - event: KeyboardEvent + event: KeyboardEvent ) => { + // Enter/Space are the handle's primary action, matching a plain click. + // preventDefault stops the browser also synthesizing a click from them. + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + setIsSidebarCollapsed((collapsed) => !collapsed); + return; + } + + if (isSidebarCollapsed) { + // Collapsed, so the only meaningful direction is back open. + if (event.key === 'ArrowRight') { + event.preventDefault(); + setIsSidebarCollapsed(false); + } + return; + } + + // At the minimum width the only place left to go is closed. + if (event.key === 'ArrowLeft' && sidebarWidth === SIDEBAR_MIN_WIDTH) { + event.preventDefault(); + setIsSidebarCollapsed(true); + return; + } + if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { event.preventDefault(); setSidebarWidth((width) => @@ -285,11 +398,6 @@ function Docs() { event.preventDefault(); setSidebarWidth(SIDEBAR_MAX_WIDTH); } - - if (event.key === 'Enter') { - event.preventDefault(); - setSidebarWidth(SIDEBAR_DEFAULT_WIDTH); - } }; const sidebarStyle = { @@ -337,15 +445,24 @@ function Docs() { ); return ( -
+
{sidebarToggle} - {isSidebarOpen && ( -
- )} + {/* Kept mounted so it can fade OUT with the drawer; unmounting it on + close made the backdrop vanish instantly while the drawer was still + sliding. Hidden state is visibility + pointer-events, not unmount. */} +
setSidebarWidth(SIDEBAR_DEFAULT_WIDTH)} - onKeyDown={handleSidebarResizerKeyDown} - /> + } ${isSidebarCollapsed ? 'is-collapsed' : ''}`} + > + {/* One bookmark-style handle does both jobs: click toggles the + sidebar, drag resizes it. A drag is only recognized past + SIDEBAR_DRAG_THRESHOLD px, so a plain click never nudges the + width. Keyboard users get arrow keys instead of the drag. */} + +
} /> } /> - } /> + } /> + {/* the page was published at updates/deprecations until 2026-08; + keep the old path working for existing links and search results */} + } + /> } /> } /> diff --git a/packages/docs/src/pages/docs/lifecycle/subscription.tsx b/packages/docs/src/pages/docs/lifecycle/subscription.tsx index 8cbea1544..419323b7e 100644 --- a/packages/docs/src/pages/docs/lifecycle/subscription.tsx +++ b/packages/docs/src/pages/docs/lifecycle/subscription.tsx @@ -104,7 +104,9 @@ function Subscription() { ✅ pendingUpgradeProductId - ❌ + + ✅ pendingPurchaseUpdateAndroid + ✅ @@ -128,7 +130,10 @@ function Subscription() { ✅ isInBillingRetry - ❌ + + ⚠️ isSuspendedAndroid (state only, no retry + details) + ✅ @@ -158,9 +163,14 @@ function Subscription() { , but server validation is still recommended for production apps.

- Android: Only isAutoRenewing available - client-side. Server-side validation is required for - complete subscription management. + Android: client-side{' '} + subscription lifecycle data is limited to{' '} + isAutoRenewing, isSuspendedAndroid, and{' '} + pendingPurchaseUpdateAndroid — the purchase itself + still carries productId, purchaseToken,{' '} + transactionDate, and purchaseState. Expiry + and renewal dates and detailed subscription state require{' '} + server-side validation.

Both platforms: Use{' '} @@ -177,11 +187,12 @@ function Subscription() {

Recommendation: Use{' '} - iapkit + IAPKit {' '} for server-side verification to get unified subscription data across both platforms—including all the iOS-only fields for Android @@ -220,8 +231,21 @@ function Subscription() { getAvailablePurchases - : Returns all purchases including expired subscriptions. Useful for - showing purchase history. + : Returns the purchases the store still holds — owned + non-consumables, active subscriptions, and unfinished transactions. + It is not a purchase-history source on either + platform. The framework SDKs default{' '} + onlyIncludeActiveItemsIOS to true, so iOS + reads Transaction.currentEntitlements; pass{' '} + {'{ onlyIncludeActiveItemsIOS: false }'} to read{' '} + Transaction.all including expired and revoked entries. + Android's queryPurchasesAsync returns only + currently-owned purchases and has no equivalent option. For full iOS + history use{' '} + + getAllTransactionsIOS + + .

@@ -253,8 +277,8 @@ function Subscription() { defaultOpen >

- Setting up server-side verification can be complex. OpenIAP's - partner{' '} + Setting up server-side verification can be complex. OpenIAP's + hosted backend{' '}

Learn more about IAPKit integration in our{' '} - + announcement - + .

@@ -336,7 +356,7 @@ function Subscription() {
1. initConnection()
2. getAvailablePurchases() → [PurchaseIOS]
3. For each purchase:
-
→ check transactionState
+
→ check purchaseState
→ validate with server
→ update local entitlements @@ -355,7 +375,9 @@ function Subscription() {
2. getAvailablePurchases() → [PurchaseAndroid]
3. For each purchase:
→ check purchaseState
-
→ check isAcknowledged
+
+ → check isAcknowledgedAndroid +
→ validate with server
→ update local entitlements @@ -384,43 +406,54 @@ function Subscription() { {{ ios: (
-
1. requestPurchase(sku) → StoreKit payment sheet
-
2. purchaseUpdatedListener receives PurchaseIOS
-
3. Check transactionState:
-
- • purchased → validate → deliver → - finishTransaction() -
-
- • pending → payment processing, wait for - update +
+ 1. requestPurchase({ request: { apple: { sku + } }, type: 'subs' }) → StoreKit + payment sheet
+
2. purchaseUpdatedListener receives PurchaseIOS
- • failed → show error, check - purchaseErrorListener + → validate → deliver → finishTransaction()
-
- • deferred → Ask to Buy (parental approval - needed) +
+ A delivered PurchaseIOS always carries{' '} + purchaseState: 'purchased' — StoreKit + only hands the listener completed transactions, so the{' '} + pending and unknown members of the + shared enum never appear on iOS. Anything that is not a + completed purchase arrives through{' '} + purchaseErrorListener instead: Ask to Buy and + other deferred payments as{' '} + ErrorCode.DeferredPayment ( + 'deferred-payment'), cancellations as{' '} + ErrorCode.UserCancelled.
), android: (
-
1. requestPurchase(sku) → Google Play payment sheet
+
+ 1. requestPurchase({ request: { google: { skus + } }, type: 'subs' }) → Google Play + payment sheet +
2. purchaseUpdatedListener receives PurchaseAndroid
3. Check purchaseState:
- • PURCHASED (1) → validate → deliver → + • purchased → validate → deliver → finishTransaction()
- • PENDING (2) → awaiting payment (slow - payment methods) + • pending → awaiting payment (slow payment + methods)
- • UNSPECIFIED (0) → unknown state, handle as - error + • unknown → unrecognized state, handle + defensively +
+
+ These are the OpenIAP wire values. The underlying Play Billing + constants are PURCHASED = 1, PENDING = 2, UNSPECIFIED = 0.
-
- 1. getActiveSubscriptions() → [ActiveSubscriptionAndroid] -
+
1. getActiveSubscriptions() → [ActiveSubscription]
2. For each subscription:
• isActive = true → grant access
- • check isAutoRenewing only (limited client data) + • check autoRenewingAndroid, currentPlanId / basePlanIdAndroid + (limited client data)
3. No active subscriptions → revoke access
@@ -785,7 +817,7 @@ function Subscription() {

This type is available on{' '} - + PurchaseIOS {' '} and{' '} @@ -817,12 +849,11 @@ function Subscription() { in milliseconds).

  • - expirationReason: Why the subscription - expired — "VOLUNTARY",{' '} - "BILLING_ERROR",{' '} - "DID_NOT_AGREE_TO_PRICE_INCREASE",{' '} - "PRODUCT_NOT_AVAILABLE", or{' '} - "UNKNOWN". + expirationReason: StoreKit's raw + integer expiration-reason value represented as a string ( + "1", "2", …), + not a symbolic name. Preserve unknown future values rather + than mapping them to a fallback.
  • gracePeriodExpirationDate: Grace period end @@ -833,8 +864,12 @@ function Subscription() { currently retrying a failed payment.
  • - offerType / offerIdentifier: Information - about any active promotional offer. + renewalOfferId / renewalOfferType: The + offer applied to the next renewal.{' '} + renewalOfferType carries values such as{' '} + "PROMOTIONAL",{' '} + "SUBSCRIPTION_OFFER_CODE", and{' '} + "WIN_BACK".
  • @@ -1041,13 +1076,24 @@ function Subscription() {
  • isAutoRenewing: Whether the subscription - will auto-renew (the only renewal-related field) + will auto-renew
  • - purchaseTime: Original purchase timestamp + isSuspendedAndroid: Whether the + subscription is suspended by a billing issue
  • - purchaseState: PURCHASED, PENDING, etc. + pendingPurchaseUpdateAndroid: The product + IDs and purchase token of an uncommitted plan change (Play + Billing 5.0+) +
  • +
  • + transactionDate: Original purchase + timestamp (Unix milliseconds) +
  • +
  • + purchaseState: purchased,{' '} + pending, or unknown
  • @@ -1064,13 +1110,29 @@ function Subscription() {

    • Next renewal date / Expiration date
    • -
    • Pending upgrade/downgrade information
    • +
    • + The effective date of a pending upgrade/downgrade — the + pending change itself is on the client as{' '} + pendingPurchaseUpdateAndroid +
    • Expiration reason
    • Grace period status
    • -
    • Billing retry status
    • - Detailed subscription state (ACTIVE, CANCELED, PAUSED, - ON_HOLD, IN_GRACE_PERIOD, EXPIRED) + Billing retry details (attempt counts, next retry + time) — the billing-issue state itself is on the client as{' '} + isSuspendedAndroid +
    • +
    • + Detailed subscription state ( + SUBSCRIPTION_STATE_ACTIVE,{' '} + SUBSCRIPTION_STATE_CANCELED,{' '} + SUBSCRIPTION_STATE_PAUSED,{' '} + SUBSCRIPTION_STATE_ON_HOLD,{' '} + SUBSCRIPTION_STATE_IN_GRACE_PERIOD,{' '} + SUBSCRIPTION_STATE_EXPIRED,{' '} + SUBSCRIPTION_STATE_PENDING,{' '} + SUBSCRIPTION_STATE_PENDING_PURCHASE_CANCELED,{' '} + SUBSCRIPTION_STATE_UNSPECIFIED)
    @@ -1093,8 +1155,22 @@ function Subscription() {

    • - subscriptionState: Current state (ACTIVE, - CANCELED, IN_GRACE_PERIOD, ON_HOLD, PAUSED, EXPIRED) + subscriptionState: Current state ( + SUBSCRIPTION_STATE_ACTIVE,{' '} + SUBSCRIPTION_STATE_CANCELED,{' '} + SUBSCRIPTION_STATE_IN_GRACE_PERIOD,{' '} + SUBSCRIPTION_STATE_ON_HOLD,{' '} + SUBSCRIPTION_STATE_PAUSED,{' '} + SUBSCRIPTION_STATE_EXPIRED,{' '} + SUBSCRIPTION_STATE_PENDING,{' '} + SUBSCRIPTION_STATE_PENDING_PURCHASE_CANCELED,{' '} + SUBSCRIPTION_STATE_UNSPECIFIED).{' '} + SUBSCRIPTION_STATE_PENDING means the initial + purchase has not completed;{' '} + SUBSCRIPTION_STATE_PENDING_PURCHASE_CANCELED is + a pending upgrade or downgrade that was cancelled — resolve + the still-active subscription it belongs to through{' '} + linkedPurchaseToken.
    • expiryTime: When the subscription expires @@ -1144,8 +1220,9 @@ function Subscription() { failed, grace period started
    • - SUBSCRIPTION_ON_HOLD (5): Paused due to - billing issues + SUBSCRIPTION_ON_HOLD (5): Account hold + after the grace period ended — entitlement suspended + (distinct from the user-initiated PAUSED state)
    • SUBSCRIPTION_RECOVERED (1): Billing retry @@ -1251,8 +1328,10 @@ function Subscription() { Upgrades/Downgrades

      - Since Android doesn't expose pending tier changes client-side, - track them via server: + The client exposes the pending change itself via{' '} + pendingPurchaseUpdateAndroid (the new product IDs + and purchase token), but its effective date and linkage still + come from the server:

      • @@ -1367,7 +1446,9 @@ function Subscription() {

        Android

        • - Only isAutoRenewing available client-side + Client-side subscription lifecycle data limited to{' '} + isAutoRenewing, isSuspendedAndroid, + and pendingPurchaseUpdateAndroid
        • Server-side required for detailed subscription info
        • Use Google Play Developer API for authoritative data
        • diff --git a/packages/docs/src/pages/docs/setup/flutter.tsx b/packages/docs/src/pages/docs/setup/flutter.tsx index 9e6daad2a..214d5e300 100644 --- a/packages/docs/src/pages/docs/setup/flutter.tsx +++ b/packages/docs/src/pages/docs/setup/flutter.tsx @@ -508,7 +508,7 @@ final allPurchases = await iap.getAvailablePurchases( dataAndroid. Flutter 10 does not accept the former custom-channel alias. Native adapters, MethodChannel fixtures, and mocks must emit dataAndroid. See{' '} - + Deprecations & 3.0 Migration . diff --git a/packages/docs/src/pages/docs/types/active-subscription.tsx b/packages/docs/src/pages/docs/types/active-subscription.tsx index 8010af0fe..b8c931314 100644 --- a/packages/docs/src/pages/docs/types/active-subscription.tsx +++ b/packages/docs/src/pages/docs/types/active-subscription.tsx @@ -219,7 +219,7 @@ function ActiveSubscription() { android: ( <> - ActiveSubscriptionAndroid + Android Fields diff --git a/packages/docs/src/pages/docs/types/purchase.tsx b/packages/docs/src/pages/docs/types/purchase.tsx index a1ce57665..103afa52e 100644 --- a/packages/docs/src/pages/docs/types/purchase.tsx +++ b/packages/docs/src/pages/docs/types/purchase.tsx @@ -744,7 +744,7 @@ function Purchase() { Only dataAndroid is accepted. Custom adapters and fixtures must use the canonical field. See{' '} - + the migration schedule . diff --git a/packages/docs/src/pages/docs/updates/announcements.tsx b/packages/docs/src/pages/docs/updates/announcements.tsx index b7a1e8e92..fe1bdd590 100644 --- a/packages/docs/src/pages/docs/updates/announcements.tsx +++ b/packages/docs/src/pages/docs/updates/announcements.tsx @@ -159,7 +159,7 @@ function Announcements() { {' '} for exact package versions, per-SDK changes, platform availability, and release links. Before upgrading, follow the{' '} - + Deprecations & 3.0 Migration catalog . @@ -230,7 +230,7 @@ function Announcements() {

          See the complete{' '} - + deprecation schedule and migration catalog . Each package reaches its major independently, so this notice does diff --git a/packages/docs/src/pages/docs/updates/deprecations.tsx b/packages/docs/src/pages/docs/updates/migration.tsx similarity index 92% rename from packages/docs/src/pages/docs/updates/deprecations.tsx rename to packages/docs/src/pages/docs/updates/migration.tsx index bb9a0a4cf..970e1afc9 100644 --- a/packages/docs/src/pages/docs/updates/deprecations.tsx +++ b/packages/docs/src/pages/docs/updates/migration.tsx @@ -478,35 +478,52 @@ const packageCompatibilityMigrations = [ }, ] as const; -function Deprecations() { +function Migration() { return (
          -

          Deprecations & 3.0 Migration

          +

          Migration

          - The coordinated major train removes the previously deprecated, - OpenIAP-owned compatibility surface. Use this catalog to update calls - before upgrading. + One section per coordinated major train. Each train lists the versions + that drop the previously deprecated, OpenIAP-owned compatibility surface + and the canonical call to use instead.

          - - The listed versions do not include compatibility wrappers, deprecated - schema members, or legacy custom-wire aliases. Upgrade coordinated - native and framework dependencies together. - - + {/* --------------------------------------------------------------- + Migration train: 2.x -> 3.0 + Add the next train as a sibling
          ABOVE this one (newest + first), with its own AnchorLink id (for example "v3-to-v4") and its + own removal table. Keep the heading ids inside a train unique across + the page - other docs deep-link to them. The train-independent + policy section stays last. + --------------------------------------------------------------- */}
          - + + 2.x → 3.0 + +

          + The coordinated major train removes the previously deprecated, + OpenIAP-owned compatibility surface. Use this catalog to update calls + before upgrading. +

          + + + The listed versions do not include compatibility wrappers, deprecated + schema members, or legacy custom-wire aliases. Upgrade coordinated + native and framework dependencies together. + + + Removal boundaries -

          OpenIAP specification and native packages

          +

          OpenIAP specification and native packages

          @@ -530,7 +547,7 @@ function Deprecations() {
          -

          Framework libraries

          +

          Framework libraries

          @@ -574,10 +591,7 @@ function Deprecations() { Removed aliases are rejected or ignored; they never override missing canonical input.

          - - -
          - + Flutter purchase payload compatibility

          @@ -619,7 +633,7 @@ function Deprecations() { The canonical id purchase identity is not deprecated. Flutter 10 requires an explicit transactionId.

          -

          Issue #248 and Android raw purchase JSON

          +

          Issue #248 and Android raw purchase JSON

          • Before Flutter 9.6.1, issue{' '} @@ -659,10 +673,7 @@ function Deprecations() { .

            -
          - -
          - + Flutter 10 package-specific migrations

          @@ -691,7 +702,7 @@ function Deprecations() {

          -

          Custom MethodChannel integrations

          +

          Custom MethodChannel integrations

          Applications normally use the Dart API and never call these internal channel names. Flutter 10 custom integrations must use the @@ -718,7 +729,7 @@ function Deprecations() { -

          Custom MethodChannel payloads

          +

          Custom MethodChannel payloads

          The official Dart API emits the canonical forms below. Flutter 10 no longer normalizes the historical custom-channel inputs. @@ -743,10 +754,7 @@ function Deprecations() { ))} - - -

          - + Removed schema migration catalog

          @@ -756,7 +764,7 @@ function Deprecations() { {migrationGroups.map((group) => (

          -

          {group.title}

          +

          {group.title}

          @@ -779,10 +787,7 @@ function Deprecations() {
          ))} -
          - -
          - + Removed package-specific compatibility shims

          @@ -793,7 +798,7 @@ function Deprecations() { {packageCompatibilityMigrations.map((group) => (

          -

          {group.title}

          +

          {group.title}

          @@ -866,4 +871,4 @@ function Deprecations() { ); } -export default Deprecations; +export default Migration; diff --git a/packages/docs/src/pages/docs/updates/releases.tsx b/packages/docs/src/pages/docs/updates/releases.tsx index e33b58615..70b1d2272 100644 --- a/packages/docs/src/pages/docs/updates/releases.tsx +++ b/packages/docs/src/pages/docs/updates/releases.tsx @@ -1991,7 +1991,7 @@ function Releases() {
          Migration notes

          Review the complete{' '} - + Deprecations & 3.0 Migration catalog {' '} before upgrading. Existing bookmarks for removed API pages redirect @@ -2389,7 +2389,7 @@ function Releases() { fields with handwritten bridges and keeps source-first mappings, alternative-store plan changes, and round-trip regressions executable across wrappers. See{' '} - + Deprecations & 3.0 Migration {' '} for the complete replacement and removal schedule. diff --git a/packages/docs/src/styles/documentation.css b/packages/docs/src/styles/documentation.css index 08cda162f..82cc3050d 100644 --- a/packages/docs/src/styles/documentation.css +++ b/packages/docs/src/styles/documentation.css @@ -333,12 +333,15 @@ color: #666; } -.docs-sidebar-resizer { +/* The rail carries a single bookmark-shaped handle: click it to collapse or + restore the sidebar, drag it to resize. Below 769px the drawer toggle in the + top nav owns that job instead, so the rail is hidden. */ +.docs-sidebar-rail { display: none; } @media (min-width: 769px) { - .docs-sidebar-resizer { + .docs-sidebar-rail { position: sticky; top: 56px; z-index: 2; @@ -350,12 +353,11 @@ align-self: flex-start; margin-left: -9px; margin-right: 9px; - cursor: col-resize; - touch-action: none; - outline: none; } - .docs-sidebar-resizer::after { + /* Guide line for the drag only. It spans the full height, so showing it on + hover reads as a stray scrollbar; the bookmark already marks the edge. */ + .docs-sidebar-rail::after { content: ''; position: absolute; top: var(--spacing-md); @@ -366,13 +368,131 @@ background: var(--primary-color); opacity: 0; transition: opacity 0.15s ease; + pointer-events: none; } - .docs-sidebar-resizer:hover::after, - .docs-sidebar-resizer:focus-visible::after, - .docs-sidebar-resizer.is-resizing::after { + .docs-sidebar-rail.is-resizing::after { opacity: 0.8; } + + .docs-sidebar-rail.is-collapsed::after { + opacity: 0; + } + + .docs-sidebar-handle { + --docs-bookmark: #e0a94f; + --docs-bookmark-hover: #cf9438; + + position: absolute; + top: 10px; + /* Rotated a quarter turn, the bookmark's notch points back at the sidebar, + so straddling the border reads as "clipped over the edge". The rail is + 18px wide with a -9px margin, i.e. its midpoint IS the border. */ + left: 1px; + display: flex; + width: 26px; + height: 18px; + align-items: center; + justify-content: center; + padding: 0; + border: 0; + border-radius: 4px; + background: transparent; + color: var(--docs-bookmark); + /* Click is the primary action, so a pointer - not col-resize, which made + it look like a scrollbar grip. The drag cursor is set on for the + duration of an actual drag instead. */ + cursor: pointer; + touch-action: none; + transition: + color 0.15s ease, + background 0.15s ease; + } + + .docs-sidebar-handle:hover, + .docs-sidebar-rail.is-resizing .docs-sidebar-handle { + background: rgba(224, 169, 79, 0.16); + color: var(--docs-bookmark-hover); + } + + .docs-sidebar-handle:focus-visible { + outline: 2px solid var(--docs-bookmark-hover); + outline-offset: 2px; + } + + /* Collapsed: the sidebar is out of the flow entirely and the handle becomes + a standalone tab, so it reads as "open me" rather than a resize grip. */ + /* Collapse by animating the column shut rather than display:none, which + cannot transition. visibility flips only after the width finishes, so the + hidden links leave the tab order without cutting the animation short. */ + .docs-sidebar { + transition: + flex-basis 0.24s ease, + width 0.24s ease, + opacity 0.16s ease, + visibility 0s linear; + } + + /* A drag sets a new width on every pointermove; animating each of those makes + the column lag the cursor by the transition duration. Snap while dragging, + animate only the collapse/restore. */ + .docs-sidebar.is-resizing, + .docs-container.is-sidebar-collapsed .docs-sidebar.is-resizing { + transition: none; + } + + .docs-container.is-sidebar-collapsed .docs-sidebar { + flex-basis: 0; + width: 0; + min-width: 0; + overflow: hidden; + opacity: 0; + visibility: hidden; + /* width:0 with box-sizing:border-box still leaves the 1px border behind */ + border-right-width: 0; + pointer-events: none; + transition: + flex-basis 0.24s ease, + width 0.24s ease, + opacity 0.16s ease, + visibility 0s linear 0.24s; + } + + /* .docs-content stays capped at the prose measure, so without the sidebar it + would hug the left edge and leave the freed space dead on the right. An + `auto` margin cannot be interpolated, so pad the container instead - that + animates, and keeps the content sliding rather than jumping. */ + .docs-container { + transition: padding-left 0.24s ease; + } + + /* the 18px rail stays in flow, so discount it to land visually centred */ + .docs-container.is-sidebar-collapsed { + padding-left: max(0px, calc((100% - 900px) / 2 - 18px)); + } + + .docs-sidebar-rail.is-collapsed .docs-sidebar-handle { + left: 9px; + } +} + +/* Selectors here must match or beat the rules they suppress: the collapsed and + open states re-declare `transition` at (0,3,0) and (0,2,0), so a bare + `.docs-sidebar` at (0,1,0) would lose and only the restore direction would + stop animating. */ +@media (prefers-reduced-motion: reduce) { + .docs-sidebar-rail::after, + .docs-sidebar-handle, + .docs-sidebar, + .docs-sidebar.open, + .docs-sidebar.is-resizing, + .docs-container, + .docs-container.is-sidebar-collapsed .docs-sidebar, + .docs-container.is-sidebar-collapsed .docs-sidebar.is-resizing, + .sidebar-overlay, + .sidebar-overlay.is-visible { + transition: none; + } } /* Nested sub-menu (used inside an outer MenuDropdown's items) */ @@ -729,19 +849,27 @@ min-width: 0; } + /* Headers are nowrap site-wide, which under table-layout:fixed makes + "Last compatible major" overflow its track and collide with the next + header. Inside this fixed-width table they wrap instead. */ + .doc-page table.deprecation-schedule-table th { + white-space: normal; + overflow-wrap: break-word; + } + .doc-page table.deprecation-schedule-table th:first-child, .doc-page table.deprecation-schedule-table td:first-child { - width: 30%; + width: 40%; } .doc-page table.deprecation-schedule-table th:nth-child(2), .doc-page table.deprecation-schedule-table td:nth-child(2) { - width: 26%; + width: 30%; } .doc-page table.deprecation-schedule-table th:nth-child(3), .doc-page table.deprecation-schedule-table td:nth-child(3) { - width: 44%; + width: 30%; } } @@ -1068,6 +1196,21 @@ display: block; backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); + opacity: 0; + visibility: hidden; + pointer-events: none; + transition: + opacity 0.26s ease, + visibility 0s linear 0.26s; + } + + .sidebar-overlay.is-visible { + opacity: 1; + visibility: visible; + pointer-events: auto; + transition: + opacity 0.26s ease, + visibility 0s linear; } .docs-sidebar { @@ -1089,10 +1232,14 @@ max-height: calc(100vh - 56px) !important; background: var(--bg-secondary); z-index: 9000; + /* Slide only. Fading it out at the same time made the drawer read as + "vanishing" instead of "sliding away", and the 0.2s opacity/visibility + against a 0.25s transform meant it went invisible mid-slide. visibility + now flips only after the slide finishes, so the exit stays on screen for + its whole travel while still leaving the tab order afterwards. */ transition: - transform 0.25s ease, - opacity 0.2s ease, - visibility 0.2s ease; + transform 0.26s cubic-bezier(0.4, 0, 0.2, 1), + visibility 0s linear 0.26s; border-right: 1px solid var(--border-color); padding: var(--spacing-xl) 0; box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15); @@ -1103,7 +1250,6 @@ hit-test area, and pointer-events: none turns off hit-testing for this entire subtree even if the transform isn't honored. */ transform: translateX(-100%); - opacity: 0; visibility: hidden; pointer-events: none; overflow-y: auto; @@ -1118,9 +1264,11 @@ .docs-sidebar.open { transform: translateX(0); - opacity: 1; visibility: visible; pointer-events: auto; + transition: + transform 0.26s cubic-bezier(0.4, 0, 0.2, 1), + visibility 0s linear; } .docs-content { diff --git a/packages/docs/src/styles/ecosystem-diagram.css b/packages/docs/src/styles/ecosystem-diagram.css new file mode 100644 index 000000000..4ae4c77ca --- /dev/null +++ b/packages/docs/src/styles/ecosystem-diagram.css @@ -0,0 +1,560 @@ +/* ============================================================================= + Ecosystem architecture diagram (src/components/EcosystemDiagram.tsx) + + Layout switches on the CONTAINER width, not the viewport. The docs sidebar is + user-resizable (300-480px), so .doc-page can be ~350px wide at a 769px + viewport - a viewport media query would be wrong by construction here. + ============================================================================= */ + +.doc-page .eco-diagram { + container-type: inline-size; + container-name: eco; + margin: var(--spacing-xl) 0 var(--spacing-2xl); + + /* light theme */ + --eco-line: rgba(0, 0, 0, 0.24); + --eco-border: rgba(0, 0, 0, 0.11); + --eco-band-bg: var(--bg-secondary); + --eco-card-bg: var(--bg-primary); + --eco-shadow: rgba(0, 0, 0, 0.09); + + /* Package names and versions share one stack; only the multi-word families + need quoting. */ + --eco-mono: 'SF Mono', Monaco, Consolas, 'Liberation Mono', monospace; + + /* geometry */ + --eco-radius: 16px; + --eco-gutter: 0px; + --eco-icon: 38px; + --eco-mark: 22px; +} + +:root.dark .doc-page .eco-diagram { + --eco-line: rgba(255, 255, 255, 0.3); + --eco-border: rgba(255, 255, 255, 0.14); + --eco-band-bg: rgba(255, 255, 255, 0.035); + --eco-card-bg: rgba(255, 255, 255, 0.07); + --eco-shadow: rgba(0, 0, 0, 0.45); +} + +/* ---------- grid ---------------------------------------------------------- */ + +/* Size-dependent tokens live here, not on .eco-diagram: an @container rule only + ever matches descendants of the container it queries, never the container + element itself. */ +/* Stacked by default: Spec -> Core -> Libraries in one column. The two-column + form is defined in the wide-container block at the bottom. */ +.eco-grid { + display: grid; + /* the band labels ride the top border, so leave room for the first one */ + padding-top: 0.6rem; + grid-template-columns: minmax(0, 1fr); +} + +/* ---------- bands --------------------------------------------------------- */ + +.doc-page .eco-diagram .eco-band { + position: relative; + margin: 0; + padding: 1rem 0.85rem 0.85rem; + border: 1px solid var(--eco-border); + border-radius: var(--eco-radius); + background: var(--eco-band-bg); +} + +/* Label chips ride the top border, the way the original diagram framed its + Core / Libraries groups. */ +.doc-page .eco-diagram .eco-band-title { + position: absolute; + top: -0.6rem; + left: 0.9rem; + margin: 0; + padding: 0 0.45rem; + border: 0; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 0.6875rem; + font-weight: 700; + letter-spacing: 0.14em; + line-height: 1.2; + text-transform: uppercase; +} + +/* Stand-in for the bypass connector while the container is too narrow to draw + it. Hidden again once .eco-bypass appears. */ +.doc-page .eco-diagram .eco-grid a.eco-band-chip, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-band-chip { + position: absolute; + top: -0.65rem; + right: 0.9rem; + padding: 0.1rem 0.5rem; + border: 1px solid var(--eco-border); + border-radius: 999px; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 0.625rem; + font-weight: 600; + letter-spacing: 0.04em; + line-height: 1.5; + text-decoration: none; + white-space: nowrap; +} + +.doc-page .eco-diagram .eco-grid a.eco-band-chip:hover, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-band-chip:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +/* The Spec group is semantic only - its children are laid out by .eco-grid + directly, so a connector can align to openiap-gql rather than to a box. */ +.eco-spec { + display: contents; +} + +.eco-hidden-title { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} + +.eco-band-body { + display: flex; + flex-direction: column; + gap: 0.55rem; +} + +.eco-band-body--pair, +.eco-band-body--grid { + display: grid; + gap: 0.55rem; +} + +.eco-band-body--pair { + /* min() so the track can shrink under its preferred width; a bare 240px + floor overflows the band on a ~320px viewport. */ + grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr)); +} + +.eco-band-body--grid { + grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); +} + +/* ---------- nodes --------------------------------------------------------- + documentation.css themes every .doc-page anchor: a link color, a bottom + border, and display:inline-flex for `a[target='_blank']`. Its dark-mode + overrides reach (0,5,1) specificity, so every anchor rule below is written + twice - once plain, once :root.dark-prefixed - to sit one class above that + in both themes regardless of stylesheet order. */ + +.doc-page .eco-diagram .eco-grid a.eco-node, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-node { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 0.7rem; + padding: 0.6rem 0.7rem; + border: 1px solid var(--eco-border); + border-radius: 12px; + background: var(--eco-card-bg); + color: var(--text-primary); + text-decoration: none; + transition: + transform 0.15s ease, + border-color 0.15s ease, + box-shadow 0.15s ease; +} + +.doc-page .eco-diagram .eco-grid a.eco-node:hover, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-node:hover { + border-color: var(--primary-color); + box-shadow: 0 6px 16px var(--eco-shadow); + transform: translateY(-2px); +} + +.doc-page .eco-diagram .eco-grid a.eco-node:focus-visible, +.doc-page .eco-diagram a.eco-band-chip:focus-visible, +.doc-page .eco-diagram .eco-grid a.eco-more:focus-visible { + outline: 2px solid var(--primary-color); + outline-offset: 2px; +} + +.eco-node-text { + display: flex; + min-width: 0; + flex-direction: column; + gap: 0.12rem; +} + +.eco-node-name { + overflow: hidden; + font-family: var(--eco-mono); + font-size: var(--font-size-sm); + font-weight: 600; + color: var(--text-primary); + text-overflow: ellipsis; + white-space: nowrap; +} + +.eco-node-note { + font-size: 0.6875rem; + line-height: 1.35; + color: var(--text-secondary); +} + +.eco-node-marks { + display: flex; + flex: none; + align-items: center; + gap: 0.4rem; +} + +/* Core cards carry platform marks rather than a version chip; sitting them on + the card's baseline reads as "this package targets these" instead of + floating mid-card. */ +.eco-band--core .eco-node-marks { + align-self: end; +} + +.eco-node-version { + font-family: var(--eco-mono); + font-size: 0.625rem; + font-variant-numeric: tabular-nums; + color: var(--text-secondary); + opacity: 0.85; +} + +.doc-page .eco-diagram .eco-grid a.eco-more, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-more { + grid-column: 1 / -1; + padding: 0.55rem 0.7rem; + border: 1px dashed var(--eco-border); + border-radius: 12px; + background: none; + color: var(--text-secondary); + font-size: var(--font-size-xs); + text-align: center; + text-decoration: none; +} + +.doc-page .eco-diagram .eco-grid a.eco-more:hover, +:root.dark .doc-page .eco-diagram .eco-grid a.eco-more:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +/* ---------- artwork ------------------------------------------------------- + Most marks are transparent full-color logos and need no treatment. The two + exceptions are declared per asset in EcosystemDiagram.tsx: artwork with its + own opaque square background gets cropped to a rounded tile, and flat + single-ink artwork is inverted for whichever theme it would vanish into. */ + +.eco-icon { + width: var(--eco-icon); + height: var(--eco-icon); + object-fit: contain; +} + +.eco-mark { + width: var(--eco-mark); + height: var(--eco-mark); + object-fit: contain; +} + +.eco-icon.eco-art--square { + border-radius: 9px; + object-fit: cover; +} + +.eco-mark.eco-art--square { + border-radius: 5px; + object-fit: cover; +} + +/* Optical size: a tall, narrow glyph fitted into a square box draws narrower + than a full-bleed neighbour. Scaling the box matches the drawn widths. */ +.eco-mark.eco-art--compact { + width: calc(var(--eco-mark) * 1.2); + height: calc(var(--eco-mark) * 1.2); +} + +.eco-art--white-ink { + filter: invert(1); +} + +:root.dark .eco-art--black-ink { + filter: invert(1); +} + +:root.dark .eco-art--white-ink { + filter: none; +} + +/* ---------- connectors ---------------------------------------------------- */ + +.eco-rail { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 58px; +} + +.eco-rail::before { + content: ''; + position: absolute; + top: 0; + bottom: 9px; + left: 50%; + width: 2px; + margin-left: -1px; + background: var(--eco-line); +} + +.eco-rail::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 0; + margin-left: -5px; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-top: 9px solid var(--eco-line); +} + +/* Only the two-column form has a gutter to route this through. Stacked, the + Libraries band carries the same meaning via its .eco-band-chip. Declared + after .eco-rail so it wins the same-specificity tie on `display`. */ +.eco-rail--bypass { + display: none; +} + +.eco-rail-label { + position: relative; + z-index: 1; + padding: 0 0.55rem; + background: var(--bg-primary); + color: var(--text-secondary); + font-size: 0.625rem; + font-weight: 600; + letter-spacing: 0.12em; + text-transform: uppercase; + white-space: nowrap; +} + +/* openiap -> openiap-gql */ +.eco-flow-arrow { + position: relative; + justify-self: center; + width: 2px; + height: 22px; + margin: 7px 0 15px; + background: var(--eco-line); +} + +.eco-flow-arrow::after { + content: ''; + position: absolute; + bottom: -8px; + left: 50%; + width: 0; + height: 0; + margin-left: -5px; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + border-top: 8px solid var(--eco-line); +} + +/* Reclaim part of the .docs-content side padding (2rem each side above 768px) + so the figure is wider than the prose measure, while leaving a full 1rem + gutter on both sides - taking all of it puts the Libraries border flush + against the content edge. This one rule is viewport-based on purpose: + `.docs-content` padding is itself viewport-based - `--spacing-2xl` at + >=769px, `--spacing-lg` inside documentation.css's max-width: 768px block. */ +@media (min-width: 769px) { + .doc-page .eco-diagram { + width: calc(100% + 2 * var(--spacing-lg)); + margin-right: calc(-1 * var(--spacing-lg)); + margin-left: calc(-1 * var(--spacing-lg)); + } +} + +/* ---------- caption ------------------------------------------------------- */ + +.doc-page .eco-diagram .eco-caption { + margin: 0.9rem 0 0; + color: var(--text-secondary); + font-size: var(--font-size-xs); + line-height: 1.6; + text-align: center; +} + +/* ============================================================================= + Wide container + ============================================================================= */ + +/* ============================================================================= + Two-column form. Spec and Core stack down the left, Libraries fills the right + in a single tall column, and both gutter arrows point into it - the same + left-to-right reading order as the original diagram. Keeping the six library + rows beside the left column instead of below it is what makes the figure wide + rather than tall. + ============================================================================= */ + +@container eco (min-width: 700px) { + .eco-grid { + --eco-gutter: 78px; + + grid-template-columns: + minmax(0, 0.92fr) var(--eco-gutter) + minmax(0, 1.13fr); + /* openiap sits alone on row 1; openiap-gql gets row 3 so both "generate + types" arrows can leave from it. Row 6 soaks up whatever the taller + Libraries column adds, keeping the connectors at their natural size. */ + grid-template-rows: auto auto auto auto auto minmax(0, 1fr); + column-gap: 0; + } + + .eco-node--root { + grid-column: 1; + grid-row: 1; + } + + .eco-flow-arrow { + grid-column: 1; + grid-row: 2; + } + + .eco-node--gql { + grid-column: 1; + grid-row: 3; + } + + .eco-rail--a { + grid-column: 1; + grid-row: 4; + } + + .eco-band--core { + grid-column: 1; + grid-row: 5; + align-self: start; + } + + /* openiap-gql -> Libraries, level with openiap-gql itself */ + .eco-rail--bypass { + display: flex; + grid-column: 2; + grid-row: 3; + } + + /* Core -> Libraries, level with the Core band */ + .eco-rail--b { + grid-column: 2; + grid-row: 5; + } + + /* Centred, not stretched: whichever column is shorter keeps its natural + height instead of growing a block of empty space at the bottom. */ + .eco-band--libs { + grid-column: 3; + grid-row: 1 / -1; + align-self: center; + } + + /* Gutter rails turn horizontal: line left-to-right, label above it. */ + .eco-rail--bypass, + .eco-rail--b { + height: 44px; + align-self: center; + } + + .eco-rail--bypass::before, + .eco-rail--b::before { + top: 50%; + right: 10px; + bottom: auto; + left: 0; + width: auto; + height: 2px; + margin-top: -1px; + margin-left: 0; + } + + .eco-rail--bypass::after, + .eco-rail--b::after { + top: 50%; + right: 0; + bottom: auto; + left: auto; + margin: -5px 0 0; + border-top: 5px solid transparent; + border-right: 0; + border-bottom: 5px solid transparent; + border-left: 10px solid var(--eco-line); + } + + .eco-rail--bypass .eco-rail-label, + .eco-rail--b .eco-rail-label { + position: absolute; + right: 0; + bottom: calc(50% + 7px); + left: 0; + padding: 0; + background: none; + line-height: 1.25; + text-align: center; + white-space: normal; + } + + /* the gutter arrows now carry this meaning graphically */ + .doc-page .eco-diagram .eco-grid a.eco-band-chip { + display: none; + } +} + +/* ---------- narrow container ---------------------------------------------- */ + +/* Title and chip both ride the band's top border - one left, one right, both + nowrap. Under ~340px they overlap and the chip's opaque pill paints over the + heading, so drop the chip; the figcaption still states the relationship. */ +@container eco (max-width: 340px) { + .doc-page .eco-diagram .eco-grid a.eco-band-chip { + display: none; + } +} + +@container eco (max-width: 419px) { + .eco-grid { + --eco-icon: 32px; + --eco-mark: 20px; + } + + .eco-node-version { + display: none; + } +} + +/* ---------- motion / forced colors ---------------------------------------- */ + +@media (prefers-reduced-motion: reduce) { + .doc-page .eco-diagram .eco-grid a.eco-node { + transition: none; + } + + .doc-page .eco-diagram .eco-grid a.eco-node:hover { + transform: none; + } +} + +@media (forced-colors: active) { + .doc-page .eco-diagram .eco-band, + .doc-page .eco-diagram .eco-grid a.eco-node { + border-color: CanvasText; + } +} diff --git a/packages/google/ALTERNATIVE_BILLING.md b/packages/google/ALTERNATIVE_BILLING.md index e4fff7d2e..7e27f3882 100644 --- a/packages/google/ALTERNATIVE_BILLING.md +++ b/packages/google/ALTERNATIVE_BILLING.md @@ -161,6 +161,6 @@ evidence. ## Resources - [OpenIAP external purchase guide](https://openiap.dev/docs/features/external-purchase) -- [OpenIAP deprecation schedule](https://openiap.dev/docs/updates/deprecations) +- [OpenIAP deprecation schedule](https://openiap.dev/docs/updates/migration) - [Google Play alternative billing documentation](https://developer.android.com/google/play/billing/alternative) - [Google Play external transaction reporting](https://developer.android.com/google/play/billing/alternative/reporting) diff --git a/scripts/agent/compile-context.ts b/scripts/agent/compile-context.ts index 313c89926..073a0cea5 100644 --- a/scripts/agent/compile-context.ts +++ b/scripts/agent/compile-context.ts @@ -231,7 +231,7 @@ async function generateLlmsTxt(): Promise<{ quick: number; full: number }> { replacement, generated warnings where supported, migration documentation, and executable absence checks at the removal boundary. Patch and minor releases must not remove them early. -- See https://openiap.dev/docs/updates/deprecations for the complete mapping. +- See https://openiap.dev/docs/updates/migration for the complete mapping. `; // Read all external API docs diff --git a/scripts/audit-deprecation-schedule.mjs b/scripts/audit-deprecation-schedule.mjs index a78b2a458..2bb2f4be5 100644 --- a/scripts/audit-deprecation-schedule.mjs +++ b/scripts/audit-deprecation-schedule.mjs @@ -295,7 +295,7 @@ const activeDocsExcluded = [ "packages/docs/src/pages/docs/index.tsx", "packages/docs/src/pages/docs/apis/index.tsx", "packages/docs/src/pages/docs/updates/announcements.tsx", - "packages/docs/src/pages/docs/updates/deprecations.tsx", + "packages/docs/src/pages/docs/updates/migration.tsx", "packages/docs/src/pages/docs/updates/releases.tsx", ]; @@ -320,13 +320,13 @@ const requiredTexts = [ "`godot-iap` 3.0.0", "`kmp-iap` 3.0.0", "`OpenIap.Maui` 2.0.0", - "/docs/updates/deprecations", + "/docs/updates/migration", "Historical release notes, migration catalogs, archives, and URL", "internal React Native, Expo, KMP, or Godot native-response", ], }, { - file: "packages/docs/src/pages/docs/updates/deprecations.tsx", + file: "packages/docs/src/pages/docs/updates/migration.tsx", values: [ 'title="Breaking major release"', "Last compatible major",