← 返回
未分类 中文

Thumbnail QA

Thumbnail image QA: browse every page, detect images poorly cropped in their containers, compute fine-grained object-position values, and auto-fix with befor...
缩略图图像QA:遍历每页,检测容器中裁剪不佳的图像,计算细粒度对象位置值,并自动修复befor...
kaicianflone kaicianflone 来源
未分类 clawhub v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 202
下载
💾 0
安装
1
版本
#latest

概述

Thumbnail QA Skill

Overview

This skill browses every page of the Next.js site, detects images that are poorly cropped

in their containers, computes optimal object-position values based on focal point analysis,

and applies fine-grained CSS fixes — each with a before/after screenshot and its own atomic commit.


Setup

# Find browse binary
B=$(command -v browse 2>/dev/null || echo "$HOME/.claude/skills/gstack/browse/bin/browse")

# Verify browse is available
if [ ! -x "$B" ]; then
  echo "ERROR: browse binary not found at $B"
  echo "Install gstack or ensure browse is on PATH."
  exit 1
fi

Check working tree cleanliness

Run git status --porcelain. If output is non-empty, ask the user:

> "Your working tree has uncommitted changes. Before running thumbnail QA, would you like to:

> 1. Commit your changes now

> 2. Stash your changes (git stash)

> 3. Abort and handle it manually

>

> Which option?"

Proceed only after the working tree is clean (or the user explicitly chooses option 3 and understands the risk).

Check dev server

# Check if dev server is running
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q "200\|301\|302" && echo "running" || echo "not running"

If not running, start it:

npm run dev &
DEV_PID=$!
# Wait for server to be ready (up to 30 seconds)
for i in $(seq 1 30); do
  curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 | grep -q "200\|301\|302" && break
  sleep 1
done

Configure viewport and output directory

# Set viewport
$B viewport 1280x800

# Create output directory
mkdir -p .gstack/thumbnail-qa/screenshots

# Ensure .gstack/ is in .gitignore
if ! grep -q "^\.gstack/" .gitignore 2>/dev/null; then
  echo ".gstack/" >> .gitignore
  git add .gitignore
  git commit -m "chore: add .gstack/ to .gitignore"
fi

Phase 1: Build Image Registry

Step 1.1 — Grep for fill-mode Next.js Image components

grep -rn "<Image" --include="*.tsx" --include="*.ts" --include="*.jsx" --include="*.js" . \
  | grep -v "node_modules" \
  | grep "fill"

For each file that contains a match, READ THE ENTIRE FILE using the Read tool (not just a 25-line window). Understanding the full component is required to correctly resolve conditional classNames and dynamic src values.

Step 1.2 — Extract per-image metadata

For each with the fill prop, record:

FieldDescription
--------------------
idSequential number (1, 2, 3, ...)
file_pathAbsolute path to TSX file
line_numberLine where starts
page_routeURL path (e.g., /, /about, /connect)
srcValue of the src prop (string or expression)
classNameFull className string or expression
current_positionExtracted object-position class (e.g., object-top, object-[50%_25%], or none)
container_classesClasses on the wrapping div (especially overflow-hidden, aspect-, h-, w-*)
conditional_keyIf className is conditional, the field/expression it keys on
notesAny dynamic/conditional complexity

Step 1.3 — Filter out non-candidates

Skip images where:

  • No fill prop present
  • src contains "logo" (case-insensitive)
  • Container div has rounded-full class (avatar/icon treatment)
  • Image uses object-contain (intentionally letterboxed)

Step 1.4 — Handle dynamic values

Dynamic src (e.g., src={photo.src} or src={item.image}):

  • Expand each unique value in the data source as a separate registry entry
  • Read the data file to enumerate all actual values

Conditional className (e.g., className={category.id === 'worship' ? 'object-top' : 'object-center'}):

  • Record conditional_key as the field driving the condition (e.g., category.id)
  • Document each branch as a separate sub-entry
  • When applying fixes in Phase 3, use a position map keyed on the SAME field

Step 1.5 — Print registry

Print a numbered list of all candidate images:

IMAGE REGISTRY (N candidates)
================================
[1] src: /images/team/alice.jpg
    route: /about
    file: components/TeamSection.tsx:42
    current position: object-top
    container: relative overflow-hidden h-64 w-full

[2] src: (dynamic) photo.src — 6 entries from data/photos.ts
    route: /gallery
    file: components/Gallery.tsx:18
    current position: object-center (static)
    conditional_key: none
    notes: expand 6 photo entries individually
...

Expected total: ~15 entries across the full site.


Phase 2: Visual Analysis

Step 2.1 — Group images by route

Group all registry entries by page_route to minimize browser navigation (visit each page once).

Step 2.2 — Navigate and screenshot

For each page:

# Navigate
$B goto http://localhost:3000/PAGE_ROUTE

# Wait for images to load
sleep 1

