Zum Inhalt springen

TypeScript Frontend

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

This example demonstrates Contextia in a React + TypeScript frontend application. It shows how to spec UI components, record state management decisions, enforce accessibility norms, and annotate TSX files.

The project is a dashboard application for analytics. It has a component library, data visualization views, and a settings panel. State management uses Zustand.

analytics-dashboard/
├── .contextia/
│ ├── config.yaml
│ └── system/
│ ├── identity.md
│ ├── specs/
│ │ ├── SPEC-010.md # Dashboard layout
│ │ ├── SPEC-011.md # Chart components
│ │ └── SPEC-012.md # Filter panel
│ ├── rationale/
│ │ ├── DEC-010.md # Zustand over Redux
│ │ └── DEC-011.md # Recharts for data viz
│ └── norms/
│ ├── NORM-A11Y-001.md # Accessibility requirements
│ └── NORM-UI-001.md # Component patterns
├── src/
│ ├── components/
│ │ ├── charts/
│ │ ├── filters/
│ │ └── layout/
│ ├── stores/
│ ├── hooks/
│ ├── pages/
│ └── types/
└── tests/
.contextia/config.yaml
project:
name: analytics-dashboard
languages:
- typescript
source_paths:
- src/
annotations:
prefix: "@"
comment_syntax:
typescript: "//"
check:
ignore_patterns:
- "src/**/*.test.tsx"
- "src/**/*.stories.tsx"
.contextia/system/identity.md
---
project: analytics-dashboard
version: "1.0"
---
## What is this
Analytics Dashboard is a React SPA for visualizing business metrics.
Built with React 18, TypeScript, Zustand, and Recharts.
## Architecture
Component-based architecture with three layers:
- Pages: route-level containers that compose components.
- Components: reusable UI elements, either presentational or connected.
- Stores: Zustand stores for client-side state (filters, preferences, cache).
Data flows one-way: API -> store -> component -> render.
Server state managed by TanStack Query. Client state in Zustand.
## Conventions
- Components are functional with hooks. No class components.
- All interactive elements must meet WCAG 2.1 AA.
- Component files export a single default component.
- Styles use CSS Modules with .module.css files.
- Types are co-located in the component directory or in src/types/ for shared types.
.contextia/system/specs/SPEC-010.md
---
id: SPEC-010
title: Dashboard layout
description: Responsive grid layout with sidebar navigation, header, and content area.
status: approved
decisions: []
norms:
- NORM-A11Y-001
- NORM-UI-001
paths:
- src/components/layout/
- src/pages/DashboardPage.tsx
---
## Objective
Provide a responsive layout shell that adapts to screen size and supports
keyboard navigation between major regions.
## Behaviors
WHEN the viewport width is 1024px or greater
THEN the sidebar is visible and the content area uses a 12-column grid.
WHEN the viewport width is below 1024px
THEN the sidebar collapses to a hamburger menu and the content area is single column.
WHEN the user presses Tab
THEN focus moves through landmark regions in order: skip-link, navigation, main content.
WHEN the sidebar navigation item is activated (click or Enter)
THEN the corresponding page loads and the navigation item shows active state.
.contextia/system/rationale/DEC-010.md
---
id: DEC-010
title: Zustand over Redux for client state
status: accepted
date: 2025-09-15
specs:
- SPEC-012
---
## Context
Need client-side state management for filter selections, user preferences,
and UI state (sidebar open/closed, active tab). Server state is handled by
TanStack Query separately.
## Decision
Use Zustand for all client-side state. Create one store per domain (filters,
preferences, ui) rather than a single global store.
## Alternatives considered
- **Redux Toolkit**: More boilerplate. Overkill for simple client state
when server state is already handled by TanStack Query.
- **React Context + useReducer**: Causes unnecessary re-renders without
manual optimization. Zustand's selector pattern avoids this.
- **Jotai**: Good for atomic state but less natural for grouped state
like filter combinations.
## Consequences
- Minimal boilerplate per store.
- Components subscribe to specific slices, avoiding re-render cascading.
- Team needs to understand Zustand's selector pattern.
.contextia/system/norms/NORM-A11Y-001.md
---
id: NORM-A11Y-001
title: Accessibility requirements
status: active
paths:
- src/components/**
- src/pages/**
---
## Rules
1. All interactive elements MUST be operable with keyboard alone.
2. All images MUST have alt text. Decorative images use `alt=""`.
3. Color MUST NOT be the sole means of conveying information. Use icons or text labels alongside color indicators.
4. Focus indicators MUST be visible. Never apply `outline: none` without a custom visible focus style.
5. ARIA roles MUST be used when semantic HTML elements are insufficient. Prefer semantic HTML first.
6. All form inputs MUST have associated labels (visible or `aria-label`).
7. Dynamic content updates MUST use `aria-live` regions to announce changes to screen readers.
8. Minimum contrast ratio: 4.5:1 for normal text, 3:1 for large text (WCAG AA).
src/components/filters/FilterPanel.tsx
// @spec SPEC-012
// @norm NORM-A11Y-001
// @decision DEC-010
import { useFilterStore } from '../../stores/filterStore';
interface FilterPanelProps {
onApply: () => void;
}
export default function FilterPanel({ onApply }: FilterPanelProps) {
const { dateRange, setDateRange, metrics, toggleMetric } = useFilterStore(
(state) => ({
dateRange: state.dateRange,
setDateRange: state.setDateRange,
metrics: state.selectedMetrics,
toggleMetric: state.toggleMetric,
})
);
return (
<aside role="complementary" aria-label="Dashboard filters">
<fieldset>
<legend>Date Range</legend>
<label htmlFor="start-date">Start</label>
<input
id="start-date"
type="date"
value={dateRange.start}
onChange={(e) => setDateRange({ ...dateRange, start: e.target.value })}
/>
<label htmlFor="end-date">End</label>
<input
id="end-date"
type="date"
value={dateRange.end}
onChange={(e) => setDateRange({ ...dateRange, end: e.target.value })}
/>
</fieldset>
<fieldset>
<legend>Metrics</legend>
{['revenue', 'users', 'sessions', 'conversion'].map((metric) => (
<label key={metric}>
<input
type="checkbox"
checked={metrics.includes(metric)}
onChange={() => toggleMetric(metric)}
/>
{metric.charAt(0).toUpperCase() + metric.slice(1)}
</label>
))}
</fieldset>
<button type="button" onClick={onApply}>
Apply Filters
</button>
</aside>
);
}
src/stores/filterStore.ts
// @spec SPEC-012
// @decision DEC-010
import { create } from 'zustand';
interface DateRange {
start: string;
end: string;
}
interface FilterState {
dateRange: DateRange;
selectedMetrics: string[];
setDateRange: (range: DateRange) => void;
toggleMetric: (metric: string) => void;
resetFilters: () => void;
}
const defaultState = {
dateRange: { start: '', end: '' },
selectedMetrics: ['revenue', 'users'],
};
export const useFilterStore = create<FilterState>((set) => ({
...defaultState,
setDateRange: (range) => set({ dateRange: range }),
toggleMetric: (metric) =>
set((state) => ({
selectedMetrics: state.selectedMetrics.includes(metric)
? state.selectedMetrics.filter((m) => m !== metric)
: [...state.selectedMetrics, metric],
})),
resetFilters: () => set(defaultState),
}));
src/components/layout/DashboardLayout.tsx
// @spec SPEC-010
// @norm NORM-A11Y-001
import { Outlet } from 'react-router-dom';
import Sidebar from './Sidebar';
import Header from './Header';
import styles from './DashboardLayout.module.css';
export default function DashboardLayout() {
return (
<div className={styles.container}>
<a href="#main-content" className={styles.skipLink}>
Skip to main content
</a>
<Header />
<div className={styles.body}>
<Sidebar />
<main id="main-content" role="main" tabIndex={-1}>
<Outlet />
</main>
</div>
</div>
);
}

When an agent receives a task to add a new chart type to the dashboard:

Terminal window
contextia context TASK-020

The agent gets:

  • TASK-020 (the work order)
  • SPEC-011 (chart component behaviors)
  • DEC-011 (why Recharts was chosen)
  • NORM-A11Y-001 (accessibility rules for the chart)
  • NORM-UI-001 (component patterns to follow)

With this context, the agent knows to use Recharts (not D3), follow the existing component pattern, and ensure the chart is keyboard-accessible with proper ARIA labels.