mkit-cli 0.3.1__tar.gz → 0.4.0__tar.gz

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.
Files changed (88) hide show
  1. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/CHANGELOG.md +29 -0
  2. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/PKG-INFO +29 -1
  3. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/README.md +28 -0
  4. mkit_cli-0.4.0/docs/about-screen.md +228 -0
  5. mkit_cli-0.4.0/docs/app-ui.md +123 -0
  6. mkit_cli-0.4.0/docs/aso.md +199 -0
  7. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/commands.md +31 -4
  8. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/config.md +49 -2
  9. mkit_cli-0.4.0/docs/in-app-review.md +351 -0
  10. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/in-app-update.md +2 -3
  11. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/news-topics.md +1 -13
  12. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/store-art.md +4 -4
  13. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/docs/store.md +1 -2
  14. mkit_cli-0.4.0/docs/support-replies.md +77 -0
  15. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/pyproject.toml +12 -0
  16. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/__init__.py +1 -1
  17. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cli.py +7 -0
  18. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/__init__.py +1 -1
  19. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/aso.py +2 -2
  20. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/dev.py +11 -1
  21. mkit_cli-0.4.0/src/mkit/commands/guide.py +47 -0
  22. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/init.py +11 -7
  23. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/docgen.py +1 -1
  24. mkit_cli-0.4.0/src/mkit/guides.py +168 -0
  25. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/registry.py +8 -2
  26. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_core.py +27 -0
  27. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_docs.py +20 -0
  28. mkit_cli-0.4.0/tests/test_guides.py +111 -0
  29. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.gitattributes +0 -0
  30. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  31. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  32. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  33. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.github/workflows/tests.yml +0 -0
  34. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/.gitignore +0 -0
  35. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/AGENTS.md +0 -0
  36. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/CONTRIBUTING.md +0 -0
  37. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/LICENSE +0 -0
  38. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/SECURITY.md +0 -0
  39. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/scripts/gen_docs.py +0 -0
  40. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/scripts/release.py +0 -0
  41. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/art.py +0 -0
  42. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/aso.py +0 -0
  43. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/__init__.py +0 -0
  44. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/auth.py +0 -0
  45. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/fbproject.py +0 -0
  46. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/firebase.py +0 -0
  47. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/insight.py +0 -0
  48. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/money.py +0 -0
  49. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/play.py +0 -0
  50. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/cloud/store.py +0 -0
  51. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/art.py +0 -0
  52. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/doctor.py +0 -0
  53. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/firebase_cmds.py +0 -0
  54. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/insight.py +0 -0
  55. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/meta.py +0 -0
  56. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/money.py +0 -0
  57. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/release.py +0 -0
  58. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/selfcmd.py +0 -0
  59. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/commands/store.py +0 -0
  60. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/config.py +0 -0
  61. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/data/art/art.toml +0 -0
  62. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/data/art/en.json +0 -0
  63. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/data/datasafety-questions.csv +0 -0
  64. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/datasafety.py +0 -0
  65. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/device.py +0 -0
  66. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/errors.py +0 -0
  67. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/git.py +0 -0
  68. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/hooks.py +0 -0
  69. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/kinds.py +0 -0
  70. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/notify.py +0 -0
  71. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/output.py +0 -0
  72. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/proc.py +0 -0
  73. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/safety.py +0 -0
  74. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/secrets.py +0 -0
  75. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/src/mkit/toolchain.py +0 -0
  76. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/conftest.py +0 -0
  77. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/fakeplay.py +0 -0
  78. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_art.py +0 -0
  79. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_aso.py +0 -0
  80. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_datasafety.py +0 -0
  81. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_device_logs.py +0 -0
  82. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_firebase_apps.py +0 -0
  83. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_network_errors.py +0 -0
  84. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_products.py +0 -0
  85. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_release.py +0 -0
  86. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_store.py +0 -0
  87. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/tests/test_studio.py +0 -0
  88. {mkit_cli-0.3.1 → mkit_cli-0.4.0}/uv.lock +0 -0