# Screenshot the PARENT DIV (overflow-hidden container), not the img tag
$B screenshot ".gstack/thumbnail-qa/screenshots/IMAGE_ID-current.png" \
  --selector "div.relative.overflow-hidden:has(img[src*='FILENAME'])"

If the selector fails (dynamic src, complex DOM), fall back to:

$B screenshot ".gstack/thumbnail-qa/screenshots/IMAGE_ID-current.png" \
  --selector "CLOSEST_IDENTIFIABLE_PARENT"

Document selector used in analysis notes.

Step 2.3 — Analyze original image

Use the Read tool to view the original image from public/:

Read: public/images/PATH_TO_IMAGE.jpg

This gives a visual view of the full uncropped image.

Step 2.4 — Determine focal point

Analyze the full image and classify:

Photo TypeFocal Point Priority Rules
---------------------------------------
Person / PortraitFace fully visible, forehead to chin. Eyes positioned in upper third of container.
Group / TeamMaximize number of visible faces. Favor horizontal center. Avoid cutting anyone.
Architecture / BuildingShow full structure. Roofline and entryway both visible when possible.
Worship / Activity / EventKeep primary action or speaker in frame. Avoid cropping hands/gestures.
Landscape / SceneKey subject centered; horizon placement depends on sky vs ground interest.

Record focal point as percentage coordinates: X_PERCENT% Y_PERCENT% (e.g., 50% 25%).

Step 2.5 — Evaluate current crop

Compare the screenshotted crop against the focal point rules. Determine:

  • OK: Current crop keeps the focal point visible and well-framed. The face (forehead to chin) is fully in frame for people photos. The existing position class achieves an acceptable result.
  • NEEDS_FIX: Focal point is clipped, partially obscured, or poorly positioned

Do NOT mark an image as NEEDS_FIX if the current position already satisfies the focal point rules. If object-top shows the face correctly, leave it. Do not replace a working value with a computed one that might be worse. The goal is curated results, not uniform syntax.

Step 2.6 — Compute optimal object-position

How object-position works: object-position: X Y aligns the X% point of the image to the X% point of the container, and the Y% point of the image to the Y% point of the container.

  • object-top (= 50% 0%) pins the top of the image to the top of the container. Best for tall portraits where the face is in the upper portion.
  • object-center (= 50% 50%) centers the image. Works when the subject is in the middle.
  • object-bottom (= 50% 100%) pins the bottom.

Key insight for tall portrait images in short wide containers: The container crops a horizontal band from the middle of the image by default. To show content near the TOP of a tall image, use object-top or very low Y values (0-10%). A value like object-[50%_20%] does NOT mean "show the top 20%" — it shifts the view DOWN, which is the opposite of what you want for faces near the top of a portrait.

Rules of thumb:

  • Face in the top 30% of a tall portrait → use object-top or object-[50%_5%]
  • Face at center of image → object-center is fine
  • Face in bottom third → use higher Y values (60-80%)
  • Standard values (object-top, object-center) are preferred when they work — only use arbitrary values when fine-tuning is genuinely needed

Round to nearest 5% for cleaner values.

Record per-image analysis:

[1] alice.jpg — NEEDS_FIX
    photo type: portrait (tall image, face near top)
    focal point: 50% 20% (face, eyes near top of image)
    current: object-center (face cut off — container shows middle of image)
    recommended: object-top
    reason: Face is in upper portion of tall portrait — object-top pins the top of the image to show the face

[2] group-photo.jpg — OK
    photo type: group
    focal point: 50% 40% (faces clustered in center)
    current: object-top
    recommended: keep current
    reason: object-top already shows all faces — do not replace a working value

Phase 3: Apply Fixes

Process each NEEDS_FIX image in order.

Step 3.1 — Take "before" screenshot

$B goto http://localhost:3000/PAGE_ROUTE
sleep 1
$B screenshot ".gstack/thumbnail-qa/screenshots/IMAGE_ID-before.png" \
  --selector "div.relative.overflow-hidden:has(img[src*='FILENAME'])"

Step 3.2 — Apply the CSS fix

Select the correct edit pattern based on the registry entry:

Pattern A — Static className string

Find the existing object-* class (or position to insert one) and replace/add:

// Before
className="relative overflow-hidden h-64 object-center"
// After
className="relative overflow-hidden h-64 object-[50%_25%]"

Use the Edit tool. Do not change any other part of the className.

Pattern B — Conditional ternary (keyed on a field)

Replace the ternary with a position map that keys on the SAME field as the original condition:

// Before (keyed on category.id)
className={`... ${category.id === 'worship' ? 'object-top' : 'object-center'}`}

// After (position map, same key: category.id)
const positionMap: Record<string, string> = {
  worship: 'object-[50%_20%]',
  music: 'object-[50%_35%]',
  community: 'object-[50%_45%]',
}
// In JSX:
className={`... ${positionMap[category.id] ?? 'object-center'}`}

