This repository is governed by Mozilla's code of conduct and etiquette guidelines.
This repository is governed by Mozilla's code of conduct and etiquette guidelines.
For more details, please read the
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/).
## How to Report
For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page.
Simulates a COLD MAIN (app icon launch) launch to report FullyDrawn, e.g when the user sees the app fully drawn after launching it.
* COLD VIEW Nav start:
An app link launch to load a page. It measures until "navigation starts" which is an internal Gecko event that indicates we're starting to load a page.
An app link launch to load a page. It measures until "navigation starts" which is an internal Gecko event that indicates we're starting to load a page.
### What to do after a performance regression is reported.
@ -27,7 +27,7 @@ On the reported [ticket](https://github.com/mozilla-mobile/fenix/issues/25253),
* **cold_main_first_frame**: it's the test we would like to run, we could also pass `cold_view_nav_start` depending on the regression type.
* **--startcommit**: it's the commit before the regression.
* **cold_main_first_frame**: it's the test we would like to run, we could also pass `cold_view_nav_start` depending on the regression type.
* **--startcommit**: it's the commit before the regression.
* **--endcommit** it's the commit where the regression appears.
* **--repository_to_test_path** is the path where your local Fenix repository is.
@ -89,7 +89,7 @@ These files are the output of the script:
* **Cold_main_first_frame-durations.txt**: Will contain the raw information of each repetition of the test.
With these files, we can identify which commit, introduced the regression by checking file by file which results are closer to the ones reported one the regression ticket.
With these files, we can identify which commit, introduced the regression by checking file by file which results are closer to the ones reported one the regression ticket.
After we found the regressing commit, we just have to update the ticket, posting our finding and tagging the person that introduced to research how to optimize the patch. Normally if the regression is significant we will ask to undo the commit until the patch is optimized.
@ -98,7 +98,7 @@ After we found the regressing commit, we just have to update the ticket, posting
* If you would like to graph the results you can use `python3 analyze_durations.py --graph results.txt`.
*Just keep in mind, the results provide from the Performance team are from running the tests on a Moto G 5. Running on a more powerful device could cause the results to diverge.
* In case, you need to start recording a profile in startup https://profiler.firefox.com/docs/#/./guide-startup-shutdown?id=firefox-for-android.
### Identifying the source of the regression.
Our main task, when looking for a performance regression is just to identify the faulty commit, but if we would like to figure out what is the exact cause, we will need to take [profile](https://wiki.mozilla.org/Performance/Fenix/Performance_reviews#Profile) from regressing version, and the version before to try to identify what could be causing the issue, checking the code path of the regressing commit could give us some hints to where to look.
Our main task, when looking for a performance regression is just to identify the faulty commit, but if we would like to figure out what is the exact cause, we will need to take [profile](https://wiki.mozilla.org/Performance/Fenix/Performance_reviews#Profile) from regressing version, and the version before to try to identify what could be causing the issue, checking the code path of the regressing commit could give us some hints to where to look.
For an overview of our current architecture, please see [this document](https://github.com/mozilla-mobile/fenix/blob/master/docs/architecture-overview.md)
---
---
These are some of the major architecture decisions we've made so far in Fenix. [Why?](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)
@ -90,4 +90,3 @@ We will experiment with writing new components using MVI unidirectional principl
Because all changes can be represented by a single, merged and serialized Observable or Flowable, we should be able to use this for debugging. All ViewStates, Changes, and Actions/Intents will be easily loggable to observe the causes of state issues.
* Is this a crash due to a recent change? If so, contact the developer.
* The histogram on the right side can help determine this along with checking the Firefox-Beta and Firefox Sentry products.
* Triage the crash to determine if the issue is real and requires a Bugzilla issue to track it.
* Triage the crash to determine if the issue is real and requires a Bugzilla issue to track it.
* When filing an issue add a link to it as a comment in the Sentry crash for the products (nightly, beta, release) where the crash appears.
* Notify the relevant teams on Slack/Matrix that there's a new crash in Nightly that needs urgent attention, e.g. **#synced-client-integrations** for all things involving application services (A-S), **#nimbus-rust-sdk** for Nimbus, and **[GeckoView on Matrix](https://chat.mozilla.org/#/room/#geckoview:mozilla.org)**.
Create a new [milestone](https://github.com/mozilla-mobile/fenix/milestones) for the `[nightly_version]` and close the existing `[beta_version]` milestone.
Create a new [milestone](https://github.com/mozilla-mobile/fenix/milestones) for the `[nightly_version]` and close the existing `[beta_version]` milestone.
The milestone is an indicator of the Fenix version where the code related to the issue is landed, and does not need to reflect when the issue is closed by QA verified. This is useful for keeping track of when a feature is shipped.
Examine all the remaining open issues in the closed milestone to see if the issue should be closed or remove the tagged milestone depending on what is appropriate. If an issue is still in "eng:qa-needed", then it is fine to let it remain in the current closed milestone and open. If an issue clearly doesn't require "eng:qa-needed" (eg, Remove strings in 104, Fix typo, etc), then remove the label and close the issue. If an issue is clearly unresolved due to being reopened by QA and work still continues, remove the milestone.
Examine all the remaining open issues in the closed milestone to see if the issue should be closed or remove the tagged milestone depending on what is appropriate. If an issue is still in "eng:qa-needed", then it is fine to let it remain in the current closed milestone and open. If an issue clearly doesn't require "eng:qa-needed" (eg, Remove strings in 104, Fix typo, etc), then remove the label and close the issue. If an issue is clearly unresolved due to being reopened by QA and work still continues, remove the milestone.
### [Dev Team] Renew telemetry
@ -50,7 +50,7 @@ After the Beta cut, another task is to renew/remove all soon to expire telemetry
4. File an issue for telemetry renewal so that a patch can target it and assign the issue to Product for increased visibility, as a reminder to to address the expiring metrics. See [issue 28190](https://github.com/mozilla-mobile/fenix/issues/28190) for an example.
5. Create a PR for review. Modify `[nightly_version add 2]`_renewal_request.txt and paste it to the PR for data review. This comment can be auto-generated using the filled `[nightly_version add 2]`_expiry_list.csv and the `tools/data_renewal_request.py` helper. Copy the filled CSV into the tools directory and run the script to create a `[nightly_version add 2]`_filled_renewal_request.txt file that will contain the text required for data review. Make sure it includes (or add manually if necessary):
- When will this collection now expire?
- Why was the initial period of collection insufficient?
- Why was the initial period of collection insufficient?
6. Please also check if you're responsible for Focus telemetry renewal.
### [Dev Team] Remove unused strings
@ -59,7 +59,7 @@ Now that we made the Beta cut, we can remove all the unused strings marked moz:r
1. File a GitHub issue named "Remove all unused strings marked moz:removedIn <= `[release_version subtract 1]`".
2. Search and remove all strings marked `moz:removedIn="[release_version subtract 1]"`.
3. Put up a pull request.
3. Put up a pull request.
4. Please also check if you're responsible for Focus as well.
@ -20,4 +20,4 @@ See [here](https://github.com/mozilla-mobile/fenix/wiki/Adjust-Usage) for detail
Sentry collects a stack trace for each crash in Fenix.
If the user has "Telemetry" enabled under Data Choices in the browser settings, then Sentry collects breadcrumbs containing the name of each Android Fragment in the app. This helps an engineer diagnose the cause of the crash by seeing the internal names of screens visited before the crash occurred, i.e. Browser, Search, Home, etc. No information is stored about any arguments passed to any Fragments.
If the user has "Telemetry" enabled under Data Choices in the browser settings, then Sentry collects breadcrumbs containing the name of each Android Fragment in the app. This helps an engineer diagnose the cause of the crash by seeing the internal names of screens visited before the crash occurred, i.e. Browser, Search, Home, etc. No information is stored about any arguments passed to any Fragments.
@ -20,4 +20,4 @@ Generally, there shouldn't be large, branching blocks directly inside Android li
### Activities
Activities should exist only as entry points to the app in order to display fragments. The logic should be limited and should be testable as Robolectric tests with TestNavigators for testing navigation.
Activities should exist only as entry points to the app in order to display fragments. The logic should be limited and should be testable as Robolectric tests with TestNavigators for testing navigation.
@ -131,4 +131,4 @@ Steps (1) to (3) can be quite time consuming. Emulator snapshots can help with t
* Launch an emulator and perform steps 1 to 3. You may need to modify your Fennec build to create an X86 build for your emulator (target `i686-linux-android`).
* Click on the three dot menu in the emulator toolbar and select "Snapshots". Press the "Take Snapshot" button. If needed give you snapshot a descriptive name in case you will need to have multiple "test snapshots".
* With the "Play" button you can always reset your emulator to that state and repeat the migration process.
* With the "Play" button you can always reset your emulator to that state and repeat the migration process.
@ -15,7 +15,7 @@ Purpose: meet any time we start design for a new feature (esp large ones) to dis
* Any existing technical constraints or dependencies (on Android OS, or other Firefox Mobile teams like GV or A-C)
* Alignment on user stories
Design Handoff
Design Handoff
Who: designer, engineer, product manager, QA lead
Purpose: before engineering sprint for a feature starts to discuss:
* Overall purpose of feature and how it relates to or interacts with existing features
@ -54,7 +54,7 @@ Purpose: before engineering sprint for a feature starts to discuss:
* Engineers will remove the “waiting” label and re-apply the appropriate label (“in progress,” “QA needed”).
## UX Review
IF a user story has a UX component that needs review, when it is ready for review:
IF a user story has a UX component that needs review, when it is ready for review:
* Consider hopping on a call to do a ‘desk check’ with the Designer*
* Engineer will add a gif/screenshot/apk (as applicable to the issue)
* Engineer will @mention the designer in the user story and ping the Designer on Slack, and add the `needs:UX-feedback` label
@ -79,7 +79,7 @@ Use tags on open PRs to show which part of the process it is on. Some notable on
4. [pr:waiting-for-authors](https://github.com/mozilla-mobile/fenix/labels/pr%3Awaiting-for-authors) - PR that has been approved and awaiting any changes before they can land. Usually a PR might be approved, but has not been landed because it is waiting for followup changes.
## QA
* Engineers will label stories as [eng:qa:needed](https://github.com/mozilla-mobile/fenix/labels/eng%3Aqa%3Aneeded) when the ticket is ready to be tested (which will move the ticket to the ‘Ready for QA’ column’).
* Engineers will label stories as [eng:qa:needed](https://github.com/mozilla-mobile/fenix/labels/eng%3Aqa%3Aneeded) when the ticket is ready to be tested (which will move the ticket to the ‘Ready for QA’ column’).
* QA will review the ticket and determine whether it can be manually tested. If no QA is needed, QA will close the ticket and move it to the ‘Done’ column.
**Once you create this PR, the CI for both the original and the duplicate PRs will run. When everything is green, you can merge either of them.**
5. To land the duplicate, close the original PR first, refresh mergify (`@Mergifyio refresh`), and then add `needs-landing` label to your PR.
5. To land the duplicate, close the original PR first, refresh mergify (`@Mergifyio refresh`), and then add `needs-landing` label to your PR.
* Mergify won’t merge the duplicate while the original is open, since they both have the same SHA and mergify does honour the first one over those created consequently.
@ -46,7 +46,7 @@ OR
5. To land the original:
* i. Make sure that contributor's branch hasn't diverged from yours (they must have the same SHA).
* ii. The change has to be on the top of the main branch when it is first in line in the merge queue.
* iii. It requires the needs-landing label.
* ii. The change has to be on the top of the main branch when it is first in line in the merge queue.
* iii. It requires the needs-landing label.
**NB**: Adding `needs-landing` label while failing to ensure the same SHA will block the mergify queue and will require manual intervention: mergify will trigger CI for the original PR again and wait for it to finish, but CI won’t run all the checks because there is no PR with the same SHA any more that backs it up. If that happens, talk to the release team.
Please use the above directions as a first way to get info. Use the following directions if your crash does not show up in the crash window of Firefox or if the crash prevents you from accessing the settings of Firefox.
Please use the above directions as a first way to get info. Use the following directions if your crash does not show up in the crash window of Firefox or if the crash prevents you from accessing the settings of Firefox.
To get information about a crash you will need an Android device that reproduces a crash, a computer running Windows, macOS or Linux and a USB cable that connects your device to your computer.
To get information about a crash you will need an Android device that reproduces a crash, a computer running Windows, macOS or Linux and a USB cable that connects your device to your computer.
## Configuring your phone
* Enable Developer Mode
* On stock Android open the Android Settings and use the search at the top to find `build number`
* Tap the build number 7 times
* Enable Developer Mode
* On stock Android open the Android Settings and use the search at the top to find `build number`
* Tap the build number 7 times
* For other devices use your favorite search engine or YouTube to find steps for your device.
* Enable Android USB debugging
* On stock Android Open the Android Settings and use the search at the top to find `USB debugging` and enable it
@ -51,4 +51,4 @@ To get information about a crash you will need an Android device that reproduces
## Optional Cleanup
* It is recommended to disable USB debugging once you are done collecting this information
* You can remove the Android SDK Platform tools by deleting the folders and zip file
* You can remove the Android SDK Platform tools by deleting the folders and zip file
Adding tag information to a metric used to involve editing the [Glean Annotations repository], but you can now add this
Adding tag information to a metric used to involve editing the [Glean Annotations repository], but you can now add this
information directly when adding or modifying `metrics.yaml`. Just add a section called `metadata` to the metric and add a list of tags that correspond to it.
For example:
@ -27,9 +27,9 @@ For example:
## Updating the feature tags
The set of valid tags is documented in a file called `tags.yaml`, but should never be updated by hand.
The set of valid tags is documented in a file called `tags.yaml`, but should never be updated by hand.
If a feature labels is ever added or removed, you can synchronize the tags file in the source tree by running `./tools/update-glean-tags.py` in the root of the repository.
Note that a tag *must* be specified in `tags.yaml` for it to be usable in a metric, so if a tag is removed from `tags.yaml` all uses of it must be removed from `metrics.yaml`.
@ -11,12 +11,12 @@ More info [here](https://mozilla.github.io/glean-annotations/contributing/creati
# Data review
Data reviews are needed on all PRs that add new telemetry or modify existing telemetry.
Data reviews are needed on all PRs that add new telemetry or modify existing telemetry.
1. The implementer must complete the forms for [data renewal](https://github.com/mozilla/data-review/blob/main/renewal_request.md) or [a new data request](https://github.com/mozilla/data-review/blob/main/request.md) and put them as a comment in their PR.
2. Once the form is complete, contact a [Data Steward](https://wiki.mozilla.org/Data_Collection) to arrange a review. Note: a data review does not replace code review! The PR should not land without both a data review and a code review.
3. Once the data review is complete, add the link to the approval in the `data_reviews` sub-section of your metric in the `metrics.yaml` file.
Example:
Example:
```
download_notification:
@ -62,7 +62,7 @@ Make sure you are selecting the correct Category of data that is being collected
## Approval process
For each telemetry probe that we want to renew, the data-review will ask us [these questions](https://github.com/mozilla/data-review/blob/main/renewal_request.md). Each probe/group of related probes should have answers to those questions ([example](https://github.com/mozilla-mobile/fenix/pull/20517#issuecomment-887038794)).
For each telemetry probe that we want to renew, the data-review will ask us [these questions](https://github.com/mozilla/data-review/blob/main/renewal_request.md). Each probe/group of related probes should have answers to those questions ([example](https://github.com/mozilla-mobile/fenix/pull/20517#issuecomment-887038794)).
### Example renewal data request
```
@ -104,4 +104,4 @@ never_save
Still need to optimize this feature and we want trends from 6+mo of data.
```
For product-defined telemetry, this will involve meeting with a product manager and discussing each probe. There are three options: renew the probe for another length of time (usually 6 months), let the probe expire to evaluate later if the probe is still needed, or remove the probe entirely.
For product-defined telemetry, this will involve meeting with a product manager and discussing each probe. There are three options: renew the probe for another length of time (usually 6 months), let the probe expire to evaluate later if the probe is still needed, or remove the probe entirely.
@ -5,18 +5,18 @@ Removing strings manually could cause crashes in **Beta** and **Release** versio
Any landed string that is not removed while in development in Nightly will persist through 3 Firefox releases (Nightly, Beta, Release) before we can remove them from our code base. For example,
if you want to remove a string that has already shipped prior to **Firefox Nightly 93**, the same string will still be in-use in **Firefox Beta 92** and **Firefox Release 91**. This means the string will be marked as unused and removed in 93 while still riding the train, and it can be removed safely when **Firefox Release 93** no longer ships, for instance, **Firefox Release 94** and beyond.
To keep us safe when you want to remove strings from nightly:
To keep us safe when you want to remove strings from nightly:
1. Add these attributes to the target strings `moz:removedIn="<<ACTUAL_NIGHTLY_VERSION>>"` and `tools:ignore="UnusedResources"`.
Example PR https://github.com/mozilla-mobile/fenix/pull/20980.
## When to remove an unused string and how
Strings that have been tagged with `moz:removedIn` attributes are safe to be removed after the marked version is no longer shipping and no longer in-use or needed.
Strings that have been tagged with `moz:removedIn` attributes are safe to be removed after the marked version is no longer shipping and no longer in-use or needed.
Consult the [Firefox release calendar](https://wiki.mozilla.org/Release_Management/Calendar). Let's say the Beta cut just happened and we are at Firefox Nightly 109, Firefox Beta 108 and Firefox Release 107. Everything marked with `moz:removedIn`<= 106 can now be removed.
@ -128,7 +128,7 @@ The view defines the mapping of State to UI. This includes initial setup of View
Views should be as dumb as possible, and should include little or no conditional logic. Ideally, each primitive value in a State object is set on some field of a UI element, with no other logic included.
Views set listeners on to UI elements, which trigger calls to one or more Interactors.
Views set listeners on to UI elements, which trigger calls to one or more Interactors.
-------
@ -162,7 +162,7 @@ These link to the architectural code that accomplishes those features:
## Known Limitations
There are a few known edge cases and potential problems with our architecture, that in certain circumstances can be confusing.
- Since [Stores](#store) live at the fragment level, our architecture does not define any way to set data outside of that scope.
- Since [Stores](#store) live at the fragment level, our architecture does not define any way to set data outside of that scope.
- For example, if it is determined during application startup that we need to run in private mode, it must eventually be passed to a fragment, but we don't specify how it will be handled until that point.
- We have no defined way to set values shared by all fragments. They must either be passed as an argument to every individual fragment, or use some system outside of our architecture (e.g., by accessing SharedPreferences).
- There isn't always a clear logical distinction between what should provoke a state change (by dispatching an [Action](#action) to a [Store](#store)), and what should start a new fragment. Passing arguments while creating a new fragment causes changes to the new [State](#state) object, while taking a very different code path than the rest of our app would.
@ -16,11 +16,11 @@ To opt in or out of Glean telemetry reporting, visit the Data collection menu un
## Breadcrumbs
[Breadcrumbs](https://github.com/mozilla-mobile/android-components/blob/main/components/support/base/src/main/java/mozilla/components/support/base/crash/Breadcrumb.kt) are trail of events that are sent with each crash report to both Socorro and Sentry.
[Breadcrumbs](https://github.com/mozilla-mobile/android-components/blob/main/components/support/base/src/main/java/mozilla/components/support/base/crash/Breadcrumb.kt) are trail of events that are sent with each crash report to both Socorro and Sentry.
### Events
In [HomeActivity](https://github.com/mozilla-mobile/fenix/blob/main/app/src/main/java/org/mozilla/fenix/HomeActivity.kt) when `onDestinationChanged` occurs, the destination fragment's name and and whether it is a custom tab is added to the breadcrumbs.
In [HomeActivity](https://github.com/mozilla-mobile/fenix/blob/main/app/src/main/java/org/mozilla/fenix/HomeActivity.kt) when `onDestinationChanged` occurs, the destination fragment's name and and whether it is a custom tab is added to the breadcrumbs.
We are using [`screengrab`](https://docs.fastlane.tools/getting-started/android/screenshots/) which works with fastlane to automate the process of capturing screenshots.
We are using [`screengrab`](https://docs.fastlane.tools/getting-started/android/screenshots/) which works with fastlane to automate the process of capturing screenshots.
All the l10n screenshots are generated through the ui tests. These particular tests run as part of the screenshots package (`app/src/androidTest/mozilla/fenix/ui/screenshots`)
### Run tests locally from Android Studio
Navigate to `app/src/androidTest/mozilla/fenix/ui/screenshots`, once in that directory, run the full test suite or a specific test by clicking on the `>` button.
By running them manually you can check whether the test works or not but screenshots will not be saved.
By running them manually you can check whether the test works or not but screenshots will not be saved.
The package configuration, apk paths as well as the locales are set in [Screengrab file](https://github.com/mozilla-mobile/fenix/blob/073fd8939067bc7a367d8db497bcf53fbd24cdd2/fastlane/Screengrabfile#L5).
In case there is a change there the file has to be modified accordingly.
Firefox for Android roughly follows the [Firefox Gecko release schedule](https://wiki.mozilla.org/Release_Management/Calendar#Calendars).
This means we cut a Beta every 4 weeks, with a full cycle (~4 weeks) of baking on Beta before going to Production release.
This means we cut a Beta every 4 weeks, with a full cycle (~4 weeks) of baking on Beta before going to Production release.
The [Firefox for Android release schedule](https://docs.google.com/spreadsheets/d/1HotjliSCGOp2nTkfXrxv8qYcurNpkqLWBKbbId6ovTY/edit#gid=0) contains more details related to specific Mobile handoffs.
@ -19,14 +19,14 @@ There are two releases this covers: the current changes in the Fenix Nightly cha
- [ ] Make a new Beta: Follow instructions [here](https://github.com/mozilla-mobile/fenix/wiki/Creating-a-release-branch) and notify the Release Management team (slack: #releaseduty-mobile). QA team is notified that a Beta release has been captured and they will run tests for Beta release sign-off
- [ ] Once there is GREEN QA signoff, the Release Management team (slack: #releaseduty-mobile) pushes the Beta version in the [Google Play Console](https://play.google.com/console/)
- [ ] Check Sentry each day for issues on [Firefox Beta](https://sentry.prod.mozaws.net/operations/firefox-beta/) and if nothing concerning, Release Management team bumps releases to 25%. Subsequent Beta builds are bumped to 100% assuming no blocking issues arise.
### Bugfix uplifts / Beta Product Integrity
### Bugfix uplifts / Beta Product Integrity
- [ ] If bugs are considered release blocker then find someone to fix them on main and the milestone branch (cherry-pick / uplift)
- [ ] Add the uplift request to the appropriate row in the [Uplifts document](https://docs.google.com/spreadsheets/d/1qIvHpcQ3BqJtlzV5T4M1MhbWVxkNiG-ToeYnWEBW4-I/edit#gid=0). Ask for approval of uplift from Release Owner [amedyne](https://github.com/amedyne) and then notify Release Management team (slack: #releaseduty-mobile) of the uplift changes
- Note: Beta release versions are captured at least once a week during the Beta cycle.
### Production Release Candidate
- Production Release Candidate is captured on the third week of Beta by the Release Management team (slack: #releaseduty-mobile). This is then sent to Quality Assurance for Production Release Testing Sign-off.
### Production Release Candidate
- Production Release Candidate is captured on the third week of Beta by the Release Management team (slack: #releaseduty-mobile). This is then sent to Quality Assurance for Production Release Testing Sign-off.
The aim of these tests is to check that the synchronization is working between Fenix and Desktop. The intention is to add tests for History, Bookmarks, Tabs and Logins.
The aim of these tests is to check that the synchronization is working between Fenix and Desktop. The intention is to add tests for History, Bookmarks, Tabs and Logins.
At this moment only tests for History and Bookmarks are defined.
### Steps to Run
@ -16,11 +16,11 @@ The process for example for History item Desktop -> Fenix, would be:
### Results
Due to the set up necessary these tests do not run as part of the regular CI, via Taskcluster.
Due to the set up necessary these tests do not run as part of the regular CI, via Taskcluster.
The idea is to have them running on Jenkins periodically (TBD how often).
Once they finish there is a slack notificattion received informing about the result (so far that is configured for #firefox-ios-alerts)
A html file is generated with all the info, for each step to make it easy to debug in case of failure.
## Notes
More detailed info can be found [`here`](https://docs.google.com/document/d/1dhxlbGQBA6aJi2Xz-CsJZuGJPRReoL7nfm9cYu4HcZI/edit?usp=sharing)
More detailed info can be found [`here`](https://docs.google.com/document/d/1dhxlbGQBA6aJi2Xz-CsJZuGJPRReoL7nfm9cYu4HcZI/edit?usp=sharing)