Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "docs",
"runtimeExecutable": "bun",
"runtimeArgs": ["run", "--cwd", "packages/docs", "dev"],
"port": 5173
}
]
}
26 changes: 26 additions & 0 deletions .claude/skills/add-showcase-app/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
name: add-showcase-app
description: Add an app to the OpenIAP "Who uses OpenIAP?" showcase — download and mask its icon, append the showcase-apps.json entry, refresh the review-count ordering metrics, and verify the docs build. Use when someone submits an app through issue #280, a showcase pull request, X, or email, or when the user asks to add or update an app on openiap.dev/showcase.
---

# Add Showcase App (Claude Code)

The canonical procedure lives in `.codex/skills/add-showcase-app/SKILL.md`.
Read it and follow every section — collecting the submission, masking the icon,
appending the JSON entry, refreshing metrics, verifying, and closing the loop
are agent-agnostic and apply as written.

## Claude Code Notes

- Fetch submissions with the GitHub MCP tools or `gh` (for example
`gh api repos/hyodotdev/openiap/issues/280/comments`) instead of asking the
user to paste them.
- To verify rendering, start the docs dev server through `preview_start`
(`.claude/launch.json` defines the `docs` configuration) and check the
showcase section in the browser pane. The home page section sits far down the
page — scroll to the `Who uses OpenIAP?` heading, or open `/showcase`
directly, which renders the full list near the top.
- If browser screenshots come back blank, fall back to headless Chrome against
the dev server and crop the region with Pillow.
- Attach the rendered section back to the user with `SendUserFile` so they can
approve the card before anything is committed.
149 changes: 149 additions & 0 deletions .codex/skills/add-showcase-app/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
---
name: add-showcase-app
description: Add an app to the OpenIAP "Who uses OpenIAP?" showcase — download and mask its icon, append the showcase-apps.json entry, refresh the review-count ordering metrics, and verify the docs build. Use when someone submits an app through issue #280, a showcase pull request, X, or email, or when the user asks to add or update an app on openiap.dev/showcase.
---

# Add Showcase App

Turn an app submission into a rendered card on the home page and `/showcase`.

Everything lives in `packages/docs`:

| Path | Role |
| ------------------------------------------- | ---------------------------------------- |
| `showcase-apps.json` | The list (SSOT for what renders) |
| `public/showcase/<slug>.webp` | Masked 256×256 app icon |
| `scripts/refresh-showcase-metrics.mjs` | Fills `ratings` / `installs` for ordering |
| `src/lib/showcase.ts` | Sorting + featured slice |
| `src/components/ShowcaseCards.tsx` | Card markup |
| `SHOWCASE.md` | Public submission guide |

## 1. Collect the submission

Required from the submitter:

- **App name** and a one-line description (keep the tagline under ~70 chars so
cards stay even)
- **App icon** — square, 512×512 PNG (a store icon URL works too)
- **Store links** — App Store and/or Google Play; a website link is optional
- **Library** — one of `expo-iap`, `react-native-iap`, `flutter_inapp_purchase`,
`kmp-iap`, `maui-iap`, `godot-iap`

