Group reasoning picker by availability - #9819
Conversation
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
7618215 to
0d91c7d
Compare
| if (index > 0) container.addView(HorizontalDivider(context)) | ||
| section.headerRes?.let { container.addSectionHeader(context.getString(it)) } | ||
| section.rows.forEach { row -> | ||
| val item = pickerMenuItem( |
There was a problem hiding this comment.
@malmstein Following from https://github.com/duckduckgo/Android/pull/9814/changes#r4094482313, should we set opensFollowUp here? "Set [opensFollowUp] for a row that leads somewhere else" a gated reasoning routes to upsell so I assume we want this as true?
There was a problem hiding this comment.
yes, agreed, they route to the same upsell so they should read the same.
karlenDimla
left a comment
There was a problem hiding this comment.
Looks good but would like to get clarity on #9819 (comment)
0d91c7d to
6178b67
Compare
6178b67 to
6e919d6
Compare
Gives the reasoning picker the same treatment as the model picker behind updatedPickers: accessible modes first, a divider, then gated ones under a header naming what unlocks them. The header rule moves next to the upsell routing so both pickers share one definition rather than drifting apart. Gated reasoning rows do not take the follow-up ellipsis the gated model rows use, matching the design, even though tapping one also opens an upsell. State now exposes sections instead of a flat row list, with a rows accessor for the callers that only care about the modes. https://app.asana.com/1/137249556945/task/1218299204169927 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reasoning popup was 220dp against the model picker's 260dp, so Extended Reasoning wrapped onto two lines once the gated section gave it a header to sit under. Both now use nativeInputMenuWidth, the width the other native input menus already share. https://app.asana.com/1/137249556945/task/1218299204169927 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review feedback: tapping a gated reasoning mode opens an upsell exactly as a gated model row does, so the two should read the same. The design shows the reasoning rows without it, worth confirming with design, but matching behaviour wins over matching the mock here. https://app.asana.com/1/137249556945/task/1218299204169927 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
225ed4f to
97891bf
Compare
Task/Issue URL: https://app.asana.com/1/137249556945/task/1218292003956804 Tech Design URL (if applicable): API Proposals URL(s) (if applicable): None ### Description Two pixels for the updated pickers. `m_aichat_unified_input_picker_upsell_shown` fires when a picker opens with a gated section, so the funnel has a denominator for the existing `..._subscription_upsell_triggered` click: fewer gated-row taps only means something against how often the section was seen. It carries which header the user saw, since that copy is what tells them why the rows are locked. `m_aichat_model_label_unknown_daily` is the debug pixel for a model `label` this version cannot render. Models are re-fetched whenever a picker attaches, so each unrecognised value is reported once per process rather than on every refresh, and the value is uppercased and stripped to the backend id shape rather than passed through verbatim. The gated header is now a typed `GatedHeader` carrying both the string resource and the pixel value, so the copy and the telemetry cannot drift apart. Both pixels are registered in `duck_chat.json5`. Local registry validation could not be run here (`npm ci` needs the git-hosted `@duckduckgo/pixel-schema`), so CI is the first check of the JSON5. Stacked on duckduckgo#9819. ### Steps to test this PR _Feature 1_ - [x] Enable `updatedPickers`, then open the model picker on an account that can purchase a subscription, so the gated section renders - [x] Confirm `m_aichat_unified_input_picker_upsell_shown` fires once with `source=model_picker`, the `header` matching the section header on screen, your `current_tier`, and the picker's `origin` - [x] Repeat with the reasoning picker on a model that has a gated reasoning effort, and confirm `source=reasoning_picker` - [x] Open a picker with nothing gated and confirm no upsell impression fires - [x] Confirm `..._model_picker_shown` and `..._model_selected` still fire as they do on develop ### UI changes None --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Task/Issue URL: https://app.asana.com/1/137249556945/task/1218299204169927
Tech Design URL (if applicable):
API Proposals URL(s) (if applicable): None
Description
Gives the reasoning picker the same shape as the model picker behind
updatedPickers:accessible modes first, a divider, then gated modes under a header naming what unlocks them.
The header rule moves next to the upsell routing as
gatedSectionHeaderRes, so both pickersshare one definition instead of drifting.
Gated reasoning rows deliberately do not take the follow-up ellipsis that gated model rows use,
matching the design, even though tapping one also opens an upsell.
ReasoningModePickerStatenow exposes
sectionsrather than a flat row list, with arowsaccessor for callers that onlycare about the modes. With the flag off it builds a single unheaded section, which renders
exactly as today.
Stacked on #9814.
Steps to test this PR
Feature 1
updatedPickersoff, open the reasoning picker and confirm it is unchanged fromdevelop: flat list, no header, no divider
updatedPickers, force stop and relaunchdivider, then the gated mode under a header
"Try Free for 7 Days" when a trial is still available, "Subscriber Exclusive" otherwise
for Plus
UI changes