mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
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:
@@ -87,6 +87,9 @@
|
||||
{
|
||||
"files": ["packages/app/src/**/*.{ts,tsx}"],
|
||||
"rules": {
|
||||
// React Native style arrays must read Unistyles proxies during render. Hoisting them to
|
||||
// satisfy this allocation rule captures the temporary startup theme instead.
|
||||
"react-perf/jsx-no-new-array-as-prop": "off",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
|
||||
@@ -58,6 +58,30 @@ For standard React Native components, the [Unistyles Babel plugin](https://www.u
|
||||
|
||||
The important detail: the automatic native path tracks `props.style`. It does not generally track every prop that happens to carry style-like values.
|
||||
|
||||
### Do Not Materialize Styles At Module Scope
|
||||
|
||||
Never read a Unistyles style property into a module-level constant. This includes cached arrays:
|
||||
|
||||
```tsx
|
||||
// Wrong: evaluated while the app may still be using the temporary system theme.
|
||||
const ROW_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
|
||||
// Right: each style proxy is read when this view renders.
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]} />;
|
||||
```
|
||||
|
||||
Paseo starts with adaptive themes, then applies the persisted theme after async settings load. A
|
||||
module-level read can therefore materialize the light style before a persisted dark theme is
|
||||
active. If the view mounts after that theme change, React Native receives the stale light object;
|
||||
Unistyles registers the node for future changes but does not retroactively replace its initial
|
||||
props. Settings dividers once rendered light `#e4e4e7` inside a dark `#252B2A` card for exactly
|
||||
this reason.
|
||||
|
||||
Render-time array syntax is intentional and exempt from the app's JSX array-allocation lint rule.
|
||||
Keep the entries separate so each retains its Unistyles metadata. If composition is needed outside
|
||||
JSX, create the array inside the component or in a `useMemo` that first runs when the component
|
||||
mounts—never at module evaluation time.
|
||||
|
||||
[`useUnistyles()`](https://www.unistyl.es/v3/references/use-unistyles) is different. It gives React access to the current theme/runtime and can make a component re-render when those values change. Use it for values that must be rendered through React props, such as icon colors or small escape hatches. Do not expect direct reads from `UnistylesRuntime` to re-render a component; [issue #817](https://github.com/jpudysz/react-native-unistyles/issues/817) is a useful reminder of that invariant.
|
||||
|
||||
## Dynamic Pixel Styles On Web
|
||||
|
||||
@@ -1234,7 +1234,9 @@ function PermissionActionButton({
|
||||
onPress,
|
||||
}: PermissionActionButtonProps) {
|
||||
const handlePress = useCallback(() => onPress(action), [onPress, action]);
|
||||
const optionTextStyle = isPrimary ? optionTextPrimaryStyle : permissionStyles.optionText;
|
||||
const optionTextStyle = isPrimary
|
||||
? [permissionStyles.optionText, permissionStyles.optionTextPrimary]
|
||||
: permissionStyles.optionText;
|
||||
const colorMapping = isPrimary ? primaryColorMapping : mutedColorMapping;
|
||||
return (
|
||||
<Pressable testID={testID} style={pressableStyle} onPress={handlePress} disabled={isResponding}>
|
||||
@@ -1622,8 +1624,6 @@ const permissionStyles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const optionTextPrimaryStyle = [permissionStyles.optionText, permissionStyles.optionTextPrimary];
|
||||
|
||||
interface StreamItemWrapperProps {
|
||||
gapBelow: number;
|
||||
children: ReactNode;
|
||||
|
||||
@@ -257,10 +257,10 @@ export default function PairScanScreen() {
|
||||
/>
|
||||
<View style={styles.overlay} pointerEvents="none">
|
||||
<View style={styles.scanFrame}>
|
||||
<View style={CORNER_TL_STYLE} />
|
||||
<View style={CORNER_TR_STYLE} />
|
||||
<View style={CORNER_BL_STYLE} />
|
||||
<View style={CORNER_BR_STYLE} />
|
||||
<View style={[styles.corner, styles.cornerTL]} />
|
||||
<View style={[styles.corner, styles.cornerTR]} />
|
||||
<View style={[styles.corner, styles.cornerBL]} />
|
||||
<View style={[styles.corner, styles.cornerBR]} />
|
||||
</View>
|
||||
{isPairing ? <Text style={helperTextStyle}>{t("pairing.scan.pairing")}</Text> : null}
|
||||
</View>
|
||||
@@ -272,7 +272,3 @@ export default function PairScanScreen() {
|
||||
}
|
||||
|
||||
const BARCODE_SCANNER_SETTINGS: BarcodeSettings = { barcodeTypes: ["qr"] };
|
||||
const CORNER_TL_STYLE = [styles.corner, styles.cornerTL];
|
||||
const CORNER_TR_STYLE = [styles.corner, styles.cornerTR];
|
||||
const CORNER_BL_STYLE = [styles.corner, styles.cornerBL];
|
||||
const CORNER_BR_STYLE = [styles.corner, styles.cornerBR];
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -570,7 +570,7 @@ function QueuedMessageRow({
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={handleSendNow}
|
||||
style={QUEUE_SEND_BUTTON_STYLE}
|
||||
style={[styles.queueActionButton, styles.queueSendButton]}
|
||||
accessibilityLabel={sendNowLabel}
|
||||
accessibilityRole="button"
|
||||
>
|
||||
@@ -2237,8 +2237,6 @@ const styles = StyleSheet.create((theme: Theme) => ({
|
||||
},
|
||||
})) as unknown as Record<string, object>;
|
||||
|
||||
const QUEUE_SEND_BUTTON_STYLE = [styles.queueActionButton, styles.queueSendButton];
|
||||
|
||||
const ThemedPencil = withUnistyles(Pencil);
|
||||
const ThemedArrowUp = withUnistyles(ArrowUp);
|
||||
const ThemedGitPullRequest = withUnistyles(GitPullRequest);
|
||||
|
||||
@@ -246,7 +246,7 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
||||
<Text style={styles.valueSubtext}>{daemonStatusDetailText}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("desktop.daemon.management.title")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{t("desktop.daemon.management.hint")}</Text>
|
||||
@@ -258,7 +258,7 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
||||
accessibilityLabel={t("desktop.daemon.management.title")}
|
||||
/>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("desktop.daemon.keepRunning.title")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{t("desktop.daemon.keepRunning.hint")}</Text>
|
||||
@@ -270,7 +270,7 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
||||
accessibilityLabel={t("desktop.daemon.keepRunning.title")}
|
||||
/>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("desktop.daemon.logs.title")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
@@ -294,7 +294,7 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("desktop.daemon.fullStatus.title")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{t("desktop.daemon.fullStatus.hint")}</Text>
|
||||
@@ -420,7 +420,7 @@ export function LocalDaemonSection() {
|
||||
testID="host-page-daemon-lifecycle-card"
|
||||
>
|
||||
{isLoading || isLoadingSettings ? (
|
||||
<View style={LOADING_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.loadingCard]}>
|
||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||
</View>
|
||||
) : (
|
||||
@@ -524,7 +524,5 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const LOADING_CARD_STYLE = [settingsStyles.card, styles.loadingCard];
|
||||
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
const LOGS_MODAL_SNAP_POINTS = ["70%", "92%"];
|
||||
const CLI_STATUS_MODAL_SNAP_POINTS = ["60%", "85%"];
|
||||
|
||||
@@ -19,8 +19,6 @@ import { useCliInstall, useSkillsStatus } from "@/desktop/hooks/use-install-stat
|
||||
|
||||
const CLI_DOCS_URL = "https://paseo.sh/docs/cli";
|
||||
const SKILLS_DOCS_URL = "https://paseo.sh/docs/skills";
|
||||
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
|
||||
const OP_KIND_ORDER: Record<SkillOp["kind"], number> = { add: 0, update: 1, delete: 2 };
|
||||
const OP_KIND_LABEL_KEY: Record<SkillOp["kind"], string> = {
|
||||
add: "settings.integrations.operations.add",
|
||||
@@ -183,7 +181,7 @@ export function IntegrationsSection() {
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<View style={styles.rowTitleRow}>
|
||||
<Blocks size={theme.iconSize.md} color={theme.colors.foreground} />
|
||||
|
||||
@@ -1091,7 +1091,11 @@ export function DiffFileBody({
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={FILE_SECTION_BODY_STYLE} onLayout={handleLayout} testID={testID}>
|
||||
<View
|
||||
style={[styles.fileSectionBodyContainer, styles.fileSectionBorder]}
|
||||
onLayout={handleLayout}
|
||||
testID={testID}
|
||||
>
|
||||
{(() => {
|
||||
if (file.status === "too_large" || file.status === "binary") {
|
||||
return (
|
||||
@@ -1118,7 +1122,7 @@ export function DiffFileBody({
|
||||
if (layout === "split") {
|
||||
const rows = buildSplitDiffRows(file);
|
||||
return (
|
||||
<View style={DIFF_CONTENT_SPLIT_ROW_STYLE} dataSet={CODE_SURFACE_DATASET}>
|
||||
<View style={[styles.diffContent, styles.splitRow]} dataSet={CODE_SURFACE_DATASET}>
|
||||
<SplitDiffColumn
|
||||
rows={rows}
|
||||
side="left"
|
||||
@@ -1172,7 +1176,7 @@ export function DiffFileBody({
|
||||
const textViewportWidth =
|
||||
scrollViewWidth > 0 ? scrollViewWidth : Math.max(0, bodyWidth - gutterWidth);
|
||||
return (
|
||||
<View style={DIFF_CONTENT_ROW_STYLE} dataSet={CODE_SURFACE_DATASET}>
|
||||
<View style={[styles.diffContent, styles.diffContentRow]} dataSet={CODE_SURFACE_DATASET}>
|
||||
<View style={styles.gutterColumn}>
|
||||
{computedLines.map(({ line, lineNumber, key, reviewTarget }, index) => (
|
||||
<View key={key} testID={`diff-gutter-row-${index}`}>
|
||||
@@ -3062,7 +3066,4 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const FILE_SECTION_BODY_STYLE = [styles.fileSectionBodyContainer, styles.fileSectionBorder];
|
||||
const DIFF_CONTENT_SPLIT_ROW_STYLE = [styles.diffContent, styles.splitRow];
|
||||
const DIFF_CONTENT_ROW_STYLE = [styles.diffContent, styles.diffContentRow];
|
||||
const DIFF_HEIGHT_CHANGE_EPSILON = 0.5;
|
||||
|
||||
@@ -55,7 +55,7 @@ function ProviderUsageBody({
|
||||
}) {
|
||||
if (view.kind === "loading") {
|
||||
return (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{providerUsageCopy.loading}</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -73,7 +73,7 @@ function ProviderUsageBody({
|
||||
|
||||
if (view.payload.providers.length === 0) {
|
||||
return (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{providerUsageCopy.empty}</Text>
|
||||
</View>
|
||||
);
|
||||
@@ -92,5 +92,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontSize: theme.fontSize.sm,
|
||||
},
|
||||
}));
|
||||
|
||||
const EMPTY_CARD_STYLE = [settingsStyles.card, styles.emptyCard];
|
||||
|
||||
@@ -210,8 +210,6 @@ function selectedSidebarItemStyle({ hovered }: PressableStateCallbackType & { ho
|
||||
];
|
||||
}
|
||||
|
||||
const ROW_WITH_BORDER_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
|
||||
function getSendBehaviorOptions(t: TFunction) {
|
||||
return [
|
||||
{ value: "interrupt" as const, label: t("settings.general.defaultSend.options.interrupt") },
|
||||
@@ -360,7 +358,7 @@ function GeneralSection({
|
||||
options={sendBehaviorOptions}
|
||||
/>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.general.language.label")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{t("settings.general.language.description")}</Text>
|
||||
@@ -387,7 +385,7 @@ function GeneralSection({
|
||||
</DropdownMenu>
|
||||
</View>
|
||||
{isDesktopApp ? (
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.general.serviceUrls.label")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
@@ -414,7 +412,7 @@ function GeneralSection({
|
||||
</DropdownMenu>
|
||||
</View>
|
||||
) : null}
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>
|
||||
{t("settings.general.terminalScrollback.label")}
|
||||
@@ -700,7 +698,7 @@ function DesktopAppUpdateRow() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.about.releaseChannel.label")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
@@ -714,7 +712,7 @@ function DesktopAppUpdateRow() {
|
||||
options={releaseChannelOptions}
|
||||
/>
|
||||
</View>
|
||||
<View style={ROW_WITH_BORDER_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.about.updates.label")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>{statusText}</Text>
|
||||
|
||||
@@ -212,7 +212,6 @@ function AutoExpandReasoningRow({ value, onChange }: AutoExpandReasoningRowProps
|
||||
);
|
||||
}
|
||||
|
||||
const TOOL_CALL_DETAIL_ROW_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||
const TOOL_CALL_DETAIL_LEVELS: readonly AppSettings["toolCallDetailLevel"][] = [
|
||||
"detailed",
|
||||
"overview",
|
||||
@@ -250,7 +249,7 @@ function ToolCallDetailRow({ value, onChange }: ToolCallDetailRowProps) {
|
||||
const { t } = useTranslation();
|
||||
const selectedLabel = getToolCallDetailLevelLabel(t, value);
|
||||
return (
|
||||
<View style={TOOL_CALL_DETAIL_ROW_STYLE}>
|
||||
<View style={[settingsStyles.row, settingsStyles.rowBorder]}>
|
||||
<View style={settingsStyles.rowContent}>
|
||||
<Text style={settingsStyles.rowTitle}>{t("settings.general.toolCallDetail.label")}</Text>
|
||||
<Text style={settingsStyles.rowHint}>
|
||||
|
||||
@@ -150,7 +150,7 @@ function HostNotFound() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<View>
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{t("settings.host.notFound")}</Text>
|
||||
</View>
|
||||
</View>
|
||||
@@ -276,7 +276,7 @@ export function HostAgentsPage({ serverId }: { serverId: string }) {
|
||||
<AppendSystemPromptCard serverId={serverId} />
|
||||
</SettingsSection>
|
||||
) : (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{t("settings.host.agents.unavailable")}</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -300,7 +300,7 @@ export function HostWorkspacesPage({ serverId }: { serverId: string }) {
|
||||
<AutoArchiveMergedWorkspacesCard serverId={serverId} />
|
||||
</SettingsSection>
|
||||
) : (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{t("settings.host.workspaces.unavailable")}</Text>
|
||||
</View>
|
||||
)}
|
||||
@@ -1940,4 +1940,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
}));
|
||||
|
||||
const FLEX_1_STYLE = { flex: 1 };
|
||||
const EMPTY_CARD_STYLE = [settingsStyles.card, styles.emptyCard];
|
||||
|
||||
@@ -264,7 +264,7 @@ export function KeyboardShortcutsSection() {
|
||||
if (isNative) {
|
||||
return (
|
||||
<SettingsSection title={t("settings.sections.shortcuts")}>
|
||||
<View style={mobileCardStyle}>
|
||||
<View style={[settingsStyles.card, styles.mobileCard]}>
|
||||
<Text style={styles.mobileText}>{t("settings.shortcuts.unavailableOnMobile")}</Text>
|
||||
</View>
|
||||
</SettingsSection>
|
||||
@@ -362,5 +362,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
}));
|
||||
|
||||
const mobileCardStyle = [settingsStyles.card, styles.mobileCard];
|
||||
|
||||
@@ -410,12 +410,12 @@ export function ProvidersSection({ serverId }: ProvidersSectionProps) {
|
||||
style={styles.sectionSpacing}
|
||||
>
|
||||
{!hasServer || !isConnected ? (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{t("settings.providers.unavailable")}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
{hasServer && isConnected && isLoading ? (
|
||||
<View style={EMPTY_CARD_STYLE}>
|
||||
<View style={[settingsStyles.card, styles.emptyCard]}>
|
||||
<Text style={styles.emptyText}>{t("settings.providers.loading")}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
@@ -543,5 +543,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
backgroundColor: theme.colors.surface3,
|
||||
},
|
||||
}));
|
||||
|
||||
const EMPTY_CARD_STYLE = [settingsStyles.card, styles.emptyCard];
|
||||
|
||||
@@ -1165,7 +1165,9 @@ function ResolvedDesktopTabChip({
|
||||
|
||||
return (
|
||||
<View style={styles.tabSlot}>
|
||||
{showDropIndicatorBefore ? <View style={TAB_DROP_INDICATOR_BEFORE_STYLE} /> : null}
|
||||
{showDropIndicatorBefore ? (
|
||||
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorBefore]} />
|
||||
) : null}
|
||||
<TabChip
|
||||
tab={item.tab}
|
||||
isActive={item.isActive}
|
||||
@@ -1184,7 +1186,9 @@ function ResolvedDesktopTabChip({
|
||||
onCloseTab={onCloseTab}
|
||||
dragHandleProps={dragHandleProps}
|
||||
/>
|
||||
{showDropIndicatorAfter ? <View style={TAB_DROP_INDICATOR_AFTER_STYLE} /> : null}
|
||||
{showDropIndicatorAfter ? (
|
||||
<View style={[styles.tabDropIndicator, styles.tabDropIndicatorAfter]} />
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
@@ -1375,6 +1379,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
height: 14,
|
||||
},
|
||||
}));
|
||||
|
||||
const TAB_DROP_INDICATOR_BEFORE_STYLE = [styles.tabDropIndicator, styles.tabDropIndicatorBefore];
|
||||
const TAB_DROP_INDICATOR_AFTER_STYLE = [styles.tabDropIndicator, styles.tabDropIndicatorAfter];
|
||||
|
||||
@@ -3341,7 +3341,7 @@ function WorkspaceScreenContent({
|
||||
[t],
|
||||
);
|
||||
|
||||
const containerStyle = containerWithWorkspaceBackgroundStyle;
|
||||
const containerStyle = [styles.container, styles.containerWorkspaceBackground];
|
||||
|
||||
const menuNewAgentIcon = MENU_NEW_AGENT_ICON;
|
||||
const menuNewTerminalIcon = MENU_NEW_TERMINAL_ICON;
|
||||
@@ -4090,9 +4090,4 @@ const styles = StyleSheet.create((theme) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const containerWithWorkspaceBackgroundStyle = [
|
||||
styles.container,
|
||||
styles.containerWorkspaceBackground,
|
||||
];
|
||||
|
||||
const EXPLORER_TOGGLE_KEYS: ShortcutKey[] = ["mod", "E"];
|
||||
|
||||
@@ -88,7 +88,9 @@ function ScriptActionButtonChildren({
|
||||
) : (
|
||||
<ThemedPlay size={10} uniProps={colorMapping} {...playFillTransparent} />
|
||||
);
|
||||
const labelStyle = hovered ? actionButtonLabelHoveredStyle : styles.actionButtonLabel;
|
||||
const labelStyle = hovered
|
||||
? [styles.actionButtonLabel, styles.actionButtonLabelHovered]
|
||||
: styles.actionButtonLabel;
|
||||
return (
|
||||
<>
|
||||
{iconElement}
|
||||
@@ -156,7 +158,7 @@ function HostLinkChildren({ hovered, disabled, label }: HostLinkChildrenProps):
|
||||
const showIcon = !disabled && (hovered || isNative);
|
||||
const isActive = Boolean(hovered) && !disabled;
|
||||
const colorMapping = isActive ? foregroundColorMapping : mutedColorMapping;
|
||||
const hostLabelStyle = isActive ? hostLabelActiveStyle : styles.hostLabel;
|
||||
const hostLabelStyle = isActive ? [styles.hostLabel, styles.hostLabelActive] : styles.hostLabel;
|
||||
return (
|
||||
<>
|
||||
<Text style={hostLabelStyle} numberOfLines={1}>
|
||||
@@ -207,7 +209,8 @@ function HostLinkRow({ label, url, scriptName, onOpenInBrowserTab }: HostLinkPro
|
||||
|
||||
function ExitCodeBadge({ code }: { code: number }): ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const exitTextStyle = code === 0 ? styles.exitBadgeText : exitBadgeTextErrorStyle;
|
||||
const exitTextStyle =
|
||||
code === 0 ? styles.exitBadgeText : [styles.exitBadgeText, styles.exitBadgeTextError];
|
||||
return (
|
||||
<View style={styles.exitBadge}>
|
||||
<Text style={exitTextStyle}>{t("workspace.scripts.states.exitCode", { code })}</Text>
|
||||
@@ -305,7 +308,7 @@ function ScriptRow({
|
||||
}, [onStartScript, script.scriptName]);
|
||||
|
||||
const scriptNameStyle = useMemo(
|
||||
() => (isRunning ? scriptNameActiveStyle : styles.scriptName),
|
||||
() => (isRunning ? [styles.scriptName, styles.scriptNameActive] : styles.scriptName),
|
||||
[isRunning],
|
||||
);
|
||||
|
||||
@@ -631,8 +634,3 @@ const styles = StyleSheet.create((theme) => ({
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
}));
|
||||
|
||||
const actionButtonLabelHoveredStyle = [styles.actionButtonLabel, styles.actionButtonLabelHovered];
|
||||
const hostLabelActiveStyle = [styles.hostLabel, styles.hostLabelActive];
|
||||
const scriptNameActiveStyle = [styles.scriptName, styles.scriptNameActive];
|
||||
const exitBadgeTextErrorStyle = [styles.exitBadgeText, styles.exitBadgeTextError];
|
||||
|
||||
76
packages/app/src/styles/unistyles-module-scope.test.ts
Normal file
76
packages/app/src/styles/unistyles-module-scope.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import * as ts from "typescript";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const SOURCE_ROOT = path.resolve(__dirname, "..");
|
||||
|
||||
function listSourceFiles(directory: string): string[] {
|
||||
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
||||
const entryPath = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
return listSourceFiles(entryPath);
|
||||
}
|
||||
return /\.tsx?$/.test(entry.name) ? [entryPath] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function containsEagerStyleRead(initializer: ts.Expression): boolean {
|
||||
if (ts.isFunctionLike(initializer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let found = false;
|
||||
|
||||
function visit(node: ts.Node): void {
|
||||
if (node !== initializer && ts.isFunctionLike(node)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
ts.isPropertyAccessExpression(node) &&
|
||||
ts.isIdentifier(node.expression) &&
|
||||
node.expression.text !== "StyleSheet" &&
|
||||
/styles$/i.test(node.expression.text)
|
||||
) {
|
||||
found = true;
|
||||
return;
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
|
||||
visit(initializer);
|
||||
return found;
|
||||
}
|
||||
|
||||
function findEagerModuleStyleReads(filePath: string): string[] {
|
||||
const source = readFileSync(filePath, "utf8");
|
||||
const sourceFile = ts.createSourceFile(
|
||||
filePath,
|
||||
source,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS,
|
||||
);
|
||||
|
||||
return sourceFile.statements.flatMap((statement) => {
|
||||
if (!ts.isVariableStatement(statement)) {
|
||||
return [];
|
||||
}
|
||||
return statement.declarationList.declarations.flatMap((declaration) => {
|
||||
if (!declaration.initializer || !containsEagerStyleRead(declaration.initializer)) {
|
||||
return [];
|
||||
}
|
||||
const line =
|
||||
sourceFile.getLineAndCharacterOfPosition(declaration.getStart(sourceFile)).line + 1;
|
||||
return [`${path.relative(SOURCE_ROOT, filePath)}:${line}`];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describe("Unistyles module scope", () => {
|
||||
it("does not materialize style proxies before the persisted theme loads", () => {
|
||||
const violations = listSourceFiles(SOURCE_ROOT).flatMap(findEagerModuleStyleReads);
|
||||
|
||||
expect(violations).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user