Skip to content

Commit dc04df2

Browse files
style: visual redesign — theme tokenization, WCAG fixes, PlotOfTheDay redesign (#5235)
## Summary - **Theme tokenization**: Replace 80+ hardcoded hex colors and font strings across 30 files with centralized theme tokens (`app/src/theme/index.ts`) - **WCAG AA compliance**: Fix 15+ contrast violations where `#9ca3af` (2.9:1 ratio) was used for text — all text now meets 4.5:1 minimum - **PlotOfTheDay redesign**: Terminal-style frame with `$ python` prompt, split layout (image left, info right), fade-in animation, GitHub link to source file, real library/Python versions from API - **Responsive breadcrumb**: `pyplots.ai` → `pp`, `matplotlib` → `mpl` on mobile via new `shortLabel` prop - **New theme tokens**: `fontSize.micro/xxs`, `colors.primaryDark/accentBg/codeBlock/highlight/tooltipLight`, shared style constants (`headingStyle`, `subheadingStyle`, `textStyle`, `codeBlockStyle`, `tableStyle`) - **Code quality**: Fix 3 pre-existing lint errors (setState in useEffect), fix SpecPage useCallback warning, remove unused ImageLightbox, centralize `LIB_ABBREV` - **Style guide**: Complete documentation with new sections (data-dense typography, page headings, code blocks, tables, chart colors, gray usage rule) ## Test plan - [ ] Visual check all pages: Home, Spec (overview + detail), Catalog, Legal, MCP, Stats, Debug, NotFound, Interactive - [ ] Verify PlotOfTheDay fade-in animation and terminal frame on desktop + mobile - [ ] Verify breadcrumb abbreviations on mobile (< 600px) - [ ] Verify WCAG contrast with browser DevTools (all text ≥ 4.5:1) - [ ] Confirm 0 lint errors (`cd app && yarn lint`) - [ ] Confirm build passes (`cd app && yarn build`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1a1b552 commit dc04df2

38 files changed

+1573
-775
lines changed

.claude/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
"Edit",
66
"Bash",
77
"WebFetch",
8-
"mcp__plugin_serena_serena__*",
98
"mcp__plugin_context7_context7__*",
10-
"mcp__plugin_playwright_playwright__*"
9+
"mcp__plugin_playwright_playwright__*",
10+
"mcp__serena__*"
1111
],
1212
"ask": [
1313
"Bash(git commit *)",

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,3 +241,4 @@ agentic/runs/
241241
docker-compose.override.yml
242242
secrets/
243243
/.playwright-mcp/
244+
/screenshots/

.serena/memories/code_style.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
- **Hooks**: camelCase with `use` prefix (e.g., `useSpecs.ts`)
1919
- **Utils/Types**: camelCase files (e.g., `api.ts`)
2020
- **Exports**: Named exports (no default exports)
21-
- **Styling**: MUI `sx` prop + Emotion `styled()`, no CSS modules
22-
- **State**: Local state + custom hooks (no Redux/Zustand)
21+
- **Styling**: MUI `sx` prop + theme tokens from `app/src/theme/index.ts`. No hardcoded hex colors — use `colors.*`, `semanticColors.*`, `fontSize.*`, `typography.fontFamily` imports. See Serena memory `style_guide` for full token reference.
22+
- **State**: Local state + custom hooks (no Redux/Zustand). Avoid `setState` inside `useEffect` for prop resets — use render-time ref pattern instead.
2323
- **API calls**: Plain `fetch()` in `utils/api.ts`
2424

2525
## Agentic Layer Conventions

.serena/memories/style_guide.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Frontend Style Guide
2+
3+
All visual values are centralized in `app/src/theme/index.ts`. Never use hardcoded hex colors or font strings — always import tokens.
4+
5+
## Imports
6+
```ts
7+
import { typography, colors, semanticColors, fontSize, spacing } from '../theme';
8+
// Shared style constants:
9+
import { headingStyle, subheadingStyle, textStyle, codeBlockStyle, tableStyle, labelStyle, monoText } from '../theme';
10+
```
11+
12+
## Colors
13+
- **Brand**: `colors.primary` (#3776AB), `colors.accent` (#FFD43B), `colors.primaryDark` (#306998)
14+
- **Gray scale**: `colors.gray[50]` to `colors.gray[900]`
15+
- **Semantic text**: `semanticColors.labelText` (7.0:1), `semanticColors.subtleText` (5.8:1), `semanticColors.mutedText` (4.6:1)
16+
- **Status**: `colors.success`, `colors.error`, `colors.warning`, `colors.info`
17+
- **Backgrounds**: `colors.background`, `colors.accentBg`
18+
- **Code blocks**: `colors.codeBlock.bg`, `colors.codeBlock.text`
19+
20+
## WCAG Rule
21+
`colors.gray[400]` (#9ca3af) and lighter **must never be used for text or icons**. Minimum text color: `semanticColors.mutedText` (#6b7280, 4.6:1 contrast).
22+
23+
## Font
24+
Always use `typography.fontFamily` — never inline `'"MonoLisa", monospace'` or raw `'monospace'`.
25+
**Exception**: ErrorBoundary uses raw `'monospace'` intentionally (crash-safe fallback).
26+
27+
## Font Sizes
28+
`fontSize.micro` (0.5rem) and `fontSize.xxs` (0.625rem) are restricted to data-dense pages (StatsPage, DebugPage).
29+
Public pages use `fontSize.xs` (0.75rem) as minimum.
30+
Full scale: micro, xxs, xs, sm, md, base, lg, xl.
31+
32+
## Shared Constants
33+
- `headingStyle` — page h2 (1.25rem, 600, gray.800)
34+
- `subheadingStyle` — page h3 (1rem, 600, gray.700)
35+
- `textStyle` — body text (0.9375rem, labelText, lineHeight 1.8)
36+
- `codeBlockStyle` — dark code blocks
37+
- `tableStyle` — consistent table cells/headers
38+
- `labelStyle` — small labels (0.875rem, labelText)
39+
40+
## Highlight Colors
41+
Use theme tokens for highlight treatments: `colors.highlight.bg`/`colors.highlight.text` for highlighted tag chips and `colors.tooltipLight` for tooltip text on dark backgrounds. Do not reintroduce hardcoded highlight hex values.
42+
43+
## Full reference
44+
See `docs/reference/style-guide.md` for complete documentation including spacing, breakpoints, component specs, animations, and accessibility rules.

agentic/commands/prime.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,25 @@ gh pr list --limit 10 2>/dev/null || echo "(gh CLI not available)"
2424

2525
## Serena
2626

27-
- Run `activate_project` with project "pyplots"
28-
- Run `list_memories` and read relevant ones
2927
- Run `check_onboarding_performed`
28+
- Run `list_memories` and read relevant ones
29+
30+
### JetBrains Tools (prefer over brute-force scanning)
31+
32+
Use Serena's JetBrains-backed tools for code navigation — they provide semantic understanding
33+
that grep/glob cannot:
34+
35+
- `jet_brains_get_symbols_overview` — get top-level symbols in a file (classes, functions, variables). Use with `depth: 1` to also see methods of classes. Start here to understand a file before diving deeper.
36+
- `jet_brains_find_symbol` — search for a symbol by name across the codebase. Supports name path patterns like `MyClass/my_method`. Use `include_body: true` to read source code, `include_info: true` for docstrings/signatures.
37+
- `jet_brains_find_referencing_symbols` — find all usages of a symbol (who calls this function? who imports this class?). Essential for understanding impact of changes.
38+
- `jet_brains_find_declaration` — jump to where a symbol is defined.
39+
- `jet_brains_find_implementations` — find implementations of an interface/abstract class.
40+
- `jet_brains_type_hierarchy` — understand class inheritance chains.
3041

31-
Prefer Serena's symbolic tools (`jet_brains_find_symbol`, `jet_brains_get_symbols_overview`,
32-
`jet_brains_find_referencing_symbols`) over brute-force file scanning.
42+
### Editing via Serena
3343

34-
Use Serena's thinking tools to maintain focus:
44+
For structural edits, prefer Serena's symbol-aware tools over raw text replacement:
3545

36-
- `think_about_collected_information` - after research/search sequences
37-
- `think_about_task_adherence` - before making code changes
38-
- `think_about_whether_you_are_done` - when task seems complete
46+
- `replace_symbol_body` — replace an entire function/class body
47+
- `insert_after_symbol` / `insert_before_symbol` — add code relative to a symbol
48+
- `search_for_pattern` — regex search across the codebase (fast, flexible)

api/routers/insights.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ class PlotOfTheDayResponse(BaseModel):
109109
preview_url: str | None = None
110110
image_description: str | None = None
111111
code: str | None = None
112+
library_version: str | None = None
113+
python_version: str | None = None
112114
date: str
113115

114116

@@ -397,6 +399,8 @@ async def _build_potd(spec_repo: SpecRepository, impl_repo: ImplRepository) -> P
397399
preview_url=preview_url,
398400
image_description=full_impl.review_image_description if full_impl else None,
399401
code=strip_noqa_comments(full_impl.code) if full_impl and full_impl.code else None,
402+
library_version=full_impl.library_version if full_impl else None,
403+
python_version=full_impl.python_version if full_impl else None,
400404
date=today,
401405
)
402406

@@ -525,7 +529,7 @@ async def _build_related(
525529
@router.get("/related/{spec_id}", response_model=RelatedSpecsResponse)
526530
async def get_related_specs(
527531
spec_id: str,
528-
limit: int = Query(default=6, ge=1, le=12),
532+
limit: int = Query(default=6, ge=1, le=24),
529533
mode: str = Query(default="spec", pattern="^(spec|full)$"),
530534
library: str | None = Query(default=None),
531535
db: AsyncSession = Depends(require_db),

app/src/components/Breadcrumb.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import { Link } from 'react-router-dom';
66
import Box from '@mui/material/Box';
77
import type { SxProps, Theme } from '@mui/material/styles';
8+
import { colors, fontSize, semanticColors, typography } from '../theme';
89

910
export interface BreadcrumbItem {
1011
label: string;
12+
shortLabel?: string; // Shown on mobile (xs) if provided
1113
to?: string; // If undefined, this is the current page (not linked)
1214
}
1315

@@ -26,11 +28,12 @@ export interface BreadcrumbProps {
2628
* <Breadcrumb items={[{ label: 'pyplots.ai', to: '/' }, { label: 'catalog' }]} />
2729
*
2830
* @example
29-
* // With right action
30-
* <Breadcrumb
31-
* items={[{ label: 'pyplots.ai', to: '/' }, { label: 'catalog' }]}
32-
* rightAction={<Link to="/suggest">suggest spec</Link>}
33-
* />
31+
* // With short labels for mobile
32+
* <Breadcrumb items={[
33+
* { label: 'pyplots.ai', to: '/' },
34+
* { label: 'scatter-basic', to: '/scatter-basic' },
35+
* { label: 'matplotlib', shortLabel: 'mpl' },
36+
* ]} />
3437
*/
3538
export function Breadcrumb({ items, rightAction, sx }: BreadcrumbProps) {
3639
return (
@@ -46,10 +49,10 @@ export function Breadcrumb({ items, rightAction, sx }: BreadcrumbProps) {
4649
px: 2,
4750
py: 1,
4851
mb: 2,
49-
bgcolor: '#f3f4f6',
50-
borderBottom: '1px solid #e5e7eb',
51-
fontFamily: '"MonoLisa", monospace',
52-
fontSize: '0.85rem',
52+
bgcolor: colors.gray[100],
53+
borderBottom: `1px solid ${colors.gray[200]}`,
54+
fontFamily: typography.fontFamily,
55+
fontSize: fontSize.base,
5356
...sx,
5457
}}
5558
>
@@ -58,7 +61,7 @@ export function Breadcrumb({ items, rightAction, sx }: BreadcrumbProps) {
5861
{items.map((item, index) => (
5962
<Box key={index} sx={{ display: 'flex', alignItems: 'center' }}>
6063
{index > 0 && (
61-
<Box component="span" sx={{ mx: 1, color: '#9ca3af' }}>
64+
<Box component="span" sx={{ mx: 1, color: semanticColors.mutedText }}>
6265
6366
</Box>
6467
)}
@@ -67,16 +70,26 @@ export function Breadcrumb({ items, rightAction, sx }: BreadcrumbProps) {
6770
component={Link}
6871
to={item.to}
6972
sx={{
70-
color: '#3776AB',
73+
color: colors.primary,
7174
textDecoration: 'none',
7275
'&:hover': { textDecoration: 'underline' },
7376
}}
7477
>
75-
{item.label}
78+
{item.shortLabel ? (
79+
<>
80+
<Box component="span" sx={{ display: { xs: 'none', sm: 'inline' } }}>{item.label}</Box>
81+
<Box component="span" sx={{ display: { xs: 'inline', sm: 'none' } }}>{item.shortLabel}</Box>
82+
</>
83+
) : item.label}
7684
</Box>
7785
) : (
78-
<Box component="span" sx={{ color: '#4b5563' }}>
79-
{item.label}
86+
<Box component="span" sx={{ color: colors.gray[700] }}>
87+
{item.shortLabel ? (
88+
<>
89+
<Box component="span" sx={{ display: { xs: 'none', sm: 'inline' } }}>{item.label}</Box>
90+
<Box component="span" sx={{ display: { xs: 'inline', sm: 'none' } }}>{item.shortLabel}</Box>
91+
</>
92+
) : item.label}
8093
</Box>
8194
)}
8295
</Box>

app/src/components/CodeHighlighter.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/prism-light';
22
import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism';
33
import python from 'react-syntax-highlighter/dist/esm/languages/prism/python';
4+
import { typography } from '../theme';
45

56
SyntaxHighlighter.registerLanguage('python', python);
67

@@ -16,7 +17,7 @@ export default function CodeHighlighter({ code }: CodeHighlighterProps) {
1617
customStyle={{
1718
margin: 0,
1819
fontSize: '0.85rem',
19-
fontFamily: '"MonoLisa", "MonoLisa Fallback", monospace',
20+
fontFamily: typography.fontFamily,
2021
background: 'transparent',
2122
}}
2223
>

0 commit comments

Comments
 (0)