@@ -7,6 +7,35 @@ it rather than doing the wrong thing.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.4.0 — 2026-10-23
11
+
12
+ **Guides.** `mkit guide` reads the guides every app follows — the About screen,
13
+ in-app review, in-app updates, ASO, support replies, app UI, the store folder,
14
+ store art, news notifications — filled in with your studio's details from
15
+ `[studio]`, `[about]` and `[support]` in `~/.mkit/config.toml` or an app's
16
+ `mkit.toml`. They ship inside the package, so they work offline. Anything not
17
+ set shows as ‹key› and is listed; `--raw` gives the guide as written.
18
+
19
+ - **The About screen is yours to shape.** `[about] parts` picks which of its
20
+ nine parts an app has, and every line it says has an `[about]` key with a
21
+ default.
22
+ - **Your own rules beside a guide.** A file of the same name in
23
+ `[guide] house_dir` is appended under *This studio's own rules*, and wins
24
+ where the two disagree.
25
+ - Five guides are new to this repo: about-screen, in-app-review, aso,
26
+ support-replies and app-ui.
27
+
28
+ **`--dry-run` can no longer quietly make the change.** `mkit init --dry-run`
29
+ wrote every file it was meant to preview, and twelve other write-class commands
30
+ ignored the flag the same way: `bump` committed, `share` uploaded, `firebase
31
+ sha-add` changed the Firebase project. Commands now declare whether they
32
+ support a dry run, and the CLI refuses `--dry-run` for any that do not — exit 2,
33
+ `NO_DRY_RUN` — before anything runs. `init` and `bump` preview for real. Every
34
+ publish-class command already honoured it and still does.
35
+
36
+ - `mkit commands --json` reports `dry_run` per command, so an agent can tell in
37
+ advance which commands it can rehearse.
38
+
10
39
  ## 0.3.1 — first public release
11
40
 
12
41
  mkit shipped a small studio's Android apps for a year before this; this is that
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mkit-cli
3
- Version: 0.3.1
3
+ Version: 0.4.0
4
4
  Summary: Release Android apps to Google Play from the command line: build, upload, listing, screenshots, data safety, in-app products, vitals and Firebase, in one tool built for humans and AI agents.
5
5
  Project-URL: Homepage, https://github.com/almasumdev/mkit-cli
6
6
  Project-URL: Documentation, https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md
@@ -137,6 +137,29 @@ Some things have no API and must be done in the Play Console. `mkit manual`
137
137
  lists them, so you know what is left rather than discovering it at release
138
138
  time.
139
139
 
