diff --git a/apps/web/partials/bounties/board-bounty-card.tsx b/apps/web/partials/bounties/board-bounty-card.tsx index caf61772ff..ce2078570f 100644 --- a/apps/web/partials/bounties/board-bounty-card.tsx +++ b/apps/web/partials/bounties/board-bounty-card.tsx @@ -7,23 +7,14 @@ import { statusKeyForId } from '~/core/bounties/labels'; import type { BoardBounty } from '~/core/bounties/types'; import type { SpaceBounty } from '~/core/community/bounty-types'; -import { - AVAILABLE_CARD_HEIGHT_PX, - AvailableBountyCard, - BountyCard, - InProgressBountyCard, -} from '~/partials/community-tab/bounty-card'; +import { AvailableBountyCard, BountyCard, InProgressBountyCard } from '~/partials/community-tab/bounty-card'; -/** - * The board mixes statuses in one grid, so every card gets the same HEIGHT — - * the available card's, the tallest of the three. Width is fluid: cards fill - * whatever column the grid gives them (a fixed width is what put the - * Community-tab grids one column short; see bounty-card's cardStyle). - */ -export const BOARD_CARD_HEIGHT_PX = AVAILABLE_CARD_HEIGHT_PX; +import { BOARD_CARD_HEIGHT_PX } from './board-layout'; -/** Same column threshold as the Community tab's available grid. */ -export const BOARD_GRID_CLASS = 'grid grid-cols-[repeat(auto-fill,minmax(min(340px,100%),1fr))] gap-4'; +// Declared in `board-layout.ts`, which carries no `'use client'`, because `app/bounties/loading.tsx` +// renders the skeleton on the server and puts the grid class into a `className`. Re-exported here so +// every existing importer keeps working. +export { BOARD_CARD_HEIGHT_PX, BOARD_GRID_CLASS } from './board-layout'; /** * The interest bindings an available card needs, lifted to the grid so one diff --git a/apps/web/partials/bounties/board-layout.ts b/apps/web/partials/bounties/board-layout.ts new file mode 100644 index 0000000000..33f4df9931 --- /dev/null +++ b/apps/web/partials/bounties/board-layout.ts @@ -0,0 +1,23 @@ +import { AVAILABLE_CARD_HEIGHT_PX } from '~/partials/community-tab/bounty-card-layout'; + +/** + * How the board lays itself out, apart from the cards that fill it. + * + * Its own module, with no `'use client'`, because `app/bounties/loading.tsx` is a Server Component + * and `BountyBoardSkeleton` puts {@link BOARD_GRID_CLASS} straight into a `className`. Read out of + * `board-bounty-card.tsx` — a client module — the class arrived as a client reference and the + * loading grid rendered with no grid at all until hydration replaced it. + * + * `board-bounty-card.tsx` re-exports both, so nothing that already imports them from there had to + * change. + */ + +/** + * The board mixes statuses in one grid, so every card gets the same HEIGHT — the available card's, + * the tallest of the three. Width is fluid: cards fill whatever column the grid gives them (a fixed + * width is what put the Community-tab grids one column short; see bounty-card's cardStyle). + */ +export const BOARD_CARD_HEIGHT_PX = AVAILABLE_CARD_HEIGHT_PX; + +/** Same column threshold as the Community tab's available grid. */ +export const BOARD_GRID_CLASS = 'grid grid-cols-[repeat(auto-fill,minmax(min(340px,100%),1fr))] gap-4'; diff --git a/apps/web/partials/bounties/bounty-board-skeleton.tsx b/apps/web/partials/bounties/bounty-board-skeleton.tsx index cc45c951bf..c5e267f2f8 100644 --- a/apps/web/partials/bounties/bounty-board-skeleton.tsx +++ b/apps/web/partials/bounties/bounty-board-skeleton.tsx @@ -1,6 +1,6 @@ import { Skeleton } from '~/design-system/skeleton'; -import { BOARD_CARD_HEIGHT_PX, BOARD_GRID_CLASS } from './board-bounty-card'; +import { BOARD_CARD_HEIGHT_PX, BOARD_GRID_CLASS } from './board-layout'; /** Placeholder grid matching the board's fluid columns and unified card height. */ export function BountyBoardSkeleton({ cards = 6 }: { cards?: number }) { diff --git a/apps/web/partials/community-tab/bounty-card-layout.ts b/apps/web/partials/community-tab/bounty-card-layout.ts new file mode 100644 index 0000000000..02c9945672 --- /dev/null +++ b/apps/web/partials/community-tab/bounty-card-layout.ts @@ -0,0 +1,15 @@ +/** + * The bounty cards' fixed heights. + * + * Their own module, with no `'use client'` on it, because the board's loading state is + * server-rendered and needs them — see `partials/bounties/board-layout.ts`. Every export of a + * client module is a client reference on the server rather than the value it looks like, so a + * number read across that boundary arrives as an opaque placeholder and lands in the DOM as one. + * + * `bounty-card.tsx` re-exports all three, so nothing that already imports them from there had to + * change. + */ + +export const COMPLETED_CARD_HEIGHT_PX = 143; +export const IN_PROGRESS_CARD_HEIGHT_PX = 110; +export const AVAILABLE_CARD_HEIGHT_PX = 240; diff --git a/apps/web/partials/community-tab/bounty-card.tsx b/apps/web/partials/community-tab/bounty-card.tsx index 935cb63d89..be49f12e10 100644 --- a/apps/web/partials/community-tab/bounty-card.tsx +++ b/apps/web/partials/community-tab/bounty-card.tsx @@ -10,6 +10,8 @@ import { useSmartAccount } from '~/core/hooks/use-smart-account'; import { Avatar } from '~/design-system/avatar'; +import { AVAILABLE_CARD_HEIGHT_PX, COMPLETED_CARD_HEIGHT_PX, IN_PROGRESS_CARD_HEIGHT_PX } from './bounty-card-layout'; + const GEO_ICON_WIDTH_PX = 8.5; const GEO_ICON_HEIGHT_PX = 10; @@ -45,9 +47,11 @@ function BountyGeoIcon() { ); } -export const COMPLETED_CARD_HEIGHT_PX = 143; -export const IN_PROGRESS_CARD_HEIGHT_PX = 110; -export const AVAILABLE_CARD_HEIGHT_PX = 240; +// Declared in `bounty-card-layout.ts`, which carries no `'use client'`, because the board's +// server-rendered loading state needs the available card's height. Re-exported here so every +// existing importer keeps working. +export { AVAILABLE_CARD_HEIGHT_PX, COMPLETED_CARD_HEIGHT_PX, IN_PROGRESS_CARD_HEIGHT_PX }; + const CARD_PADDING_PX = 20; /** diff --git a/apps/web/partials/entity-page/client-values-in-server-boundaries.test.ts b/apps/web/partials/entity-page/client-values-in-server-boundaries.test.ts index 467ae62bbf..17c8e59ced 100644 --- a/apps/web/partials/entity-page/client-values-in-server-boundaries.test.ts +++ b/apps/web/partials/entity-page/client-values-in-server-boundaries.test.ts @@ -493,11 +493,6 @@ function staticReferences(sourceFile: ts.SourceFile): Reference[] { * existing entry would go on authorising the same name after someone repointed the import at a * *different* client module — debt quietly licensing a new fault. * - * - `bounty-board-skeleton` is the live one. `app/bounties/loading.tsx` is server-rendered and puts - * `BOARD_GRID_CLASS` straight into a `className`, so the grid has no grid during the loading - * flash. Left here rather than fixed because `BOARD_CARD_HEIGHT_PX` derives from - * `AVAILABLE_CARD_HEIGHT_PX` in a second client module, so the fix relocates layout constants - * across two features and wants someone who can look at the bounties board while doing it. * - `read-block-media-dimensions` would return a client reference in place of its empty-dimensions * object. Nothing calls it outside its own test today, so it is a landmine rather than a fault. * - `bounties/config` is inert: `useFeatureFlag` is only ever called from `useBountiesEnabled`, @@ -505,8 +500,6 @@ function staticReferences(sourceFile: ts.SourceFile): Reference[] { * Untangling it moves a hook out of `config.ts` and repoints nine files, for no behaviour change. */ const KNOWN = new Set([ - 'partials/bounties/bounty-board-skeleton.tsx -> BOARD_CARD_HEIGHT_PX (from partials/bounties/board-bounty-card.tsx)', - 'partials/bounties/bounty-board-skeleton.tsx -> BOARD_GRID_CLASS (from partials/bounties/board-bounty-card.tsx)', 'core/blocks/data/read-block-media-dimensions.ts -> NO_BLOCK_MEDIA_DIMENSIONS (from core/hooks/use-block-media-dimensions.ts)', 'core/bounties/config.ts -> useFeatureFlag (from core/state/feature-flags.ts)', ]);