From e200e33228cde58004a9ad3f1b23c4c8709a2f40 Mon Sep 17 00:00:00 2001 From: Min Ong Date: Fri, 20 Mar 2026 15:48:24 +0000 Subject: [PATCH] docs: standardise agent context files, architecture docs, and .agents/ structure - AGENTS.md: single source of truth for all AI coding assistants - CLAUDE.md: lightweight pointer to AGENTS.md with Claude-specific extensions - docs/ARCHITECTURE.md: Mermaid diagrams and component descriptions - .agents/: tool-agnostic rules and commands - README.md: updated to complement agent files Co-Authored-By: Claude Opus 4.6 --- .agents/rules/code-style.md | 45 ++++++++++++++++ .agents/rules/security.md | 21 ++++++++ .agents/rules/testing.md | 27 ++++++++++ .gitignore | 7 ++- AGENTS.md | 56 ++++++++++++++++++++ CLAUDE.md | 12 +++++ README.md | 8 ++- docs/ARCHITECTURE.md | 101 ++++++++++++++++++++++++++++++++++++ 8 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 .agents/rules/code-style.md create mode 100644 .agents/rules/security.md create mode 100644 .agents/rules/testing.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 docs/ARCHITECTURE.md diff --git a/.agents/rules/code-style.md b/.agents/rules/code-style.md new file mode 100644 index 0000000..9ea45dd --- /dev/null +++ b/.agents/rules/code-style.md @@ -0,0 +1,45 @@ +# Code Style Rules + +## Language & Runtime +- TypeScript (strict mode) targeting `esnext`. +- Node 22 via `mise`. Package manager: `pnpm 10.20`. + +## Formatting (Prettier) +| Setting | Value | +|---|---| +| `printWidth` | 100 | +| `tabWidth` | 2 | +| `useTabs` | false | +| `singleQuote` | true | +| `semi` | false | +| `trailingComma` | all | +| `arrowParens` | avoid | +| `bracketSpacing` | true | +| `endOfLine` | auto | + +Run: `pnpm prettier` + +## EditorConfig +- UTF-8, LF line endings, 2-space indent, trim trailing whitespace, insert final newline. + +## TypeScript Conventions +- `strict: true` with all implicit checks enabled (`noImplicitAny`, `noImplicitReturns`, `noImplicitThis`, `noUnusedLocals`, `noImplicitOverride`, `noUncheckedIndexedAccess`, `strictNullChecks`). +- Use `Readonly<>` wrappers on model/data types (see `src/models/`). +- Prefer `type` aliases over `interface` for data shapes; use `interface` for stateful contracts (e.g., `State`). +- No semicolons at end of statements. +- Use single quotes for strings. +- Use trailing commas everywhere. +- Use arrow functions with omitted parens for single parameters (`x => x`). + +## CSS Conventions +- Plain CSS with PostCSS (`postcss-preset-env` stage 0). +- CSS custom properties defined in `src/styles/vars.css`. +- Component styles co-located as `.css`, imported via barrel `index.ts`. +- No CSS modules or CSS-in-JS; class names are plain global strings. +- Use `var(--token)` references for colors, spacing, typography, and borders. + +## File Organisation +- Barrel exports via `index.ts` per component directory. +- Components follow `src/components//` pattern with `.ts`, `.css`, `.test.ts`, `index.ts`. +- Models in `src/models/` as standalone type-only files. +- DOM utilities in `src/dom/`. diff --git a/.agents/rules/security.md b/.agents/rules/security.md new file mode 100644 index 0000000..7ec5e96 --- /dev/null +++ b/.agents/rules/security.md @@ -0,0 +1,21 @@ +# Security Rules + +## General +- This is a frontend-only application with no backend server or authentication. +- API data is served as static JSON files from `public/api/` via Vite's dev server. + +## XSS Considerations +- The custom `html` tagged template literal in `src/dom/html.ts` performs **no sanitisation** -- it concatenates strings directly. Any user-controlled or external data interpolated into templates is injected as raw HTML via `innerHTML`. +- **Do not** interpolate untrusted input into `html` templates without first escaping HTML entities. + +## Dependencies +- Keep dependencies minimal. The project has zero production dependencies; all packages are `devDependencies`. +- Run `pnpm audit` periodically to check for known vulnerabilities. + +## Secrets +- No secrets, API keys, or tokens are used in this project. +- Do not commit `.env` files or secrets to the repository. + +## Data +- All data (`survey.json`, `demographics.json`, `respondents.json`) is mock/synthetic data with no PII. + diff --git a/.agents/rules/testing.md b/.agents/rules/testing.md new file mode 100644 index 0000000..a3b637f --- /dev/null +++ b/.agents/rules/testing.md @@ -0,0 +1,27 @@ +# Testing Rules + +## Framework +- **Vitest 4.x** with `happy-dom` environment. +- Config: `vitest.config.ts`. + +## Test File Conventions +- Co-locate tests with source: `src/components//.test.ts`. +- Test files match pattern `**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}`. +- Import from `vitest`: `describe`, `it`, `expect`. + +## Running Tests +```sh +pnpm test # watch mode (default vitest behaviour) +``` + +## Writing Tests +- Render components by calling the component function with a `State` object, then invoking the returned render function on a DOM element (e.g., `document.body`). +- Use mock JSON data from `public/api/` (`survey.json`, `demographics.json`, `respondents.json`) imported directly. +- Use `toMatchInlineSnapshot()` for full HTML output assertions. +- Use `toHaveLength()` for element count checks. +- Simulate user interaction via `dispatchEvent(new MouseEvent('click'))`. +- Assert state changes by importing `getState()` from `src/store.ts`. + +## Coverage + +No coverage thresholds are currently configured. diff --git a/.gitignore b/.gitignore index 9b7c02d..cd661d4 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,9 @@ eslint-data ######## /dist/* /config.json -/.cache-loader \ No newline at end of file +/.cache-loader + +# AI agent local files # +##################### +CLAUDE.local.md +.claude/settings.local.json diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..469b91d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,56 @@ +# AGENTS.md + +## Project Summary +Attest Frontend Technical Test -- a vanilla TypeScript SPA that displays survey results with demographic filtering. Built with Vite, Vitest, PostCSS, and no framework. + +## Quick Reference +| Command | Purpose | +|---|---| +| `pnpm start` | Dev server at http://localhost:5173 | +| `pnpm build` | Typecheck + production build | +| `pnpm test` | Run unit tests (Vitest, watch mode) | +| `pnpm typecheck` | TypeScript type checking (`tsc --noEmit`) | +| `pnpm prettier` | Format all files | + +## Architecture +See `docs/ARCHITECTURE.md` for full details. + +### Key Paths +| Path | Purpose | +|---|---| +| `src/main.ts` | App entry point; fetches data, subscribes to store | +| `src/store.ts` | Global state and pub/sub | +| `src/dom/` | Minimal HTML templating and rendering utilities | +| `src/components/filters/` | Demographic filter sidebar | +| `src/components/survey/` | Survey results display | +| `src/models/` | TypeScript type definitions (Demographic, Respondent, Survey) | +| `src/styles/` | Global CSS and design tokens | +| `public/api/` | Static mock JSON data | + +## Coding Standards +See `.agents/rules/code-style.md` for formatting, TypeScript, and CSS conventions. + +Key points: +- Strict TypeScript. No semicolons. Single quotes. Trailing commas. +- Prettier-formatted (run `pnpm prettier`). +- CSS custom properties from `src/styles/vars.css`. No CSS modules. +- Components are pure functions: `(state: State) => (el: Element | null) => void`. + +## Testing +See `.agents/rules/testing.md` for full testing guidelines. + +- Vitest with happy-dom. +- Tests co-located with components as `.test.ts`. +- Mock data imported from `public/api/*.json`. + +## Security +See `.agents/rules/security.md` for security considerations. + +- The `html` template utility does **not** sanitise input. Never interpolate untrusted data. +- Zero production dependencies. + +## Documentation Maintenance +When a code change invalidates any section of this file, `README.md`, or `docs/ARCHITECTURE.md`: +1. Flag the outdated section in your response. +2. Propose an update with the exact edit. +3. Apply the update only after confirmation. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..dcdfe7a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,12 @@ +@AGENTS.md +@.agents/rules/code-style.md +@.agents/rules/testing.md +@.agents/rules/security.md +@docs/ARCHITECTURE.md + + +### Response Preferences +- Be concise. Prefer code over prose. +- When uncertain about architecture, read `docs/ARCHITECTURE.md` before assuming. +- When a change would invalidate any section of `AGENTS.md` or `README.md`, + flag it and offer to update them per the instructions in AGENTS.md. diff --git a/README.md b/README.md index 8ae0516..aa59747 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,15 @@ You can use [`mise`](https://mise.jdx.dev/) to setup environment ## How to run? -- Open up the Dev Server on `https://localhost:5173` with `pnpm start` +- Open up the Dev Server on `http://localhost:5173` with `pnpm start` - To retrieve the survey data request `http://localhost:5173/api/survey.json` - To retrieve the demographics data request `http://localhost:5173/api/demographics.json` - To retrieve the respondents data request `http://localhost:5173/api/respondents.json` - Run unit tests `pnpm test` (these will only pass upon successfully completing task 3) - Run type checking `pnpm typecheck` + +## Documentation + +- [`AGENTS.md`](./AGENTS.md) -- AI agent instructions and project quick reference. +- [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) -- System architecture, data flow, and directory structure. +- [`.agents/rules/`](./.agents/rules/) -- Code style, testing, and security conventions. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..84612d2 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,101 @@ +# Architecture + +## Overview + +A vanilla TypeScript single-page application that displays survey results with demographic filtering. No framework -- rendering is done with a minimal custom tagged-template-literal + `innerHTML` approach. + +## Tech Stack +| Layer | Tool | +|---|---| +| Language | TypeScript 5.9 (strict) | +| Bundler | Vite (via `rolldown-vite`) | +| CSS | PostCSS with `postcss-preset-env` stage 0 | +| Testing | Vitest 4 + happy-dom | +| Formatting | Prettier | +| Runtime | Node 22 (managed by `mise`) | +| Package Manager | pnpm 10.20 | + +## Directory Structure + +``` +. +├── index.html # SPA entry point +├── public/ +│ ├── api/ +│ │ ├── survey.json # Mock survey definition (questions + answers) +│ │ ├── demographics.json # Mock demographic categories and options +│ │ └── respondents.json # Mock respondent data (segmentation + responses) +│ └── assets/ +│ └── logo.svg +├── src/ +│ ├── main.ts # App bootstrap: subscribes to store, fetches data +│ ├── store.ts # Global state + pub/sub (patchState / subscribe) +│ ├── dom/ +│ │ ├── html.ts # Tagged template literal for HTML string building +│ │ ├── render.ts # Renders HTML string into a DOM element via innerHTML +│ │ └── index.ts # Barrel export +│ ├── components/ +│ │ ├── filters/ +│ │ │ ├── filters.ts # Demographic filter sidebar component +│ │ │ ├── filters.css +│ │ │ ├── filters.test.ts +│ │ │ └── index.ts +│ │ └── survey/ +│ │ ├── survey.ts # Survey results display component +│ │ ├── survey.css +│ │ ├── survey.test.ts +│ │ └── index.ts +│ ├── models/ +│ │ ├── demographic.ts # Demographic, DemographicOption types +│ │ ├── respondent.ts # Respondent, RespondentSegmentation, RespondentResponses types +│ │ └── survey.ts # Survey, SurveyQuestion, SurveyQuestionAnswer types +│ └── styles/ +│ ├── vars.css # CSS custom properties (design tokens) +│ └── main.css # Global styles, page layout grid +├── vite.config.ts +├── vitest.config.ts +├── tsconfig.json +├── prettier.config.js +├── .postcssrc.js +├── .editorconfig +└── .mise.toml +``` + +## Data Flow + +``` +index.html + └─ src/main.ts (bootstrap) + ├─ fetch /api/survey.json ──► patchState({ survey }) + ├─ fetch /api/demographics.json ──► patchState({ demographics }) + ├─ fetch /api/respondents.json ──► patchState({ respondents }) + └─ subscribe(callback) + ├─ renderSurvey(state) ──► survey(state)(element) + └─ renderFilters(state) ──► filters(state)(element) +``` + +### State Management (`src/store.ts`) + +A minimal pub/sub store: +- **`State`** holds: `survey`, `demographics`, `respondents`, `selectedDemographics`, `selectedQuestionAnswers`. +- **`patchState(partial)`** merges partial state via `Object.assign` and notifies subscribers. +- **`subscribe(fn)`** registers a callback invoked on every state change. +- **`getState()`** returns current state reference. + +### Rendering (`src/dom/`) + +- `html` -- tagged template literal that concatenates strings (no escaping, no virtual DOM). +- `render(htmlString, effects?)` -- returns a function `(el) => void` that sets `el.innerHTML` and optionally runs side effects (e.g., attaching event listeners). + +### Components + +Components are **pure functions**: `(state: State) => (el: Element | null) => void`. + +- **`filters`** -- renders demographic options in a sidebar. Clicking an option calls `toggleSelectedDemographicOption` which patches `selectedDemographics` in the store. +- **`survey`** -- renders survey questions and answer bars. Filters respondents by `selectedDemographics` to show filtered counts and percentages. A "marker" bar shows the unfiltered baseline; a "track" bar shows the filtered value. + +### Domain Model + +- **`Survey`** -- has a title and a map of `SurveyQuestion`s, each with a map of `SurveyQuestionAnswer`s. +- **`Demographic`** -- has an id, display name, and a map of `DemographicOption`s. +- **`Respondent`** -- has segmentation (which demographic options they belong to) and responses (which answers they selected per question).