diff --git a/apps/mobile/src/screens/app-shell.tsx b/apps/mobile/src/screens/app-shell.tsx
index 1526a85..596510c 100644
--- a/apps/mobile/src/screens/app-shell.tsx
+++ b/apps/mobile/src/screens/app-shell.tsx
@@ -37,8 +37,7 @@ import { useWorkspaceSelection } from "../state/workspace-selection-context";
import { palette, radius, space, typeRamp, usesLargeTextLayout } from "../theme";
import { AppUpdateCard } from "../updates/app-updates";
import { FormRequestList } from "./form-request-list";
-import { permissionActionExplanation } from "./permission-presentation";
-import { sanitizeTranscriptText } from "./session-transcript-model";
+import { PermissionRequestCard } from "./permission-request-card";
type Section = "Pending" | "Settings" | "Workspace";
type SessionBranch = {
@@ -171,64 +170,13 @@ export function PendingInteractionsScreen({ navigation }: ScreenProps<"Pending">
{selection.permissions.map((request) => (
-
- PERMISSION
- {sanitizeTranscriptText(request.action, 256)}
- {request.resources.map((resource) => (
-
- {sanitizeTranscriptText(resource, 1_024)}
-
- ))}
- {request.save?.map((pattern) => (
-
- Save pattern: {sanitizeTranscriptText(pattern, 1_024)}
-
- ))}
- {permissionActionExplanation(request.action) ? (
-
- OPENCODE MOBILE EXPLANATION
-
- {permissionActionExplanation(request.action)}
-
-
- ) : null}
-
- {request.save && request.save.length > 0
- ? `Always allow may save ${request.save.length} broader permission${request.save.length === 1 ? "" : "s"}. `
- : ""}
- Reject may also reject other pending permission requests in this session.
-
- {selection.permissionReplyError ? (
-
- The permission reply was not accepted. The request has been reloaded.
-
- ) : null}
-
- selection.replyPermission(request.id, request.sessionID, "once")}
- />
-
- selection.replyPermission(request.id, request.sessionID, "always")
- }
- secondary
- />
-
- selection.replyPermission(request.id, request.sessionID, "reject")
- }
- secondary
- />
-
-
+
))}
{selection.forms.length > 0 ? (
{
+ const reply = jest.fn();
+ render(
+ ,
+ );
+ expect(screen.getByText("Run shell command")).toBeOnTheScreen();
+ expect(screen.queryByText(command)).toBeNull();
+ expect(screen.queryByText("python3 *")).toBeNull();
+ fireEvent.press(screen.getByRole("button", { name: "Always allow" }));
+ expect(reply).toHaveBeenCalledWith("per_approval", "ses_owner", "always");
+ fireEvent.press(screen.getByRole("button", { name: "Details" }));
+ expect(screen.getByText(command)).toBeOnTheScreen();
+ expect(screen.getByText("python3 *")).toBeOnTheScreen();
+ expect(
+ screen.getByText(
+ "Always allow saves the displayed patterns, which can cover more than this request.",
+ ),
+ ).toBeOnTheScreen();
+});
+
+test("custom tool permissions use desktop wording and keep raw resources in Details", () => {
+ render(
+ ,
+ );
+ expect(screen.getByText("Permission required")).toBeOnTheScreen();
+ expect(screen.getByText("Call tool mobile-approval-ui-test")).toBeOnTheScreen();
+ expect(screen.queryByText(/python3/)).toBeNull();
+ fireEvent.press(screen.getByRole("button", { name: "Details" }));
+ expect(screen.getByText(command)).toBeOnTheScreen();
+});
+
+test("pending replies disable every choice and errors give a retry path", () => {
+ const view = render(
+ ,
+ );
+ for (const name of ["Allow once", "Always allow", "Reject"])
+ expect(screen.getByRole("button", { name })).toBeDisabled();
+ expect(screen.getByText("Sending permission reply…")).toBeOnTheScreen();
+ view.rerender(
+ ,
+ );
+ expect(screen.getByRole("alert")).toHaveTextContent(
+ "The server did not accept that reply. Review the refreshed request and try again.",
+ );
+ expect(screen.getByRole("button", { name: "Allow once" })).toBeEnabled();
+});
diff --git a/apps/mobile/src/screens/permission-request-card.tsx b/apps/mobile/src/screens/permission-request-card.tsx
new file mode 100644
index 0000000..29d7378
--- /dev/null
+++ b/apps/mobile/src/screens/permission-request-card.tsx
@@ -0,0 +1,188 @@
+import type { PermissionReply, PermissionRequest } from "@opencode2-mobile/opencode-adapter";
+import { useState } from "react";
+import { Platform, Pressable, StyleSheet, Text, useWindowDimensions, View } from "react-native";
+import { ModalSheet } from "../components/modal-sheet";
+import { markdownPalette, palette, radius, space, usesLargeTextLayout } from "../theme";
+import { permissionActionExplanation } from "./permission-presentation";
+import { sanitizeTranscriptText } from "./session-transcript-model";
+
+export function PermissionRequestCard({
+ request,
+ replying,
+ error,
+ onReply,
+}: {
+ request: PermissionRequest;
+ replying: boolean;
+ error: boolean;
+ onReply: (requestID: string, sessionID: string, reply: PermissionReply) => void;
+}) {
+ const [details, setDetails] = useState(false);
+ const { fontScale } = useWindowDimensions();
+ const largeText = usesLargeTextLayout(fontScale);
+ const patterns = request.save ?? [];
+ const explanation = permissionActionExplanation(request.action);
+ const shell = request.action === "shell";
+ const action = shell
+ ? "Run shell command"
+ : `Call tool ${sanitizeTranscriptText(request.action, 256)}`;
+ const actions = (
+
+ {(
+ [
+ ["once", "Allow once"],
+ ["always", "Always allow"],
+ ["reject", "Reject"],
+ ] as const
+ ).map(([reply, label]) => (
+ onReply(request.id, request.sessionID, reply)}
+ style={({ pressed }) => [
+ styles.button,
+ reply === "once" && styles.primary,
+ replying && styles.disabled,
+ pressed && styles.disabled,
+ ]}
+ >
+
+ {label}
+
+
+ ))}
+
+ );
+ const resources = request.resources.map((resource) => (
+
+ {sanitizeTranscriptText(resource, resource.length)}
+
+ ));
+ const scope =
+ patterns.length > 0 ? (
+
+ Always allow saves these patterns for future requests
+
+ {patterns.map((pattern) => (
+
+ {sanitizeTranscriptText(pattern, pattern.length)}
+
+ ))}
+
+
+ ) : null;
+ return (
+
+
+ Permission required
+
+ {action}
+ {shell ? (
+
+
+ {sanitizeTranscriptText(request.resources.join("\n"), 1024)}
+
+
+ ) : null}
+ setDetails(true)}
+ style={styles.detailsButton}
+ >
+ Details ›
+
+ {replying ? (
+
+ Sending permission reply…
+
+ ) : null}
+ {error ? (
+
+ The server did not accept that reply. Review the refreshed request and try again.
+
+ ) : null}
+ {actions}
+ setDetails(false)}>
+ {action}
+ {shell ? "Full command" : "Requested resources"}
+ {resources}
+ {scope}
+ {patterns.length > 0 ? (
+
+ Always allow saves the displayed patterns, which can cover more than this request.
+
+ ) : null}
+ {explanation ? {explanation} : null}
+
+ Reject may also reject other pending permission requests in this session.
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ card: {
+ backgroundColor: palette.card,
+ borderColor: palette.border,
+ borderWidth: 1,
+ borderRadius: radius.lg,
+ padding: 12,
+ gap: 8,
+ },
+ title: { color: palette.warm, fontSize: 16, fontWeight: "600" },
+ action: { color: palette.ink, fontSize: 15, lineHeight: 22 },
+ commandBox: { backgroundColor: palette.background, borderRadius: radius.sm },
+ command: {
+ color: markdownPalette.code,
+ fontFamily: Platform.select({ ios: "Menlo", android: "monospace" }),
+ fontSize: 13,
+ lineHeight: 19,
+ padding: 10,
+ },
+ scope: { gap: 4 },
+ pattern: {
+ color: palette.ink,
+ fontFamily: Platform.select({ ios: "Menlo", android: "monospace" }),
+ fontSize: 13,
+ lineHeight: 19,
+ },
+ caption: { color: palette.dim, fontSize: 12, lineHeight: 18 },
+ copy: { color: palette.dim, fontSize: 15, lineHeight: 22, marginVertical: space.sm },
+ actions: { flexDirection: "row", flexWrap: "wrap", gap: 8 },
+ actionsLarge: { flexDirection: "column" },
+ button: {
+ minHeight: 48,
+ flexGrow: 1,
+ justifyContent: "center",
+ alignItems: "center",
+ borderColor: palette.border,
+ borderWidth: 1,
+ borderRadius: radius.md,
+ padding: 10,
+ },
+ primary: { backgroundColor: palette.signal },
+ primaryLabel: { color: palette.background },
+ buttonLabel: { color: palette.ink, fontSize: 14, fontWeight: "600" },
+ reject: { color: palette.danger },
+ disabled: { opacity: 0.5 },
+ detailsButton: { minHeight: 44, justifyContent: "center" },
+});
diff --git a/apps/mobile/src/screens/session-composer.tsx b/apps/mobile/src/screens/session-composer.tsx
index b225b3a..1330140 100644
--- a/apps/mobile/src/screens/session-composer.tsx
+++ b/apps/mobile/src/screens/session-composer.tsx
@@ -240,7 +240,10 @@ export function SessionComposer({
styles.inputExpanded,
{ minHeight: minimumInputHeight, maxHeight: maximumInputHeight },
]
- : styles.inputCollapsed,
+ : [
+ styles.inputCollapsed,
+ { paddingVertical: Math.max(0, (42 - 23 * fontScale) / 2) },
+ ],
]}
submitBehavior="newline"
textAlignVertical={expanded ? "top" : "center"}
@@ -829,6 +832,7 @@ const styles = StyleSheet.create({
},
surfaceCollapsed: {
borderRadius: 999,
+ justifyContent: "center",
minHeight: 60,
paddingLeft: 12,
paddingRight: 8,
diff --git a/apps/mobile/src/screens/session-execution-panel.test.tsx b/apps/mobile/src/screens/session-execution-panel.test.tsx
index cd5ed0a..63c53c4 100644
--- a/apps/mobile/src/screens/session-execution-panel.test.tsx
+++ b/apps/mobile/src/screens/session-execution-panel.test.tsx
@@ -160,7 +160,7 @@ test("shows and replies to a permission blocking the current session", () => {
);
expect(screen.getByText("Waiting for permission")).toBeOnTheScreen();
- expect(screen.getByText("PERMISSION REQUIRED")).toBeOnTheScreen();
+ expect(screen.getByText("Run shell command")).toBeOnTheScreen();
expect(screen.getByText("pnpm test")).toBeOnTheScreen();
fireEvent.press(screen.getByRole("button", { name: "Allow once" }));
expect(callbacks.onReplyPermission).toHaveBeenCalledWith("per_test", "ses_test", "once");
diff --git a/apps/mobile/src/screens/session-execution-panel.tsx b/apps/mobile/src/screens/session-execution-panel.tsx
index 3139875..74b1e98 100644
--- a/apps/mobile/src/screens/session-execution-panel.tsx
+++ b/apps/mobile/src/screens/session-execution-panel.tsx
@@ -4,11 +4,11 @@ import type {
SessionInboxInfo,
} from "@opencode2-mobile/opencode-adapter";
import type { ReactNode } from "react";
-import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
+import { Pressable, ScrollView, StyleSheet, Text, useWindowDimensions, View } from "react-native";
import { WorkingIndicator } from "../components/working-indicator";
import { palette, radius, space, typeRamp } from "../theme";
-import { permissionActionExplanation } from "./permission-presentation";
+import { PermissionRequestCard } from "./permission-request-card";
import {
type PromptAdmission,
promptAdmissionLabel,
@@ -51,6 +51,7 @@ export function SessionExecutionPanel({
projectedMessageIds: Set;
replyingPermissionId?: string | undefined;
}) {
+ const { height } = useWindowDimensions();
const inboxIds = new Set(inbox.map((item) => item.id));
const localOverlays = admissions.filter(
(admission) =>
@@ -72,7 +73,7 @@ export function SessionExecutionPanel({
accessibilityLabel="Session execution"
contentContainerStyle={styles.content}
keyboardShouldPersistTaps="handled"
- style={styles.shell}
+ style={[styles.shell, permissions.length > 0 && { maxHeight: height * 0.65 }]}
>
{active ? (
@@ -109,84 +110,15 @@ export function SessionExecutionPanel({
) : null}
- {permissions.map((request) => {
- const replying = replyingPermissionId === request.id;
- const explanation = permissionActionExplanation(request.action);
- return (
-
-
- PERMISSION REQUIRED
-
-
- {sanitizeTranscriptText(request.action, 256)}
-
- {request.resources.map((resource) => (
-
- {sanitizeTranscriptText(resource, 1_024)}
-
- ))}
- {request.save?.map((pattern) => (
-
- Save pattern: {sanitizeTranscriptText(pattern, 1_024)}
-
- ))}
- {explanation ? (
-
-
- OPENCODE MOBILE EXPLANATION
-
-
- {explanation}
-
-
- ) : null}
- {request.save && request.save.length > 0 ? (
-
- Always allow may save {request.save.length} broader permission
- {request.save.length === 1 ? "" : "s"}. Reject may also reject other pending
- permission requests in this session.
-
- ) : (
-
- Reject may also reject other pending permission requests in this session.
-
- )}
- {permissionReplyError ? (
-
- The permission reply was not accepted. The request has been reloaded.
-
- ) : null}
-
- onReplyPermission(request.id, request.sessionID, "once")}
- />
- onReplyPermission(request.id, request.sessionID, "always")}
- />
- onReplyPermission(request.id, request.sessionID, "reject")}
- />
-
-
- );
- })}
+ {permissions.map((request) => (
+
+ ))}
{formRequests}
@@ -347,31 +279,6 @@ const styles = StyleSheet.create({
inboxHeading: { alignItems: "center", flexDirection: "row", justifyContent: "space-between" },
inboxType: { color: palette.dim, fontSize: 11, fontWeight: "700" },
pressed: { opacity: 0.62 },
- permissionAction: { color: palette.ink, fontSize: 16, fontWeight: "800" },
- permissionCard: {
- backgroundColor: palette.background,
- borderColor: palette.warm,
- borderRadius: radius.md,
- borderWidth: 1,
- gap: space.sm,
- padding: space.md,
- },
- permissionError: { color: palette.danger, fontSize: 13, lineHeight: 18 },
- permissionExplanation: { gap: 2 },
- permissionExplanationLabel: {
- color: palette.dim,
- fontSize: 10,
- fontWeight: "800",
- letterSpacing: 0.7,
- },
- permissionResource: {
- backgroundColor: palette.background,
- borderRadius: radius.sm,
- color: palette.ink,
- fontSize: 13,
- padding: space.sm,
- },
- permissionWarning: { color: palette.warm, fontSize: 12, lineHeight: 17 },
promptPreview: { color: palette.ink, fontSize: 14, lineHeight: 20 },
content: { gap: space.sm, paddingHorizontal: space.md, paddingVertical: space.xs },
shell: { flexGrow: 0, flexShrink: 1, maxHeight: 280 },
diff --git a/apps/mobile/src/screens/session-transcript.test.tsx b/apps/mobile/src/screens/session-transcript.test.tsx
index 9710dd3..2f68999 100644
--- a/apps/mobile/src/screens/session-transcript.test.tsx
+++ b/apps/mobile/src/screens/session-transcript.test.tsx
@@ -5,10 +5,58 @@ import { Alert, Linking, View } from "react-native";
import { resetTranscriptPerformanceMetrics } from "../state/transcript-performance";
import { markdownPalette, palette } from "../theme";
-import { groupTranscriptMessages, SessionTranscriptRow } from "./session-transcript";
+import {
+ activitySummary,
+ groupTranscriptMessages,
+ SessionTranscriptRow,
+} from "./session-transcript";
afterEach(resetTranscriptPerformanceMetrics);
+test("inline code preserves literal markup and does not create links inside code", () => {
+ const message = messages.find((item) => item.type === "assistant");
+ if (!message) throw new Error("fixture");
+ render(
+ ,
+ );
+ expect(screen.getByText("pnpm check")).toHaveStyle({
+ color: markdownPalette.code,
+ fontFamily: "monospace",
+ });
+ expect(screen.getByText("**literal**")).toBeOnTheScreen();
+ expect(screen.queryByRole("link", { name: "https://code.test" })).toBeNull();
+ expect(screen.getByText("Important")).toHaveStyle({ fontWeight: "700" });
+});
+
+test("activity summaries count operations rather than inventing file counts", () => {
+ const message = messages.find((item) => item.type === "assistant");
+ if (!message) throw new Error("fixture");
+ const tool = message.content.find((part) => part.type === "tool");
+ if (!tool) throw new Error("fixture");
+ expect(
+ activitySummary([
+ {
+ ...message,
+ content: ["glob", "grep", "shell", "patch"].map((name, index) => ({
+ ...tool,
+ name,
+ id: `tool_${index}`,
+ })),
+ },
+ ]),
+ ).toBe("2 lookups · 1 command · 1 edit");
+});
+
test("cross-message grouping respects replies, errors, reasoning visibility and detailed mode", () => {
const original = messages.find((message) => message.type === "assistant");
if (!original) throw new Error("fixture");
diff --git a/apps/mobile/src/screens/session-transcript.tsx b/apps/mobile/src/screens/session-transcript.tsx
index 3bcd4ca..4538799 100644
--- a/apps/mobile/src/screens/session-transcript.tsx
+++ b/apps/mobile/src/screens/session-transcript.tsx
@@ -91,13 +91,40 @@ export function groupTranscriptMessages(
return result;
}
+export function activitySummary(messages: SessionMessageInfo[]) {
+ const counts = new Map();
+ for (const message of messages) {
+ if (message.type !== "assistant") continue;
+ for (const part of message.content) {
+ if (part.type !== "tool") continue;
+ const category = toolCategory(part);
+ const label =
+ category === "exploration"
+ ? "lookup"
+ : category === "shell"
+ ? "command"
+ : category === "edit"
+ ? "edit"
+ : category === "skill"
+ ? "skill call"
+ : "tool call";
+ counts.set(label, (counts.get(label) ?? 0) + 1);
+ }
+ }
+ return [...counts]
+ .map(([label, count]) => `${count} ${label}${count === 1 ? "" : "s"}`)
+ .join(" · ");
+}
+
export function TranscriptActivityGroup({
+ waitingFor,
item,
largeText,
showReasoning,
onOpenDiff,
onOpenSubagent,
}: {
+ waitingFor?: "permission" | "input" | undefined;
item: Extract;
largeText: boolean;
showReasoning: boolean;
@@ -106,16 +133,18 @@ export function TranscriptActivityGroup({
}) {
const [expanded, setExpanded] = useState(false);
return (
-
+
setExpanded((value) => !value)}
style={styles.activityGroupHeader}
>
-
- {item.running ? "Working" : "Activity"} · {item.count} tool calls
+
+ {item.running ? (waitingFor ? `Waiting for ${waitingFor} · ` : "Working · ") : ""}
+ {activitySummary(item.messages)}
{expanded ? "Hide" : "Show"}
@@ -577,7 +606,7 @@ function ToolDisclosure({
))
: null}
{expanded && presentation.command ? (
-
+
{`$ ${presentation.command}`}
) : null}
@@ -692,7 +721,7 @@ function ShellDisclosure({ largeText, message }: { largeText: boolean; message:
onPress={() => setExpanded((current) => !current)}
/>
{expanded ? (
-
+
{`$ ${message.command}`}
) : null}
@@ -1007,13 +1036,19 @@ function InlineMarkdownText({
return (
{prefix ? {`${prefix} `} : null}
- {splitBoldText(text).map((token) => (
-
- ))}
+ {splitInlineText(text).map((token) =>
+ token.code ? (
+
+ {token.text}
+
+ ) : (
+
+ ),
+ )}
);
}
@@ -1136,6 +1171,28 @@ function splitBoldText(text: string) {
return tokens;
}
+function splitInlineText(text: string) {
+ const tokens: { bold: boolean; code: boolean; key: string; text: string }[] = [];
+ const pattern = /(`+)([\s\S]*?)\1(?!`)/g;
+ let cursor = 0;
+ const prose = (value: string) => {
+ for (const token of splitBoldText(value))
+ tokens.push({ ...token, code: false, key: `inline:${tokens.length}` });
+ };
+ for (const match of text.matchAll(pattern)) {
+ prose(text.slice(cursor, match.index));
+ tokens.push({
+ bold: false,
+ code: true,
+ key: `inline:${tokens.length}`,
+ text: (match[2] ?? "").replace(/\n/g, " "),
+ });
+ cursor = match.index + match[0].length;
+ }
+ prose(text.slice(cursor));
+ return tokens;
+}
+
function splitCodeBlocks(text: string): MarkdownBlock[] {
const blocks: MarkdownBlock[] = [];
const prose: string[] = [];
@@ -1456,6 +1513,26 @@ function keyDisclosureText(entries: string[]) {
}
const styles = StyleSheet.create({
+ activityGroup: {
+ backgroundColor: palette.card,
+ borderRadius: radius.md,
+ marginHorizontal: space.md,
+ marginVertical: space.xs,
+ overflow: "hidden",
+ },
+ inlineCode: {
+ backgroundColor: palette.raised,
+ color: markdownPalette.code,
+ fontFamily: "monospace",
+ },
+ commandText: {
+ backgroundColor: palette.raised,
+ color: markdownPalette.code,
+ fontFamily: "monospace",
+ fontSize: 13,
+ lineHeight: 20,
+ padding: 12,
+ },
activityGroupHeader: {
alignItems: "center",
flexDirection: "row",
@@ -1467,6 +1544,9 @@ const styles = StyleSheet.create({
paddingVertical: space.sm,
},
activity: {
+ backgroundColor: palette.card,
+ borderRadius: radius.md,
+ paddingHorizontal: space.sm,
borderBottomColor: palette.border,
borderBottomWidth: StyleSheet.hairlineWidth,
},
@@ -1600,7 +1680,12 @@ const styles = StyleSheet.create({
padding: 12,
},
pressed: { opacity: 0.7 },
- reasoningLabel: { color: palette.dim, fontSize: 9, fontWeight: "900", letterSpacing: 0.8 },
+ reasoningLabel: {
+ color: markdownPalette.reasoning,
+ fontSize: 11,
+ fontWeight: "700",
+ letterSpacing: 0.4,
+ },
reasoningText: { color: palette.dim, fontSize: 13, lineHeight: 19 },
statusText: { color: palette.dim, fontSize: 12 },
subagent: {
diff --git a/apps/mobile/src/screens/workspace-screen.integration.test.tsx b/apps/mobile/src/screens/workspace-screen.integration.test.tsx
index 58a7ac9..3c375dc 100644
--- a/apps/mobile/src/screens/workspace-screen.integration.test.tsx
+++ b/apps/mobile/src/screens/workspace-screen.integration.test.tsx
@@ -323,7 +323,7 @@ test("shows a permission blocking the open session and can reply", async () => {
);
try {
- expect(await screen.findByText("PERMISSION REQUIRED")).toBeOnTheScreen();
+ expect(await screen.findByText("Run shell command")).toBeOnTheScreen();
expect(screen.getByLabelText("Keyboard composer dock")).toBeOnTheScreen();
fireEvent.press(screen.getByRole("button", { name: "Allow once" }));
expect(mockReplyPermission).toHaveBeenCalledWith("per_test", "ses_transcript", "once");
diff --git a/apps/mobile/src/screens/workspace-screen.tsx b/apps/mobile/src/screens/workspace-screen.tsx
index 24c7877..db38941 100644
--- a/apps/mobile/src/screens/workspace-screen.tsx
+++ b/apps/mobile/src/screens/workspace-screen.tsx
@@ -987,6 +987,13 @@ export function SessionScreen({ navigation, route }: SessionProps) {
renderItem={({ item }) =>
item.type === "activity-group" ? (
0
+ ? "permission"
+ : sessionForms.length > 0
+ ? "input"
+ : undefined
+ }
item={item}
largeText={largeText}
showReasoning={transcriptPreferences.reasoning}
diff --git a/apps/mobile/src/theme.ts b/apps/mobile/src/theme.ts
index cbc1e2a..fd607d2 100644
--- a/apps/mobile/src/theme.ts
+++ b/apps/mobile/src/theme.ts
@@ -27,6 +27,8 @@ export const diffPalette = {
} as const;
export const markdownPalette = {
+ code: "#a8d5ba",
+ reasoning: "#c4b5fd",
link: "#8ab4f8",
linkText: "#8ab4f8",
strong: "#eeeeee",