Only list an app when the submitter asked for it. A comment on
[issue #280](https://github.com/hyodotdev/openiap/issues/280), a showcase PR, an
email, or a public reply to the announcement all count as permission; a mention
of the library somewhere else does not.

If the icon is missing, pull it from the stores rather than asking again:

```bash
# App Store artwork + metadata
curl -s "https://itunes.apple.com/lookup?id=<TRACK_ID>" | python3 -m json.tool | grep artworkUrl512

# Google Play icon
curl -s "https://play.google.com/store/apps/details?id=<PACKAGE>" \
| grep -o 'https://play-lh.googleusercontent.com/[A-Za-z0-9_=-]\{20,\}' | head -1
```

## 2. Add the icon

Icons are stored pre-masked so store artwork with baked-in rounded corners and
plain square artwork render identically. Append `=s512` to a Play icon URL for
the full-size original.

```bash
cd packages/docs && python3 - <<'PY'
import urllib.request, io
from PIL import Image, ImageDraw

SLUG = "your-app" # kebab-case, matches the logo path in the JSON
URL = "https://..." # 512px source icon

SIZE, SS, RATIO = 256, 4, 0.2237 # 0.2237 ≈ the Apple icon corner radius
mask = Image.new("L", (SIZE*SS, SIZE*SS), 0)
ImageDraw.Draw(mask).rounded_rectangle(
(0, 0, SIZE*SS-1, SIZE*SS-1), radius=int(SIZE*SS*RATIO), fill=255
)
mask = mask.resize((SIZE, SIZE), Image.LANCZOS)

req = urllib.request.Request(URL, headers={"User-Agent": "Mozilla/5.0"})
raw = urllib.request.urlopen(req, timeout=30).read()
img = Image.open(io.BytesIO(raw)).convert("RGBA").resize((SIZE, SIZE), Image.LANCZOS)
out = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
out.paste(img, (0, 0), mask)
out.save(f"public/showcase/{SLUG}.webp", "WEBP", quality=90, method=6)
print("saved", SLUG)
PY
```

`sips` cannot write WebP on macOS — use the Pillow snippet above.

## 3. Append the entry

Add to the end of the `apps` array in `packages/docs/showcase-apps.json`.
Ordering is computed at render time, so position in the file does not matter.

```json
{
"name": "Your App",
"tagline": "One line about what the app does",
"logo": "/showcase/your-app.webp",
"library": "expo-iap",
"ios": "https://apps.apple.com/us/app/your-app/id0000000000",
"android": "https://play.google.com/store/apps/details?id=com.example.yourapp"
}
```

`ios`, `android`, and `web` are each optional, but an entry with none of them is
dropped at render time. Leave `ratings` and `installs` out — step 4 writes them.

## 4. Refresh the ordering metrics

```bash
cd packages/docs && bun run showcase:metrics
```

The script fills every entry:

- `ratings` — App Store `userRatingCount` **summed across every storefront**
plus the Google Play review count. **Primary sort key, descending.**
- `installs` — the Play install floor (`"1K+"` → `1000`). **Fallback** when
review counts tie, which is common for new apps.

Apple reports ratings per storefront and publishes no global total, so a US-only
lookup reads zero for an app reviewed mainly in Korea or Japan. The sweep covers
~170 storefronts and takes about a minute; Apple throttles bursts, so the script
retries failures in later rounds and **keeps the previous numbers rather than
writing a partial sweep**. If output says `kept existing ratings`, rerun it.

Neither store publishes download totals: Apple exposes no install data in any
public API, and Play reports only a coarse bucket. Do not add a `downloads`
field or invent numbers — review count is the one verifiable signal both stores
share. If a submitter reports their own install figures, keep them out of the
JSON.

The scraper depends on Play's HTML, whose class names are obfuscated and change.
If `ratings` comes back unexpectedly `0` for an app that clearly has reviews,
re-check the regexes in `scripts/refresh-showcase-metrics.mjs` rather than
hand-editing the JSON.

## 5. Verify

```bash
cd packages/docs && bun run typecheck && bun run build
```

Then confirm the card renders and the icon actually loads — a broken `logo` path
fails silently as a missing image, not a build error. The home page shows the
top `FEATURED_SHOWCASE_LIMIT` (5) apps plus the submit card and a "See all"
link; `/showcase` lists everything.

## 6. Close the loop

- Reply to the submission thread (issue #280 comment, PR, or email) confirming
the app is listed, and note that updates or removal are available anytime.
- Public GitHub replies must be in English — see
`knowledge/internal/06-git-deployment.md`.
- Commit with a lowercase subject after the tag, e.g.
`docs: add recallai to showcase`. Do not commit, push, or open a PR unless the
user already authorized it.
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ if git diff --cached --name-only --diff-filter=ACMR \
bun install --frozen-lockfile
bun run --filter @hyodotdev/openiap-docs typecheck
bun test "$REPO_ROOT/scripts/audit-docs.test.ts"
bun run --filter @hyodotdev/openiap-docs showcase:metrics:test
bun run audit:docs
(
cd packages/docs && bunx prettier --check "src/**/*.{ts,tsx,css}"
Expand Down
8 changes: 4 additions & 4 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions packages/docs/SHOWCASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Submit your app to the OpenIAP showcase

Shipped an app with `react-native-iap`, `expo-iap`, `flutter_inapp_purchase`,
`kmp-iap`, `maui-iap`, or `godot-iap`? Add it to the
**[Who uses OpenIAP?](https://www.openiap.dev)** section on the home page.

It's one entry in [`showcase-apps.json`](./showcase-apps.json).

## Open a pull request

1. Fork [hyodotdev/openiap](https://github.com/hyodotdev/openiap) and create a branch.
2. Add your app to the end of the `apps` array in `packages/docs/showcase-apps.json`:

```json
{
"name": "Your App",
"tagline": "One line about what your app does",
"logo": "/showcase/your-app.webp",
"library": "expo-iap",
"ios": "https://apps.apple.com/us/app/your-app/id0000000000",
"android": "https://play.google.com/store/apps/details?id=com.example.yourapp"
}
```

3. Add your icon to `packages/docs/public/showcase/` as a **square 512×512 PNG**
or a 256×256 `.webp`. Don't pre-round the corners — we apply the same rounded
mask to every icon so the row stays consistent.
4. Open the PR with the title `docs: add <Your App> to showcase`.

That's it. No build step or code change is needed — the pages render the JSON
directly.

## Ordering

Apps are ordered by **combined App Store + Google Play review count**,
descending, with the Google Play install count as a tiebreaker. Neither store
publishes download totals — Apple exposes no install data publicly and Play
reports only a bucket like "1K+" — so review count is the one verifiable signal
both stores share.

App Store review counts are **summed across every storefront**, not just the US
one: Apple reports `userRatingCount` per country and publishes no global total,
so an app reviewed mainly in Korea or Japan would otherwise read as zero.

Maintainers refresh the numbers with:

```bash
cd packages/docs && bun run showcase:metrics
```

Leave `ratings` and `installs` out of your PR — the script fills them in.

## Fields

| Field | Required | Notes |
| --------- | -------- | -------------------------------------------------------------------------------------- |
| `name` | ✅ | App name as it appears on the stores. |
| `tagline` | ✅ | One short line. Keep it under ~70 characters so cards stay even. |
| `logo` | ✅ | Path under `packages/docs/public` (e.g. `/showcase/your-app.webp`) or a full https URL. |
| `ratings` / `installs` | — | Maintainer-managed ordering metrics. Leave these out. |
| `library` | ✅ | One of `expo-iap`, `react-native-iap`, `flutter_inapp_purchase`, `kmp-iap`, `maui-iap`, `godot-iap`. |
| `ios` | — | App Store URL. |
| `android` | — | Google Play URL. |
| `web` | — | Website or other store, shown as "Website". |

At least one of `ios`, `android`, or `web` is required — entries without a link
are skipped at render time.

## Don't want to send a PR?

Comment on [issue #280](https://github.com/hyodotdev/openiap/issues/280) 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.
2 changes: 2 additions & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
"version": "3.0.1",
"type": "module",
"scripts": {
"showcase:metrics": "node scripts/refresh-showcase-metrics.mjs",
"showcase:metrics:test": "node --test scripts/refresh-showcase-metrics.test.mjs",
"dev": "bunx vite",
"build": "bun run typecheck && bunx vite build",
"typecheck": "tsc --noEmit",
Expand Down
Binary file added packages/docs/public/showcase/loader.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/docs/public/showcase/martie.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/docs/public/showcase/recallai.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/docs/public/showcase/sudoku-rabbit.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading