Skip to content

[style] unify body prose at max-w-prose across public site#2416

Draft
dewierwan wants to merge 14 commits into
masterfrom
dewi/cap-prose-line-length
Draft

[style] unify body prose at max-w-prose across public site#2416
dewierwan wants to merge 14 commits into
masterfrom
dewi/cap-prose-line-length

Conversation

@dewierwan

@dewierwan dewierwan commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Body prose was running 95–126 chars per line on wide viewports across the public marketing site (lander pages, policies, missions, program/grant pages), past WCAG 1.4.8's 80ch ceiling and the Baymard 50–75ch sweet spot. This PR unifies all body prose at max-w-prose (~65ch / ~580px @ 16px body) — the team's documented standard in apps/website/CLAUDE.md and the readability optimum.

Per Will's feedback on the original About+Join Us scope: rather than widening About/Join Us to max-w-text (840px ≈ ~105ch, off-spec for body prose), this expands the sweep so the whole site lands at one readable width. Card/grid sections (Schedule, Pathways, CourseInformation, History timeline, Team grid, etc.) intentionally stay on max-w-text / max-w-section-wide — they're structured layouts, not prose.

Scope

About + Join Usmax-w-prose wrapper:

  • about/IntroSection, BeliefsSection, ValuesSection
  • join-us/WhyUsSection, JobsListSection

CourseLander prose components (max-w-textmax-w-prose):

  • WhoIsThisForTextSection, HowTheCourseWorksSection, CourseBenefitsTextSection, FieldBuildingSection, QuoteSection

Program/grant page sections:

  • incubator-week: AboutBlueDotSection, TrackRecordSection, LogisticsSection, TheWeekSection, WhatThisIsForSection
  • advising: WhatThisIsForSection, WhatToExpectSection, WhoYouAreSection
  • career-transition-grant: ExpectationsSection, WhatThisIsForSection
  • rapid-grants: WhatThisIsForSection, FundedProjectsSection (GranteesListSection wrap)
  • pages/programs.tsx: ProgramsList wrapped

Markdown wrapper pages (max-w-3xlmax-w-prose):

  • privacy-policy, code-of-conduct, attendance-policy, our-community, resources, missions/[slug], join-us/[slug]

Documentation:

  • apps/website/CLAUDE.md: tightened the prose convention to distinguish prose (max-w-prose) from structured layouts (max-w-text / max-w-section-wide).

Verified

  • Playwright sweep at 320 / 480 / 720 / 1024 / 1440 / 1920 on all changed pages. Worst-case body prose: 65ch at content layouts, 76ch at FAQ accordion answers (under 80ch ceiling). Hero subtitles excluded per CLAUDE.md "hero taglines" carve-out. Char count measured via the browser's actual 1ch width (getBoundingClientRect on a hidden <span>0</span> injected into each <p>).
  • npm test passes (103/103 test files, 620 tests).
  • One section that lightly exceeds the ceiling: CourseInformationSection card has a label-description row at ~83ch (label column 160px + description column 680px inside an 840px card). Treating as exempt under the "table cells / form labels" carve-out — it's structured data, not flowing prose.

Screenshots

/about

Before After
📱 Mobile
🖥️ Desktop

/join-us

Before After
📱 Mobile
🖥️ Desktop

/privacy-policy

Before After
📱 Mobile
🖥️ Desktop

/programs/incubator-week

Before After
📱 Mobile
🖥️ Desktop

/programs/advising

Before After
📱 Mobile
🖥️ Desktop

Out of scope

  • Course unit chrome (UnitLayout.tsx — bespoke responsive widths tied to sidebar/video).
  • Certificate components — special layout constraints.
  • Modals — max-w-modal (600px) is fine.
  • Hero max-w-narrow callouts — intentional design choice.
  • Renaming max-w-text token — separate cleanup; just stop using it for prose.

Test plan

  • Eyeball the 5 page screenshots above at desktop + mobile
  • Spot-check /our-community, /resources, /code-of-conduct, /attendance-policy, /missions/[slug] in dev
  • Confirm card/grid sections (Schedule, Pathways, History timeline, Team grid, About-you 3-col on incubator-week) still span their full design widths

🤖 Generated with Claude Code

@dewierwan

Copy link
Copy Markdown
Contributor Author

@claude review

@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 5, 2026 16:38 Destroyed
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR updates website layout styling across multiple components and pages. Documentation in CLAUDE.md is revised to clarify min-width/min-height usage and add prose width guidance. Across the codebase, numerous components receive structural updates to wrap content in max-width containers using max-w-prose and mx-auto classes. Page-level Section wrappers are changed from max-w-3xl to max-w-prose. Additionally, JobsListSection gains an invisible anchor for navigation, and QuoteSection receives an accessibility attribute. No changes to component signatures or public APIs occur.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description is comprehensive and well-structured, including a clear summary, detailed scope, verification details with accessibility metrics, screenshots, and test results. However, the required template sections (Issue fix link, Developer checklist, and Before/After screenshot table) are not included, though the substantive information is present.
Title check ✅ Passed The title accurately and concisely describes the main change: capping prose width to max-w-prose on About and Join Us pages, which aligns with the core objective of improving readability by constraining line length to ~65 characters.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dewi/cap-prose-line-length

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude

claude Bot commented May 5, 2026

Copy link
Copy Markdown

Claude finished @dewierwan's task —— View job


Code Review for Prose Width Capping

I've completed my review of the prose width capping implementation. Here's my comprehensive analysis:

Todo List

  • Read and analyze the 4 modified component files
  • Check for code quality and consistency issues
  • Verify the implementation follows established patterns
  • Provide comprehensive review feedback

Review Summary

Overall Assessment: ✅ Excellent implementation

The changes are clean, consistent, and well-executed. The implementation properly addresses the readability concerns mentioned in the PR description.

Positive Aspects

  1. Consistent Implementation Pattern: All 4 components follow the exact same pattern:

    <div className="max-w-prose mx-auto">
      <h3 className={`${pageSectionHeadingClass} mb-6`}>Title</h3>
      <div className="...">Content</div>
    </div>
  2. Proper Use of Tailwind Utility: max-w-prose is a standard Tailwind utility that caps width at ~65ch, which aligns perfectly with WCAG 1.4.8 guidelines mentioned in the PR.

  3. Correct Centering Strategy: Using mx-auto with max-w-prose ensures the content is centered while maintaining the desired character limit.

  4. Semantic Structure Preserved: The wrapper div doesn't interfere with the semantic structure—headings and content remain properly nested.

  5. Existing Design System Respected:

    • All components continue using pageSectionHeadingClass from the shared import
    • @bluedot/ui components (P) are used consistently
    • No deviation from established spacing patterns (mb-6, gap-5, gap-6)

Technical Details

  • Files affected: IntroSection.tsx:7, BeliefsSection.tsx:22, ValuesSection.tsx:22, WhyUsSection.tsx:7
  • Change scope: Minimal and targeted—only adds the necessary wrapper div
  • Design token usage: Uses standard Tailwind max-w-prose rather than custom tokens, which is appropriate for this use case
  • Accessibility: Improves readability per WCAG guidelines (50-75 characters per line)

Code Quality Notes

  1. No Code Duplication: While all 4 components use the same pattern, this isn't problematic duplication—each component serves a different semantic purpose.

  2. No Side Effects: The changes are purely presentational and don't affect component logic or data flow.

  3. Test Coverage: Snapshots have been properly updated to reflect the changes.

Potential Considerations (Not Issues)

  1. Design Token Opportunity: The codebase has a max-w-text token (840px) defined in the design system, but max-w-prose (~65ch ≈ 520-585px) is the correct choice for readability, so this is appropriate.

  2. Reusability: If this pattern becomes more common, consider creating a ProseContainer component, but the current scope doesn't justify it.

Recommendations

No changes needed. This is a solid implementation that:

  • ✅ Follows existing code patterns
  • ✅ Uses appropriate Tailwind utilities
  • ✅ Maintains semantic structure
  • ✅ Improves accessibility/readability
  • ✅ Has proper test coverage

The PR is ready for merge.

@greptile-apps

greptile-apps Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR caps prose line length on the /about (Intro, Beliefs, Values) and /join-us (Why Us) pages by wrapping each section's heading and body content in a single max-w-prose mx-auto div, following WCAG 1.4.8 / Baymard readability guidance (50–75 chars optimal).

  • Four component files are updated with an identical structural pattern: the existing heading + body content is wrapped in <div className=\"max-w-prose mx-auto\">, centering both elements together to avoid the orphaned-heading anti-pattern.
  • All required snapshot files (page-level and component-level) are regenerated; ValuesSection and WhyUsSection have no component-level snapshot tests (confirmed by absent .snap files), so the page-level snapshots in about.test.tsx.snap and join-us.test.tsx.snap are the only ones needed for those components.

Confidence Score: 5/5

Safe to merge — purely additive CSS wrapper changes with no logic, no data flow, and no behavioural side-effects.

All four components receive an identical, mechanically correct structural change: a max-w-prose mx-auto div wrapping existing content inside a flex flex-col section. Centering via mx-auto in a flex column is valid CSS. Page-level and available component-level snapshots are all regenerated. The author verified the output at six viewport widths and confirmed all 622 tests pass.

No files require special attention.

Important Files Changed

Filename Overview
apps/website/src/components/about/IntroSection.tsx Wraps heading + four body paragraphs in max-w-prose mx-auto; snapshot regenerated; change is correct and self-contained.
apps/website/src/components/about/BeliefsSection.tsx Wraps heading + mapped belief items in max-w-prose mx-auto; component-level snapshot regenerated.
apps/website/src/components/about/ValuesSection.tsx Same wrapper pattern applied to mapped values list; no component-level snapshot test exists for this component, covered by page-level snapshot.
apps/website/src/components/join-us/WhyUsSection.tsx Same wrapper pattern applied to two paragraphs; no component-level snapshot test exists, covered by page-level snapshot.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["<section class='section-body'>\nflex flex-col · max-w-1436px · mx-auto"] --> B["<div class='max-w-prose mx-auto'>\n~65ch · centred in flex column"]
    B --> C["<h3> Section heading"]
    B --> D["Body content\n(paragraphs / mapped items)"]

    style A fill:#e8f0fe,stroke:#4a90d9
    style B fill:#d4edda,stroke:#28a745
    style C fill:#fff3cd,stroke:#ffc107
    style D fill:#fff3cd,stroke:#ffc107
Loading

Reviews (1): Last reviewed commit: "[chore] PR #2416: drop screenshots (embe..." | Re-trigger Greptile

@dewierwan

Copy link
Copy Markdown
Contributor Author

Bot pass:

  • Greptile: 5/5 confidence, safe to merge — purely additive CSS wrapper, no logic/data-flow changes.
  • Claude bot: ✅ Excellent implementation, ready for merge. No changes requested.
  • CodeRabbit: rate-limited, skipped (expected).

No findings to address. Holding for human eyeball.

dewierwan added a commit that referenced this pull request May 5, 2026
@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 5, 2026 17:03 Destroyed
@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 5, 2026 17:04 Destroyed
@dewierwan

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented May 5, 2026

