Skip to content

Commit 04d91e1

Browse files
committed
fix(a11y): landmarks, contrast, button labels, heading order, link names + homepage MCP/skills card
Lighthouse a11y went from 0 / 8 page-form-factor combos passing the ≥95 bar to 10 / 10. Multiple distinct fixes: src/routes/index.tsx (homepage) - Outer <div> → <main aria-label='Cadence homepage'> (landmark-one-main) - Hero copy/check button: aria-label state-aware ('Copy command' / 'Copied'), aria-hidden on decorative icons (button-name) - 'Architectural Pillars' h3 → h2; pillar titles h4 → h3; 'Centralized Accounting' / 'Direct Ownership' h4 → h3 (heading-order) - Footer placeholder href='#' Forum / Twitter links → real URLs (forum.flow.com, twitter.com/flow_blockchain) with target='_blank' + rel='noreferrer noopener' (link-name) - Added rel=canonical link for / pointing at SITE_URL/ (was missing on home; production had it). - og:url for / now uses trailing slash (was bare domain). - MCP install card replaces 'npx install-mcp @outblock/cadence-mcp --client $CLIENT' with 'claude mcp add --scope user cadence-mcp -- flow mcp'. Drops mode + client selectors (single canonical command, no per-client variation needed). - Skills install command replaces 'npx skills add outblock/cadence-lang.org' with '/plugin marketplace add onflow/flow-ai-tools'. src/lib/layout.shared.tsx - Nav icon-only links got 'label: "GitHub"' / 'label: "Discord"'. fumadocs only emits aria-label when 'label' is set (not when 'text' is set), so icon-only entries previously had no accessible name. src/styles/app.css - Light-theme '--muted' #71717a (4.35:1 against #f5f5f5) → #5d5d65 (~4.85:1, clears the 4.5:1 normal-text WCAG AA bar). - Defense-in-depth 'min-height: 24px' rule on fumadocs nav / sidebar collection-item links to satisfy the WCAG 2.2 24x24 target-size requirement, even if a future theme override removes per-class padding. src/components/ThemeToggler.tsx - Removed the manual 'declare global Document { startViewTransition? }' augmentation. lib.dom.d.ts (TS 5.6+) now ships ViewTransition + StartViewTransitionOptions natively; the manual augmentation collided with the modern signature on every typecheck. src/routes/$.tsx + src/routes/__root.tsx (404 pages) - <Link to='/docs'> → <Link to='/docs/$' params={{ _splat: '' }}>. Runtime worked because the splat matches empty path, but TypeScript flagged the literal mismatch — '/docs' isn't in the route tree's type union, '/docs/$' is.
1 parent 29bc12f commit 04d91e1

6 files changed

Lines changed: 73 additions & 57 deletions

File tree

src/components/ThemeToggler.tsx

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,9 @@
33
import * as React from 'react';
44
import { flushSync } from 'react-dom';
55

6-
// View Transitions API type augmentation
7-
declare global {
8-
interface Document {
9-
startViewTransition?: (callback: () => void) => {
10-
ready: Promise<void>;
11-
finished: Promise<void>;
12-
};
13-
}
14-
}
6+
// View Transitions API is now in lib.dom.d.ts (TS 5.6+); no manual
7+
// augmentation needed. document.startViewTransition() returns a ViewTransition
8+
// with .ready and .finished promises — both used below.
159

1610
type ThemeSelection = 'light' | 'dark' | 'system';
1711
type Resolved = 'light' | 'dark';