140
+ ## Guides
141
+
142
+ Some things every app has to do, and every app that works them out alone does
143
+ differently: an About screen, asking for a rating, checking for updates, being
144
+ found on Play, answering users. mkit carries a guide for each, and fills it in
145
+ with your studio's details:
146
+
147
+ ```bash
148
+ mkit guide # the list
149
+ mkit guide about # the About screen, with your email, developer page, links
150
+ mkit guide review # when to ask for a rating, and what Play forbids around it
151
+ ```
152
+
153
+ Set your details once in `~/.mkit/config.toml` — `[studio]` for the facts,
154
+ `[about]` for which parts your About screen has and what it says — and every
155
+ guide, for every app, uses them. Anything not set shows as ‹key› and is listed.
156
+ Keep your own rules beside a guide in `[guide] house_dir`; they are appended
157
+ and win where they disagree. The keys are in
158
+ [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md#studio-details-mkit-guide).
159
+
160
+ They are written so an AI agent building the screen can follow them too:
161
+ `mkit guide about --json` returns the text.
162
+
140
163
  ## Working with AI agents
141
164
 
142
165
  mkit answers in JSON whenever it is not attached to a terminal, with one
@@ -151,6 +174,11 @@ and what an agent must never do.
151
174
  |---|---|
152
175
  | [docs/commands.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md) | every command and flag |
153
176
  | [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md) | `mkit.toml`, the store and keyword files, credentials |
177
+ | [docs/about-screen.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/about-screen.md) | the About screen: its parts, words, marks and behaviour (`mkit guide about`) |
178
+ | [docs/in-app-review.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-review.md) | asking for a rating: when, where, and the rules Play enforces (`mkit guide review`) |
179
+ | [docs/aso.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/aso.md) | being found on Play: the indexed fields, winnable keywords, the title |
180
+ | [docs/support-replies.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/support-replies.md) | answering users by email |
181
+ | [docs/app-ui.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/app-ui.md) | how an app is organised: colours, Pro, pickers, Settings |
154
182
  | [docs/store.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store.md) | the `store/` folder: what lives where, in every language |
155
183
  | [docs/store-art.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store-art.md) | screenshots and the feature graphic, built from real captures |
156
184
  | [docs/in-app-update.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-update.md) | how an app should check for updates: Play first, your policy second |
@@ -105,6 +105,29 @@ Some things have no API and must be done in the Play Console. `mkit manual`
105
105
  lists them, so you know what is left rather than discovering it at release
106
106
  time.
107
107
 
108
+ ## Guides
109
+
110
+ Some things every app has to do, and every app that works them out alone does
111
+ differently: an About screen, asking for a rating, checking for updates, being
112
+ found on Play, answering users. mkit carries a guide for each, and fills it in
113
+ with your studio's details:
114
+
115
+ ```bash
116
+ mkit guide # the list
117
+ mkit guide about # the About screen, with your email, developer page, links
118
+ mkit guide review # when to ask for a rating, and what Play forbids around it
119
+ ```
120
+
121
+ Set your details once in `~/.mkit/config.toml` — `[studio]` for the facts,
122
+ `[about]` for which parts your About screen has and what it says — and every
123
+ guide, for every app, uses them. Anything not set shows as ‹key› and is listed.
124
+ Keep your own rules beside a guide in `[guide] house_dir`; they are appended
125
+ and win where they disagree. The keys are in
126
+ [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md#studio-details-mkit-guide).
127
+
128
+ They are written so an AI agent building the screen can follow them too:
129
+ `mkit guide about --json` returns the text.
130
+
108
131
  ## Working with AI agents
109
132
 
110
133
  mkit answers in JSON whenever it is not attached to a terminal, with one
@@ -119,6 +142,11 @@ and what an agent must never do.
119
142
  |---|---|
120
143
  | [docs/commands.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/commands.md) | every command and flag |
121
144
  | [docs/config.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/config.md) | `mkit.toml`, the store and keyword files, credentials |
145
+ | [docs/about-screen.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/about-screen.md) | the About screen: its parts, words, marks and behaviour (`mkit guide about`) |
146
+ | [docs/in-app-review.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-review.md) | asking for a rating: when, where, and the rules Play enforces (`mkit guide review`) |
147
+ | [docs/aso.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/aso.md) | being found on Play: the indexed fields, winnable keywords, the title |
148
+ | [docs/support-replies.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/support-replies.md) | answering users by email |
149
+ | [docs/app-ui.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/app-ui.md) | how an app is organised: colours, Pro, pickers, Settings |
122
150
  | [docs/store.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store.md) | the `store/` folder: what lives where, in every language |
123
151
  | [docs/store-art.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/store-art.md) | screenshots and the feature graphic, built from real captures |
124
152
  | [docs/in-app-update.md](https://github.com/almasumdev/mkit-cli/blob/main/docs/in-app-update.md) | how an app should check for updates: Play first, your policy second |
@@ -0,0 +1,228 @@
1
+ # The About screen
2
+
3
+ Every app needs an About screen, and every app that solves it on its own ends
4
+ up with a different one. This guide is a spine that works: which parts, in what
5
+ order, what each says and does, and what it must never claim. **Only the theme
6
+ changes between apps** — colours, type, and the app's own row and button
7
+ components.
8
+
9
+ Read it with `mkit guide about`. Your studio's details are filled in from
10
+ `[studio]`, and which parts you use and what they say from `[about]` — in
11
+ `~/.mkit/config.toml` for every app, or an app's own `mkit.toml` to differ.
12
+ Anything you have not set shows as ‹key›.
13
+
14
+ ## Make it yours
15
+
16
+ ```toml
17
+ [studio]
18
+ name = "Acme Apps"
19
+ tagline = "Small tools, done properly."
20
+ email = "hello@acme.dev" # where users and clients write
21
+ website = "https://acme.dev"
22
+ privacy_url = "https://acme.dev/privacy"
23
+ terms_url = "https://acme.dev/terms"
24
+ play_developer_id = "1234567890123456789" # from your Play developer page URL
25
+ developer = "Ada Example"
26
+ developer_role = "Mobile app developer at Acme Apps"
27
+ portfolio = "https://ada.example"
28
+ photo = "https://github.com/ada-example.png?size=460"
29
+
30
+ [about]
31
+ # Leave out a part to drop it - its row, its words and its behaviour.
32
+ parts = ["mark", "name", "version", "what", "rating", "maker", "ask", "more_apps", "footer"]
33
+ # Every line below can be reworded; these are the defaults.
34
+ rating_heading = "Enjoying <App>?"
35
+ ask = "We design and build mobile apps. <App> is one of ours. If you have one in mind, tell us about it."
36
+ ```
37
+
38
+ `<App>` stands for the app's name, translated with the rest of the line.
39
+
40
+ ## The spine
41
+
42
+ Top to bottom, in this order. A studio leaves out what it does not want;
43
+ nothing is reordered.
44
+
45
+ | # | Part | What it holds |
46
+ |---|---|---|
47
+ | 1 | **Mark** | the app's own launcher icon, centred, nothing behind it | <!-- part:mark -->
48
+ | 2 | **Name** | the app name, the largest type on the page | <!-- part:name -->
49
+ | 3 | **Version** | `v1.2.3` — the short form, bare, no tag or box, no build number | <!-- part:version -->
50
+ | 4 | **What it is** | one sentence, centred, the app's own words; never marketing | <!-- part:what -->
51
+ | 5 | **Rating** | a heading, one line, five stars → the Play listing | <!-- part:rating -->
52
+ | 6 | **The maker** | photo, name, role → the portfolio | <!-- part:maker -->
53
+ | 7 | **The ask** | a few lines about the studio, then two actions: email, website | <!-- part:ask -->
54
+ | 8 | **More apps** | a heading, one line, one Play button → the developer page | <!-- part:more_apps -->
55
+ | 9 | **Footer** | Privacy · Terms, then `© <year> {{studio.name}}` | <!-- part:footer -->
56
+
57
+ The first four are one block, centred. The middle parts are banks, each with a
58
+ heading and left-aligned text. The footer is centred and quiet. That single
59
+ change of alignment is what tells the eye where the reading starts.
60
+
61
+ **No cards.** Not for the rating, not for the maker, not for the Play button —
62
+ even in an app whose other screens are made of cards. Groups are made by space
63
+ alone: a heading sits close to what it names and far from what it does not, and
64
+ that asymmetry is the whole of the grouping. Roughly 10dp under a heading
65
+ against 26dp under a bank; wider — about 34dp — above the first bank and below
66
+ the last, where the page changes subject rather than section. An About page is
67
+ a few short things in a column, and a container round each is how it turns into
68
+ a filing cabinet.
69
+
70
+ ## The words
71
+
72
+ English below; translate them into each of the app's locales. Every line is an
73
+ `[about]` key, so a studio rewords it once for all its apps.
74
+
75
+ | String | Key | Text |
76
+ |---|---|---|
77
+ | What it is | — | the app's own one sentence — the only line it writes itself | <!-- part:what -->
78
+ | Rating heading | `rating_heading` | `{{about.rating_heading}}` | <!-- part:rating -->
79
+ | Rating line | `rating_line` | `{{about.rating_line}}` | <!-- part:rating -->
80
+ | Maker heading | `maker_heading` | `{{about.maker_heading}}` | <!-- part:maker -->
81
+ | Maker name / role | `[studio]` | `{{studio.developer}}` / `{{studio.developer_role}}` | <!-- part:maker -->
82
+ | The ask | `ask` | `{{about.ask}}` | <!-- part:ask -->
83
+ | Email button | `email_button` | `{{about.email_button}}` | <!-- part:ask -->
84
+ | Website button | `website_button` | `{{about.website_button}}` | <!-- part:ask -->
85
+ | More heading | `more_heading` | `{{about.more_heading}}` | <!-- part:more_apps -->
86
+ | More line | `more_line` | `{{about.more_line}}` | <!-- part:more_apps -->
87
+ | Play button | — | `Google Play` | <!-- part:more_apps -->
88
+ | Footer | — | `Privacy` · `Terms` · `© <year> {{studio.name}}` | <!-- part:footer -->
89
+
90
+ The buttons **name their destination and nothing else**. The sentence saying
91
+ what is over there goes above them. A button carrying the whole explanation is
92
+ a sentence with a button drawn round it.
93
+
94
+ An app's own heading style still applies: if its section headers are uppercased
95
+ by its own component, these are too. The words are shared; the case is the
96
+ app's.
97
+
98
+ ## What it never does
99
+
100
+ - **No rating it does not have.** The stars carry no number and show nothing
101
+ back. They are a link to the listing, not a score.
102
+ - **No install counts, no review counts, no awards, no "loved by N users".**
103
+ - **No "All rights reserved."** It has carried no legal weight since the Berne
104
+ Convention. The bare `© <year> {{studio.name}}` stays, and the year is read
105
+ from the clock so the line cannot rot.
106
+ - **No list of the studio's other apps inside the app.** A hard-coded list
107
+ cannot know what shipped after the build, what was renamed or what was
108
+ pulled, and each of those becomes a false line on a page whose whole claim is
109
+ that it states facts. The Play developer page is the same information, kept
110
+ by the shop that sells it.
111
+ - **No second button saying what the first one does.** A "Rate on Google Play"
112
+ button under five stars that do exactly that asks twice.
113
+
114
+ ## The marks
115
+
116
+ One shape per job, at one weight. Take them from the app's own outline icon set.
117
+
118
+ | Where | Mark |
119
+ |---|---|
120
+ | A row that navigates | chevron pointing forward |
121
+ | Email button | envelope | <!-- part:ask -->
122
+ | Website button | globe | <!-- part:ask -->
123
+ | Google Play button | Google Play's own four-colour mark, from Google's brand resources — never redrawn | <!-- part:more_apps -->
124
+ | Rating | five hollow stars | <!-- part:rating -->
125
+
126
+ Outline weight throughout, one family throughout. A filled glyph beside a hollow
127
+ one is the fastest way to make a page look assembled from whatever was to hand.
128
+ The Play mark is the single exception to the app's palette and is **never
129
+ tinted**: it is a trademark, and an icon slot that paints its content with the
130
+ surface's colour would flatten all four of its colours into one.
131
+
132
+ ## What changes per app
133
+
134
+ A short list on purpose. Read the app's design doc, or its theme file, and take
135
+ four things from it:
136
+
137
+ 1. **Every colour** — the ground, the ink, and a warm tone for the stars.
138
+ 2. **The type scale**, and which style is a heading.
139
+ 3. **The row it already uses** for "tap to go somewhere".
140
+ 4. **The button it already uses** for a primary action.
141
+
142
+ Two rules hold whatever those are:
143
+
144
+ 1. **Reuse the app's own components.** If its settings list has a row, the
145
+ About page uses that row; a page built from new one-off widgets is how two
146
+ screens in one app stop looking related. Reuse the *component*, not a
147
+ container: a card the app has is still a card and still does not belong here.
148
+ 2. **Keep the app's colour discipline.** Where an app gives colours meanings,
149
+ this page obeys them and adds no new accent. If its palette has no gold for
150
+ the stars, take the nearest warm tone it already has.
151
+
152
+ ## The two assets that fool you
153
+
154
+ **The app icon.** An adaptive icon is drawn on a canvas bigger than the mark on
155
+ it — the safe-zone margin a launcher masks away. Drawn plainly it arrives with a
156
+ ring of transparent air that reads as a hole, and no spacer around it fixes that,
157
+ because the air is inside the picture. Measure the mark in the vector's own path
158
+ data, then draw the canvas at `mark_size × viewport / mark_height` inside a box
159
+ the size of the mark, and let the margin overflow. A mark running 21→87 of a 108
160
+ viewport, wanted at 72dp, needs a 118dp canvas. In Compose that overflow needs
161
+ `requiredSize`, not `size`, or the box's constraints shrink it back.
162
+
163
+ **No tile behind it** — not the icon's own background layer, not a rounded plate.
164
+ A filled tile is a card. That leaves two layers, and which one is a contrast
165
+ question, not a matter of taste:
166
+
167
+ 1. **The foreground alone**, where its artwork carries its own colour and that
168
+ colour reads on both of the app's grounds.
169
+ 2. **The monochrome layer, tinted with the page's ink**, where the foreground
170
+ was drawn for the icon's own background and disappears without it — a
171
+ near-black foreground on a dark theme is the same colour twice. The
172
+ monochrome layer is that shape with the colour taken out, so it takes the
173
+ page's ink and reads on both themes.
174
+
175
+ Check the foreground against the app's darkest **and** lightest surface before
176
+ choosing. Both layers carry the same margin, so size either off the mark.
177
+
178
+ **The photo.** One square image, centre-cropped to a circle, never letterboxed
179
+ into it. 256px is enough for a 46–52dp avatar. Take it from `{{studio.photo}}`
180
+ and store it density-independent (`drawable-nodpi` on Android, a single asset in
181
+ Flutter).
182
+
183
+ ## Behaviour
184
+
185
+ | Control | Does |
186
+ |---|---|
187
+ | Any star | the app's own Play listing: `market://details?id=<package>`, then `https://play.google.com/store/apps/details?id=<package>` | <!-- part:rating -->
188
+ | Maker row | `{{studio.portfolio}}` | <!-- part:maker -->
189
+ | Email button | `mailto:{{studio.email}}`, with a subject naming the app | <!-- part:ask -->
190
+ | Website button | `{{studio.website}}` | <!-- part:ask -->
191
+ | Google Play button | `market://dev?id={{studio.play_developer_id}}`, then `https://play.google.com/store/apps/dev?id={{studio.play_developer_id}}` | <!-- part:more_apps -->
192
+ | Privacy, Terms | `{{studio.privacy_url}}`, `{{studio.terms_url}}` | <!-- part:footer -->
193
+
194
+ Every one of these hands the user to something the app does not own, so every
195
+ one can fail: no mail app, no browser, no Play. Fail in silence — there is no
196
+ second way to do any of them, and a toast saying the phone has no email client
197
+ helps nobody.
198
+
199
+ For when a rating is *asked for* rather than offered here — the prompt at a
200
+ good moment, and what happens after a low score — see `mkit guide review`.
201
+
202
+ ## Strings
203
+
204
+ Every line except the studio's own names is translated into all of the app's
205
+ locales in the same change that adds it. An About page that is half English is
206
+ worse than one that is plainly smaller. These stay untranslated
207
+ (`translatable="false"` on Android): the developer's name, the studio name, the
208
+ website's address, and `Google Play` — a product name Google requires to be left
209
+ in English.
210
+
211
+ ## In Flutter
212
+
213
+ The same spine maps onto the app's own widgets: `package_info_plus` for the
214
+ version, `url_launcher` for every row in the behaviour table, the launcher icon
215
+ from the app's own asset, and the app's existing section header, list row and
216
+ button widgets for everything below the centred block.
217
+
218
+ ## Checklist for a new app
219
+
220
+ 1. Set `[studio]` and `[about]` once; check them with `mkit guide about`.
221
+ 2. Read the app's design doc and take the four things above from it.
222
+ 3. Build the spine with that app's components — no new widgets unless the app
223
+ genuinely lacks one, and no cards.
224
+ 4. App icon sized off its own artwork; photo cropped to a circle.
225
+ 5. All strings in every locale the app ships.
226
+ 6. Wire the behaviours; let each fail silently.
227
+ 7. Check it in the app's light and dark themes, and in its right-to-left locale
228
+ if it has one.
@@ -0,0 +1,123 @@
1
+ # App UI foundations
2
+
3
+ This decides how an app is organised, never how it looks: where colours
4
+ live, how Pro is marked, what a picker does, how Settings is put together. No
5
+ colour value (except Pro's gold) and no size of the app's look is set here;
6
+ each app keeps its own palette, type, shapes and components. A studio whose
7
+ apps share one look keeps that look in its own rules beside this guide.
8
+
9
+ Read this before adding a colour, a Pro mark, a settings row or a picker to
10
+ any app. Read it with `mkit guide ui`.
11
+
12
+ ## Colours from one place
13
+
14
+ Every colour an app draws is defined in its theme folder and nowhere else.
15
+
16
+ | Kind | Where |
17
+ |---|---|
18
+ | Follows the theme and palette | the palette file: one complete set per palette, light and dark |
19
+ | Never changes with the theme | `fixed_colors.dart` (or the native app's equivalent) |
20
+ | The framework's own (Material `ColorScheme`, Compose `ColorScheme`) | mapped from the palette set by the theme builder |
21
+
22
+ Widgets read colours through the app's token accessor (a context extension in
23
+ Flutter, a `CompositionLocal` in Compose) and the framework's widgets get them
24
+ through the colour scheme the builder maps them into. A widget never writes a
25
+ colour literal: in Flutter no `Color(0x…)`, `Color.fromRGBO`, `Color.fromARGB`
26
+ or `Colors.<name>` (only `Colors.transparent`); in Compose no `Color(0x…)`.
27
+
28
+ **The rule is a test, not a convention.** Each app carries one that walks its
29
+ source, skips the theme folder, generated files and comment lines, and fails
30
+ on any colour literal, naming the file and line. Write it once and copy it to
31
+ each app, changing only the folder name.
32
+
33
+ **One complete set per palette.** Every palette, in light and in dark, is one
34
+ full colour set: the ground, the inks from strongest to faintest, the card,
35
+ the lines, the primary fill and the ink on it, the icon tile and its glyph,
36
+ the accent and its soft wash, danger, the warning colour, and whatever else
37
+ the app draws with (a receipt's paper, a panel's gradient). The theme builder
38
+ and the token class only map that set; neither writes a colour of its own. An
39
+ app with one palette has one set per brightness, and the same rule.
40
+
41
+ **Two inks for the primary colour.** A primary colour dark enough to carry
42
+ white text as a button is often too dark to read as text on a dark ground.
43
+ So a set carries an ink for the primary colour used as text or an icon (a
44
+ price, a check, a cursor, a focused field, a text button), and one for the
45
+ glyph on an icon tile, separate from the fill. Fills (buttons, switches, a
46
+ selected dot) keep the primary. Where the two are the same colour, the set
47
+ simply says so twice.
48
+
49
+ What goes in `fixed_colors.dart`, and nothing else: the brand (a mascot, the
50
+ intro slides), Pro's gold, other companies' marks in their own colours,
51
+ shadows and scrims, and colours fixed by what they sit on (white on a red
52
+ swipe, ink on a photo). A colour that ought to change with the palette is in
53
+ the palette set, even if today it happens to be the same in every palette.
54
+
55
+ ## Pro's mark
56
+
57
+ Pro is the studio's product, so its mark is the same in every app: **one
58
+ crown, drawn, in one gold.**
59
+
60
+ - **The shape** is one crown, drawn in code on a 24-unit
61
+ grid, never an icon font's crown (`workspace_premium` and the like): three
62
+ points with round tips over a band, solid, one colour, the jewel and the gap
63
+ above the band cut through it (even-odd).
64
+
65
+ ```text
66
+ body (even-odd): M3.6 8.2 7.7 11.7 12 5.4 16.3 11.7 20.4 8.2 18.7 16.2H5.3Z
67
+ M12 11.2 13.6 13 12 14.8 10.4 13Z
68
+ tips: circles r1.6 at (3.6, 6.9) and (20.4, 6.9), r1.7 at (12, 3.9)
69
+ band: rounded rect 4.8,18 → 19.2,20.8, corner 1.4
70
+ ```
71
+
72
+ One component per app draws it: an icon widget in Flutter, a composable in
73
+ Compose.
74
+ - **The colour** is Pro gold — this guide uses `#E3A93C` — a fixed colour, the same in light
75
+ and dark and in every palette: a mark of the product that changed with the
76
+ theme would read as two marks.
77
+ - **The tag** beside anything the free plan lacks is an outline, never a
78
+ fill: the crown and the word `PRO` in gold inside a gold-outlined pill, in
79
+ the app's own type. Shown only while Pro is not owned.
80
+ - **Where the crown stands alone:** a locked colour theme's swatch, a locked
81
+ action's icon, the Pro screen's header, in gold. Inside a filled button it
82
+ takes the button's own ink instead. On a row whose other rows have icon
83
+ tiles, it sits on the row's usual tile in the tile's glyph colour, so it
84
+ lines up with the rest.
85
+
86
+ ## Settings
87
+
88
+ - **Groups under section headers.** Rows that belong together form a group;
89
+ groups sit under short section headers in the app's own style.
90
+ - **The Pro row** comes first, on its own, with the crown as its icon.
91
+ - **The rating, share and feedback rows** are those of `mkit guide review`;
92
+ the update row and the version line at the very foot are those of
93
+ `mkit guide update`.
94
+ - **An app with colour themes** puts the light/dark choice and the colour
95
+ choice together, one after the other in the same place. A locked colour can
96
+ be tried on before buying; leaving Settings ends the try-on.
97
+
98
+ ## Picker sheets
99
+
100
+ A choice from a list (a language, a currency, a list to move to) is a
101
+ **bottom sheet**; a question with a few answers is a dialog
102
+ (`mkit guide review`).
103
+
104
+ - **One frame** for every sheet in the app: its handle, its title, its body.
105
+ One component, not one per sheet.
106
+ - **A long list has search.** Any picker with more than about a dozen
107
+ choices (languages, currencies) opens with a search field and stays clear of
108
+ the keyboard. Matching: a language by its own name, its English name or a
109
+ code prefix; a currency by code, sign, name or country. Nothing found is one
110
+ plain line saying so, not an empty sheet.
111
+ - **The chosen row is marked one way in every picker** of the app, in its own
112
+ colours (a wash, a check, a bolder title: whichever the app chose, the same
113
+ everywhere). Two pickers that mark it two ways is the bug.
114
+ - Tapping a row applies it and closes the sheet; there is no Done button.
115
+
116
+ ## Checklist, every app
117
+
118
+ 1. No colour literal outside the theme folder, and the test that proves it.
119
+ 2. One complete colour set per palette and brightness; the builder only maps.
120
+ 3. Pro gold in `fixed_colors.dart`, the drawn crown everywhere Pro is marked,
121
+ no icon-font crown left, and the Pro tag an outline.
122
+ 4. Language and currency pickers search, and every picker marks the choice
123
+ the same way.