Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b0fb79d
fix: name a build for the week Taipei is in, not the week UTC is in
whes1015 Aug 17, 2026
cc26892
feat(settings): row-align the sponsor card with the hero stack
whes1015 Aug 17, 2026
7b29648
build: stamp the last release into every build
whes1015 Aug 17, 2026
aac96d3
feat(settings): rework the More hero cards, flat and number-led
whes1015 Aug 17, 2026
67fd44f
fix(map): show timeline labels in the device's local time
whes1015 Aug 17, 2026
be1f313
refactor(build): feed the version card its train, not the last release
whes1015 Aug 17, 2026
a1a2fdc
fix(settings): align the three hero-card rows on the same left edge
whes1015 Aug 17, 2026
afe4132
feat(settings): lead the version card with the train number
whes1015 Aug 17, 2026
f6f15c5
feat(settings): fold the server-status card into the hero stack
whes1015 Aug 17, 2026
3fa86e3
feat(settings): always print a fine-print version line on the hero card
whes1015 Aug 17, 2026
e5b7e39
feat(build): let a release's train drop its patch
whes1015 Aug 17, 2026
9a50a69
feat(build): stamp the build date alongside the label and train
whes1015 Aug 17, 2026
0ed0b18
feat(settings): show the build date next to the version badge
whes1015 Aug 17, 2026
a0cbc2e
feat(home): float a gold support pill under the region bar
whes1015 Aug 17, 2026
6a2a45a
fix(l10n): retitle the Traditional Chinese sponsor copy
whes1015 Aug 17, 2026
f7996df
docs(readme): list the Android and iOS beta channels
whes1015 Aug 17, 2026
15a8d24
fix(changelog): read the channel from the build, not its name
whes1015 Aug 17, 2026
ef2802c
feat(changelog): contributor avatar strip, parsed from release bodies
whes1015 Aug 17, 2026
a516d7f
ci: refuse a branch that merges the base or lags behind it
whes1015 Aug 17, 2026
a0c6fe2
fix(log): stop the log screen from feeding itself until it freezes
whes1015 Aug 17, 2026
611b7dc
refactor(log): use Talker's own screen and cap the table at 5000 rows
whes1015 Aug 17, 2026
5e01931
fix(log): suppress the console dump, and keep a replayed level
whes1015 Aug 17, 2026
3608749
fix(log): replay into history without logging it again
whes1015 Aug 17, 2026
a77586f
fix(log): make clearing the log clear the stored log
whes1015 Aug 17, 2026
d133721
fix(log): count a replayed line under its own level
whes1015 Aug 17, 2026
7ef2faa
fix(log): stop the replay from evicting the running session
whes1015 Aug 17, 2026
f50fd14
refactor(log): let the table be the log, and load it whole
whes1015 Aug 17, 2026
84c7cb7
refactor(log): tag each line with an upper-case level
whes1015 Aug 17, 2026
bb6d418
refactor(log): print one plain line per entry
whes1015 Aug 17, 2026
d1a81a8
feat(log): colour the level tag when the terminal can show it
whes1015 Aug 17, 2026
95b422b
fix(log): never emit colour on iOS, where it cannot survive
whes1015 Aug 17, 2026
2045c42
feat(log): colour the log in the terminal, where ANSI works
whes1015 Aug 17, 2026
3c63688
feat(log): wrap the run and its colouring in tool/run.sh
whes1015 Aug 17, 2026
097185a
fix(log): keep the colouriser alive while the run shuts down
whes1015 Aug 17, 2026
00f7244
fix(status): restore the ApiClient hooks the status page needs
whes1015 Aug 17, 2026
b405be4
build: refuse a debug launch that skipped the run script
whes1015 Aug 17, 2026
89c877e
feat(run): refuse to start outside the run script, with Windows support
whes1015 Aug 17, 2026
4f1dab5
feat(more): add the beta channels and partner tiles under Get the app
whes1015 Aug 17, 2026
ee0a72d
feat(network): cache status-dashboard POSTs under their URL
whes1015 Aug 17, 2026
5ab2205
feat(changelog): ground contributor avatars into a tappable name badge
whes1015 Aug 17, 2026
4270107
feat(report): load the CWA report image through the ETag store
whes1015 Aug 17, 2026
c7e736e
fix(location): swallow MissingPluginException when draining breadcrumbs
whes1015 Aug 17, 2026
5db09ec
docs(commit): drop the deleted release-note example files from the link
whes1015 Aug 17, 2026
759c389
build: stop the launch guard from refusing its own run script
whes1015 Aug 17, 2026
4d0bda2
fix(changelog): credit whoever wrote a change, not whoever merged it
whes1015 Aug 17, 2026
7cae2a4
feat(settings): add a button that uploads the diagnostics and the log
whes1015 Aug 17, 2026
2e74f1a
refactor(diagnostics): move the dump to the More menu
whes1015 Aug 17, 2026
1526718
feat(changelog): foot every release card with a link to its GitHub page
whes1015 Aug 17, 2026
a038d36
chore(l10n): trim the diagnostics hint to its essential step
whes1015 Aug 17, 2026
3564cd8
fix(log): say a package's debug assert once instead of once per row
whes1015 Aug 17, 2026
c1db357
fix(tool): sweep the DevFS snapshots every debug run leaves behind
whes1015 Aug 17, 2026
64f9c54
feat(status): add Cloudflare and local probe tables to the status page
whes1015 Aug 18, 2026
6f802d3
refactor(tool): give every workflow its own script
whes1015 Aug 18, 2026
ea092ad
perf(ios): pin gtm-session-fetcher identically in both Xcode containers
whes1015 Aug 18, 2026
e69f6e9
chore: drop the macOS platform this app does not ship
whes1015 Aug 18, 2026
fe0ba60
docs(commit): add tool/commit.sh, the briefing to run before committing
whes1015 Aug 18, 2026
fbda0a0
feat(more): open a version-highlights page from the version card
whes1015 Aug 18, 2026
36fefc7
feat(tool): run all of CI from commit.sh, on a content-hash cache
whes1015 Aug 18, 2026
e06be12
feat(tool): refuse to build without mise
whes1015 Aug 18, 2026
5473798
docs: point every comment at the script paths the restructure moved
whes1015 Aug 18, 2026
96e2801
fix(tool): make the commit briefing block instead of only saying so
whes1015 Aug 18, 2026
6b08797
test(tool): stop the cache tests inheriting DPIP_NO_CACHE
whes1015 Aug 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .githooks/post-checkout
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/gen_build_info.sh"
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/dev/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/release/build_info.sh"
4 changes: 2 additions & 2 deletions .githooks/post-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/gen_build_info.sh"
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/dev/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/release/build_info.sh"
4 changes: 2 additions & 2 deletions .githooks/post-merge
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/gen_build_info.sh"
# Refresh lib/core/build_info.g.dart after HEAD moves. See tool/dev/setup.sh.
exec "$(git rev-parse --show-toplevel)/tool/release/build_info.sh"
26 changes: 26 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Refuses a commit that tool/commit.sh calls a blocker.
#
# It used to only print them. A check nothing enforces is a check, then a habit,
# then neither — this one printed "behind origin/main" and was read and ignored
# in the same minute.
#
# Commit-time severity only (see `profile` in tool/commit.sh): being behind the
# base is a *merge* problem, and demanding a rebase to record work that is not
# going anywhere yet would just teach everybody --no-verify. The gates do not
# run here either — they belong to pre-push, where a minute is affordable and a
# red CI is what it buys back.
#
# git commit --no-verify # the escape hatch, when you mean it
set -euo pipefail
root="$(git rev-parse --show-toplevel)"

# Mid-rebase, mid-merge, mid-cherry-pick: `git commit` runs this hook while HEAD
# is detached and the branch state is meaningless. Every answer it could give
# there is about a tree that exists for the next few seconds.
git_dir="$(git rev-parse --git-dir)"
for state in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD; do
[[ -e "$git_dir/$state" ]] && exit 0
done

exec "$root/tool/commit.sh" --no-check
15 changes: 15 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# The stricter half: everything that has to be true before anyone else sees it.
#
# Where the rebase check becomes a blocker, and where the CI gates run. A push
# is the last moment a mistake is still free — after it, a bad commit message
# can only be fixed by a rebase and a force-push, and a red CI costs a round
# trip for everybody watching the PR.
#
# The gates are content-hash cached, so a push right after a green `tool/check.sh`
# costs about a second.
#
# git push --no-verify # the escape hatch, when you mean it
set -euo pipefail
root="$(git rev-parse --show-toplevel)"
exec "$root/tool/commit.sh" --push
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

## 檢查清單

- [ ] `tool/check_commits.sh origin/main..HEAD` 通過
- [ ] `tool/check/commits.sh origin/main..HEAD` 通過
—— commit 訊息就是更新日誌,格式見 [commit.md](../commit.md)
- [ ] **一個 commit 一件事**(這條 gate 驗不了,靠自己和 review)
- [ ] `mise exec -- flutter analyze` 與 `mise exec -- flutter test` 通過
Expand Down
96 changes: 74 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,67 @@ jobs:
# repair is a rebase and a force-push — and the later that is discovered,
# the more there is to rebase.
- name: Commit message gate
env:
# Through the environment, like every other value here: a base ref is
# a branch name, and on a fork's pull request whoever opened it chose
# that name.
EVENT: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
BEFORE: ${{ github.event.before }}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
range="origin/${{ github.base_ref }}..HEAD"
git fetch --quiet origin "${{ github.base_ref }}"
if [ "$EVENT" = pull_request ]; then
# The branch's own tip, not the checked-out merge commit: a pull
# request builds a synthetic merge of the branch into the base, and
# every commit this gate judges has to be one somebody wrote.
git fetch --quiet origin "$BASE_REF"
range="$(git rev-parse "origin/$BASE_REF")..$HEAD_SHA"
else
# A push event names what it replaced. On the first push to a new
# branch the before-sha is all zeroes, so fall back to the last
# commit alone.
before="${{ github.event.before }}"
case "$before" in
case "$BEFORE" in
''|0000000000000000000000000000000000000000) range="HEAD~1..HEAD" ;;
*) range="$before..HEAD" ;;
*) range="$BEFORE..HEAD" ;;
esac
fi
echo "checking $range"
bash tool/check_commits.sh "$range"
bash tool/check/commits.sh "$range"

