Use when designing, implementing, reviewing, debugging, testing, or shipping mobile app client features across Flutter, React Native, native Android, and native iOS, including navigation, state, API integration, offline/cache, platform capabilities, accessibility, performance, app build/release, and rendered device verification. Product-agnostic; use miniapp-product-dev for mini-programs, web-react-dev for React web, product-ui-ux-design for UI/UX, backend skills for services, and testing-str...
Scanned 9/2/2026
Install to Claude Code
npx -y skills add ccoalm/ccl-skills --skill app-cross-platform-dev --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of App Cross Platform Dev?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/ccoalm-app-cross-platform-dev)More formats (shields.io, HTML) on the badges page.
---
name: app-cross-platform-dev
description: Use when designing, implementing, reviewing, debugging, testing, or shipping mobile app client features across Flutter, React Native, native Android, and native iOS, including navigation, state, API integration, offline/cache, platform capabilities, accessibility, performance, app build/release, and rendered device verification. Product-agnostic; use miniapp-product-dev for mini-programs, web-react-dev for React web, product-ui-ux-design for UI/UX, backend skills for services, and testing-strategy for test planning. Triggers include "移动端怎么做", "Flutter / React Native / iOS / Android App 端怎么写", "RN 端", "原生 iOS / Android 页面怎么写", "重构这个 App 页面/组件(局部)", "refactor an app screen/widget".
---
# App Cross-Platform Dev
Use this skill for mobile app and cross-platform client engineering. It covers Flutter, React Native, native Android, and native iOS. It does not own mini-programs, React web, backend service design, product requirements, or visual design rules.
## Routing
- Use `product-rd-workflow` first when the work spans product intent, architecture, design, implementation, testing, and release.
- Use `product-ui-ux-design` before or alongside coding for user-facing surfaces, interaction model, visual hierarchy, density, motion, empty/error states, and accessibility acceptance.
- Use `miniapp-product-dev` for WeChat/Alipay/Douyin/Baidu mini-program pages, host-platform APIs, developer tools, real-device preview, review submission, and miniapp release.
- Use `web-react-dev` for React web and browser-specific client work.
- Use Go or Python backend skills for server contracts, persistence, queues, auth services, and API ownership.
- Use `testing-strategy` to choose the test layer; return here for Flutter, React Native, Android, or iOS implementation details.
- Use `test-artifact-management` when the ask is about generating structured test cases from a Feishu requirements doc or codebase and tracking them in Feishu Bitable before implementation begins.
- Use `defect-diagnosis` first for bugs, failed tests, flaky behavior, crashes, rendering regressions, build failures, or store/release symptoms.
- For money, quota, permission, tenant/user data, high-impact AI, repeated submit, async finality, or support-traceable incidents, apply `product-rd-workflow` high-risk resilience gates before treating the app flow as complete.
## Core Workflow
Before editing app code, native project files, platform configs, styles, assets, or tests, complete enough analysis and planning for the change to be reviewable. Scale the plan to risk: a simple low-risk single-screen change can use a short inline plan; multi-platform, API-visible, native-capability, release/store, bug-fix, branch/MR, unclear-risk, or high-risk work needs explicit task split, target-device matrix, acceptance checks, verification commands, rollback or stop conditions, and named handoffs to testing, backend, release, or diagnosis skills before edits. Runtime-visible work additionally consumes the canonical UI/UX delivery contract's Design brief and Test selection Phase 0 before the first implementation edit.
Repo-local agent contracts (`AGENTS.md` at the repo root and in source directories) are part of the delivery contract: when a change moves a stable boundary, generated surface, workflow, or directory-local rule, update the nearest contract in the same MR and keep coverage in sync per `product-rd-workflow`'s spec / repo-contract sync gate.
When checking a cross-platform app against team standards, split conformance into deterministic and agent review evidence. Deterministic checks cover build flavors/schemes, package locks, generated API/IDL client usage, environment/lane config, unit/widget/instrumented/E2E commands, CI gates, signing/release config, and request/trace identifiers in central clients. Agent review checks cover screen/module boundaries, platform abstraction leakage, native bridge contracts, finite-value mapping, device evidence quality, and whether tests prove the shipped runtime paths rather than only mocked widgets. For the per-substack deterministic executor list (each analyzer ships thin defaults) and the shipped client conformance checkers, see `testing-strategy/references/fitness-functions.md` §4.1.3 (spec 006). Layering/dependency-direction has no out-of-box rule on any substack — express it as import-ban config (Konsist/ArchUnit/custom_lint/dep-cruiser).
1. Resolve the target app shape.
- Flutter shared app, React Native app, native Android, native iOS, or mixed native plus shared module.
- Target devices, OS versions, form factors, app-store constraints, and native capability needs.
- Whether the feature is shared across platforms or intentionally platform-specific.
2. Analyze the existing client surface.
- Locate the owning feature/module, route entry, state owner, API client, local storage, platform adapter, and tests before editing.
- Identify shared behavior versus platform-specific behavior; list which platforms must be changed and verified.
- Read local wrappers first: Flutter/Gradle/Xcode scripts, package managers, CI jobs, test targets, flavors/schemes, and generated files.
- If a design exists, map each visible state and interaction to code ownership before implementing.
- For every visible UI change, load `../product-ui-ux-design/references/delivery-contract.md` and consume either its full Design brief + Phase 0 or its valid low-risk copy-only record + lightweight Phase 0 before coding. The lightweight path checks semantics, accessible name, localization, rendered extent, and target render without inventing unrelated matrices; risk-bearing copy uses the full path. For full slices, map structure, state/adaptation matrices, behavior and criteria to shared/platform owners; record build target, device, safe area, keyboard, orientation, text scale, input/lifecycle, and preserved behavior. For a native WebView, this skill owns the container/bridge member while `web-react-dev` or the actual web-content owner supplies a separate content member; neither render closes the other layer.
- Before the first implementation edit, add the canonical `client_entry` defined there: local rule identifier or short quote and implementation decision, target surface/runtime, planned run/capture command, and behavior that must remain unchanged.
3. Define the client boundary before coding.
- Screens, routes, tabs, navigation stack, deep links, and back behavior.
- UI state owner, domain/data state owner, and source of truth.
- API client contract, request cancellation, retry, auth/session behavior, and error mapping.
- API observability: central clients should attach or preserve request/trace/operation identifiers, measure duration, distinguish cancel from failure, classify upload or long-running requests, and map backend envelopes into typed user-facing errors.
- Finite-value boundary: generated API enums, backend string codes, deep-link params, local cache values, analytics dimensions, and native bridge payload codes should flow through one typed client/domain mapping module. Screens/widgets should use mapped symbols and label tables instead of scattering raw values such as `"US"`, `"CN"`, `"active"`, or `"default"` across render, storage, routing, or tracking code. If shared client-domain ownership is unclear, keep a local mapper for the slice, mark temporary duplicate/raw uses with `finite-value-debt: <task-ref> <owner> <deadline> <reason>`, and record the consolidation owner. Architecture owns the cross-stack semantic decision when the same value must align across app, web, mini-program, backend, storage, and analytics.
- Local persistence, offline/cache policy, invalidation, conflict handling, and sync triggers.
- Platform capabilities such as camera, files, notifications, background tasks, payments, share sheet, sensors, biometrics, and permissions.
- Observability: crash reporting, analytics events, logs, performance traces, and feature flags.
- High-risk submit/finality behavior: duplicate tap protection, request cancellation semantics, offline/foreground recovery, durable pending/final states, retry rules, and a visible tracking identifier when support or reconciliation may be needed.
4. Keep architecture boring and inspectable.
- Keep view/widget code thin; move data loading, validation, command handling, and mapping out of render code.
- Use unidirectional data flow where practical: user action -> intent/command -> state update -> render.
- Separate transient UI state from durable domain/data state.
- Keep typed API clients and explicit error envelopes; do not let raw transport errors leak into UI copy.
- Isolate platform-specific code behind adapters, plugins, protocols, or modules.
- Treat route/deep-link behavior as a public contract with tests or smoke verification.
5. Implement by platform.
- Codegen regeneration discipline (cross-stack — Flutter `build_runner`/`freezed`/`pigeon`, Android KSP/Hilt/Room/Moshi, iOS `Sourcery` and other accessor/mock generators): never hand-edit pure generator-owned files (mark them `// DO NOT EDIT`; they regenerate freely *only when their working copy is clean/committed* — a dirty or untracked generated file may hold local edits a regen would silently restore over, so it gets the same preimage snapshot/diff/approval as below) — put customization in extension points / handwritten files beside the generated code. A file that is generated once and then hand-maintained is a *different* class: mark it explicitly `FROZEN` / `HAND-MAINTAINED` (not `DO NOT EDIT`, which a reader treats as regenerate-freely), exclude it from normal regeneration, and overwrite it only on explicit request through the preimage gate below. Edit the source of truth (annotations / IDL / `.proto` / templates) first, then regenerate. Do not overwrite a hand-maintained or user-edited file on regeneration without an explicit request, and gate that overwrite on the *pre-overwrite* preimage — generate to a temp/backup path, diff against the current file, and only replace after that diff is reviewed and approved (deleting the backup only once generation succeeds). Reviewing the git diff *after* an in-place overwrite is too late, and silently loses edits in dirty/untracked files. After a generator or toolchain *upgrade* (not just an input change) the emitted shape can change, so a cache key built only on input source can silently serve stale output — force a fresh, no-cache full regeneration and review the resulting diff against expected drift (clean/no-op is a valid outcome when the upgrade is semantically neutral; the point is that the regen actually ran rather than being skipped by a stale cache). Record the regeneration evidence (the command, that it ran fresh/no-cache, and the generated diff or an explicit no-op note) when the source-of-truth or generator/toolchain changed; when a shape change is expected *or* appears in the regenerated diff, also confirm the runtime/consumer reflects the new schema and, for a generated artifact carrying a version/schema stamp, that the stamp advanced — a clean `build` alone is not proof the regeneration ran. A shape change that appears unexpectedly in the diff is a stop-for-approval signal, not something to wave through because it was not anticipated. A parallel codegen-regeneration discipline exists for backend protobuf/IDL/DI codegen — load that skill for backend work, since this app-skill bullet does not gate backend codegen: `go-microservice-dev` owns the fuller set (never hand-edit generated, edit-source-then-regenerate, guarded-backup before overwrite, fresh-regen smoke for generator-shape changes, regenerate-and-verify); `python-service-dev` owns the core (never hand-edit generated clients/protobuf, update source contract then regenerate, verify generated output is clean or intentionally reviewed).
- Flutter: use feature-first modules when the repo allows it; keep widgets declarative; choose state management from the existing repo pattern; isolate platform channels and plugins; test with unit, widget, golden/screenshot when available, and integration tests for critical flows.
- React Native: keep navigation, storage, native modules, permissions, image/media pickers, and platform branches behind repo-owned adapters. Verify both JS behavior and native module availability on the affected iOS/Android target; do not treat a Metro bundle running as proof that the native capability works.
- Flutter platform scaffolds: after `flutter create` or adding a new target platform, inspect generated noise before committing. Preserve existing `.metadata` platform entries, align bundle/package identifiers and display names across platforms, and keep generated/local files such as IDE module files, Flutter generated configs, ephemeral debugger files, and generated plugin registrants ignored unless the repo explicitly tracks them.
- Android: follow UI/data layer separation; use ViewModel or state holders for screen state; hoist Compose state; collect lifecycle-aware state; keep repositories as data boundaries; use WorkManager or platform APIs for background work when needed.
- iOS: use SwiftUI for new UI unless the repo is UIKit-first; keep views data-driven; use State/Binding/Observable-style ownership deliberately; isolate URLSession/client code; use async/await where it matches the repo; bridge UIKit only behind clear boundaries.
- iOS simulator setup: do not treat a successful platform download as proof that Xcode can build. Verify runtime registration and destination discovery with `xcrun simctl list runtimes -j`, `xcrun simctl runtime list`, `xcodebuild -showBuildSettings -destination ...`, and, when storyboard or asset compilation fails, a minimal `xcrun ibtool` reproduction. If Xcode reports that the iOS platform is not installed while `simctl runtime list` shows duplicate or unusable runtime images, remove only the unusable duplicates with `xcrun simctl runtime delete`, then run `xcrun simctl runtime scan-and-mount` and recheck before changing app code.
- H5/WebView or hybrid app surfaces: initialize bridge capability explicitly, gate debug tooling to development builds, model auth/session cleanup across native and web storage, and verify gestures, safe area, keyboard, orientation, image preview, and floating toolbars on the target devices.
- Native WebView shells need a written shell contract: initial route and launch/consent gate, injected app info, JS bridge handler names and callback semantics, native/web storage sync, permission flows, loading/error/retry surfaces, back/edge-swipe behavior, orientation lock/unlock, network-state recovery, and update/download/install states.
- JS bridge handlers are API contracts. Validate input payloads, keep callback ids scoped and cleaned up, return terminal success/cancel/failure states, avoid exposing broad native capabilities, and make handler availability detectable from the web side.
- App-hosted web surfaces need lifecycle state rules: foreground/background save and restore, TTL or expiry, sensitive-field handling, session invalidation, public/guest access modes, and recovery when the native bridge or injected app info is missing.
- App-hosted web surfaces should use full-screen containers by default and add max-width/device frames only for intentional preview modes. Bottom tabs, sticky actions, overlays, and toasts must respect safe-area insets from the native host when available and fall back predictably in browser or iframe contexts.
- App startup/auth shells need an explicit sequence: native splash or fake launch screen, first-run privacy/terms gate, web content load, login/account-opening or session restore, and deterministic handoff to the first tab. Do not initialize optional background listeners or broad native capabilities before consent and shell readiness; do not enable debug overlays or debug menus in production builds.
- Foundational app shells need a written app-state contract across native and web: splash timeout, consent persistence, initial route, bottom-tab ownership, guest/authenticated session, verification restore, web/native cache keys, logout/delete cleanup, privacy/legal document routes, about/version/update state, network/load retry, and production debug gating.
- The native shell should treat login, home, profile, privacy, and about as one launch-to-account system. It owns splash or fake-launch timing, first-run privacy gate persistence, WebView/WKWebView readiness, app-info injection before web layout depends on it, native storage get/set/remove/clear, network-error retry, back/edge-swipe behavior, update handoff, and debug/diagnostic gating; the hosted web owns auth modes, verification, home modules, profile routes, and legal document rendering.
- When logout, guest exit, account deletion, or identity reset succeeds, clear both hosted-web state and native shell state where present. Verify that the next launch cannot reuse stale WebView storage, stale injected account metadata, or an old first-tab route.
- Account-opening or binding in an app-hosted flow is conditional on the product contract. When present, the web layer owns validation, consent, verification, first-login password setup, and user-visible copy; the native shell owns app info, storage sync, privacy gate, and post-auth cache/session handoff. New-user account creation must not hide required setup or recovery states behind server side effects.
- Verification and password setup flows need native-container resilience: masked destination, resend countdown, paste/autofill support, focus and Backspace behavior, foreground/background restore with sensitive-field handling, expired restore cleanup, and a deterministic route after success or failure.
- Keyboard avoidance must distinguish visual-viewport resize, overlay mode, Android legacy behavior, and iOS native behavior. Keep the active input and submit/retry action visible; reset padding/translation on blur, orientation changes, and route exit.
- Orientation changes are platform capability flows, not CSS toggles. Model enter, exit, failed bridge call, iframe/postMessage fallback, browser-only instruction, state sync, cleanup on unmount, and route-level return-to-portrait behavior.
- Async content shells need stable loading, error, retry, empty, and children geometry. Put retry inside the failed content region, not only in a toast, so users can recover without losing route or scroll context.
- Mobile UI must treat safe area, keyboard, orientation, foreground/background, weak network, and app restart as first-class UX states, not late bug fixes.
- Mobile forms running in WebView need platform-aware input handling: paste, deletion, cursor restoration, old Android WebView fallback, iOS WebKit duplicate or delayed input behavior, focus/blur reformatting, and keyboard-safe submit reachability.
- Media, upload, scan, review, or annotation flows: cover permission prompts, selected asset state, upload/progress/failure, preview/zoom/pan/reset, retry/cancel, foreground/background recovery, and final status rather than only a success toast.
- Microphone or speech-capture flows need a native capability lifecycle, not only an input widget.
- Lazy-load native audio modules so app startup does not freeze or prompt for permission before intent; probe by actually opening a capture path rather than checking that a binary, bridge, or permission string exists, discard probe bytes, keep probes out of transcription/model-visible paths, and stop the probe recorder even after partial initialization failure.
- Distinguish unsupported remote/headless environments, no device, permission denied, silent input, connection failure, unsupported language, and speech-not-detected states.
- Cross-platform fallback chains must match the real start path, consume subprocess/native error streams to avoid backpressure, stop every active recorder on release, blur, cancel, retry, or unmount, and fence callbacks by session generation so stale audio or transcripts cannot update a newer input.
- AI media-extraction flows need a staged client state machine: capture/import, crop/preview, upload, analyze, render generated content, classify/tag, save/publish, retake, and retry. Keep final commit disabled until required generated content and metadata are valid, and isolate rich-content renderer failure from the rest of the screen.
- Camera/media capture launched from WebView must not assume success just because the native picker opened; model native capability, returned object key/URL, and web callback recovery as a contract.
- Native capture launched from hosted web should be a two-phase finality contract: native success means an asset key or URL was produced; the hosted destination route still owns metadata validation, save/publish, page-ready acknowledgement, and final user feedback.
- Native camera capture for document/image extraction needs preview-final parity. If the user sees a processed crop, the uploaded bytes should use the same crop coordinates, orientation correction, color-space normalization, binarization/enhancement class, and size budget. Do not show a clean preview and upload a materially different image.
- Media processing pipelines need guardrails, not only filters: EXIF/upright normalization, crop rect clamping in normalized coordinates, aspect-ratio alignment between preview and final output, all-black/all-white detection, output-size backoff, and logs that expose ratios/bytes/timing without leaking credentials.
- WebView-launched native capture should keep old and new bridge contracts isolated. Use versioned handler names or explicit capability detection, scoped callback ids, terminal `success/cancel/failure` states, parseable JSON payloads, and page-ready or transition-cover cleanup so the web layer never depends on a hidden native screen finishing silently.
- Capture overlays should be owned by the native shell until the hosted page confirms readiness. Add an automatic timeout as a recovery guard, but treat the page-ready callback as the normal completion path.
- Native camera screens need stable full-screen geometry: safe-area back, dominant preview/crop area, bottom bar reserved for secondary import, centered shutter/confirm, and device tool such as torch or rotate. Do not resize the primary image area when loading, focusing, or toggling tools.
- Upload configuration and token selection should be runtime-configurable behind a coordinator or adapter with a conservative fallback. Log chosen config, object key, byte size, and token shape only in redacted form; do not require an app restart for bucket, endpoint, or signing-version changes when the product expects remote switching.
- Camera UX must cover real device craft: tap-to-focus or focus pulse where supported, torch availability and unsupported feedback, album fallback, visible taking/uploading overlays, retake path, permission temporary versus permanent denial, unavailable hardware, settings handoff, upload/token failure, and disabled controls while capture or upload is in flight.
- iOS and Android may need different media hardening, but the product contract should stay aligned: upright orientation, color-space normalization where needed, crop bounds clamping, preview/final parity, size budget/backoff, redacted diagnostics, and terminal callback cleanup on both platforms.
- Native app update flows need visible download progress, failed-token/download/install states, install-permission handling, cancel/retry, and platform-appropriate update path. Treat "open store" and "download APK then install" as different contracts.
- Mobile home, list, and insight surfaces need independent state ownership per module: initial skeleton, section-level error/retry, search debounce, pull-to-refresh, infinite scroll, no-more state, pending-versus-committed filters, and keyboard-safe bottom sheets.
- App-hosted mobile insight/report surfaces need separate owners for report context, metadata cache, data cache, filter state, advanced settings, card state, native-shell state, and scroll state. Do not let a global store or stale cache silently override an explicit route scope.
- When the same insight/report is reachable from a home summary and a bottom-tab list, define one entry contract: home shortcut, tab list, detail page, drilldown, and back behavior must share the same report id, scope, filter precedence, and stale-cache rules.
- For report filters, merge route, stored state, metadata defaults, and permissions in that order unless the product defines a stricter source of truth. Preserve explicit all-scope sentinels; never collapse them into `undefined`.
- For module data refresh, merge by affected module. A shorter or permission-limited response should not overwrite fuller cached context unless the scope genuinely changed. Setting changes should show card-level loading instead of full-page reload where feasible.
- Dense app-hosted tables need fixed headers or context columns, dynamic grouped columns, visible sort state, horizontal scroll affordance, load-more throttling, and in-table loading/error/empty overlays.
- Analytics scroll restoration should wait for stable layout height, clamp to current scroll bounds, abort on user wheel/touch, handle storage failure, and clear on filter, route, or context changes.
- Native shells that host mobile reports need a first-class contract for launch/consent, injected app info, status-bar and safe-area values, bridge capability detection, native storage get/set/remove/clear, network/load failure retry, back-stack behavior, orientation lock/unlock, update handoff, and shell overlay return to the originating page.
- For app-hosted report/detail pages, H5/Flutter owns context, metadata, filters, card state, chart/table rendering, drilldown routes, and module refresh; the native shell owns WebView/load retry, safe area/status injection, native storage, back gesture, orientation lock/unlock, and overlay page-ready cleanup.
- For app-hosted resource or asset-library entries, distinguish the shell from the library. The native shell owns WebView load/error/retry, injected app info, safe area/status values, native storage, bridge capability detection, update/download handoff, orientation/back behavior, and page-ready overlay cleanup. The hosted web or H5 route owns resource metadata, save/share/download/preview state, and final user feedback.
- Do not infer a native resource manager from a hosted save-to-library or upload entry. Add native file/download/share capabilities only when the target platform code exposes a scoped handler, permission flow, callback semantics, and recovery state.
- Do not initialize broad listeners, background recovery, debug WebView inspection, or optional native capabilities before consent and shell readiness. Production builds must gate inspectability, verbose logs, and bridge debug output.
- Bridge callbacks must be terminal and scoped: validate payloads, store callback ids per handler or request, return success/cancel/failure where relevant, remove callbacks after completion, and expose a page-ready acknowledgement for native overlays that wait for web route mount.
- Mobile precision workspaces need explicit runtime state for retryable media, zoom/pan, selected item, task switcher, progress counters, custom numeric/keypad input, invalid value feedback, handedness or control placement, persisted settings, and portrait/landscape composition differences.
- Mobile precision workspaces need a written state contract before implementation: route/context ids, task-source state, selected item or selected batch, cached task batch, cached media, persisted user preferences, context preferences, item-scoped geometry/scale, timer or quality gates, shell orientation state, and exit/unlock behavior.
- Precision media should avoid blank swaps: prioritize current-screen preloads, limit background preload concurrency, wait for image decode or readiness before replacing the displayed artifact, keep the previous artifact visible during load, provide in-region retry after automatic retry, and remove committed artifacts from memory/cache.
- Pan/zoom surfaces should use refs or imperative transforms for high-frequency pointer updates when the WebView would jank under render-loop state updates. Coordinate pinch, wheel, pan, tap, and page-swipe blocking explicitly, and persist only normalized scale values that can be cleared safely on item or orientation changes.
- Precision submission must be idempotent and explainable: block duplicate taps, submit only changed or still-pending items when possible, handle unprocessed-item defaults through an explicit confirmation policy, validate minimum time or quality gates before commit, clean cache after success, and recover from locked, completed, expired, permission, or no-work responses without losing route context.
- App-hosted precision workspaces need a native-plus-H5 route contract: the H5 route owns selected item, selected batch, task-source state, cached work, media readiness, scoring/input state, settings scope, final submit, and exception dialogs; the native shell owns orientation, WebView loading/error/retry, injected app info, storage bridge, back gesture, and page-ready or overlay cleanup.
- Orientation for precision work is a shell contract. H5 should request orientation through capability-detected bridge calls, skip no-op iOS rotation calls when they would hang, fall back to iframe or browser guidance where needed, clear measured geometry on rotation, and return to the expected portrait/default orientation on exit after the route has saved or unlocked state.
- Precision work must survive slow networks and repeated entry: distinguish cache hit, loading, stale, expired, retrying, and final states; keep user-visible context while refreshing; and map transport, permission, lock, expiry, and invalid-input failures to local copy and recovery controls.
- Accessibility is part of the implementation contract. Verify touch target size, focus order, programmatic names for icon-only controls, screen-reader labels for compact toolbars, text scaling, contrast, reduced-motion behavior, and keyboard/safe-area reachability for primary and recovery actions. Visible icon labels are not enough when compact modes hide text.
- App-hosted flows that can be interrupted need foreground/background restoration rules: storage scope, TTL, sensitive-field handling, route/context validation after restore, pagehide/pageshow/focus/blur coverage, and a visible stale-or-expired recovery path.
- Repeated-use mobile tools should separate local device state, user-scoped preferences, context-scoped preferences, and item-scoped preferences. Restore each layer independently, validate it against current permissions and available items, and reset only the invalid layer.
- Dense mobile data surfaces need deliberate landscape support: orientation request and failure guidance, dynamic table/canvas scroll bounds, fixed context columns when needed, search/filter/sort ownership, load-more thresholds, and cleanup back to the prior orientation on exit.
- Account deletion, logout-all, identity reset, or other high-consequence account actions need explicit disclosure loading, acknowledgement or cool-down gating, second confirmation, failure feedback, session/native-cache cleanup, and deterministic return to login or onboarding.
- Profile/settings shells need account/security, privacy, about/version, logout, and delete-account flows to coordinate native and web storage. A successful logout or account deletion should clear both web cache and native cache where present, then return to login/onboarding without leaving a stale WebView session.
6. Debug systematically when behavior is wrong.
- Reproduce on the smallest affected target: device, emulator, simulator, preview, or focused test.
- Treat repeated device, emulator, simulator, Gradle, Xcode, CocoaPods, adb, or NDK blockers as app infrastructure defects, not fresh one-off environment gaps. If the same blocker appears again, fix or harden the setup script, readiness check, or toolchain pin before reporting the app test layer as unavailable.
- Classify the failure by layer: route/deep link, render/layout, state transition, API/client contract, cache/offline, platform permission/capability, build/signing, or release config.
- Inspect logs, crash traces, network payloads, lifecycle events, permission state, local storage, and feature flags before changing code.
- Prove the cause on each affected platform; do not assume a Flutter/shared fix covers native host behavior or store/runtime configuration.
- Add regression evidence at the lowest sufficient layer, then run broader device/build smoke based on risk.
7. Verify on rendered surfaces.
- Run the repo's formatter, analyzer/linter, typecheck/build, and focused tests.
- Test-code authoring: pick the matching § from the decision table in `testing-strategy/references/test-code-authoring-patterns.md`; smell lint: `testing-strategy/references/fitness-functions.md` §4.1.4.
- **TC traceability**: Flutter/Dart tests link via `tcTest(['TC-XX-NNN'], 'desc', () { ... })` wrapper (registers at registration time, so `skip:` / `skipIf:` etc. still register correctly). Helper from `test-artifact-management/references/tc_helpers/tc.dart`, installed under `test/tc.dart`. Native Android/iOS use the language-appropriate wrapper convention (write a small helper that appends to `test/results/tc-map.jsonl`). See `test-artifact-management/references/tc-marker-conventions.md`. Before adding tests, `grep -rn 'tcTest.*"TC-[A-Z]' test/` plus the sidecar to check for existing coverage — extend rather than duplicate. When a TC is marked 废弃, grep both source and sidecar for that TC ID; follow deprecation cascade in `testing-strategy`. Tests without any `tcTest(...)` / equivalent helper call: prompt user only when the underlying code is also removed.
- **废弃级联:业务代码是否仍在用** — 每端原生 + 跨端各有套路:
1. Flutter / Dart:`grep -rEn "import ['\"]package:<pkg>/<file>|import ['\"]\.+/<file>" lib/`;运行 `dart analyze` 看 unused-import warning;路由表 `MaterialApp.routes` / GoRouter 配置内未注册的页面可删
2. Android(Kotlin/Java):`grep -rn "import .*<class>" app/src` + `./gradlew lintDebug` 看 UnusedResource / UnusedSymbol;AndroidManifest.xml 内未注册的 Activity/Service 实际不会被启动
3. iOS(Swift/ObjC):`grep -rn "import <module>\|<ClassName>" ios/`;Xcode 的 dead-code-stripping 报告;Storyboard / XIB 引用 grep `.storyboard` 与 `.xib`
4. 边界:跨端项目(KMM / 桥接代码)每层独立判,删一层不代表另一层也死;反射 / runtime 注解(Android)/ Objective-C runtime(iOS)grep 抓不到,需运行时验证
- Inspect the screen in a real device, emulator, simulator, preview, or captured screenshot for any visible UI change.
- For UI/UX redesign evidence, include the target device/form factor, safe-area, keyboard, orientation, dynamic type/text scale, loading/empty/error/final states, and a screenshot or equivalent rendered artifact; mark each dimension covered or `N/A` with a one-line reason. `N/A` is valid only when the reason names a verifiable structural fact, explains why that fact makes the dimension unreachable or unchanged for this slice, and includes a checkable pointer such as a file path, config key, or commit that resolves at review time. Persist evidence artifacts where reviewers can access them using sanitized/test accounts and redacting tokens, PII, credentials, private paths, and raw personal data; remove temporary smoke files or scripts before commit unless the repo intentionally owns them.
- Return the complete canonical client-record member defined in `../product-ui-ux-design/references/delivery-contract.md` for testing Phase 1 and the design verdict. The member includes its applied rule/decision, affected files/components, preserved behavior, exact command, immutable candidate binding, producer member/version actually exercised, artifacts, tested build/device targets, dimensions/states/input and lifecycle modes, criterion-mapped observations, coverage boundary, and gaps. A screenshot proves only the captured host/member states; it cannot close an unbound producer member. `testing-strategy` records aggregate sufficiency before the design owner records the candidate-bound verdict.
- In approval-sensitive runtimes, do not create a new one-off smoke file for every screenshot or slice. Prefer a repo-owned smoke harness, existing integration test, debug route, fixture flag, deep link, or already-created slice harness; if a temporary harness is genuinely unavoidable, reuse one stable harness for the whole batch, mutate it minimally, and explicitly delete it before commit (a throwaway harness is evidence scaffolding, not shippable code; verify it is gone in the pre-commit diff) unless the repo intentionally owns it. Repeated edit-approval prompts from throwaway helper files are an execution defect, not normal evidence collection.
- Capture Android evidence as approval-friendly single commands. In environments where command-prefix approval is used, do not combine `adb` with shell pipes, `>`, `>>`, command substitution, `&&`, or local filtering in the same command; those forms are split or de-scoped by the approval layer and can re-trigger prompts even when `adb` itself is approved. Use sanitized/test fixture screens, then run three standalone commands: `adb -s <serial> shell screencap -p "/sdcard/<artifact>.png"`, `adb -s <serial> pull "/sdcard/<artifact>.png" "<repo-evidence-path>/"`, and `adb -s <serial> shell rm "/sdcard/<artifact>.png"` in cleanup even when pull fails or the run aborts. Generate `<artifact>` from a safe basename alphabet (no spaces, globs, or path separators) and quote the `/sdcard` path in every command so `rm` targets exactly the file you created, and create the destination with `mkdir -p <repo-evidence-path>` before pulling. Never write credential, PII, account, or otherwise sensitive real-user screens to `/sdcard`; redact or fixture first. If the environment explicitly pre-approves shell redirection for `adb`, `adb -s <serial> exec-out screencap -p > <repo-evidence-path>/<artifact>.png` is acceptable and avoids device-side storage, but the transcript must record that redirection is approved for that command shape. Either way, validate the captured file is a non-empty valid PNG before relying on it: a failed pull, or an empty/zero-byte `exec-out` stream, means there is no host artifact — retry rather than counting it as evidence.
- A missing or absent design verdict remains `pending` and blocks the canonical contract's `complete`, MR-ready, merge-ready, and normal MR/handoff readiness states; a mobile screenshot proves only that the app rendered and is never design acceptance. If a hierarchy, composition, behavior, safe-area/keyboard, or brief criterion fails, return that failure instead of self-adjudicating taste; the design owner applies `delivery-contract.md`'s rejected-candidate path and requires fresh runtime evidence for the revised candidate.
- For platform enablement, verify each newly added platform with a real platform build and an install/run smoke on the relevant emulator or simulator. Adding the host project or passing one platform does not complete the slice; record the platform targets, commands, and screenshot or runtime evidence.
- For mobile runtime changes, device/emulator/simulator smoke is a completion gate when lower layers cannot prove the behavior. This includes changes to native capabilities, WebView/native bridge callbacks, foreground/background recovery, orientation/safe-area/keyboard behavior, storage/session restore, route/deep-link behavior, upload/media flows, permissions, and rendered loading/error/final states. If the runner is missing, first attempt normal setup; if still unavailable, stop at `pre-runtime-test-ready` or `blocked` and name the owner, attempted commands, residual risk, and next unblock action. `pre-runtime-test-ready` is handoff-only, not merge-ready, release-ready, or complete.
- For emulator readiness scripts, do not trust process launch or boot log text alone. Confirm the runner sees the target device, then verify direct device state such as `adb devices -l`, `adb -s <serial> get-state`, and platform boot readiness before calling Android ready; keep the serial in the output so later E2E commands target the same device.
- If the app or app-hosted H5 CI only builds or deploys packages, treat that as structural release evidence, not functional proof. Interaction, permission, lifecycle, upload/media, and recovery changes still need focused assertions plus rendered device or host-container smoke.
- Check accessibility labels, dynamic type/text scale, focus order, screen reader reachability, contrast, and touch targets.
- Check startup impact, scroll/frame stability, image loading, memory, app size, and network failure behavior when relevant.
- For release work, verify signing/flavors/schemes, environment config, store metadata implications, crash reporting, rollback/feature flag path, and smoke path on installable builds.
## Non-Negotiable Rules
- Do not treat one platform passing as proof that all platforms pass.
- Do not let reorderable, insertable, or filterable lists fall back to array-index identity. React Native `FlatList`/`SectionList` derive the key from `item.key` then `item.id` and only then the array index — so provide an explicit `keyExtractor` (or a stable `item.key`/`item.id`) whenever items lack a stable id; Flutter list children need a `ValueKey`/`ObjectKey` on stable item identity. Without a stable key a reorder/insert/delete makes the framework reuse element state on the wrong row (scroll offset, `TextField`/`TextEditingController` contents, animation progress, and selection/checkbox state bleed across rows, and inline `dispose` can hit the wrong element). Same reconciliation class as the web index-key rule.
- Do not call a Flutter platform scaffold complete until generated/local files have been reviewed, platform identifiers are aligned, and the target platform has fresh build plus runtime evidence.
- Do not hide a platform-specific failure by weakening shared behavior unless product explicitly accepts the difference.
- Do not add live network, credentials, or store-only dependencies to default fast tests.
- Do not ship UI changes without rendered inspection when the change affects layout, hierarchy, interaction, or visual state.
- Do not manufacture rendered evidence through repeated one-off temporary smoke files when an existing route, test, debug entry, fixture, or reusable harness can reach the same screen. Batch or reuse the harness and keep evidence collection inside the least-noisy approved command path.
- Do not add platform permission prompts, background work, biometric/payment flows, or push/deep-link behavior without explicit user-facing state and failure handling.
- Do not ship app-hosted H5/WebView UI with production-visible debug consoles, unbounded keyboard/layout fallbacks, or untested safe-area/orientation behavior on the target container.
- Do not ship debug-only instrumentation — debug bridges, in-app state/inspection servers, dev overlays/menus, network inspectors, verbose-token/remote-debug hooks, or mock-auth/bypass surfaces — in any build that can reach real users, real auth, or real PII (App Store/Play production AND TestFlight, enterprise, dogfood, internal/closed testing, staged rollout — scope by channel + data sensitivity, not an `isProduction` enum). Exclude it from the release dependency/target (not a runtime flag), and verify on the exact signed artifact per channel/variant with a release-blocking evidence row (digest + variant matrix + check output); a symbol grep alone or a manual pre-release removal is not the guard. See `references/mobile-quality-release.md` Release Readiness.
- Do not rely on CSS safe-area variables alone when the host app can inject physical status-bar or bottom-inset values; normalize them by device pixel ratio and verify iOS, Android, iframe, and browser fallbacks where applicable.
- Do not ship high-risk app actions with only optimistic UI or generic toast/error surfaces; users must be able to tell whether the operation is pending, succeeded, failed, retryable, blocked, or needs support after weak network, app restart, or foreground recovery.
- Do not treat a mobile API client as done until empty response, invalid JSON, non-2xx envelope, auth expiry, network failure, cancellation, and backend error message extraction are covered at the client or screen boundary when relevant.
- Do not scatter backend enum/string literals through app screens, deep links, native bridge payload handling, storage, analytics, or tests. Centralize finite-value parsing, display labels, defaults, and unknown-value behavior at the API/client-domain boundary, and keep raw literals only in clearly named boundary conversion tests that cover every known external value plus unknown/default behavior. Migrate existing non-boundary test raw literals for that value in the same pull request or mark each remaining use with `finite-value-debt: <task-ref> <owner> <deadline> <reason>`, even when the current slice does not introduce a new mapper.
- Do not debug app failures from code inspection alone when a runnable reproduction, trace, screenshot, or device log can be collected.
- Do not claim a mobile client fix is complete without naming the platform targets that were verified. If required device/emulator/simulator smoke is unavailable after remediation, the status is `pre-runtime-test-ready` or `blocked`, not complete.
## Reference Loading
- For source provenance, current extraction boundary, and keep/merge/discard decisions, read `references/source-evidence-map.md` when auditing or re-extracting this skill.
- For joint extraction from Figma design source AND the mobile app workspace (with package class mapping, design-token cross-validation, deprecation-marker detection), read `../skill-extraction-workflow/references/two-source-extraction-pattern.md`. Use when both sources are available; produces aligned design + implementation rules with cross-source token validation.
- For platform selection, shared-vs-native tradeoffs, and client boundaries, read `references/mobile-platform-boundaries.md`.
- For Flutter architecture, state, navigation, platform channels, testing, performance, and release checks, read `references/flutter-dev.md`.
- For native Android architecture, Compose, lifecycle, testing, quality, and release checks, read `references/android-dev.md`.
- For native iOS architecture, SwiftUI/UIKit boundaries, state, accessibility, testing, performance, and release checks, read `references/ios-dev.md`.
- For Kotlin Multiplatform (KMP) + Compose Multiplatform — adoption decision matrix, source-set hierarchy + expect/actual, iOS framework integration (direct / CocoaPods / SPM), Kotlin/Native runtime, Compose MP iOS Stable scope, code-reuse tiering, build/test matrix, platform-stability table — read `references/kotlin-multiplatform.md`. Required when evaluating shared logic across Android + iOS, or considering Compose Multiplatform iOS adoption.
- For mobile testing, accessibility, performance, observability, and release readiness across platforms, read `references/mobile-quality-release.md`.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!