Copy link
Copy Markdown

Claude finished @dewierwan's task —— View job


Code Review for Prose Width Capping

I've completed my review of the prose width capping implementation. Here's my comprehensive analysis:

Todo List

  • Read and analyze the 4 modified component files
  • Check for code quality and consistency issues
  • Verify the implementation follows established patterns
  • Provide comprehensive review feedback

Review Summary

Overall Assessment: ✅ Excellent implementation

The changes are clean, consistent, and well-executed. The implementation properly addresses the readability concerns mentioned in the PR description.

Positive Aspects

  1. Consistent Implementation Pattern: All 4 components follow the exact same pattern:

    <div className="max-w-prose mx-auto">
      <h3 className={`${pageSectionHeadingClass} mb-6`}>Title</h3>
      <div className="...">Content</div>
    </div>
  2. Proper Use of Tailwind Utility: max-w-prose is a standard Tailwind utility that caps width at ~65ch, which aligns perfectly with WCAG 1.4.8 guidelines mentioned in the PR.

  3. Correct Centering Strategy: Using mx-auto with max-w-prose ensures the content is centered while maintaining the desired character limit.

  4. Semantic Structure Preserved: The wrapper div doesn't interfere with the semantic structure—headings and content remain properly nested.

  5. Existing Design System Respected:

    • All components continue using pageSectionHeadingClass from the shared import
    • @bluedot/ui components (P) are used consistently
    • No deviation from established spacing patterns (mb-6, gap-5, gap-6)

Technical Details

  • Files affected: IntroSection.tsx:7, BeliefsSection.tsx:22, ValuesSection.tsx:22, WhyUsSection.tsx:7
  • Change scope: Minimal and targeted—only adds the necessary wrapper div
  • Design token usage: Uses standard Tailwind max-w-prose rather than custom tokens, which is appropriate for this use case
  • Accessibility: Improves readability per WCAG guidelines (50-75 characters per line)

Code Quality Notes

  1. No Code Duplication: While all 4 components use the same pattern, this isn't problematic duplication—each component serves a different semantic purpose.

  2. No Side Effects: The changes are purely presentational and don't affect component logic or data flow.

  3. Test Coverage: Snapshots have been properly updated to reflect the changes.

Potential Considerations (Not Issues)

  1. Design Token Opportunity: The codebase has a max-w-text token (840px) defined in the design system, but max-w-prose (~65ch ≈ 520-585px) is the correct choice for readability, so this is appropriate.

  2. Reusability: If this pattern becomes more common, consider creating a ProseContainer component, but the current scope doesn't justify it.

Recommendations

No changes needed. This is a solid implementation that:

  • ✅ Follows existing code patterns
  • ✅ Uses appropriate Tailwind utilities
  • ✅ Maintains semantic structure
  • ✅ Improves accessibility/readability
  • ✅ Has proper test coverage

The PR is ready for merge.


View job

@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 5, 2026 17:10 Destroyed
@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 5, 2026 17:11 Destroyed
@Will-Howard

Will-Howard commented May 6, 2026

Copy link
Copy Markdown
Collaborator

We use 840px elsewhere (e.g. on lander pages) to solve this problem, and there is at least one place where the 840px/65ch values clash to create conflicting column widths:

Screenshot 2026-05-06 at 08 51 48

I suggest adopting 840px across the board, since it's the standard we had already generally adopted. This is flagged as out of scope in the PR description, "max-w-text design token at 840px", but I think it would be what's worth doing now for consistency and to solve this more once-and-for-all.

Update: Oh I see max-w-text already exists on master, in that case I think the case for adopting it here is even stronger.

(happy to make this change and finish off the PR overall if you want)

@dewierwan

Copy link
Copy Markdown
Contributor Author

