Skip to content

feat(mobile): persist collapsed connect CTAs on the new-session screen - #6189

Merged
iscekic merged 1 commit into
mainfrom
kwf/collapse-connect-ctas-f9ff
Sep 16, 2026
Merged

iscekic merged 1 commit into
mainfrom
kwf/collapse-connect-ctas-f9ff

Conversation

@iscekic

@iscekic iscekic commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Changelog for users

  • The "Connect GitHub", "Connect GitLab", and "Connect Bitbucket" cards on the new-session screen can now be collapsed to just their title row.
  • Tapping a title row toggles that card between collapsed and expanded.
  • The collapsed or expanded choice is stored per provider and survives an app restart, so a collapsed card stays collapsed.
  • A provider whose connection is already made still shows no connect card.
  • The chevron rotates as a card opens and closes, and the motion is skipped when the device asks for reduced motion.

Changelog for maintainers

  • CollapsibleSection gains optional controlled props expanded and onToggle; when expanded is supplied the parent owns the state and the section never flips it itself, and omitting it keeps the existing uncontrolled behavior.
  • CollapsibleSection also accepts titleClassName and contentClassName so a call site can match its surrounding typography and spacing.
  • A new persisted store keeps the collapsed provider platforms as a JSON array under the secure-store key collapsed-connect-ctas; it drops malformed, non-array, and unknown entries and de-duplicates the rest.
  • A write that races the initial disk read merges with the loaded list, so a collapse made during startup is not lost.
  • The connect card renders nothing until the persisted read settles, so it cannot paint expanded and then snap shut; the read starts at module import.
  • Sign-out clears the stored preference alongside the other session-scoped preferences.
  • Review first the controlled/uncontrolled split in CollapsibleSection, the merge-on-load behavior, and the reduced-motion branch.

E2E proof

needs:seed — fresh worktree-scoped account, then connect GitHub for it with $KILO_WORKFLOW/e2e/github-installation.sh e2e-empty-$(basename "$WT")@example.com, relaunch the app: Agents → New session… — e3.png

ux-check: Collapse all three CTAs, force-quit, relaunch, then expand only GitLab and force-quit/relaunch again: GitLab stays expanded while GitHub and Bitbucket stay collapsed. — e1.png

[e6] ux-check: Force-quit and relaunch, then open a new session: every CTA collapsed before the quit is still collapsed, with no frame that shows it expanded first. — android/emulator-5554. On the account with a live connect CTA, the GitLab card was collapsed before the quit (e6a.log 'SCENE e6a OK': 'android.widget.Button Connect GitLab tappable [76,1423][1004,1469]' immediately followed by 'android.widget.TextView Changes tappable [37,1555][1045,1601]', i.e. only the title row, no description or Open button) and is still collapsed after a real process restart (e6-pids.log 'before=31169 afterStop= afterLaunch=32108'; finalcheck3.log 'SCENE finalcheck3 OK' shows the same collapsed rows at [76,1423]/[37,1555]; e6b3.log repeats it after a third relaunch). The…

e6-reopen-collapsed.mp4

[e2] needs:seed — collapse the connect CTA(s), force-quit, relaunch, per-CTA persistence (android emulator-5554) — plan-defect: the Connect GitHub CTA the plan names never renders for this device account — its seeded platform_integrations row is re-created on the next repository fetch (e2-github-unreachable.log: 'github|active|2026-09-15 19:57:48.792799+00'), so the live digest lists only the Connect GitLab CTA; the reachable behaviour passed on android emulator-5554: collapse survived force-quit+relaunch ('SCENE persist1 OK' with only 'android.widget.Button Connect GitLab tappable' and no Open GitLab/description row in e2-restart-collapsed.log), tapping the header returned the description and Open GitLab…

e2-persistence.mp4.trim.mp4