# Rebased, not merged, and not behind.
#
# Two reasons, and neither is taste. A merge commit is invisible to the
# gate above — `check_commits.sh` walks with `--no-merges`, because a
# merge message is generated rather than written — so anything that
# arrives through one is never judged. And a branch that is behind was
# tested against a main that no longer exists; the gates that passed
# describe a tree nobody will ever have.
- name: Branch is rebased on the base
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
git fetch --quiet origin "$BASE_REF"
base="$(git rev-parse "origin/$BASE_REF")"

if merges="$(git rev-list --merges "$base..$HEAD_SHA")" &&
[ -n "$merges" ]; then
echo "::error::this branch merges $BASE_REF instead of rebasing onto it"
git --no-pager log --format=' %h %s' --merges "$base..$HEAD_SHA"
printf '\n git rebase origin/%s\n git push --force-with-lease\n' "$BASE_REF"
exit 1
fi

if ! git merge-base --is-ancestor "$base" "$HEAD_SHA"; then
echo "::error::this branch is behind $BASE_REF; rebase before merging"
printf ' %s is %s commit(s) ahead of this branch\n' \
"$BASE_REF" "$(git rev-list --count "$HEAD_SHA..$base")"
printf '\n git fetch origin\n git rebase origin/%s\n git push --force-with-lease\n' "$BASE_REF"
exit 1
fi

echo "branch gate: rebased on $BASE_REF, no merge commits"

# The commits on the branch are not what lands on main. This repo
# squash-merges, so GitHub builds the merged commit out of the PR's title
Expand All @@ -69,7 +114,7 @@ jobs:
printf '%s\n\n' "$PR_TITLE"
printf '%s\n' "$PR_BODY"
} > /tmp/pr_message.txt
bash tool/check_commits.sh --message /tmp/pr_message.txt
bash tool/check/commits.sh --message /tmp/pr_message.txt

# The workflows themselves, because a broken one does not fail — it is
# *refused*. GitHub interpolates a `run` block whole before bash sees it,
Expand All @@ -84,25 +129,32 @@ jobs:

# Architecture gate — pure grep, no toolchain, fails fast.
- name: Layering gate
run: bash tool/check_layering.sh
run: bash tool/check/layering.sh

# Localization gate — bash + python3 (preinstalled), no toolchain either:
# ARB parity + no hardcoded UI strings. See CLAUDE.md → Localization.
- name: Localization gate
run: bash tool/check_l10n.sh
run: bash tool/check/l10n.sh

# Storage gate — settings via the typed SettingsStore; sqflite only in
# the store that owns each table.
- name: Storage gate
run: bash tool/check_storage.sh
run: bash tool/check/storage.sh

# Every channel sound must resolve, or the channel is rejected on device.
- name: Notification-sound gate
run: bash tool/check_notification_sounds.sh
run: bash tool/check/notification_sounds.sh

# Lockfile must not embed a machine-local path (pubspec_overrides leak).
- name: pubspec.lock path gate
run: bash tool/check_pubspec_lock.sh
run: bash tool/check/pubspec_lock.sh

# Every workflow has a script under tool/; the docs and this file may not
# tell anyone to type the toolchain directly. A copied `flutter test` runs
# whatever SDK the shell cached, and running against the wrong SDK looks
# exactly like running against the right one.
- name: Tooling
run: bash tool/check/tooling.sh

# Flutter/Dart come from mise.toml, so CI uses the exact pinned version
# developers run locally — one source of truth. v4 = Node 24 runtime
Expand All @@ -111,10 +163,13 @@ jobs:
uses: jdx/mise-action@v4

- name: Install dependencies
run: mise exec -- flutter pub get
run: bash tool/dev/deps.sh

- name: Format
run: mise exec -- dart format --set-exit-if-changed lib test
# The same script a developer runs. CI naming its own commands is how CI
# and the local checklist drift apart, and the drift is only ever found
# by a red PR on a branch that was green.
- name: Format and analyze
run: bash tool/dev/analyze.sh

# Committed *.freezed.dart / *.g.dart must match a fresh build — a stale
# generated file (e.g. after editing a model without regenerating) fails
Expand All @@ -126,11 +181,11 @@ jobs:
# fails: the file is `skip-worktree`, so a developer's own copy has the
# symbol and their `flutter analyze` passes while CI's does not.
- name: Build info
run: bash tool/check_build_info.sh
run: bash tool/check/build_info.sh

- name: Codegen is up to date
run: |
mise exec -- dart run build_runner build --delete-conflicting-outputs
bash tool/dev/codegen.sh
# build_info.g.dart is excluded because it *cannot* match: it holds
# the hash of the commit it is committed in.
git diff --exit-code -- . ':!lib/core/build_info.g.dart' || {
Expand All @@ -140,14 +195,11 @@ jobs:
exit 1
}

- name: Analyze
run: mise exec -- flutter analyze

# The ETag-cache tests run SQLite on the host via sqflite_common_ffi;
# ubuntu ships libsqlite3.so.0 but not the unversioned symlink its loader
# needs, so install the dev package.
- name: Install SQLite (sqflite_common_ffi host tests)
run: sudo apt-get update && sudo apt-get install -y libsqlite3-dev

- name: Test
run: mise exec -- flutter test
run: bash tool/dev/test.sh
36 changes: 20 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Every commit on main is a snapshot; every tag is a release.
#
# The three values a build carries come from `tool/version.sh` and nowhere
# The three values a build carries come from `tool/release/version.sh` and nowhere
# else — no human edits a version, and `pubspec.yaml`'s stays a placeholder for
# local runs. See that script for why the label, the train and the ordinal are
# three separate things.
Expand All @@ -26,6 +26,7 @@ jobs:
label: ${{ steps.v.outputs.label }}
train: ${{ steps.v.outputs.train }}
code: ${{ steps.v.outputs.code }}
date: ${{ steps.v.outputs.date }}
prerelease: ${{ steps.v.outputs.prerelease }}
steps:
# Full history: the label counts this week's commits and the train reads
Expand All @@ -36,17 +37,18 @@ jobs:

- id: v
run: |
eval "$(tool/version.sh)"
eval "$(tool/release/version.sh)"
echo "label=$DPIP_LABEL" >> "$GITHUB_OUTPUT"
echo "train=$DPIP_TRAIN" >> "$GITHUB_OUTPUT"
echo "code=$DPIP_CODE" >> "$GITHUB_OUTPUT"
echo "date=$DPIP_DATE" >> "$GITHUB_OUTPUT"
# A tag is a release; anything else is a snapshot.
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
echo "prerelease=false" >> "$GITHUB_OUTPUT"
else
echo "prerelease=true" >> "$GITHUB_OUTPUT"
fi
echo "::notice::$DPIP_LABEL — $DPIP_TRAIN ($DPIP_CODE)"
echo "::notice::$DPIP_LABEL — $DPIP_TRAIN ($DPIP_CODE, built $DPIP_DATE)"

# The ordinal is the one value that must never repeat or go backwards: a
# store that has accepted a code refuses every build at or below it,
Expand All @@ -56,7 +58,7 @@ jobs:
#
# This only sees what *this* workflow published. A build uploaded by hand
# is invisible to it — which is how 408283049 reached TestFlight ahead of
# the scheme. The per-store floors in tool/version.sh are what cover
# the scheme. The per-store floors in tool/release/version.sh are what cover
# those; raise one whenever something is shipped outside this workflow.
- name: Ordinal must be higher than the last published one
env:
Expand Down Expand Up @@ -95,7 +97,7 @@ jobs:
runs-on: macos-latest
runs-on: ${{ matrix.runs-on }}
steps:
# Full history: tool/gen_build_info.sh counts commits and reads tags.
# Full history: tool/release/build_info.sh counts commits and reads tags.
- uses: actions/checkout@v5
with:
fetch-depth: 0
Expand Down Expand Up @@ -141,9 +143,9 @@ jobs:
# The committed copy is a stub; the real values come from git. CI also
# passes them with --dart-define below, so this is belt and braces — but
# it is what makes the file compile at all.
- run: bash tool/gen_build_info.sh
- run: bash tool/release/build_info.sh