(This is Claude replying on Dewi's behalf — Dewi will weigh in too.)

Pushback with a counter-proposal — would value your take.

The team's own apps/website/CLAUDE.md:120 already says max-w-prose for readability ("avoid max-width/max-height (except max-w-prose for readability)"). Crunching the numbers, max-w-text at 840px works out to ~105 chars per line at our 16px body, which is past WCAG 1.4.8's 80ch hard ceiling and Baymard's 50–75ch optimum. Reading those wide lines on a 1440+ monitor is exactly the bug Dewi opened the PR to fix.

So the counter: keep this PR at max-w-prose AND fold in the lander prose sections + policy/missions/community markdown wrappers in this same PR so the whole site lands at one readable width. That's the unification you're after, just at the readability-respecting endpoint.

Concretely the sweep would be:

  • 5 lander prose components (WhoIsThisForTextSection, HowTheCourseWorksSection, CourseBenefitsTextSection, FieldBuildingSection, QuoteSection): max-w-textmax-w-prose.
  • Card/grid sections (ScheduleListSection, PathwaysListSection, CourseInformationSection, CourseOutcomesSection, etc.) stay on max-w-text / max-w-section-wide — they're not prose.
  • Policy/markdown pages (privacy-policy, code-of-conduct, attendance-policy, our-community, resources, missions/[slug], join-us/[slug]): wrapper max-w-3xl (~96ch) → max-w-prose.
  • Tighten the apps/website/CLAUDE.md line into a 2-line rule that distinguishes prose (max-w-prose) from structured layouts (max-w-text / max-w-section-wide) so future PRs don't drift.

Yes please on the offer to help — happy to coordinate. Want to chat for 5 min before swinging the hammer, or shall Dewi just push and you can react to the diff?

dewierwan added 9 commits May 6, 2026 12:27
Body copy on /about (Intro, Beliefs, Values) and /join-us (Why us)
ran full-page-width on desktop (~1436px ≈ 130+ chars per line),
making it harder to read. Cap at max-w-prose (~65ch) per Baymard /
WCAG 1.4.8 readability guidance.

Verified at 320/480/720/1024/1440/1920 — max ch is 65 across all
widths, 0 violators.
Wrap heading + prose container in a single max-w-prose mx-auto
wrapper per section. Avoids the "orphaned heading flush-left,
prose centred" mismatch — heading and body share one anchor.
Open roles + Support our mission lists were full-page-width while
the new Why us section above them was 65ch-centred. The width
discontinuity was visually jarring. Cap both list sections at
max-w-3xl (768px) — wider than max-w-prose so titles + Learn-more
CTAs have breathing room, narrow enough to unify the page geometry.
Why us at max-w-prose and JobsListSection at max-w-3xl gave the
page two slightly-different-width centred columns — visually
jarring. Cap both at max-w-prose so the page reads as one
editorial column from Why us through the role list.

Also keeps everything inside the 80ch readability ceiling
(max-w-3xl was ~87ch, technically over the limit even though
list rows are exempt from the strict prose rule).
dewierwan and others added 2 commits May 6, 2026 12:27
Per Will's feedback on the original About+Join Us PR, unify body prose
width across the public site at max-w-prose (~65ch) — the team's
documented standard in apps/website/CLAUDE.md and the WCAG 1.4.8 sweet
spot. Lander prose sections and policy/markdown wrappers were running
840–928px (~96–105ch), past the readability ceiling.

- 5 lander prose components (WhoIsThisForTextSection,
  HowTheCourseWorksSection, CourseBenefitsTextSection,
  FieldBuildingSection, QuoteSection): max-w-text → max-w-prose.
  Card/grid sections (Schedule, Pathways, CourseInformation,
  CourseOutcomes, etc.) untouched — they're not prose.
- 7 markdown wrapper pages (privacy-policy, code-of-conduct,
  attendance-policy, our-community, resources, missions/[slug],
  join-us/[slug]): Section className max-w-3xl → max-w-prose.
- apps/website/CLAUDE.md: tightened the convention to distinguish prose
  (max-w-prose) from structured layouts (max-w-text / max-w-section-wide).

Verified at 320/480/720/1024/1440/1920 via Playwright; all body prose
≤65ch on every changed page (hero subtitles excluded per CLAUDE.md).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends PR #2416's prose-width sweep to bespoke per-page sections on
program/grant landers that were running 100ch+ — the actual sites users
read end-to-end.

Pure-prose sections (whole inner div wrapped in max-w-prose mx-auto):
- incubator-week: AboutBlueDotSection, TrackRecordSection, LogisticsSection,
  TheWeekSection
- advising: WhatThisIsForSection, WhatToExpectSection, WhoYouAreSection
- career-transition-grant: ExpectationsSection

Mixed sections (prose group wrapped, card grids left at section width):
- incubator-week: WhatThisIsForSection (3 prose groups + About-you grid)
- career-transition-grant: WhatThisIsForSection (prose + 3-card support grid)
- rapid-grants: WhatThisIsForSection (prose + 2-card decision grid)

List pages (PageListGroup wrapped):
- pages/programs.tsx: ProgramsList wrapped in max-w-prose
- rapid-grants: FundedProjectsSection (GranteesListSection) wrapped

Verified at 320/480/720/1024/1440/1920 via Playwright on /programs,
/programs/incubator-week, /programs/advising, /programs/career-transition-grant,
/programs/rapid-grants, /grants/rapid. All body prose ≤69ch on every page;
remaining 76ch hits are FAQ accordion answers (under 80ch ceiling) and
hero subtitles (excluded per CLAUDE.md).

Card/grid sections (Schedule, Pathways, CourseInformation, Outcomes, etc.)
intentionally untouched — they are structured layouts, not prose.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@render render Bot temporarily deployed to dewi/cap-prose-line-length - bluedot-preview PR #2416 May 6, 2026 12:35 Destroyed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/website/src/components/incubator-week/WhatThisIsForSection.tsx (1)

63-67: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

"About you" heading sits outside the max-w-prose wrapper, breaking alignment with the headings above it.

"What you'll do" and "Track record" (lines 32, 43) are centred at ~65ch inside the new wrapper. The "About you" h3 at line 65 is outside it, so on wide viewports it is left-aligned at the section's full content width — a noticeable horizontal jump relative to the prose headings above.

If the heading is meant to label the 3-column card grid (which rightly stays full-width), the current placement is intentional but the visual misalignment may still read oddly. One option is to wrap just the heading in its own max-w-prose mx-auto div so it aligns with the prose column, while keeping the grid unrestricted:

✏️ Suggested fix
     </div>
   </div>

-  <h3 className={`${pageSectionHeadingClass} pt-4`}>About you</h3>
+  <div className="max-w-prose mx-auto w-full">
+    <h3 className={`${pageSectionHeadingClass} pt-4`}>About you</h3>
+  </div>

   <div className="pt-2 grid gap-8 grid-cols-1 bd-md:grid-cols-3">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/src/components/incubator-week/WhatThisIsForSection.tsx` around
lines 63 - 67, The "About you" h3 is outside the max-width prose wrapper causing
horizontal misalignment; wrap that heading in a container with the same prose
constraints (e.g., a div with classes "max-w-prose mx-auto") or move the h3 into
the existing prose wrapper so it uses the same width as the other headings —
update the element that renders the About you heading (the h3 using
pageSectionHeadingClass) so it is placed inside (or wrapped by) a max-w-prose
mx-auto wrapper while leaving the following grid (the div with "pt-2 grid gap-8
grid-cols-1 bd-md:grid-cols-3") full-width.
🧹 Nitpick comments (1)
apps/website/CLAUDE.md (1)

120-121: ⚡ Quick win

Clarify the prose-width carve-out on the adjacent "avoid max-width" rule.

Line 120 says to avoid max-width/max-height, while line 121 immediately prescribes max-w-prose. Without a bridging note, an LLM assistant treating these as independent bullet points could apply line 120 and ignore line 121, or flag max-w-prose usages as violations.

✏️ Suggested wording
-- Use `min-width`/`min-height` for flexibility; avoid `max-width`/`max-height`.
-- **Prose width:** body-text columns …
+- Use `min-width`/`min-height` for flexibility; avoid `max-width`/`max-height` on structural elements (prose text is the exception — see rule below).
+- **Prose width (exception to the rule above):** body-text columns …
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/CLAUDE.md` around lines 120 - 121, Clarify the apparent
contradiction between the general "avoid `max-width`/`max-height`" rule and the
specific `max-w-prose` guidance by adding a bridging sentence that explicitly
states `max-w-prose` is an intentional exception for body-text (prose) columns;
update the CLAUDE.md bullets so the "avoid `max-width`/`max-height`" rule
remains general while a follow-up line (referencing `max-w-prose`, `max-w-text`,
and `max-w-section-wide`) explains these allowed exceptions and when to use each
(prose vs cards/grids/sections).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/website/src/components/join-us/JobsListSection.tsx`:
- Line 29: The invisible anchor div with id "open-roles-anchor" in
JobsListSection.tsx currently uses the utility class "bottom-48" which shifts
the anchor 192px upward; verify this large offset against the design and, if not
intentional, reduce it to match the sticky nav height plus a small buffer (e.g.,
replace "bottom-48" with "bottom-24" or "bottom-32") so the scroll target aligns
correctly under the sticky nav; update the class on the <div
id="open-roles-anchor"> element accordingly.

In `@apps/website/src/pages/join-us/`[slug].tsx:
- Line 96: The Section component on the join-us page is missing the mx-auto
utility so the constrained max-w-prose block is left-aligned; update the JSX
where Section is rendered (the Section component in
apps/website/src/pages/join-us/[slug].tsx) to include the className "max-w-prose
mx-auto" (or append "mx-auto" if className already exists) so the prose block is
centered on wide viewports.

In `@apps/website/src/pages/programs.tsx`:
- Around line 29-31: The wrapper div around ProgramsList currently uses the
utility class "max-w-prose", which is too narrow for structured rows; update
that div's class to use "max-w-text" instead of "max-w-prose" so ProgramsList
(rendering PageListGroup/PageListRow) has the appropriate 840px max width for
desktop layouts.

---

Outside diff comments:
In `@apps/website/src/components/incubator-week/WhatThisIsForSection.tsx`:
- Around line 63-67: The "About you" h3 is outside the max-width prose wrapper
causing horizontal misalignment; wrap that heading in a container with the same
prose constraints (e.g., a div with classes "max-w-prose mx-auto") or move the
h3 into the existing prose wrapper so it uses the same width as the other
headings — update the element that renders the About you heading (the h3 using
pageSectionHeadingClass) so it is placed inside (or wrapped by) a max-w-prose
mx-auto wrapper while leaving the following grid (the div with "pt-2 grid gap-8
grid-cols-1 bd-md:grid-cols-3") full-width.

---

Nitpick comments:
In `@apps/website/CLAUDE.md`:
- Around line 120-121: Clarify the apparent contradiction between the general
"avoid `max-width`/`max-height`" rule and the specific `max-w-prose` guidance by
adding a bridging sentence that explicitly states `max-w-prose` is an
intentional exception for body-text (prose) columns; update the CLAUDE.md
bullets so the "avoid `max-width`/`max-height`" rule remains general while a
follow-up line (referencing `max-w-prose`, `max-w-text`, and
`max-w-section-wide`) explains these allowed exceptions and when to use each
(prose vs cards/grids/sections).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 05099864-d7f7-4e97-b442-757fcfdd7d73

📥 Commits

Reviewing files that changed from the base of the PR and between 5092386 and 8510397.

⛔ Files ignored due to path filters (6)
  • apps/website/src/__tests__/pages/__snapshots__/about.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/__tests__/pages/__snapshots__/join-us.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/about/__snapshots__/BeliefsSection.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/about/__snapshots__/IntroSection.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/admin/__snapshots__/ImpersonationBadge.test.tsx.snap is excluded by !**/*.snap
  • apps/website/src/components/lander/__snapshots__/AgiStrategyLander.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (31)
  • apps/website/CLAUDE.md
  • apps/website/src/components/about/BeliefsSection.tsx
  • apps/website/src/components/about/IntroSection.tsx
  • apps/website/src/components/about/ValuesSection.tsx
  • apps/website/src/components/advising/WhatThisIsForSection.tsx
  • apps/website/src/components/advising/WhatToExpectSection.tsx
  • apps/website/src/components/advising/WhoYouAreSection.tsx
  • apps/website/src/components/career-transition-grant/ExpectationsSection.tsx
  • apps/website/src/components/career-transition-grant/WhatThisIsForSection.tsx
  • apps/website/src/components/incubator-week/AboutBlueDotSection.tsx
  • apps/website/src/components/incubator-week/LogisticsSection.tsx
  • apps/website/src/components/incubator-week/TheWeekSection.tsx
  • apps/website/src/components/incubator-week/TrackRecordSection.tsx
  • apps/website/src/components/incubator-week/WhatThisIsForSection.tsx
  • apps/website/src/components/join-us/JobsListSection.tsx
  • apps/website/src/components/join-us/WhyUsSection.tsx
  • apps/website/src/components/lander/components/CourseBenefitsTextSection.tsx
  • apps/website/src/components/lander/components/FieldBuildingSection.tsx
  • apps/website/src/components/lander/components/HowTheCourseWorksSection.tsx
  • apps/website/src/components/lander/components/QuoteSection.tsx
  • apps/website/src/components/lander/components/WhoIsThisForTextSection.tsx
  • apps/website/src/components/rapid-grants/FundedProjectsSection.tsx
  • apps/website/src/components/rapid-grants/WhatThisIsForSection.tsx
  • apps/website/src/pages/attendance-policy.tsx
  • apps/website/src/pages/code-of-conduct.tsx
  • apps/website/src/pages/join-us/[slug].tsx
  • apps/website/src/pages/missions/[slug].tsx
  • apps/website/src/pages/our-community.tsx
  • apps/website/src/pages/privacy-policy.tsx
  • apps/website/src/pages/programs.tsx
  • apps/website/src/pages/resources.tsx

@@ -27,23 +27,27 @@ const JobsListSection = ({ jobs }: { jobs: JobsListSectionProps }) => {
<>
<section className="section section-body">
<div id="open-roles-anchor" className="invisible relative bottom-48" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check sticky nav height and section-body padding-top to validate the bottom-48 (12rem / 192px) anchor offset.

# Find the nav/header component to inspect its height classes
rg -n "sticky|fixed" --type=tsx -C 2 apps/website/src/components/ 2>/dev/null | grep -i "nav\|header\|top-0" | head -40

# Find section-body CSS definition
rg -n "section-body" apps/website/src/ --type=css -A 5 2>/dev/null
rg -n "section-body" apps/website/src/ --type=tsx -C 2 2>/dev/null | head -30

# Check globals.css for section-body padding
fd -e css -e scss . apps/website/src/ --exec grep -n "section-body" {} \;

Repository: bluedotimpact/bluedot

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Read the JobsListSection file to understand the context
cat -n apps/website/src/components/join-us/JobsListSection.tsx | head -60

# Search for nav/header components more broadly
find apps/website/src/components -name "*nav*" -o -name "*header*" -o -name "*Nav*" -o -name "*Header*" 2>/dev/null | head -20

# Look for CSS files with section definitions
fd -e css -e scss apps/website/src/ 2>/dev/null | head -20

Repository: bluedotimpact/bluedot

Length of output: 3326


🏁 Script executed:

#!/bin/bash
# Read Nav.tsx to see the sticky nav height classes
cat -n apps/website/src/components/Nav/Nav.tsx | head -80

# Find and read CSS files that define section and section-body
find apps/website/src -name "globals.css" -o -name "*.css" -o -name "*.scss" 2>/dev/null | head -10

# Search for section-body class definition in CSS/SCSS
rg "section-body" apps/website/src --type=css --type=scss -B 2 -A 5 2>/dev/null

Repository: bluedotimpact/bluedot

Length of output: 3302


🏁 Script executed:

#!/bin/bash
# Read globals.css to find nav height and section-body CSS variables
cat -n apps/website/src/globals.css | head -150

# Also search for any CSS definitions of section-body and section classes
rg "section|nav-height" apps/website/src/globals.css -B 1 -A 3

Repository: bluedotimpact/bluedot

Length of output: 6642


🏁 Script executed:

#!/bin/bash
# Search for section-body and section class definitions
rg "section-body|section-base|\.section" apps/website/src --type=css --type=scss -B 2 -A 5 2>/dev/null | head -100

# Also check for Tailwind arbitrary section classes
rg "section" apps/website/tailwind.config.ts -B 3 -A 3 2>/dev/null

Repository: bluedotimpact/bluedot

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Find all CSS and config files in apps/website
find apps/website -name "*.css" -o -name "*.scss" -o -name "tailwind.config*" 2>/dev/null

# Try a broader search for section class definitions
rg "section" apps/website --type=tsx -B 1 -A 1 2>/dev/null | grep -i "classname.*section" | head -20

# Check if section-body is defined as a Tailwind utility or custom CSS
rg "section-body\|section-base" apps/website/src -B 2 -A 3 --type=tsx 2>/dev/null | head -50

Repository: bluedotimpact/bluedot

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Search for section-body usage in TSX files to understand what it does
rg "section-body|section-base" apps/website/src --type=tsx -B 2 -A 2 2>/dev/null | head -60

# Check tailwind.config files at root and apps level
find . -name "tailwind.config*" -type f 2>/dev/null | grep -E "(root|apps)" | head -5

# Read the join-us page to see the full context of JobsListSection usage
rg "JobsListSection" apps/website/src --type=tsx -B 5 -A 5 2>/dev/null | head -60

Repository: bluedotimpact/bluedot

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Read the tailwind config to see if section is defined there
find . -maxdepth 3 -name "tailwind.config*" -type f 2>/dev/null

# Check libraries/ui for section-body definition
rg "section-body" . --type=tsx --type=css 2>/dev/null | head -20

# Search for any references to section class in apps/website
grep -r "section-body\|section-base" apps/website/src --include="*.tsx" --include="*.css" 2>/dev/null | head -30

# Look at the join-us page directory for context
ls -la apps/website/src/pages/join-us/ 2>/dev/null
cat -n apps/website/src/pages/join-us/index.tsx 2>/dev/null | head -80

Repository: bluedotimpact/bluedot

Length of output: 6097


🏁 Script executed:

#!/bin/bash
# Search for section and section-body class definitions in the libraries/ui folder
rg "section-body|\.section" libraries/ui/src --type=css --type=scss -B 2 -A 5 2>/dev/null | head -100

# Also check if they're defined in a component
rg "section-body|\.section" libraries/ui/src --type=tsx -B 2 -A 3 2>/dev/null | grep -i "classname\|css" | head -30

# Search the entire apps/website for any CSS that defines these classes
find apps/website -type f \( -name "*.css" -o -name "*.scss" \) -exec grep -l "section" {} \; 2>/dev/null

Repository: bluedotimpact/bluedot

Length of output: 94


🏁 Script executed:

#!/bin/bash
# Check the entire globals.css file to see all content
wc -l apps/website/src/globals.css

# Read the complete globals.css
cat apps/website/src/globals.css

# Check if section/section-body are Tailwind `@apply` classes
grep -n "@apply\|section" apps/website/src/globals.css

Repository: bluedotimpact/bluedot

Length of output: 5849


🏁 Script executed:

#!/bin/bash
# Check the UI library's tailwind config for section definitions
find libraries/ui -name "tailwind.css" -o -name "tailwind.config*" 2>/dev/null

# Look for section in the UI library default config
cat libraries/ui/src/default-config/tailwind.css 2>/dev/null | grep -A 10 "section\|@apply" | head -100

# Also check if section is a standard Tailwind class or custom in UI lib
rg "section\|section-body" libraries/ui/src --type=css -B 2 -A 5 2>/dev/null | head -80

Repository: bluedotimpact/bluedot

Length of output: 3603


🏁 Script executed:

#!/bin/bash
# Verify the px-spacing-x value used by section-body
rg "spacing-x|--spacing-x" libraries/ui/src/default-config/tailwind.css -B 2 -A 2

Repository: bluedotimpact/bluedot

Length of output: 1910


Confirm the bottom-48 (192 px) scroll offset is intentional.

The invisible anchor uses position: relative; bottom: 12rem to shift its rendered position 192 px above its natural layout position. When the browser scrolls to #open-roles-anchor, it positions that visual point at the viewport top, creating 192 px of clearance for the sticky nav (60–76 px on mobile/desktop) and additional scroll context.

This offset is unusually large—more than double the sticky nav height. Confirm this is intentional by checking against the design or scroll behaviour requirements. If not, consider reducing it to align with the nav height plus a modest buffer (e.g., bottom-24 or bottom-32 for 96–128 px total clearance).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/src/components/join-us/JobsListSection.tsx` at line 29, The
invisible anchor div with id "open-roles-anchor" in JobsListSection.tsx
currently uses the utility class "bottom-48" which shifts the anchor 192px
upward; verify this large offset against the design and, if not intentional,
reduce it to match the sticky nav height plus a small buffer (e.g., replace
"bottom-48" with "bottom-24" or "bottom-32") so the scroll target aligns
correctly under the sticky nav; update the class on the <div
id="open-roles-anchor"> element accordingly.

/>
<Breadcrumbs route={currentRoute} />
<Section className="max-w-3xl">
<Section className="max-w-prose">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the Section component from `@bluedot/ui` to check whether mx-auto is applied by default.
rg -n "mx-auto\|margin.*auto\|Section" --type tsx -C 3 packages/ui/src/components/Section.tsx 2>/dev/null \
  || fd -i "Section.tsx" --exec cat {}

Repository: bluedotimpact/bluedot

Length of output: 50378


Add mx-auto to centre the prose block – currently left-aligned without it.

The Section component from @bluedot/ui does not apply mx-auto internally. Without it, the constrained width will hug the left edge rather than centre on wide viewports. This violates the guideline requiring prose sections to use max-w-prose mx-auto.

Proposed fix
-      <Section className="max-w-prose">
+      <Section className="max-w-prose mx-auto">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Section className="max-w-prose">
<Section className="max-w-prose mx-auto">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/src/pages/join-us/`[slug].tsx at line 96, The Section component
on the join-us page is missing the mx-auto utility so the constrained
max-w-prose block is left-aligned; update the JSX where Section is rendered (the
Section component in apps/website/src/pages/join-us/[slug].tsx) to include the
className "max-w-prose mx-auto" (or append "mx-auto" if className already
exists) so the prose block is centered on wide viewports.

Comment on lines +29 to 31
<div className="max-w-prose mx-auto w-full flex flex-col gap-12 lg:gap-14">
<ProgramsList />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect ProgramsList to determine if it renders prose or cards/grids.
fd -i "ProgramsList.tsx" --exec cat {}

Repository: bluedotimpact/bluedot

Length of output: 2152


Use max-w-text instead of max-w-prose for the ProgramsList wrapper.

ProgramsList renders a structured list of program rows (via PageListGroup and PageListRow), not prose content. Per the coding guidelines, structured layouts require max-w-text (840px) or max-w-section-wide (1100px). Constraining it to max-w-prose (~65 characters) will produce unnecessarily cramped rows on desktop.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/website/src/pages/programs.tsx` around lines 29 - 31, The wrapper div
around ProgramsList currently uses the utility class "max-w-prose", which is too
narrow for structured rows; update that div's class to use "max-w-text" instead
of "max-w-prose" so ProgramsList (rendering PageListGroup/PageListRow) has the
appropriate 840px max width for desktop layouts.

@dewierwan

Copy link
Copy Markdown
Contributor Author

@claude review

@claude

claude Bot commented May 6, 2026

Copy link
Copy Markdown

Claude finished @dewierwan's task —— View job


Code Review for Prose Width Capping

I've completed my review of the prose width capping implementation. Here's my comprehensive analysis:

Todo List

  • Read and analyze the key modified component files
  • Check CLAUDE.md changes for consistency
  • Review CodeRabbit findings and validate them
  • Assess the strategic direction (65ch vs 840px debate)
  • Provide comprehensive review feedback

Review Summary

Overall Assessment: ✅ Excellent implementation with a few minor fixes needed.

The changes are clean, consistent, and well-executed. The implementation properly addresses the readability concerns mentioned in the PR description while following established patterns.

Positive Aspects

  1. Consistent Implementation Pattern: All 4 About/Join Us components follow the exact same pattern:

    <div className="max-w-prose mx-auto">
      <h3 className={`${pageSectionHeadingClass} mb-6`}>Title</h3>
      <div className="...">Content</div>
    </div>
  2. Proper Use of Tailwind Utility: max-w-prose is a standard Tailwind utility that caps width at ~65ch, which aligns perfectly with WCAG 1.4.8 guidelines mentioned in the PR.

  3. Correct Centering Strategy: Using mx-auto with max-w-prose ensures the content is centered while maintaining the desired character limit.

  4. Semantic Structure Preserved: The wrapper div doesn't interfere with the semantic structure—headings and content remain properly nested.

  5. Existing Design System Respected:

    • All components continue using pageSectionHeadingClass from the shared import
    • @bluedot/ui components (P) are used consistently
    • No deviation from established spacing patterns (mb-6, gap-5, gap-6)
  6. CLAUDE.md Updated Appropriately: The documentation now clearly distinguishes prose content (max-w-prose) from structured layouts (max-w-text/max-w-section-wide).

Issues Found (from CodeRabbit analysis)

Critical Issues:

  1. apps/website/src/pages/join-us/[slug].tsx:96: Missing mx-auto class. The Section component needs className="max-w-prose mx-auto" instead of just className="max-w-prose" to properly center the content.

  2. apps/website/src/pages/programs.tsx:29: Using max-w-prose for ProgramsList is incorrect. This renders structured program rows (via PageListGroup/PageListRow), not prose content, so it should use max-w-text for proper 840px width.

Minor Issues:
3. apps/website/src/components/incubator-week/WhatThisIsForSection.tsx:65: The "About you" heading is outside the prose wrapper, causing horizontal misalignment with the prose headings above it. This should be wrapped in max-w-prose mx-auto for visual consistency.

  1. apps/website/src/components/join-us/JobsListSection.tsx:29: The anchor uses bottom-48 (192px offset) which seems excessive compared to typical sticky nav heights (60-76px). Consider reducing to bottom-24 or bottom-32.

Strategic Direction: 65ch vs 840px Debate

My recommendation: Stay with 65ch approach for the following reasons:

  1. Accessibility Compliance: WCAG 1.4.8 recommends max 80 characters per line, with 50-75ch being optimal. 840px ≈ 105ch at 16px exceeds these guidelines.

  2. Team Documentation: apps/website/CLAUDE.md:121 explicitly states prose should use max-w-prose for readability.

  3. Proper Content Classification: The current PR correctly distinguishes:

    • Prose content: Paragraphs, markdown, descriptions → max-w-prose (~65ch)
    • Structured layouts: Cards, grids, tables → max-w-text (840px) or max-w-section-wide (1100px)
  4. Complete Solution: Rather than compromising at 840px, completing the sweep to unify all prose at 65ch provides better readability and consistency.

Recommendations

  1. Fix the 4 issues identified above - they are genuine bugs that need addressing.

  2. Complete the expanded sweep as suggested in Dewi's counter-proposal to unify the entire site:

    • Lander prose sections: WhoIsThisForTextSection, HowTheCourseWorksSection, etc. → max-w-prose
    • Policy/markdown pages → max-w-prose
    • Keep card/grid sections at max-w-text/max-w-section-wide
  3. Clarify CLAUDE.md: The apparent contradiction between "avoid max-width" and "use max-w-prose" could be resolved with a bridging sentence.

Technical Details

  • Files affected: 37 files with consistent patterns
  • Change scope: Minimal and targeted—only adds necessary wrapper divs
  • Test Coverage: All snapshots properly updated
  • Accessibility: Significantly improves readability per WCAG guidelines

The PR demonstrates excellent technical execution and addresses a real accessibility concern. After fixing the 4 identified issues, this will be ready for merge.


View job

@dewierwan dewierwan changed the title [style] cap prose at max-w-prose on About + Join Us [style] unify body prose at max-w-prose across public site May 6, 2026
@Will-Howard

Copy link
Copy Markdown
Collaborator

So the counter: keep this PR at max-w-prose AND fold in the lander prose sections + policy/missions/community markdown wrappers in this same PR so the whole site lands at one readable width. That's the unification you're after, just at the readability-respecting endpoint.

@dewierwan on the counter-proposal:

IMO the main issue with this is that it changes things across the site, which practically means we need to audit every place that is affected and check for any visual issues. The difference between max-w-text and max-w-prose is 840px vs ~660px, so in practice it doesn't make much difference (whereas the /join-us and /about pages were originally on 1276px, which is hard to read)

There are ~20 pages in principle affected. I just spot checked a few and did find some new issues of mixed-widths (this is /programs/incubator-week and /courses/agi-strategy):
Screenshot 2026-05-07 at 09 35 37
Screenshot 2026-05-07 at 09 45 07

On the CLAUDE.md point: I think this is probably a general best practice added by Claude rather than a long-term rule we endorse. And per the above the difference between max-w-text and max-w-prose is small, so I would suggest removing that from CLAUDE.md.

Still happy to jump in if you want!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants