From a9a9f2442d6508389752dfd6873e03daf0756834 Mon Sep 17 00:00:00 2001 From: hyochan Date: Sat, 15 Aug 2026 23:32:50 +0900 Subject: [PATCH 1/3] feat(docs): improve community discovery Route showcase and community resource submissions through GitHub Discussions, share showcase data across entry points, and improve resource metadata. Fix Meta wordmark theming and normalize the framework sponsor rail. --- packages/docs/COMMUNITY_RESOURCES.md | 9 +- packages/docs/SHOWCASE.md | 4 +- packages/docs/src/components/MetaWordmark.tsx | 12 ++ .../docs/src/components/ShowcaseCards.tsx | 6 +- packages/docs/src/lib/communityResources.ts | 68 ++++++--- packages/docs/src/lib/searchData.ts | 4 +- .../docs/src/pages/community-resources.tsx | 134 ++++++++++-------- packages/docs/src/pages/home.tsx | 7 +- packages/docs/src/pages/showcase.tsx | 8 +- packages/docs/src/pages/sponsors.tsx | 58 +------- packages/docs/src/styles/base.css | 14 ++ .../docs/src/styles/community-resources.css | 87 +++++++----- packages/docs/src/styles/explore-pages.css | 63 +------- packages/docs/src/styles/home.css | 9 +- 14 files changed, 235 insertions(+), 248 deletions(-) create mode 100644 packages/docs/src/components/MetaWordmark.tsx diff --git a/packages/docs/COMMUNITY_RESOURCES.md b/packages/docs/COMMUNITY_RESOURCES.md index 0e3f3d114..d6be114ac 100644 --- a/packages/docs/COMMUNITY_RESOURCES.md +++ b/packages/docs/COMMUNITY_RESOURCES.md @@ -2,7 +2,7 @@ The [Community Resources page](https://openiap.dev/community-resources) is a curated library of third-party knowledge about OpenIAP and the implementations -built on its specification. The official OpenIAP blog is presented separately +built on its specification. Official OpenIAP updates are presented separately from this community-maintained collection. Thank you to every developer, writer, speaker, and team who shares practical @@ -53,10 +53,13 @@ these rules: them. 5. Add accurate language metadata. English is the initial default; future non-English additions should ship with a visible language filter. -6. Do not add view counts or other metrics that will become stale. +6. Add a verified `publishedAt` date in `YYYY-MM-DD` format. Use the original + publication date when available. For sources that only expose an update or + submission date, set `dateLabel` to `Updated` or `Submitted`. +7. Do not add view counts or other metrics that will become stale. Before opening a pull request, run the docs format check, typecheck, and build described in [`README.md`](README.md). If you prefer to introduce your work before editing the library, share it in -[Show and Tell](https://github.com/hyodotdev/openiap/discussions/categories/show-and-tell). +the [community resources discussion](https://github.com/hyodotdev/openiap/discussions/349). diff --git a/packages/docs/SHOWCASE.md b/packages/docs/SHOWCASE.md index fdd610045..6b804a447 100644 --- a/packages/docs/SHOWCASE.md +++ b/packages/docs/SHOWCASE.md @@ -68,11 +68,11 @@ are skipped at render time. ## Don't want to send a PR? -Comment on [issue #280](https://github.com/hyodotdev/openiap/issues/280) or +Reply to [discussion #350](https://github.com/hyodotdev/openiap/discussions/350) or email **hyo@hyo.dev** with your app name, one-liner, logo, store links, and which library you use — we'll add it for you. ## Removal and updates Your app is listed only with your permission. To change or remove an entry, open -a PR, comment on issue #280, or email hyo@hyo.dev anytime. +a PR, comment on discussion #350, or email hyo@hyo.dev anytime. diff --git a/packages/docs/src/components/MetaWordmark.tsx b/packages/docs/src/components/MetaWordmark.tsx new file mode 100644 index 000000000..812456a3f --- /dev/null +++ b/packages/docs/src/components/MetaWordmark.tsx @@ -0,0 +1,12 @@ +import type { ReactElement } from 'react'; + +function MetaWordmark(): ReactElement { + return ( + + + + + ); +} + +export default MetaWordmark; diff --git a/packages/docs/src/components/ShowcaseCards.tsx b/packages/docs/src/components/ShowcaseCards.tsx index 3a0ee2047..74d62e302 100644 --- a/packages/docs/src/components/ShowcaseCards.tsx +++ b/packages/docs/src/components/ShowcaseCards.tsx @@ -3,8 +3,8 @@ import { SiApple, SiGoogleplay } from 'react-icons/si'; import { Globe } from 'lucide-react'; import type { ShowcaseApp } from '../lib/showcase'; -export const SHOWCASE_ISSUE_URL = - 'https://github.com/hyodotdev/openiap/issues/280'; +export const SHOWCASE_DISCUSSION_URL = + 'https://github.com/hyodotdev/openiap/discussions/350'; export const SHOWCASE_GUIDE_URL = 'https://github.com/hyodotdev/openiap/blob/main/packages/docs/SHOWCASE.md'; @@ -159,7 +159,7 @@ export function ShowcaseSubmitCard() { Send your app name, icon, and store links — we'll add it here. getEcosystemMeta(ecosystem).label - ), + ...resource.ecosystems.flatMap((ecosystem) => { + const meta = getEcosystemMeta(ecosystem); + return [meta.label, meta.module]; + }), ] .filter((value): value is string => Boolean(value)) .some((value) => value.toLowerCase().includes(normalizedQuery)); } function ResourceRow({ resource }: ResourceRowProps) { - const ecosystemLabels = resource.ecosystems - .map((ecosystem) => getEcosystemMeta(ecosystem).label) + const ecosystemModules = resource.ecosystems + .map((ecosystem) => getEcosystemMeta(ecosystem).module) .join(' · '); return ( @@ -208,8 +213,16 @@ function ResourceRow({ resource }: ResourceRowProps) { {resource.author && resource.organization && ( {resource.organization} )} + + + {resource.platform} - {ecosystemLabels} + {ecosystemModules} {resource.seriesLabel && {resource.seriesLabel}}

@@ -341,7 +354,7 @@ function CommunityResources() {
+ @@ -350,7 +363,7 @@ function CommunityResources() { Products shipping with OpenIAP + @@ -382,7 +395,7 @@ function CommunityResources() { strokeWidth={1.9} aria-hidden="true" /> - Official blog + Official updates {resource.platform} @@ -413,6 +426,19 @@ function CommunityResources() {

Community resource library

+ +

These third-party resources were written for different @@ -453,7 +479,7 @@ function CommunityResources() {

@@ -581,15 +607,38 @@ function CommunityResources() {

Apps in the wild

Built with OpenIAP

+
+ +

- Products using OpenIAP libraries across iOS, Android, and the - web. + A preview of products using OpenIAP libraries. Open the showcase + for the full list and store links.

- {SHOWCASE_APPS.map((app, index) => ( + {FEATURED_SHOWCASE_APPS.map((app, index) => (
{app.library} -
- {app.ios ? ( - - iOS - ) : null} - {app.android ? ( - - Android - ) : null} - {app.web ? ( - - Web - ) : null} -
+ + View showcase +
))}
- -
-

- Shipping with OpenIAP? Add your app icon and - store links to the community index. -

- - Add your app - -
@@ -670,15 +697,6 @@ function CommunityResources() { Edit and open a PR
@@ -552,7 +553,7 @@ function Home() {
Apps built with OpenIAP @@ -602,7 +603,7 @@ function Home() { rel="noopener noreferrer" aria-label="Visit Meta" > - Meta + Angel
diff --git a/packages/docs/src/pages/showcase.tsx b/packages/docs/src/pages/showcase.tsx index 35c7a74dd..dd1e22bec 100644 --- a/packages/docs/src/pages/showcase.tsx +++ b/packages/docs/src/pages/showcase.tsx @@ -3,7 +3,7 @@ import { ShowcaseAppCard, ShowcaseSubmitCard, SHOWCASE_GUIDE_URL, - SHOWCASE_ISSUE_URL, + SHOWCASE_DISCUSSION_URL, showcaseGridStyle, } from '../components/ShowcaseCards'; import { SHOWCASE_APPS } from '../lib/showcase'; @@ -48,14 +48,14 @@ function Showcase() { marginTop: '0.5rem', }} > - Comment on{' '} + Reply to{' '} - the showcase issue + the showcase discussion {' '} with the details below and we'll add your app. Prefer a pull request? Add an entry to{' '} diff --git a/packages/docs/src/pages/sponsors.tsx b/packages/docs/src/pages/sponsors.tsx index c73219de7..a6c01ffa9 100644 --- a/packages/docs/src/pages/sponsors.tsx +++ b/packages/docs/src/pages/sponsors.tsx @@ -6,6 +6,7 @@ import { ShieldCheck, Smartphone, } from 'lucide-react'; +import MetaWordmark from '../components/MetaWordmark'; import SEO from '../components/SEO'; import { LIBRARIES, LIBRARY_IMAGES } from '../lib/images'; @@ -35,32 +36,13 @@ const FUNDING_LINES = [ }, ] as const; -interface CurrentSponsor { - name: string; - image: string; - url: string; -} - -const CURRENT_SPONSORS: CurrentSponsor[] = [ +const CURRENT_SPONSORS = [ { name: 'Meta', - image: '/sponsors/meta.webp', + logo: , url: 'https://meta.com', }, -]; - -const REACT_NATIVE_FAMILY = LIBRARIES.filter( - (library) => - library.name === 'react-native-iap' || library.name === 'expo-iap' -).sort((first, second) => { - if (first.name === second.name) return 0; - return first.name === 'react-native-iap' ? -1 : 1; -}); - -const OTHER_FRAMEWORKS = LIBRARIES.filter( - (library) => - library.name !== 'react-native-iap' && library.name !== 'expo-iap' -); +] as const; function Sponsors() { return ( @@ -134,7 +116,7 @@ function Sponsors() { rel="noopener noreferrer" aria-label={`Visit ${sponsor.name}`} > - {sponsor.name} + {sponsor.logo} ))} @@ -190,35 +172,7 @@ function Sponsors() { /> Google -
- {REACT_NATIVE_FAMILY.map((library) => ( - - - - {library.homeLabel} - - {library.name === 'react-native-iap' - ? 'Primary' - : 'Same API'} - - - - ))} -
- {OTHER_FRAMEWORKS.map((library) => ( + {LIBRARIES.map((library) => ( img { + width: auto; + height: 100%; + object-fit: contain; +} + img[src='/frameworks/apple.svg'] { filter: var(--apple-logo-filter); } diff --git a/packages/docs/src/styles/community-resources.css b/packages/docs/src/styles/community-resources.css index 866ba881a..cf53a7cfa 100644 --- a/packages/docs/src/styles/community-resources.css +++ b/packages/docs/src/styles/community-resources.css @@ -565,6 +565,55 @@ line-height: var(--leading-display); } +.cr-apps-actions { + display: flex; + flex-wrap: wrap; + gap: 0 0.65rem; +} + +.cr-discussion-link { + display: inline-flex; + align-items: center; + gap: 0.55rem; + margin-top: 0.9rem; + padding: 0.52rem 0.66rem; + border: 1px solid var(--border-color); + border-radius: 0.55rem; + color: var(--text-primary); + background: var(--bg-secondary); + text-decoration: none; + transition: + border-color 150ms ease, + transform 150ms ease; +} + +.cr-discussion-link > svg { + flex: 0 0 auto; + color: var(--cr-accent); +} + +.cr-discussion-link > span { + display: flex; + flex-direction: column; + gap: 0.08rem; +} + +.cr-discussion-link strong { + font-size: 0.72rem; + line-height: 1.25; +} + +.cr-discussion-link small { + color: var(--text-secondary); + font-size: 0.59rem; + line-height: 1.25; +} + +.cr-discussion-link:hover { + border-color: var(--cr-accent); + transform: translateY(-1px); +} + .cr-apps-heading > p { margin: 0; color: var(--text-secondary); @@ -634,15 +683,7 @@ font-weight: 680; } -.cr-app-meta > div { - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - gap: 0.55rem; -} - -.cr-app-meta a, -.cr-app-submit a { +.cr-app-meta a { display: inline-flex; align-items: center; gap: 0.18rem; @@ -652,31 +693,11 @@ text-decoration: none; } -.cr-app-meta a:hover, -.cr-app-submit a:hover { +.cr-app-meta a:hover { text-decoration: underline; text-underline-offset: 0.18em; } -.cr-app-submit { - display: flex; - align-items: center; - justify-content: space-between; - gap: 2rem; - padding-top: 1rem; -} - -.cr-app-submit p { - margin: 0; - color: var(--text-secondary); - font-size: 0.72rem; - line-height: 1.5; -} - -.cr-app-submit strong { - color: var(--text-primary); -} - .cr-contribute { display: flex; align-items: center; @@ -875,12 +896,6 @@ align-items: flex-start; flex-direction: column; } - - .cr-app-submit { - align-items: flex-start; - flex-direction: column; - gap: 0.6rem; - } } @media (prefers-reduced-motion: reduce) { diff --git a/packages/docs/src/styles/explore-pages.css b/packages/docs/src/styles/explore-pages.css index 395cfe900..325f9accd 100644 --- a/packages/docs/src/styles/explore-pages.css +++ b/packages/docs/src/styles/explore-pages.css @@ -771,6 +771,7 @@ .xp-sponsor-node:not(.xp-sponsor-open-node) > a { display: inline-block; + filter: drop-shadow(0 0.7rem 1.2rem rgba(45, 25, 20, 0.08)); line-height: 0; transition: transform 180ms ease; } @@ -779,12 +780,9 @@ transform: translateY(-3px) scale(1.025); } -.xp-sponsor-node img { - width: auto; +.xp-sponsor-node .meta-wordmark { max-width: 12rem; height: 3rem; - object-fit: contain; - filter: drop-shadow(0 0.7rem 1.2rem rgba(45, 25, 20, 0.08)); } .xp-sponsor-open-node > a { @@ -819,8 +817,7 @@ border-block: 1px solid var(--border-color); } -.xp-stack-rail > a, -.xp-stack-family > a { +.xp-stack-rail > a { display: flex; min-width: 0; flex-direction: column; @@ -851,54 +848,6 @@ color: var(--xp-accent); } -.xp-stack-family { - display: grid; - min-width: 0; - grid-column: span 2; - grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.75fr); - border-left: 1px solid var(--border-color); - background: rgba(220, 104, 67, 0.025); -} - -.xp-stack-family > a { - flex-direction: row; - gap: 0.7rem; - align-items: center; - justify-content: center; - text-align: left; -} - -.xp-stack-family > a + a { - border-left: 1px solid var(--border-color); -} - -.xp-stack-family > a > span { - display: flex; - min-width: 0; - flex-direction: column; - gap: 0.15rem; -} - -.xp-stack-family strong { - overflow: hidden; - color: var(--text-primary); - font-size: 0.64rem; - text-overflow: ellipsis; - white-space: nowrap; -} - -.xp-stack-family small { - color: var(--text-secondary); - font-size: 0.5rem; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.xp-stack-family-secondary img { - width: 1.75rem; - height: 1.75rem; -} - .xp-funding-ledger { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); @@ -1108,11 +1057,11 @@ grid-template-columns: repeat(4, minmax(0, 1fr)); } - .xp-stack-rail > a:nth-child(4) { + .xp-stack-rail > a:nth-child(5) { border-left: 0; } - .xp-stack-rail > a:nth-child(n + 4) { + .xp-stack-rail > a:nth-child(n + 5) { border-top: 1px solid var(--border-color); } @@ -1283,7 +1232,7 @@ gap: 1rem; } - .xp-sponsor-node img { + .xp-sponsor-node .meta-wordmark { max-width: 8.75rem; height: 2.35rem; } diff --git a/packages/docs/src/styles/home.css b/packages/docs/src/styles/home.css index 3dc924b7c..ed58e7fde 100644 --- a/packages/docs/src/styles/home.css +++ b/packages/docs/src/styles/home.css @@ -548,10 +548,8 @@ gap: 0.2rem; } -.hero-backer img { - width: auto; +.hero-backer .meta-wordmark { height: 1.35rem; - object-fit: contain; } @media (max-width: 1050px) { @@ -1515,8 +1513,7 @@ a.spec-item:hover code { transform: scale(1.035); } -.home-sponsor-logo-link img { - width: auto; +.home-sponsor-logo-link .meta-wordmark { height: 4.1rem; } @@ -1668,7 +1665,7 @@ a.spec-item:hover code { padding-inline: 1rem; } - .home-sponsor-logo-link img { + .home-sponsor-logo-link .meta-wordmark { height: 2.5rem; } From e9c743852bc2aa979d6d4f282395d89c7da8bc09 Mon Sep 17 00:00:00 2001 From: hyochan Date: Sun, 16 Aug 2026 00:30:27 +0900 Subject: [PATCH 2/3] ci(codeql): scope Swift scans by change --- .github/workflows/codeql.yml | 73 +++++++++++++++++++++++++++------ scripts/audit-security.test.mjs | 39 ++++++++++++++++-- 2 files changed, 95 insertions(+), 17 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c918c9a34..966e6b399 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -12,10 +12,61 @@ on: permissions: contents: read +concurrency: + group: codeql-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + env: XCODE_VERSION: 16.4 jobs: + codeql-scope: + name: Detect CodeQL Scope + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + outputs: + swift_core: ${{ github.event_name != 'pull_request' || steps.core.outputs.swift_core == 'true' }} + swift_wrappers: ${{ github.event_name != 'pull_request' || steps.wrappers.outputs.changes != '[]' }} + swift_components: >- + ${{ github.event_name != 'pull_request' && + '["react-native","expo","expo-onside","flutter","godot"]' || + steps.wrappers.outputs.changes }} + steps: + - name: Checkout + if: github.event_name == 'pull_request' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + + - name: Detect Apple core changes + if: github.event_name == 'pull_request' + id: core + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 + with: + filters: | + swift_core: + - 'packages/apple/**' + - 'libraries/kmp-iap/native/InAppPurchaseBridge/**' + + - name: Detect Swift wrapper changes + if: github.event_name == 'pull_request' + id: wrappers + uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 + with: + filters: | + react-native: + - 'libraries/react-native-iap/**' + expo: + - 'libraries/expo-iap/**' + expo-onside: + - 'libraries/expo-iap/**' + flutter: + - 'libraries/flutter_inapp_purchase/**' + godot: + - 'libraries/godot-iap/**' + analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-latest @@ -225,6 +276,8 @@ jobs: analyze-swift: name: Analyze (swift) + needs: codeql-scope + if: needs.codeql-scope.outputs.swift_core == 'true' runs-on: macos-15 timeout-minutes: 45 permissions: @@ -267,8 +320,12 @@ jobs: analyze-swift-wrappers: name: Analyze (swift / ${{ matrix.component }}) - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository - runs-on: ${{ matrix.runner }} + needs: codeql-scope + if: >- + needs.codeql-scope.outputs.swift_wrappers == 'true' && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ${{ (github.event_name == 'pull_request' || matrix.component == 'godot') && 'macos-26' || 'xcode-27' }} timeout-minutes: 90 permissions: contents: read @@ -276,17 +333,7 @@ jobs: strategy: fail-fast: false matrix: - include: - - component: react-native - runner: xcode-27 - - component: expo - runner: xcode-27 - - component: expo-onside - runner: xcode-27 - - component: flutter - runner: xcode-27 - - component: godot - runner: macos-26 + component: ${{ fromJSON(needs.codeql-scope.outputs.swift_components) }} steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 diff --git a/scripts/audit-security.test.mjs b/scripts/audit-security.test.mjs index ce5971cf4..1ff456047 100644 --- a/scripts/audit-security.test.mjs +++ b/scripts/audit-security.test.mjs @@ -568,20 +568,51 @@ test("compiled CodeQL Gradle builds reuse the transient-network retry guard", () assert.match(workflow, /:library:compilePlayDebugKotlinAndroid/u); }); -test("CodeQL never runs pull-request code on private Xcode runners", () => { +test("CodeQL scopes Swift pull requests to public macOS runners", () => { const workflow = readFileSync( new URL("../.github/workflows/codeql.yml", import.meta.url), "utf8", ); + const scope = workflow.slice( + workflow.indexOf(" codeql-scope:"), + workflow.indexOf(" analyze:"), + ); const wrappers = workflow.slice( workflow.indexOf(" analyze-swift-wrappers:"), ); + assert.match( + workflow, + /group: codeql-\$\{\{ github\.event\.pull_request\.number \|\| github\.run_id \}\}/u, + ); + assert.match( + workflow, + /cancel-in-progress: \$\{\{ github\.event_name == 'pull_request' \}\}/u, + ); + assert.match(scope, /swift_core:/u); + assert.match(scope, /react-native:/u); + assert.match(scope, /expo-onside:/u); + assert.match(scope, /flutter:/u); + assert.match(scope, /godot:/u); + assert.match( + scope, + /\["react-native","expo","expo-onside","flutter","godot"\]/u, + ); + assert.match( + wrappers, + /needs\.codeql-scope\.outputs\.swift_wrappers == 'true'/u, + ); + assert.match( + wrappers, + /github\.event\.pull_request\.head\.repo\.full_name == github\.repository/u, + ); + assert.match( + wrappers, + /runs-on: \$\{\{ \(github\.event_name == 'pull_request' \|\| matrix\.component == 'godot'\) && 'macos-26' \|\| 'xcode-27' \}\}/u, + ); assert.match( wrappers, - /if: github\.event_name != 'pull_request' \|\| github\.event\.pull_request\.head\.repo\.full_name == github\.repository/u, + /component: \$\{\{ fromJSON\(needs\.codeql-scope\.outputs\.swift_components\) \}\}/u, ); - assert.match(wrappers, /runner: xcode-27/u); - assert.match(wrappers, /component: expo-onside/u); assert.match( wrappers, /EXPO_IAP_ONSIDE: \$\{\{ matrix\.component == 'expo-onside' && '1' \|\| '0' \}\}/u, From 96e1b970eb9b9826ce20e3724f47a017d98b9700 Mon Sep 17 00:00:00 2001 From: hyochan Date: Sun, 16 Aug 2026 00:38:25 +0900 Subject: [PATCH 3/3] test(codeql): cover Swift core scope gate --- scripts/audit-security.test.mjs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/audit-security.test.mjs b/scripts/audit-security.test.mjs index 1ff456047..ea7cb7fa4 100644 --- a/scripts/audit-security.test.mjs +++ b/scripts/audit-security.test.mjs @@ -580,6 +580,10 @@ test("CodeQL scopes Swift pull requests to public macOS runners", () => { const wrappers = workflow.slice( workflow.indexOf(" analyze-swift-wrappers:"), ); + const swiftCore = workflow.slice( + workflow.indexOf(" analyze-swift:"), + workflow.indexOf(" analyze-swift-wrappers:"), + ); assert.match( workflow, /group: codeql-\$\{\{ github\.event\.pull_request\.number \|\| github\.run_id \}\}/u, @@ -589,6 +593,11 @@ test("CodeQL scopes Swift pull requests to public macOS runners", () => { /cancel-in-progress: \$\{\{ github\.event_name == 'pull_request' \}\}/u, ); assert.match(scope, /swift_core:/u); + assert.match(swiftCore, /needs: codeql-scope/u); + assert.match( + swiftCore, + /if: needs\.codeql-scope\.outputs\.swift_core == 'true'/u, + ); assert.match(scope, /react-native:/u); assert.match(scope, /expo-onside:/u); assert.match(scope, /flutter:/u);