From 7cd7fcdd0ae05d5deb673a6b5667b22f8f58c190 Mon Sep 17 00:00:00 2001 From: Guilherme Albert Date: Thu, 17 Sep 2026 13:05:26 -0300 Subject: [PATCH] fix(ui): restore mobile spacing scale --- src/app/authors/index.tsx | 4 +-- src/app/authors/profile/index.tsx | 4 +-- src/app/communities/index.tsx | 4 +-- src/app/communities/profile/index.tsx | 4 +-- src/app/home/index.tsx | 6 ++-- .../components/jobs-filter-modal/index.tsx | 8 ++--- .../components/jobs-result-toolbar/index.tsx | 2 +- .../jobs-workspace-header/index.tsx | 6 ++-- src/app/jobs/details/index.tsx | 10 +++--- src/app/jobs/index.tsx | 4 +-- src/components/action-button/index.tsx | 2 +- .../analytics-consent-banner/index.tsx | 6 ++-- src/components/app-drawer/index.tsx | 6 ++-- src/components/app-error-boundary/index.tsx | 2 +- src/components/app-header/index.tsx | 2 +- src/components/catalog-state/index.tsx | 2 +- src/components/data-confidence-card/index.tsx | 8 ++--- src/components/detail-header/index.tsx | 2 +- src/components/directory-card/index.tsx | 2 +- src/components/filter-chip/index.tsx | 4 +-- src/components/job-detail-actions/index.tsx | 10 +++--- src/components/new-matches-card/index.tsx | 4 +-- .../notification-consent-prompt/index.tsx | 6 ++-- src/components/opportunity-card/index.tsx | 2 +- .../optional-update-banner/index.tsx | 6 ++-- src/components/preference-popover/index.tsx | 4 +-- src/components/profile-hero/index.tsx | 8 ++--- src/components/screen-header/index.tsx | 2 +- src/components/selection-sheet/index.tsx | 8 ++--- src/components/support-card/index.tsx | 4 +-- tests/contracts/spacing-scale.test.ts | 34 +++++++++++++++++++ 31 files changed, 105 insertions(+), 71 deletions(-) create mode 100644 tests/contracts/spacing-scale.test.ts diff --git a/src/app/authors/index.tsx b/src/app/authors/index.tsx index 2c9a5ff..0cf5d33 100644 --- a/src/app/authors/index.tsx +++ b/src/app/authors/index.tsx @@ -61,9 +61,9 @@ export function AuthorsScreen(): React.ReactNode { } const header = ( - + - + setFilters({ ...filters, query })} diff --git a/src/app/authors/profile/index.tsx b/src/app/authors/profile/index.tsx index 8d1eab3..9b95e1e 100644 --- a/src/app/authors/profile/index.tsx +++ b/src/app/authors/profile/index.tsx @@ -48,12 +48,12 @@ export function AuthorProfileScreen({ handle }: AuthorProfileScreenProps): React subtitle={`@${profile.handle}`} title={profile.name} /> - + void openHttpsUrl(githubUrl)} /> void shareUrl(profile.name, canonical)} /> - {messages.jobs.title} + {messages.jobs.title} {jobs.map((item) => ( + - + setActivity("healthy")} /> setActivity("no-openings")} /> diff --git a/src/app/communities/profile/index.tsx b/src/app/communities/profile/index.tsx index be6c97d..a6fa5f6 100644 --- a/src/app/communities/profile/index.tsx +++ b/src/app/communities/profile/index.tsx @@ -56,12 +56,12 @@ export function CommunityProfileScreen({ repository }: CommunityProfileScreenPro subtitle={profile.repository} title={profile.name} /> - + void openHttpsUrl(profile.repositoryUrl)} /> void shareUrl(profile.name, canonical)} /> - {messages.jobs.title} + {messages.jobs.title} {jobs.length === 0 ? ( ) : jobs.map((item) => ( diff --git a/src/app/home/index.tsx b/src/app/home/index.tsx index acd80a4..05a649d 100644 --- a/src/app/home/index.tsx +++ b/src/app/home/index.tsx @@ -19,7 +19,7 @@ export function HomeScreen(): React.ReactNode { > - + openings.dev @@ -32,13 +32,13 @@ export function HomeScreen(): React.ReactNode { - + {messages.eyebrow} - + {messages.title} diff --git a/src/app/jobs/components/jobs-filter-modal/index.tsx b/src/app/jobs/components/jobs-filter-modal/index.tsx index 570d511..6f241fc 100644 --- a/src/app/jobs/components/jobs-filter-modal/index.tsx +++ b/src/app/jobs/components/jobs-filter-modal/index.tsx @@ -55,7 +55,7 @@ function MultiSection({ if (options.length === 0) return null; return ( - + {label} @@ -151,7 +151,7 @@ export function JobsFilterModal({ visible={open} > - + {messages.jobs.title} @@ -184,10 +184,10 @@ export function JobsFilterModal({ - + + diff --git a/src/app/jobs/components/jobs-workspace-header/index.tsx b/src/app/jobs/components/jobs-workspace-header/index.tsx index 1abc43e..82602e9 100644 --- a/src/app/jobs/components/jobs-workspace-header/index.tsx +++ b/src/app/jobs/components/jobs-workspace-header/index.tsx @@ -149,7 +149,7 @@ export function JobsWorkspaceHeader({ ); return ( - + onChange({ ...filters, query })} @@ -174,7 +174,7 @@ export function JobsWorkspaceHeader({ {activeFilters.length > 0 ? ( - + {activeFilters.map((filter) => { const label = activeLabel(filter, messages); diff --git a/src/app/jobs/details/index.tsx b/src/app/jobs/details/index.tsx index abe20ed..8f17562 100644 --- a/src/app/jobs/details/index.tsx +++ b/src/app/jobs/details/index.tsx @@ -147,7 +147,7 @@ export function JobDetailsScreen({ id }: JobDetailsScreenProps): React.ReactNode contentContainerClassName="gap-8 py-6" contentContainerStyle={{ paddingBottom: dockHeight + 24 }} > - + {messages.jobs.detailsTitle} @@ -213,7 +213,7 @@ export function JobDetailsScreen({ id }: JobDetailsScreenProps): React.ReactNode > {item.title} - + {salary ? ( {description} @@ -266,7 +266,7 @@ export function JobDetailsScreen({ id }: JobDetailsScreenProps): React.ReactNode /> {presentation.supportingTags.length > 0 ? ( - + {presentation.supportingTags.map((tag) => ( 0 ? ( - + {messages.jobs.similar} {similar.map((opportunity) => ( diff --git a/src/app/jobs/index.tsx b/src/app/jobs/index.tsx index a8e8112..fb6749a 100644 --- a/src/app/jobs/index.tsx +++ b/src/app/jobs/index.tsx @@ -111,7 +111,7 @@ export function JobsScreen(): React.ReactNode { ? messages.common.offline : messages.common.sourceError; const header = ( - + item.id} ListHeaderComponent={header} ListEmptyComponent={emptyState} - ListFooterComponent={visibleCount < filtered.length ? setVisibleCount((count) => count + PAGE_SIZE)}>{messages.common.loadMore} : } + ListFooterComponent={visibleCount < filtered.length ? setVisibleCount((count) => count + PAGE_SIZE)}>{messages.common.loadMore} : } maxToRenderPerBatch={8} refreshControl={ void catalog.refresh()} tintColor={theme.colors["primary-deep"]} />} renderItem={({ item }) => { diff --git a/src/components/action-button/index.tsx b/src/components/action-button/index.tsx index 533d131..0169aec 100644 --- a/src/components/action-button/index.tsx +++ b/src/components/action-button/index.tsx @@ -21,7 +21,7 @@ export function ActionButton({ icon, label, onPress, primary }: ActionButtonProp const { theme } = useAppTheme(); const Icon = ACTION_ICONS[icon]; return ( - + {label} diff --git a/src/components/analytics-consent-banner/index.tsx b/src/components/analytics-consent-banner/index.tsx index 89f78c3..fc2985d 100644 --- a/src/components/analytics-consent-banner/index.tsx +++ b/src/components/analytics-consent-banner/index.tsx @@ -44,11 +44,11 @@ export function AnalyticsConsentBanner(): React.ReactNode { {copy.title} {copy.purpose} - - void decide("granted")}> + + void decide("granted")}> {copy.accept} - void decide("denied")}> + void decide("denied")}> {copy.decline} diff --git a/src/components/app-drawer/index.tsx b/src/components/app-drawer/index.tsx index 5076a50..16e0793 100644 --- a/src/components/app-drawer/index.tsx +++ b/src/components/app-drawer/index.tsx @@ -98,7 +98,7 @@ export function AppDrawer({ onClose, visible }: AppDrawerProps): React.ReactNode className="flex-1" style={{ paddingRight: insets.right, paddingTop: insets.top }} > - + - + {navigationItems.map((item) => { const Icon = item.icon; @@ -139,7 +139,7 @@ export function AppDrawer({ onClose, visible }: AppDrawerProps): React.ReactNode diff --git a/src/components/app-error-boundary/index.tsx b/src/components/app-error-boundary/index.tsx index 4128f42..07fbbeb 100644 --- a/src/components/app-error-boundary/index.tsx +++ b/src/components/app-error-boundary/index.tsx @@ -44,7 +44,7 @@ export class AppErrorBoundary extends Component< return ( - + {fallback.title} diff --git a/src/components/app-header/index.tsx b/src/components/app-header/index.tsx index 4fc0264..2bbaf4c 100644 --- a/src/components/app-header/index.tsx +++ b/src/components/app-header/index.tsx @@ -18,7 +18,7 @@ export function AppHeader(): React.ReactNode { return ( <> - + + {pending ? : null} {message} {actionLabel && onAction ? ( diff --git a/src/components/data-confidence-card/index.tsx b/src/components/data-confidence-card/index.tsx index 8d25564..a5ace34 100644 --- a/src/components/data-confidence-card/index.tsx +++ b/src/components/data-confidence-card/index.tsx @@ -59,7 +59,7 @@ export function DataConfidenceCard({ : [{ id: item.id, repository: item.repository, url: item.url }]; return ( - + {copy.title} @@ -70,17 +70,17 @@ export function DataConfidenceCard({ {summary.stale ? ( - + {copy.staleWarning} ) : null} {summary.incomplete ? ( - + {copy.incompleteWarning} ) : null} - + {copy.lastVerified} diff --git a/src/components/detail-header/index.tsx b/src/components/detail-header/index.tsx index c5a33bb..f7e30f6 100644 --- a/src/components/detail-header/index.tsx +++ b/src/components/detail-header/index.tsx @@ -13,7 +13,7 @@ interface DetailHeaderProps { export function DetailHeader(props: DetailHeaderProps): React.ReactNode { const { theme } = useAppTheme(); return ( - + diff --git a/src/components/directory-card/index.tsx b/src/components/directory-card/index.tsx index ba61560..d942976 100644 --- a/src/components/directory-card/index.tsx +++ b/src/components/directory-card/index.tsx @@ -26,7 +26,7 @@ export function DirectoryCard(props: DirectoryCardProps): React.ReactNode { diff --git a/src/components/filter-chip/index.tsx b/src/components/filter-chip/index.tsx index ae06a41..1cfd947 100644 --- a/src/components/filter-chip/index.tsx +++ b/src/components/filter-chip/index.tsx @@ -13,8 +13,8 @@ export function FilterChip({ icon, label, onPress, selected = false }: FilterChi accessibilityRole="button" accessibilityState={{ selected }} className={selected - ? "min-h-11 flex-row items-center justify-center gap-2 rounded-pill border border-primary bg-primary px-4" - : "min-h-11 flex-row items-center justify-center gap-2 rounded-pill border border-line bg-paper px-4"} + ? "min-h-11 flex-row items-center justify-center gap-2 rounded-pill border border-primary bg-primary px-16" + : "min-h-11 flex-row items-center justify-center gap-2 rounded-pill border border-line bg-paper px-16"} onPress={onPress} > {icon} diff --git a/src/components/job-detail-actions/index.tsx b/src/components/job-detail-actions/index.tsx index 30d13b6..5f4514e 100644 --- a/src/components/job-detail-actions/index.tsx +++ b/src/components/job-detail-actions/index.tsx @@ -48,10 +48,10 @@ export function JobDetailActions({ onLayout={handleLayout} testID="job-detail-actions" > - + @@ -61,7 +61,7 @@ export function JobDetailActions({ @@ -72,7 +72,7 @@ export function JobDetailActions({ @@ -82,7 +82,7 @@ export function JobDetailActions({ diff --git a/src/components/new-matches-card/index.tsx b/src/components/new-matches-card/index.tsx index 2111014..89c1b06 100644 --- a/src/components/new-matches-card/index.tsx +++ b/src/components/new-matches-card/index.tsx @@ -18,7 +18,7 @@ export function NewMatchesCard({ const { theme } = useAppTheme(); return ( - + diff --git a/src/components/notification-consent-prompt/index.tsx b/src/components/notification-consent-prompt/index.tsx index 13983a5..cbbd26d 100644 --- a/src/components/notification-consent-prompt/index.tsx +++ b/src/components/notification-consent-prompt/index.tsx @@ -58,11 +58,11 @@ export function NotificationConsentPrompt(): React.ReactNode { > {copy.title} {copy.purpose} - - void decide("accept")}> + + void decide("accept")}> {copy.accept} - void decide("decline")}> + void decide("decline")}> {copy.decline} diff --git a/src/components/opportunity-card/index.tsx b/src/components/opportunity-card/index.tsx index 5860355..a5943cd 100644 --- a/src/components/opportunity-card/index.tsx +++ b/src/components/opportunity-card/index.tsx @@ -77,7 +77,7 @@ export function OpportunityCard({ item, ...props }: OpportunityCardProps): React {copy.optionalDescription} - + void run(startOptionalUpdate)} testID="optional-update-action" @@ -59,7 +59,7 @@ export function OptionalUpdateBanner(): React.ReactNode { void run(dismissOptionalUpdate)} testID="optional-update-dismiss" diff --git a/src/components/preference-popover/index.tsx b/src/components/preference-popover/index.tsx index bc97a28..6133851 100644 --- a/src/components/preference-popover/index.tsx +++ b/src/components/preference-popover/index.tsx @@ -33,7 +33,7 @@ export function PreferencePopover({ ({ > {title} diff --git a/src/components/profile-hero/index.tsx b/src/components/profile-hero/index.tsx index 9602c50..c68aacb 100644 --- a/src/components/profile-hero/index.tsx +++ b/src/components/profile-hero/index.tsx @@ -18,8 +18,8 @@ interface ProfileHeroProps { export function ProfileHero(props: ProfileHeroProps): React.ReactNode { const { theme } = useAppTheme(); return ( - - + + {props.status ? ( @@ -37,8 +37,8 @@ export function ProfileHero(props: ProfileHeroProps): React.ReactNode { - - + + {props.countLabel} diff --git a/src/components/screen-header/index.tsx b/src/components/screen-header/index.tsx index 6a3c4d0..cef990c 100644 --- a/src/components/screen-header/index.tsx +++ b/src/components/screen-header/index.tsx @@ -12,7 +12,7 @@ export function ScreenHeader({ title, }: ScreenHeaderProps): React.ReactNode { return ( - + {eyebrow} diff --git a/src/components/selection-sheet/index.tsx b/src/components/selection-sheet/index.tsx index 57d3908..ed0dddd 100644 --- a/src/components/selection-sheet/index.tsx +++ b/src/components/selection-sheet/index.tsx @@ -64,7 +64,7 @@ export function SelectionSheet({ visible={visible} > - + ({ - + {options.length === 0 ? ( {emptyLabel} @@ -97,8 +97,8 @@ export function SelectionSheet({ accessibilityRole="button" accessibilityState={{ selected }} className={index === 0 - ? "min-h-[52px] flex-row items-center gap-3 px-4" - : "min-h-[52px] flex-row items-center gap-3 border-t border-line px-4"} + ? "min-h-[52px] flex-row items-center gap-3 px-16" + : "min-h-[52px] flex-row items-center gap-3 border-t border-line px-16"} onPress={() => select(option.value)} > + @@ -30,7 +30,7 @@ export function SupportCard(): React.ReactNode { void openHttpsUrl(OPENINGS_WEB_REPOSITORY_URL)} > diff --git a/tests/contracts/spacing-scale.test.ts b/tests/contracts/spacing-scale.test.ts new file mode 100644 index 0000000..3587ca9 --- /dev/null +++ b/tests/contracts/spacing-scale.test.ts @@ -0,0 +1,34 @@ +import { readdirSync, readFileSync, statSync } from "node:fs"; +import path from "node:path"; + +const sourceRoot = path.resolve(__dirname, "../../src"); + +function tsxFiles(directory: string): string[] { + return readdirSync(directory).flatMap((entry) => { + const entryPath = path.join(directory, entry); + + if (statSync(entryPath).isDirectory()) return tsxFiles(entryPath); + return entryPath.endsWith(".tsx") ? [entryPath] : []; + }); +} + +describe("mobile spacing scale", () => { + it("does not confuse the literal 4dp token with Tailwind's former 16dp spacing", () => { + const ambiguousSpacing = /(?:^|\s)(?:m[trblxy]?|p[trblxy]?|gap)-4(?=\s|$)/g; + const violations = tsxFiles(sourceRoot).flatMap((file) => { + const source = readFileSync(file, "utf8"); + const matches = [...source.matchAll(/(?:contentContainerClassName|className)="([^"]*)"/g)]; + + return matches.flatMap((match) => { + const classes = match[1] ?? ""; + const offending = classes.match(ambiguousSpacing) ?? []; + + return offending.map((className) => + `${path.relative(sourceRoot, file)}: ${className.trim()}`, + ); + }); + }); + + expect(violations).toEqual([]); + }); +});