[e9] ux-check: tapping a connect header still collapses/expands with Reduce Motion enabled — Android emulator-5554; Reduce Motion was enabled for the run and read back in e9-reduce-motion.log ('animator_duration_scale=0', 'transition_animation_scale=0', 'window_animation_scale=0' plus the app's low-power mode 'status: 3' / 'level: 15'), then reset; under it the collapse run 'SCENE e9c OK' ends (e9-collapsed.log) with only 'android.widget.Button Connect GitLab tappable [76,1423][1004,1469]' — description, 'Open GitLab' and 'Refresh repositories' hidden — and the expand run 'SCENE e9d OK' (e9-expanded.log) restores them with the header at the same y=1423, so the toggle still works…

e9-collapse-reduce-motion.mp4.trim.mp4

[e2] ux-check: cold-start app straight into a new session — connect cards do not appear late and push the 'Changes' segmented control, profile row, or Start button down (android emulator-5604) — android emulator-5604, account e2e-empty-collapse-connect-ctas-f9ff@example.com (the only signed-in state whose new-session screen renders a connect card; reached with login.sh, e2-login-empty.log, default account restored afterwards, e2-login-restore.log): after the declared force-stop+monkey trigger the app lands on Home and TAP OK 'New coding task' matches=2 (e2-tap.log) opens the new session, whose first sampled frame — e2-burst-summary.log #13 + 29.2s carrying GL=[76,1423][944,1469], Changes=[37,1818][1045,1864], Start=[442,2247][638,2274] — already contains the GitLab connect…

e2-coldstart.mp4.trim.mp4

[e8] ux-check — cold start straight into a new session: connect cards do not appear late and push the Changes segmented control / profile row / Start button down — android emulator-5606: the title-sampled mount burst shows the pre-navigation frame with no card and no rows yet ('# 2 + 2.4s GitLab=- Changes=- Start=- NewTask=y') and the very next sampled frame already fully settled ('# 3 + 4.8s GitLab=[76,1423][1004,1469] Changes=[37,1818][1045,1864]'), identical at '# 60 +142.7s GitLab=[76,1423][1004,1469] Changes=[37,1818][1045,1864]' — so no late card and no row movement was observed; the scene ended 'SCENE e8 OK' (e8-digest.log) and e8.png plus e8-coldstart.mp4 are the captures for the visual reviewer, who owns the first-2.4s appearance judgement and…

e8-coldstart.mp4.trim.mp4

[e6] ux-check: Force-quit and relaunch, then open a new session: every CTA collapsed before the quit is still collapsed, with no frame that shows it expanded first. — android/emulator-5554. On the account with a live connect CTA, the GitLab card was collapsed before the quit (e6a.log 'SCENE e6a OK': 'android.widget.Button Connect GitLab tappable [76,1423][1004,1469]' immediately followed by 'android.widget.TextView Changes tappable [37,1555][1045,1601]', i.e. only the title row, no description or Open button) and is still collapsed after a real process restart (e6-pids.log 'before=31169 afterStop= afterLaunch=32108'; finalcheck3.log 'SCENE finalcheck3 OK' shows the same collapsed rows at [76,1423]/[37,1555]; e6b3.log repeats it after a third relaunch). The…

e6-collapse-relaunch.mp4.trim.mp4

[e6] ux-check: Force-quit and relaunch, then open a new session: every CTA collapsed before the quit is still collapsed, with no frame that shows it expanded first. — android/emulator-5554. On the account with a live connect CTA, the GitLab card was collapsed before the quit (e6a.log 'SCENE e6a OK': 'android.widget.Button Connect GitLab tappable [76,1423][1004,1469]' immediately followed by 'android.widget.TextView Changes tappable [37,1555][1045,1601]', i.e. only the title row, no description or Open button) and is still collapsed after a real process restart (e6-pids.log 'before=31169 afterStop= afterLaunch=32108'; finalcheck3.log 'SCENE finalcheck3 OK' shows the same collapsed rows at [76,1423]/[37,1555]; e6b3.log repeats it after a third relaunch). The…

e6-reopen-collapsed.mp4.trim.mp4

E2E proof — log excerpts

[e1] ux-check: Collapse all three CTAs, force-quit, relaunch, then expand only G -> pass :: android emulator-5554: after collapse (e1-collapsed-scene.log) android.widget.Button Connect Bitbucket tappable [76,1612][1004,1658] then android.widget.TextView Changes tappable [37,1743][1045,1789]; after restart (e1-restart.log) force-stop rc=0 and pidof after=1613 the collapsed stack remains (e1-collapsed-restart-scene.log) android.widget.Button Connect GitHub tappable [76,1307][1004,1353] then android.widget.Button Connect GitLab tappable [76,1459][1004,1505] then android.widget.Button Connect Bitbucket tappable [76,1612][1004,1658] then android.widget.TextView Changes tappable [37,1743][1045,1789]; after expanding GitLab and a second restart (e1-expanded-restart.log) force-stop rc=0 th
/home/igor_kilocode_ai/.local/share/kwf/sections/collapse-connect-ctas-f9ff/e2e-mobile-app/e1.log
android.widget.TextView Auto Efficient tappable [347,721][546,767]
android.widget.TextView Run on tappable [37,857][1045,903]
android.widget.Button Run on: Cloud Agent tappable [37,921][909,1037]
android.widget.TextView Cloud Agent tappable [67,951][844,1007]
android.widget.Button Refresh tappable [928,921][1043,1037]
android.widget.TextView Run `kilo remote` on your computer, or `/remote` in a running CLI session, to control a local kilo process. tappable [36,1056][1044,1130]
android.widget.TextView Repository tappable [37,1176][1045,1222]
android.widget.Button Connect GitHub tappable [76,1307][1004,1353]
android.widget.TextView Connect GitHub tappable [76,1307][944,1353]
android.widget.Button Connect GitLab tappable [76,1459][1004,1505]
android.widget.TextView Connect GitLab tappable [76,1459][944,1505]
android.widget.TextView Connect GitLab in your browser, then return here to pick a repository. tappable [76,1533][1004,1625]
android.widget.Button Open GitLab tappable [76,1653][870,1768]
android.widget.TextView Open GitLab tappable [412,1687][594,1733]
android.widget.Button Refresh repositories tappable [888,1653][1004,1768]
android.widget.Button Connect Bitbucket tappable [76,1874][1004,1920]
android.widget.TextView Connect Bitbucket tappable [76,1874][944,1920]
android.widget.TextView Changes tappable [37,2006][1045,2052]
android.view.View Changes tappable [37,2070][1043,2189]
android.widget.RadioButton Leave changes tappable [46,2079][540,2180]
android.widget.TextView Leave changes tappable [182,2106][403,2152]
android.widget.RadioButton Commit and push tappable [540,2079][1034,2180]
android.widget.TextView Commit and push tappable [656,2106][917,2152]
android.widget.TextView Environment tappable [37,2236][1045,2274]
/home/igor_kilocode_ai/.local/share/kwf/sections/collapse-connect-ctas-f9ff/e2e-mobile-app/e1-collapsed-scene.log
android.widget.Button Auto Efficient tappable [319,707][623,780]
android.widget.TextView Auto Efficient tappable [347,721][546,767]
android.widget.TextView Run on tappable [37,857][1045,903]
android.widget.Button Run on: Cloud Agent tappable [37,921][909,1037]
android.widget.TextView Cloud Agent tappable [67,951][844,1007]
android.widget.Button Refresh tappable [928,921][1043,1037]
android.widget.TextView Run `kilo remote` on your computer, or `/remote` in a running CLI session, to control a local kilo process. tappable [36,1056][1044,1130]
android.widget.TextView Repository tappable [37,1176][1045,1222]
android.widget.Button Connect GitHub tappable [76,1307][1004,1353]
android.widget.TextView Connect GitHub tappable [76,1307][944,1353]
android.widget.Button Connect GitLab tappable [76,1459][1004,1505]
android.widget.TextView Connect GitLab tappable [76,1459][944,1505]
android.widget.Button Connect Bitbucket tappable [76,1612][1004,1658]
android.widget.TextView Connect Bitbucket tappable [76,1612][944,1658]
android.widget.TextView Changes tappable [37,1743][1045,1789]
android.view.View Changes tappable [37,1807][1043,1926]
android.widget.RadioButton Leave changes tappable [46,1816][540,1917]
android.widget.TextView Leave changes tappable [182,1843][403,1889]
android.widget.RadioButton Commit and push tappable [540,1816][1034,1917]
android.widget.TextView Commit and push tappable [656,1843][917,1889]
android.widget.TextView Environment tappable [37,1973][1045,2019]
android.widget.TextView Default environment tappable [37,2037][1045,2083]
android.widget.Button Start session [37,2138][1043,2254]
android.widget.TextView Start session tappable [442,2172][638,2218]
/home/igor_kilocode_ai/.local/share/kwf/sections/collapse-connect-ctas-f9ff/e2e-mobile-app/e1-restart.log
== e1 collapsed restart ==
pidof before=31251
force-stop rc=0
  bash arg: -p
  bash arg: com.kilocode.kiloapp
  bash arg: -c
  bash arg: android.intent.category.LAUNCHER
  bash arg: 1
args: [-p, com.kilocode.kiloapp, -c, android.intent.category.LAUNCHER, 1]
 arg: "-p"
 arg: "com.kilocode.kiloapp"
 arg: "-c"
 arg: "android.intent.category.LAUNCHER"
 arg: "1"
data="com.kilocode.kiloapp"
data="android.intent.category.LAUNCHER"
Events injected: 1
## Network stats: elapsed time=101ms (0ms mobile, 0ms wifi, 101ms not connected)
pidof after=1613
/home/igor_kilocode_ai/.local/share/kwf/sections/collapse-connect-ctas-f9ff/e2e-mobile-app/e1-collapsed-restart-scene.log
android.widget.Button Auto Efficient tappable [319,707][623,780]
android.widget.TextView Auto Efficient tappable [347,721][546,767]
android.widget.TextView Run on tappable [37,857][1045,903]
android.widget.Button Run on: Cloud Agent tappable [37,921][909,1037]
android.widget.TextView Cloud Agent tappable [67,951][844,1007]
android.widget.Button Refresh tappable [928,921][1043,1037]
android.widget.TextView Run `kilo remote` on your computer, or `/remote` in a running CLI session, to control a local kilo process. tappable [36,1056][1044,1130]
android.widget.TextView Repository tappable [37,1176][1045,1222]
android.widget.Button Connect GitHub tappable [76,1307][1004,1353]
android.widget.TextView Connect GitHub tappable [76,1307][944,1353]
android.widget.Button Connect GitLab tappable [76,1459][1004,1505]
android.widget.TextView Connect GitLab tappable [76,1459][944,1505]
android.widget.Button Connect Bitbucket tappable [76,1612][1004,1658]
android.widget.TextView Connect Bitbucket tappable [76,1612][944,1658]
android.widget.TextView Changes tappable [37,1743][1045,1789]
android.view.View Changes tappable [37,1807][1043,1926]
android.widget.RadioButton Leave changes tappable [46,1816][540,1917]
android.widget.TextView Leave changes tappable [182,1843][403,1889]
android.widget.RadioButton Commit and push tappable [540,1816][1034,1917]
android.widget.TextView Commit and push tappable [656,1843][917,1889]
android.widget.TextView Environment tappable [37,1973][1045,2019]
android.widget.TextView Default environment tappable [37,2037][1045,2083]
android.widget.Button Start session [37,2138][1043,2254]
android.widget.TextView Start session tappable [442,2172][638,2218]
/home/igor_kilocode_ai/.local/share/kwf/sections/collapse-connect-ctas-f9ff/e2e-mobile-app/e1-expanded-restart.log
== e1 expand-restart ==
pidof before=1613
force-stop rc=0
  bash arg: -p
  bash arg: com.kilocode.kiloapp
  bash arg: -c
  bash arg: android.intent.category.LAUNCHER
  bash arg: 1
args: [-p, com.kilocode.kiloapp, -c, android.intent.category.LAUNCHER, 1]
 arg: "-p"
 arg: "com.kilocode.kiloapp"
 arg: "-c"
 arg: "android.intent.category.LAUNCHER"
 arg: "1"
data="com.kilocode.kiloapp"
data="android.intent.category.LAUNCHER"
Events injected: 1
## Network stats: elapsed time=39ms (0ms mobile, 0ms wifi, 39ms not connected)
pidof after=
Owner request

Surface: the mobile app (apps/mobile).

The "connect x" CTAs for cloud agent sessions cannot be dismissed, so they
take space on every visit. Let the user collapse them, and keep them collapsed
across app restarts.

Collapsed means the CTA is reduced, not deleted: the user can expand it again.
Persist the state per CTA, in the app's own persistent storage. A CTA whose
connection is later made is no longer shown at all, as today.

Verify live: collapse each CTA, force-quit the app, reopen it, and show every
CTA still collapsed. Then expand one and show it stays expanded across another
restart. Capture the collapsed and expanded states.

The bar is best-in-class UX. A change that works but feels worse than
the rest of the app is not done: the states, the spacing, the motion and
the wording match what the app already does well.

[e7] ux-check — collapse CTAs, restart, expand only GitLab, restart: per-CTA persistence (android emulator-5554) — plan-defect: only one connect CTA exists in this environment, so 'GitHub and Bitbucket stay collapsed' is unobservable — the e7-restart-expanded.log digest lists only the GitLab CTA ('android.widget.Button Connect GitLab tappable'); on android emulator-5554 the reachable user outcome held: after collapsing and a force-quit+relaunch the card was still collapsed ('SCENE e7a2 OK' with no Open GitLab row in e7-restart-collapsed.log), tapping the header expanded it again ('SCENE e7b2 OK' and 'android.widget.TextView Connect GitLab in your browser, then return here to pick a repository.' in…

[e7] ux-check — collapse CTAs, restart, expand only GitLab, restart: per-CTA persistence (android emulator-5554) — e7-collapsed-gitlab-card.png

[e4] ux-check: tap a connect card's title row on the new-session screen with at least two unconnected providers — Android emulator-5554, worktree-scoped empty account with two unconnected providers (Connect GitHub + Connect GitLab); pre-state e4-before-collapse.digest holds 'Open GitHub tappable [76,1501][870,1617]' and two 'Refresh repositories' rows, and after tapping the GitHub title row e4-collapse.log holds only 'android.widget.Button Connect GitHub tappable [76,1307][1004,1353]' plus 'Connect GitLab in your browser, then return here to pick a repository.' — description, 'Open GitHub' and that card's Refresh are gone (Refresh occurrences fall 2 -> 1), the GitLab card stays expanded, the tapped…

[e4] ux-check: tap a connect card's title row on the new-session screen with at least two unconnected providers — e4.png

[e2] needs:seed — collapse the connect CTA(s), force-quit, relaunch, per-CTA persistence (android emulator-5554) — plan-defect: the Connect GitHub CTA the plan names never renders for this device account — its seeded platform_integrations row is re-created on the next repository fetch (e2-github-unreachable.log: 'github|active|2026-09-15 19:57:48.792799+00'), so the live digest lists only the Connect GitLab CTA; the reachable behaviour passed on android emulator-5554: collapse survived force-quit+relaunch ('SCENE persist1 OK' with only 'android.widget.Button Connect GitLab tappable' and no Open GitLab/description row in e2-restart-collapsed.log), tapping the header returned the description and Open GitLab…

[e2] needs:seed — collapse the connect CTA(s), force-quit, relaunch, per-CTA persistence (android emulator-5554) — e2-collapsed-gitlab-card.png

[e5] ux-check: one provider collapsed, another expanded, tap the collapsed row — android emulator-5606, default account in organization context; e5-scene.log carries 'SCENE e5 OK' with the end state GitHub expanded ('Connect GitHub in your browser, then return here to pick a repository.' plus 'Open GitHub' at line 23) and Bitbucket still only its 'Connect Bitbucket' header at line 32 with no description before 'Changes', and e5.replay.json records the ordered steps tap GitHub -> assert-absent description -> tap Bitbucket -> assert-absent -> tap GitHub -> assert description+'Open GitHub' present -> assert Bitbucket description still absent -> assert GitLab description…

[e5] ux-check: one provider collapsed, another expanded, tap the collapsed row — e5.png

[e2] needs:seed — collapse the connect CTA(s), force-quit, relaunch, per-CTA persistence (android emulator-5554) — plan-defect: the Connect GitHub CTA the plan names never renders for this device account — its seeded platform_integrations row is re-created on the next repository fetch (e2-github-unreachable.log: 'github|active|2026-09-15 19:57:48.792799+00'), so the live digest lists only the Connect GitLab CTA; the reachable behaviour passed on android emulator-5554: collapse survived force-quit+relaunch ('SCENE persist1 OK' with only 'android.widget.Button Connect GitLab tappable' and no Open GitLab/description row in e2-restart-collapsed.log), tapping the header returned the description and Open GitLab…

[e2] needs:seed — collapse the connect CTA(s), force-quit, relaunch, per-CTA persistence (android emulator-5554) — e2-expanded-gitlab-card.png

[e7] ux-check — collapse CTAs, restart, expand only GitLab, restart: per-CTA persistence (android emulator-5554) — plan-defect: only one connect CTA exists in this environment, so 'GitHub and Bitbucket stay collapsed' is unobservable — the e7-restart-expanded.log digest lists only the GitLab CTA ('android.widget.Button Connect GitLab tappable'); on android emulator-5554 the reachable user outcome held: after collapsing and a force-quit+relaunch the card was still collapsed ('SCENE e7a2 OK' with no Open GitLab row in e7-restart-collapsed.log), tapping the header expanded it again ('SCENE e7b2 OK' and 'android.widget.TextView Connect GitLab in your browser, then return here to pick a repository.' in…

[e7] ux-check — collapse CTAs, restart, expand only GitLab, restart: per-CTA persistence (android emulator-5554) — e7-expanded-gitlab-card.png

[e1] connect CTA collapse persists across restart — android emulator-5554: both connect cards painted expanded, each header tap collapsed it (titles kept, description + Open button gone, other card untouched) per e1.log, and after a real force-quit/relaunch (pid 10946/12315/15910 per e1-restart-trigger.log) both were still titles-only with no repositories listed per e1-collapsed.log; expanding persisted across another restart per e1-expanded-restart.log and e1-expanded-persist.log, with the on-device SecureStore key measured 19/10/2 plaintext bytes per e1-securestore.log; the declared no-connection start state needed one db.sh fixture row…

e1-collapse.mp4

[e4] ux-check: tap a connect card's title row on the new-session screen with at least two unconnected providers — Android emulator-5554, worktree-scoped empty account with two unconnected providers (Connect GitHub + Connect GitLab); pre-state e4-before-collapse.digest holds 'Open GitHub tappable [76,1501][870,1617]' and two 'Refresh repositories' rows, and after tapping the GitHub title row e4-collapse.log holds only 'android.widget.Button Connect GitHub tappable [76,1307][1004,1353]' plus 'Connect GitLab in your browser, then return here to pick a repository.' — description, 'Open GitHub' and that card's Refresh are gone (Refresh occurrences fall 2 -> 1), the GitLab card stays expanded, the tapped…

[e4] ux-check: tap a connect card's title row on the new-session screen with at least two unconnected providers — e4-expanded.png

[e1] connect CTA collapse persists across restart — android emulator-5554: both connect cards painted expanded, each header tap collapsed it (titles kept, description + Open button gone, other card untouched) per e1.log, and after a real force-quit/relaunch (pid 10946/12315/15910 per e1-restart-trigger.log) both were still titles-only with no repositories listed per e1-collapsed.log; expanding persisted across another restart per e1-expanded-restart.log and e1-expanded-persist.log, with the on-device SecureStore key measured 19/10/2 plaintext bytes per e1-securestore.log; the declared no-connection start state needed one db.sh fixture row…

[e1] connect CTA collapse persists across restart — e1-collapsed.png

[e2] ux-check: cold-start app straight into a new session — connect cards do not appear late and push the 'Changes' segmented control, profile row, or Start button down (android emulator-5604) — android emulator-5604, account e2e-empty-collapse-connect-ctas-f9ff@example.com (the only signed-in state whose new-session screen renders a connect card; reached with login.sh, e2-login-empty.log, default account restored afterwards, e2-login-restore.log): after the declared force-stop+monkey trigger the app lands on Home and TAP OK 'New coding task' matches=2 (e2-tap.log) opens the new session, whose first sampled frame — e2-burst-summary.log #13 + 29.2s carrying GL=[76,1423][944,1469], Changes=[37,1818][1045,1864], Start=[442,2247][638,2274] — already contains the GitLab connect…

[e2] ux-check: cold-start app straight into a new session — connect cards do not appear late and push the 'Changes' segmented control, profile row, or Start button down (android emulator-5604) — e2.png

[e1] ux-check: Collapse all three CTAs, force-quit, relaunch, then expand only GitLab and force-quit/relaunch again: GitLab stays expanded while GitHub and Bitbucket stay collapsed. — Android emulator-5554, organization context (three connect cards): the canonical replay ran as one script call and its log opens SCENE e1 OK, ending after the second force-quit/relaunch with GitLab expanded - android.widget.Button Open GitLab tappable [76,1653][870,1768] - while GitHub and Bitbucket are title-only android.widget.Button Connect GitHub tappable [76,1307][1004,1353] and android.widget.Button Connect Bitbucket tappable [76,1874][1004,1920]; all-three-collapsed-across-a-restart is shown independently by e1-collapsed-restart.log (SCENE chk2 OK, `android.widget.Button…

[e1] ux-check: Collapse all three CTAs, force-quit, relaunch, then expand only GitLab and force-quit/relaunch again: GitLab stays expanded while GitHub and Bitbucket stay collapsed. — e1-collapsed-all-three.png

Follow-ups (not changed here)

  • not proved live: needs:seed — same worktree-scoped fresh account as e1, app cold on Home: collapse both connect cards (tap each header), force-quit (adb -s <serial> shell am force-stop com.kilocode.kiloapp) and relaunch, then Agents → New session → Repository → tap 'Connect GitHub' to expand it (its description and 'Open GitHub' return) while 'Connect GitLab' stays collapsed; force-quit and relaunch once more, navigate Agents → New session → Repository: GitHub is still expanded and GitLab is still collapsed, so the state is per CTA and expanding persists. Captured (record.sh + shot): the expanded GitHub card beside the collapsed GitLab card. (no capture cited it)
  • not proved live: needs:seed — worktree-scoped fresh account with no provider connections (sign out the default account, then $KILO_WORKFLOW/e2e/login.sh <udid> e2e-empty-$(basename "$WT")@example.com), cold app on Home. Taps: Agents tab (2 of 3) → 'New session' → scroll to 'Repository' → both connect cards show expanded (titles 'Connect GitHub' and 'Connect GitLab' with their 'in your browser, then return here to pick a repository.' descriptions and their Open buttons). Tap 'Connect GitHub' → the GitHub description and 'Open GitHub' disappear, the 'Connect GitHub' title stays, the GitLab card is untouched. Tap 'Connect GitLab' → the same for GitLab. Force-quit (adb -s <serial> shell am force-stop com.kilocode.kiloapp), relaunch (adb -s <serial> shell monkey -p com.kilocode.kiloapp -c android.intent.category.LAUNCHER 1), navigate Agents → New session → Repository: both cards are still collapsed (titles only, no description, no Open button) and the section lists no repositories. Captured (record.sh + shot): the collapsed state after the restart. (no capture cited it)
  • not proved live: ux-check: After completing the connect flow for a provider, its connect card is absent from the new-session screen entirely (not present as a collapsed row). (no capture cited it)
  • not proved live: ux-check: Cold-start the app straight into a new session: the connect cards do not appear late and push the 'Changes' segmented control, profile row, or Start button down. (no capture cited it)
  • not proved live: ux-check: Enable Reduce Motion: tapping a connect header still collapses/expands, with no chevron rotation or content fade. (no capture cited it)
  • not proved live: ux-check: On the new-session screen with at least two unconnected providers, tap a connect card's title row: the card collapses to a single title row (description, provider note, 'Open X', and refresh hidden), the chevron points down, and no other control jumps. (no capture cited it)
  • not proved live: ux-check: With one provider collapsed and another expanded, tap the collapsed row: it expands (description + buttons visible, chevron points up) and the other provider keeps its prior state. (no capture cited it)
  • not proved live: ux-check: With the screen reader on, focus a connect header: it announces as a button named after the provider with expanded/collapsed state; when collapsed, the hidden description and buttons are not focusable. (no capture cited it)
  • not proved live: iOS: not run — the diff forks on no platform, so Android proves both

Open findings (not fixed here)

  • the '## E2E proof' section links a file path, which 404s on GitHub
e9-collapse-reduce-motion.mp4
e8-coldstart.mp4

e8

e2-persistence.mp4
e6-collapse-relaunch.mp4
e2-coldstart.mp4

Surface: the mobile app (apps/mobile).

The "connect x" CTAs for cloud agent sessions cannot be dismissed, so they
take space on every visit. Let the user collapse them, and keep them collapsed
across app restarts.

Collapsed means the CTA is reduced, not deleted: the user can expand it again.
Persist the state per CTA, in the app's own persistent storage. A CTA whose
connection is later made is no longer shown at all, as today.

Verify live: collapse each CTA, force-quit the app, reopen it, and show every
CTA still collapsed. Then expand one and show it stays expanded across another
restart. Capture the collapsed and expanded states.

The bar is best-in-class UX. A change that works but feels worse than
the rest of the app is not done: the states, the spacing, the motion and
the wording match what the app already does well.
@iscekic
iscekic marked this pull request as draft September 15, 2026 22:13
@kilo-code-bot

kilo-code-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the mobile collapsed-connect-CTA feature across 10 changed files (4 production, 6 test); the controlled/uncontrolled split, the persisted store, the merge-on-load behavior, the sign-out clear, and the reduced-motion path all hold up, with no correctness, security, or leak defects found in changed lines.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

Verification Notes

  • CollapsibleSection: resolvedExpanded = expanded ?? internalExpanded, the rotation effect depends on resolvedExpanded, and accessibilityState.expanded is always boolean; the four pre-existing security-agent callers pass none of the new props, so their behavior is unchanged.
  • Store: useSyncExternalStore gets stable snapshots (store.get/store.getHasLoaded), subscribe returns an unsubscribe, and the read-error path still sets hasLoaded in finally, so the connect card cannot be gated off permanently. The expand-during-load hole in the union mergeOnLoad is unreachable because the card renders null until hasLoaded, and setConnectCtaCollapsed has no other caller.
  • Sign-out: clearCollapsedConnectCtasPreference() sits in the always-running try of the sign-out teardown, and store.clear() deletes the key and wins the load race via the dirty/cleared flags. Direct account-switch behaves like every sibling preference.
  • Storage key collapsed-connect-ctas is distinct from all existing keys.
  • Class overrides go through cn() (twMerge), so titleClassName/contentClassName/className override the defaults as intended; the mounted test's expectations are asserted against its own plain-join cn mock, so they are consistent.
  • No memory leaks introduced: no listeners, timers, or refs added outside the app-lifetime store singleton.
Files Reviewed (10 files)
  • apps/mobile/src/components/agents/new-session-repository-section.tsx
  • apps/mobile/src/components/agents/new-session-repository-section.test.ts
  • apps/mobile/src/components/security-agent/collapsible-section.tsx
  • apps/mobile/src/components/security-agent/collapsible-section.mounted.test.tsx
  • apps/mobile/src/lib/hooks/use-collapsed-connect-ctas-preference.ts
  • apps/mobile/src/lib/hooks/use-collapsed-connect-ctas-preference.test.ts
  • apps/mobile/src/lib/auth/auth-context.tsx
  • apps/mobile/src/lib/auth/auth-context.test.tsx
  • apps/mobile/src/lib/auth/credentials.test.ts
  • apps/mobile/src/lib/storage-keys.ts

Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0

Review guidance: REVIEW.md from base branch main

@iscekic
iscekic marked this pull request as ready for review September 15, 2026 22:27
@iscekic iscekic added the human-ready The PR is ready for human review. label Sep 15, 2026
@iscekic iscekic self-assigned this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants