Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
日本語の概要は準備中です。原文の説明を表示しています。
Use when you are about to hand off any change to a screen or component on a mobile repository - the size, text-scale and dark-mode matrix, the exact tool calls to see your own build, and the checklist that decides whether the screen is done.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
A green build and a passing widget test say the code compiles and the tree is what the test asserted. Neither says the screen looks right on a 360 phone at 200% text, on a tablet, in landscape, or in dark mode. Default assumption: the change is NOT done until you've looked at it rendered — be the skeptic here, not the advocate.
Core principle: two layers, always both. A matrix test in the suite catches overflow mechanically on every future change; looking at the rendered screen catches what a test assertion can't (a placeholder glyph, a clipped icon, invisible text in dark mode).
360×640 (phone) · 430×932 (large phone) · 640×360 landscape · 768×1024 (tablet) — each at text scale 1.0 and 2.0, and in dark mode.
Flutter (verified, Flutter 3.41.6 — 11/11 green after the fix below):
const sizes = <String, Size>{
'phone 360x640': Size(360, 640),
'large phone 430x932': Size(430, 932),
'landscape 640x360': Size(640, 360),
'tablet 768x1024': Size(768, 1024),
};
for (final e in sizes.entries) {
for (final scale in [1.0, 2.0]) {
testWidgets('PriceRow at ${e.key}, text x$scale, dark', (tester) async {
tester.view.physicalSize = e.value * 3;
tester.view.devicePixelRatio = 3;
tester.platformDispatcher.textScaleFactorTestValue = scale;
addTearDown(tester.view.reset);
addTearDown(tester.platformDispatcher.clearAllTestValues);
final handle = tester.ensureSemantics();
await tester.pumpWidget(host(PriceRow(title: longRealisticTitle, price: '1.299,00 TL', onAdd: () {}),
brightness: Brightness.dark));
expect(tester.takeException(), isNull); // overflow = FlutterError here
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
handle.dispose();
});
}
}
Verified result: Row[Expanded(title), price, IconButton] passes every case at text 1.0. At text 2.0 it fails — "A RenderFlex overflowed by 82 pixels on the right" at 360 (122 at 320, 12 at 430). The fix that passes all cases: wrap in LayoutBuilder; if constraints.maxWidth < 280 * MediaQuery.textScalerOf(context).scale(1), render Column[title, Row[Expanded(price), IconButton]] instead of the single Row. This is the ❌/✅ worked example for both this skill and mobile-ui-ux.
Compose: DeviceConfigurationOverride(ForcedSize(DpSize(360.dp, 640.dp)) then FontScale(2f) then DarkMode(true)) { ... } around the composable under test, repeated per matrix cell.
SwiftUI: #Preview with .environment(\.dynamicTypeSize, .accessibility3), .preferredColorScheme(.dark) and traits: .landscapeLeft (typechecks against the iOS 18 simulator SDK — verified) — plus snapshot tests if the repo already has them.
Pick the first path that applies:
(a) Host-local emulator/simulator — your own build.
mobile_launch_app {repository_id} — this takes the device lease and installs the build registered on the deploy target (the stage build), not your branch.flutter build apk --debug
adb devices
adb -s <serial> uninstall <pkg> # only needed on INSTALL_FAILED_UPDATE_INCOMPATIBLE
adb -s <serial> install build/app/outputs/flutter-apk/app-debug.apk
adb -s <serial> shell monkey -p <pkg> -c android.intent.category.LAUNCHER 1
Native Android: ANDROID_SERIAL=<serial> ./gradlew installDebug.flutter build ios --simulator --debug
xcrun simctl install <udid> build/ios/iphonesimulator/Runner.app
xcrun simctl launch <udid> <bundle-id>
Native iOS: xcodebuild -scheme <S> -destination 'id=<udid>' -derivedDataPath build build, then simctl install the .app.mobile_wait_for the new element → mobile_screenshot → mobile_rotate {orientation: landscape} → mobile_screenshot → mobile_rotate {orientation: portrait}.adb shell settings put system font_scale 2.0 and adb shell cmd uimode night yes; reset with font_scale 1.0 and night no.xcrun simctl ui <udid> content_size accessibility-extra-extra-extra-large and xcrun simctl ui <udid> appearance dark (simctl options verified). Reset with content_size large and appearance light.mobile_read_ui {filter: "clickable=\"true\""} — see below.mobile_release_device as your last mobile step.(b) Remote/shared phone with no local install path. It shows the stage build only — use (c) or (d) to see your own change; say so in the closing message.
(c) Flutter widget previewer (no device needed, verified). Add @Preview(name: 'phone dark 2x', size: Size(360, 640), brightness: Brightness.dark, textScaleFactor: 2.0) beside the widget (import 'package:flutter/widget_previews.dart'). Run mkdir -p /tmp/tt-<task key> && flutter widget-preview start --web-server > /tmp/tt-<task key>/preview.log 2>&1 &. The log prints lib/main.dart is being served at http://localhost:<port>; then browser_navigate → browser_screenshot. The previewer runs on the web: a widget touching dart:io or a plugin needs a fake in the preview.
(d) Native screenshots over loopback. Start a loopback server over the screenshot directory, e.g. mkdir -p /tmp/tt-<task key> && python3 -m http.server 8799 --bind 127.0.0.1 -d <dir> > /tmp/tt-<task key>/shots.log 2>&1 & (py -3 instead of python3 on Windows). Then browser_navigate http://127.0.0.1:8799/<file>.png → browser_screenshot. file:// is refused by the browser guard; loopback http is allowed by default.
If a device call reports the device busy, stop: the task is parked and resumes by itself — do not retry in a loop.
mobile_read_ui {filter: "clickable=\"true\""}. Every line needs a non-empty text or content-desc; bounds ≥48dp × (dpi/160) px on Android, ≥44pt on iOS. Verify the line-per-node shape on first use — the exact field names can differ by platform.
Collect every finding from every matrix cell before touching code. Fix everything from the batch in one pass, then re-run the full matrix once. At most two rounds total — whatever is still open after round two goes into the closing message, honestly, rather than triggering a third silent pass.
Name the render path used (device-own-build / device-stage-only / previewer / simulator) and, for each matrix size checked, what you saw. A known gap reported honestly is not a failure; an unverified claim of "done" is.
mobile_launch_app (with no own-build install) as proof of your change — it's the stage build.--update-goldens or regenerating a snapshot to make a red matrix test green.まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases
日本語の概要は準備中です。原文の説明を表示しています。
Use when the diff adds or changes an endpoint, resolver, RPC, job or query that takes an object id, a role check, a request binding or a tenant filter - BOLA/IDOR, function-level authorization, mass assignment and tenant scoping
日本語の概要は準備中です。原文の説明を表示しています。
Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal
日本語の概要は準備中です。原文の説明を表示しています。
Use when a task changes any screen, form, dialog, menu or control - Lighthouse/axe scan of the changed screens, a keyboard walk, and the thresholds that fail a task
日本語の概要は準備中です。原文の説明を表示しています。
How to work a task returned with review, QA or UAT findings. Use when a task is in need_revision or PR review comments are in your context.
日本語の概要は準備中です。原文の説明を表示しています。
Use when deciding whether a request needs an analiz task before implementation - the conditions that require the architect's analysis versus going straight to implementation
日本語の概要は準備中です。原文の説明を表示しています。