From 90635bf8f2a719b91b54d6e611c07f410fc7acfc Mon Sep 17 00:00:00 2001 From: Stefano Fiorini Date: Fri, 27 Mar 2026 12:02:47 -0500 Subject: [PATCH] Improve gallery photo-review guidance for property assessment --- skills/property-assessor/SKILL.md | 29 ++++++++++++++++++++++++++- skills/web-automation/SKILL.md | 15 ++++++++++++++ skills/web-automation/scripts/flow.ts | 14 ++++++++++--- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/skills/property-assessor/SKILL.md b/skills/property-assessor/SKILL.md index 5687253..1a9a9d6 100644 --- a/skills/property-assessor/SKILL.md +++ b/skills/property-assessor/SKILL.md @@ -63,6 +63,25 @@ For the target property, capture when available: Always look at the listing photos when they are available. Do not rate a property only from structured text. +### Required gallery workflow + +When the source site has a photo gallery or modal lightbox, you must explicitly open it and verify that you are seeing distinct images, not just a gallery preview tile or repeated screenshot of the first image. + +Use `web-automation` for this. Preferred process: +1. Open the listing page. +2. Click the gallery entry point such as `See all photos`, `See all 29 photos`, or the main hero image. +3. Confirm the gallery/lightbox is actually open. +4. Capture the first image. +5. Advance through the gallery using the actual next-arrow control inside the gallery, not a blind keyboard press unless you have confirmed it changes the image. +6. After each advance, confirm the image changed before treating it as reviewed. +7. Review enough images to cover the key rooms and exterior, and for smaller listings aim to review all photos when practical. +8. If navigation fails, say so explicitly and do not claim that you reviewed all photos. + +Minimum honesty rule: never say you "looked at all photos" unless you successfully inspected distinct gallery images one by one. +A gallery landing page, collage view, repeated first image, or a single screenshot of the listing page does **not** count as full photo review. + +### What to inspect in the photos + At minimum, note: - overall finish level: dated, average, lightly updated, fully updated - kitchen condition: cabinets, counters, backsplash, appliance quality @@ -72,8 +91,16 @@ At minimum, note: - obvious make-ready issues: paint, damaged trim, old fixtures, mismatched finishes, worn surfaces - visible missing items: refrigerator, washer/dryer, range hood, dishwasher, etc. - any signs of deferred maintenance or water intrusion visible in photos +- exterior/common-area condition when visible +- balconies, decks, sliders, windows, and waterfront-facing elements for condos/townhomes near water -If photos are weak or incomplete, say so explicitly and lower confidence. +### If photo review is incomplete + +If photos are weak, incomplete, blocked, or the gallery automation fails: +- say so explicitly +- lower confidence +- avoid strong condition claims +- do not infer turnkey condition from marketing text alone ## Normalization / make-ready adjustment diff --git a/skills/web-automation/SKILL.md b/skills/web-automation/SKILL.md index 2df5e84..15ac72d 100644 --- a/skills/web-automation/SKILL.md +++ b/skills/web-automation/SKILL.md @@ -127,6 +127,21 @@ Example: npx tsx flow.ts --instruction 'go to https://search.fiorinis.com then type "pippo" then press enter then wait 2s' ``` +### Gallery/lightbox workflows + +For real-estate listings and other image-heavy pages, prefer opening the gallery/lightbox explicitly and verifying that each navigation step produces a distinct image. + +Practical rules: +- Do not treat a listing page hero image, gallery collage, or modal landing view as full photo review. +- After opening the gallery, capture the first image, then use the actual in-gallery next control. +- Confirm the image changed before counting the next screenshot as reviewed. +- If a generic `Next` control exits the gallery or returns to the listing shell, stop and adjust the selector/interaction; do not claim the photos were reviewed. +- Blind `ArrowRight` presses are not reliable enough unless you have already verified that they advance the gallery on that site. +- For smaller listings, review all photos when practical; otherwise review enough distinct photos to cover kitchen, baths, living areas, bedrooms, exterior, and any waterfront/balcony/deck elements. +- If automation cannot reliably advance the gallery, say so explicitly in the final answer. + +Where possible, prefer specific controls over vague ones, for example a gallery-specific next-arrow button rather than a page-level `Next` button. + ## Compatibility Aliases - `CAMOUFOX_PROFILE_PATH` still works as a legacy alias for `CLOAKBROWSER_PROFILE_PATH` diff --git a/skills/web-automation/scripts/flow.ts b/skills/web-automation/scripts/flow.ts index a355ad3..b7671fb 100644 --- a/skills/web-automation/scripts/flow.ts +++ b/skills/web-automation/scripts/flow.ts @@ -6,7 +6,7 @@ import { launchBrowser } from './browse'; type Step = | { action: 'goto'; url: string } - | { action: 'click'; selector?: string; text?: string } + | { action: 'click'; selector?: string; text?: string; role?: string; name?: string } | { action: 'type'; selector?: string; text: string } | { action: 'press'; key: string; selector?: string } | { action: 'wait'; ms: number } @@ -41,7 +41,13 @@ function parseInstruction(instruction: string): Step[] { continue; } - // click on "text" or click #selector + // click on "text" or click #selector or click button "name" + const clickRole = p.match(/^click\s+(button|link|textbox|img|image|tab)\s+"([^"]+)"$/i); + if (clickRole) { + const role = clickRole[1].toLowerCase() === 'image' ? 'img' : clickRole[1].toLowerCase(); + steps.push({ action: 'click', role, name: clickRole[2] }); + continue; + } const clickText = p.match(/^click(?: on)?\s+"([^"]+)"/i); if (clickText) { steps.push({ action: 'click', text: clickText[1] }); @@ -203,6 +209,8 @@ async function runSteps(page: Page, steps: Step[]) { case 'click': if (step.selector) { await page.locator(step.selector).first().click({ timeout: 15000 }); + } else if (step.role && step.name) { + await page.getByRole(step.role as any, { name: new RegExp(escapeRegExp(step.name), 'i') }).first().click({ timeout: 15000 }); } else if (step.text) { const clicked = await clickByText(page, step.text); if (!clicked) { @@ -212,7 +220,7 @@ async function runSteps(page: Page, steps: Step[]) { } } } else { - throw new Error('click step missing selector/text'); + throw new Error('click step missing selector/text/role'); } try { await page.waitForLoadState('domcontentloaded', { timeout: 10000 });