본문으로 건너뛰기
cccskills
무료GitHub에 공개

mobile-visual-self-review

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.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(1)

  • SKILL.md8.7 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Mobile Visual Self-Review

Overview

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).

The matrix

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.

Layer 1 — a matrix test in the repo's test suite

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.

Layer 2 — look at the rendered screen

Pick the first path that applies:

(a) Host-local emulator/simulator — your own build.

  1. 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.
  2. Put your own build on the device:
    • Android, local emulator:
      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.
    • iOS, local simulator:
      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.
  3. mobile_wait_for the new element → mobile_screenshot → mobile_rotate {orientation: landscape} → mobile_screenshot → mobile_rotate {orientation: portrait}.
  4. Large text and dark mode, local only:
    • Android: 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.
    • iOS: 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.
  5. TalkBack proxy: mobile_read_ui {filter: "clickable=\"true\""} — see below.
  6. 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.

TalkBack/VoiceOver proxy on device

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.

Checklist (yes/no, each with a threshold)

  • Overflow / clipping: none in any cell of the matrix.
  • Insets: essential content clear of the status bar, notch, home indicator and keyboard.
  • Targets: touch targets ≥48dp / 44pt.
  • Text: essential text not truncated at 2.0; scales with the system setting (no hard-coded sizes).
  • States: loading, empty and error all reachable and designed.
  • Dark mode: intentional (no black-on-dark, no invisible icons); status-bar icons legible.
  • Consistency: one primary action; the same control looks identical everywhere (built from the same atom).
  • Copy: in the locale; no placeholder text.
  • Navigation: landscape keeps the primary action reachable; tablet doesn't stretch text past ~600–840dp (constrain or use two panes).

Batch, then fix, then one re-check

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.

Closing message

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.

Common Mistakes

  • Treating a screenshot taken right after mobile_launch_app (with no own-build install) as proof of your change — it's the stage build.
  • Skipping the 2.0 text-scale cell because 1.0 looked fine — the overflow in the worked example only appears at 2.0.
  • --update-goldens or regenerating a snapshot to make a red matrix test green.
  • More than two rounds of screenshot → fix → screenshot on the same screen.

Red Flags

  • A closing message that names no render path.
  • "Looks fine" without ever having set text scale to 2.0 or dark mode.
  • A device-busy result retried instead of treated as parked.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when writing acceptance criteria for a task - express each as an observable Given/When/Then that QA can execute, including negative cases

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

Use on every UI change - semantic HTML, labels for controls, keyboard-navigable dialogs/menus, visible focus, and never color as the only signal

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

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.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

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

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

makifbaysal/tasktrooper1172026년 10월 18일 업데이트

makifbaysal의 스킬 모두 보기

이 스킬의 문제 신고