trunative 1.0.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/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/cli.js +105 -0
- package/dist/commands/build.js +35 -0
- package/dist/commands/detect.js +91 -0
- package/dist/commands/doctor.js +110 -0
- package/dist/commands/graph.js +106 -0
- package/dist/commands/install.js +76 -0
- package/dist/commands/lint.js +87 -0
- package/dist/commands/rubric.js +104 -0
- package/dist/commands/spec.js +300 -0
- package/dist/compile.js +235 -0
- package/dist/detect/rules.js +208 -0
- package/dist/detect/types.js +36 -0
- package/dist/emit.js +77 -0
- package/dist/graph.js +288 -0
- package/dist/heuristics.js +185 -0
- package/dist/lock.js +18 -0
- package/dist/mdx.js +84 -0
- package/dist/paths.js +62 -0
- package/dist/skill.js +69 -0
- package/package.json +51 -0
- package/src/skills/SKILL.md +125 -0
- package/src/skills/flow/build.md +57 -0
- package/src/skills/flow/firebase.md +102 -0
- package/src/skills/flow/init.md +116 -0
- package/src/skills/flow/review.md +186 -0
- package/src/skills/flow/spec.md +149 -0
- package/src/skills/heuristics/accessibility.md +124 -0
- package/src/skills/heuristics/ads.md +140 -0
- package/src/skills/heuristics/auth.md +130 -0
- package/src/skills/heuristics/background-work.md +129 -0
- package/src/skills/heuristics/buttons.md +99 -0
- package/src/skills/heuristics/camera.md +127 -0
- package/src/skills/heuristics/chat.md +125 -0
- package/src/skills/heuristics/colors.md +129 -0
- package/src/skills/heuristics/copy.md +157 -0
- package/src/skills/heuristics/data-display.md +124 -0
- package/src/skills/heuristics/feedback.md +122 -0
- package/src/skills/heuristics/forms.md +124 -0
- package/src/skills/heuristics/icons-and-imagery.md +135 -0
- package/src/skills/heuristics/layout.md +125 -0
- package/src/skills/heuristics/lists.md +129 -0
- package/src/skills/heuristics/localization.md +128 -0
- package/src/skills/heuristics/maps.md +129 -0
- package/src/skills/heuristics/media.md +130 -0
- package/src/skills/heuristics/motion.md +113 -0
- package/src/skills/heuristics/navigation.md +116 -0
- package/src/skills/heuristics/network.md +118 -0
- package/src/skills/heuristics/notifications.md +121 -0
- package/src/skills/heuristics/offline.md +124 -0
- package/src/skills/heuristics/onboarding.md +103 -0
- package/src/skills/heuristics/payments.md +138 -0
- package/src/skills/heuristics/performance.md +111 -0
- package/src/skills/heuristics/permissions.md +125 -0
- package/src/skills/heuristics/privacy-ui.md +112 -0
- package/src/skills/heuristics/scrolling.md +114 -0
- package/src/skills/heuristics/search.md +127 -0
- package/src/skills/heuristics/sense.md +128 -0
- package/src/skills/heuristics/settings.md +129 -0
- package/src/skills/heuristics/sharing.md +102 -0
- package/src/skills/heuristics/sound.md +95 -0
- package/src/skills/heuristics/splashscreen.md +111 -0
- package/src/skills/heuristics/states.md +120 -0
- package/src/skills/heuristics/touch.md +95 -0
- package/src/skills/heuristics/typography.md +99 -0
- package/src/skills/heuristics/updates.md +129 -0
- package/src/skills/heuristics/webviews.md +114 -0
- package/src/skills/heuristics/widgets.md +128 -0
- package/src/skills/references/capability-checks.md +59 -0
- package/src/skills/references/fonts.json +339 -0
- package/src/skills/references/icon-and-image-assets.md +103 -0
- package/src/skills/references/input-fields.md +82 -0
- package/src/skills/references/launch-surface.md +94 -0
- package/src/skills/references/motion-tokens.md +89 -0
- package/src/skills/references/navigation-containers.md +51 -0
- package/src/skills/references/search-controls.md +49 -0
- package/src/skills/references/type-scales.md +60 -0
- package/src/skills/references/wireframe-frame.md +209 -0
package/dist/skill.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, relative, sep } from 'node:path';
|
|
4
|
+
export const FALLBACK_SKILL_NAME = 'trunative';
|
|
5
|
+
/**
|
|
6
|
+
* Files starting with a dot are repository plumbing (.gitkeep and friends).
|
|
7
|
+
* Skipping them keeps the packaged hash equal to the installed hash.
|
|
8
|
+
*/
|
|
9
|
+
export function isSkillFile(name) {
|
|
10
|
+
return !name.startsWith('.');
|
|
11
|
+
}
|
|
12
|
+
async function listFiles(root) {
|
|
13
|
+
const found = [];
|
|
14
|
+
async function walk(dir) {
|
|
15
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (!isSkillFile(entry.name)) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const full = join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
await walk(full);
|
|
23
|
+
}
|
|
24
|
+
else if (entry.isFile()) {
|
|
25
|
+
found.push(full);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
await walk(root);
|
|
30
|
+
return found;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Content hash of a set of files. Stable across machines: paths are normalized
|
|
34
|
+
* to forward slashes and sorted before hashing.
|
|
35
|
+
*/
|
|
36
|
+
export function hashFiles(files) {
|
|
37
|
+
const hash = createHash('sha256');
|
|
38
|
+
for (const path of [...files.keys()].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))) {
|
|
39
|
+
hash.update(path);
|
|
40
|
+
hash.update('\0');
|
|
41
|
+
hash.update(files.get(path));
|
|
42
|
+
hash.update('\0');
|
|
43
|
+
}
|
|
44
|
+
return `sha256:${hash.digest('hex')}`;
|
|
45
|
+
}
|
|
46
|
+
/** The same hash, over a directory on disk. */
|
|
47
|
+
export async function hashSkill(root) {
|
|
48
|
+
const files = new Map();
|
|
49
|
+
for (const file of await listFiles(root)) {
|
|
50
|
+
files.set(relative(root, file).split(sep).join('/'), await readFile(file));
|
|
51
|
+
}
|
|
52
|
+
return hashFiles(files);
|
|
53
|
+
}
|
|
54
|
+
/** Reads the "name" field from the SKILL.md frontmatter. */
|
|
55
|
+
export async function readSkillName(skillDir) {
|
|
56
|
+
let source;
|
|
57
|
+
try {
|
|
58
|
+
source = await readFile(join(skillDir, 'SKILL.md'), 'utf8');
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return FALLBACK_SKILL_NAME;
|
|
62
|
+
}
|
|
63
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(source);
|
|
64
|
+
if (!match) {
|
|
65
|
+
return FALLBACK_SKILL_NAME;
|
|
66
|
+
}
|
|
67
|
+
const name = /^name:\s*(.+)$/m.exec(match[1]);
|
|
68
|
+
return name ? name[1].trim().replace(/^["']|["']$/g, '') : FALLBACK_SKILL_NAME;
|
|
69
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trunative",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A mobile-only design skill for AI coding agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Tiago Danin",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/TiagoDanin/Trunative.git"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"bin": {
|
|
13
|
+
"trunative": "dist/cli.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"skill",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"mobile",
|
|
20
|
+
"design",
|
|
21
|
+
"ui",
|
|
22
|
+
"react-native",
|
|
23
|
+
"expo",
|
|
24
|
+
"flutter",
|
|
25
|
+
"swiftui",
|
|
26
|
+
"jetpack-compose"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://github.com/TiagoDanin/Trunative#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/TiagoDanin/Trunative/issues"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/**/*.js",
|
|
34
|
+
"src/skills"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc && node dist/cli.js build",
|
|
41
|
+
"typecheck": "tsc --noEmit",
|
|
42
|
+
"lint": "tsx src/cli.ts lint",
|
|
43
|
+
"graph": "tsx src/cli.ts graph",
|
|
44
|
+
"prepublishOnly": "yarn build"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^22.10.2",
|
|
48
|
+
"tsx": "^4.19.2",
|
|
49
|
+
"typescript": "^5.7.2"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trunative
|
|
3
|
+
description: Design and review mobile app UI for any stack (React Native, Expo, Flutter, SwiftUI, Jetpack Compose, mobile web). Use when building a screen, component, navigation flow, or form for a phone, or when reviewing existing mobile UI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Trunative
|
|
7
|
+
|
|
8
|
+
Mobile-only design. This file routes, it does not hold the content: the rules live in `heuristics/`, the procedures in `flow/`, the lookup material in `references/`.
|
|
9
|
+
|
|
10
|
+
A phone is not a small desktop. Input is imprecise and one-handed, the session gets interrupted, the network drops, the OS owns gestures and insets and permissions, text scales to whatever the user set, and the battery is finite. Design for that, not for a narrow viewport.
|
|
11
|
+
|
|
12
|
+
## Flow
|
|
13
|
+
|
|
14
|
+
Follow it in order. Never skip init, never end on build.
|
|
15
|
+
|
|
16
|
+
<Index of="flow" />
|
|
17
|
+
|
|
18
|
+
1. **init**, read `flow/init.md`. Once per project, and again whenever `npx trunative doctor` fails.
|
|
19
|
+
2. **spec**, read `flow/spec.md`. Whenever the task establishes or changes hierarchy, actions, states or navigation, which a new screen, a new component with state of its own, or a new flow always does. It writes the screen brief, renders it as a greyscale wireframe, and asks the user once, before any code exists. Skipping it is a verdict written in one line, never a default: only a cosmetic or local change goes straight to build.
|
|
20
|
+
3. **build**, read `flow/build.md`. One screen, one component, or one flow at a time.
|
|
21
|
+
4. **review**, read `flow/review.md`. Runs on the code that was just written, and again on request over a finished screen. It scores every rule in scope from 1 to 5, and a 1 or a 2 is a violation.
|
|
22
|
+
5. **loop**, back to build while review reports violations. Stop when review comes back clean.
|
|
23
|
+
|
|
24
|
+
Loaded by build, not a step: `flow/firebase.md`, whenever the screen touches Firebase.
|
|
25
|
+
|
|
26
|
+
## Heuristics
|
|
27
|
+
|
|
28
|
+
One concern per file, in two tiers. The tier decides when a file is opened, not how much it counts: a rule in an extra file binds exactly like a rule in a base file once the screen touches it.
|
|
29
|
+
|
|
30
|
+
### Base
|
|
31
|
+
|
|
32
|
+
Opened on every screen, before writing. Every screen has colour, text, targets, a layout, states, at least one action, words, something that moves, and something drawn, so nobody gets to decide a screen does not touch these.
|
|
33
|
+
|
|
34
|
+
<Index of="base" />
|
|
35
|
+
|
|
36
|
+
| File | Prefix | Covers |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `heuristics/colors.md` | `color-` | palette roles and tokens, the default palette, ramps, the accent, colour that varies per item, gradients, dark theme, contrast, color as state |
|
|
39
|
+
| `heuristics/typography.md` | `type-` | the platform type scale, roles per screen, weight and its distribution, typeface choice, measure, text scaling, real strings |
|
|
40
|
+
| `heuristics/touch.md` | `touch-` | hit areas and spacing, thumb reach, where destructive actions go, press feedback, gestures and system edges, the keyboard as layout |
|
|
41
|
+
| `heuristics/buttons.md` | `button-` | one primary per screen, the emphasis ladder, labels, button states, the FAB, chips, tabs and segmented controls |
|
|
42
|
+
| `heuristics/layout.md` | `layout-` | insets and safe areas, the spacing scale, grouping and density, radius and shape, one column, the width range, fixed chrome and overlays, the first screenful, orientation |
|
|
43
|
+
| `heuristics/states.md` | `state-` | the full state set, loading and skeletons, the three empties, error classes and retry, offline and queued work, stale and partial data, permission, interruption |
|
|
44
|
+
| `heuristics/motion.md` | `motion-` | what earns an animation, the platform's own transitions, springs against durations, choreography, loops, motion that blocks input, cheap properties, reduced motion |
|
|
45
|
+
| `heuristics/accessibility.md` | `a11y-` | names, roles and values, hidden decoration, focus order, announcements, focus containment, gesture alternatives, alternative input, the system settings, media, how to test it |
|
|
46
|
+
| `heuristics/copy.md` | `copy-` | the word budget, the first word, voice, error wording, jargon, one term per thing, capitalisation, what absence says, sample content and its arithmetic, numbers, figures that describe the product, rationale text |
|
|
47
|
+
| `heuristics/icons-and-imagery.md` | `icon-` | one icon set, icon weight, emoji standing in for an icon, vector and density, reserving space, cropping, artwork that depicts nothing, dark variants, avatars, the app icon |
|
|
48
|
+
|
|
49
|
+
### Extra
|
|
50
|
+
|
|
51
|
+
Opened when the screen touches the concern, and left closed otherwise. The list is what exists, not what to read.
|
|
52
|
+
|
|
53
|
+
Whether it is touched is settled by a pass over this table, not by recall. Say in one line what is in front of you, as the thing it is rather than as the feature it belongs to, then read the Covers column against that line, row by row. A row whose words are on the screen gets opened. A row stays closed only when you can name what this screen does not have that the row is about, and "it does not seem relevant" is not that sentence.
|
|
54
|
+
|
|
55
|
+
The pass runs before the first line of code. A file opened afterwards reviews the screen instead of shaping it, and the rule it carries has become a rewrite rather than a decision.
|
|
56
|
+
|
|
57
|
+
It fails in two quiet ways. Judging by the feature closes a file that owns something the screen plainly shows, because the feature has a name and the screen has content. Judging by how simple the screen looks closes the file whose rules that screen is about to break, since a concern a screen does not advertise is precisely the one nobody designed for.
|
|
58
|
+
|
|
59
|
+
<Index of="extra" />
|
|
60
|
+
|
|
61
|
+
| File | Prefix | Covers |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| `heuristics/navigation.md` | `nav-` | how deep the hierarchy goes, choosing between screen, tab, modal and sheet, back and up, deep links, per-destination stacks, search, state after interruption |
|
|
64
|
+
| `heuristics/lists.md` | `list-` | virtualisation, row density and the row as a target, separators, swipe actions, images, sections, the end of the list, refresh, selection |
|
|
65
|
+
| `heuristics/forms.md` | `form-` | one column, field count, persistent labels, input type and autofill, when to validate, error recovery, what survives backgrounding, submit |
|
|
66
|
+
| `heuristics/chat.md` | `chat-` | the transcript's anchor and where it opens, paging history upward, arrivals while reading, grouping and time, the row, the empty conversation, the composer's ceiling, the state of one message, attachments, presence, announcing an arrival |
|
|
67
|
+
| `heuristics/permissions.md` | `perm-` | the inventory, asking for less, scope, the rationale before the prompt, purpose strings, the three answers to a prompt, re-checking, coercion, tracking |
|
|
68
|
+
| `heuristics/onboarding.md` | `onboard-` | the branded frame, how many screens, explaining in place, what to defer, the order of the asks, looking before signing up, account obligations, the first real action, resuming |
|
|
69
|
+
| `heuristics/localization.md` | `l10n-` | no hardcoded strings, direction and what never mirrors, text expansion, locale formats, plurals, script coverage, personal data shapes, collation, per-app language, pseudolocalisation |
|
|
70
|
+
| `heuristics/notifications.md` | `notify-` | what earns an interruption, channels and categories, interruption level, quiet delivery, the lock screen, destination, actions, the shade, badges, the in-app equivalent |
|
|
71
|
+
| `heuristics/widgets.md` | `widget-` | the update budget, stating what is stale, fitting a size with no scroll, per-size authoring, the tap as a deep link, signed-out and empty and error, the stranger reading it, staying on the app's own content, labels on every presentation, the Dynamic Island, the final frame, Android promotion |
|
|
72
|
+
| `heuristics/sense.md` | `sense-` | a capability past the grant: absent hardware, switched off above the app, running, imprecise, failing, plus preview, biometrics, haptics and motion |
|
|
73
|
+
| `heuristics/camera.md` | `cam-` | handing off to the system capture screen, the shutter and its ground, torch, lens stops and zoom, the frame that matches what is analysed, a scan resolving by itself, review and retake, a capture the app rejects, orientation and mirroring, size and destination, the limited photo grant, thermal pressure |
|
|
74
|
+
| `heuristics/network.md` | `net-` | timeouts, backoff, cancellation, deduplication, fan-out, payload weight, metered connections, reachability, prefetch, uploads |
|
|
75
|
+
| `heuristics/offline.md` | `off-` | local first, freshness marks, cache policy, reclaimable storage, write modes, the queue, destructive work offline, conflict, the empty cache |
|
|
76
|
+
| `heuristics/splashscreen.md` | `splash-` | the system launch surface, the double splash, what it may contain, matching the first frame, fake progress, what may hold it, appearance, the entry it hands over to |
|
|
77
|
+
| `heuristics/performance.md` | `perf-` | cold start, the main thread, the frame budget, image decoding, memory, power, app size, and measuring instead of guessing |
|
|
78
|
+
| `heuristics/feedback.md` | `fb-` | the vehicle ladder, silent success, when a dialog is justified, undo, where a message lands, duration, reach, queueing, surviving rotation, the review prompt |
|
|
79
|
+
| `heuristics/search.md` | `search-` | the two search surfaces, the stock control, placement per platform, typing and suggestions, recents, scope, filters, the result row, zero results, coming back |
|
|
80
|
+
| `heuristics/auth.md` | `auth-` | the methods and their order, provider buttons, web flows, last used, code screens, biometrics over a session, expiry, re-auth, the active account, sign out, deletion |
|
|
81
|
+
| `heuristics/webviews.md` | `webview-` | which surface a URL opens in, somebody else's credential field, the wrapper's own chrome, back inside the page, links that leave it, theme and text size reaching content nobody can restyle, insets and the keyboard, downloads and file pickers, the session the app cannot read, the wrapped site |
|
|
82
|
+
| `heuristics/settings.md` | `set-` | a better default before a switch, settings in context, what the system owns, shape and status, controls, effect, what syncs, destructive rows, search, the account exit, diagnostics |
|
|
83
|
+
| `heuristics/media.md` | `media-` | the system player, controls and scrubbing, unasked sound, audio focus, becoming noisy, background audio, remote controls, picture in picture, fullscreen, keeping awake, quality, live |
|
|
84
|
+
| `heuristics/maps.md` | `map-` | the first camera, who owns the drag, markers as targets, clustering, the equivalent list, following the user, legibility over tiles nobody chose, routes as text, tiles that did not arrive, the cost of a live map, attribution, the provider's contract |
|
|
85
|
+
| `heuristics/background-work.md` | `bg-` | what may run at all, now or later, periodic work, visible and stoppable, the foreground service last, declared types, location, durability, exact time, push wake-ups, restriction, exemption, failing while away |
|
|
86
|
+
| `heuristics/privacy-ui.md` | `priv-` | the stranger beside the user, masked values, the app switcher snapshot, blocking capture and merely detecting it, the second gate, what gets instrumented, deleting data, the declaration matching the code |
|
|
87
|
+
| `heuristics/sharing.md` | `share-` | the system sheet and nothing hand-rolled, the payload and its preview, a link rather than a screenshot, readiness, file access, the outcome, what the app accepts and how it arrives, clipboard and paste, invites |
|
|
88
|
+
| `heuristics/updates.md` | `upd-` | the test for blocking at all, minimum version, the gate screen, prompt shape, flexible install, restart state, migration running once and its path, never wiping, carrying work over, what changed, the store channel |
|
|
89
|
+
| `heuristics/scrolling.md` | `scroll-` | nesting on one axis, the affordance that says there is more, collapsing chrome, anchoring, restoration, return to top, programmatic scrolls, overscroll, scrolling with the keyboard up |
|
|
90
|
+
| `heuristics/data-display.md` | `data-` | precision that does not lie, the shape of a table that does not fit, when a chart earns its place, scale and reach, relative against absolute time, units, entering a date, empty against null against zero |
|
|
91
|
+
| `heuristics/sound.md` | `sound-` | the inventory, being silenced by the switch and the ringer, mixing with other audio, system sounds, never sound alone, unasked sound, the off switch |
|
|
92
|
+
| `heuristics/payments.md` | `pay-` | which rail the item legally takes, linking out by storefront, the wallet first, the system sheet, the total, where the price comes from, subscription terms, cancelling, restore, card entry, leaving and returning, pending and idempotency, the honest paywall |
|
|
93
|
+
| `heuristics/ads.md` | `ads-` | labelled as advertising, the close control, placement, frequency, reserved space, adjacency to a real action, rewarded and consent flows, reporting, accessibility, the paid-to-remove entitlement, cost, children |
|
|
94
|
+
|
|
95
|
+
<Index of="references" />
|
|
96
|
+
|
|
97
|
+
Lookup material, read on demand for one value and never as background: `references/type-scales.md`, `references/fonts.json`, `references/input-fields.md`, `references/navigation-containers.md`, `references/motion-tokens.md`, `references/capability-checks.md`, `references/launch-surface.md`, `references/icon-and-image-assets.md`, `references/search-controls.md`, `references/wireframe-frame.md`.
|
|
98
|
+
|
|
99
|
+
## Always in scope
|
|
100
|
+
|
|
101
|
+
Base decides what is read on every screen. These nine decide what is scored on every review, whatever the screen is for, and `n/a` is not available for them. They are the ones a screen ships without, because nobody decided it touched that file:
|
|
102
|
+
|
|
103
|
+
<Index of="always" />
|
|
104
|
+
|
|
105
|
+
- `type-scale`, `type-scaling`: text arrives through a style with no literal size, and the screen still holds at the largest accessibility step.
|
|
106
|
+
- `layout-insets`: the safe area read at runtime, on all four edges.
|
|
107
|
+
- `touch-floor`, `touch-feedback`: the hit area meets the platform floor, and the press answers under the finger.
|
|
108
|
+
- `color-contrast`, `color-dark-composed`: contrast measured, and both appearances actually built.
|
|
109
|
+
- `a11y-name`: every control carries a name, a role and a value.
|
|
110
|
+
- `motion-reduced`: anything that animates reads the system setting first.
|
|
111
|
+
|
|
112
|
+
A splash screen, a settings list and a chart all answer these. Open the file that owns one when the answer is not obvious, and leave the rest of the folder closed.
|
|
113
|
+
|
|
114
|
+
## Loading rules
|
|
115
|
+
|
|
116
|
+
- Open every base file, then every extra file this screen touches, settled by the pass over the Extra table rather than from memory of what the folder holds. Reading all of it wastes the context the code needs, and closing a file because the screen looked simple wastes the review.
|
|
117
|
+
- Read `references/` on demand, for one specific number or API. Never as background.
|
|
118
|
+
- This copy was built for one agent and, when its name says so, for one stack. It is not the file that was written: the branches for other harnesses and other frameworks were resolved away at build time. Never hand-edit it, and never reason about what a branch might have said.
|
|
119
|
+
- The project briefs override nothing in `heuristics/`, but they decide which rules apply and record the exceptions accepted on purpose. `PRODUCT.md` is who uses this and for what, `DESIGN.md` is the visual identity in the [design.md format](https://github.com/google-labs-code/design.md), and `STACK.md` is this codebase: primitives, navigation, components, exceptions. A fourth brief is per screen rather than per project: `.trunative/screens/<name>.md` holds the structure `flow/spec.md` settled, and it records intent rather than granting an exception.
|
|
120
|
+
|
|
121
|
+
## Non-negotiable
|
|
122
|
+
|
|
123
|
+
- Mobile only. There is no desktop breakpoint to defer a decision to.
|
|
124
|
+
- A rule that was not checked is a violation, not a pass.
|
|
125
|
+
- Never hand-edit the installed copy of this skill. Change it in the repository and run `npx trunative install`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Build
|
|
2
|
+
|
|
3
|
+
Writes or changes one screen, one component, or one flow. Never runs before init passes.
|
|
4
|
+
|
|
5
|
+
## 1. Frame the screen
|
|
6
|
+
|
|
7
|
+
Before anything else, write one line saying whether this task establishes or changes hierarchy, actions, states or navigation. If it touches any of the four, stop here and run `flow/spec.md`: a new screen, a new component with state of its own, or a new flow touches all four by definition, and arriving at this file with one of those in hand means the step was skipped rather than judged.
|
|
8
|
+
|
|
9
|
+
That line is written, not recalled. Unwritten, the answer is always the same one, because code is what this step is for and the pull is toward starting it.
|
|
10
|
+
|
|
11
|
+
**When the screen has a brief**, at `.trunative/screens/<name>.md`, read it and build what it says. The job, the hierarchy, the one primary action, the six states and the scope were settled and approved in `flow/spec.md`, and rederiving them here is the context this step exists to save. Where the brief and the request disagree, the brief is stale: go back to `flow/spec.md` and change it rather than building against a file that now lies.
|
|
12
|
+
|
|
13
|
+
**When it has none**, and the line above says the task touches none of the four, state in one or two lines before writing code:
|
|
14
|
+
|
|
15
|
+
- the single job this screen does, taken from `PRODUCT.md`
|
|
16
|
+
- the one primary action, and where the thumb reaches it
|
|
17
|
+
- what happens on a slow network, on failure, and with no data
|
|
18
|
+
|
|
19
|
+
If the screen has more than one primary action, it is more than one screen. Split it and say so.
|
|
20
|
+
|
|
21
|
+
If framing it turns out to move hierarchy, actions, states or navigation after all, the change was structural and misjudged. Stop and run `flow/spec.md`.
|
|
22
|
+
|
|
23
|
+
## 2. Load only what applies
|
|
24
|
+
|
|
25
|
+
Read `DESIGN.md` for the tokens and `STACK.md` for the primitives that reach them. Then open every file under **Base** in `SKILL.md`, and from **Extra** the files this screen triggers. Do not read the whole folder. Use `references/` on demand, for a specific number or platform API, never as background reading.
|
|
26
|
+
|
|
27
|
+
Base is not part of this choice. Colour, text, targets, layout, states, actions, words, motion and artwork are on a splash screen and on a chart alike, so deciding a screen does not touch one of them is not a decision this step gets to make.
|
|
28
|
+
|
|
29
|
+
Extra is not a feeling either. Name the screen in one line, as the thing it is rather than as the feature it belongs to, then run that line down the Covers column of the Extra table in `SKILL.md`, row by row. Open every row whose words are on the screen. Leave a row closed only with a sentence naming what this screen does not have that the row is about. List what you opened, and what you closed and why, before writing anything, because a file opened after the screen exists reviews it rather than shapes it.
|
|
30
|
+
|
|
31
|
+
A brief's `scope` has already made that pass. Open what `scope.open` names and reuse the sentences in `scope.closed`, and run the pass yourself for anything the brief does not cover, because a screen grows between the brief and the code.
|
|
32
|
+
|
|
33
|
+
When the screen touches Firebase, in any of auth, Firestore, Storage, Messaging, Remote Config or Crashlytics, read `flow/firebase.md` as well. It is loaded here the way a heuristic is, and it is not a step.
|
|
34
|
+
|
|
35
|
+
**When `DESIGN.md` is missing, or says nothing about a role this screen needs.** Init writes that file and `doctor` is what notices it is gone, but screens get built in the gap anyway, and what fills the gap on its own is the median of everything a model has read: a system font, a violet button, a rounded card, and a screen that would fit any other product. Settle the identity in writing before the first line of code, in five lines:
|
|
36
|
+
|
|
37
|
+
- the material or reference this product evokes, named. Newsprint, film stock, enamel signage, a receipt, a ledger. An adjective is not a reference.
|
|
38
|
+
- ground, ink and accent derived from it, as roles rather than as values typed into the screen (`color-derived`, `color-roles`).
|
|
39
|
+
- the display face and the interface face, each with the reason it was picked (`type-face`).
|
|
40
|
+
- the shape language: which radii exist, which edges stay square, and how depth arrives (`layout-shape`).
|
|
41
|
+
- what the artwork on this screen depicts (`icon-depicts`).
|
|
42
|
+
|
|
43
|
+
Write the reference before any value, never the other way round. What arrives first in this gap is the median of everything the model read, and it arrives with a reference attached to it afterwards, which reads exactly like a derivation. When the palette lands in either family `color-derived` names, the second derivation it asks for happens here, before the first line of code, and the identity says which of the two was kept and why.
|
|
44
|
+
|
|
45
|
+
Those five lines are provisional and say so. They go to the user to confirm into `DESIGN.md`, and until that happens they live in one place in the code rather than inside the components that read them. The next screen built in the same gap uses the same five lines, or the product has two identities and nobody decided which one it has.
|
|
46
|
+
|
|
47
|
+
## 3. Write it
|
|
48
|
+
|
|
49
|
+
- Reuse the tokens `DESIGN.md` defines and the components `STACK.md` lists. A raw value where a token exists is a defect, and a new primitive needs a reason.
|
|
50
|
+
- Apply the heuristics as you write, not as a pass afterwards.
|
|
51
|
+
- Where a heuristic cannot be met, leave the code correct and record the conflict for review. Do not silently drop the rule.
|
|
52
|
+
|
|
53
|
+
## 4. Hand off
|
|
54
|
+
|
|
55
|
+
Say which heuristics files you applied and which you deliberately skipped, with the reason, and name the triggers that opened the extra ones. When this screen had to settle an identity because `DESIGN.md` did not, the five lines go in the hand-off, marked provisional. Name the brief you built against, or say the change was not structural and had none. Then run `flow/review.md`. Build is never the last step.
|
|
56
|
+
|
|
57
|
+
Where the code had to depart from the brief, change the brief in the same turn and say what moved. A brief left behind is what review reports as drift, and it is cheaper to correct here than to explain there.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Firebase
|
|
2
|
+
|
|
3
|
+
Loaded by `flow/build.md`, never as a step of its own, whenever the screen touches auth, Firestore, Storage, Messaging, Remote Config or Crashlytics.
|
|
4
|
+
|
|
5
|
+
It exists because Firebase decides things this skill already has rules about. Offline persistence changes what an honest loading state is. Local writes come back before the server has seen them, so what the screen shows is a claim rather than a fact. An agent that wires it up without knowing that produces screens the heuristics then have to fight.
|
|
6
|
+
|
|
7
|
+
Two lines of scope, said once rather than left to silence:
|
|
8
|
+
|
|
9
|
+
- Security rules, indexes, quotas and billing are not design. This skill does not review them, and a screen that looks right over rules that let anyone read the collection is still the project's problem to fix.
|
|
10
|
+
- Mobile web is out. The JavaScript SDK is a different product with different offline behaviour, and a project on `web` uses this file for nothing but the auth gate below.
|
|
11
|
+
|
|
12
|
+
## 1. Auth is a navigation gate with three states
|
|
13
|
+
|
|
14
|
+
The signed-out and signed-in pair is the one everybody builds. The third is **restoring**: the moment between launch and the SDK answering from its own persisted session. It is not a loading spinner over the app, it is a state the router has to name.
|
|
15
|
+
|
|
16
|
+
- The first frame while the session resolves is the launch surface handing over, not a screen that flashes and replaces itself. That is `splash-first-frame` and `splash-entry`.
|
|
17
|
+
- Each of the three states lands somewhere written down, and the signed-in landing is the product's own screen rather than a home that then redirects. `nav-restore` and `onboard-first-action`.
|
|
18
|
+
- The restoring state is short and bounded, so nothing in it measures anything (`splash-no-progress`), and a session that fails to restore is a signed-out user rather than an error screen.
|
|
19
|
+
- A route guard that runs before the session resolves sends a signed-in user to the sign-in screen for one frame. That frame is the defect.
|
|
20
|
+
|
|
21
|
+
## 2. Sign in methods
|
|
22
|
+
|
|
23
|
+
The order, the provider buttons and the account the routes resolve to are `auth-methods` and `auth-provider-button`. The web flow leaving the app is `auth-web-flow`. The OTP screen, its autofill and its error recovery are `auth-code-screen` plus `form-autofill` and `form-error`.
|
|
24
|
+
|
|
25
|
+
What belongs here rather than there: Firebase returns one user object from every provider, so two routes that reach the same person must reach the same uid. Linking a credential to the current user rather than signing in again is what makes that true, and it is the step that gets skipped.
|
|
26
|
+
|
|
27
|
+
## 3. Firestore persistence is on, so the honest state is queued
|
|
28
|
+
|
|
29
|
+
Offline persistence is enabled by default on the mobile SDKs. A write goes into the local store, the listener fires immediately, and the server sees it later.
|
|
30
|
+
|
|
31
|
+
- A screen waiting on a server that was never needed is a defect, not a loading state. Read the local snapshot and render.
|
|
32
|
+
- The state for an unconfirmed write is pending, not done and not failed. That is `state-queued`, and the snapshot's own pending-writes flag is where it comes from.
|
|
33
|
+
- Cached content carries its age (`state-stale`), and the snapshot's from-cache flag is the source of that mark.
|
|
34
|
+
- The network being off is not an error class here. `state-offline` names four states and this is the cached one.
|
|
35
|
+
|
|
36
|
+
## 4. Latency compensation makes optimistic UI the default
|
|
37
|
+
|
|
38
|
+
The local write returns before the round trip, so the screen is already optimistic whether or not anyone decided it. Two consequences:
|
|
39
|
+
|
|
40
|
+
- The rollback path is the one that gets skipped. A write the server finally rejects has to undo what the screen already showed, keep what the user typed, and say what happened without blaming them: `state-retry` and `copy-error`.
|
|
41
|
+
- A rejection can arrive minutes later, on another screen. It lands in the quietest vehicle that still reaches the user (`fb-ladder`), attached to the item rather than to wherever they happen to be.
|
|
42
|
+
|
|
43
|
+
## 5. Storage uploads outlive the screen
|
|
44
|
+
|
|
45
|
+
Progress counted in real bytes, a resume handle that survives the process, and a transfer that does not die with the screen are `net-upload` and `off-queue`. The photo still going up is a row with its space reserved (`list-images`), not a blocking dialog.
|
|
46
|
+
|
|
47
|
+
## 6. Messaging and the push permission
|
|
48
|
+
|
|
49
|
+
The system prompt is one tap and it is spent forever. Everything about when to ask, what the screen before it says and what the denied path does is `perm-notify-ask`, `perm-rationale` and `perm-answers`. The in-app equivalent for someone who said no is `notify-inapp`.
|
|
50
|
+
|
|
51
|
+
Firebase-specific: the token is per install and it rotates. Nothing in the interface promises delivery, and a screen that says notifications are on because a token exists is reporting the wrong fact.
|
|
52
|
+
|
|
53
|
+
## 7. Remote Config arrives after the first frame
|
|
54
|
+
|
|
55
|
+
The screen has to be correct before any value lands, which means shipped defaults rather than empty strings, and no layout that shifts when the fetch returns.
|
|
56
|
+
|
|
57
|
+
- Defaults are set in code and the screen renders from them. A paywall whose price appears a second late is `pay-price-source` and `icon-reserve` at once.
|
|
58
|
+
- A value that changes what the user is looking at waits for the next screen rather than rewriting the current one.
|
|
59
|
+
|
|
60
|
+
## 8. Crashlytics and Analytics, the part that is design
|
|
61
|
+
|
|
62
|
+
- Screen names match the flow the user walks, so the funnel and the navigation graph are the same thing.
|
|
63
|
+
- Nothing the user typed leaves the device in an event, a log or a crash report. That is `priv-instrument`, and it is the rule an analytics call breaks fastest.
|
|
64
|
+
- What the store declaration says has to match what the SDKs actually collect: `priv-declared`.
|
|
65
|
+
|
|
66
|
+
## 9. Setup
|
|
67
|
+
|
|
68
|
+
Only commands and file paths live here. Every design consequence above applies whatever the stack is.
|
|
69
|
+
|
|
70
|
+
<If stack="flutter">
|
|
71
|
+
Add the plugins with `flutterfire configure`, which writes `lib/firebase_options.dart` and both native config files. Initialise with `Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform)` before `runApp`, and keep that off the launch path beyond what the first screen draws (`perf-cold-start`). Persistence is on by default; the settings object is `FirebaseFirestore.instance.settings`.
|
|
72
|
+
</If>
|
|
73
|
+
|
|
74
|
+
<If stack="react-native">
|
|
75
|
+
Install `@react-native-firebase/app` plus one package per product. Put `GoogleService-Info.plist` in the iOS target and `google-services.json` in `android/app/`, then apply the `com.google.gms.google-services` plugin in the app-level Gradle file. The modular API is the current one; the namespaced calls are deprecated. Expo needs a development build and the config plugin rather than Expo Go.
|
|
76
|
+
</If>
|
|
77
|
+
|
|
78
|
+
<If stack="swiftui">
|
|
79
|
+
Add the Firebase SDK through Swift Package Manager, put `GoogleService-Info.plist` in the app target, and call `FirebaseApp.configure()` in the `App` initialiser or an `AppDelegate` adaptor. Persistence is on by default.
|
|
80
|
+
</If>
|
|
81
|
+
|
|
82
|
+
<If stack="compose">
|
|
83
|
+
Add the Google services plugin and the Firebase BoM to Gradle, put `google-services.json` in `app/`, and let the content provider initialise Firebase rather than calling it from `Application.onCreate`. Persistence is on by default.
|
|
84
|
+
</If>
|
|
85
|
+
|
|
86
|
+
## What review scores this against
|
|
87
|
+
|
|
88
|
+
This file adds no rules. It says what Firebase does to the screen, and review scores the result against the rules that already exist:
|
|
89
|
+
|
|
90
|
+
| What Firebase decided | Scored as |
|
|
91
|
+
|---|---|
|
|
92
|
+
| The restoring state and where each of the three lands | `state-set`, `nav-restore`, `splash-first-frame` |
|
|
93
|
+
| One uid behind every route on the sign-in screen | `auth-methods` |
|
|
94
|
+
| Reading the local store instead of waiting on the server | `off-local-first`, `state-loading` |
|
|
95
|
+
| The pending-writes flag drawn as pending, the cache flag as age | `state-queued`, `state-stale` |
|
|
96
|
+
| The rejection that arrives after the screen already showed the write | `state-retry`, `copy-error`, `fb-ladder` |
|
|
97
|
+
| The upload that outlives the screen | `net-upload`, `off-queue` |
|
|
98
|
+
| The push prompt and the denied path | `perm-notify-ask`, `perm-rationale`, `notify-inapp` |
|
|
99
|
+
| Shipped Remote Config defaults and a screen that does not shift | `pay-price-source`, `icon-reserve` |
|
|
100
|
+
| Screen names, and nothing typed leaving the device | `priv-instrument`, `priv-declared` |
|
|
101
|
+
|
|
102
|
+
Two of them are answered on a device rather than in the router, and both pass on a warm app with a fast connection, which is the only condition the code was written against. Cold start with a stored session and watch the first two frames, then turn the network off, make a write, and look at what the row says while the server has not seen it.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Init
|
|
2
|
+
|
|
3
|
+
Runs once per project, and again whenever `doctor` fails. Nothing else in the flow starts before this passes.
|
|
4
|
+
|
|
5
|
+
## Asking
|
|
6
|
+
|
|
7
|
+
This step runs on answers, not on guesses. Every decision below that the code cannot settle is a question for the user, asked one at a time: one decision per question, the options in the order given, the recommended one first and named as the recommendation, and each option phrased as what it costs the design.
|
|
8
|
+
|
|
9
|
+
The user chooses. A recommendation is a default worth stating, not a decision already taken, and an answer that goes against it wins with no argument back.
|
|
10
|
+
|
|
11
|
+
## 1. Run the doctor
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npx trunative doctor
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It checks four things and exits non-zero if any fails:
|
|
18
|
+
|
|
19
|
+
- the product brief, at `.trunative/PRODUCT.md` or `PRODUCT.md`
|
|
20
|
+
- the design brief, at `.trunative/DESIGN.md` or `DESIGN.md`
|
|
21
|
+
- the stack brief, at `.trunative/STACK.md` or `STACK.md`
|
|
22
|
+
- the installed skill, against the hash in `.trunative/skill.lock`
|
|
23
|
+
|
|
24
|
+
The doctor only checks that a brief exists. Whether it says anything useful is your job, here in init.
|
|
25
|
+
|
|
26
|
+
## 2. Fix what it reports
|
|
27
|
+
|
|
28
|
+
**Skill missing or stale.** Run `npx trunative install`, then run the doctor again. Do not hand-edit the copy inside the agent directory: the hash check exists to catch exactly that, and the edit is lost on the next install.
|
|
29
|
+
|
|
30
|
+
**Product brief missing.** Interview the user, then write `.trunative/PRODUCT.md`. Do not invent answers, and do not fill a template with plausible text. Ask:
|
|
31
|
+
|
|
32
|
+
- who uses this, and in what situation (walking, driving, at a counter, at home)
|
|
33
|
+
- the two or three jobs the app exists to do
|
|
34
|
+
- what a session looks like: seconds or minutes, one-handed or two, foreground or interrupted
|
|
35
|
+
- the constraints that are already decided: platforms, stack, minimum OS versions, offline requirements
|
|
36
|
+
|
|
37
|
+
**Design brief missing.** `DESIGN.md` follows the [design.md specification](https://github.com/google-labs-code/design.md) from Google Labs, so any agent that already reads it gets the visual identity for free. Do not invent a second format.
|
|
38
|
+
|
|
39
|
+
Read the codebase first and derive the tokens from what is actually there. Ask the user only about what the code cannot answer, and about the intent behind values the code shows but does not explain.
|
|
40
|
+
|
|
41
|
+
- YAML frontmatter: `name` is required. Add `version`, `description`, and the token groups the project has: `colors`, `typography`, `rounded`, `spacing`, `components`. List what the project genuinely does not define under `omitted`, instead of inventing values to fill the schema.
|
|
42
|
+
- Markdown body, in this order: Overview, Colors, Typography, Layout, Elevation & Depth, Shapes, Components, Do's and Don'ts. The body is where the reason lives. A token without a reason gets copied into the wrong place later.
|
|
43
|
+
- Run `npx @google/design.md spec` for the authoritative schema and `npx @google/design.md lint` to check the file. The doctor does not lint, it only checks the file exists.
|
|
44
|
+
|
|
45
|
+
**Stack brief missing.** `DESIGN.md` describes the visual identity and nothing else. Everything specific to this codebase goes in `.trunative/STACK.md`, written from the code, not from the user's description of the code:
|
|
46
|
+
|
|
47
|
+
- the stack and its UI primitives, with the exact names used in this project
|
|
48
|
+
- how the DESIGN.md tokens are actually reached in code: the theme object, the CSS variables, the constants file
|
|
49
|
+
- navigation shape: tabs, stack, modals, sheets, and which library provides them
|
|
50
|
+
- component inventory worth reusing, with file paths
|
|
51
|
+
- platform floors: minimum OS versions, target devices, offline requirements
|
|
52
|
+
- deliberate exceptions to the heuristics, each with the reason and the date
|
|
53
|
+
|
|
54
|
+
**No stack yet.** There is no code to read when the project is empty, so `STACK.md` records a decision instead of an observation. Never pick the stack silently, and never lay the options out as equivalent: a flat list of frameworks is the absence of a recommendation.
|
|
55
|
+
|
|
56
|
+
**Flutter is the recommended option, and it goes first with the reason in one line.** The reason is design control, which is the whole point of this skill:
|
|
57
|
+
|
|
58
|
+
- Flutter draws its own widgets instead of delegating to the OS, so a spacing, weight or radius decision lands identically on both platforms. Everywhere else the same code renders two different screens and the design work has to be done twice.
|
|
59
|
+
- Material 3 and Cupertino both ship inside the SDK, so the token tables the heuristics reference are already in the framework, with no third-party UI library to pick, pin and outgrow.
|
|
60
|
+
- Text scale, safe areas and semantics are first-class (`MediaQuery.textScaler`, `SafeArea`, `Semantics`), so the accessibility floor is reachable without extra packages.
|
|
61
|
+
- Hot reload keeps the build and review loop short, and this flow runs that loop on every screen.
|
|
62
|
+
|
|
63
|
+
**A stated constraint moves the recommendation off Flutter**, and only a stated one does. Never a preference of yours. When code already exists, or the team already ships in another stack, use what is there and do not ask at all. Otherwise:
|
|
64
|
+
|
|
65
|
+
<If stack="flutter, expo, react-native, swiftui, compose, web">
|
|
66
|
+
This copy of the skill was built for one stack, which is the answer. Record it in `STACK.md` with the reason it was chosen, and do not ask.
|
|
67
|
+
</If>
|
|
68
|
+
|
|
69
|
+
<If stack="undecided">
|
|
70
|
+
<Ask header="Stack">
|
|
71
|
+
There is no code to read yet, so the stack is a decision rather than an observation. Which one?
|
|
72
|
+
|
|
73
|
+
<Option recommended>
|
|
74
|
+
**Flutter.** One rendering engine, so every spacing, weight and radius decision lands identically on both platforms and the design work is done once.
|
|
75
|
+
</Option>
|
|
76
|
+
|
|
77
|
+
<Option>
|
|
78
|
+
**React Native or Expo.** Right when the product is a feature inside an app that already ships in it. Two renderers, so every design decision is verified twice.
|
|
79
|
+
</Option>
|
|
80
|
+
|
|
81
|
+
<Option>
|
|
82
|
+
**SwiftUI or Jetpack Compose.** One platform only, with integration no cross-platform layer reaches: widgets, App Clips, deep system APIs. The other platform is a second product.
|
|
83
|
+
</Option>
|
|
84
|
+
|
|
85
|
+
<Option>
|
|
86
|
+
**Mobile web.** The target is a website. The browser owns gestures, insets and the keyboard, and the heuristics apply to what it leaves.
|
|
87
|
+
</Option>
|
|
88
|
+
</Ask>
|
|
89
|
+
</If>
|
|
90
|
+
|
|
91
|
+
Write the answer into `STACK.md` as its first line, `Stack:` followed by exactly one of `flutter`, `expo`, `react-native`, `swiftui`, `compose` or `web`, because that line is what decides which copy of this skill the project installs next. Under it, the reason, naming the constraint that moved the recommendation when one did. A stack chosen off the recommendation and a stack chosen against it are different facts, and review needs to tell them apart.
|
|
92
|
+
|
|
93
|
+
## 3. Check the project is not wearing another app's identity
|
|
94
|
+
|
|
95
|
+
Apps are routinely started from a fork, a template, or a sibling product in the same account, and the copy keeps everything the original had. This matters here because step 2 derives the briefs **from the code**, so anything inherited gets recorded as a deliberate decision and then defended in every later review.
|
|
96
|
+
|
|
97
|
+
The tell is that none of it fails. The app builds, runs and looks finished while pointing at another product's identity and another product's services.
|
|
98
|
+
|
|
99
|
+
Ask the user what the app is, then check the code against the answer:
|
|
100
|
+
|
|
101
|
+
- **Identity**: the display name, the bundle or package identifier, and the copyright line.
|
|
102
|
+
- **Launch artwork**: the icon and the launch surface, plus the config files their generators read. A generator config carried over from the original names the original's artwork paths, and the tool then succeeds while producing the wrong app's icon.
|
|
103
|
+
- **Third-party project targets**: analytics, crash reporting, push, feature flags, ads. These are identifiers in a config file, and an identifier from the source project is the worst case, because it works: the new app quietly reports into the old app's dashboards.
|
|
104
|
+
- **Brand tokens**: colours, typography and the launch background, which is where the previous product's palette survives longest.
|
|
105
|
+
- **Written-down URLs**: support, marketing, privacy and terms.
|
|
106
|
+
|
|
107
|
+
Two rules for what you find:
|
|
108
|
+
|
|
109
|
+
- **Report, do not silently fix.** Some of it is intentional: a shared account, a deliberately shared analytics project, a house palette. The user knows which; you do not.
|
|
110
|
+
- **Never assume the value in the code is the intent.** In a derived project the code is evidence of where it came from, not of what it is meant to be. Where the code and the user disagree, the user decides, and `STACK.md` records that it was inherited rather than chosen.
|
|
111
|
+
|
|
112
|
+
An app with no ancestor answers this in one line and moves on.
|
|
113
|
+
|
|
114
|
+
## 4. Confirm
|
|
115
|
+
|
|
116
|
+
Run `npx trunative doctor` again. Every check must pass before the build step. If the user declines to answer something, write down what is unknown instead of guessing, and treat it as a risk in review.
|