Release 1.98.0 to master - #1967
Conversation
- Add Oura authentication - Add provider flag to require the patient must be logged in to connect - Add provider flag to require an existing data source to connect - Filter active providers for only allowed connections based on above - Add new accept status to the OAuth workflow for UI before authorization - Add Oura logo
…vey the state to the user
Restructured the consent dialog layout for full-width dividers and proper content alignment per design specifications. - Moved padding from wrapper to individual sections (header/content/footer) - Image now sits inline with subheading only, body text below full-width - Right-justified the "I understand" button in footer
Wrapped first consent paragraph with Trans component to make "Tidepool's Privacy Policy" a clickable link. - Added URL_PRIVACY_POLICY import from constants - Used html.oauth-accept-privacy-message i18n key for Trans - Updated message rendering to handle both string and JSX
Added check to ensure return_url starts with '/' before redirecting, protecting against potential open redirect attacks via crafted URLs.
Improved return_url validation with isSafeReturnUrl helper to prevent open redirect attacks. Added test coverage for new provider features. - Added isSafeReturnUrl with checks for protocol-relative URLs - Added oura provider to test expectations - Added tests for requiresLoggedInUser filtering - Added tests for requiresExistingDataSource filtering - Added test for accept status early return in popup - Updated AGENTS.md with targeted test running guidance
Fixed tests to account for oura's requiresLoggedInUser and requiresExistingDataSource restrictions. Updated AGENTS.md to use .only for isolating Karma tests instead of --grep.
…scripting Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The scroll handler only sets the ConsentRead flag when the user scrolls to the bottom of the consent document. If the content is short enough that the container isn't scrollable, the scroll event never fires and the user has no way to enable the consent checkbox. Attach a ref to the scroll container and run an effect that compares scrollHeight to clientHeight; if the content fits without scrolling, mark the current step's ConsentRead field as true. Guarded against re-setting an already-true value to avoid a Formik update loop.
…nsent - When the consent document is short enough that the container isn't scrollable, the scroll handler never fires and the consent checkbox stays disabled. Capture the scroll container via a useState setter ref so MUI Portal's deferred child mount triggers a re-render, then in a useEffect mark the current step's ConsentRead as true if scrollHeight fits within clientHeight. Guarded against already-true values (prevents Formik update loops) and zero-height layout (jsdom). - Include the name field in the primary schema for child patients on personal accounts (previously only enforced for youth).
Release 1.97.0 to develop
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ssage [WEB-4616] Show donation-only connected message for oura connections
[WEB-4240,WEB-4241] Add Oura authentication
|
/deploy qa5 |
|
clintonium-119 updated values.yaml file in qa5 |
|
clintonium-119 updated flux policies file in qa5 |
|
clintonium-119 deployed blip release-1.98.0 branch to qa5 namespace |
|
@coderabbitai review |
✅ Action performedReview finished.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds Oura provider support and provider-specific connection states, introduces an OAuth accept step with guarded return handling, updates consent dialog read-state behavior, and refreshes related tests and project metadata. ChangesData Connections, OAuth Accept Flow, and Consent Dialog
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant OAuthConnection
participant useProviderConnectionPopup
participant ProviderRedirect
User->>OAuthConnection: Click "I understand"
OAuthConnection->>OAuthConnection: handleAccept validates return_url
OAuthConnection->>ProviderRedirect: window.location.href redirect
useProviderConnectionPopup->>useProviderConnectionPopup: Detect accept status and keep popup open
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR updates the application and test suite for the 1.98.0 release line, introducing Oura as a donation-only OAuth data source with a new pre-authorization “accept” step and updating connection-state messaging/visibility rules for patients vs clinicians.
Changes:
- Add Oura as an OAuth provider (donation-only) and adjust DataConnections filtering to hide certain providers for clinicians and/or when no existing data source exists.
- Introduce an OAuth “accept” status page/flow and update popup-watching logic to not auto-close on
/accept. - Update connected-state copy (e.g., “Awaiting data…”) and adjust/unit-test expectations accordingly.
Reviewed changes
Copilot reviewed 11 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| test/unit/components/useProviderConnectionPopup.test.js | Adds coverage for the new /accept pre-authorization behavior (popup should stay open, no toast). |
| test/unit/components/datasources/DataConnections.test.js | Updates provider lists/UI expectations for Oura and new connected/awaiting messaging; adds visibility-rule tests. |
| test/unit/components/chart/settings.test.js | Adjusts settings tests for clinician-visible providers (excluding requiresLoggedInUser providers like Oura). |
| package.json | Bumps the package version for the release line. |
| app/providers/AppBanner/appBanners.js | Updates “data source just connected” banner copy. |
| app/pages/patient/DataDonationConsentDialog.js | Auto-marks consent step “read” when the consent content is not scrollable (portal timing-safe). |
| app/pages/oauth/OAuthConnection.js | Adds accept-status UI/content, safe return_url validation, and accept redirect handling. |
| app/core/icons/oura_logo.svg | Adds Oura logo asset (SVG). |
| app/core/icons/oura_logo_black.svg | Adds Oura logo asset (black SVG). |
| app/components/datasources/useProviderConnectionPopup.js | Avoids closing popup / triggering completion behavior on /accept intermediate step. |
| app/components/datasources/DataConnections.js | Adds Oura provider configuration + donation-only connected messaging + provider filtering rules. |
| AGENTS.md | Documents repo agent rules, restricted directories, and build/test conventions. |
| .gitignore | Ignores .claude directory. |
| tests/unit/pages/dashboard/PatientDrawer/StackedDaily/StackedDaily.test.js | Updates viz mocking shape for utils.datetime.getLocalizedCeiling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| title: provider?.indeterminateDataImportTime | ||
| ? t('If you have connected your {{displayName}} device, data is on its way. This usually takes a few minutes but occassionally takes longer. Refresh the page to see data.', provider) | ||
| : t('{{displayName}} data is on its way. This usually takes a few minutes but occasionally takes longer. Refresh the page to see data.', provider), | ||
| ? t('If you have connected your {{displayName}} device, data is on its way. This usually takes a few minutes but occassionally takes longer.', provider) | ||
| : t('{{displayName}} data is on its way. This usually takes a few minutes but occasionally takes longer.', provider), |
| // Validate return_url is a safe relative path to prevent open redirect attacks | ||
| if (isSafeReturnUrl(returnUrl)) { | ||
| window.location.href = returnUrl; | ||
| } else { | ||
| // eslint-disable-next-line no-console | ||
| console.error('OAuthConnection: invalid or missing return_url query parameter on accept.'); | ||
| } | ||
| }; |
| <Flex sx={{ flexDirection: 'row', justifyContent: 'center' }} mb={5}> | ||
| <img src={logoSrc} width={190} /> | ||
| </Flex> |
| useEffect(() => { | ||
| if (!scrollContainer || !consentDocument?.content) return; | ||
| const fieldName = `${formSteps[currentConsentStep]}ConsentRead`; | ||
| if (formikContext.values[fieldName]) return; | ||
| if (scrollContainer.clientHeight > 0 && scrollContainer.scrollHeight <= scrollContainer.clientHeight) { | ||
| formikContext.setFieldValue(fieldName, true); | ||
| } | ||
| }, [scrollContainer, consentDocument?.content, currentConsentStep, formikContext.values]); |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
app/pages/patient/DataDonationConsentDialog.js (1)
167-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winNarrow the effect's dependency and include the setter function.
formikContext.values(the whole object) is used as a dependency, so the effect reruns on every unrelated field change (e.g. typing the caregiver name), not just when the relevantConsentReadfield changes — it's guarded from redundant writes but still wasteful. Also,formikContext.setFieldValueis used inside the effect but omitted from the dependency array.♻️ Suggested refactor
useEffect(() => { if (!scrollContainer || !consentDocument?.content) return; const fieldName = `${formSteps[currentConsentStep]}ConsentRead`; - if (formikContext.values[fieldName]) return; + if (formikContext.values[fieldName]) return; if (scrollContainer.clientHeight > 0 && scrollContainer.scrollHeight <= scrollContainer.clientHeight) { formikContext.setFieldValue(fieldName, true); } - }, [scrollContainer, consentDocument?.content, currentConsentStep, formikContext.values]); + }, [scrollContainer, consentDocument?.content, currentConsentStep, formikContext.values[`${formSteps[currentConsentStep]}ConsentRead`], formikContext.setFieldValue]);As per coding guidelines,
{app,__tests__,test,stories}/**/*.{js,jsx}: "Followreact-hooks/exhaustive-depsrules for hook dependencies."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/pages/patient/DataDonationConsentDialog.js` around lines 167 - 176, The `useEffect` in `DataDonationConsentDialog` is depending on the entire `formikContext.values` object, which makes it rerun on unrelated form updates; change it to depend only on the specific `fieldName` value used by this effect. Also include `formikContext.setFieldValue` in the dependency array since it is referenced inside the effect. Keep the logic around `scrollContainer`, `consentDocument?.content`, and `currentConsentStep` intact while tightening the dependencies to satisfy `react-hooks/exhaustive-deps`.Source: Coding guidelines
app/components/datasources/DataConnections.js (1)
336-354: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueMinor:
connectStateUI/inviteExpiredare computed before the early-return checks.
getConnectStateUI(...)(which does time-ago formatting work) andinviteExpiredare computed unconditionally at the top of the reducer callback, even for providers that get excluded a few lines later via therequiresLoggedInUser/requiresExistingDataSourceearly returns. Moving those two computations below the early-return guards would avoid the wasted work.♻️ Optional reordering
const provider = providers[providerName]; const dataSource = getCurrentDataSourceForProvider(patient, providerName); - const connectStateUI = getConnectStateUI(patient, isLoggedInUser, providerName); - const inviteExpired = dataSource?.expirationTime < moment.utc().toISOString(); // If the provider requires a logged in user to create the connection, then ensure that is the case. if (!!provider.requiresLoggedInUser && !isLoggedInUser) { return result; } // If the provider requires an existing data source to create the connection, then ensure that is the case. // This mechanism can be used to limit access to certain providers to only users who have previously connected // or where Tidepool has created a data source on their behalf. if (!!provider.requiresExistingDataSource && !dataSource) { return result; } + const connectStateUI = getConnectStateUI(patient, isLoggedInUser, providerName); + const inviteExpired = dataSource?.expirationTime < moment.utc().toISOString(); + let connectState;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/components/datasources/DataConnections.js` around lines 336 - 354, Move the `getConnectStateUI(...)` and `inviteExpired` computations in `DataConnections.js` so they run only after the `provider.requiresLoggedInUser` and `provider.requiresExistingDataSource` early-return checks. Keep the existing guard logic in the reducer callback, and compute those values only for providers that actually continue past the exclusions to avoid unnecessary work.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@AGENTS.md`:
- Around line 177-183: The markdown example fences in AGENTS.md are missing
language tags, which triggers markdownlint warnings. Update the example blocks
to use an explicit tag such as text so the fenced code samples are properly
labeled. Apply the same fix to the related example sections referenced by the
comment, keeping the formatting consistent across those blocks.
In `@app/pages/oauth/OAuthConnection.js`:
- Around line 225-232: The “I understand” button in OAuthConnection is reusing
the same DOM id as the mobile back button, which can confuse selectors. Update
the Button in the accept flow to use a distinct accept-specific id (for example,
one tied to the handleAccept action), and make sure the mobile “Back to
Tidepool” Button keeps its own separate id.
- Around line 167-171: The OAuthConnection logo render uses a raw img without
alt text, and it should match the theme-ui pattern used elsewhere in this
component. Update the isAcceptStatus block in OAuthConnection to use the
theme-ui Image component instead of img, and provide a meaningful explicit alt
attribute for the logo, keeping the existing logoSrc and sizing behavior intact.
- Around line 113-129: The handleAccept flow sets acceptProcessing to true
before validating return_url, so invalid or missing values leave the spinner
stuck. Move the loading state update in OAuthConnection.handleAccept so it only
happens on the safe redirect path, and make sure the invalid-return_url branch
resets or never sets the flag while surfacing an error to the user instead of
only logging to console.
---
Nitpick comments:
In `@app/components/datasources/DataConnections.js`:
- Around line 336-354: Move the `getConnectStateUI(...)` and `inviteExpired`
computations in `DataConnections.js` so they run only after the
`provider.requiresLoggedInUser` and `provider.requiresExistingDataSource`
early-return checks. Keep the existing guard logic in the reducer callback, and
compute those values only for providers that actually continue past the
exclusions to avoid unnecessary work.
In `@app/pages/patient/DataDonationConsentDialog.js`:
- Around line 167-176: The `useEffect` in `DataDonationConsentDialog` is
depending on the entire `formikContext.values` object, which makes it rerun on
unrelated form updates; change it to depend only on the specific `fieldName`
value used by this effect. Also include `formikContext.setFieldValue` in the
dependency array since it is referenced inside the effect. Keep the logic around
`scrollContainer`, `consentDocument?.content`, and `currentConsentStep` intact
while tightening the dependencies to satisfy `react-hooks/exhaustive-deps`.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 73430153-0b7a-43d1-8cb2-5b0644d3a291
⛔ Files ignored due to path filters (5)
app/core/icons/oura_logo.pngis excluded by!**/*.pngapp/core/icons/oura_logo.svgis excluded by!**/*.svgapp/core/icons/oura_logo_black.pngis excluded by!**/*.pngapp/core/icons/oura_logo_black.svgis excluded by!**/*.svgapp/pages/oauth/images/consent_data.pngis excluded by!**/*.png
📒 Files selected for processing (12)
.gitignoreAGENTS.md__tests__/unit/pages/dashboard/PatientDrawer/StackedDaily/StackedDaily.test.jsapp/components/datasources/DataConnections.jsapp/components/datasources/useProviderConnectionPopup.jsapp/pages/oauth/OAuthConnection.jsapp/pages/patient/DataDonationConsentDialog.jsapp/providers/AppBanner/appBanners.jspackage.jsontest/unit/components/chart/settings.test.jstest/unit/components/datasources/DataConnections.test.jstest/unit/components/useProviderConnectionPopup.test.js
| ``` | ||
| <Imperative summary (50 chars or less)> | ||
|
|
||
| <Optional body: 2-4 sentences> | ||
|
|
||
| <Optional bullet points, one per line with "- "> | ||
| ``` |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add language tags to the example code fences.
These unlabeled fences trigger the markdownlint warning and should be tagged explicitly (for example, text).
🛠 Suggested fix
-```
+```textAlso applies to: 192-201, 203-211
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 177-177: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AGENTS.md` around lines 177 - 183, The markdown example fences in AGENTS.md
are missing language tags, which triggers markdownlint warnings. Update the
example blocks to use an explicit tag such as text so the fenced code samples
are properly labeled. Apply the same fix to the related example sections
referenced by the comment, keeping the formatting consistent across those
blocks.
Source: Linters/SAST tools
| const handleAccept = () => { | ||
| // Return to the authorization flow after accepting. The backend may enforce (or not) any | ||
| // actual acceptance requirements for the connection (e.g. formal consent) and, if not met, | ||
| // reroute back to this accept step until resolved. In the basic case, the backend does not | ||
| // impose actual acceptance requirements other than just adding the accepted query param. | ||
| trackMetric('Oauth - Connection - Accept', { providerName, status }); | ||
| setAcceptProcessing(true); | ||
| const returnUrl = queryParams.get('return_url'); | ||
|
|
||
| // Validate return_url is a safe relative path to prevent open redirect attacks | ||
| if (isSafeReturnUrl(returnUrl)) { | ||
| window.location.href = returnUrl; | ||
| } else { | ||
| // eslint-disable-next-line no-console | ||
| console.error('OAuthConnection: invalid or missing return_url query parameter on accept.'); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
acceptProcessing is never reset when return_url is invalid/missing.
setAcceptProcessing(true) runs before validation, so when isSafeReturnUrl fails the button remains in the processing/spinner state indefinitely with only a console.error and no user-facing feedback. Set the flag only on the successful redirect path (and surface an error otherwise).
🛠️ Proposed fix
const handleAccept = () => {
trackMetric('Oauth - Connection - Accept', { providerName, status });
- setAcceptProcessing(true);
const returnUrl = queryParams.get('return_url');
// Validate return_url is a safe relative path to prevent open redirect attacks
if (isSafeReturnUrl(returnUrl)) {
+ setAcceptProcessing(true);
window.location.href = returnUrl;
} else {
// eslint-disable-next-line no-console
console.error('OAuthConnection: invalid or missing return_url query parameter on accept.');
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const handleAccept = () => { | |
| // Return to the authorization flow after accepting. The backend may enforce (or not) any | |
| // actual acceptance requirements for the connection (e.g. formal consent) and, if not met, | |
| // reroute back to this accept step until resolved. In the basic case, the backend does not | |
| // impose actual acceptance requirements other than just adding the accepted query param. | |
| trackMetric('Oauth - Connection - Accept', { providerName, status }); | |
| setAcceptProcessing(true); | |
| const returnUrl = queryParams.get('return_url'); | |
| // Validate return_url is a safe relative path to prevent open redirect attacks | |
| if (isSafeReturnUrl(returnUrl)) { | |
| window.location.href = returnUrl; | |
| } else { | |
| // eslint-disable-next-line no-console | |
| console.error('OAuthConnection: invalid or missing return_url query parameter on accept.'); | |
| } | |
| }; | |
| const handleAccept = () => { | |
| // Return to the authorization flow after accepting. The backend may enforce (or not) any | |
| // actual acceptance requirements for the connection (e.g. formal consent) and, if not met, | |
| // reroute back to this accept step until resolved. In the basic case, the backend does not | |
| // impose actual acceptance requirements other than just adding the accepted query param. | |
| trackMetric('Oauth - Connection - Accept', { providerName, status }); | |
| const returnUrl = queryParams.get('return_url'); | |
| // Validate return_url is a safe relative path to prevent open redirect attacks | |
| if (isSafeReturnUrl(returnUrl)) { | |
| setAcceptProcessing(true); | |
| window.location.href = returnUrl; | |
| } else { | |
| // eslint-disable-next-line no-console | |
| console.error('OAuthConnection: invalid or missing return_url query parameter on accept.'); | |
| } | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/pages/oauth/OAuthConnection.js` around lines 113 - 129, The handleAccept
flow sets acceptProcessing to true before validating return_url, so invalid or
missing values leave the spinner stuck. Move the loading state update in
OAuthConnection.handleAccept so it only happens on the safe redirect path, and
make sure the invalid-return_url branch resets or never sets the flag while
surfacing an error to the user instead of only logging to console.
| {isAcceptStatus && ( | ||
| <Flex sx={{ flexDirection: 'row', justifyContent: 'center' }} mb={5}> | ||
| <img src={logoSrc} width={190} /> | ||
| </Flex> | ||
| )} |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use theme-ui Image and add an alt attribute for the logo.
This raw <img> has no alt (jsx-a11y/alt-text) and is inconsistent with the theme-ui Image used at Line 192. Use Image with an explicit alt.
♻️ Proposed change
- <img src={logoSrc} width={190} />
+ <Image src={logoSrc} alt="Tidepool" sx={{ width: '190px' }} />As per coding guidelines: "Use theme-ui components such as Box, Flex, Text, Divider, and Link for layout and styling."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| {isAcceptStatus && ( | |
| <Flex sx={{ flexDirection: 'row', justifyContent: 'center' }} mb={5}> | |
| <img src={logoSrc} width={190} /> | |
| </Flex> | |
| )} | |
| {isAcceptStatus && ( | |
| <Flex sx={{ flexDirection: 'row', justifyContent: 'center' }} mb={5}> | |
| <Image src={logoSrc} alt="Tidepool" sx={{ width: '190px' }} /> | |
| </Flex> | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/pages/oauth/OAuthConnection.js` around lines 167 - 171, The
OAuthConnection logo render uses a raw img without alt text, and it should match
the theme-ui pattern used elsewhere in this component. Update the isAcceptStatus
block in OAuthConnection to use the theme-ui Image component instead of img, and
provide a meaningful explicit alt attribute for the logo, keeping the existing
logoSrc and sizing behavior intact.
Source: Coding guidelines
| <Button | ||
| id="oauth-redirect-home-button" | ||
| variant="primary" | ||
| onClick={handleAccept} | ||
| processing={acceptProcessing} | ||
| > | ||
| {t('I understand')} | ||
| </Button> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Duplicate DOM id oauth-redirect-home-button.
This accept "I understand" button reuses the same id as the mobile "Back to Tidepool" button (Line 271). They're mutually exclusive at render time, but the shared id is misleading and can collide with id-based test/metric selectors. Rename to something accept-specific (e.g. oauth-accept-button).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/pages/oauth/OAuthConnection.js` around lines 225 - 232, The “I
understand” button in OAuthConnection is reusing the same DOM id as the mobile
back button, which can confuse selectors. Update the Button in the accept flow
to use a distinct accept-specific id (for example, one tied to the handleAccept
action), and make sure the mobile “Back to Tidepool” Button keeps its own
separate id.
No description provided.