If the conditional keys on photo.caption or similar string content rather than an ID, prefer adding a position field to the data objects and reading from that instead.

Pattern C — Existing object-[X_Y] arbitrary value

Replace only the arbitrary value portion:

// Before
object-[50%_50%]
// After
object-[50%_25%]

Step 3.3 — Wait for hot reload

sleep 2

Step 3.4 — Take "after" screenshot

$B screenshot ".gstack/thumbnail-qa/screenshots/IMAGE_ID-after.png" \
  --selector "div.relative.overflow-hidden:has(img[src*='FILENAME'])"

Step 3.5 — Verify fix against focal point rules

Display both before and after screenshots inline. Then re-apply the focal point rules from Step 2.4 against the AFTER screenshot:

  • For people photos: is the face FULLY visible (forehead to chin)? Are the eyes in the frame?
  • For group photos: are MORE faces visible than before, not fewer?
  • For architecture: is the structure MORE complete than before?

If the after screenshot fails any focal point rule that the BEFORE screenshot passed: the fix made things WORSE. This is the most common failure mode — the computed position looked right on paper but the actual crop is worse.

  1. Revert the file change using Edit (restore the original className)
  2. Mark the entry as MANUAL_REVIEW with a note: "computed position {X} degraded framing vs original {Y}"
  3. Do NOT commit

If the after screenshot is ambiguous (marginal improvement, unclear if better): mark as MANUAL_REVIEW rather than committing. Err on the side of keeping the original position.

Only proceed to commit if the after screenshot is CLEARLY better than the before.

Step 3.6 — Atomic commit

After a confirmed good fix:

git add PATH/TO/CHANGED_FILE.tsx
git commit -m "style: reposition FILENAME thumbnail — REASON"

# Example:
# git commit -m "style: reposition alice.jpg thumbnail — pull frame up to keep face centered"

One commit per image. Exception: when multiple images share the same conditional block in one file (e.g., a position map covers 6 images in one component), commit them together with a message listing all affected images.


Phase 4: Summary Report

Step 4.1 — Collect results

Gather all per-image outcomes:

  • OK: No fix needed
  • REPOSITIONED: Fix applied and committed
  • SKIPPED: Filtered out (logo, icon, object-contain)
  • MANUAL_REVIEW: Fix attempted but result was poor or ambiguous

Step 4.2 — Print structured report

THUMBNAIL QA REPORT
===================
Date:      YYYY-MM-DD
Viewport:  1280x800
Pages checked: N

RESULTS SUMMARY
---------------
Total candidates checked:  N
  OK (no fix needed):      N
  Repositioned:            N
  Skipped (filtered):      N
  Manual review needed:    N

REPOSITIONED IMAGES
-------------------
| # | Image | Page | Before Class | After Class | Reason |
|---|-------|------|--------------|-------------|--------|
| 1 | alice.jpg | /about | object-top | object-[50%_15%] | Face centered with forehead visible |
...

OK IMAGES (no change)
---------------------
| # | Image | Page | Position | Notes |
...

SKIPPED IMAGES
--------------
| # | Image | Reason |
...

MANUAL REVIEW NEEDED
--------------------
| # | Image | Page | Attempted Fix | Issue |
...

COMMITS
-------
  abc1234  style: reposition alice.jpg thumbnail — ...
  def5678  style: reposition team-photo.jpg thumbnail — ...

SCREENSHOTS
-----------
  .gstack/thumbnail-qa/screenshots/

Step 4.3 — Save report to disk

REPORT_DATE=$(date +%Y-%m-%d)
REPORT_PATH=".gstack/thumbnail-qa/report-${REPORT_DATE}.md"
# Write the structured report above to $REPORT_PATH

Step 4.4 — Final message

> "Thumbnail QA complete. N images repositioned across N pages. Report saved to .gstack/thumbnail-qa/report-YYYY-MM-DD.md. Run /thumbnail-qa again after your next image upload."

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-05-28 13:38

安全检测

腾讯云安全 (Keen)

队列中

腾讯云安全 (Sanbu)

队列中

🔗 相关推荐

design-media

Nano Banana Pro

steipete
使用 Nano Banana Pro (Gemini 3 Pro Image) 生成或编辑图像。支持文生图、图生图及 1K/2K/4K 分辨率,适用于图像创建、修改及编辑请求,使用 --input-image 指定输入图像。
★ 435 📥 117,962
design-media

Openai Whisper

steipete
使用 Whisper CLI 进行本地语音转文字(无需 API 密钥)
★ 335 📥 94,773
design-media

UI/UX Pro Max

xobi667
提供 UI/UX 设计智能与实现指导,帮助打造精美界面。适用于 UI 设计、UX 流程、信息架构、视觉风格、设计系统/标记、组件规格、文案/微文案、无障碍及前端 UI(HTML/CSS/JS、React、Next.js、Vue、Svelte
★ 228 📥 48,960