feat(mobile): Wave 5 sessions-list tier — TS-4 closes (Sprint 1 / PR 7 of 7)#4912
feat(mobile): Wave 5 sessions-list tier — TS-4 closes (Sprint 1 / PR 7 of 7)#4912justincrich wants to merge 40 commits into
Conversation
Cherry-picked from f4f2a687b (originally on the deleted chat-mobile-ui-elements
branch). Adds Storybook 9 native sandbox env-gated on EXPO_PUBLIC_STORYBOOK,
Design System token stories (Colors/Typography/Spacing/Icons reading existing
global.css tokens via className), and a HelloWorld reference component.
Manifest updated with structured `constraints` block:
- preserve_theme: lib/theme.ts, global.css, uniwind-env.d.ts, uniwind-types.d.ts
are canonical — scaffold and later phases must not overwrite.
- wireframes_are_reference_only: PRD ASCII wireframes describe structural
intent only; use frontend-design skill for high-fidelity during build.
Gates: discover/target/equip/scaffold = passed on mobile-ios and mobile-android.
Next: /pixel-perfect:build --platform mobile-ios (or mobile-android).
Adds storybook 9 + addon-ondevice-actions/controls + @storybook/react-native devDeps in apps/mobile to match the cherry-picked package.json. Also picks up apps/desktop 1.10.3 -> 1.11.0 from main.
…Phase 0) Execute the token migration described in plans/chat-mobile-plan/14-token-migration-audit.md. Path A: keep flat shadcn key names (--color-*) for rn-reusables CLI compatibility; swap values to desktop ember warm palette + add chat-domain extensions (state palette, domain tokens, fonts, touch-target spacing). Vendor react-native-reusables components in apps/mobile/components/ui/* are not touched — they read tokens at runtime and cascade automatically against the new values, per the "vendor libraries + style overrides only" rule. - apps/mobile/global.css: rewrite under Tailwind 4 @theme + uniwind @variant. Warm-neutral ramp (#151110 background / #201e1c card / #2a2827 secondary in dark; #ffffff / warm-tinted light grays in light). Ember accent #e07850 (hsl(17 69% 60%)) as --color-primary in both themes. Add state palette (live/warning/danger/success/neutral × fg/bg) and chat domain tokens (streaming-cursor, tool-rule). Pre-compute oklch literals as hsl for RN safety; omit color-mix() hover/pressed (rn-reusables handles interaction via opacity/scale). - apps/mobile/lib/theme.ts: mirror global.css key-for-key. NAV_THEME.primary now resolves to ember (was inverted-neutral); NAV_THEME.notification stays destructive per audit §4. Add stateXxx + streamingCursor / toolRule + fontBody / fontMono. - apps/mobile/app/_layout.tsx: wire Geist + Geist Mono via @expo-google-fonts/geist with SplashScreen.preventAutoHideAsync gate. Storybook + production both wait for fonts. - apps/mobile/.rnstorybook/stories/DesignSystem/Colors.stories.tsx: extend with State palette + Domain tokens sections. - apps/mobile/.rnstorybook/stories/DesignSystem/Typography.stories.tsx: add Font families section demonstrating all 4 Geist weights + Geist Mono weights via fontFamily prop. - apps/mobile/design/manifest.json: bump to v5.1.0. Vibe rewritten to ember. Narrow preserve_theme.paths to uniwind machinery only (global.css + lib/theme.ts no longer locked). Add vendor_components_immutable constraint (per the new rule). Add tokens_source pointer to designs/tokens/tokens.css. Add fonts tool spec to both platform entries. - apps/mobile/package.json + bun.lock: add @expo-google-fonts/geist 0.4.2, @expo-google-fonts/geist-mono 0.4.2, expo-font ~56, expo-splash-screen ~56. Verified: bun typecheck passes (exit 0), biome check passes on touched files.
Storybook v9 react-native does not auto-detect AsyncStorage when
`shouldPersistSelection: true` — it expects the consumer to pass a
`storage` adapter explicitly. Without one, the persistence layer attempts
to call `.getItem()` on `undefined` and throws:
TypeError: Cannot read property 'getItem' of undefined
This appears on first launch when Storybook tries to read the last-selected
story.
Fix: pass `{ getItem: AsyncStorage.getItem, setItem: AsyncStorage.setItem }`
from `@react-native-async-storage/async-storage` (already a dep). Storybook
now persists story selection across launches without erroring.
Storybook 9.x's `instrumenter` (transitively via @storybook/addon-ondevice-
controls + @storybook/addon-ondevice-actions) pulls in `tinyrainbow`, which
requires Node's built-in `tty` module. Metro cannot bundle it, surfacing as:
ERROR Unable to resolve module tty from
.../storybook@9.1.20/.../instrumenter/index.cjs
Fix: shim `tty` to an empty module via `resolver.resolveRequest`. Returning
`{ type: "empty" }` is Metro's built-in pattern for Node built-ins it does
not bundle (same trick used for `fs`, `path`, etc. in RN bundles).
Pre-existing infra issue surfaced during pixel-perfect Wave-1 atom
verification; unrelated to Wave-1 atom additions.
Several screen modules (OrganizationHeaderButton, AuthenticatedTabBar, TabBarAccessory, OrgDropdown, MoreMenuScreen, SettingsScreen, etc.) call `useRouter`/`useNavigation` hooks that require an active expo-router NavigationContainer. Storybook 9's `addon-ondevice-controls` eagerly evaluates each story's render function during `createPreparedStoryMapping`, which throws "Couldn't find an UnhandledLinkingContext context" outside a running navigator. The affected stories already comment-acknowledge they're "not renderable in Storybook isolation" — they exist mainly as documentation surfaces. Commenting out the `../screens/**/*.stories.?(ts|tsx|js|jsx)` glob in .rnstorybook/main.js stops Storybook from pulling them into the bundle. To restore screen stories later, uncomment the glob AND add a nav-mock decorator to preview.tsx that provides UnhandledLinkingContext + a stub useRouter (or wrap the story tree in a real NavigationContainer with mock state). Pre-existing infra issue surfaced during pixel-perfect Wave-1 atom verification; unrelated to Wave-1 atom additions.
Pre-existing screen modules (OrganizationHeaderButton, AuthenticatedTabBar, TabBarAccessory, OrgDropdown, MoreMenuScreen, SettingsScreen, etc.) call `useRouter`/`useNavigation` hooks. Storybook 9's `addon-ondevice-controls` eagerly evaluates each story's render path during `createPreparedStoryMapping`, throwing "Couldn't find an UnhandledLinkingContext context" outside a running NavigationContainer. Fix: wrap the preview decorator chain in `<NavigationContainer>` from `expo-router/react-navigation`. That sets up `UnhandledLinkingContext`, `LinkingContext`, `LocaleDirContext`, and the base navigation state — the same contexts expo-router's `<ExpoRoot>` provides in the real app. Using the expo-router sub-path (`expo-router/react-navigation`) instead of `@react-navigation/native` directly so we ride on apps/mobile's already- declared expo-router dep — avoids a phantom-dep on `@react-navigation/native` (which is only present transitively in the bun store). Reverts the prior commit `dedf3dd56` that hid screens stories from main.js. Screens stories now render correctly in Storybook isolation.
… prep Tried wrapping the storybook preview chain in `<NavigationContainer>` from `expo-router/react-navigation` (commit 0e805a1). Did not resolve the `UnhandledLinkingContext` error. Root cause: Storybook 9 RN's `loadStory` (called inside `createPreparedStoryMapping`) does eager module + render-fn evaluation BEFORE preview decorators apply. The screen placeholder stories transitively import `useTheme` → `lib/theme.ts` → `expo-router/react-navigation`, which during prep calls `useLinking` → reads the default `UnhandledLinkingContext` value's getter → throws "Couldn't find an UnhandledLinkingContext context." Decorators in preview.tsx wrap render-time only, not prep-time. So the NavigationContainer wrapper is structurally unable to fix this chain. Re-exclude `../screens/**/*.stories.?(ts|tsx)` (same as the earlier dedf3dd commit). Keep the NavigationContainer in preview.tsx as defense for any future story that DOES route through decorators and needs nav context (e.g. a future composer molecule that uses `<Link>`). Long-term restoration of screen stories requires decoupling them from `lib/theme.ts` (mirror the pattern used in `components/ScrollFade/ScrollFade.tsx`) or moving them under `expo-router/testing-library`'s `renderRouter`.
Unblock storybook RN prep for views that transitively import expo-router by providing a self-contained StorybookRouterProvider wrapping the preview-time linking contexts. - Add StorybookRouterProvider that wraps PreviewRouteContext + LinkingContext + UnhandledLinkingContext so views (including AskUserSheet from REMED-009) can render in Storybook without crashing on "Couldn't find an UnhandledLinkingContext context." - Update preview.tsx + index.tsx to wrap stories in StorybookRouterProvider; remove the prior "do not import nav modules" ban (now properly worked-around at runtime, not at module-load time). - metro.config.js teaches metro to resolve the new router/ subpath + storybook config files in app bundling. Follow-up to ae4494c (REMED-009 task commit).
|
Important Review skippedToo many files! This PR contains 231 files, which is 81 over the limit of 150. To get a review, narrow the scope: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (231)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
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. |
There was a problem hiding this comment.
3 issues found across 241 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="apps/mobile/components/ChatThread/ChatThread.tsx">
<violation number="1" location="apps/mobile/components/ChatThread/ChatThread.tsx:42">
P1: `assistant-body` is typed as `ReactNode`, which includes primitive `string` and `number` values that cause React Native runtime errors when rendered directly inside a `<View>`.</violation>
</file>
<file name="apps/mobile/components/ApprovalFooter/ApprovalFooter.tsx">
<violation number="1" location="apps/mobile/components/ApprovalFooter/ApprovalFooter.tsx:80">
P3: `disabled` does not suppress the action spinner, so the component can show a spinner while claiming to be fully disabled.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 100 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
| | { | ||
| id: string; | ||
| kind: "assistant-body"; | ||
| body: ReactNode; |
There was a problem hiding this comment.
P1: assistant-body is typed as ReactNode, which includes primitive string and number values that cause React Native runtime errors when rendered directly inside a <View>.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mobile/components/ChatThread/ChatThread.tsx, line 42:
<comment>`assistant-body` is typed as `ReactNode`, which includes primitive `string` and `number` values that cause React Native runtime errors when rendered directly inside a `<View>`.</comment>
<file context>
@@ -0,0 +1,185 @@
+ | {
+ id: string;
+ kind: "assistant-body";
+ body: ReactNode;
+ }
+ | {
</file context>
| onPress={onDecline} | ||
| accessibilityLabel="Decline tool action" | ||
| > | ||
| {resolving === "decline" ? ( |
There was a problem hiding this comment.
P3: disabled does not suppress the action spinner, so the component can show a spinner while claiming to be fully disabled.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mobile/components/ApprovalFooter/ApprovalFooter.tsx, line 80:
<comment>`disabled` does not suppress the action spinner, so the component can show a spinner while claiming to be fully disabled.</comment>
<file context>
@@ -0,0 +1,119 @@
+ onPress={onDecline}
+ accessibilityLabel="Decline tool action"
+ >
+ {resolving === "decline" ? (
+ <ActivityIndicator size="small" className="text-white" />
+ ) : (
</file context>
|
Ready to review this PR? Stage has broken it down into 12 individual chapters for you: Chapters generated by Stage for commit 4d873b8 on May 26, 2026 9:04pm UTC. |
544be9b to
f8d6981
Compare
…ed-UI items Sprint 01 (Pixel-Perfect UI Components) shipped 2026-05-24 as a 7-PR stack (superset-sh#4870..superset-sh#4912) covering 10 atoms + 26 molecules + 14 organisms + 30 view stories. Pixel-perfect manifest gates all flipped passed for mobile-ios + mobile-android. Status: 🟠 In flight → ✅ Completed; PR cell populated with all 7 stack URLs. The original Sprint 01 inventory included 9 components that were not built standalone. They are deferred to the integration sprint that wires them, where they emerge as domain wrappers / assemblies over the as-shipped primitives (no rework — additive to each sprint's task table): Sprint 02 (+3): NewChatFab, SessionSearchBar, FilterButton domain wrappers Sprint 03 (+4): MessageMarkdown, PlanBlock, ReasoningBlock, SubagentExecutionMessage Sprint 04 (+3): TiptapPromptEditor + extensions (the largest deferral — WebView-shell editor; ComposerRow currently uses Textarea), PermissionModePicker, ThinkingLevelPicker domain wrappers Sprint 05 (+1): PendingQuestionSheet assembly Sprint 06 (+2): PushPrePromptScreen, RebableInSettingsBanner Sprint 07 (+1): HostOfflineBanner Sprint counts updated in Sprint Sequence table; Overview Current Sprint set to Sprint 02 (next). Each integration sprint now lists deferred-UI rows in bold at the top of its Tasks table with a build-first carryover note. No structural changes to the Phase 2 gate set, dependency graph, or PR-sequencing convention.
- Revert global.css and theme.ts to reactnativereusables default theme (stock shadcn/ui keys + values, no custom ember palette) - Remove DesignSystem gallery stories (Colors, Icons, Spacing, Typography) - Remove HelloWorld scaffold component - Remove design/manifest.json (superseded by Storybook) - Trim comments in global.css and main.js - Pin expo-font and expo-splash-screen to exact versions - Move tty stub from metro.config.js to Storybook module mock - Handle useFonts error so splash screen never stalls
Matches global.css and theme.ts exactly to origin/main — no custom theme changes in this PR, only Storybook tooling additions.
…ganisms (Wave 5) Theme migration (5 files): - text-state-danger-fg → text-destructive - text-state-warning-fg → text-amber-600 Per Token Migration Table cascade from superset-sh#4874. Also drops the "4 sessions-list organisms" commit — those components belong on the sessions-list PR (superset-sh#4912), not the views PR (superset-sh#4911).
f8d6981 to
0fb536a
Compare
- Remove duplicate NavigationContainer from preview.tsx decorator. StorybookRouterProvider already provides one. SDK 56 blocks direct @react-navigation/native imports inside the expo-router tree. - Add Welcome.stories.tsx so Storybook has at least one story on the tooling branch (prevents EmptyIndexError crash).
…migration)
Migration verification surface for the ember theme rewrite. Every primitive in
apps/mobile/components/ui/* now has a sidecar Components/{Primitive} story so
the live app components are visually inspectable on iOS Simulator + Android
Emulator under both light and dark ember themes.
Per the vendor-immutable rule, no primitive source file is edited — the stories
import the real components the app already uses. Discovered token-bypass
divergences (shadow-black/5, bg-black/50 backdrops, text-white in destructive
variants) are documented in apps/mobile/components/ui/AUDIT.md with the
upstream-PR resolution path. Local source edits to vendor primitives are
explicitly not the fix.
- 28 stories: accordion, alert, alert-dialog, aspect-ratio, avatar, badge,
button, card, checkbox, collapsible, context-menu, dialog, dropdown-menu,
hover-card, icon (catalog with 22 chat-domain lucide icons), input, label,
menubar, popover, progress, radio-group, select, separator, skeleton, switch,
tabs, text, textarea, toggle, toggle-group, tooltip
- AUDIT.md: 3 token-bypass findings catalogued, accepted as upstream-PR work
- .rnstorybook/preview.tsx: PortalHost added to decorator so primitives that
render via portal (Popover, Dialog, AlertDialog, DropdownMenu, ContextMenu,
Menubar, Select, Tooltip, HoverCard) work in Storybook
- Story conventions match the workflow spec: title='Components/{Name}',
argTypes wired for all controllable props, realistic chat-domain mock data
(model names, session statuses, branch · host disambiguation)
Verified: bun typecheck (exit 0), biome check (no warnings).
This is the migration verification layer for Sprint 01 Phase 0 (token rewrite,
2c21dbc). Pixel-perfect manifest atoms gate stays pending — the gate flips
only after the human reviewer walks the Storybook inventory on simulator.
…udit + stories) Completes the theme-migration story coverage for live first-party app components in apps/mobile/screens/**/components/. Per the audit pass: CRITICAL fixes (active bugs under ember): - OrganizationHeaderButton.tsx:25 — replaced hardcoded `hsl(240 5% 64.9%)` (OLD cool-neutral palette) with `theme.mutedForeground` (warm). Chevron now renders correctly under the warm-neutral surface ramp. MEDIUM fixes (token bypass / wrong source): - SocialButton.tsx — replaced `useColorScheme()` + hardcoded `"white"/"black"` icon color with `useTheme().foreground`. Previously the icon read the OS appearance setting (which may disagree with `Uniwind.setTheme()`); now tracks the active app theme. Google brand colors retained per brand-asset policy. DOCUMENTED exceptions: - SocialButton Google icon brand colors (#4285F4 / #34A853 / #FBBC05 / #EA4335) — kept as official Google brand assets. - OrganizationSwitcherSheet forces `colorScheme: dark` in SwiftUI environment. Documented as design choice; revisit if app supports system light/dark. Story coverage (8 stories) — sidecar to source per existing convention: - OrganizationAvatar — fully presentational; matrix story for sizes 20/28/36/48/64 - SocialButton — both providers; brand-color verification - DevSignInButton — base render only - OrganizationHeaderButton — *Preview wrapper renders inner content* (real component lives inside expo-router Stack.Toolbar — not isolatable) - OrganizationSwitcherSheet — *Inner content* + NativeSheet iOS-only story (real sheet uses @expo/ui/swift-ui — Android renders disclaimer) - OrgDropdown — *Preview wrapper* with mock orgs (real reads authClient + Electric collections — not isolatable) - TabBarAccessory — *Preview wrapper* with mock org (real reads useOrganizations) - AuthenticatedTabBar — placeholder documenting integration dependencies (TabBarView SwiftUI bridge + expo-router/ui Tabs context + Electric) Storybook glob — extended `.rnstorybook/main.js` to include `../screens/**/*.stories.?(ts|tsx|js|jsx)` so screen-component stories are picked up by sb-rn-get-stories. Audit doc — apps/mobile/screens/AUDIT.md catalogs all 8 components, the 2 fixes applied, the 2 documented exceptions, the 6 clean components, and the isolation caveats per integration-heavy component. This completes the migration of the live app component set to the ember theme. The Storybook walkthrough on iOS Simulator + Android Emulator is the verification surface. Verified: bun typecheck (exit 0), biome check (clean).
…ganisms (Wave 5) Theme migration (5 files): - text-state-danger-fg → text-destructive - text-state-warning-fg → text-amber-600 Per Token Migration Table cascade from superset-sh#4874. Also drops the "4 sessions-list organisms" commit — those components belong on the sessions-list PR (superset-sh#4912), not the views PR (superset-sh#4911).
0fb536a to
e5e52b3
Compare
…s PR) The Welcome story was added in PR superset-sh#4874 (tooling) to give Storybook at least one entry — otherwise the empty stories index threw an EmptyIndexError on the tooling branch in isolation. This PR brings real component stories (8 first-party + 28 vendor primitives), so the placeholder is no longer needed.
Adds 5 chat-specific atom components + stories as foundation for the chat
mobile sprint. Token-driven (state palette, ember accent), 44pt touch targets,
no hardcoded values. These do NOT yet match the designs/atoms/{name}/ HTML
mockups exactly — a follow-up pass will refine each against the design spec
before chat screens consume them.
Why "foundation" not "migration": these are new components, not migration of
existing live app components to the new theme. They were started during the
build pass but the user redirected scope to migration-first; committing as
foundation rather than discarding so the work survives for future refinement.
- components/IconButton/ — Pressable + Icon wrapper with 44pt min hit target,
5 variants (default/primary/secondary/ghost/destructive), 3 sizes.
Real-design refinement TODO: match designs/atoms/icon-button/README.md
variant taxonomy (--ghost is default per spec, also needs --soft and
--neutral variants; pill shape modifier).
- components/Pill/ — chat-domain pill (model chip, mode chip, suggested answer)
with 6 variants (default/selected/warning/danger/success/live) and
interactive/non-interactive split.
Real-design refinement TODO: match designs/atoms/pill/README.md — needs
6 variants per spec (default/strong/accent/live/warning/danger), 3 sizes
with monospace + uppercase modifiers, separate dismiss button.
- components/StatusDot/ — single colored circle (live/warning/danger/success/
neutral + ember). 3 sizes.
Real-design refinement TODO: match designs/atoms/status-dot/README.md —
6px/8px/10px exact sizes (sm 8px is default), live variant needs pulse
animation, warning needs box-shadow ring.
- components/StreamingCursor/ — Reanimated ▌ blink. Configurable duration +
color. accessibilityElementsHidden per spec.
Real-design refinement TODO: match designs/atoms/streaming-cursor/README.md
— 2px width × 1em height with 6px glow shadow, 1s steps(2) animation,
--default / --steady / --paused variants.
- components/ToolStatusRule/ — 3px (default) left rule with running/completed/
failed/pending/neutral/ember variants.
Real-design refinement TODO: match designs/atoms/tool-status-rule/README.md
— 5 status variants (--running/--done/--pending/--error/--neutral), needs
--vertical/--horizontal orientation, glow shadow on running + pending.
All atoms have Components/{Name} stories with argTypes wired. Verified:
bun typecheck (exit 0), biome check (no warnings).
ScrollFade and HitTargetWrapper from the original 7-atom plan are NOT
included — they were not started. Will be built fresh from designs/atoms/
spec when chat screens need them.
Phase 4b BUILD PLAN written to apps/mobile/design/manifest.json (plan
gate → passed for mobile-ios + mobile-android). Scope cut to chat-view
per designs/AUDIT.md; sessions-list/nav surfaces deferred.
Refined against design specs (designs/atoms/<name>/README.md):
- IconButton → 5 variants × 4 sizes × 2 shapes; composes vendor Button
- Pill → 6 variants × 3 sizes; mono/uppercase mods + dismiss
✕ button; composes vendor Badge
- StatusDot → xs/sm/md (6/8/10px); live-pulse + warning-ring halos,
reduced-motion via AccessibilityInfo
- StreamingCursor → default/steady/paused variants; steps(2) emulation;
reduced-motion fallback
- ToolStatusRule → 5 status variants × vertical/horizontal; glow on
running + pending; fixed 3px per spec
Created:
- FabBase — 56/64pt circular FAB; live-ring pulse halo;
composes existing IconButton/Pressable patterns
- HitTargetWrapper — 44pt invisible tap zone; square/circle shapes;
debug-outline modifier
- ProgressDots — 3-dot staggered pulse; 4 variants × 3 sizes;
reduced-motion fallback
- ScrollFade — theme-aware gradient (expo-linear-gradient);
top/bottom × 3 surfaces × 3 sizes; 120ms hide
transition via Reanimated
- ToastBase — 5 variants × inline/stacked; composes
ToolStatusRule + Icon + Text + HitTargetWrapper
Composition priority observed: existing internal components (vendor
Button/Badge/Icon/Text + first-party atoms) → rn-primitives → custom.
NativeWind className preferred over inline style where reasonable; inline
style retained only for Reanimated animated values and RN custom-colored
shadows.
Stories use Components/ prefix per pixel-perfect convention; all props
wired to argTypes controls with descriptive options.
Deps:
- apps/mobile: + expo-linear-gradient@56.0.4 (latest stable for SDK 56)
Verification:
- bun run typecheck (apps/mobile): clean
- bunx biome check (touched files): clean
ScrollFade imported THEME from @/lib/theme to pick the gradient opaque
stop. lib/theme.ts transitively imports DarkTheme/DefaultTheme from
expo-router/react-navigation, which pulls in UnhandledLinkingContext.
Storybook RN does not wrap stories in expo-router's NavigationContainer,
so any story that touches ScrollFade crashed with:
ERROR [Error: Couldn't find an UnhandledLinkingContext context.]
Fix: inline the 3 surface color values (page/soft/overlay × light/dark)
locally in ScrollFade.tsx. Keep in sync with --color-background /
--color-card / --color-popover in global.css. Decouples ScrollFade from
the nav-coupled theme module.
…ents out of args Two prep-time blockers cleared: 1. preview.tsx imported NavigationContainer from expo-router/react-navigation. Storybook 9 RN evaluates preview.tsx during createPreparedStoryMapping (before decorators apply). Loading expo-router's nav module reads the default UnhandledLinkingContext value, whose getters throw "Couldn't find an UnhandledLinkingContext context." Decorators apply at render time, not prep time — so the wrapping I added in commit 0e805a1 was structurally unable to fix the chain. Worse, it BECAME the trigger. Fix: remove the nav import + decorator from preview.tsx. Keep only the neutral View + PortalHost wrapper. Documented why nav can't live here. 2. ToastBase WithAction + StackedWithCTA stories put `<Button>` React elements directly in `args.action`. Storybook tries to JSON-serialize args to persist control state; React elements have circular refs that trigger "cycle in arg" warnings repeatedly. Fix: move the action element into the story's `render` function. Args stay JSON-serializable; the render fn supplies the live React element at render time. Verified: bundler running from apps/mobile cwd; full bundle compiles 21MB HTTP 200; sim screenshot shows DesignSystem "Semantic pairs" story rendering (background/foreground, card/card-foreground swatches visible). No UnhandledLinkingContext error in fresh bundler log; no cycle warning in fresh re-bundles.
…ganisms (Wave 5) Theme migration (5 files): - text-state-danger-fg → text-destructive - text-state-warning-fg → text-amber-600 Per Token Migration Table cascade from superset-sh#4874. Also drops the "4 sessions-list organisms" commit — those components belong on the sessions-list PR (superset-sh#4912), not the views PR (superset-sh#4911).
e5e52b3 to
3dc3907
Compare
…e behavior HitTargetWrapper was a 44pt Pressable wrapper with no consumers beyond ToastBase's dismiss button. IconButton already provides the same 44pt touch target plus hitSlop, variants, sizes, shapes, and loading state. ToastBase dismiss now uses IconButton (variant=ghost, shape=pill, size=xs) instead of HitTargetWrapper + Icon composition.
…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.
Replace the stacked layout (settings pill on its own row above textarea)
with the Claude iOS pattern: single rounded container with the textarea
on top and an action toolbar inside the same chrome below.
Toolbar order (mirrors Claude reference):
LEFT: [+] commands button → [Shield/Model/Brain] settings pill
RIGHT: send / stop / progress-dots (state-driven swap)
Behavior per variant is unchanged:
- idle: Send disabled; toolbar fully enabled
- typing: Send active (primary ember)
- streaming: Stop (destructive); toolbar buttons disabled
- sending: ProgressDots replaces Send in 44pt slot; toolbar buttons disabled
API:
+ Added `onCommandsPress?: () => void` + `commandsAccessibilityLabel?: string`
- Settings prop unchanged. Send/stop/value/onChangeText/variant/placeholder kept.
- When `onCommandsPress` is omitted the `+` button is hidden — keeps the
API surface optional for hosts that haven't wired a slash-command
popover yet.
Composition: composes vendor Textarea (inner editable, borderless) +
first-party IconButton (commands `+` ghost sm; send/stop primary/
destructive pill md) + ComposerSettingsButton + ProgressDots. The outer
container owns the border + bg-card; the textarea is transparent inside.
Out of scope (organisms / Wave 3):
- Bottom-sheet behind ComposerSettingsButton
- Slash-command popover behind the `+` button
Verification:
- bun run typecheck: clean
- bunx biome check apps/mobile/components/ComposerRow: clean
- Storybook RN on iOS 26.5 sim: bundle compiles HTTP 200 (22MB, 5249 mod);
Molecules/ComposerRow story renders new layout; bottom toolbar shows
[+] [○ Sonnet 4.6] on left, ember send circle on right; sample text
rendered in textarea above. Screenshot in plan-mode reference confirms.
Per plan: ~/.claude/plans/i-feel-we-need-clever-plum.md
…tching commands circle
User feedback on the first Claude-style pass:
1. The input body was two-tone — vendor Textarea has `dark:bg-input/30` +
`shadow-sm` chrome that wins over my `bg-transparent` in dark mode,
creating a darker rectangle inside the outer card surface.
2. The commands icon should be `/` (slash-command intent) not `+`
(attachments).
3. The commands button should match the settings pill in size/border —
a circle so the option remains visually open when the layout grows.
Fixes:
- Textarea className: `bg-transparent dark:bg-transparent border-0
rounded-none shadow-none` to fully suppress vendor chrome. Outer
container owns border + bg-card so composer renders as one tonal
block end-to-end.
- Replaced `Plus` import with `Slash` from lucide-react-native.
- Commands IconButton: `size="xs" shape="pill"` (28×28 circle, matches
ComposerSettingsButton's h-7 height) + `variant="soft"` + explicit
`border border-border` className to mirror the settings pill border
treatment.
Verification:
- bun run typecheck: clean
- bunx biome check apps/mobile/components/ComposerRow: clean
- Storybook RN on iOS 26.5 sim: Molecules/ComposerRow/Typing renders
one-tone rounded container; bottom toolbar shows `[/]` circle button
+ `[○ Sonnet 4.6 🧠]` settings pill on left, ember send circle on
right. Verified via xcrun simctl screenshot.
…gate
Inserts a new organisms tier between molecules and views per
designs/AUDIT.md §ORGANISM AUDIT. The 10 organisms cluster existing
molecules + vendor primitives into the named composites that views
will compose directly.
Organisms (all under apps/mobile/components/{Name}/):
- ChatHeader — AppHeader + safe-area + status row + banner slot
- ChatThread — FlatList with typed item union (user/assistant/tool/collapsed)
- Composer — KeyboardAvoidingView + ComposerRow + suppression states
- SlashCommandPopover — SlashCommandOption rows grouped by source
- PickerPopover — generic RadioGroup picker (Model + Thinking + Permission)
- PauseApprovalOverlay — PendingApprovalCard + ApprovalFooter (composer hidden)
- BottomSheet — themed wrapper around @gorhom/bottom-sheet BottomSheetModal
- PlanReviewScreen — full-screen ModalHeader + plan body + Reject/Approve
- ConfirmationDialog — controlled AlertDialog wrapper (Cancel/Destructive)
- LoadingSkeleton — alternating-width Skeleton rows for UC-SESS-02 §A
Stories all titled Organisms/{Name} with full argTypes controls and
representative fixtures (single conversation, plan block, queued
approvals, multiple sheet heights, model + thinking picker variants).
Vendor: adds @gorhom/bottom-sheet@5.2.12 (compatible with reanimated
4.3.1 + gesture-handler 2.31.2). Required by the chat-view ask_user
sheet, session overflow sheet, project / filter / new-chat pickers.
Manifest: bumps version 5.1.0 → 5.2.0, adds organisms entry to both
mobile-ios + mobile-android build_plan (10 created, vendor_added
list, source: designs/AUDIT.md §ORGANISM AUDIT), flips organisms
gate to passed, phase advances to "organisms".
Also includes biome auto-format pass on designs/* HTML/CSS/JSON
files (tab/whitespace normalization picked up by `bun run lint:fix`
during the wave 3 verification step — pure formatting, no semantic
changes).
Typecheck + lint clean for the 10 new organism folders. Wave 4
(screens / compose phase) remains deferred behind explicit user
confirmation per the manifest scope_note.
Storybook 9 RN warns when args contain JSX elements, React components,
or other non-serializable values — Storybook can't represent them in
the controls UI and recommends moving such props into `render` (or
using `mapping` for enum-style mapping).
Four organism stories had this pattern; fixed each by promoting the
JSX / component refs out of `args` into per-story `render` functions:
- ChatHeader.stories.tsx — `banner: <Banner …/>` JSX moved into the
OfflineWithBanner story's render(). `banner: { control: false }`
added; status mapping aligned with ApprovalFooter pattern.
- ChatThread.stories.tsx — message item arrays (which contain
`body: <Text>` JSX) moved out of args into per-story renders.
`items: { control: false, table: { disable: true } }`.
- PauseApprovalOverlay.stories.tsx — `header: <Text>` JSX promoted
into a const referenced by each render; resolving mapping aligned
with ApprovalFooter pattern ("(none)" sentinel, all enum values
explicitly mapped).
- SlashCommandPopover.stories.tsx — `commands` arrays contain
LucideIcon component references; moved into per-story renders.
No behavioral changes — same fixtures, same stories, just placed
where Storybook expects (render) instead of where it warns (args).
Note on the separate `UnhandledLinkingContext` error: not from this
work. None of the Wave 3 organisms or their molecule deps import
expo-router or call useNavigation/useTheme/useRouter. The known
trigger documented in apps/mobile/.rnstorybook/main.js is screen
stories transitively importing lib/theme.ts → expo-router; those
are already excluded.
…al pattern)
The previous declarative `open` / `onClose` wrapper hid the imperative
ref behind a useEffect that called `ref.current?.present()`. In
Storybook RN this pattern was unreliable — the lazy-mounted
BottomSheetModal's ref isn't connected on first render, so the
useEffect fired before the modal could respond, and tapping "Open
sheet" appeared to do nothing.
Switching to gorhom's canonical pattern:
const ref = useRef<BottomSheetRef>(null);
<BottomSheet ref={ref}>…</BottomSheet>
<Button onPress={() => ref.current?.present()} />
Wrapper changes:
- `forwardRef<BottomSheetRef, BottomSheetProps>` exposes gorhom's
`BottomSheetModal` methods (present, dismiss, snapToIndex, etc.)
directly to consumers
- Drops `open` / `onClose` props + the useEffect indirection
- `BottomSheetRef = BottomSheetModal<any>` typed to match gorhom's
public ref signature
Story changes:
- Imperative trigger: button onPress calls `sheetRef.current?.present()`
- Added inline Close button inside the sheet that calls `.dismiss()`
- Provider + GestureHandlerRootView remain at the harness root
The new API matches gorhom docs and the production chat-mobile sheet
callers (ask_user, overflow, project picker, filter, new chat) will
use the same imperative pattern when wired in Wave 4.
…6 files) Cascades the theme revert from superset-sh#4874 through atom + molecule components: - state-live/warning/danger/success/neutral → green-600/amber-600/destructive/muted-foreground - streaming-cursor → foreground - tool-rule → muted-foreground / border Per Token Migration Table in plans/pull-all-commented-feedback-polymorphic-yao.md
Storybook 9 RN warns when args contain JSX elements, React components,
or other non-serializable values — Storybook can't represent them in
the controls UI and recommends moving such props into `render` (or
using `mapping` for enum-style mapping).
Four organism stories had this pattern; fixed each by promoting the
JSX / component refs out of `args` into per-story `render` functions:
- ChatHeader.stories.tsx — `banner: <Banner …/>` JSX moved into the
OfflineWithBanner story's render(). `banner: { control: false }`
added; status mapping aligned with ApprovalFooter pattern.
- ChatThread.stories.tsx — message item arrays (which contain
`body: <Text>` JSX) moved out of args into per-story renders.
`items: { control: false, table: { disable: true } }`.
- PauseApprovalOverlay.stories.tsx — `header: <Text>` JSX promoted
into a const referenced by each render; resolving mapping aligned
with ApprovalFooter pattern ("(none)" sentinel, all enum values
explicitly mapped).
- SlashCommandPopover.stories.tsx — `commands` arrays contain
LucideIcon component references; moved into per-story renders.
No behavioral changes — same fixtures, same stories, just placed
where Storybook expects (render) instead of where it warns (args).
Note on the separate `UnhandledLinkingContext` error: not from this
work. None of the Wave 3 organisms or their molecule deps import
expo-router or call useNavigation/useTheme/useRouter. The known
trigger documented in apps/mobile/.rnstorybook/main.js is screen
stories transitively importing lib/theme.ts → expo-router; those
are already excluded.
SessionsList, ProjectPickerSheet, SessionFilterSheet, NewChatSheet under apps/mobile/screens/sessions-list/components/. Includes shared sessions-list types + mock-data, plus the .rnstorybook main.js glob extension to discover stories under screens/sessions-list/. Sessions-list view stories ship in the downstream sessions-list PR. Composed atop the 7 sessions-list molecules from the upstream molecules PR.
UC-NAV-01 canonical: SessionsListLoaded (project-first chrome, flat recency-sorted list scoped to selected project). UC-NAV-06 5-variant empty state: - SessionsListEmptyNoProjects - SessionsListEmptyNoWorkspaces - SessionsListEmptyNoSessions - SessionsListSearchNoMatch - SessionsListFiltersNoMatch UC-NAV-04 / UC-NAV-08 overlays: - ProjectPickerSheetView - SessionFilterSheetView - NewChatSheetView + SessionsListCombinedEmpty contact-sheet reference view. Composed atop the 7 sessions-list molecules (molecules PR) + 4 sessions-list organisms (organisms PR). Closes the TS-4 gate from the 2026-05-23 red-hat review.
SessionRow, ProjectChipHeader, EmptyState, FilterCheckboxRow, AppliedFilterTag, WorkspacePickerRow, ProjectPickerRow. Closes the sessions-list TS-4 dependency at the molecules tier (per 2026-05-23 red-hat review + ROADMAP `apps/mobile/screens/AUDIT.md`). Sessions-list organisms + views land in subsequent PRs in the stack.
…mber-600 Cascades from superset-sh#4874 theme revert per Token Migration Table.
…ganisms (Wave 5) Theme migration (5 files): - text-state-danger-fg → text-destructive - text-state-warning-fg → text-amber-600 Per Token Migration Table cascade from superset-sh#4874. Also drops the "4 sessions-list organisms" commit — those components belong on the sessions-list PR (superset-sh#4912), not the views PR (superset-sh#4911).
3dc3907 to
4d873b8
Compare
Captures current Mobile Chat v2 state: Sprint 01 7-PR stack (superset-sh#4874-superset-sh#4912) in review, Sprint 02 all 17 tasks shipped on chat-mobile-sprint-2 (no PR yet, 53 WIP files), Sprints 03-07 planned (53 tasks, ~108h). Maps every related PR -- in-stack, precursor, and superseded -- to a sprint or sprint AC. Documents concrete handoffs the Claude tooling can take: /kb-run-sprint per sprint, Sprint 02 WIP triage, Sprint 01 stack landing, Sprint 06 parallel waves, ROADMAP syncing, Maestro driving.
Sprint 1 / PR 7 of 7 — sessions-list views
Wave 5: full sessions-list tier closing the TS-4 build gap flagged in the 2026-05-23 red-hat review. The ROADMAP test step 4 lists 9 sessions-list components as required for Sprint 01; this PR ships all of them plus 10 view stories.
Inventory
7 new molecules at
apps/mobile/components/:SessionRow— two-line layout with🌿 branch · 💻 host · timemetadataProjectChipHeader— project chip + search input + filter button (sticky)EmptyState— centered no-content state with optional CTA (5 variants per UC-NAV-06)FilterCheckboxRow— workspace + status multi-select rowAppliedFilterTag— dismissible filter chipWorkspacePickerRow— workspace + host disambiguation rowProjectPickerRow— project + workspace/session counts row4 new organisms at
apps/mobile/screens/sessions-list/components/:SessionsList— FlatList + NewChatFabProjectPickerSheet— bottom sheet with project rowsSessionFilterSheet— bottom sheet with workspace + status multi-selectNewChatSheet— workspace picker bottom sheet (UC-NAV-04)10 view stories at
apps/mobile/screens/sessions-list/views/:SessionsListLoaded(UC-NAV-01 canonical) — flat recency-sorted list scoped to selected projectSessionsListEmptyNoProjects/EmptyNoWorkspaces/EmptyNoSessions/SearchNoMatch/FiltersNoMatch(UC-NAV-06 5-variant empty state)SessionsListCombinedEmpty— contact-sheet reference viewProjectPickerSheetView(UC-NAV-08)SessionFilterSheetView(UC-NAV-08 §C)NewChatSheetView(UC-NAV-04)Stack base
This PR stacks on top of organisms (#4872). Once organisms merges, GitHub will auto-rebase onto main.
Verified
bun run typecheck→ EXIT 0biome check apps/mobile/screens/sessions-list/ + Wave 5 molecules→ no findingsCloses
Closes the TS-4 build gap. Sprint 01 sessions-list tier now ships.
Summary by cubic
Ships the full mobile sessions-list tier and closes TS-4, plus an env-gated React Native Storybook sandbox with router context and stabilized prep. Also expands chat-view components to speed UI review.
New Features
@gorhom/bottom-sheet); and 10 view stories (loaded/empty/search/filters + sheet views).AppHeader,ChatHeader,ChatThread,Composer(+ row/settings),PauseApprovalOverlay(+ApprovalFooter),PickerPopover,ConfirmationDialog,PlanReviewScreen,LoadingSkeleton,BottomSheet(imperative ref), plus supportingBanner,CodeBlock,CollapsedBlock,PendingApprovalCard,PendingActionPill,AssistantMessageHead,ProgressDots,IconButton,Pill,FabBase,ModelPickerOption,ModalHeader..rnstorybook/withStorybookRouterProvider(expo-router contexts), persisted selection via@react-native-async-storage/async-storage,ttymodule mock, and scoped story globs for../components/**,../screens/chat-view/**, and../screens/sessions-list/**. Fonts + splash handled inapp/_layout.tsx.Dependencies
@storybook/react-native(+@storybook/addon-ondevice-controls,@storybook/addon-ondevice-actions), gated byEXPO_PUBLIC_STORYBOOK; persistence via@react-native-async-storage/async-storage.@gorhom/bottom-sheetfor sheets.@expo-google-fonts/geist,@expo-google-fonts/geist-mono,expo-font,expo-splash-screen(loaded inapp/_layout.tsx).Written for commit 4d873b8. Summary will update on new commits. Review in cubic