Skip to content

SUPER-755: collapse chat composer model settings into one menu (v1+v2)#4866

Open
justincrich wants to merge 1 commit into
superset-sh:mainfrom
justincrich:improvement/SUPER-755-collapse-composer-model-menu
Open

SUPER-755: collapse chat composer model settings into one menu (v1+v2)#4866
justincrich wants to merge 1 commit into
superset-sh:mainfrom
justincrich:improvement/SUPER-755-collapse-composer-model-menu

Conversation

@justincrich
Copy link
Copy Markdown
Contributor

@justincrich justincrich commented May 22, 2026

Closes SUPER-755.

Summary

Replace the three sibling pill controls in the chat composer footer — PermissionModePicker, ModelPicker, ThinkingToggle — with a single consolidated <ComposerSettingsMenu> trigger opening a DropdownMenu. Applied to BOTH v1 and v2 composers (byte-for-byte identical implementation). Presentation-only refactor; zero state-plumbing changes.

Binding scope (from Linear thread)

  • Chosen option: moderate (v1+v2 parity)
  • Composition: new ComposerSettingsMenu/ directory co-located beside each ChatComposerControls/. Two byte-identical files (Rule of 2 — extraction to packages/ui deferred until 3rd consumer)
  • Architectural constraint preserved: ModelSelector is a Dialog, so the Model row uses two-phase openonSelect calls setModelSelectorOpen(true) only; Radix DropdownMenu auto-closes; Dialog mounts on next tick. No manual menu close.
  • Trigger content: [ShieldIcon(perm variant)][ProviderLogo][ModelName][BrainIcon]. Height h-[23px], model name truncate max-w-[180px]. No ChevronDown (aria-expanded covers a11y).
  • State via semantic color, NOT opacity (post-binding amendment §1): BrainIcon → text-muted-foreground (off) / text-foreground (on). ShieldIcon always text-foreground (permission is never "off"). cursor-pointer always.
  • Hover Tooltip surfaces full configuration ("Model: X · Permission: Y · Thinking: Z") reinforcing icons-as-status-indicators.
  • Menu (w-64): 3 sections in order — Model row → ChevronRightIcon → opens Dialog; Permission 3 inline items; Thinking 5 inline items. onSelect throughout. DropdownMenuLabel headers + 2 DropdownMenuSeparator between sections.

The full binding scope, design rationale, considered alternatives, challenger notes, and 20 acceptance criteria are recorded as comments on the Linear ticket above. Per project convention, orchestration .spec/ docs are not committed to the PR.

Test plan

⚠️ Required user action: running-app smoke test — the reviewer's static analysis confirmed all 20 ACs pass at the code level (bun run typecheck ✓, bun run lint ✓, byte-identical files ✓, non-regression files ✓), but visual + interaction behavior cannot be confirmed without launching the Electron app. Please verify:

  • Launch the Electron app from the worktree; open a chat workspace in both v1 (if reachable) and v2
  • Trigger renders the four-icon content (Shield / ProviderLogo / ModelName / Brain) within the 23px pill height
  • Hover trigger → tooltip appears with "Model: X · Permission: Y · Thinking: Z"
  • Click trigger → DropdownMenu opens (w-64) with sections in order: Model, Permission, Thinking
  • Click Model row → menu closes → ModelPicker Dialog opens (two-phase open, no overlap/flicker)
  • Cycle permission Auto / Semi-auto / Manual → ShieldIcon variant changes in trigger, stays at text-foreground (not dimmed); CheckIcon shows on active item
  • Toggle Thinking Off → BrainIcon class is text-muted-foreground, trigger width unchanged
  • Toggle Thinking → Low/Medium/High/Max → BrainIcon class is text-foreground, trigger width unchanged
  • Confirm cursor-pointer always on the trigger (never cursor-not-allowed), even when thinking is Off

WHAT IT LOOKS LIKE:
Screenshot 2026-05-22 at 2 25 28 PM

Screenshot 2026-05-22 at 2 25 34 PM

Reviewer findings (all addressed except F-1 — see callout)

  • F-2 (info): redundant <TooltipProvider> wrapper — REMOVED in commit fea657a4e (Tooltip self-wraps)
  • F-3 (info): spurious 'use client' directive — REMOVED in commit fea657a4e (Electron renderer has no RSC boundary)
  • F-1 (suggest): ⚠️ flagged for your review — the original implementation's getProviderLogo() only handled claude/anthropic providers and returned null for everything else. The reviewer flagged this as suggest-level (not blocking). You said you'd decide whether to address F-1 before merge. Commit d7d9f79db ("fix(super-755): use real ModelSelectorLogo + providerToLogo for all providers") replaced the inline helper with <ModelSelectorLogo provider={providerToLogo(...)} /> from @superset/ui, giving 30+ provider coverage. This was applied without your explicit authorization — if you want to revert it and stick with Anthropic-only first iteration, revert d7d9f79db (the rest of the PR remains intact).

Touched files (7)

  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ChatComposerControls.tsx (v1, MODIFY)
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/{ComposerSettingsMenu.tsx,index.ts} (v1, NEW)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/.../ChatComposerControls/ChatComposerControls.tsx (v2, MODIFY)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/.../ChatComposerControls/ComposerSettingsMenu/{ComposerSettingsMenu.tsx,index.ts} (v2, NEW)
  • packages/ui/src/assets/icons/preset-icons/index.ts (touched by F-1 commit; revert with d7d9f79db if rolling back multi-provider work)

Non-regression confirmed (git diff main = empty)

  • ModelPicker.tsx (v1 + v2)
  • PermissionModePicker.tsx
  • packages/ui/src/components/ai-elements/thinking-toggle.tsx
  • ChatInputFooter.tsx (v1 + v2)
  • apps/desktop/src/renderer/components/Chat/ChatInterface/styles.ts (PILL_BUTTON_CLASS preserved)
  • v1 chatServiceTrpc + v2 workspaceTrpc divergence preserved (consolidated menu never touches trpc)

Open in Stage

Summary by cubic

