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 the repository keeps its alerting in code, or a person asks for alerts outside TaskTrooper - PrometheusRule YAML, a Cloud Monitoring alert policy in Terraform, a Datadog monitor, each routed to TaskTrooper's alert ingest URL so it opens an incident here
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.