@freetesh/canon 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +37 -31
- package/cli/dist/api.d.ts +1 -2
- package/cli/dist/api.js +1 -2
- package/cli/dist/check.d.ts +7 -3
- package/cli/dist/check.js +18 -7
- package/cli/dist/checks/commands.d.ts +1 -1
- package/cli/dist/checks/commands.js +1 -1
- package/cli/dist/checks/designs.d.ts +7 -0
- package/cli/dist/checks/designs.js +46 -0
- package/cli/dist/checks/index.js +3 -0
- package/cli/dist/checks/refs.js +8 -4
- package/cli/dist/commands/add.d.ts +1 -1
- package/cli/dist/commands/add.js +1 -4
- package/cli/dist/commands/check.js +4 -4
- package/cli/dist/commands/copies.js +1 -1
- package/cli/dist/commands/hooks.js +4 -4
- package/cli/dist/commands/learn.js +7 -5
- package/cli/dist/commands/pipeline.js +2 -2
- package/cli/dist/commands/scripts.d.ts +3 -0
- package/cli/dist/commands/scripts.js +32 -0
- package/cli/dist/commands/standards.js +1 -1
- package/cli/dist/commands/sync.js +1 -1
- package/cli/dist/commands/ui.js +3 -3
- package/cli/dist/config/load.d.ts +5 -11
- package/cli/dist/config/load.js +21 -25
- package/cli/dist/config/schema.d.ts +6 -2
- package/cli/dist/config/schema.js +9 -5
- package/cli/dist/doctor.js +2 -1
- package/cli/dist/index.js +4 -2
- package/cli/dist/learn.d.ts +2 -2
- package/cli/dist/learn.js +6 -6
- package/cli/dist/lib/env.d.ts +3 -3
- package/cli/dist/lib/env.js +4 -4
- package/cli/dist/packs.d.ts +1 -3
- package/cli/dist/packs.js +4 -7
- package/cli/dist/standards.d.ts +11 -6
- package/cli/dist/standards.js +37 -17
- package/cli/dist/status.js +1 -1
- package/cli/dist/sync.d.ts +0 -1
- package/cli/dist/sync.js +1 -1
- package/package.json +4 -1
- package/scripts/arch-set.py +140 -0
- package/scripts/check-designs.py +709 -0
- package/scripts/flow.py +313 -0
- package/skills/ingest/SKILL.md +1 -1
- package/standards/README.md +23 -6
- package/standards/agent-contract.md +21 -6
- package/standards/context-layout.md +42 -7
- package/standards/library/design-sets.md +857 -0
- package/standards/library/expo.md +54 -0
- package/standards/library/frontend.md +44 -0
- package/standards/library/performance.md +45 -0
- package/standards/library/pipeline.md +42 -0
- package/standards/library/secrets.md +37 -0
- package/standards/library/store-listing.md +122 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Expo apps
|
|
2
|
+
|
|
3
|
+
How an Expo app is created and laid out. Opt in with `standards: [expo]`.
|
|
4
|
+
Code rules for the `.tsx` files are in `frontend`.
|
|
5
|
+
|
|
6
|
+
## Read the versioned docs
|
|
7
|
+
|
|
8
|
+
This project is on **Expo SDK {{expo.sdk}}** (from `expo.sdk` in
|
|
9
|
+
`.claude/canon.yaml`). Read <https://docs.expo.dev/versions/v{{expo.sdk}}.0.0/>
|
|
10
|
+
before writing any Expo or React Native code. APIs move between SDKs, and
|
|
11
|
+
recalled shapes are often wrong.
|
|
12
|
+
|
|
13
|
+
## Scaffold with the Expo CLI
|
|
14
|
+
|
|
15
|
+
Create the app with `npx create-expo-app@latest`, on the latest Expo SDK. Never
|
|
16
|
+
hand-assemble a React Native project, never copy another app's `node_modules`
|
|
17
|
+
layout, never fork an old scaffold that is a major version behind.
|
|
18
|
+
|
|
19
|
+
Set these up at scaffold time, before any feature work: expo-router, lucide
|
|
20
|
+
icons (`lucide-react-native`), UniWind, RevenueCat (`react-native-purchases`),
|
|
21
|
+
Firebase, and any AI integrations the app needs. Structure only — no features.
|
|
22
|
+
Copy the lint rules in at the same time (`frontend`).
|
|
23
|
+
|
|
24
|
+
## Layout
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
<app>/
|
|
28
|
+
CLAUDE.md # stack, directory tree, monetization, persistence rules
|
|
29
|
+
AGENTS.md # the agent contract; CLAUDE.md imports it with @AGENTS.md
|
|
30
|
+
CHANGELOG.md # Keep a Changelog 1.1.0 — feeds the store release notes
|
|
31
|
+
.context/ # see context-layout
|
|
32
|
+
src/ # application code; routes in src/app/ (or apps/<name>/src/ in a monorepo)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `/ios` and `/android` are **generated** and gitignored. Never hand-edit them —
|
|
36
|
+
change `app.config.ts` or a config plugin and prebuild again.
|
|
37
|
+
- The store listing is in `.context/listing/` (see `store-listing`).
|
|
38
|
+
|
|
39
|
+
## Styling
|
|
40
|
+
|
|
41
|
+
UniWind only — the rule is `frontend` rule 5. The Tailwind tokens in the app
|
|
42
|
+
mirror the design set's `tailwind.config.js` (`design-sets` §8), so a token
|
|
43
|
+
means the same thing in the drawing and in the app.
|
|
44
|
+
|
|
45
|
+
## Before calling work done
|
|
46
|
+
|
|
47
|
+
On top of the repo's lint, typecheck and tests:
|
|
48
|
+
|
|
49
|
+
1. For anything touching imports, dependencies or assets, also run
|
|
50
|
+
`npx expo-doctor` and `pnpm expo export --platform android`. They catch SDK
|
|
51
|
+
pin drift and runtime resolution failures that typecheck cannot.
|
|
52
|
+
2. If a native dependency changed, it ships as a release
|
|
53
|
+
(`npm version patch && git push --follow-tags`), not an OTA update.
|
|
54
|
+
`scripts/check-ota-safe.mjs` blocks the OTA otherwise.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Frontend code
|
|
2
|
+
|
|
3
|
+
Rules for every `.tsx` file in a React Native or React app, new or touched.
|
|
4
|
+
Opt in with `standards: [frontend]`.
|
|
5
|
+
|
|
6
|
+
These are not style preferences and there is no "I'll split it later". **Plan
|
|
7
|
+
the files before writing the screen**: list the components, hooks and helpers a
|
|
8
|
+
screen needs, then write each in its own file. A screen written as one file and
|
|
9
|
+
split afterwards is the failure this standard exists to stop.
|
|
10
|
+
|
|
11
|
+
1. **One component per file, named after the file.** No private sub-component at
|
|
12
|
+
the bottom of a screen, however small. The only exception is a set of sibling
|
|
13
|
+
primitives always imported together (a type ramp, a layout trio), in a file
|
|
14
|
+
named for the set and listed in the lint config.
|
|
15
|
+
2. **A route file is thin.** `app/**` reads params, finds the data, and renders
|
|
16
|
+
one screen component from `components/<feature>/`. No business logic, no
|
|
17
|
+
event handlers longer than a line, and under 200 lines.
|
|
18
|
+
3. **Helpers live apart from what renders.** A pure function goes in `lib/` with
|
|
19
|
+
a test. Anything that touches React, a store or navigation goes in `hooks/`.
|
|
20
|
+
No lowercase function is declared in a `.tsx` file.
|
|
21
|
+
4. **Constants live in `constants/`.** A number, a label map, a limit, a regex:
|
|
22
|
+
if it has a name, it has a home outside the component.
|
|
23
|
+
5. **UniWind only** (`uniwind` + `tailwindcss`). `className` for everything a
|
|
24
|
+
class can express. No `StyleSheet`, no styled-components, no NativeWind, no
|
|
25
|
+
CSS files beyond the theme. `style={{ }}` only for a value computed at
|
|
26
|
+
runtime — a measured or animated number. A third-party native view that
|
|
27
|
+
ignores `className` (expo-image, expo-camera) is wrapped once with
|
|
28
|
+
`withUniwind` in `components/ui/` and used through the wrapper.
|
|
29
|
+
6. **Props are a named `interface`**, declared above the component. No
|
|
30
|
+
`React.FC`, no anonymous inline prop types on an exported component.
|
|
31
|
+
7. **Hooks hold the state and effects; components render.** A component that
|
|
32
|
+
fetches, navigates and lays out is three files.
|
|
33
|
+
8. **Lowercase kebab-case file names, PascalCase component exports.**
|
|
34
|
+
|
|
35
|
+
## The linter enforces these
|
|
36
|
+
|
|
37
|
+
`pnpm lint` is a gate. Keep the rules in one ESLint file (for example
|
|
38
|
+
`eslint/standards.js`): one component per file, no helpers in component files,
|
|
39
|
+
no fixed inline styles, no `StyleSheet`, a line cap on routes. Copy it from the
|
|
40
|
+
reference repo (`copies.source`) at scaffold time. A standard that is only
|
|
41
|
+
written down gets broken; one that fails the build does not.
|
|
42
|
+
|
|
43
|
+
Before reporting frontend work as done, run the linter and read the diff against
|
|
44
|
+
this list. If a file breaks one of these, the work is not done.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
For web and Expo apps. Opt in with `standards: [performance]`.
|
|
4
|
+
|
|
5
|
+
Learned from an editor where every canvas edit redrew the whole screen, and each
|
|
6
|
+
redraw cost 21 database round trips. Nothing was broken; it was just slow.
|
|
7
|
+
|
|
8
|
+
1. **A local edit never redraws the screen.** When the UI already shows the
|
|
9
|
+
change (a canvas, a field, a toggle), save it and update only what the save
|
|
10
|
+
changed, from the mutation's answer. Next.js: no `router.refresh()`. Expo: no
|
|
11
|
+
refetch or invalidate-all of the screen's query. Redraw only when
|
|
12
|
+
server-drawn content changes shape (a row added or removed), and say why in
|
|
13
|
+
a comment.
|
|
14
|
+
2. **Hot paths are guarded by lint.** The screens people edit on (an editor, a
|
|
15
|
+
canvas, a form that saves on every change) get a `no-restricted-syntax` rule
|
|
16
|
+
that bans the redraw call in their folders. Each allowed exception gets a
|
|
17
|
+
disable comment with its reason.
|
|
18
|
+
3. **Count the round trips of a screen's loader.** Each query is one trip to
|
|
19
|
+
the database, and on the deployed app every trip waits on the network.
|
|
20
|
+
- Never look the same row up twice in one request. Pass on what you already
|
|
21
|
+
loaded.
|
|
22
|
+
- Never count rows you also read (`count` next to `findMany`). Use the
|
|
23
|
+
length.
|
|
24
|
+
- A nested Prisma select is one more query per relation. Fold reads into one
|
|
25
|
+
query where you can: all open refs at once, not main and then the
|
|
26
|
+
branches.
|
|
27
|
+
- **Separation of duties comes before speed.** Database roles, row
|
|
28
|
+
policies and column grants are not traded for fewer queries. Prisma's
|
|
29
|
+
`relationJoins` reads every column, so it fails where a column has its own
|
|
30
|
+
grant (a sealed password column, say). Change the data layer for speed
|
|
31
|
+
only when speed is make-or-break, measured, and the owner decides.
|
|
32
|
+
4. **A navigation shows something at once.** Never put a whole screen inside
|
|
33
|
+
`<Suspense fallback={null}>`: the click looks dead, then the screen jumps.
|
|
34
|
+
The fallback is a drawn loading state, so where `design-sets` applies it is a
|
|
35
|
+
screen in the design set first. What every screen of a section shares (a
|
|
36
|
+
bar, a rail) lives in a layout, so it stays on screen while the screen inside
|
|
37
|
+
changes; data both the layout and the page need is loaded once per request
|
|
38
|
+
(React `cache`). Expo: the same with a shared navigator layout.
|
|
39
|
+
5. **Measure before and after.** A performance change states a number: round
|
|
40
|
+
trips per screen (count Prisma `query` events in a throwaway test), or a
|
|
41
|
+
trace of the slow interaction. A guess is not a result.
|
|
42
|
+
6. **Session reads may be cached, writes may not.** A signed session cookie
|
|
43
|
+
(Better Auth's `cookieCache`, a few minutes) spares each read a query. Every
|
|
44
|
+
write checks the session in the database, so a device signed out elsewhere
|
|
45
|
+
changes nothing. An e2e test proves it.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Pipeline
|
|
2
|
+
|
|
3
|
+
How a new app is built: five stages, in order, with hard gates. Opt in with
|
|
4
|
+
`standards: [pipeline]`.
|
|
5
|
+
|
|
6
|
+
These are hard rules. There is no "quick prototype" exception and no "I'll add
|
|
7
|
+
it later" exception.
|
|
8
|
+
|
|
9
|
+
## The stages
|
|
10
|
+
|
|
11
|
+
Each stage stops and waits for the owner's approval before the next starts.
|
|
12
|
+
|
|
13
|
+
1. **HTML designs** → **hard gate: the owner reviews them by hand.**
|
|
14
|
+
Static HTML in `.context/designs/<surface>/`, built to `design-sets`. Every
|
|
15
|
+
screen and every state the screen has gets its own file (`design-sets` §5).
|
|
16
|
+
No app project, no schema, no components until the designs exist and are
|
|
17
|
+
approved.
|
|
18
|
+
2. **Scaffold the app.** Structure only — no features. For an Expo app, see
|
|
19
|
+
`expo`. The stage does not end until every credential it produced is backed
|
|
20
|
+
up (see `secrets`).
|
|
21
|
+
3. **Plan all features**, as `features/` pages.
|
|
22
|
+
4. **Build the schema** → **hard gate: the owner reviews it.**
|
|
23
|
+
5. **Build the app.**
|
|
24
|
+
|
|
25
|
+
Present the stage, then stop. A plan approved is not code approved. Do not start
|
|
26
|
+
with "a small piece", a spike, or a test harness because it looks useful.
|
|
27
|
+
|
|
28
|
+
## Principles
|
|
29
|
+
|
|
30
|
+
These join canon's constitution when a repo opts in. Verdicts cite them by ID.
|
|
31
|
+
|
|
32
|
+
### C-16 Designs before code
|
|
33
|
+
No app code for a screen that has no approved HTML design. A component in no
|
|
34
|
+
HTML file does not ship. The wireframe is the spec; when code and wireframe
|
|
35
|
+
disagree, fix the code or change the wireframe first and say so.
|
|
36
|
+
*Source:* `pipeline`, `design-sets`.
|
|
37
|
+
|
|
38
|
+
### C-17 Stages stop at their gates
|
|
39
|
+
Designs, scaffold, plan, schema, build — in order, each waiting for approval.
|
|
40
|
+
Approval of a plan is not approval to code. No "small piece", spike or harness
|
|
41
|
+
to get ahead.
|
|
42
|
+
*Source:* `pipeline`.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Secrets
|
|
2
|
+
|
|
3
|
+
Every secret is backed up the moment it exists. Opt in with
|
|
4
|
+
`standards: [secrets]`. The principle is C-10.
|
|
5
|
+
|
|
6
|
+
Keep one offline backup folder for every credential, outside every repo. It is
|
|
7
|
+
not a repo and never will be — that is what makes it safe to hold plaintext
|
|
8
|
+
credentials. It mirrors the path layout of the repos, so restoring a file is a
|
|
9
|
+
straight `cp` back. The owner decides where it lives and writes that path in
|
|
10
|
+
the `CLAUDE.md` above the repos.
|
|
11
|
+
|
|
12
|
+
**Whenever a secret is created, rotated, or first needed, copy it into the
|
|
13
|
+
backup folder in the same run and add its line to the folder's `INVENTORY.md`.**
|
|
14
|
+
Not "later", not "once it works" — a secret that exists in only one place is
|
|
15
|
+
already a lost secret. This covers:
|
|
16
|
+
|
|
17
|
+
| Trigger | What lands in the backup |
|
|
18
|
+
|---|---|
|
|
19
|
+
| New app scaffolded | its `.env`, `eas.json`, `app.config.ts` skeleton |
|
|
20
|
+
| EAS project created | `eas env:list --format long` per environment |
|
|
21
|
+
| RevenueCat configured | the `EXPO_PUBLIC_REVENUECAT_*` keys |
|
|
22
|
+
| iOS signing set up | `.p12`, `.p8`, `.mobileprovision`, `credentials.json` |
|
|
23
|
+
| Play Console wired | `play-service-account.json` |
|
|
24
|
+
| Android signing set up | `keystore.jks` and its passwords |
|
|
25
|
+
| AdMob wired | the `*_AD_ID` values |
|
|
26
|
+
| Firebase project added | `google-services.json`, `GoogleService-Info.plist` |
|
|
27
|
+
| Any new API key or token | the `.env` it lives in |
|
|
28
|
+
|
|
29
|
+
Rules that go with it:
|
|
30
|
+
|
|
31
|
+
- `INVENTORY.md` records **key names only**. Never write a secret value into a
|
|
32
|
+
Markdown file, a commit message, or a chat reply.
|
|
33
|
+
- Never commit a real secret to a repo. `.env.example` is committed; `.env` is
|
|
34
|
+
not. If one is already tracked, untrack it — do not just add it to
|
|
35
|
+
`.gitignore` and move on.
|
|
36
|
+
- The folder is a snapshot, not a sync. Re-copy after every rotation.
|
|
37
|
+
- A secret-leak scan must not start failing because of a new secret.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Store listing
|
|
2
|
+
|
|
3
|
+
How an app's App Store and Play Store listing lives in `.context/listing/`.
|
|
4
|
+
Opt in with `standards: [store-listing]`.
|
|
5
|
+
|
|
6
|
+
`listing/` is outside the app's `assets/`, so it is never bundled into the app.
|
|
7
|
+
|
|
8
|
+
## The tree is fastlane's, verbatim
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
.context/listing/
|
|
12
|
+
├── listing.md the authored source — fenced blocks generate the .txt files
|
|
13
|
+
├── goldie.config.ts screenshot and preview capture
|
|
14
|
+
├── android/metadata/ supply metadata_path — ONLY locale folders
|
|
15
|
+
│ └── en-US/
|
|
16
|
+
│ ├── title.txt ≤50 · short_description.txt ≤80
|
|
17
|
+
│ ├── full_description.txt ≤4000 · video.txt (a YouTube URL)
|
|
18
|
+
│ ├── changelogs/<versionCode>.txt · default.txt ≤500
|
|
19
|
+
│ └── images/icon.png · featureGraphic.png
|
|
20
|
+
│ phoneScreenshots/NN-<screen>.png
|
|
21
|
+
│ sevenInchScreenshots/ · tenInchScreenshots/
|
|
22
|
+
├── ios/
|
|
23
|
+
│ ├── metadata/ deliver metadata_path — ONLY locale folders + review_information
|
|
24
|
+
│ │ ├── copyright.txt · primary_category.txt · secondary_category.txt
|
|
25
|
+
│ │ ├── app_rating_config.json the advisory keys fastlane has no .txt for
|
|
26
|
+
│ │ ├── review_information/*.txt
|
|
27
|
+
│ │ └── en-US/name.txt ≤30 · subtitle.txt ≤30 · description.txt ≤4000
|
|
28
|
+
│ │ keywords.txt ≤100 · promotional_text.txt ≤170 · release_notes.txt
|
|
29
|
+
│ │ support_url.txt · marketing_url.txt · privacy_url.txt
|
|
30
|
+
│ ├── screenshots/<locale>/ deliver screenshots_path
|
|
31
|
+
│ ├── app-previews/<locale>/ 15–30s, 886×1920 H.264
|
|
32
|
+
│ └── iap.md
|
|
33
|
+
└── out/ gitignored — the capture tool's raw output, before the mapper
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The leaf names are **not ours to choose**. `supply`, `deliver`, `screengrab`,
|
|
37
|
+
F-Droid and IzzyOnDroid all parse these exact strings — `full_description.txt`,
|
|
38
|
+
`phoneScreenshots`, `changelogs/<versionCode>.txt`. Rename one character and a
|
|
39
|
+
zero-maintenance upload becomes a custom transform kept up in every repo
|
|
40
|
+
forever.
|
|
41
|
+
|
|
42
|
+
**Each store folder is self-contained, and `metadata/` is why.** Both tools
|
|
43
|
+
treat every *directory* inside `metadata_path` as a locale — deliver's
|
|
44
|
+
`Loader.language_folders` hard-errors on any subdirectory that is not a language
|
|
45
|
+
name (`deliver/lib/deliver/loader.rb:167`), and supply's `all_languages` is
|
|
46
|
+
literally `Dir.entries(metadata_path)`. Worse, deliver runs
|
|
47
|
+
`FileUtils.mkdir_p(screenshots_path)` *before* it validates, so putting
|
|
48
|
+
`screenshots/` directly inside `ios/` would have deliver create the offending
|
|
49
|
+
folder and then abort on it, on a clean clone.
|
|
50
|
+
|
|
51
|
+
So the locale tree gets its own `metadata/` level, and everything else a store
|
|
52
|
+
needs is a sibling of it, inside that store's folder:
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
supply(metadata_path: ".context/listing/android/metadata")
|
|
56
|
+
deliver(metadata_path: ".context/listing/ios/metadata",
|
|
57
|
+
screenshots_path: ".context/listing/ios/screenshots",
|
|
58
|
+
app_previews_path: ".context/listing/ios/app-previews")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Files at `metadata/` root are fine — only directories are validated.
|
|
62
|
+
|
|
63
|
+
## Rules
|
|
64
|
+
|
|
65
|
+
1. **`listing.md` is authored; the `.txt` files are generated from it**,
|
|
66
|
+
committed, and never hand-edited.
|
|
67
|
+
2. **`listing/ios/` is canonical for Apple.** A repo-root `store.config.json` is
|
|
68
|
+
generated from it or deleted. One format for both stores, and
|
|
69
|
+
`deliver download_metadata` pulls the **live** listing down so you can diff
|
|
70
|
+
it, which EAS metadata cannot do.
|
|
71
|
+
3. **deliver infers the device from image resolution**, not folder name, so a
|
|
72
|
+
screenshot one pixel off lands on the wrong device silently.
|
|
73
|
+
4. **Screenshot file names are `NN-<screen>.png`, zero-padded.** fastlane uploads
|
|
74
|
+
in alphanumeric file name order, so `10.png` sorting before `2.png` is a real
|
|
75
|
+
bug, not a cosmetic one.
|
|
76
|
+
5. **A locale appears in `listing/*/metadata/<locale>/` and nowhere else**, and
|
|
77
|
+
adding one means adding it to *both* stores or writing down in `tasks.md` why
|
|
78
|
+
not. An app that ships three languages with an English-only listing is a gap,
|
|
79
|
+
and it has to look like one.
|
|
80
|
+
6. **Store images are exports.** `listing/*/images/` is generated from
|
|
81
|
+
`designs/brand/` (see `design-sets` §1).
|
|
82
|
+
|
|
83
|
+
## Screenshots and previews
|
|
84
|
+
|
|
85
|
+
They are captured by **goldie** (`goldie all --config
|
|
86
|
+
.context/listing/goldie.config.ts`). Its output axes are inverted from
|
|
87
|
+
fastlane's, so a mapper moves them:
|
|
88
|
+
|
|
89
|
+
| goldie output | fastlane destination |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `out/screenshots/pixel-10-pro/<locale>/` | `android/metadata/<locale>/images/phoneScreenshots/` |
|
|
92
|
+
| `out/screenshots/iphone-6.9/<locale>/` | `ios/screenshots/<locale>/` |
|
|
93
|
+
| `out/previews/iphone-6.9/<locale>/preview.mp4` | `ios/app-previews/<locale>/` |
|
|
94
|
+
|
|
95
|
+
Run it as `pnpm listing:capture`, never `goldie all` alone: goldie writes only
|
|
96
|
+
to the gitignored `out/`, and the command's last step is the mapper.
|
|
97
|
+
|
|
98
|
+
**The store art has a house style**, the same in every app: `template:
|
|
99
|
+
"showcase"`, `layout: "classic"`, `screenOnly: true`, Merriweather, the
|
|
100
|
+
`17-pro-silver` frame. Only the background is the app's own. It is written in
|
|
101
|
+
`goldie.config.ts` and again in `goldie.design.json` — the file `goldie studio`
|
|
102
|
+
saves and the CLI lays over the config. A repo check should fail when an app
|
|
103
|
+
differs from the reference repo's style or when its two files disagree.
|
|
104
|
+
|
|
105
|
+
## Release notes come from `features/`
|
|
106
|
+
|
|
107
|
+
A feature page carries `release: x.y.z` and a `## Store copy` blockquote (see
|
|
108
|
+
`context-layout`). `pnpm listing:notes` (`scripts/release-notes.mjs`, copied
|
|
109
|
+
from the reference repo) collects the blockquote from every feature with that
|
|
110
|
+
release and writes both `listing/ios/metadata/en-US/release_notes.txt` and
|
|
111
|
+
`listing/android/metadata/en-US/changelogs/<versionCode>.txt`. That makes the
|
|
112
|
+
board the *source* of the What's New text, so it earns its keep every release
|
|
113
|
+
instead of rotting the way every roadmap document does.
|
|
114
|
+
|
|
115
|
+
## Store files outside `listing/`
|
|
116
|
+
|
|
117
|
+
- `legal/declaration.yml` — the single source for privacy declarations, in
|
|
118
|
+
Apple's `NSPrivacyCollectedDataType` vocabulary. The legal pages and
|
|
119
|
+
`legal/play-data-safety.csv` are produced from it, never hand-edited.
|
|
120
|
+
- `metrics/aso/keywords.yml` (authored) and `metrics/aso/scans/<date>/ranks.csv`
|
|
121
|
+
— keyword tracking.
|
|
122
|
+
- `listing/ios/iap.md` — the in-app purchases as the store shows them.
|