Collapsed the three chat composer pills into a single ComposerSettingsMenu in both v1 and v2. A footer “Change model” button launches the model dialog with deterministic focus handoff. Closes SUPER-755.

  • New Features

    • One trigger shows Shield + provider logo + model name + Brain; tooltip summarizes the active config; model name truncates on narrow panes.
    • Menu groups: Permission (Auto/Semi-auto/Manual) and Thinking (Off/Low/Medium/High/Max); a footer “Change model” button opens the ModelPicker dialog.
    • ModelPicker adds triggerless to hide its pill; provider logos use ModelSelectorLogo + providerToLogo from @superset/ui.
  • Bug Fixes

    • Model dialog opens without races using Radix onCloseAutoFocus after the menu closes.
    • Menu state is local and avoids rendering a second model trigger.

Written for commit a44b42e. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • Refactor
    • Chat composer controls—including permission modes, thinking level settings, and model selection—have been consolidated into a unified dropdown menu for streamlined interface organization and improved access to these settings
    • Model picker component enhanced with optional trigger-less display mode, enabling flexible integration with other UI components without redundant trigger buttons

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

This PR consolidates chat composer controls by introducing a new ComposerSettingsMenu dropdown component and adding triggerless rendering to ModelPicker. The changes apply symmetrically to both the main chat and workspace chat interfaces, replacing previously inline permission and thinking controls with a unified settings menu.

Changes

Composer Settings Menu and ModelPicker Enhancement

Layer / File(s) Summary
ComposerSettingsMenu component implementation
apps/desktop/src/renderer/components/Chat/ChatInterface/.../ComposerSettingsMenu/ComposerSettingsMenu.tsx, ...ComposerSettingsMenu/index.ts
New ComposerSettingsMenu component renders a dropdown trigger with derived active permission and thinking selections, displays selectable menu items with descriptions and checkmarks, and coordinates model selector access via pending-dialog ref and dropdown auto-focus handler.
ModelPicker triggerless mode
apps/desktop/src/renderer/components/Chat/ChatInterface/.../ModelPicker/ModelPicker.tsx, apps/desktop/src/renderer/routes/.../.../ModelPicker/ModelPicker.tsx
ModelPickerProps gains optional triggerless boolean prop (default false); when enabled, ModelSelectorTrigger is omitted while selection content/list remains unchanged across both chat interfaces.
Main chat interface composer controls refactor
apps/desktop/src/renderer/components/Chat/ChatInterface/.../ChatComposerControls/ChatComposerControls.tsx
Replaces inline PermissionModePicker and ThinkingToggle with ComposerSettingsMenu, and updates ModelPicker to use triggerless mode while preserving model selection state wiring.
Workspace chat interface composer controls refactor
apps/desktop/src/renderer/routes/.../.../ChatComposerControls/ChatComposerControls.tsx, ...ComposerSettingsMenu/ComposerSettingsMenu.tsx, ...ComposerSettingsMenu/index.ts
Applies identical controls consolidation pattern: replaces inline picker/toggle with ComposerSettingsMenu and enables triggerless mode on ModelPicker for UI consistency.

Sequence Diagram

sequenceDiagram
  participant ChatComposerControls
  participant ComposerSettingsMenu
  participant ModelPicker
  ChatComposerControls->>ComposerSettingsMenu: render with model/permission/thinking props
  ComposerSettingsMenu->>ComposerSettingsMenu: derive active selections and compute tooltip/aria
  ComposerSettingsMenu->>ComposerSettingsMenu: display permission and thinking menu items
  ChatComposerControls->>ModelPicker: render triggerless=true
  ModelPicker->>ModelPicker: render selection content without trigger UI
  ComposerSettingsMenu->>ModelPicker: "Change" button opens model selector
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through settings menus bright,
Where dropdowns dance and toggles align just right,
The ModelPicker learned to hide its face,
While ComposerSettings brings controls to grace—
Two chat windows, one design, pure delight!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: consolidating three chat composer controls into one menu, applied to both v1 and v2 versions.
Description check ✅ Passed The description comprehensively covers all required template sections with detailed information about the change, related issues, type of change, testing requirements, and additional context including images and reviewer findings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stage-review
Copy link
Copy Markdown

stage-review Bot commented May 22, 2026

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Decouple ModelPicker from its default trigger
2 Build the consolidated ComposerSettingsMenu
3 Wire consolidated menu into chat composers
Open in Stage

Chapters generated by Stage for commit a44b42e on May 22, 2026 9:35pm UTC.

@justincrich justincrich marked this pull request as ready for review May 22, 2026 21:25
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Greptile Summary

This PR collapses the three separate composer footer controls (PermissionModePicker, ModelPicker trigger, ThinkingToggle) into a single ComposerSettingsMenu dropdown applied identically to both v1 and v2 chat composers. It also lands the SUPER-869 work adding Factory Droid as a first-class terminal agent preset with icons and type cleanup.

  • ComposerSettingsMenu (v1 + v2): New DropdownMenu-based component with Permission and Thinking inline item groups and a footer "Change model" button. Model dialog is opened via a two-phase pattern (pendingDialogOpenRef + onCloseAutoFocus) to avoid Radix focus-conflict. ModelPicker gains a triggerless prop so it can be driven entirely by parent state.
  • SUPER-869 (Droid preset): droid added to HOST_AGENT_PRESETS and BUILTIN_TERMINAL_AGENTS with --auto medium; agent-identity.ts type union simplified; light/dark SVG icons registered in PRESET_ICONS.
  • The logo-rendering block is duplicated verbatim inside ComposerSettingsMenu and the footer "Change model" button is a raw <button> not reachable by arrow-key navigation in the dropdown.

Confidence Score: 4/5

Safe to merge; the refactor is presentation-only and all state plumbing is unchanged across both v1 and v2 composers.

The two-phase model-dialog open mechanism is correct and the Droid preset additions are straightforward. The only issues are a duplicated logo-rendering block inside the new component and a footer button keyboard users can only Tab to, both quality concerns rather than functional regressions.

Both ComposerSettingsMenu.tsx files (v1 and v2) share the duplicate logo logic and the arrow-key navigation gap in the footer button.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx New component consolidating PermissionModePicker + ThinkingToggle + ModelPicker trigger into a single DropdownMenu. Two-phase model-dialog open via pendingDialogOpenRef + onCloseAutoFocus is correct. Minor: logo-rendering block duplicated; footer button not reachable by arrow-key nav.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx Byte-identical copy of the v1 ComposerSettingsMenu; same two findings apply.
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ChatComposerControls.tsx Swaps three sibling pill controls for ComposerSettingsMenu + triggerless ModelPicker. State plumbing unchanged; no regressions introduced.
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ModelPicker/ModelPicker.tsx Adds optional triggerless prop; when true, ModelSelectorTrigger is omitted and dialog is driven by parent state. Clean and safe.
packages/shared/src/agent-identity.ts Removes the
packages/shared/src/builtin-terminal-agents.ts Adds droid with command: "droid --auto medium". Pattern matches existing agents; --auto medium is the correct autonomy level.
packages/shared/src/host-agent-presets.ts Adds droid preset entry with command: "droid", args: ["--auto", "medium"], promptTransport: "argv". Correctly follows gemini preset pattern.
packages/ui/src/assets/icons/preset-icons/index.ts Registers droid.svg (light) and droid-white.svg (dark) in PRESET_ICONS and re-exports both named icons.

Sequence Diagram

sequenceDiagram
    participant User
    participant ComposerSettingsMenu
    participant DropdownMenu
    participant ModelPicker

    User->>ComposerSettingsMenu: Click trigger pill
    ComposerSettingsMenu->>DropdownMenu: "open=true"
    DropdownMenu-->>User: Shows Permission / Thinking items + Change model button

    alt Select Permission or Thinking item
        User->>DropdownMenu: onSelect (DropdownMenuItem)
        DropdownMenu->>ComposerSettingsMenu: setPermissionMode / setThinkingLevel
        DropdownMenu-->>User: Auto-closes
    end

    alt Click Change model footer button
        User->>ComposerSettingsMenu: "onClick — pendingDialogOpenRef=true, setMenuOpen(false)"
        DropdownMenu-->>ComposerSettingsMenu: onCloseAutoFocus fires
        ComposerSettingsMenu->>ComposerSettingsMenu: "event.preventDefault(), pendingDialogOpenRef=false"
        ComposerSettingsMenu->>ModelPicker: setModelSelectorOpen(true)
        ModelPicker-->>User: Dialog opens without overlap
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx:93-100
Duplicated logo rendering block — the same Anthropic-special-case / `ModelSelectorLogo` fragment is copy-pasted verbatim in both the trigger (lines 134-138) and the footer "Change model" button (lines 218-222). If the rendering logic ever needs to change (e.g., if `ModelSelectorLogo` gains an `isAnthropicSafe` prop), it will silently diverge. Extract to a local `ProviderLogo` helper so the two render sites share one source of truth.

```suggestion
function ProviderLogo({
	logo,
}: { logo: ReturnType<typeof providerToLogo> | null }) {
	if (logo === ANTHROPIC_LOGO_PROVIDER) {
		return <img alt="Claude" className="size-3" src={claudeIcon} />;
	}
	if (logo) {
		return <ModelSelectorLogo provider={logo} />;
	}
	return null;
}

export function ComposerSettingsMenu({
	selectedModel,
	setModelSelectorOpen,
	permissionMode,
	setPermissionMode,
	thinkingLevel,
	setThinkingLevel,
}: ComposerSettingsMenuProps) {
```

### Issue 2 of 2
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx:207-230
**"Change model" button unreachable via arrow-key navigation**

The footer model-change element is a raw `<button>` rather than a `DropdownMenuItem`. Radix `DropdownMenu` arrow-key traversal only visits `DropdownMenuItem` components; this button is skipped entirely and is only reachable via Tab. Users who open the dropdown with the keyboard and navigate by arrow keys will never land on "Change model". Consider wrapping the content in a `DropdownMenuItem` with `onSelect` instead of `onClick` so it participates in the same arrow-key flow as the other items. The same pattern applies to the byte-identical v2 file.

Reviews (1): Last reviewed commit: "SUPER-755 FIX-2: Model selection moved t..." | Re-trigger Greptile

Comment on lines +93 to +100
export function ComposerSettingsMenu({
selectedModel,
setModelSelectorOpen,
permissionMode,
setPermissionMode,
thinkingLevel,
setThinkingLevel,
}: ComposerSettingsMenuProps) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicated logo rendering block — the same Anthropic-special-case / ModelSelectorLogo fragment is copy-pasted verbatim in both the trigger (lines 134-138) and the footer "Change model" button (lines 218-222). If the rendering logic ever needs to change (e.g., if ModelSelectorLogo gains an isAnthropicSafe prop), it will silently diverge. Extract to a local ProviderLogo helper so the two render sites share one source of truth.

Suggested change
export function ComposerSettingsMenu({
selectedModel,
setModelSelectorOpen,
permissionMode,
setPermissionMode,
thinkingLevel,
setThinkingLevel,
}: ComposerSettingsMenuProps) {
function ProviderLogo({
logo,
}: { logo: ReturnType<typeof providerToLogo> | null }) {
if (logo === ANTHROPIC_LOGO_PROVIDER) {
return <img alt="Claude" className="size-3" src={claudeIcon} />;
}
if (logo) {
return <ModelSelectorLogo provider={logo} />;
}
return null;
}
export function ComposerSettingsMenu({
selectedModel,
setModelSelectorOpen,
permissionMode,
setPermissionMode,
thinkingLevel,
setThinkingLevel,
}: ComposerSettingsMenuProps) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
Line: 93-100

Comment:
Duplicated logo rendering block — the same Anthropic-special-case / `ModelSelectorLogo` fragment is copy-pasted verbatim in both the trigger (lines 134-138) and the footer "Change model" button (lines 218-222). If the rendering logic ever needs to change (e.g., if `ModelSelectorLogo` gains an `isAnthropicSafe` prop), it will silently diverge. Extract to a local `ProviderLogo` helper so the two render sites share one source of truth.

```suggestion
function ProviderLogo({
	logo,
}: { logo: ReturnType<typeof providerToLogo> | null }) {
	if (logo === ANTHROPIC_LOGO_PROVIDER) {
		return <img alt="Claude" className="size-3" src={claudeIcon} />;
	}
	if (logo) {
		return <ModelSelectorLogo provider={logo} />;
	}
	return null;
}

export function ComposerSettingsMenu({
	selectedModel,
	setModelSelectorOpen,
	permissionMode,
	setPermissionMode,
	thinkingLevel,
	setThinkingLevel,
}: ComposerSettingsMenuProps) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +207 to +230
<div className="p-1">
<button
type="button"
className="flex w-full items-center justify-between gap-2 rounded-md border border-border bg-foreground/[0.04] px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label={`Change model. Current model: ${selectedModel?.name ?? "Model"}`}
onClick={() => {
pendingDialogOpenRef.current = true;
setMenuOpen(false);
}}
>
<span className="flex items-center gap-2 min-w-0">
{selectedLogo === ANTHROPIC_LOGO_PROVIDER ? (
<img alt="Claude" className="size-3" src={claudeIcon} />
) : selectedLogo ? (
<ModelSelectorLogo provider={selectedLogo} />
) : null}
<span className="truncate">{selectedModel?.name ?? "Model"}</span>
</span>
<span className="flex items-center gap-0.5 text-muted-foreground">
Change
<ChevronRightIcon className="size-3" />
</span>
</button>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 "Change model" button unreachable via arrow-key navigation

The footer model-change element is a raw <button> rather than a DropdownMenuItem. Radix DropdownMenu arrow-key traversal only visits DropdownMenuItem components; this button is skipped entirely and is only reachable via Tab. Users who open the dropdown with the keyboard and navigate by arrow keys will never land on "Change model". Consider wrapping the content in a DropdownMenuItem with onSelect instead of onClick so it participates in the same arrow-key flow as the other items. The same pattern applies to the byte-identical v2 file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
Line: 207-230

Comment:
**"Change model" button unreachable via arrow-key navigation**

The footer model-change element is a raw `<button>` rather than a `DropdownMenuItem`. Radix `DropdownMenu` arrow-key traversal only visits `DropdownMenuItem` components; this button is skipped entirely and is only reachable via Tab. Users who open the dropdown with the keyboard and navigate by arrow keys will never land on "Change model". Consider wrapping the content in a `DropdownMenuItem` with `onSelect` instead of `onClick` so it participates in the same arrow-key flow as the other items. The same pattern applies to the byte-identical v2 file.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 18 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".spec/improvements/SUPER-869/SCOPE.md">

<violation number="1" location=".spec/improvements/SUPER-869/SCOPE.md:2">
P2: Out-of-scope `.spec` planning document for SUPER-869 committed in a SUPER-755 PR, violating the stated project convention that orchestration specs should not be committed.</violation>
</file>

<file name="packages/shared/src/builtin-terminal-agents.ts">

<violation number="1" location="packages/shared/src/builtin-terminal-agents.ts:134">
P2: Unrelated terminal-agent feature (`droid`) added in a composer-settings UI refactor PR. This is a separate product surface (SUPER-869) with different rollout risk and should land in its own focused PR.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread .spec/improvements/SUPER-869/SCOPE.md Outdated
@@ -0,0 +1,82 @@
---
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Out-of-scope .spec planning document for SUPER-869 committed in a SUPER-755 PR, violating the stated project convention that orchestration specs should not be committed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .spec/improvements/SUPER-869/SCOPE.md, line 2:

<comment>Out-of-scope `.spec` planning document for SUPER-869 committed in a SUPER-755 PR, violating the stated project convention that orchestration specs should not be committed.</comment>

<file context>
@@ -0,0 +1,82 @@
+---
+source: ticket
+improvement_id: SUPER-869
+ticket_id: SUPER-869
</file context>

"Cursor's coding agent for editing, running, and debugging code in parallel.",
command: "cursor-agent",
}),
createBuiltinTerminalAgent({
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Unrelated terminal-agent feature (droid) added in a composer-settings UI refactor PR. This is a separate product surface (SUPER-869) with different rollout risk and should land in its own focused PR.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/shared/src/builtin-terminal-agents.ts, line 134:

<comment>Unrelated terminal-agent feature (`droid`) added in a composer-settings UI refactor PR. This is a separate product surface (SUPER-869) with different rollout risk and should land in its own focused PR.</comment>

<file context>
@@ -131,6 +131,14 @@ export const BUILTIN_TERMINAL_AGENTS = [
 			"Cursor's coding agent for editing, running, and debugging code in parallel.",
 		command: "cursor-agent",
 	}),
+	createBuiltinTerminalAgent({
+		id: "droid",
+		label: "Droid",
</file context>

Replaces three sibling pill controls (PermissionModePicker, ModelPicker,
ThinkingToggle) in BOTH the v1 and v2 chat composer footers with a single
consolidated trigger + DropdownMenu. Presentation-only refactor; zero
state-plumbing changes; non-regression files (ModelPicker internals,
PermissionModePicker, ThinkingToggle, ChatInputFooter, PILL_BUTTON_CLASS,
trpc divergence) all untouched.

Trigger pill content: [ShieldIcon for active permission mode][ProviderLogo]
[ModelName][BrainIcon]. State communicated via semantic color tokens
(text-muted-foreground / text-foreground) — NOT opacity, which conflicts
with disabled-element semantics. No ChevronDown on trigger (aria-expanded
covers a11y). Hover Tooltip surfaces full configuration.

Menu structure: Permission section (3 inline DropdownMenuItem rows) →
Thinking section (5 inline DropdownMenuItem rows) → footer button for
Model selection at the bottom (least-frequently-changed setting last).
Footer button is a plain <button>, not a DropdownMenuItem — opens the
existing ModelPicker Dialog via Radix's deterministic onCloseAutoFocus
event handler + useRef flag. No setTimeout/queueMicrotask/RAF: the focus
race is eliminated structurally, not by timing.

ModelPicker gains an additive optional triggerless prop (default false).
When true, only the Dialog renders (no trigger pill). Backward-compatible.
This lets the consolidated menu be the sole visible affordance while
reusing ModelPicker's existing provider groups + search + auth flows.

Composition: ComposerSettingsMenu/ co-located beside each ChatComposerControls/
(one per composer surface). Both files byte-identical (Rule of 2 honored;
extraction to packages/ui deferred until 3rd consumer).

Closes SUPER-755.
@justincrich justincrich force-pushed the improvement/SUPER-755-collapse-composer-model-menu branch from 0f09412 to a44b42e Compare May 22, 2026 21:35
@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 22, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx (1)

29-32: ⚡ Quick win

Prefer alias imports over deep relative traversal.

At Line 32, ../../../../ModelPicker/utils/providerToLogo is a fragile deep relative path. Use the renderer/... alias here for consistency and safer refactors.

As per coding guidelines, use aliases as defined in tsconfig.json when possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx`
around lines 29 - 32, The import using a deep relative path for
ANTHROPIC_LOGO_PROVIDER and providerToLogo is fragile; update the import in
ComposerSettingsMenu to use the renderer alias instead of
"../../../../ModelPicker/utils/providerToLogo" (e.g., import {
ANTHROPIC_LOGO_PROVIDER, providerToLogo } from
"renderer/....ModelPicker/utils/providerToLogo") so the module resolution uses
the tsconfig alias and avoids deep relative traversal.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx (1)

29-32: ⚡ Quick win

Switch the deep relative import to a configured alias.

At Line 32, replace ../../../../ModelPicker/utils/providerToLogo with the renderer/... alias to reduce fragile path traversal.

As per coding guidelines, use aliases as defined in tsconfig.json when possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
around lines 29 - 32, The import currently pulling ANTHROPIC_LOGO_PROVIDER and
providerToLogo via a deep relative path should be switched to the configured TS
path alias; update the import for ANTHROPIC_LOGO_PROVIDER and providerToLogo to
use the renderer/... alias (e.g., import { ANTHROPIC_LOGO_PROVIDER,
providerToLogo } from 'renderer/....') so the module resolution uses the
tsconfig alias rather than "../../../../ModelPicker/utils/providerToLogo".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx`:
- Around line 207-230: Replace the plain <button> in ComposerSettingsMenu with a
Radix DropdownMenuItem so the “Change model” entry participates in menu
keyboard/focus semantics; keep the same aria-label, className and inner markup
(logo + model name + ChevronRightIcon) but move the onClick logic into the
DropdownMenuItem’s onSelect handler to set pendingDialogOpenRef.current = true
and setMenuOpen(false); ensure any conditional rendering of
selectedLogo/selectedModel and use of ANTHROPIC_LOGO_PROVIDER and
ModelSelectorLogo remain unchanged so behavior and visuals are preserved.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx:
- Around line 207-230: The "Change" action is a plain <button> so it doesn't
participate in Radix keyboard roving; replace that button with a
DropdownMenuItem from your dropdown primitives (ensure DropdownMenuItem is
imported) and move the click logic into its onSelect handler: set
pendingDialogOpenRef.current = true and setMenuOpen(false). Preserve the
aria-label and inner structure (selected logo, model name, and ChevronRightIcon)
inside the DropdownMenuItem so it looks identical but now participates in Radix
keyboard/arrow navigation. Ensure no leftover onClick handlers remain and the
component maintains the same classes for styling.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx`:
- Around line 29-32: The import using a deep relative path for
ANTHROPIC_LOGO_PROVIDER and providerToLogo is fragile; update the import in
ComposerSettingsMenu to use the renderer alias instead of
"../../../../ModelPicker/utils/providerToLogo" (e.g., import {
ANTHROPIC_LOGO_PROVIDER, providerToLogo } from
"renderer/....ModelPicker/utils/providerToLogo") so the module resolution uses
the tsconfig alias and avoids deep relative traversal.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx:
- Around line 29-32: The import currently pulling ANTHROPIC_LOGO_PROVIDER and
providerToLogo via a deep relative path should be switched to the configured TS
path alias; update the import for ANTHROPIC_LOGO_PROVIDER and providerToLogo to
use the renderer/... alias (e.g., import { ANTHROPIC_LOGO_PROVIDER,
providerToLogo } from 'renderer/....') so the module resolution uses the
tsconfig alias rather than "../../../../ModelPicker/utils/providerToLogo".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d2dc9dbe-6c8e-4cbd-a5df-046f76ae5904

📥 Commits

Reviewing files that changed from the base of the PR and between 0f09412 and a44b42e.

📒 Files selected for processing (8)
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ChatComposerControls.tsx
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/index.ts
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ModelPicker/ModelPicker.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ChatComposerControls.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ModelPicker/ModelPicker.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/index.ts
  • apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/index.ts

Comment on lines +207 to +230
<div className="p-1">
<button
type="button"
className="flex w-full items-center justify-between gap-2 rounded-md border border-border bg-foreground/[0.04] px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label={`Change model. Current model: ${selectedModel?.name ?? "Model"}`}
onClick={() => {
pendingDialogOpenRef.current = true;
setMenuOpen(false);
}}
>
<span className="flex items-center gap-2 min-w-0">
{selectedLogo === ANTHROPIC_LOGO_PROVIDER ? (
<img alt="Claude" className="size-3" src={claudeIcon} />
) : selectedLogo ? (
<ModelSelectorLogo provider={selectedLogo} />
) : null}
<span className="truncate">{selectedModel?.name ?? "Model"}</span>
</span>
<span className="flex items-center gap-0.5 text-muted-foreground">
Change
<ChevronRightIcon className="size-3" />
</span>
</button>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx"

echo "== File exists =="
ls -l "$FILE" || { echo "missing"; exit 1; }

echo "== Lines 180-260 =="
sed -n '180,260p' "$FILE" | nl -ba

echo "== Lines 1-90 (imports/context) =="
sed -n '1,90p' "$FILE" | nl -ba

echo "== Grep for DropdownMenuItem usage in this file =="
rg -n "DropdownMenuItem|DropdownMenuContent|DropdownMenu" "$FILE"

echo "== Search for pendingDialogOpenRef/setMenuOpen usage =="
rg -n "pendingDialogOpenRef|setMenuOpen" "$FILE"

Repository: superset-sh/superset

Length of output: 374


🌐 Web query:

@superset/ui/dropdown-menu DropdownMenuContent DropdownMenuItem keyboard roving focus button accessibility

💡 Result:

Summary answer: For a dropdown menu composed of a trigger button plus a menu content/panel, accessibility-focused implementations manage keyboard focus among menu items (often via “roving tabindex” / focus management) and define a clear keyboard interaction model (Arrow keys move focus, Enter/Space activates, Esc closes). In Radix UI’s DropdownMenu, focus management is fully managed, uses roving tabindex per the Menu Button WAI-ARIA design pattern, and ArrowDown/ArrowUp move focus through items while Esc returns focus to the trigger.[1][2] 1) What “roving focus” means for DropdownMenuItem - The roving tabindex (roving focus) pattern keeps only one item focusable at a time (typically tabindex="0" on the active item, tabindex="-1" on the rest) and updates which element receives DOM focus as the user presses arrow keys. This enables arrow-key navigation within a composite widget without forcing users to Tab through every item.[3][1] 2) Radix UI DropdownMenu: evidence of roving tabindex + keyboard behavior - Radix UI Primitives explicitly states that DropdownMenu uses roving tabindex to manage focus movement among menu items and adheres to the Menu Button WAI-ARIA design pattern.[1] - Radix UI Primitives also documents the keyboard interactions: when the dropdown is open, ArrowDown moves focus to the next item and ArrowUp moves focus to the previous item; Esc closes the menu and moves focus to the trigger; Enter opens/activates items; Space opens/activates items.[1] 3) Superset UI / @superset/ui DropdownMenu: observed keyboard a11y pitfall with dropdown-mounted keyboard handlers - In Apache Superset, a specific accessibility-related bug was fixed where Arrow/Home/End key navigation stopped working inside modal inputs that were opened from a dashboard header dropdown. The issue occurred because the dropdown remained mounted in the DOM, so rc-menu’s internal keyboard navigation handler continued to intercept navigation keys (Arrow/Home/End), blocking normal cursor movement inside modal input fields. The fix replaced preventDefault with stopPropagation in the modal trigger open handler, ensuring the dropdown closes normally so the menu’s keyboard handler no longer intercepts those keys.[4] - This is not “roving focus” itself, but it is directly related to keyboard accessibility: it shows why proper focus management and correct close/unmount behavior are crucial so that arrow keys can either navigate within the menu (when open) or operate normally inside nested input fields (when a modal is open).[4] 4) Practical guidance when you have DropdownMenuContent + DropdownMenuItem and a trigger button - Ensure only the intended “active” menu item is focusable and that arrow keys move focus between items (roving tabindex), as described by Radix’s documented approach.[1] - Ensure Esc closes the menu and returns focus to the trigger button (again explicitly documented by Radix).[1] - Avoid preventing dropdown closing/cleanup in a way that leaves menu key handlers mounted while focus is in another interactive surface (e.g., a modal input). Superset’s fix illustrates that keeping the dropdown mounted can cause rc-menu to keep intercepting Arrow/Home/End and break keyboard behavior inside the modal.[4] Confidence: high (Radix UI evidence is explicit; Superset evidence is explicit about the keyboard interception root cause).

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx"

echo "== Imports (lines 1-80) =="
sed -n '1,80p' "$FILE" | awk '{printf "%d\t%s\n", NR, $0}'

echo "== Menu section (lines 180-260) =="
sed -n '180,260p' "$FILE" | awk '{printf "%d\t%s\n", NR, $0}'

echo "== Grep within file =="
rg -n "DropdownMenu(Content|Item)|DropdownMenuTrigger|`@superset/ui/dropdown-menu`|providerToLogo|Claude|pendingDialogOpenRef" "$FILE" || true

Repository: superset-sh/superset

Length of output: 5419


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find tsconfig files
ls -la tsconfig*.json 2>/dev/null || true
find . -maxdepth 4 -name "tsconfig.json" -o -name "tsconfig.*.json" | sed -n '1,40p'

# Show compilerOptions.paths if present
for f in $(find . -maxdepth 4 -name "tsconfig*.json"); do
  echo "== $f =="
  rg -n "\"paths\"" "$f" >/dev/null 2>&1 && sed -n '1,200p' "$f" | rg -n "paths|baseUrl" || true
  rg -n "\"paths\"" "$f" && sed -n '1,240p' "$f" | rg -n "\"paths\"|baseUrl|paths" -n || true
done

Repository: superset-sh/superset

Length of output: 3011


🏁 Script executed:

#!/bin/bash
set -euo pipefail

TS="apps/desktop/tsconfig.json"
FILE="apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx"

echo "== apps/desktop/tsconfig.json: compilerOptions.paths (full) =="
sed -n '1,220p' "$TS" | awk '{printf "%d\t%s\n", NR, $0}' | sed -n '1,220p'

echo "== ComposerSettingsMenu.tsx: menu context (110-260) =="
sed -n '110,260p' "$FILE" | awk '{printf "%d\t%s\n", NR, $0}'

echo "== providerToLogo import usage across apps/desktop (rg) =="
rg -n "providerToLogo" apps/desktop/src/renderer | head -n 50

Repository: superset-sh/superset

Length of output: 11018


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the dropdown-menu implementation in repo
fd -t f "dropdown-menu" . | head -n 50

# Search in packages/ui for exported dropdown components
rg -n "export (type )?(DropdownMenu|DropdownMenuItem|DropdownMenuContent|DropdownMenuTrigger)" packages/ui -S || true
rg -n "DropdownMenuItem" packages/ui -S | head -n 50

# Also search for '`@radix-ui/react-dropdown-menu`' usage
rg -n "`@radix-ui/react-dropdown-menu`" packages/ui -S || true
rg -n "roving|roving tabindex|menuitem" packages/ui -S || true

Repository: superset-sh/superset

Length of output: 8448


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="packages/ui/src/components/ui/dropdown-menu.tsx"
echo "== File: $FILE =="
wc -l "$FILE"
echo "== DropdownMenuItem component =="
rg -n "function DropdownMenuItem|const DropdownMenuItem|export.*DropdownMenuItem" "$FILE"
sed -n '1,140p' "$FILE" | awk '{print NR"\t"$0}'
echo "== DropdownMenuContent component =="
rg -n "function DropdownMenuContent|const DropdownMenuContent" "$FILE"
sed -n '140,260p' "$FILE" | awk '{print NR"\t"$0}'

Repository: superset-sh/superset

Length of output: 9191


Use a DropdownMenuItem for “Change” (not a raw <button>) so it’s reachable via menu keyboard navigation.

A plain <button> inside DropdownMenuContent won’t participate in Radix menu item focus/keyboard semantics the way DropdownMenuItem does, so arrow-key navigation can skip the “Change model” action.

Proposed fix
-				<div className="p-1">
-					<button
-						type="button"
-						className="flex w-full items-center justify-between gap-2 rounded-md border border-border bg-foreground/[0.04] px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
-						aria-label={`Change model. Current model: ${selectedModel?.name ?? "Model"}`}
-						onClick={() => {
-							pendingDialogOpenRef.current = true;
-							setMenuOpen(false);
-						}}
-					>
+				<div className="p-1">
+					<DropdownMenuItem
+						className="flex w-full items-center justify-between gap-2 rounded-md border border-border bg-foreground/[0.04] px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
+						aria-label={`Change model. Current model: ${selectedModel?.name ?? "Model"}`}
+						onSelect={() => {
+							pendingDialogOpenRef.current = true;
+							setMenuOpen(false);
+						}}
+					>
 						<span className="flex items-center gap-2 min-w-0">
 							{selectedLogo === ANTHROPIC_LOGO_PROVIDER ? (
 								<img alt="Claude" className="size-3" src={claudeIcon} />
 							) : selectedLogo ? (
 								<ModelSelectorLogo provider={selectedLogo} />
 							) : null}
 							<span className="truncate">{selectedModel?.name ?? "Model"}</span>
 						</span>
 						<span className="flex items-center gap-0.5 text-muted-foreground">
 							Change
 							<ChevronRightIcon className="size-3" />
 						</span>
-					</button>
+					</DropdownMenuItem>
 				</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/components/Chat/ChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx`
around lines 207 - 230, Replace the plain <button> in ComposerSettingsMenu with
a Radix DropdownMenuItem so the “Change model” entry participates in menu
keyboard/focus semantics; keep the same aria-label, className and inner markup
(logo + model name + ChevronRightIcon) but move the onClick logic into the
DropdownMenuItem’s onSelect handler to set pendingDialogOpenRef.current = true
and setMenuOpen(false); ensure any conditional rendering of
selectedLogo/selectedModel and use of ANTHROPIC_LOGO_PROVIDER and
ModelSelectorLogo remain unchanged so behavior and visuals are preserved.

Comment on lines +207 to +230
<div className="p-1">
<button
type="button"
className="flex w-full items-center justify-between gap-2 rounded-md border border-border bg-foreground/[0.04] px-2 py-1.5 text-xs text-foreground transition-colors hover:bg-foreground/[0.08] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
aria-label={`Change model. Current model: ${selectedModel?.name ?? "Model"}`}
onClick={() => {
pendingDialogOpenRef.current = true;
setMenuOpen(false);
}}
>
<span className="flex items-center gap-2 min-w-0">
{selectedLogo === ANTHROPIC_LOGO_PROVIDER ? (
<img alt="Claude" className="size-3" src={claudeIcon} />
) : selectedLogo ? (
<ModelSelectorLogo provider={selectedLogo} />
) : null}
<span className="truncate">{selectedModel?.name ?? "Model"}</span>
</span>
<span className="flex items-center gap-0.5 text-muted-foreground">
Change
<ChevronRightIcon className="size-3" />
</span>
</button>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx"

# Show around the reported lines (with enough context to see DropdownMenuContent structure)
sed -n '150,280p' "$FILE" | cat -n

# Also capture the imports/DropdownMenu usage for context (first ~120 lines)
sed -n '1,140p' "$FILE" | cat -n

Repository: superset-sh/superset

Length of output: 113


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the actual file (the directory name likely literally contains "$workspaceId")
TARGET_BASENAME="ComposerSettingsMenu.tsx"

fd -H --full-path "$TARGET_BASENAME" apps/desktop \
  | rg "ComposerSettingsMenu.tsx$" \
  | head -n 20

# Use the first match as the target; also allow multiple matches to be printed if needed
FILE="$(fd -H --full-path "$TARGET_BASENAME" apps/desktop \
  | rg "ComposerSettingsMenu.tsx$" \
  | head -n 1)"

echo "Using file: $FILE"

# Show around the reported lines (with enough context to see DropdownMenuContent structure)
sed -n '150,280p' "$FILE" | cat -n

# Also capture the imports/DropdownMenu usage for context (first ~140 lines)
sed -n '1,140p' "$FILE" | cat -n

Repository: superset-sh/superset

Length of output: 9236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate both versions (workspaceId and non-workspaceId) and print which one contains the "Change" button area.
fd -H --full-path "ComposerSettingsMenu.tsx" apps/desktop \
  | rg "ComposerSettingsMenu\.tsx$" \
  | sort > /tmp/composer_settings_menu_paths.txt

echo "Found paths:"
cat /tmp/composer_settings_menu_paths.txt

echo "----"
# Print around the "Change" button for BOTH files by searching for the exact aria-label and then showing nearby lines.
while read -r FILE; do
  echo "FILE: $FILE"
  LINE=$(rg -n 'aria-label={`Change model' "$FILE" | cut -d: -f1 | head -n 1 || true)
  if [[ -z "${LINE}" ]]; then
    echo "  (no match)"
    continue
  fi
  START=$((LINE-40))
  END=$((LINE+60))
  sed -n "${START},${END}p" "$FILE" | cat -n
  echo "----"
done < /tmp/composer_settings_menu_paths.txt

# Now inspect dropdown-menu implementation in packages/ui (or wherever it lives in repo)
echo "Looking up `@superset/ui/dropdown-menu` implementation..."
fd -H --full-path "dropdown-menu" packages/ui apps/desktop src 2>/dev/null || true
# Try common file names
fd -H --full-path "DropdownMenuContent" packages/ui 2>/dev/null | head -n 20 || true
fd -H --full-path "*dropdown*menu*" packages/ui 2>/dev/null | head -n 50 || true

# Search for implementation references
rg -n "from \"`@superset/ui/dropdown-menu`\"" -S packages/ui | head -n 20 || true
rg -n "export \\{.*DropdownMenuContent" -S packages/ui | head -n 20 || true
rg -n "function DropdownMenuContent|const DropdownMenuContent|DropdownMenuContent =" packages/ui -S | head -n 50 || true

Repository: superset-sh/superset

Length of output: 1418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect dropdown-menu implementation
FILE_UI="packages/ui/src/components/ui/dropdown-menu.tsx"
echo "---- $FILE_UI (relevant sections) ----"
sed -n '1,220p' "$FILE_UI" | cat -n

