Float the Questionnaire Action and Add an Eraser to the Annotation Editor - #89
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 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 |
There was a problem hiding this comment.
🟡 Changes recommended
It introduces downstream-breaking removals in a public UI-test helper API and appears to remove/add doc screenshot resources without the repo’s usual .license sidecars.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates GroveQuestionnaire’s questionnaire page UX to match the app-wide “shared header + floating bottom action” pattern, improves the image annotation editor with an eraser workflow, and refreshes the UI-test and documentation scaffolding to reflect the new behavior.
Changes:
- Move the questionnaire primary action from an in-form last row to a floating bottom action (including the completion page) and introduce a rising in-content page title (
PageHeader). - Add an eraser tool to image annotation (replace “Remove all” flow), update PencilKit tooling to support non-inking tools, and adjust UI tests accordingly.
- Update UI-test navigation/title assertions and add a scripted screenshot-capture UI test plus new documentation screenshots/markdown.
File summaries
| File | Description |
|---|---|
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/TestAppUITests.swift | Updates questionnaire “presented + titled” wait helper to new title API. |
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/QuestionKindTests.swift | Updates annotate-image UI test to exercise eraser + undo workflow. |
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/PrimaryActionTests.swift | Adjusts expectations for floating primary action and renames a test to match behavior. |
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/PageLayoutTests.swift | Switches from nav-bar-only title checks to isTitled and tightens title repetition assertions. |
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/DocumentationScreenshots.swift | Adds gated UI test to drive scripted documentation screenshot capture. |
| Tests/GroveQuestionnaireTests/UITests/TestAppUITests/BasicTests.swift | Removes now-unnecessary scrolling to reach primary action. |
| Tests/GroveQuestionnaireTests/UITests/TestApp/Routes/SwiftDSLRoute.swift | Adds Heart Check-In instrument to Swift DSL examples (for docs entry state). |
| Tests/GroveQuestionnaireTests/UITests/TestApp/Questionnaires/QuestionKinds.swift | Makes date/time demo questions optional for documentation walkthrough flow. |
| Tests/GroveQuestionnaireTests/UITests/TestApp/Questionnaires/HeartCheckIn.swift | Introduces Heart Check-In demo instrument used for docs/testing. |
| Sources/XCTGroveQuestionnaire/QuestionnaireSheetNavigator+Scanning.swift | Adds reachability helper to avoid taps landing under the nav bar. |
| Sources/XCTGroveQuestionnaire/QuestionnaireSheetNavigator+Question.swift | Uses reachability scan for taps; updates follow-up waits to use waitUntilTitled. |
| Sources/XCTGroveQuestionnaire/QuestionnaireSheetNavigator.swift | Adds page-title reading/waiting API; removes primary-action scrolling assumptions for floating action. |
| Sources/GroveQuestionnaire/UI/View+FormActionRow.swift | Removes obsolete “action as last form row” helper. |
| Sources/GroveQuestionnaire/UI/Tasks/TaskView+Choice.swift | Removes follow-up navigation title/header chrome to align with new page title pattern. |
| Sources/GroveQuestionnaire/UI/TaskRun.swift | Adjusts group heading suppression to compare against page title. |
| Sources/GroveQuestionnaire/UI/SwiftUI+Title.swift | Removes custom navigation title/subtitle helper in favor of PageHeader/rising title. |
| Sources/GroveQuestionnaire/UI/QuestionnaireSheet.swift | Updates module docs to reflect floating action and rising title behavior. |
| Sources/GroveQuestionnaire/UI/QuestionnaireSectionView.swift | Adds PageHeader title section + floating primary action; refactors card rendering; updates missing-answer messaging. |
| Sources/GroveQuestionnaire/UI/CompletionPage.swift | Moves Done button into floating actions inset. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/ZoomableImageAnnotationView.swift | Generalizes tool type to support eraser (any PKTool). |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/ImageAnnotationView.swift | Updates CanvasView integration for any PKTool and disables tool picker. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationRegionPicker.swift | Adds eraser button, trailing fade, and selection behavior for erasing vs region selection. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationPreviewImage.swift | Updates preview tool initialization to match new tool typing. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationHistoryController.swift | Removes “remove all actions” support (trash workflow removed). |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationEraserButton.swift | Adds eraser button component with accessibility identifier/selection traits. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationEditorCanvas.swift | Refactors editor canvas inputs to receive tool + drawing-enabled state. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotationDrawingStyle.swift | Adds eraser tool generation sized relative to stroke width. |
| Sources/GroveQuestionnaire/Tasks/AnnotateImage/AnnotateImageSheet.swift | Replaces trash/reset flow with eraser + undo/redo; wires tool selection state. |
| Sources/GroveQuestionnaire/Resources/Localizable.xcstrings | Removes reset-related strings; adds “Eraser”. |
| Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview~dark.png.license | Removes license sidecar for screenshot resource. |
| Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview.png.license | Removes license sidecar for screenshot resource. |
| Sources/GroveQuestionnaire/GroveQuestionnaire.docc/QuestionKinds.md | Adds documentation screenshots for question kinds and annotate-image/custom kinds. |
| Sources/GroveQuestionnaire/GroveQuestionnaire.docc/GroveQuestionnaire.md | Expands overview with multiple screenshots and updated narrative. |
| Sources/GroveQuestionnaire/GroveQuestionnaire.docc/AuthoringQuestionnaires.md | Adds screenshot callout for score/conditional behavior section. |
Review details
- Files reviewed: 34/50 changed files
- Comments generated: 4
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
5f67a8b to
5e9278a
Compare
5e9278a to
ef69da1
Compare
ef69da1 to
ec23e36
Compare
ec23e36 to
9cdea62
Compare
9cdea62 to
8fc1e52
Compare
8fc1e52 to
e1618db
Compare
e1618db to
1bdd83a
Compare
1bdd83a to
1f38003
Compare
1f38003 to
0d73409
Compare
0d73409 to
fe14a9b
Compare
fe14a9b to
191c1a0
Compare
### ♻️ Current situation & Problem Onboarding, account setup, consent and the questionnaire each built their own page: their own title, their own footer, their own idea of what a missing answer looks like. Validation drew a red box inside the grey card it sat on, the questionnaire and consent tinted their cards each in their own way, and a page that had to be read to the end looked nothing like a page with a button. The documentation showed screenshots from different eras, and the walks that regenerate them ran inside the CI suites. ### ⚙️ Release Notes <table><tr><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Welcome~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Welcome.png" width="200" alt="Page with floating actions"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/ImageHeader~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/ImageHeader.png" width="200" alt="Header with a symbol"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveOnboarding/GroveOnboarding.docc/Resources/ScrolledTitle~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveOnboarding/GroveOnboarding.docc/Resources/ScrolledTitle.png" width="200" alt="Title risen into the bar"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Validation~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Validation.png" width="200" alt="Blocking feedback"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveLLMOpenAI/GroveLLMOpenAI.docc/Resources/APITokenStep~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveLLMOpenAI/GroveLLMOpenAI.docc/Resources/APITokenStep.png" width="200" alt="LLM setup step"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Tiles~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/page-scaffold/Sources/GroveViews/GroveViews.docc/Resources/Tiles.png" width="200" alt="Tiles"></picture></td></tr><tr><td align="center"><sub>Page with floating actions</sub></td><td align="center"><sub>Header with a symbol</sub></td><td align="center"><sub>Title risen into the bar</sub></td><td align="center"><sub>Blocking feedback</sub></td><td align="center"><sub>LLM setup step</sub></td><td align="center"><sub>Tiles</sub></td></tr></table> - GroveViews gets the page scaffold every full-page step uses: `PageView` with a header, scrolling content and a footer that floats over it, `PageHeader` with an optional symbol and a `spacing` option for a header under a navigation bar, and `PageActions` with `ActionButtonRole` and `actionButtonStyle(_:)`. The pieces are modifiers too: `floatingActions`, `fadesIntoBottomEdge`, `risesIntoNavigationBar` with `acceptsRisingTitle` (measured in global coordinates, so it also works on a form that recycles its rows), and `softScrollEdge` for lists under a bar. GroveOnboarding keeps `OnboardingInformationView` and `SequentialOnboardingView`; `OnboardingView` is an alias of `PageView` with the areas initialiser. The LLM setup steps move over here; account, consent and the questionnaire follow in #88, #90 and #89. - One language for something that still needs the participant: `BlockingMessage`, and `reportsBlocking` on the control with `highlightsBlockingContent` on its row or card, so the red is painted in the container's own shape. `VerifiableTextField` uses it here; the questionnaire and consent cards adopt it in #89 and #90. - `Scripts/documentation-screenshots.sh` captures through RocketSim and a `snapshot` directive installs a snapshot-test reference for views only a snapshot renders. The walks skip themselves unless the script runs them. Every catalogue except chat, LLM, account, consent and the questionnaire is regenerated here, with inline images on the public views. - Textual 0.6.4, `ProgressiveBlur` moves from the chat to GroveViews, and lists under a bar get the soft edge. - The questionnaire navigator taps a row only once it is reachable below the bar, and the consent test helper drags a picker back out from under it: with a floating footer both used to tap the bar instead. - `ListHeader` stays until #88 removes its last users, the consent view only picks up the new names, the chat keeps its own blur until #91, and three images the README still shows leave with #90. ### 📚 Documentation GroveViews' page gets a Pages and a Blocking Feedback topic, GroveOnboarding describes onboarding on top of the scaffold, and the Documentation Guide covers the script's directives. ### ✅ Testing UI suites for Views, Onboarding, Chat, LLM, Scheduler, Contact, License, AccessGuard, Devices and HealthKit on an iPhone 17 Pro with iOS 26.5, the iOS and macOS unit legs, the floor build and the documentation build. The chat, LLM, account, consent and questionnaire suites were run on the full stack. ### Code of Conduct & Contributing Guidelines By creating and submitting this pull request, you agree to follow our [Code of Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md).
…estionnaire (#97) ### ♻️ Current situation & Problem A questionnaire gave no sense of how far along it was, its choice marks looked the same for one answer and for several, a missing answer was pointed out by a line that pushed the question around as it came and went, and a drop-down, date or file answer took two taps to open. The keyboard stayed up when a participant tapped beside a field. ### ⚙️ Release Notes <table><tr><td></td><td align="center"><sub>Progress bar</sub></td><td align="center"><sub>Blocking line</sub></td><td align="center"><sub>Day options and score</sub></td></tr><tr><td align="center"><sub>#89</sub></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview.png" width="200" alt="Progress bar before"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Validation~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Validation.png" width="200" alt="Blocking line before"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Score~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Score.png" width="200" alt="Day options and score before"></picture></td></tr><tr><td align="center"><sub>#97</sub></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Overview.png" width="200" alt="Progress bar"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Validation~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Validation.png" width="200" alt="Blocking line"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Score~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Score.png" width="200" alt="Day options and score"></picture></td></tr><tr><td></td><td align="center"><sub>Date and time pills</sub></td><td align="center"><sub>Numbers</sub></td><td align="center"><sub>Annotation editor</sub></td></tr><tr><td align="center"><sub>#89</sub></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/DatesAndTimes~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/DatesAndTimes.png" width="200" alt="Date and time pills before"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Numbers~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Numbers.png" width="200" alt="Numbers before"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/AnnotateImage~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-on-the-page-scaffold/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/AnnotateImage.png" width="200" alt="Annotation editor before"></picture></td></tr><tr><td align="center"><sub>#97</sub></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/DatesAndTimes~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/DatesAndTimes.png" width="200" alt="Date and time pills"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Numbers~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/Numbers.png" width="200" alt="Numbers"></picture></td><td align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/AnnotateImage~dark.png"><img src="https://raw.githubusercontent.com/SchmiedmayerLab/Grove/questionnaire-progress/Sources/GroveQuestionnaire/GroveQuestionnaire.docc/Resources/AnnotateImage.png" width="200" alt="Annotation editor"></picture></td></tr></table> - A hairline progress bar hangs off the navigation bar and fills as answers and page turns come in; it never moves back while a condition still might ask a question. It is on unless asked otherwise, `progress:` takes `.bar`, `.questionNumbers` or both, and `progressRange:` lets a questionnaire that is one step of a longer flow fill only its part. `QuestionProgressConfig` is deprecated in favour of it. - With the bar, the page's name sits in the navigation bar with the instrument's name under it, and both rise as the page scrolls; `PageHeader(subtitleRises:)` in GroveViews is what carries the subtitle along. - Single and multiple choice questions show distinct marks; "Select all that apply" and other hints are off unless `hints:` asks for them. - A question that keeps the page from continuing is tinted and gets its line under the answers: the card grows down over 0.4 s and the line fades in once there is room; on an answer the line and the tint fade together at the pace the answer is confirmed and the room closes over 0.2 s. The question above never moves. - Drop-down, date and file answers open from a pill in one tap; a drop-down answer scrolls on to the next question like a choice does; an attachment can be removed, and a photo taken for one is cleaned up once it is copied. - `ResponseValidationResult` gains `.incomplete` for an answer on its way, such as "Other" with nothing typed: the page holds it back only once the participant tries to move on. An exhaustive `switch` over the result needs the new case. - A tap beside the fields lets the keyboard go, as on a form; the annotation editor's buttons are back to their earlier look. - A number field keeps its fraction: its formatter kept none, so a typed 3.5 settled as 4. - `progressRange` bounds outside `0...1` are clamped, as documented; a range reaching below the bar drew its halfway mark too low. - The floating action and the completion page's Done button take the `PageActions` style and size, so they match the actions on the other pages; they stood a few points taller. - A card scrolled to the top stops halfway into the gap it shares with the card before, not against the navigation bar; the progress line keeps its place. ### 📚 Documentation The questionnaire article covers `progress`, `progressRange` and `hints`; `QuestionnaireProgress` and `QuestionnaireHints` are documented in place. The questionnaire screenshots are retaken with the bar, the marks and the pills, and with every kind answered: dates and times chosen, numbers typed, a mark drawn on the body map, the stopwatch running. The screenshot script starts RocketSim itself, since RocketSim quits whenever a simulator it shows is shut down and its CLI reports success either way, and stops on a failed build rather than shooting a stale app. ### ✅ Testing Unit tests cover the progress counting and the clamped range: answers, page turns, questions a condition hides, an answer the page rejects, a page outside the run, and a questionnaire that asks nothing. UI tests cover the bar, the page naming, the pills, the keyboard, and the blocking line. The test helper opens a date pill at the row's trailing end, where the pill is. The gate jobs of Tests, Deployment Floor and release validation run under `!cancelled()` rather than `always()`, so a run superseded by a newer push ends as cancelled instead of failed, and Repository Standards has a concurrency group like the other workflows. Run locally: the questionnaire's unit legs on iOS and macOS and its UI suite, the GroveViews unit legs on iOS, macOS and watchOS and its UI suite, SwiftLint, Periphery, the documentation build, the UI-test project and documentation-target checks, REUSE, and the five deployment-floor builds. The documentation walk closes every date picker popover before it continues, so the dates page is taken with none open. ### Code of Conduct & Contributing Guidelines By creating and submitting this pull request, you agree to follow our [Code of Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/SchmiedmayerLab/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/SchmiedmayerLab/.github/blob/main/CONTRIBUTING.md).
♻️ Current situation & Problem
The questionnaire's action was the last row of the form, so a long page hid it below the fold and the navigator had to scroll for it before every check. The large navigation title cut long names and did not match the other pages. In the image annotation the only way to correct a stroke was to remove all of them.
⚙️ Release Notes
BlockingMessage, the way the rest of Grove does since Share One Page Scaffold and Blocking Feedback Across the Views #87.PageHeaderin its compact spacing, aligned with the cards, and the name rises into the bar as the page scrolls. Group and section captions sit under it as before.QuestionnaireSheetNavigatorreads a page's name withpageTitle,isTitled(_:)andwaitUntilTitled(_:timeout:);waitUntilNavigationBarShowsandscrollToPrimaryActionare gone. Follow-up questions are tapped only once they are reachable below the bar, which fixes the follow-up tests.enableWhenExpressiongates the items it holds, the way an item's own does; the FHIR conversion dropped it before.📚 Documentation
The questionnaire page opens on the heart check-in and shows one page per family of question kinds.
✅ Testing
The questionnaire UI suite and unit legs on iOS and the macOS unit leg.
Code of Conduct & Contributing Guidelines
By creating and submitting this pull request, you agree to follow our Code of Conduct and Contributing Guidelines: