Repository navigation
ios: 'I can't get there' moves on to the next requested view (#39) - #49
AidenJohnston wants to merge 11 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (13)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe scan engine now queues distinct server-requested views, continues to later requests after a homeowner skips one, and tracks the remaining follow-ups. The gap screen adds a “Show my result” action to stop further requests. Autopilot and UI tests cover the updated flow. ChangesServer follow-up requests
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant ScanEngine
participant GapPlanner
participant GapRequestScreen
participant Homeowner
ScanEngine->>GapPlanner: Select ordered distinct requests
GapPlanner-->>ScanEngine: Return capturable request queue
ScanEngine->>GapRequestScreen: Provide current request and follow-up count
Homeowner->>GapRequestScreen: Tap Show my result
GapRequestScreen->>ScanEngine: Call showResultNow()
ScanEngine->>ScanEngine: Stop requests and send current request for installer review
Suggested reviewers: Merge Risk: ⚪ Minimal · up to The change lets a refused follow-up view lead to the next requested view and adds a "Show my result" action. No actionable merge-blocking issue remains after review. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to The change appears bounded to the current scan and preserves request limits, capture eligibility, and installer review when the homeowner stops. No introduced security vulnerability was established. Failure recovery and the isolation of automated test controls could not be fully verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
After the check's answer asks for several views, "I can't get there" on one of them used to stop the loop: the next upload showed the result, so "2 more views to finish" delivered only one view. Now it skips only the current view: the next answer raises the next item it lists, and the result shows once none is left. Each item is raised at most once per pass (GapPlanner.nextServerRequest); a past-end item is known by its side, since raising it clears the end and it comes back with a different request. A new "Show my result" button on the follow-up request stops early, and the skip button's VoiceOver hint says where it leads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Note Review skipped Codemetrics is installed on this repository, but it hasn't been added to your team yet. To enable AI code reviews for this repo, go to your dashboard and add house-scanning-master to your team's repositories. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4ce503160
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| switch followUps { | ||
| case 0: "Skips this view. An installer will look at this part instead." | ||
| case 1: "Skips this view and shows your result. An installer will look at this part instead." | ||
| default: "Skips this view and goes on to the next one. An installer will look at this part instead." |
There was a problem hiding this comment.
Exclude already-raised views from the remaining count
When a past-end request is skipped, clearing that end makes the same server item produce a new GapPlan, so presentation(of:) still marks it capturable even though nextServerRequest correctly excludes it via asksForSameView. Consequently, on the actual last request followUps can remain 2: the eyebrow claims two views remain and this new hint says skipping will go to another view, but the subsequent upload goes directly to the result. Derive the displayed count and hint from the same asked-view filtering used by the planner (and account for the automatic-request cap) so the UI describes the transition that will actually occur.
Useful? React with 👍 / 👎.
|
Read-only review of #49 found two integration/copy risks. First, #47 changes |
|
One point beyond the caretaker's two, from reading e4ce503: A refused view can come back with slightly different numbers. The caretaker's points still stand: pass |
|
Current head |
Pre-CI checkThis is the cause of two of the three failures in run 36276179058. Both come from this branch's change, so rerunning 1. Fix: when a past-end request is skipped, the homeowner couldn't get past the end, so the end should stay where it was. In 2. Contrast failure on 3. |
Pre-CI check (follow-up)#47 ( The two branches will conflict in |
Pre-CI check (after #47 merged)
After the merge, the sample result's left past-end request, skipped by the |
Resolves the GapPlanner/ScanEngine conflicts with #47: the selector now passes limitEnds, matches requests by view with a tolerance (same band and need, reach within 0.1 m, 80% span overlap), and publishes how many views it will actually raise as state.followUps. 'Show my result' shows only when more than one view is left. Co-authored-by: Cursor <cursoragent@cursor.com>
|
My point at e4ce503 is addressed at ce90112. |
|
@AidenJohnston Independent source review of Please exercise the multi-view follow-up at AX5 with both This is a verified coverage gap, not a claim that contrast still fails at this head. I have not run a local Simulator; current-head CI is still running. |
|
@AidenJohnston Two further findings are confirmed by source review at
The five-request cap itself is enforced by the inspected queue code; I found no sixth-request issue. The multi-view AX5 coverage request in comment 5851111550 remains open. Build 4.1 field report #82 describes seven rapid refusal taps to finish, including taps that set unexplored ends. This supports the persistent-stop acceptance requirement; it is evidence from beta 45017ba, not a reproduction on this PR head. Keep #82 as the field issue rather than duplicating it. Also merge current t3/ios-mvf de04c3b or later to retain the shared schema-fetch correction before final CI. |
Merged
|
|
@AidenJohnston Sam is moving ahead with a test beta while main PRs stay open. Please prioritize two small fixes on your #49 branch so the beta can test refusal progression meaningfully:
Please merge current t3/ios-mvf while preserving its end restoration, limit-end forwarding and three-file schema-fetch guard, then post the fix SHA. The beta lead can test that concrete head; branch writes remain yours. The multi-view AX5 case stays in beta QA. Current ce90112 is not independently cleared, and this request does not ask for main integration or a blind CI retry. |
Stacks this branch on PR #49 at its current head, on top of the current t3/ios-mvf. The merge had no conflicts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…very follow-up (#39) The two open review findings on #49: - GapPlan.asksForSameView suppressed a request whose span was 80 % inside an earlier one and whose reach was within 0.1 m, so 2-9 ft after 2-8 ft, or ground out to 5.10 ft after 4.83 ft, was dropped as a duplicate. It now matches only a request whose span lies inside the earlier one and whose reach is no more than the earlier one's, each give or take 0.1 ft. Tests cover both cases through serverRequests, after a raised and a skipped request, and as two items in one answer. - "Show my result" was hidden with one view left, and the skip hint said skipping shows the result. The next upload can still bring a new request, so the button now shows on every server follow-up and the one-view hint says the scan is checked again. Also deletes the empty ios/ServerGapTests.swift that #49 added by mistake. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts: the export's capturable check takes the base's capturable(item) closure, and the automatic queue keeps GapPlanner.serverRequests with the base's sourceState.mayCapture guard and captureCanSettle filter. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings the two #39 fixes Sam asked for on this branch: a follow-up that asks for more span or reach is a new view (asksForSameView), and "Show my result" on every server follow-up. #109 carries the same commit, so the two branches share it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
| case let (.groundOut(a), .groundOut(b)), let (.walkOut(a), .walkOut(b)), let (.wallUp(a), .wallUp(b)): | ||
| return abs(a - b) <= tolerance | ||
| case let (.overhead(a), .overhead(b)): | ||
| guard let a, let b else { return a == nil && b == nil } | ||
| return abs(a - b) <= tolerance |
There was a problem hiding this comment.
Distinct reaches treated as identical When the server asks for two views of the same span at different required reaches, such as 6.5 ft and 6.6 ft, this 0.1 m tolerance treats them as the same view. Capturing the first may not satisfy the second, but
serverRequests drops it and can show the result while evidence is still missing. If the first view was skipped, the same comparison also disables the second view’s capture action on the result.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift
Line: 375-379
Comment:
**Distinct reaches treated as identical** When the server asks for two views of the same span at different required reaches, such as 6.5 ft and 6.6 ft, this 0.1 m tolerance treats them as the same view. Capturing the first may not satisfy the second, but `serverRequests` drops it and can show the result while evidence is still missing. If the first view was skipped, the same comparison also disables the second view’s capture action on the result.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Fixed in 88bca30. The 0.1 ft tolerance now applies to the span only. A reach may be no farther out than the earlier one: compared on the server's out_ft when both requests have one, else in meters within float noise. ServerGapTests.aSlightlyFartherReachIsANewView covers 6.59 ft after 6.50 ft through serverRequests, after a raised request, after a skipped one, and as two items in one answer.
| /// Issue #39: the answer asks for two views and the homeowner can't get to the first. The | ||
| /// next answer, which still lists it, moves on to the second instead of raising the first | ||
| /// again or stopping; once both were raised, none is left and the result shows. The skipped | ||
| /// past_end request gets its end back (`ScanEngine.settleClearedEnd`), so it comes back as |
There was a problem hiding this comment.
Engine transition remains untested This test checks planner output, but not the reported failure path: tapping “I can’t get there,” uploading, and entering the next request instead of the result. It also does not test whether “Show my result” stops that transition. A change to the engine’s stopping flag or upload path could therefore bring back the bug while these assertions still pass.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ios/HouseScanKit/Tests/HouseScanKitTests/ServerGapTests.swift
Line: 251
Comment:
**Engine transition remains untested** This test checks planner output, but not the reported failure path: tapping “I can’t get there,” uploading, and entering the next request instead of the result. It also does not test whether “Show my result” stops that transition. A change to the engine’s stopping flag or upload path could therefore bring back the bug while these assertions still pass.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Covered in 6c3ac62 with UI tests that run the engine. The autopilot writes how each request the server's answers raised ended (server-requests.json in the gate folder). FullFlowUITests.testCantGetThereEndsWhereThePhoneIs asserts a request is raised after a skipped one. The new testShowMyResultEndsTheRequests taps "Show my result" on the first request and asserts no other is raised before the result. Both passed on the Mac mini at 6c3ac62.
This comment has been minimized.
This comment has been minimized.
Ported from #109 (a44780a). With the button back on every follow-up, the one-view state has the extra row that failed the reply's contrast at AX5 (CI run 36276179058). At accessibility sizes the button now goes below the tape, so the space under the card matches the layout that passed. The screen-state test asserts the follow-up's two controls can be tapped, after scrolling to them at AX5. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…result Brings #49's merge of the current base. Conflicts: - ScanEngine.upload: the #31 resultHold wait, then the base's presentAnswer (spot check) instead of going straight to the result. DemoEngine the same. - ResultScreen: the base's redesigned layout; its unseen-side footnote now shows #83's unseenEnd copy. ScanCopy.unseenSide is unused again and removed. - ScreenStatesUITests: expectations hold a list per state, so result-review checks both the base's headline and #83's unexplored-end copy. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@AidenJohnston At e576190, the reach-dedup finding remains for smaller increases. GapPlanner367 and392-397 allow another0.1ft: for identical spans,6.59ft after6.50ft is treated as already asked, while progress160-166 requires the exact requested reach. The stricter request can still disappear, or its capture action stays disabled after a skip. The reduced tolerance fixes larger differences, not this case. Please separate harmless span jitter from genuinely stricter reach, or provide evidence that the upstream contract treats this difference as equivalent. This affects evidence collection, not a claim that the server returns PASS. #109 f2309cb inherits it. Empty test file removal is verified; engine stop/skip transition coverage remains separate. |
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Updated for review: head
|
Conflict: upload() takes the base's snapshot loop (AnswerFreshness). After it, the answer sets state.followUps from automaticGapQueue before .done, and the next request is automaticGapQueue(result).first. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iew (#39) asksForSameView let a reach move 0.1 ft like the span, so ground out to 6.59 ft after the same span out to 6.50 ft counted as the same view and was never raised, and after a skip its capture action stayed off, though progress needs the requested out_ft itself. The span keeps the 0.1 ft tolerance; a reach may be no farther than the earlier one's, compared on the server's out_ft when both have one, else in meters within float noise. Tests: 6.59 ft after 6.50 ft through serverRequests after a raised and a skipped request and as two items in one answer; the refused-view test now moves the span only, and a farther reach after a refusal is raised. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The autopilot writes how each request the server's answers raised ended (server-requests.json in the gate folder). testCantGetThereEndsWhereThePhoneIs now asserts a request is raised after a skipped one, and the new testShowMyResultEndsTheRequests taps "Show my result" on the first (-autopilotShowResult) and asserts no other request is raised before the result. "Show my result" appears and goes at once (.identity), like the card's reply, so no faded frame meets the contrast audit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings the base at 6be087d through #49. Conflicts: - ScanEngine.upload: the base's snapshot loop (AnswerFreshness), with #31 ported into it: analyzingSince resets on each attempt before submit and is set when its bytes are sent, the analyzingMinimum wait comes after the loop before .done, and resultHold before presentAnswer, both in the base's guard (try? await Task.sleep(...)) form. - ScreenStatesUITests: the base's wallWalk-fullLegend expectation, in the list format. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Updated for review: head
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
@greptileai review Please review the current head |
| /// (0.03 m), meters. Numerical jitter from the server working the request out again is a few | ||
| /// hundredths of a foot; a guess, not measured. This applies to the span only: a reach gets | ||
| /// no allowance beyond float noise (`reachNoise`). | ||
| public static let sameViewTolerance: Float = 0.03048 |
There was a problem hiding this comment.
If a later server answer extends a previously raised or skipped span by 0.09 ft, this 0.1 ft tolerance treats it as the same view. But GapPlanner.progress counts an uncovered gap of at least 0.01 ft as missing. serverRequests can therefore discard a request the server still needs and show the result without offering that view. The deduplication tolerance needs to agree with what settles a request.
Prompt To Fix With AI
This is a comment left during a code review.
Path: ios/HouseScanKit/Sources/HouseScanKit/Guidance/GapPlanner.swift
Line: 379
Comment:
**Unmet views can be discarded**
If a later server answer extends a previously raised or skipped span by 0.09 ft, this 0.1 ft tolerance treats it as the same view. But `GapPlanner.progress` counts an uncovered gap of at least 0.01 ft as missing. `serverRequests` can therefore discard a request the server still needs and show the result without offering that view. The deduplication tolerance needs to agree with what settles a request.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
@AidenJohnston Caretaker check at 6c3ac62: confirmed, and new in this PR (the base compared plans exactly). Ask for 2…8 ft, and the next answer asks for 2…8.09 ft at the same reach. asksForSameView treats them as the same view within sameViewTolerance = 0.1 ft (GapPlanner.swift:379-399), so serverRequests drops the second request. Meanwhile progress counts any uncovered stretch of 0.01 ft or more as missing (lines 236-249). With nothing else queued, ScanEngine.swift:1528-1541 shows the result, and after a skip ScanEngine+Export.swift:193-200 marks the request as not capturable. Evidence gets lost; it doesn't turn UNSURE into PASS. Your 88bca30 reach fix holds; this is the span side.
Please make the span allowance agree with progress: an extension of 0.01 ft or more is a new view. Use requestedSpanFt when both plans have it. Add ServerGapTests cases just below, at and above 0.01 ft, for a raised request, a skipped one, and two items in one answer.
There was a problem hiding this comment.
The 0.1 ft deduplication tolerance is too large relative to progress, which treats gaps of 0.01 ft or more as missing. Use requestedSpanFt when both plans have server-provided spans and make the threshold strictly less than 0.01 ft, so an extension of exactly 0.01 ft is a new view.
| public static let sameViewTolerance: Float = 0.03048 | |
| public func asksForSameView(as other: GapPlan) -> Bool { | |
| guard band == other.band, need.asksNoMore(than: other.need, within: Self.reachNoise) else { return false } | |
| // Compare the server's exact endpoints when available: these are the values | |
| // that progress compares against. | |
| if let span = requestedSpanFt, let otherSpan = other.requestedSpanFt { | |
| let tolerance = Self.sameViewToleranceFt | |
| if span.lowerBound <= otherSpan.lowerBound - tolerance || | |
| span.upperBound >= otherSpan.upperBound + tolerance { return false } | |
| } else { | |
| let tolerance = Self.sameViewTolerance | |
| if span.lowerBound <= other.span.lowerBound - tolerance || | |
| span.upperBound >= other.span.upperBound + tolerance { return false } | |
| } | |
| if let reach = requestedOutFt, let otherReach = other.requestedOutFt, reach > otherReach { | |
| return false | |
| } | |
| return true | |
| } |
Define the tolerances as:
public static let sameViewToleranceFt: Double = 0.01
public static let sameViewTolerance: Float = Float(sameViewToleranceFt) * 0.3048Add ServerGapTests for span extensions of 0.009, 0.010, and 0.011 ft, each tested after a raised request, after a skipped request, and as the second item in one answer. The expected behavior is:
0.009 ft: deduplicated0.010 ft: raised as a new request0.011 ft: raised as a new request
The existing 2.40 → 2.41 ft case is exactly 0.01 ft, so its expectation should change to a newly raised request.
There was a problem hiding this comment.
@AidenJohnston For your information, the #191 app candidate now carries its own local fix for this, in commit d837c4e. It's unpublished; root publishes after review. Your branch is untouched.
- Fix:
asksForSameViewcompares the exact requested extents with the same 0.01 ftcountsAsMissingtest thatGapPlanneruses for progress. The 0.1 ftsameViewToleranceis removed. - Regressions: an 8-case edge sweep after an asked request, after a skipped one and within one answer, plus
aStricterReachIsANewViewAndAnEqualOneIsNot. - Evidence so far: the app lane reports 629 package tests passing at d837c4e. The app build and UI tests are still running.
This thread stays open on #49's own head, 6c3ac62. #109 contains the same matcher.
…ence missing (#49) GapPlan.asksForSameView suppressed a request already asked, skipped or earlier in the same answer when its span and reach were within 0.1 ft of the other's, measured on the Float-meter span. Progress measures the server's exact span_ft and counts any uncovered edge of 0.01 ft or more as missing, so a request for 2...8.09 ft after 2...8 ft disappeared though its last 0.09 ft could never be met; after a skip the result also marked it not capturable (ScanEngine+Export.swift:198). Same-view now decides as progress does: the same band and kind of need; a reach no higher than the other's, compared exactly in requested feet (requestedOutFt, or the need's meters converted when unset); and no span edge running past the other's by the amount progress counts as missing. GapPlanner.countsAsMissing is that one comparison, now shared by GapPlanner.fraction and the same-view rule, and GapPlan.requestedSpanInFeet is the one span both read (requestedSpanFt, or the span at the export's four decimals when unset). Equal and contained requests are still the same view, an overhead request without a height still asks no more than one with, and the request limit and "Show my result" are unchanged. The guessed 0.1 ft sameViewTolerance is gone. Tests: an 8-case sweep moves one edge outward by 0.009, 0.010, 0.011 and 0.09 ft on each side, after asked, after skipped and within one answer, with the server's exact span_ft. In doubles 8.01 - 8.0 is just under 0.01 and 2.0 - 1.99 just over it, so 0.010 is rounding on the high edge and missing on the low edge, for progress and this rule alike; each case is also checked against GapPlanner.fraction over the old span. A reach test covers stricter, equal and lower reaches and requests built in meters. Two older expectations encoded the 0.1 ft guess and are changed: the refused view that came back at 4.9 ft after 4.833334 ft, and a "jitter" item 0.05 ft wider with a farther reach, both now ask for evidence progress requires. Focused run: ServerGapTests and its suite, 27 tests passed (job app-pkg-servergap-49). Co-Authored-By: Claude Code <noreply@anthropic.com>
…ce stays in progress (#49) Root's fresh review of d837c4e found the 0.01 ft allowance could stack. Coverage seen to exactly [2, 8] ft meets a request for [2, 8.0085] ft, because progress reads its 0.0085 ft tail as rounding. The next request, [2, 8.0175] ft, ran only 0.009 ft past that one, so the same-view rule dropped it, though against the coverage 0.0175 ft is missing and progress is about 0.997. asksForSameView now requires the span to lie inside the other's, compared exactly in requested feet, as the reach already was. Rounding tolerance is applied only by progress, against the coverage actually seen (GapPlanner.countsAsMissing, now used there alone). The server lists only what its own reading of the uploaded scene still lacks, so an exactly contained request asks for nothing new. Equal and contained requests stay the same view; the limit and "Show my result" are unchanged. Tests: aRoundingAllowanceUsedOnceIsNotUsedAgain is root's exact case (old request met with progress 1, next request progress 1 - 0.0175/6.0175, raised after asked and after skipped). The 8-case sweep now expects every outward edge (0.009, 0.010, 0.011, 0.09 ft, each side) to be a new view after asked, after skipped and in one answer; anEqualOrContainedRequestIsTheSameView covers equal spans, edges moved inward by 0.009 ft and a contained span. Focused run: ServerGapTests and its suite, 29 tests passed (job app-pkg-servergap-49b). Co-Authored-By: Claude Code <noreply@anthropic.com>
Problem. When the check's answer asked for several views ("2 more views to finish"), tapping "I can't get there" on the first one ended the whole loop.
skipCurrentGapsetautomaticGapsStoppedfor any refused server request, so the upload that followed went straight to the result. The count promised views that never came (field run 2, 3:56.6).Fix.
maxAutomaticGaps(5) requests.GapPlanner.serverRequests), so it can be unit tested.ScanEngine.automaticGapQueuecalls it after each answer, and it keeps the base's guards: the source may capture, and the stretch wasn't refused on the spot check (captureCanSettle).limitEndsis passed through, so ios: past-end requests keep their end; no requests past a wall end (#35, #38) #47's past-end guard also applies to requests raised without a tap.GapPlan.asksForSameView). That means the same band and kind of need, a span inside the earlier one give or take 0.1 ft, and a reach no farther out. The reach gets no tolerance, becauseprogressneeds the requestedout_ftitself: ground out to 6.59 ft after 6.50 ft is a new view. A skipped past-end request gets its end back (ios: past-end requests keep their end; no requests past a wall end (#35, #38) #47'ssettleClearedEnd), so it comes back as the same request.action.showResult) appears on every request an answer raised, including when one view is left, because the upload after a skip can still bring a new request. It sends this view to installer review, uploads once more and shows the result. At accessibility text sizes it sits below the tape, so the card's "I can't get there" reply keeps the space that passed the contrast audit. It appears and disappears at once, with no fade.state.followUps) comes from the same queue the loop uses: duplicates count once, and the cap applies. The skip hint says "goes on to the next one" with more than one view left, and "checks your scan again" with one. It no longer promises the result.ScanActions.showResultNow()was added.DemoEngineimplements it.Tests.
ServerGapTests:FullFlowUITests. The autopilot writes how each raised request ended (server-requests.json).testCantGetThereEndsWhereThePhoneIsasserts a request is raised after a skipped one.testShowMyResultEndsTheRequeststaps "Show my result" on the first request and asserts no other is raised before the result.ScreenStatesUITests:gapRequest-followUpmust offeraction.skipGapandaction.showResult, and both must be tappable (at AX5, after scrolling).Check on a device. Scan a wall and stop short of one end, so the answer asks for two or more views. On the first request, tap "I can't get there". After a short upload the camera should ask for the next view, with the count down by one. Run it again and tap "Show my result" on the first request: the result shows after one upload.
Worth knowing.
Fixes #39
🤖 Generated with Claude Code
Summary by CodeRabbit