From 4a70bc3f5ac1c32a17628abcacbf3e2e92df8428 Mon Sep 17 00:00:00 2001 From: jinhojang6 Date: Fri, 19 Jun 2026 00:31:22 +0900 Subject: [PATCH] fix: update responsive styles and improve layout across various components --- .gitignore | 4 + .../lambda-prize/_sections/support.tsx | 14 +-- .../sections/builders-hub/rfp-list-row.tsx | 4 +- .../research/_sections/contribute-section.tsx | 2 +- .../research/_sections/overview-section.tsx | 10 +-- .../sections/shared/related-articles-card.tsx | 6 +- .../shared/tech-stack-related-articles.tsx | 88 +++++++++---------- .../lib/__tests__/rfps-github-links.test.ts | 49 +++++++++++ apps/web/lib/rfps-github.ts | 47 +++++++++- 9 files changed, 161 insertions(+), 63 deletions(-) create mode 100644 apps/web/lib/__tests__/rfps-github-links.test.ts diff --git a/.gitignore b/.gitignore index b75786e620..ae662d2bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,7 @@ apps/*/public/fonts/**/*.otf mcp.json .env*.local + +# Playwright / browser-MCP artifacts (never commit) +.playwright-mcp/ +**/.playwright-mcp/ diff --git a/apps/web/app/[locale]/lambda-prize/_sections/support.tsx b/apps/web/app/[locale]/lambda-prize/_sections/support.tsx index 29c0fb129b..4e7851953d 100644 --- a/apps/web/app/[locale]/lambda-prize/_sections/support.tsx +++ b/apps/web/app/[locale]/lambda-prize/_sections/support.tsx @@ -6,22 +6,22 @@ import type { LambdaPrizePageCopy } from './types' export function Support({ copy }: { copy: LambdaPrizePageCopy['support'] }) { return ( -
+
-
+

{copy.heading}

-

+

{copy.body}