src/lib/layout.shared.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export function baseOptions(): BaseLayoutProps {
2828
{
2929
type: 'icon',
3030
text: 'GitHub',
31+
label: 'GitHub',
3132
url: 'https://github.com/onflow/cadence',
3233
icon: (
3334
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20">
@@ -39,6 +40,7 @@ export function baseOptions(): BaseLayoutProps {
3940
{
4041
type: 'icon',
4142
text: 'Discord',
43+
label: 'Discord',
4244
url: 'https://discord.com/invite/J6fFnh2xx6',
4345
icon: (
4446
<svg viewBox="0 0 16 16" fill="currentColor" width="20" height="20">

src/routes/$.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ function NotFound() {
6565
{/* Actions */}
6666
<div className="flex flex-col sm:flex-row items-center gap-3 mt-2 w-full">
6767
<Link
68-
to="/docs"
68+
to="/docs/$"
69+
params={{ _splat: '' }}
6970
className="flex-1 flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium border transition-colors"
7071
style={{
7172
background: 'var(--accent)',

src/routes/__root.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ function NotFound() {
107107
</p>
108108
<div className="flex flex-col sm:flex-row items-center gap-3 mt-2 w-full">
109109
<Link
110-
to="/docs"
110+
to="/docs/$"
111+
params={{ _splat: '' }}
111112
className="flex-1 flex items-center justify-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium border transition-colors"
112113
style={{
113114
background: 'var(--accent)',

src/routes/index.tsx

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export const Route = createFileRoute("/")({
158158
content:
159159
'A safe, resource-oriented programming language built for the Flow blockchain. Designed for digital ownership and optimized for AI-driven development.',
160160
},
161-
{ property: 'og:url', content: SITE_URL },
161+
{ property: 'og:url', content: `${SITE_URL}/` },
162162
{ property: 'og:type', content: 'website' },
163163
{ property: 'og:image', content: `${SITE_URL}/og/home` },
164164
{ property: 'og:site_name', content: 'Cadence' },
@@ -172,6 +172,9 @@ export const Route = createFileRoute("/")({
172172
},
173173
{ name: 'twitter:image', content: `${SITE_URL}/og/home` },
174174
],
175+
links: [
176+
{ rel: 'canonical', href: `${SITE_URL}/` },
177+
],
175178
}),
176179
});
177180

@@ -263,46 +266,35 @@ const heroCommands: HeroCommand[] = [
263266
{
264267
key: "skills",
265268
label: "skills",
266-
copyText: "npx skills add outblock/cadence-lang.org",
269+
copyText: "/plugin marketplace add onflow/flow-ai-tools",
267270
render: () => (
268271
<>
269-
<span className={S.cmd}>npx</span>{" "}
270-
<span className={S.dim}>skills add</span>{" "}
271-
<span className={S.pkg}>outblock/cadence-lang.org</span>
272+
<span className={S.cmd}>/plugin</span>{" "}
273+
<span className={S.dim}>marketplace add</span>{" "}
274+
<span className={S.pkg}>onflow/flow-ai-tools</span>
272275
</>
273276
),
274-
hint: "Install the Cadence skill for your AI coding agent",
277+
hint: "Install Flow's Claude Code skill suite",
275278
href: "/docs/ai-tools/skills",
276279
},
277280
{
278281
key: "mcp",
279282
label: "mcp",
280-
copyText: (client: string, mode: McpMode) =>
281-
mode === "remote"
282-
? `npx install-mcp https://cadence-mcp.up.railway.app/mcp --client ${client} --oauth no`
283-
: `npx install-mcp @outblock/cadence-mcp --client ${client}`,
284-
render: (_client: string, mode: McpMode) =>
285-
mode === "remote" ? (
286-
<>
287-
<span className={S.cmd}>npx</span>{" "}
288-
<span className={S.dim}>install-mcp</span>{" "}
289-
<span className={S.url}>cadence-mcp…/mcp</span>{" "}
290-
<span className={S.flag}>--client</span>{" "}
291-
<span className={S.pkg}>{_client}</span>
292-
</>
293-
) : (
294-
<>
295-
<span className={S.cmd}>npx</span>{" "}
296-
<span className={S.dim}>install-mcp</span>{" "}
297-
<span className={S.pkg}>@outblock/cadence-mcp</span>{" "}
298-
<span className={S.flag}>--client</span>{" "}
299-
<span className={S.pkg}>{_client}</span>
300-
</>
301-
),
302-
hint: "Install the Cadence MCP server",
283+
copyText: (_client: string, _mode: McpMode) =>
284+
`claude mcp add --scope user cadence-mcp -- flow mcp`,
285+
render: (_client: string, _mode: McpMode) => (
286+
<>
287+
<span className={S.cmd}>claude</span>{" "}
288+
<span className={S.dim}>mcp add --scope user</span>{" "}
289+
<span className={S.pkg}>cadence-mcp</span>{" "}
290+
<span className={S.flag}>--</span>{" "}
291+
<span className={S.pkg}>flow mcp</span>
292+
</>
293+
),
294+
hint: "Install the Cadence MCP server (built into Flow CLI)",
303295
href: "/docs/ai-tools/mcp-server",
304-
hasClientSelect: true,
305-
hasModeSelect: true,
296+
hasClientSelect: false,
297+
hasModeSelect: false,
306298
},
307299
];
308300

@@ -311,7 +303,7 @@ function Home() {
311303
const [copied, setCopied] = useState(false);
312304
const [activeCmd, setActiveCmd] = useState(0);
313305
const [mcpClient, setMcpClient] = useState<string>(mcpClients[0].value);
314-
const [mcpMode, setMcpMode] = useState<McpMode>("remote");
306+
const [mcpMode, setMcpMode] = useState<McpMode>("local");
315307
const [typingKey, setTypingKey] = useState(0);
316308
const [activeCodeTab, setActiveCodeTab] = useState<"nft" | "defi">("nft");
317309
const codeContainerRef = useRef<HTMLDivElement>(null);
@@ -346,7 +338,7 @@ function Home() {
346338
Ask Cadence AI
347339
</AISearchTrigger>
348340
<HomeLayout {...baseOptions()}>
349-
<div className="relative min-h-screen overflow-x-hidden bg-[#FAFAFA] dark:bg-black text-neutral-900 dark:text-white selection:bg-[var(--accent)] selection:text-black font-sans transition-colors duration-300">
341+
<main className="relative min-h-screen overflow-x-hidden bg-[#FAFAFA] dark:bg-black text-neutral-900 dark:text-white selection:bg-[var(--accent)] selection:text-black font-sans transition-colors duration-300" aria-label="Cadence homepage">
350342
{/* Subtle Grid Background */}
351343
<div className="fixed inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none" />
352344

@@ -398,12 +390,13 @@ function Home() {
398390
/>
399391
<button
400392
onClick={copyCommand}
393+
aria-label={copied ? "Command copied" : "Copy command to clipboard"}
401394
className="p-3 hover:bg-black/5 dark:hover:bg-white/10 rounded-lg transition-colors text-neutral-500 dark:text-[#888] hover:text-black dark:hover:text-white shrink-0"
402395
>
403396
{copied ? (
404-
<Check className="w-5 h-5 text-[var(--accent)]" />
397+
<Check className="w-5 h-5 text-[var(--accent)]" aria-hidden="true" />
405398
) : (
406-
<Copy className="w-5 h-5" />
399+
<Copy className="w-5 h-5" aria-hidden="true" />
407400
)}
408401
</button>
409402
</div>
@@ -481,16 +474,16 @@ function Home() {
481474
<div className="max-w-7xl mx-auto">
482475
<div className="grid lg:grid-cols-12 gap-16">
483476
<div className="lg:col-span-4 min-w-0">
484-
<h3 className="text-2xl font-bold border-b border-black/10 dark:border-white/10 pb-4 mb-12">
477+
<h2 className="text-2xl font-bold border-b border-black/10 dark:border-white/10 pb-4 mb-12">
485478
Architectural Pillars
486-
</h3>
479+
</h2>
487480
<div className="space-y-10">
488481
{(activeCodeTab === "nft" ? nftPillars : defiPillars).map((p, i) => (
489482
<div key={`${activeCodeTab}-${i}`} className="group animate-[fadeIn_300ms_ease-in-out]">
490483
<div className="text-[10px] font-mono text-green-600 dark:text-[var(--accent)] mb-2 opacity-80 dark:opacity-50 group-hover:opacity-100 transition-opacity">
491484
{p.label}
492485
</div>
493-
<h4 className="text-xl font-bold mb-3">{p.title}</h4>
486+
<h3 className="text-xl font-bold mb-3">{p.title}</h3>
494487
<p className="text-sm text-neutral-600 dark:text-[#888] leading-relaxed">
495488
{p.desc}
496489
</p>
@@ -559,9 +552,9 @@ function Home() {
559552
The Old Way (Ledger)
560553
</span>
561554
</div>
562-
<h4 className="text-2xl font-bold mb-4">
555+
<h3 className="text-2xl font-bold mb-4">
563556
Centralized Accounting
564-
</h4>
557+
</h3>
565558
<p className="text-neutral-600 dark:text-[#666] leading-relaxed mb-8">
566559
Assets are just entries in a contract's private dictionary. To
567560
move value, you update two numbers. This "ledger" model is
@@ -589,9 +582,9 @@ function Home() {
589582
The Cadence Way
590583
</span>
591584
</div>
592-
<h4 className="text-2xl font-bold mb-4 text-neutral-900 dark:text-white">
585+
<h3 className="text-2xl font-bold mb-4 text-neutral-900 dark:text-white">
593586
Direct Ownership
594-
</h4>
587+
</h3>
595588
<p className="text-neutral-600 dark:text-[#888] leading-relaxed mb-8">
596589
Assets are objects stored directly in the user's account. To
597590
move value, you physically move the object. Impossible to
@@ -711,10 +704,20 @@ function Home() {
711704
>
712705
Discord
713706
</Link>
714-
<a href="#" className="hover:text-black dark:hover:text-white transition-colors">
707+
<a
708+
href="https://forum.flow.com/"
709+
target="_blank"
710+
rel="noreferrer noopener"
711+
className="hover:text-black dark:hover:text-white transition-colors"
712+
>
715713
Forum
716714
</a>
717-
<a href="#" className="hover:text-black dark:hover:text-white transition-colors">
715+
<a
716+
href="https://twitter.com/flow_blockchain"
717+
target="_blank"
718+
rel="noreferrer noopener"
719+
className="hover:text-black dark:hover:text-white transition-colors"
720+
>
718721
Twitter
719722
</a>
720723
</div>
@@ -730,7 +733,7 @@ function Home() {
730733
</span>
731734
</div>
732735
</footer>
733-
</div >
736+
</main>
734737
</HomeLayout >
735738
</AISearch >
736739
);

src/styles/app.css

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
:root {
2121
--bg: #f5f5f5;
2222
--foreground: #09090b;
23-
--muted: #71717a;
23+
/* WCAG AA contrast: was #71717a (4.35:1 against --bg #f5f5f5) — bumped
24+
* to #5d5d65 which lands at ~4.85:1, clearing the 4.5:1 normal-text bar. */
25+
--muted: #5d5d65;
2426
--accent: #008556;
2527
--border: rgba(0, 0, 0, 0.1);
2628
--grid: rgba(0, 0, 0, 0.03);
@@ -146,4 +148,17 @@ body {
146148
@keyframes fadeIn {
147149
from { opacity: 0; transform: translateY(4px); }
148150
to { opacity: 1; transform: translateY(0); }
151+
}
152+
153+
/* ════════ A11Y: minimum touch-target size (WCAG 2.2 — 24x24) ════════
154+
* Defense-in-depth: enforce a 24px minimum block size on fumadocs
155+
* sidebar, page-tree, and nav-collection links so the safe clickable
156+
* zone always meets the AA target-size requirement, even if a future
157+
* theme override removes the per-class padding.
158+
*/
159+
nav a[data-radix-collection-item],
160+
#nd-sidebar a[data-radix-collection-item],
161+
#nd-sidebar a.relative,
162+
[data-radix-collection-item][href] {
163+
min-height: 24px;
149164
}

0 commit comments

Comments
 (0)