Skip to content

shapeOf: keep fixed ids that contain a useId fragment (#107) - #117

Merged
gabe4coding merged 4 commits into
mainfrom
quality/107-useid-fragment
Oct 26, 2026
Merged

gabe4coding merged 4 commits into
mainfrom
quality/107-useid-fragment

Conversation

@gabe-bots

@gabe-bots gabe-bots Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Step 2/3 of quality/108-goto-query. Previous: quality/108-goto-query. Next: quality/109-long-context-note.

Part of #107

Intent

A fixed id that only contains a useId fragment stays in the shape hash. A no-judge replay of swapped buttons is inconclusive.

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  id["aria-controls value"] --> token["whole useId token?"]
  token -->|yes| drop["drop the attribute from the hash"]
  token -->|no| keep["keep tab_r_1_panel in the hash"]
  keep --> swap["ids swap on reload"]
  swap --> miss["shapes differ"]
  miss --> stop["no-judge click is inconclusive"]
Loading

Drift

The issue text says the whole attribute value. This branch uses token bounds, so aria-labelledby=":r1: :r2:" still matches. That is the brief’s rule.

Decisions

  • LOCK_VERSION goes from 2 to 3. New lock files are version 3. plain ignores a version 2 file. A version 1 file still replays locators. Door: one-way. Old version 2 locks re-record once.
  • GENERATED_ID keeps the old loose match, so the id strategy still skips tab_r_1_panel. Door: two-way.
  • No e2e spec. The site cannot tell record from replay, and a real swap is inconclusive in no-judge. Door: two-way.

Evidence

Before: both buttons hash to 71ca4835…aaa683, equal true. Lock replay click status pass with (replayed).

After: hashes differ, equal false. menu:r1:list and menu:r2:list differ too. The two make it fail tests pass. npm run verify: 580 pass, 0 fail.

Locator benchmark on this branch: 144/144 locators, wrong hits 0, including use-id-regenerated. main was not re-run.

The reload assertion in record-locator.test.ts now uses the next real id (_r_6n_ to _r_6o_, :r2:, «r4», base-ui-_r_6o_, radix-:r3:, headlessui-menu-button-2). `${id}x` is no longer a useId token.

Review round 1

Before: a version 2 lock replayed the click as pass with (replayed). Fail-first: 1 fail.

After: that lock makes the click inconclusive. The detail matches no recorded locator. No step is marked (replayed). New files are version 3. npm run verify: 584 pass, 0 fail. Head e5550b7. The branch was rebased onto main after #115 merged, so verify ran on the commits before the rebase.

Reading map

  • Tests changed: src/browser/record-locator.test.ts, src/browser/lock.test.ts.
  • Lock format: LOCK_VERSION is 3.

Stack

Step 2/3 of quality/108-goto-query. Previous: quality/108-goto-query. Next: quality/109-long-context-note.

@gabe-bots gabe-bots Bot added the needs-gabe Needs Gabe's decision before work continues label Oct 26, 2026
@gabe-bots

gabe-bots Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@gabe-bots

gabe-bots Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Stack: merge 116 first. This branch includes its commits.

@gabe4coding

Copy link
Copy Markdown
Owner

@greptile review

@greptile-apps

greptile-apps Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] Refines how element IDs are classified and URL navigation works.

The PR appears safe to merge, with a non-blocking fix needed for explicit empty query and hash overrides.

Findings

  1. P2 Empty URL overrides are ignored ▶
Fix with agent prompt
### Issue 1
src/browser/steps.ts:180-181
`resolveUrl` restores the base query or hash even when the target explicitly clears it. With base `https://host/app?filter=active#old`, target `/login?` gets `filter=active` again, and `/login#` gets `old` again. Specs trying to clear a filter or anchor therefore keep the old value.

Check whether the target contains each delimiter rather than whether `resolved.search` or `resolved.hash` is nonempty. Add tests for explicit empty overrides.

```suggestion
  if (!relative.split('#')[0].includes('?')) resolved.search = parsed.search;
  if (!relative.includes('#')) resolved.hash = parsed.hash;
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Keeps fixed IDs containing a useId fragment in the shape hash and adds swapped-button replay coverage.

  • Position-based replay keeps fixed ID fragments to spot swapped buttons.
  • Relative goto keeps the app path and base query or hash.

Intentional choices acknowledged by gabe-bots[bot]: keep LOCK_VERSION at 2, keep loose GENERATED_ID matching, and omit a new shape end-to-end spec because the fixture cannot distinguish recording from replay. Token-bound matching, including multiple generated references, is also intentional.

Reviews (1) · Last reviewed commit: "Merge quality/108-goto-query into qualit..." · Reviewed by Greptile

Comment thread src/browser/steps.ts Outdated
Comment on lines +180 to +181
if (!resolved.search) resolved.search = parsed.search;
if (!resolved.hash) resolved.hash = parsed.hash;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Empty URL overrides are ignored

resolveUrl restores the base query or hash even when the target explicitly clears it. With base https://host/app?filter=active#old, target /login? gets filter=active again, and /login# gets old again. Specs trying to clear a filter or anchor therefore keep the old value.

Check whether the target contains each delimiter rather than whether resolved.search or resolved.hash is nonempty. Add tests for explicit empty overrides.

Suggested change
if (!resolved.search) resolved.search = parsed.search;
if (!resolved.hash) resolved.hash = parsed.hash;
if (!relative.split('#')[0].includes('?')) resolved.search = parsed.search;
if (!relative.includes('#')) resolved.hash = parsed.hash;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/browser/steps.ts
Line: 180-181

Comment:
**Empty URL overrides are ignored**

`resolveUrl` restores the base query or hash even when the target explicitly clears it. With base `https://host/app?filter=active#old`, target `/login?` gets `filter=active` again, and `/login#` gets `old` again. Specs trying to clear a filter or anchor therefore keep the old value.

Check whether the target contains each delimiter rather than whether `resolved.search` or `resolved.hash` is nonempty. Add tests for explicit empty overrides.

```suggestion
  if (!relative.split('#')[0].includes('?')) resolved.search = parsed.search;
  if (!relative.includes('#')) resolved.hash = parsed.hash;
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bbc0183: an empty query or hash in the target clears that part of the base url.

cursoragent and others added 4 commits October 26, 2026 10:18
… and hash

Co-authored-by: Gabe <gpavanello@thefork.com>
…hash

Co-authored-by: Gabe <gpavanello@thefork.com>
New lock files are version 3. plain ignores a version 2 file, so those locators are never replayed. A version 1 file still replays locators.

Co-authored-by: Gabe <gpavanello@thefork.com>
@gabe4coding
gabe4coding force-pushed the quality/107-useid-fragment branch from 1cce966 to e5550b7 Compare October 26, 2026 09:18
@gabe-bots

gabe-bots Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

@greptileai review

@gabe4coding
gabe4coding merged commit 4f38cf3 into main Oct 26, 2026
11 checks passed
@gabe4coding
gabe4coding deleted the quality/107-useid-fragment branch October 26, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-gabe Needs Gabe's decision before work continues risk:t2-hard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants