Repository navigation
Review plan: land Collect One (wip/baobab) through 60 stacked PRs #2629
Description
TL;DR: Land the Collect One rewrite on wip/baobab (frozen at b7fd5917: 902 files, ~174k lines of source, ~48k lines of tests, ~50k lines of data) through 60 path-disjoint, stacked PRs into the integration branch c1. c1 is master plus one commit that deletes every file, so it starts empty. At most 5 PRs open at once, roughly 12 waves. Reviewer: rfontanarosa on every PR; one approval (with no outstanding change requests) is enough to merge. gino-m opens the PRs and answers comments.
Why this approach
- Don't replay history.
wip/baobabgrew by iteration (29 merge commits, repeated renames and reworks), so its commits don't make reviewable units. Each PR is a slice of the final tree at a frozen commit, cut along module and package boundaries. - Path-disjoint slices. Every added or modified file belongs to exactly one slice. Slices never conflict, so rebasing a stack after a squash-merge is mechanical.
- Stacked, not serialized. Within a Gradle build (
shared/core,shared/app, …), slices stack in dependency order. Each PR targets the branch of the slice before it, so its diff shows only its own files, CI builds the whole prefix, and reviewers can work on 5 PRs in parallel. PRs merge bottom-up. - Truly independent tracks (docs, protos,
shared/map, library content) targetc1directly and can merge in any order. - Size budget: ≤ ~5k source lines per PR, usually 2–4k, with tests alongside the code they cover. Three single files exceed the budget and get their own PR:
ControlWidgets.kt(3.3k lines),FormEditorPage.kt(4.7k),UserDataBackend.kt(2.3k, inside S2). Data files (seed JSON, purpose-pack.textproto, goldens) go in separate PRs that reviewers can skim.
Dependency graph
flowchart LR
C01[C01 scaffolding] --> D[D1–D4 docs]
C01 --> P[P1–P3 protos]
K --> M1[M1 map]
C01 --> X1[X1 library content]
P --> K[K1→K7 core]
K --> U[U1→U3 ui]
U --> S[S1→S4 server]
U --> A[A1→A11 shared app]
M1 --> A
X1 --> A
A --> W[W1→W10 web console]
A --> MB[MB1→MB2 mobile]
W --> H1[H1 webApp]
MB --> H2[H2 mobileWebApp]
MB --> H3[H3 android/iOS]
S --> E[E1→E2 launchpad & e2e]
H1 --> E
H2 --> E
Decisions
- Integration branch:
c1, created frommasterwith all files deleted.masterkeeps shipping Ground 1.0. Cuttingmasterover to Collect One is a separate proposal after this issue closes. - Reviewer: rfontanarosa on every PR; one approval merges. gino-m opens the PRs and isn't a reviewer.
- Source of truth:
wip/baobabis where code that hasn't been sent for review lives (Copybara mirrors it to Git-on-Borg). The original snapshot is taggedcollect-one-review-base(b7fd5917). Accepted review fixes are carried forward ontowip/baobab, so slices that haven't opened yet already include them. New features land as follow-up PRs onc1. - Where to edit: code merged into
c1→ PR againstc1; code in an open review PR → that PR (the fix is then carried forward towip/baobabautomatically); code not yet sent for review →wip/baobab. Never force-pushwip/baobab.
Setup
- Resolve the open decisions (see Decisions)
- Create the integration branch
c1(master+ "Delete all files in c1 branch") - Tag the frozen commit
collect-one-review-base(b7fd5917) - Protect
c1: squash-only, 1 approving review,checkpassing once C01 lands. When C01 addscheck.yml, includec1in itspushbranches - Create a
collect-onelabel for all PRs - Pre-flight: scan the frozen tree for internal hostnames, bug IDs, personal handles, keys, and Firebase configs (open-source purity). Run
scripts/ktfmt.shandscripts/buf-format.shonce on the frozen tree - Dry run locally: build each cumulative stack prefix (
./gradlew checkin each touched module). Wherever a prefix fails to compile, move the forward-referenced file into the earlier slice. Freeze the final slice manifest - Optional: add
.gitattributeslinguist-generated=truefor seed JSON,shared/assets/library/*.textproto, ande2eTests/goldens/**so GitHub collapses them
Per-PR checklist (applies to every slice)
- Create branch
review/c1/<id>-<slug>from its base (the previous slice's branch, orc1). Then rungit checkout origin/wip/baobab -- <slice files>. No other edits, except CI wiring (each new module adds its own job incheck.yml) and review fixes. - Run ktfmt, buf, and
./gradlew checklocally for the module. Paste the result into the PR. - Write the description from the template below. Open the PR with the
collect-onelabel. Request the assigned reviewer. Check the box here and add the PR link. - Address comments on the PR branch. File out-of-scope suggestions as follow-up issues labeled
collect-one. Don't grow the PR. - After the parent squash-merges:
git rebase --onto c1 <old-parent-tip> <branch>, force-push,gh pr edit --base c1. - When a PR merges, open the next PR in the queue (keep ≤5 open).
PR description template
## Collect One review series: <ID> <title> (<n>/60)
Tracking issue: #<this issue> · Base: <parent PR or c1> · Next: <child PR>
### Context
Collect One is the Kotlin Multiplatform successor to Ground: one shared codebase for the
web console, the mobile app (Android, iOS, browser), and a Spanner-backed user-data
service. Forms use ProtoForms, a lossless proto3 encoding of ODK XForms. Background:
docs/product/prd.md, docs/technical/client/architecture.md (from D1/D4).
This PR is one slice of the final tree in `wip/baobab`. It only adds files under the
paths listed below. Earlier slices are already merged or are the base of this PR.
### What's in this PR
- <packages / files, one line each, with the key types>
### Where it fits
<1–3 sentences: which layer, who calls it, what it calls>
### Not in this PR
- <related code and the slice it lands in>
### Suggested reading order
1. <README / entry-point file> 2. <core type> 3. <tests that show behavior>
### Focus areas for review
- <design decisions, spec conformance (cite ODK/XForms sections), risky logic>
### Skim
- <tests that are tables of cases, data/fixture files, generated-looking code>
### Size
| source | tests | data | files |
### Testing
`cd <module> && ./gradlew check`: <result>. CI: `check` (ktfmt, buf[, server tests])PR queue
Sizes are added lines: source · tests · data. "Base" is the slice this PR stacks on. "Merged → c1" means it targets c1 once those merge.
Wave 1
- C01 Repo scaffolding — 948 src · 0 test — base: — — PR [Collect One 1/60] Repo scaffolding (C01) #2630
Root README, LICENSE, GEMINI.md, AUTHORS, .gitignore, ktfmt/buf scripts, and the CIcheckjob. Until this merges, PRs intoc1get no CI. - D1 Docs: product & UX — 2.4k src · 0 test — base: C01 — PR [Collect One 2/60] Docs: product & UX (D1) #2639
Product requirements, terminology, UX content guidelines. Read first: context for every later PR. - P1 Protos: forms — 1.4k src · 0 test — base: C01 — PR [Collect One 3/60] Protos: forms (P1) #2631
ProtoForms (groundplatform.v2.forms): XForms data model in proto3. - P2 Protos: survey, ACL, organization — 2.0k src · 0 test — base: C01 — PR [Collect One 4/60] Protos: survey, ACL, organization (P2) #2632
SurveyDef, ACLs, organizations, Survey/Organization services. - P3 Protos: data, library, userdata — 2.1k src · 0 test — base: C01 — PR [Collect One 5/60] Protos: data, library, userdata (P3) #2633
SubmissionRecord,EntityRecord,AuditRecord, impact, library,DataService, client sync state.
Wave 2
- K1 Core: scaffolding, XML/TextProto primitives, model, mutation — 2.8k src · 358 test — base: P1–P3 merged → c1
Gradle build forshared/core, proto codegen, XML & TextProto parsers/writers, form state models, record patches. - M1 Map module — 3.9k src · 771 test — base: K1
Standaloneshared/mapmodule: Mapbox renderers for web, Android, iOS. No code dependency on core, but itsgradlewdelegates toshared/core/gradlew, so it stacks on K1. - D2 Docs: forms data model — 3.3k src · 0 test — base: C01 — PR [Collect One 8/60] Docs: forms data model (D2) #2634
ProtoForms data model spec: structure, bindings, instances, repeats, entities, encryption. - D4 Docs: client/backend/sync architecture — 2.8k src · 0 test — base: C01
Client architecture, local data store, web console, sync engine, Spanner schema, impact aggregation. - X1 Library content (purpose packs) — 0 src · 0 test · 11.4k data — base: C01
Purpose-pack library content (.textprotodata, skim).
Wave 3
- K2 Core: XForms XML <-> ProtoForms serializers — 3.3k src · 1.5k test — base: K1
XForms XML ↔ ProtoForms and record-instance XML; round-trip tests. - K3 Core: ProtoJSON & TextProto serializers, library — 3.0k src · 868 test — base: K2
ProtoJSON and TextProto serializers; library TextProto. - K4 Core: XPath 1.0 evaluator — 5.0k src · 1.1k test — base: K3
XPath 1.0 lexer/parser/evaluator + ODK function library; parameterized spec tests. - D3 Docs: survey/data/library model — 2.0k src · 0 test — base: C01
Survey, entity dataset, map, ACL/quota, record and library model docs. - K5 Core: form engine & media — 4.2k src · 2.0k test — base: K4
Reactive form engine (FormEngine,FormSession,RecordMutator) and media capture model.
Wave 4
- K6 Core: geo (S2, GeoJSON/KML, geoid) — 2.8k src · 2.6k test — base: K5
S2 cells, GeoJSON/KML readers, ring validation, AgStack GeoID (golden tests are large, skim). - K7 Core: sampling & impact — 2.8k src · 3.3k test — base: K6
Sampling designs/engine and impact aggregation. - U1 UI: theme, scaffolding, media/rank/choice widgets — 3.2k src · 620 test — base: K7
GroundTheme, strings (9 locales), media/rank/choice-image widgets. - S1 Server: scaffolding & Spanner store — 2.7k src · 1.2k test — base: U1 (
gradlewdelegates toshared/ui/gradlew)server/userDataServicebuild,UserDataStorecontract, Spanner schema & store, emulator script, CI server job. - S2 Server: RPC backend & sync lists — 3.0k src · 2.0k test — base: S1
UserDataBackendRPCs, gRPC bindings, per-user sync lists, SSE broadcasts; idempotency/versioning tests.
Wave 5
- S3 Server: authn/authz & access epochs — 378 src · 800 test — base: S2
Authentication, survey-role/org authorization, access epochs. Security-sensitive. - S4 Server: debug tooling & seed data — 3.4k src · 540 test · 31.1k data — base: S3
RPC/Spanner debug pages and seed JSON (31k-line data file, skim). - U2 UI: question control widgets — 3.3k src · 198 test — base: U1
ControlWidgets.kt(3.3k-line single file: all question widgets). - U3 UI: form runners, wizard controller, example catalog — 4.9k src · 1.2k test — base: U2
Compact & mobile form runners, wizard controller, example forms catalog (fixture-like). - A1 App: scaffolding & core domain models — 2.4k src · 152 test — base: U3 (+M1 merged)
shared/appbuild, core domain models (survey, form, entity, submission, org, auth).
Wave 6
- A1b App: library, places, map, quota & impact models — 3.2k src · 550 test — base: A1
Library, places, map layers/clusters, quotas & cost, impact models. - A2 App: form editor domain models — 3.7k src · 415 test — base: A1b
Form editor domain models (save-to, validation, concept/dataset links). - A2b App: form import & survey editor domain models — 3.4k src · 842 test — base: A2
XLSForm/Collect Earth import, survey editor draft/projection/derivation models. - A3 App: repository interfaces & use cases — 4.4k src · 1.5k test — base: A2b
Repository interfaces and use cases. - A4 App: local store, device clients, repositories — 4.2k src · 1.5k test · 298 data — base: A3
Local store, device clients (auth, location, network, places), repository impls.
Wave 7
- A5 App: sync engine (outbox, rebase) — 2.9k src · 1.4k test — base: A4
Offline-first sync: outbox recorder/coalescer/drainer, rebaser, folds. Both reviewers. - A6 App: gRPC client & remote mapping — 3.2k src · 787 test — base: A5
gRPC-web transport, remote user-data services, proto ↔ domain mapping. - A7 App: PDF reports — 1.7k src · 294 test — base: A6
Client-side PDF reports. - A8 App: test fixtures & seed data — 0 src · 912 test · 6.7k data — base: A7 (+X1 merged)
app-testingfixtures and sample seed data (data, skim). - A9 App: session, DI & UI state — 3.5k src · 276 test — base: A8
AppSession, DI, UI state holders.
Wave 8
- A9b App: shared view models — 3.6k src · 2.4k test — base: A9
Shared view models (dashboard, data collection, onboarding, settings, …). - A10 App: map UI — 3.6k src · 662 test — base: A9b
Survey map UI: markers, layers control, camera, thumbnails. - A10b App: common UI components & branding — 2.9k src · 310 test — base: A10
Common components, branding, device frame, media capture UI. - A11 App: data collection UI — 4.5k src · 0 test — base: A10b
Data collection screens: form screen, entity details, bottom sheet. - W1 Web: scaffolding, domain models & PDF — 2.9k src · 496 test — base: A11
shared/webbuild, web domain models, PDF, DI.
Wave 9
- W1b Web: use cases — 2.7k src · 1.8k test — base: W1
Web use cases (org, library, sampling, lifecycle, impact). - W2 Web: app state, shell & common UI — 3.8k src · 802 test — base: W1b
WebAppState, top toolbar, language selector, shared web UI. - W3 Web: dashboard — 2.6k src · 946 test — base: W2
Web dashboard: side panel, survey list, data tables. - MB1 Mobile: app state, onboarding, settings — 3.5k src · 1.9k test — base: A11
shared/mobilebuild,MobileAppState, sign-in/ToS/download, settings. - MB2 Mobile: survey screen & navigation drawer — 3.0k src · 0 test — base: MB1
Main survey screen and navigation drawer.
Wave 10
- W4 Web: organizations — 4.1k src · 1.0k test — base: W3
Organizations pages + view model. - W4b Web: organization library & templates — 2.1k src · 0 test — base: W4
Organization library panes and template dialog. - W5 Web: create-survey wizard — 4.4k src · 942 test — base: W4b
Create-survey wizard. - W6 Web: survey editor view model — 2.2k src · 1.2k test — base: W5
Survey editor view model & UI state. - W6b Web: survey editor page, sharing & dialogs — 4.2k src · 1.5k test — base: W6
Survey editor page, sharing, add-form/table/layer dialogs.
Wave 11
- W7 Web: survey editor datasets, areas & sampling — 4.5k src · 1.3k test — base: W6b
Entity dataset editor, interactive layer map, survey area, sampling panel. - W8 Web: form editor view model — 2.2k src · 858 test — base: W7
Form editor view model & UI state. - W8b Web: form editor page — 4.8k src · 2.3k test — base: W8
FormEditorPage.kt(4.7k-line single file). - W9 Web: form editor save-to, concepts & preview — 3.4k src · 870 test — base: W8b
Form save-to editor, concept linking, web preview. - W10 Web: impact dashboard — 2.1k src · 305 test — base: W9
Impact dashboard.
Wave 12
- H1 Host: webApp (web console) — 1.4k src · 0 test · 29 data — base: W10
webApphost (thin Wasm entry point). - H2 Host: mobileWebApp & device workbench — 2.2k src · 1.2k test · 29 data — base: MB2
mobileWebApphost and device workbench. - H3 Hosts: androidApp & iosApp — 1.8k src · 0 test · 13 data — base: MB2
androidAppandiosAppthin hosts. - E1 Dev launchpad — 2.5k src · 0 test — base: H1, H2, S4 merged
Local dev launchpad (devtools/launchpad,scripts/launchpad.sh). - E2 E2E golden-screenshot tests — 612 src · 0 test · 82 data — base: E1
Playwright golden-screenshot tests.
Wrap-up
- Parity check:
git diff origin/wip/baobab origin/c1is empty (apart from per-slice CI wiring).git log collect-one-review-base..wip/baobablists the carried-forward review fixes - Triage the follow-up issues filed during review
- Retire
wip/baobab(archive tag stays) and point new Collect One work atc1 - Write the cutover proposal for
master(separate issue)
Estimated timeline
12 waves × ~2–3 business days per wave (2-day review turnaround, 1-day author response) ≈ 5–7 weeks. The critical path is core → ui → shared app → web console. Docs, protos, map, and server run alongside it.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
Remove encrypted form submissions
[Collect One 3/60] Protos: forms (P1)
[Collect One 4/60] Protos: survey, ACL, organization (P2)
[Collect One 5/60] Protos: data, library, userdata (P3)
[Collect One 8/60] Docs: forms data model (D2)
Remove encrypted form submissions (P1)
Remove encrypted form submissions (P3)
Remove encrypted form submissions (D2)
[Collect One 3/60] Protos: forms (P1)
[Collect One 4/60] Protos: survey, ACL, organization (P2)
[Collect One 5/60] Protos: data, library, userdata (P3)
[Collect One 8/60] Docs: forms data model (D2)
[Collect One 3/60] Protos: forms (P1)
[Collect One 4/60] Protos: survey, ACL, organization (P2)
[Collect One 5/60] Protos: data, library, userdata (P3)
[Collect One 8/60] Docs: forms data model (D2)
[Collect One 3/60] Protos: forms (P1)
[Collect One 4/60] Protos: survey, ACL, organization (P2)
[Collect One 5/60] Protos: data, library, userdata (P3)
[Collect One 8/60] Docs: forms data model (D2)