fix(app): preserve persisted theme styles after startup
Module-level style composites could materialize the temporary adaptive theme before persisted settings loaded. Keep Unistyles reads in render and guard against eager module-scope access.
This commit is contained in:
@@ -230,7 +230,6 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const SEARCH_INPUT_STYLE = [styles.searchInput, isWeb && { outlineStyle: "none" }];
|
||||
const WEB_EXIT_DURATION_MS = 160;
|
||||
|
||||
function SheetBackground({ style }: BottomSheetBackgroundProps) {
|
||||
@@ -373,7 +372,7 @@ export function SheetHeaderView({
|
||||
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||
<AdaptiveTextInput
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={SEARCH_INPUT_STYLE}
|
||||
style={[styles.searchInput, isWeb && { outlineStyle: "none" }]}
|
||||
placeholder={search.placeholder ?? t("common.actions.search")}
|
||||
resetKey={search.resetKey}
|
||||
onChangeText={handleSearchChange}
|
||||
@@ -430,7 +429,7 @@ export function InlineHeaderView({ header }: { header: SheetHeader }) {
|
||||
<Search size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||
<AdaptiveTextInput
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={SEARCH_INPUT_STYLE}
|
||||
style={[styles.searchInput, isWeb && { outlineStyle: "none" }]}
|
||||
placeholder={header.search.placeholder ?? t("common.actions.search")}
|
||||
resetKey={header.search.resetKey}
|
||||
onChangeText={header.search.onChange}
|
||||
|
||||
@@ -544,7 +544,7 @@ export function AgentList({
|
||||
</Text>
|
||||
<View style={styles.sheetButtonRow}>
|
||||
<Pressable
|
||||
style={SHEET_CANCEL_BUTTON_STYLE}
|
||||
style={[styles.sheetButton, styles.sheetCancelButton]}
|
||||
onPress={handleCloseActionSheet}
|
||||
testID="agent-action-cancel"
|
||||
>
|
||||
@@ -552,7 +552,7 @@ export function AgentList({
|
||||
</Pressable>
|
||||
<Pressable
|
||||
disabled={isActionDaemonUnavailable}
|
||||
style={SHEET_ARCHIVE_BUTTON_STYLE}
|
||||
style={[styles.sheetButton, styles.sheetArchiveButton]}
|
||||
onPress={handleArchiveAgent}
|
||||
testID="agent-action-archive"
|
||||
>
|
||||
@@ -789,6 +789,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.base,
|
||||
},
|
||||
}));
|
||||
|
||||
const SHEET_CANCEL_BUTTON_STYLE = [styles.sheetButton, styles.sheetCancelButton];
|
||||
const SHEET_ARCHIVE_BUTTON_STYLE = [styles.sheetButton, styles.sheetArchiveButton];
|
||||
|
||||
@@ -105,7 +105,7 @@ export function DictationControls({
|
||||
<Pressable
|
||||
onPress={onRetry}
|
||||
accessibilityLabel={t("message.dictation.retry")}
|
||||
style={ACTION_CONFIRM_STYLE}
|
||||
style={[styles.actionButton, styles.actionButtonConfirm]}
|
||||
>
|
||||
<RefreshCcw size={theme.iconSize.sm} color={theme.colors.surface0} />
|
||||
</Pressable>
|
||||
@@ -115,14 +115,14 @@ export function DictationControls({
|
||||
<Pressable
|
||||
onPress={onAccept}
|
||||
accessibilityLabel={t("message.dictation.insert")}
|
||||
style={ACTION_SECONDARY_STYLE}
|
||||
style={[styles.actionButton, styles.actionButtonSecondary]}
|
||||
>
|
||||
<Check size={theme.iconSize.sm} color={theme.colors.foreground} />
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={onAcceptAndSend}
|
||||
accessibilityLabel={t("message.dictation.insertAndSend")}
|
||||
style={ACTION_CONFIRM_STYLE}
|
||||
style={[styles.actionButton, styles.actionButtonConfirm]}
|
||||
>
|
||||
<ArrowUp size={theme.iconSize.sm} color={theme.colors.surface0} />
|
||||
</Pressable>
|
||||
@@ -240,7 +240,7 @@ export function DictationOverlay({
|
||||
onPress={onAccept}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={t("message.dictation.insert")}
|
||||
style={OVERLAY_ACCEPT_BUTTON_STYLE}
|
||||
style={[overlayStyles.actionButton, OVERLAY_ACCEPT_BUTTON_BG]}
|
||||
>
|
||||
<Pencil
|
||||
size={theme.iconSize.lg}
|
||||
@@ -400,7 +400,4 @@ const overlayStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const ACTION_CONFIRM_STYLE = [styles.actionButton, styles.actionButtonConfirm];
|
||||
const ACTION_SECONDARY_STYLE = [styles.actionButton, styles.actionButtonSecondary];
|
||||
const OVERLAY_ACCEPT_BUTTON_BG = { backgroundColor: "rgba(255, 255, 255, 0.25)" };
|
||||
const OVERLAY_ACCEPT_BUTTON_STYLE = [overlayStyles.actionButton, OVERLAY_ACCEPT_BUTTON_BG];
|
||||
|
||||
@@ -211,7 +211,7 @@ export function ExplorerSidebar({
|
||||
|
||||
return (
|
||||
<Animated.View style={desktopSidebarStyle}>
|
||||
<View style={DESKTOP_SIDEBAR_BORDER_STYLE}>
|
||||
<View style={[styles.desktopSidebarBorder, { flex: 1 }]}>
|
||||
<SidebarResizeHandle
|
||||
edge="left"
|
||||
gesture={resizeGesture}
|
||||
@@ -516,5 +516,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
minHeight: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const DESKTOP_SIDEBAR_BORDER_STYLE = [styles.desktopSidebarBorder, { flex: 1 }];
|
||||
|
||||
@@ -575,7 +575,7 @@ function FileExplorerPaneContent(props: FileExplorerPaneContentProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={TREE_PANE_CONTAINER_STYLE}>
|
||||
<View style={[styles.treePane, styles.treePaneFill]}>
|
||||
<View style={styles.paneHeader} testID="files-pane-header">
|
||||
<Pressable onPress={handleSortCycle} style={sortTriggerStyleProp}>
|
||||
<Text style={styles.sortTriggerText}>{currentSortLabel}</Text>
|
||||
@@ -1253,5 +1253,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
padding: theme.spacing[4],
|
||||
},
|
||||
}));
|
||||
|
||||
const TREE_PANE_CONTAINER_STYLE = [styles.treePane, styles.treePaneFill];
|
||||
|
||||
@@ -155,7 +155,7 @@ export function ProviderCatalogList({
|
||||
accessibilityLabel={t("providerCatalog.search")}
|
||||
placeholder={t("providerCatalog.search")}
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={SEARCH_INPUT_STYLE}
|
||||
style={[styles.searchInput, isWeb && { outlineStyle: "none" }]}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
/>
|
||||
@@ -284,5 +284,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
}));
|
||||
|
||||
const SEARCH_INPUT_STYLE = [styles.searchInput, isWeb && { outlineStyle: "none" }];
|
||||
|
||||
@@ -225,7 +225,7 @@ function AddCustomModelSubSheet({
|
||||
autoCorrect={false}
|
||||
returnKeyType="done"
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={FORM_INPUT_STYLE}
|
||||
style={[sheetStyles.formInput, isWeb && { outlineStyle: "none" }]}
|
||||
/>
|
||||
{error ? <Text style={sheetStyles.errorText}>{error}</Text> : null}
|
||||
<View style={sheetStyles.formActions}>
|
||||
@@ -437,7 +437,9 @@ function renderProviderSheetFooter({
|
||||
const contentStyle = isCompact ? sheetStyles.compactFooterContent : sheetStyles.footerContent;
|
||||
const actionsStyle = isCompact ? sheetStyles.compactFooterActions : sheetStyles.footerActions;
|
||||
const buttonStyle = isCompact ? sheetStyles.compactFooterButton : null;
|
||||
const metaStyle = isCompact ? COMPACT_FOOTER_META_STYLE : sheetStyles.footerMeta;
|
||||
const metaStyle = isCompact
|
||||
? [sheetStyles.footerMeta, sheetStyles.compactFooterMeta]
|
||||
: sheetStyles.footerMeta;
|
||||
|
||||
return (
|
||||
<View style={contentStyle}>
|
||||
@@ -871,9 +873,6 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const FORM_INPUT_STYLE = [sheetStyles.formInput, isWeb && { outlineStyle: "none" }];
|
||||
const COMPACT_FOOTER_META_STYLE = [sheetStyles.footerMeta, sheetStyles.compactFooterMeta];
|
||||
|
||||
const MAIN_SNAP_POINTS = ["65%", "92%"];
|
||||
const ADD_SNAP_POINTS = ["40%"];
|
||||
const DIAGNOSTIC_SNAP_POINTS = ["50%", "85%"];
|
||||
|
||||
@@ -21,7 +21,10 @@ const webResizeCursorStyle = isWeb
|
||||
export function SidebarResizeHandle({ edge, gesture, testID }: SidebarResizeHandleProps) {
|
||||
const [highlighted, setHighlighted] = useState(false);
|
||||
const highlightTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const hitAreaStyle = edge === "left" ? LEFT_HIT_AREA_STYLE : RIGHT_HIT_AREA_STYLE;
|
||||
const hitAreaStyle =
|
||||
edge === "left"
|
||||
? [styles.hitArea, styles.leftEdge, webResizeCursorStyle]
|
||||
: [styles.hitArea, styles.rightEdge, webResizeCursorStyle];
|
||||
|
||||
const cancelHighlightTimer = useCallback(() => {
|
||||
if (highlightTimerRef.current === null) return;
|
||||
@@ -84,6 +87,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
opacity: 0.25,
|
||||
},
|
||||
}));
|
||||
|
||||
const LEFT_HIT_AREA_STYLE = [styles.hitArea, styles.leftEdge, webResizeCursorStyle];
|
||||
const RIGHT_HIT_AREA_STYLE = [styles.hitArea, styles.rightEdge, webResizeCursorStyle];
|
||||
|
||||
@@ -315,7 +315,9 @@ export function PrBadge({ hint }: { hint: PrHint }) {
|
||||
const handleHoverIn = useCallback(() => setIsHovered(true), []);
|
||||
const handleHoverOut = useCallback(() => setIsHovered(false), []);
|
||||
|
||||
const textStyle = isHovered ? prBadgeTextHoveredCombined : prBadgeStyles.text;
|
||||
const textStyle = isHovered
|
||||
? [prBadgeStyles.text, prBadgeStyles.textHovered]
|
||||
: prBadgeStyles.text;
|
||||
const iconUniProps = isHovered ? foregroundColorMapping : getPrIconUniMapping(hint.state);
|
||||
const presentation = getForgePresentation(normalizeForge(hint.forge));
|
||||
|
||||
@@ -395,8 +397,6 @@ const prBadgeStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const prBadgeTextHoveredCombined = [prBadgeStyles.text, prBadgeStyles.textHovered];
|
||||
|
||||
function StatusDotOverlay({
|
||||
dotColorStyle,
|
||||
size,
|
||||
|
||||
@@ -711,7 +711,7 @@ function ErrorSection({ errorText, ds }: { errorText: string; ds: DetailStyles }
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View style={styles.section}>
|
||||
<Text style={SECTION_TITLE_ERROR_STYLE}>{t("toolCallDetails.error")}</Text>
|
||||
<Text style={[styles.sectionTitle, styles.errorText]}>{t("toolCallDetails.error")}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
@@ -719,7 +719,11 @@ function ErrorSection({ errorText, ds }: { errorText: string; ds: DetailStyles }
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={SCROLL_TEXT_ERROR_STYLE} dataSet={CODE_SURFACE_DATASET}>
|
||||
<Text
|
||||
selectable
|
||||
style={[styles.scrollText, styles.errorText]}
|
||||
dataSet={CODE_SURFACE_DATASET}
|
||||
>
|
||||
{errorText}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
@@ -948,6 +952,3 @@ const styles = StyleSheet.create((theme) => {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const SECTION_TITLE_ERROR_STYLE = [styles.sectionTitle, styles.errorText];
|
||||
const SCROLL_TEXT_ERROR_STYLE = [styles.scrollText, styles.errorText];
|
||||
|
||||
@@ -51,7 +51,7 @@ export function TreeIndentGuides({ depth }: { depth: number }) {
|
||||
/** Rotating disclosure chevron for a directory row (points right; rotates down when expanded). */
|
||||
export function TreeChevron({ expanded }: { expanded: boolean }) {
|
||||
return (
|
||||
<View style={expanded ? CHEVRON_EXPANDED_STYLE : styles.chevron}>
|
||||
<View style={expanded ? [styles.chevron, styles.chevronExpanded] : styles.chevron}>
|
||||
<ThemedChevronRight size={16} uniProps={foregroundMutedIconColorMapping} />
|
||||
</View>
|
||||
);
|
||||
@@ -76,7 +76,3 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
}));
|
||||
|
||||
// Stable module-level style ref so TreeChevron passes a constant array, not one created
|
||||
// per render — satisfies react-perf (no inline-array prop) without a per-render useMemo.
|
||||
const CHEVRON_EXPANDED_STYLE = [styles.chevron, styles.chevronExpanded];
|
||||
|
||||
@@ -199,7 +199,7 @@ export function SearchInput({
|
||||
<AdaptiveTextInput
|
||||
ref={inputRef}
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={SEARCH_INPUT_STYLE}
|
||||
style={[styles.searchInput, IS_WEB && { outlineStyle: "none" }]}
|
||||
placeholder={placeholder}
|
||||
resetKey={resetKey}
|
||||
onChangeText={onChangeText}
|
||||
@@ -212,7 +212,7 @@ export function SearchInput({
|
||||
key={resetKey}
|
||||
ref={inputRef}
|
||||
// @ts-expect-error - outlineStyle is web-only
|
||||
style={SEARCH_INPUT_STYLE}
|
||||
style={[styles.searchInput, IS_WEB && { outlineStyle: "none" }]}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={theme.colors.foregroundMuted}
|
||||
onChangeText={onChangeText}
|
||||
@@ -1711,5 +1711,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
}));
|
||||
|
||||
const SEARCH_INPUT_STYLE = [styles.searchInput, IS_WEB && { outlineStyle: "none" }];
|
||||
|
||||
@@ -418,7 +418,8 @@ function CopyableInfoRow({
|
||||
if (copied || isHovered) {
|
||||
iconUniProps = foregroundColorMapping;
|
||||
}
|
||||
const textStyle = copied || isHovered ? cardInfoTextHoveredCombined : styles.cardInfoText;
|
||||
const textStyle =
|
||||
copied || isHovered ? [styles.cardInfoText, styles.cardInfoTextHovered] : styles.cardInfoText;
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
@@ -506,7 +507,9 @@ function ChecksSummaryContent({
|
||||
const { t } = useTranslation();
|
||||
const { passed, failed, pending } = getChecksSummaryCounts(checks);
|
||||
|
||||
const labelStyle = hovered ? checksSummaryLabelHoveredCombined : styles.checksSummaryLabel;
|
||||
const labelStyle = hovered
|
||||
? [styles.checksSummaryLabel, styles.checksSummaryLabelHovered]
|
||||
: styles.checksSummaryLabel;
|
||||
const iconUniProps = hovered ? foregroundColorMapping : foregroundMutedColorMapping;
|
||||
const icon = getForgePresentation(normalizeForge(forge)).icon;
|
||||
|
||||
@@ -670,10 +673,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.statusSuccess,
|
||||
},
|
||||
}));
|
||||
|
||||
const checksSummaryLabelHoveredCombined = [
|
||||
styles.checksSummaryLabel,
|
||||
styles.checksSummaryLabelHovered,
|
||||
];
|
||||
|
||||
const cardInfoTextHoveredCombined = [styles.cardInfoText, styles.cardInfoTextHovered];
|
||||
|
||||
Reference in New Issue
Block a user