- run: mise exec -- flutter pub get
- run: bash tool/dev/deps.sh

# Without these the release build silently falls back to the *debug*
# signing config (see `buildTypes.release` in android/app/build.gradle.kts),
Expand Down Expand Up @@ -190,16 +192,18 @@ jobs:
# two that can disagree.
DPIP_LABEL: ${{ needs.version.outputs.label }}
run: |
# One number for both stores — see tool/version.sh for why they can
# One number for both stores — see tool/release/version.sh for why they can
# be aligned. It is also what the release advertises and what the
# update check compares, so nothing anywhere has to translate.
COMMON="--build-name=${{ needs.version.outputs.train }} \
--build-number=${{ needs.version.outputs.code }} \
--dart-define=DPIP_LABEL=${{ needs.version.outputs.label }} \
--dart-define=DPIP_CODE=${{ needs.version.outputs.code }}"
--dart-define=DPIP_CODE=${{ needs.version.outputs.code }} \
--dart-define=DPIP_TRAIN=${{ needs.version.outputs.train }} \
--dart-define=DPIP_DATE=${{ needs.version.outputs.date }}"
if [ "${{ matrix.platform }}" = "android" ]; then
mise exec -- flutter build apk --release $COMMON
mise exec -- flutter build appbundle --release $COMMON
bash tool/dev/build.sh android $COMMON
bash tool/dev/build.sh bundle $COMMON
mv build/app/outputs/flutter-apk/app-release.apk \
"DPIP-${{ needs.version.outputs.label }}.apk"
else
Expand All @@ -212,7 +216,7 @@ jobs:
# The Flutter step still has to run first: it writes
# FLUTTER_BUILD_NUMBER into ios/Flutter/Generated.xcconfig, which is
# where Info.plist's CFBundleVersion comes from.
mise exec -- flutter build ios --release --no-codesign $COMMON
bash tool/dev/build.sh ios $COMMON
AUTH=(-allowProvisioningUpdates
-authenticationKeyPath "$RUNNER_TEMP/private_keys/AuthKey_${{ secrets.APPSTORE_API_KEY_ID }}.p8"
-authenticationKeyID "${{ secrets.APPSTORE_API_KEY_ID }}"
Expand Down Expand Up @@ -323,17 +327,17 @@ jobs:
# The ordinal rides in the body as an HTML comment: GitHub renders it as
# nothing, so the note stays a note while the app still has something
# unambiguous to compare. See `buildCodeOf` in update_check.dart.
# Built from the commit messages themselves — see tool/release_notes.sh
# Built from the commit messages themselves — see tool/release/notes.sh
# for why a release accumulates across the snapshots it followed while a
# snapshot is only a delta.
- name: Release notes
run: |
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
bash tool/release_notes.sh \
bash tool/release/notes.sh \
"${{ needs.version.outputs.label }}" \
"${{ needs.version.outputs.code }}" --release > NOTES.md
else
bash tool/release_notes.sh \
bash tool/release/notes.sh \
"${{ needs.version.outputs.label }}" \
"${{ needs.version.outputs.code }}" > NOTES.md
fi
Expand All @@ -348,7 +352,7 @@ jobs:
else
# The bare label. `v[0-9]*` is what makes a build a release, so a
# snapshot named `26w33a` is already in its own namespace — and it
# is the name tool/version.sh counts to work out the next letter.
# is the name tool/release/version.sh counts to work out the next letter.
# An earlier `snapshot/` prefix put the tags somewhere the counter
# did not look, so every run computed `a` and this step died on a
# tag that already existed.
Expand Down
13 changes: 8 additions & 5 deletions .metadata
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
- platform: macos
create_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
base_revision: 4cf24164269a5ebf0c16a028a00727d0e77bbb05
create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2
base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2
- platform: android
create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2
base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2
- platform: ios
create_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2
base_revision: e1fd963c6f6922bd32afde2e9698a363cd0406d2

# User provided section

Expand Down
Loading
Loading