-
+
{copy.cta}
-
+
{copy.rows.map((row, index) => (
@@ -31,7 +31,7 @@ export function Support({ copy }: { copy: LambdaPrizePageCopy['support'] }) {

{row.label}

-

+

{row.body}

diff --git a/apps/web/components/sections/builders-hub/rfp-list-row.tsx b/apps/web/components/sections/builders-hub/rfp-list-row.tsx index f6c233a107..4975e5d1c6 100644 --- a/apps/web/components/sections/builders-hub/rfp-list-row.tsx +++ b/apps/web/components/sections/builders-hub/rfp-list-row.tsx @@ -29,7 +29,7 @@ export function RfpListRow({ index, rfp }: Props) { > {/* Mobile / tablet (below 1025px): 2-column flow grid so a long title pushes the description down instead of overlapping it. */} -
+

{indexLabel} {rfp.title} @@ -56,7 +56,7 @@ export function RfpListRow({ index, rfp }: Props) { {/* Desktop (1025px+): single flex row. The reward + CTA block is shrink-0 so it always stays inside the table; title and description truncate as the window narrows. */} -

+
{indexLabel} diff --git a/apps/web/components/sections/research/_sections/contribute-section.tsx b/apps/web/components/sections/research/_sections/contribute-section.tsx index 7e68f53f0a..e389b00c6a 100644 --- a/apps/web/components/sections/research/_sections/contribute-section.tsx +++ b/apps/web/components/sections/research/_sections/contribute-section.tsx @@ -17,7 +17,7 @@ export function ContributeSection({ jobs: ReactNode }) { return ( -
+

{title} diff --git a/apps/web/components/sections/research/_sections/overview-section.tsx b/apps/web/components/sections/research/_sections/overview-section.tsx index 4c499ff95d..3ea5e8de06 100644 --- a/apps/web/components/sections/research/_sections/overview-section.tsx +++ b/apps/web/components/sections/research/_sections/overview-section.tsx @@ -15,17 +15,17 @@ export function OverviewSection({ cta: LinkItem }) { return ( -
- -

+
+ +

{title}

-
+
{paragraphs.map((paragraph, index) => (

{paragraph}

))}
-
+
diff --git a/apps/web/components/sections/shared/related-articles-card.tsx b/apps/web/components/sections/shared/related-articles-card.tsx index ee54ccb61c..75cb7f7b4b 100644 --- a/apps/web/components/sections/shared/related-articles-card.tsx +++ b/apps/web/components/sections/shared/related-articles-card.tsx @@ -52,7 +52,7 @@ export function ArticleCard({ return (
{mobileTitle ? ( <> - {mobileTitle} - {title} + {mobileTitle} + {title} ) : ( title diff --git a/apps/web/components/sections/shared/tech-stack-related-articles.tsx b/apps/web/components/sections/shared/tech-stack-related-articles.tsx index d9d9457562..cba3ecda1d 100644 --- a/apps/web/components/sections/shared/tech-stack-related-articles.tsx +++ b/apps/web/components/sections/shared/tech-stack-related-articles.tsx @@ -11,7 +11,7 @@ import { getTechStackRelatedArticleCards } from '@/lib/tech-stack-related-articl import { ArticleCard } from './related-articles-card' const TITLE_CLASSNAME = - 'text-caption-sans w-[169.5px] shrink-0 font-medium text-brand-dark-green min-[1025px]:text-[14px] min-[1025px]:leading-[1.2]' + 'text-caption-sans w-[169.5px] shrink-0 font-medium text-brand-dark-green lg:text-[14px] lg:leading-[1.2]' type Props = { data: RelatedArticlesSection @@ -36,51 +36,51 @@ export default function TechStackRelatedArticles({ className={twMerge('h-220 px-3 pb-3 pt-0', contentClassName)} > -
- {data.label ? ( -

- {data.mobileLabel ?? data.label} -

- ) : null} - {data.eyebrow ? ( - // xl, not min-[1025px]: the fixed left-178.5 offset collides with the - // right-anchored CTA on viewports narrower than ~1190px. -

- {data.eyebrow} -

- ) : null} - {data.cta ? ( -
- -
- ) : null} -
- -

- {data.title} -

- - - {cards.map((card) => ( - - - - ))} +
+ {data.label ? ( +

+ {data.mobileLabel ?? data.label} +

+ ) : null} + {data.eyebrow ? ( + // xl, not lg: the fixed left-178.5 offset collides with the + // right-anchored CTA on viewports narrower than ~1190px. +

+ {data.eyebrow} +

+ ) : null} + {data.cta ? ( +
+ +
+ ) : null} +
+ +

+ {data.title} +

+ + + {cards.map((card) => ( + + + + ))} +
-
diff --git a/apps/web/lib/__tests__/rfps-github-links.test.ts b/apps/web/lib/__tests__/rfps-github-links.test.ts new file mode 100644 index 0000000000..e327d0765f --- /dev/null +++ b/apps/web/lib/__tests__/rfps-github-links.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, it } from 'vitest' + +import { rewriteRfpMarkdownLinks } from '@/lib/rfps-github' + +const FILE_URL = + 'https://github.com/logos-co/rfp/blob/master/RFPs/RFP-016-lbp-launchpad.md' + +describe('rewriteRfpMarkdownLinks', () => { + it('rewrites sibling RFP cross-references to internal detail routes', () => { + const md = 'See [LBP](./RFP-008-lending-borrowing-protocol.md) for context.' + + expect(rewriteRfpMarkdownLinks(md, FILE_URL)).toBe( + 'See [LBP](/builders-hub/rfps/lending-borrowing-protocol) for context.' + ) + }) + + it('rewrites RFP references without a leading ./', () => { + const md = '[admin](RFP-001-admin-authority-lib.md)' + + expect(rewriteRfpMarkdownLinks(md, FILE_URL)).toBe( + '[admin](/builders-hub/rfps/admin-authority-lib)' + ) + }) + + it('resolves other repo-relative .md links to absolute GitHub URLs, keeping anchors', () => { + const md = + '[fees](../appendix/token-launchpad-ecosystem.md#fee-structures)' + + expect(rewriteRfpMarkdownLinks(md, FILE_URL)).toBe( + '[fees](https://github.com/logos-co/rfp/blob/master/appendix/token-launchpad-ecosystem.md#fee-structures)' + ) + }) + + it('leaves external, absolute, and anchor links unchanged', () => { + const md = [ + '[ext](https://github.com/logos-co/lambda-prize/blob/master/prizes/LP-0004.md)', + '[abs](/builders-hub/rfps)', + '[frag](#overview)', + ].join('\n') + + expect(rewriteRfpMarkdownLinks(md, FILE_URL)).toBe(md) + }) + + it('leaves relative non-markdown links untouched', () => { + const md = '![diagram](./images/flow.png)' + + expect(rewriteRfpMarkdownLinks(md, FILE_URL)).toBe(md) + }) +}) diff --git a/apps/web/lib/rfps-github.ts b/apps/web/lib/rfps-github.ts index 16ebb6b96d..88256f13b8 100644 --- a/apps/web/lib/rfps-github.ts +++ b/apps/web/lib/rfps-github.ts @@ -1,6 +1,7 @@ import matter from 'gray-matter' import { cache } from 'react' +import { ROUTES } from '@/constants/routes' import type { RfpListItem } from '@/lib/rfp-types' /** @@ -73,6 +74,50 @@ const toSlug = (filename: string, fallback: string): string => { return slug || fallback.toLowerCase() } +/** Matches the URL target of a markdown inline link/image: `](target)`. */ +const MARKDOWN_LINK_TARGET = /\]\(\s*(<[^>]+>|[^)\s]+)\s*\)/g + +const isAbsoluteOrAnchor = (href: string): boolean => + /^(https?:\/\/|\/|#|mailto:)/i.test(href) + +/** + * Rewrites repo-relative markdown links so RFP detail pages don't 404. Upstream + * RFP files cross-reference each other with paths relative to the repo's + * `RFPs/` directory (e.g. `./RFP-008-lending-borrowing-protocol.md`), which the + * browser would otherwise resolve against the page URL and 404. + * + * - `RFP-NNN-*.md` references become the internal detail route + * (`/builders-hub/rfps/`), using the same slug the pages are built with. + * - Other repo-relative `.md` links (e.g. `../appendix/…`) have no site page, so + * they resolve to their absolute GitHub URL (anchors preserved). + * - Absolute, external, and anchor links are left untouched. + * + * `fileHtmlUrl` is the GitHub `blob` URL of the file being parsed; it anchors + * the resolution of the remaining relative links. + */ +export const rewriteRfpMarkdownLinks = ( + markdown: string, + fileHtmlUrl: string +): string => + markdown.replace(MARKDOWN_LINK_TARGET, (match, rawTarget: string) => { + const href = rawTarget.replace(/^<|>$/g, '') + if (isAbsoluteOrAnchor(href)) return match + + const [path] = href.split('#') + if (!/\.md$/i.test(path)) return match + + const filename = path.split('/').pop() ?? '' + if (/^RFP-\d+/i.test(filename)) { + return `](${ROUTES.rfps}/${toSlug(filename, filename)})` + } + + try { + return `](${new URL(href, fileHtmlUrl).href})` + } catch { + return match + } + }) + const firstMatch = (raw: string, patterns: RegExp[]): string | undefined => { for (const pattern of patterns) { const value = raw.match(pattern)?.[1]?.replace(/[`*]/g, '').trim() @@ -169,7 +214,7 @@ const parseRfpMarkdown = ( status, tier: tier ?? '', githubUrl: htmlUrl, - rawMarkdown: content.trim(), + rawMarkdown: rewriteRfpMarkdownLinks(content.trim(), htmlUrl), } }