From e927fc74c80a825ba47abd658d216abf0b3b4d54 Mon Sep 17 00:00:00 2001 From: Eivind Jonassen Date: Wed, 30 Sep 2026 20:10:31 +0200 Subject: [PATCH] fix(mobile): align question and message styles with shared theme --- .../src/screens/form-request-card.test.tsx | 12 +++++ apps/mobile/src/screens/form-request-card.tsx | 20 ++++++--- .../screens/permission-request-card.test.tsx | 13 ++++++ .../src/screens/permission-request-card.tsx | 5 ++- .../src/screens/session-transcript.test.tsx | 23 +++++++++- .../mobile/src/screens/session-transcript.tsx | 44 +++++++++---------- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/apps/mobile/src/screens/form-request-card.test.tsx b/apps/mobile/src/screens/form-request-card.test.tsx index 4f88d27..9f6657b 100644 --- a/apps/mobile/src/screens/form-request-card.test.tsx +++ b/apps/mobile/src/screens/form-request-card.test.tsx @@ -3,6 +3,7 @@ import type { FormInfo } from "@opencode2-mobile/opencode-adapter"; import { fireEvent, render, screen } from "@testing-library/react-native"; import { Alert, Linking } from "react-native"; +import { palette, typography } from "../theme"; import { FormRequestCard, openExternalFormUrl } from "./form-request-card"; const form: FormInfo = { @@ -29,6 +30,17 @@ const form: FormInfo = { title: "Deploy", }; +test("question controls use shared typography and neutral attention colors", () => { + render(); + + expect(screen.getByText("Input required")).toHaveStyle({ + ...typography.label, + color: palette.dim, + }); + expect(screen.getByText("Production")).toHaveStyle(typography.control); + expect(screen.getByLabelText("name")).toHaveStyle(typography.body); +}); + test("renders all field controls, conditional visibility, and a validated reply", () => { const onSubmit = jest.fn(); render(); diff --git a/apps/mobile/src/screens/form-request-card.tsx b/apps/mobile/src/screens/form-request-card.tsx index 01b2cf7..f12d97a 100644 --- a/apps/mobile/src/screens/form-request-card.tsx +++ b/apps/mobile/src/screens/form-request-card.tsx @@ -334,8 +334,17 @@ function ChoiceButton({ pressed && styles.pressed, ]} > - {label} - {description ? {description} : null} + + {label} + + {description ? ( + + {description} + + ) : null} ); } @@ -368,6 +377,7 @@ function FormButton({ ]} > { + render(); + expect(screen.getByText("Permission required")).toHaveStyle({ + ...typography.heading, + color: palette.ink, + }); + expect(screen.getByRole("alert")).toHaveStyle({ + ...typography.body, + color: palette.danger, + }); +}); + test("shell requests show a short preview and preserve full commands and scope in Details", () => { const reply = jest.fn(); render( diff --git a/apps/mobile/src/screens/permission-request-card.tsx b/apps/mobile/src/screens/permission-request-card.tsx index 3440e13..deac83b 100644 --- a/apps/mobile/src/screens/permission-request-card.tsx +++ b/apps/mobile/src/screens/permission-request-card.tsx @@ -123,7 +123,7 @@ export function PermissionRequestCard({ ) : null} {error ? ( - + The server did not accept that reply. Review the refreshed request and try again. ) : null} @@ -161,7 +161,7 @@ const styles = StyleSheet.create({ padding: 12, gap: 8, }, - title: { ...typography.heading, color: palette.warm }, + title: { ...typography.heading, color: palette.ink }, action: { ...typography.body, color: palette.ink }, commandBox: { backgroundColor: palette.background, borderRadius: radius.sm }, command: { @@ -190,6 +190,7 @@ const styles = StyleSheet.create({ primaryLabel: { color: palette.background }, buttonLabel: { ...typography.control, color: palette.ink, textAlign: "center" }, reject: { color: palette.danger }, + error: { ...typography.body, color: palette.danger }, disabled: { opacity: 0.5 }, detailsButton: { minHeight: 44, justifyContent: "center" }, }); diff --git a/apps/mobile/src/screens/session-transcript.test.tsx b/apps/mobile/src/screens/session-transcript.test.tsx index a852846..a4d3f55 100644 --- a/apps/mobile/src/screens/session-transcript.test.tsx +++ b/apps/mobile/src/screens/session-transcript.test.tsx @@ -5,7 +5,7 @@ import * as Clipboard from "expo-clipboard"; import { Alert, Linking, View } from "react-native"; import { resetTranscriptPerformanceMetrics } from "../state/transcript-performance"; -import { markdownPalette, palette } from "../theme"; +import { markdownPalette, palette, typography } from "../theme"; import { activitySummary, buildTranscriptPresentation, @@ -17,6 +17,25 @@ import { afterEach(resetTranscriptPerformanceMetrics); +test("assistant prose uses the shared body typography and text color", async () => { + const message = messages.find((item) => item.type === "assistant"); + if (!message) throw new Error("fixture"); + render( + , + ); + let text = screen.getByText("Themed response"); + while (!text.props.selectable && text.parent) text = text.parent; + expect(text).toHaveStyle({ + ...typography.body, + color: palette.ink, + }); + await waitFor(() => + expect(screen.getByRole("button", { name: "Copy response" })).toBeOnTheScreen(), + ); +}); + jest.mock("@opencode2-mobile/opencode-adapter", () => ({ getOpenCodeShell: jest.fn(), getOpenCodeShellOutput: jest.fn(), @@ -747,7 +766,7 @@ test("shows the responding model display name and measured runtime in the footer }} />, ); - expect(screen.getByText("Plan · Model One · 28s")).toHaveStyle({ fontSize: 14, lineHeight: 20 }); + expect(screen.getByText("Plan · Model One · 28s")).toHaveStyle(typography.caption); }); test("renders fenced assistant code without markdown fence markers", () => { diff --git a/apps/mobile/src/screens/session-transcript.tsx b/apps/mobile/src/screens/session-transcript.tsx index 73893c8..2c532e2 100644 --- a/apps/mobile/src/screens/session-transcript.tsx +++ b/apps/mobile/src/screens/session-transcript.tsx @@ -1809,7 +1809,7 @@ const styles = StyleSheet.create({ paddingHorizontal: 0, }, loadedSkill: { minHeight: 44, justifyContent: "center", paddingVertical: 4 }, - activityAction: { color: palette.dim, fontSize: 14 }, + activityAction: { ...typography.control, color: palette.dim }, activityError: { borderBottomWidth: 0, marginHorizontal: -space.sm, @@ -1827,7 +1827,7 @@ const styles = StyleSheet.create({ minWidth: 0, }, activityCopyLargeText: { alignItems: "flex-start", flexDirection: "column", gap: 2 }, - activityDetail: { color: palette.dim, flexShrink: 1, fontSize: 14, lineHeight: 18 }, + activityDetail: { ...typography.caption, color: palette.dim, flexShrink: 1 }, activityFile: { ...typography.code, borderTopColor: palette.border, @@ -1845,14 +1845,14 @@ const styles = StyleSheet.create({ paddingVertical: 8, }, activityHeaderLargeText: { alignItems: "flex-start", flexDirection: "column" }, - activityLabel: { color: palette.ink, fontSize: 14, lineHeight: 20, fontWeight: "500" }, - activitySummary: { color: palette.dim, fontSize: 14, lineHeight: 20, flexShrink: 1 }, + activityLabel: { ...typography.control, color: palette.ink }, + activitySummary: { ...typography.caption, color: palette.dim, flexShrink: 1 }, activityNested: { marginLeft: 0 }, compactActivity: { paddingLeft: space.sm, gap: 2 }, toolDetails: { paddingLeft: 0 }, responseFooter: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", gap: space.xs }, activityStandalone: { marginHorizontal: space.md, paddingVertical: space.xs }, - assistantFooter: { color: palette.dim, fontSize: 14, lineHeight: 20, flexShrink: 1 }, + assistantFooter: { ...typography.caption, color: palette.dim, flexShrink: 1 }, assistantRow: { gap: space.xs, paddingHorizontal: space.md, @@ -1867,13 +1867,13 @@ const styles = StyleSheet.create({ paddingHorizontal: 9, paddingVertical: 5, }, - attachmentLabel: { color: palette.dim, fontSize: 11, fontWeight: "600" }, + attachmentLabel: { ...typography.label, color: palette.dim }, attachments: { flexDirection: "row", flexWrap: "wrap", gap: space.xs, marginTop: space.sm }, - bodyText: { color: palette.ink, fontSize: 16, lineHeight: 23 }, + bodyText: { ...typography.body, color: palette.ink }, disclosure: { backgroundColor: "transparent", }, - disclosureAction: { color: palette.dim, fontSize: 14 }, + disclosureAction: { ...typography.control, color: palette.dim }, disclosureActionLargeText: { alignSelf: "flex-start" }, disclosureHeader: { alignItems: "center", @@ -1887,11 +1887,10 @@ const styles = StyleSheet.create({ }, disclosureHeaderLargeText: { alignItems: "flex-start", flexDirection: "column" }, disclosureLabel: { + ...typography.label, color: palette.dim, flex: 1, flexShrink: 1, - fontSize: 12, - fontWeight: "500", minWidth: 0, }, disclosureLabelLargeText: { flex: 0, width: "100%" }, @@ -1901,16 +1900,16 @@ const styles = StyleSheet.create({ minHeight: 44, paddingRight: space.md, }, - diffActionLabel: { color: palette.signal, fontSize: 14, fontWeight: "700" }, - errorText: { color: palette.danger, fontSize: 14, lineHeight: 21 }, + diffActionLabel: { ...typography.control, color: palette.signal }, + errorText: { ...typography.body, color: palette.danger }, linkText: { color: markdownPalette.linkText, textDecorationLine: "underline" }, notice: { marginHorizontal: space.md, paddingVertical: space.xs, }, noticeLabel: { ...typography.label, color: palette.dim }, - noticeText: { color: palette.dim, fontSize: 14, lineHeight: 19, marginTop: 5 }, - omittedText: { color: palette.dim, fontSize: 11, marginTop: 7 }, + noticeText: { ...typography.body, color: palette.dim, marginTop: 5 }, + omittedText: { ...typography.caption, color: palette.dim, marginTop: 7 }, outputText: { ...typography.code, color: palette.dim, @@ -1921,8 +1920,8 @@ const styles = StyleSheet.create({ ...typography.label, color: markdownPalette.reasoning, }, - reasoningText: { color: palette.dim, fontSize: 14, lineHeight: 19 }, - statusText: { color: palette.dim, fontSize: 12 }, + reasoningText: { ...typography.body, color: markdownPalette.reasoning }, + statusText: { ...typography.caption, color: palette.dim }, subagent: { backgroundColor: palette.card, borderColor: palette.activity, @@ -1942,9 +1941,9 @@ const styles = StyleSheet.create({ paddingVertical: 8, }, subagentAction: { justifyContent: "center", minHeight: 44, paddingRight: space.md }, - subagentActionLabel: { color: palette.signal, fontSize: 14, fontWeight: "700" }, + subagentActionLabel: { ...typography.control, color: palette.signal }, subagentActions: { flexDirection: "row", flexWrap: "wrap" }, - subagentAgent: { color: palette.dim, fontSize: 12 }, + subagentAgent: { ...typography.caption, color: palette.dim }, subagentHeading: { alignItems: "center", flexDirection: "row", @@ -1955,19 +1954,18 @@ const styles = StyleSheet.create({ subagentHeadingLargeText: { alignItems: "flex-start", flexDirection: "column", gap: space.xs }, subagentLabel: { ...typography.label, color: palette.activity }, subagentResult: { + ...typography.body, borderTopColor: palette.border, borderTopWidth: StyleSheet.hairlineWidth, color: palette.ink, - fontSize: 14, - lineHeight: 21, paddingTop: space.sm, }, subagentState: { ...typography.label, color: palette.dim }, subagentStateError: { color: palette.danger }, subagentStateRunning: { color: palette.activity }, - subagentTitle: { color: palette.ink, fontSize: 16, fontWeight: "700" }, + subagentTitle: { ...typography.heading, color: palette.ink }, textAction: { alignSelf: "flex-start", minHeight: 44, paddingVertical: 10 }, - textActionLabel: { color: palette.signal, fontSize: 12, fontWeight: "700" }, + textActionLabel: { ...typography.label, color: palette.signal }, userBubble: { backgroundColor: palette.prompt, borderRadius: radius.sm, @@ -1979,5 +1977,5 @@ const styles = StyleSheet.create({ // Supply intrinsic text width to Yoga; the native selection view supplies height. textWidthMeasurement: { height: 0, overflow: "hidden", opacity: 0 }, userRow: { alignItems: "flex-end", paddingHorizontal: space.md, paddingVertical: space.md }, - userText: { color: palette.ink, fontSize: 16, lineHeight: 23 }, + userText: { ...typography.body, color: palette.ink }, });