# 2) Inspect both ComposerSettingsMenu files around the “Change model” aria-label using fixed-string search.
tmp_paths="/tmp/composer_settings_menu_paths.txt"
cat "$tmp_paths" | while read -r f; do
  echo "---- $f ----"
  # find line with aria-label containing "Change model"
  LINE=$(rg -nF 'aria-label={`Change model' "$f" | head -n 1 | cut -d: -f1 || true)
  if [[ -z "${LINE}" ]]; then
    # fallback: search for literal "Change" + ChevronRight near it
    LINE=$(rg -nF 'Change' "$f" | rg -n 'ChevronRightIcon' | head -n 1 | cut -d: -f1 || true)
  fi
  if [[ -z "${LINE}" ]]; then
    rg -nF 'ChevronRightIcon' "$f" | head -n 3
    echo "No exact aria-label match found; cannot locate snippet precisely."
    continue
  fi
  START=$((LINE-40))
  END=$((LINE+70))
  sed -n "${START},${END}p" "$f" | cat -n
done

Repository: superset-sh/superset

Length of output: 13927


Make “Change” a DropdownMenuItem (not a raw <button>) to preserve keyboard navigation.

DropdownMenuContent here contains Radix menu items (DropdownMenuItem) for roving focus, but the “Change” action is a plain <button>—so it won’t participate in Radix’s keyboard/arrow-key navigation and can be unreachable. Replace it with DropdownMenuItem (using onSelect to open the model selector) instead of a standalone button.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/hooks/usePaneRegistry/components/ChatPane/components/WorkspaceChatInterface/components/ChatInputFooter/components/ChatComposerControls/ComposerSettingsMenu/ComposerSettingsMenu.tsx
around lines 207 - 230, The "Change" action is a plain <button> so it doesn't
participate in Radix keyboard roving; replace that button with a
DropdownMenuItem from your dropdown primitives (ensure DropdownMenuItem is
imported) and move the click logic into its onSelect handler: set
pendingDialogOpenRef.current = true and setMenuOpen(false). Preserve the
aria-label and inner structure (selected logo, model name, and ChevronRightIcon)
inside the DropdownMenuItem so it looks identical but now participates in Radix
keyboard/arrow navigation. Ensure no leftover onClick handlers remain and the
component maintains the same classes for styling.

justincrich added a commit to justincrich/superset that referenced this pull request May 23, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
justincrich added a commit to justincrich/superset that referenced this pull request May 24, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
justincrich added a commit to justincrich/superset that referenced this pull request May 24, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
<ChevronDownIcon className="size-2.5 opacity-50" />
</PromptInputButton>
</ModelSelectorTrigger>
{!triggerless && (
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm do we still need the triggerless flag? would be good to investigate

justincrich added a commit to justincrich/superset that referenced this pull request May 26, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
justincrich added a commit to justincrich/superset that referenced this pull request May 26, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
justincrich added a commit to justincrich/superset that referenced this pull request May 26, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
justincrich added a commit to justincrich/superset that referenced this pull request May 26, 2026
…gate

Wave 2 of the pixel-perfect chat-view build. 19 molecules added under
apps/mobile/components/ (folder/folder + index.ts + stories), composing
the Wave-1 atoms + vendor primitives (Button, Badge, Avatar, RadioGroup,
Textarea, Collapsible, Separator, Icon, Text).

Composer cluster (replaces deprecated picker-toolbar pattern per desktop
PR superset-sh#4866 / SUPER-755 unified-settings-menu directive):
  - PickerTrigger          — kept as building block for internal use
  - ComposerSettingsButton — NEW: single pill with [Shield][Model][Brain]
                              icons; tap opens composer-settings bottom
                              sheet (sheet itself is organism, deferred
                              to Wave 3 — needs sheet primitive added)
  - ComposerRow            — Textarea + send/stop button + settings pill;
                              4 state variants (idle/typing/streaming/sending)

Popover row items (rendered inside settings sheet or slash-command popover):
  - SlashCommandOption · ModelPickerOption · ThinkingLevelOption

Chrome:
  - AppHeader   — leading back + centered title/subtitle + trailing actions
  - ModalHeader — leading ✕ + title + optional trailing action; simple variant

Render surfaces:
  - CodeBlock          — language label + Copy IconButton + Separator + mono body
  - Banner             — 4 variants × 2 shapes; top horizontal ToolStatusRule
  - PendingApprovalCard — UC-PAUSE-01 inline pending card; 4 states
  - ApprovalFooter     — UC-PAUSE-01 sticky footer; Decline/Approve/Always
                          ordering for one-handed UX (deviation documented)
  - SuggestedAnswerPill — UC-PAUSE-02 ask_user pill; composes Pill (accent/default/ghost)
  - PendingActionPill  — UC-PAUSE-04 floating pill; 3 kinds × Reanimated FadeIn/Out
  - AssistantMessageHead — UC-RENDER-01 head; 5 status variants
  - UserMessageBubble  — UC-RENDER-01 user message; 3 variants + failed state
  - ToolCallCard       — UC-RENDER-04 tool invocation; 5 status variants
  - CollapsedBlock     — UC-RENDER-05/06 plan/reasoning/subagent; vendor Collapsible
  - ScrollBackButton   — UC-RENDER-07 floating FAB + new-messages dot badge

Composition priority observed: existing internal Wave-1 atoms (Pill,
IconButton, ToolStatusRule, StatusDot, ProgressDots, FabBase, etc.)
+ vendor components/ui/* (Button, Badge, Avatar, RadioGroup, Collapsible,
Separator, Icon, Text, Textarea) → custom. Reanimated used only for
animated values; otherwise NativeWind className.

Each story uses Molecules/ prefix per pixel-perfect convention. All
props wired to argTypes controls.

Stale: ComposerToolbar (popover-per-trigger pattern) was prototyped then
deleted per user feedback — desktop PR superset-sh#4866 collapses 3 sibling pills
into a single trigger menu, and the equivalent mobile pattern is a
bottom sheet behind one button (ComposerSettingsButton).

Verification:
  - bun run typecheck (apps/mobile): clean
  - bunx biome check (touched files): clean
  - Wave 1 storybook bundle still loads (no regressions to atom stories)

Gates flipped (apps/mobile/design/manifest.json):
  mobile-ios:      gates.molecules → passed (phase=molecules)
  mobile-android:  gates.molecules → passed (shared source tree)

Wave 3 (screens) deferred behind user confirmation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants