fix(app): dismiss the composer track panel when a row is chosen

Track pills became menu surfaces when the trackers moved out of stacked
cards, but ComposerTrackRow kept the raw Pressable it had as an inline
card row. Nothing in that path reaches selectItem, so the popover or
sheet stayed parked over the transcript after opening a subagent, and on
iOS the action fired while UIKit was still tearing the sheet down. Rows
now hand the press to the engine and take closeOnSelect, as MenuItem
does; bulk archive opts out because its progress lands in the panel.

The running mark is the shared StatusRing rather than a still dot the
pill drew for itself, and the remaining dots read their colour from the
one bucket-to-colour map. The slot is sized for the ring in every state
so the label does not step sideways when the last child finishes.

Mounting the real menu engine in a test needed harness work: stubs for
two packages that ship untranspiled Flow, reanimated left out of Vite's
prebundle so its .web files win, and the gaps filled in the unistyles
test theme.
This commit is contained in:
Mohamed Boudra
2026-08-17 23:43:01 +02:00
parent 1cc6712e51
commit c859b59c99
13 changed files with 396 additions and 73 deletions
@@ -0,0 +1,13 @@
// The package ships untranspiled Flow, which the unit project cannot parse. The sheet is a native
// presentation; tests that reach a menu do so through the popover, so rendering nothing is enough
// to keep the module graph importable.
const Stub = () => null;
const PassThrough = ({ children }: { children?: unknown }) => children;
export default Stub;
export const BottomSheetModal = Stub;
export const BottomSheetModalProvider = PassThrough;
export const BottomSheetBackdrop = Stub;
export const BottomSheetScrollView = PassThrough;
export const BottomSheetView = PassThrough;
export const BottomSheetTextInput = Stub;
@@ -22,6 +22,7 @@ export const Blocks = StubIcon;
export const Bot = StubIcon;
export const Brain = StubIcon;
export const Check = StubIcon;
export const CheckCircle = StubIcon;
export const CheckCircle2 = StubIcon;
export const ChevronDown = StubIcon;
export const ChevronLeft = StubIcon;
@@ -0,0 +1,8 @@
// Reanimated's native code path imports `react-native/Libraries/Renderer/shims/ReactFabric`.
// The `react-native` alias below points at a react-native-web file, so the subpath cannot
// resolve; on web reanimated takes `findHostInstance.web.js` and never reaches this.
export function findHostInstance_DEPRECATED(): null {
return null;
}
export default { findHostInstance_DEPRECATED };
@@ -0,0 +1,12 @@
// The package ships untranspiled Flow, which the unit project cannot parse. Insets are a device
// fact no test asserts on, so they are zero.
const ZERO_INSETS = { top: 0, right: 0, bottom: 0, left: 0 } as const;
export const useSafeAreaInsets = () => ZERO_INSETS;
export const useSafeAreaFrame = () => ({ x: 0, y: 0, width: 0, height: 0 });
export const SafeAreaProvider = ({ children }: { children?: unknown }) => children;
export const SafeAreaView = ({ children }: { children?: unknown }) => children;
export const initialWindowMetrics = {
insets: ZERO_INSETS,
frame: { x: 0, y: 0, width: 0, height: 0 },
};
@@ -7,6 +7,11 @@ const testTheme = {
statusDanger: "#b91c1c",
statusWarning: "#d97706",
statusMerged: "#7c3aed",
// The light band's values, so a test can name the colour it expects.
statusDotSuccess: "#299f51",
statusDotDanger: "#f12e2f",
statusDotWarning: "#b37824",
statusDotRunning: "#268ae0",
accent: "#2563eb",
accentForeground: "#ffffff",
destructive: "#dc2626",
@@ -19,6 +24,7 @@ const testTheme = {
palette: {
amber: { 500: "#f59e0b" },
blue: { 300: "#93c5fd" },
green: { 500: "#22c55e" },
red: { 300: "#fca5a5" },
white: "#ffffff",
},
@@ -45,8 +51,28 @@ const testTheme = {
xl: 12,
full: 9999,
},
iconSize: { sm: 16 },
iconSize: { sm: 16, md: 20 },
opacity: { 50: 0.5 },
shadow: {
sm: {
shadowColor: "rgba(0, 0, 0, 0.02)",
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
elevation: 2,
},
md: {
shadowColor: "rgba(0, 0, 0, 0.04)",
shadowOffset: { width: 0, height: 4 },
shadowRadius: 16,
elevation: 4,
},
lg: {
shadowColor: "rgba(0, 0, 0, 0.08)",
shadowOffset: { width: 0, height: 8 },
shadowRadius: 24,
elevation: 8,
},
},
};
type StyleFactory<T> = (theme: typeof testTheme) => T;
@@ -0,0 +1,3 @@
// Reanimated imports this CJS script with a default import. Unbundled, the browser cannot interop
// it, and the check it performs is a native-build concern with nothing to say in a browser test.
export default function validateWorkletsVersion(): void {}