Skip to main content

droidkit-item

Design and build a DroidKit registry item (component, pattern, block) or a core theme/foundation change. Use when creating, revising, or extending anything under registry/ or core/. Covers the experience spec (walk the moments, real-app references, the signature detail), the product decision, the state matrix, grounding in official Android docs via the android CLI, API shape, Compose/Android gotchas, the foundation vocabulary (haptics, press, shake, spinner timing, reduced motion), folder anatomy, registry.json, and the gates that must be green before hand-off.

Quellinformationen

Repository
thisuxhq/droidkit
Letzte Quellaktivität
14. September 2026 um 05:22
Erkannte Sprache von SKILL.md
Englisch
Sterne
1
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
droidkit-item
description
Design and build a DroidKit registry item (component, pattern, block) or a core theme/foundation change. Use when creating, revising, or extending anything under registry/ or core/. Covers the experience spec (walk the moments, real-app references, the signature detail), the product decision, the state matrix, grounding in official Android docs via the android CLI, API shape, Compose/Android gotchas, the foundation vocabulary (haptics, press, shake, spinner timing, reduced motion), folder anatomy, registry.json, and the gates that must be green before hand-off.
# Build a DroidKit item Read first, follow, do not paraphrase back: `AGENTS.md`, `docs/principles.md`, `docs/anatomy.md`, `docs/quality.md`. Settled choices are in `docs/decisions.md`; the loop you are inside is `docs/verification.md`. You are the **author**. You never review your own item. When the gates are green and you have looked at every PNG, hand off to `/review <name>`. ## Step 0 — Walk the moments Correct is the floor. 48 dp, `ContentType`, an IME chain — a reviewer can check those and the user never notices them. This step is where *designed* comes from, and it happens before the state matrix and before any code. Skipping it produces an item that passes every gate and feels like nothing. ### 0a. Look at real apps first Search Mobbin for the job this item does, not the widget name. Three to five screens or flows; read the images, not the metadata. ```text search_screens("create password screen with live strength meter and requirement checklist") search_flows("sign in with wrong password and recover") ``` Write down, in one line each, what the good ones do that Material's primitive does not. Keep the URLs; they go in the hand-off. If every reference does the same thing, that thing is the floor, not the signature. ### 0b. The moments table → `registry.json` → `ux` Walk one person through the item. For each moment they actually hit, one row: what they notice, what we do, why it matters *to them*. ```text moment the user is… decide see glancing at it what reads at a glance · reserved space so nothing jumps later reach focusing / pressing press scale · haptic · caret · where focus lands act typing / tapping / dragging live feedback · clear button · auto-advance · paste mistake doing it wrong shake · error text on the field · reject haptic · never colour-only recover fixing it error hides on first keystroke · select-all on refocus · focus back succeed done check morph · confirm haptic · auto-submit · what announces "done" leave moving on IME action · what persists · what resets ``` Rules: - **One `signature: true`.** The detail a person would remember and mention. If you cannot name it, you have a wrapper, not an item. Stop and rethink the product decision. - **Every row is observable.** A golden shows it, a behaviour test asserts it, or a `journey.xml` step drives it. A row nothing can observe gets deleted, not kept as intent. - **Motion states `reducedMotion`.** Anything that shakes, scales, slides, crossfades, pulses, or pops says what happens at animator scale 0. `lintRegistryUx` rejects the row otherwise. The end state must be reached without the animation. - **Haptics have a semantics twin.** `reject()` pairs with `error(...)`; `confirm()` with a `stateDescription`; `tick()` with a visible change. The moment must exist for someone with haptics off. - **Build from `foundation`.** `pressScale`, `shake`, `rememberLoadingVisibility`, `rememberReducedMotion`, `rememberAppHaptics` are the vocabulary. Do not re-implement them in the item; if one is missing and a second item will need it, add it to `registry/foundation/` first. - `why` is user-facing. "So the pill feels pressed" is a why. "Per Material spec" is not. Example, password-field on sign-up: ```json { "moment": "act", "behaviour": "Rules under the field tick live as the user types, each with a tick haptic", "why": "Nobody learns the rules from an error after submit", "signature": true, "reducedMotion": "Checks appear without the morph" } { "moment": "mistake", "behaviour": "On the error edge the field shakes once, fires reject, and the supporting line shows the message", "why": "The refusal is felt before it is read", "reducedMotion": "No shake; haptic and text only" } { "moment": "recover", "behaviour": "Error text hides on the first keystroke and space stays reserved", "why": "The person is already fixing it; stop shouting and do not jump the form" } ``` ### 0c. The product decision, one sentence Now the sentence: what does this item decide that Material's primitive leaves open? It should be the summary of the table, not a separate thought. ```text ✓ password-field: rules tick live on sign-up, refusal is felt (shake + reject), error hides the moment you start fixing it ✓ empty-state: one title, one line, at most one action; never used for loading or errors ✗ card: Material Card with 16 dp radius ← not an item. Stop. ``` If the sentence is about corners, colours, or "a nicer default", it is not an item. ## Step 1 — State matrix, before code Write it into `registry.json` → `states`. Walk every axis; cross out what does not apply and say why in one line. ```text state default · focused · pressed · disabled · loading · error · success · readonly · empty content short · long-text · rtl · large-font (2×) · with-icon / no-icon environment light · dark · phone · tablet (widthDp 840) · ime-open · reduced-motion transitions → loading → success | error error → typing (clears when? on keystroke or on blur — decide) focus after error (where does focus land?) rotation (what must survive? rememberSaveable) double-tap during transition ImeAction.Next chain (who receives focus?) ``` Every item declares at least `default`, `dark`, `large-font`. Inputs add `error`, `disabled`, `rtl`. Blocks add `tablet`. Keep it 5–9 states; do not invent states the API cannot produce. For each transition, one line, in the file's KDoc or a comment above the composable: ```kotlin // error → typing: isError clears on the first keystroke; supportingText stays until blur. ``` ## Step 2 — Ground in official docs (required) Training data is stale for Compose. The `android` CLI has a local, offline index of developer.android.com: ```bash android docs search "<topic>" # pick the kb://android/develop/ui/compose/... hit android docs fetch <kb-url> # read it, then act ``` If it prints "Waiting for index" on first use, wait; it is a one-off download. Search **whenever the item touches one of these**: ```text semantics contentDescription · stateDescription · liveRegion · mergeDescendants · role · heading material the exact Material 3 component you wrap — read its parameter list, do not guess defaults touch targets minimumInteractiveComponentSize · when Material pads to 48 dp and when it does not text input KeyboardOptions · ImeAction · ContentType / autofill · VisualTransformation · TextFieldState focus FocusRequester · focus order · keyboard navigation · onKeyEvent animation AnimatedContent · SizeTransform · animateContentSize · MotionScheme · reduced motion insets WindowInsets · imePadding · edge-to-edge adaptive WindowSizeClass · ListDetailPaneScaffold · Material 3 Adaptive back predictive back · BackHandler · NavigationEventHandler lazy lists key · contentType · stability · recomposition experimental anything @Experimental*Api — confirm it is still experimental in the current BOM ``` Rules: - Docs beat memory. If the doc contradicts what you know, the doc wins. - If the docs are silent, say so under `unverified` in your hand-off. Do not fill the gap with a guess. - Record what you read in `registry.json` → `sources` as `kb://` URLs. Review checks trigger topics against this list. Google ships agent skills in the same index; use them instead of improvising: `android skills add adaptive | edge-to-edge | testing-setup | navigation-event | android-profiler`. ## Step 3 — API shape - Two entry points on the same name: a friendly overload (`text: String`) and a slot overload (`content: @Composable () -> Unit`). Never a configuration object; never an enum for every option. - Parameter order: required data → callbacks → `modifier: Modifier = Modifier` (first optional) → behaviour → appearance → slots. - `modifier` is applied to the root exactly once. - Colour, type, shape from `MaterialTheme.*`; spacing, motion, elevation from `AppTheme.*`. - Hardcoded dp only where the number *is* the opinion. Named `private val` at the top of the file. The conventions lint rejects inline `.dp` literals in source files. - State that must survive rotation → `rememberSaveable`. Everything else the caller owns. - Blocks: stateless. State + callbacks in, own `data class`, no ViewModel/Nav/DI. - Experimental Material opt-ins go on the file **and** in `experimentalApis`. - Prefix `App` on theme and components; patterns and blocks use plain names (`EmptyState`, `SettingsScreen`). Decisions #2. ## Step 4 — Gotchas (tick every one; these are the usual misses) ```text [ ] text ↔ spinner swap: SizeTransform or a min width — the button must not change width [ ] loading: not clickable, still focusable, stateDescription + liveRegion announce it [ ] loading is not "disabled": keep the primary container colour; a greyed loading button reads as broken [ ] icon-only control: contentDescription that changes with state ("Show password" / "Hide password") [ ] compound rows: mergeDescendants so TalkBack reads one item, not six [ ] 48 dp: Material pads only when the control is interactive; a clickable Icon is not an IconButton [ ] KeyboardType + ImeAction + ContentType set; onDone / onNext actually wired [ ] focus requests inside LaunchedEffect, never during composition [ ] error: text attached to the field, not only a colour; announced [ ] RTL: padding(start=), mirrored chevrons and progress; rtl preview exists [ ] long text: wraps or ellipsises by decision; fixed height() clips — use heightIn(min=) [ ] large font 2×: nothing clips, nothing overlaps [ ] infinite transitions stop when the item is not visible (skeletons) [ ] lazy lists: keys; no unstable lambdas into rows; no spring per row [ ] motion respects animator scale 0 / reduced motion [ ] insets in blocks: imePadding, navigationBarsPadding, edge-to-edge [ ] copy: sentence case, verb-first actions, no "Oops", no "!" — the lint rejects both [ ] press: pressScale + click() on anything that is a primary tap target; same interactionSource as the ripple [ ] spinner: rememberLoadingVisibility for what shows; the raw flag for what is ignored — never flash, never blink [ ] refusal: shake fires on the error edge, once; reject() beside it; the message is on the field [ ] error text: the supporting line is reserved (or animateContentSize) so the form does not jump [ ] reduced motion: every animated ux row has its reducedMotion path and still reaches the end state ``` ## Step 5 — Folder ```text registry/<type>s/<name>/ ├── App<Name>.kt source package com.droidkit.registry.<type>s ├── App<Name>Preview.kt preview one internal fun per state, @Preview(name = "<name> <state>") ├── App<Name>Test.kt test behaviour through semantics, not existence ├── registry.json type · status · ux · states · sources · avoidWhen · aiHints └── journey.xml optional; on-device script for /device (see droidkit-device) ``` Items that use `foundation` (haptics, motion) declare it in `registryDependencies`. Preview file rules: a tiny `private fun XPreviewSurface(darkTheme, content)` helper, then one `internal fun` per state. `uiMode`, `fontScale`, `widthDp` go on the `@Preview` annotation — the screenshot generator copies it verbatim. RTL is `CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl)` inside the preview. No logic in previews. Copy `registry/components/button/AppButtonPreview.kt`. Test rules: an assertion that would pass on an empty `Box` is not a test. Assert behaviour: disabled does not fire `onClick`; loading does not fire `onClick`; the show/hide toggle changes `contentDescription`; error text is present in the semantics tree. Use `mainClock` for transitions. Metadata rules: `description` says when to use it; `avoidWhen` names the real confusion (`otp-input` vs `password-field`); `aiHints` are the things an agent will get wrong composing it, not a restatement of the description. Register the item in `registry/registry.json` and add a showcase screen in `apps/showcase` (every item is used there — Decisions #14). ## Step 6 — Gates, then look ```bash ./gradlew :registry:check :apps:showcase:assembleDebug # imports · states · conventions · ux · tests · screenshots ./gradlew :registry:updateDebugScreenshotTest :registry:contactSheets # (re)record goldens after intentional visual change ``` Then **read** `registry/build/contact-sheets/<name>.png` and every PNG under `registry/src/screenshotTestDebug/reference/` for the item. Go through the state matrix row by row: does the image show the state it claims? A "loading" image with no visible spinner is a lie; fix it before hand-off. Then go through the `ux` table row by row: where is each row observable — which golden, which test, which journey step? Write it down. A row you cannot point at is a row you did not build. Set `status` to `review` and hand off with: ```text /review <name> ``` Include in the hand-off: the product decision sentence, the `ux` table with where each row is observable, the Mobbin references, the transition rules, `sources`, and an `unverified` list. ## Slop list — reject on sight, in your own code first ```text config objects · 10+ params · wrapper with no decision · modifier missing or mid-list · inline .dp "Oops!" · previews that show only default · tests asserting a count of text · a helper file "for reuse" spring animations on list rows · if (isTablet) forks · a second colour system beside MaterialTheme aiHints that restate description · avoidWhen copy-pasted from another item · generated-looking KDoc ux rows that restate states ("error: shows error colour") · a signature that is a colour or a radius motion with no reducedMotion · a haptic with no semantics twin · a shake that loops · a spinner that flashes ``` Less code with the right intention beats more code with coverage. If you can delete a parameter, delete it; a block will ask for it back when it is real.
Auf GitHub ansehen