Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/agent-review-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
4 changes: 4 additions & 0 deletions .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
settings.local.json
worktrees/
*.lock
.DS_Store
93 changes: 93 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Project Guide

Bruno API Docs fork — open-source API docs generated from a Bruno collection (React + Redux +
Vite). One workspace: `packages/bruno-api-docs`, published as `@opencollection/docs`.

Collection docs render the collection a team already runs: every folder, request, and environment
gets a page (method, URL, params, headers, body, auth, examples, code snippets, scripts, tests),
and the embedded playground lets readers edit and send those requests from the docs. The output
is one static HTML file plus the CDN bundle this repo builds.

The app is an API **client**, not a server. Judge every behaviour and edge case by "what should an
API client do here?", not what a document viewer or an API server would do.

## Quick commands

```bash
nvm use && npm install # Node from .nvmrc; installs the husky pre-commit hook
npm run lint # root
npm run lint:fix # root: auto-fix; the pre-commit hook runs this too
```

From `packages/bruno-api-docs/`:

```bash
npm run dev # Vite on http://127.0.0.1:3001 (?fixture=folders|vars|descriptions|qa)
npm run test:run # Vitest one-shot (pretest builds the QuickJS lib bundle)
npm run test:run -- src/utils/cx.spec.ts # single unit spec
npm run test:e2e # Playwright; starts the dev server itself
npx playwright test e2e/tests/sidebar/ # one e2e directory
npm run build && npm run build:standalone # library + CDN bundle (dist/, dist-standalone/)
```

Prefer the smallest scope (one spec, one directory) over the full suite.

## Key architecture

- **Entry**: `src/components/OpenCollection/OpenCollection.tsx` owns the store, parses the
collection (YAML, then JSON fallback), and renders `AppShell` inside a `HashRouter`.
`components/PageRouter` maps routes (`src/routing/`) to `src/pages/*`.
- **Layers**: `src/pages/` routed screens, `src/components/` reusable UI, `src/ui/` primitives,
`src/hooks/`, `src/utils/` pure helpers, `src/store/` Redux Toolkit slices, `src/runner/`
request execution, `src/scripting/` the QuickJS sandbox and `bru.*` runtime, `src/theme/`
tokens. List a directory for the current set; do not trust a catalogue in a doc.
- **Standalone bundle**: `src/standalone.ts` (`OpenCollectionRenderer`) is what the HTML Bruno
generates loads from the CDN. Build output lives in `dist*/`; edit `src/` only.
- **Theming**: tokens in `src/theme/tokens/{light,dark}.ts` become CSS custom properties in
the generated `src/styles/theme.generated.css`. Components consume `var(--...)` only.

## Coding standards

Full list: `CODING_STANDARDS.md` (read it before writing code). Mechanical style is
ESLint-enforced; the rules worth holding in every session:

- Colours and fonts only via CSS custom properties; hex literals fail lint outside `src/theme/`.
- Slices import through `@/store/slices/<slice>`; `@slices/*` fails lint. Other `src/` imports
use `@/*`. `e2e/` has no aliases.
- One component per folder (`Foo.tsx` + `StyledWrapper.ts` + `Foo.spec.tsx`); `testId` prop
with derived child ids; classes over inline `style`; no comments in `StyledWrapper.ts`.
- `description` fields are string **or** `{ content }`; always go through the normalisers.
- Every changed behaviour maps to a unit spec (via `useRenderToDom`) or an e2e spec.

## Testing

- **Unit**: Vitest, `environment: 'node'`, specs beside the code as `*.spec.ts(x)`. Render with
`useRenderToDom` and query with `src/test-utils/dom.ts`. No DOM interaction tests here.
- **E2E**: Playwright, class-based page-object model under `packages/bruno-api-docs/e2e/`.
Guide: `e2e/README.md`; quick reference: `.claude/rules/testing.md`; use `/write-e2e-test`.
- **CI** (`.github/workflows/ci.yml`): lint, unit tests, both builds, then e2e. Draft PRs skip
the builds and e2e.

## Rules and skills

Path-scoped rules in `.claude/rules/` attach when you touch matching files: `app-conventions`
(components, styling, state, format consumption), `conventions` (readability, comment and diff
hygiene), `cross-os-compat` (line endings, modifier keys, SSR safety), `unit-testing`,
`testing` (Playwright quick reference). Skills: `/code-review` (parallel lenses mirroring
`.coderabbit.yaml`), `/write-e2e-test`, `/new-component`. Layout and maintenance notes:
`.claude/README.md`.

## Gotchas

- `tsc` and Vitest fail with `Cannot find module './bundled-libraries.iife.js'` until
`npm run build:lib-bundle` has run once (`pretest`/`predev`/`prebuild` do it for you).
- Never edit `src/styles/theme.generated.css`; change the tokens and run `npm run gen:theme`.
- The dev entry `src/dev.tsx` mounts e2e fixture collections via `?fixture=`. Do not add ad-hoc
user collections there; exercise them through the standalone build instead.
- Every PR that changes published behaviour needs a changeset (`npm run changeset` or a
`changeset:patch|minor|major` label). Tooling-only PRs use an empty changeset.

## Before you call a change done

From the root: `npm run lint`. From `packages/bruno-api-docs/`: `npm run test:run`, plus
`npm run test:e2e` when UI behaviour changed. Remove dead code with the feature that used it.
115 changes: 115 additions & 0 deletions .claude/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Claude Config

The [Claude Code](https://code.claude.com/docs) configuration for Bruno API Docs: project
context, path-scoped engineering rules, and review/test skills. Claude picks it up automatically
when launched from the repo root. It is committed so every contributor and CI reviewer works
from the same conventions.

<!-- This README is a human maintainer guide. It is NOT a memory file and is never imported into
Claude's always-loaded context. Keep contributor/ownership guidance here, not in CLAUDE.md. -->

## How the pieces link

```
CODING_STANDARDS.md (repo root) ← single source of truth for how code is written
├── .coderabbit.yaml ingests it as review guidelines (knowledge_base)
├── .claude/CLAUDE.md pointer + the few rules worth holding every session
├── .claude/rules/*.md judgment layer + repo detail on top of it
└── .claude/skills/code-review/ local mirror of the CodeRabbit review
packages/bruno-api-docs/e2e/README.md ← canonical e2e guide; referenced by .coderabbit.yaml,
rules/testing.md and the write-e2e-test skill
```

Change a standard in `CODING_STANDARDS.md` first. The other files reference it; they should not
restate it. If two files disagree, the rules win over `.coderabbit.yaml`, and the standards file
wins over both.

## What's inside

| Path | What it is | Loads |
|------|------------|-------|
| `CLAUDE.md` | Project overview: commands, architecture pointers, standards summary, gotchas, index of rules and skills. | Every session. |
| `rules/app-conventions.md` | Components, styling, state, format consumption, test ids for `packages/bruno-api-docs/src/**`. | When Claude touches a matching file. |
| `rules/conventions.md` | Readability, comment and diff hygiene for all packages, scripts, examples. | On match. |
| `rules/cross-os-compat.md` | Line endings, modifier keys, SSR safety for `src/**`. | On match. |
| `rules/unit-testing.md` | Vitest: `useRenderToDom`, unconditional assertions, coverage mapping. | On `*.spec.*` / `*.test.*`. |
| `rules/testing.md` | Playwright quick reference for `e2e/**`. | On match. |
| `skills/code-review/` | `/code-review`: parallel lenses in `reviewers/`; mirrors `.coderabbit.yaml`. | On invocation or when relevant. |
| `skills/write-e2e-test/` | `/write-e2e-test`: a spec in the class-based page-object style. | On invocation or when relevant. |
| `skills/new-component/` | `/new-component`: scaffold a component or page with the folder, styling, and spec conventions. | On invocation or when relevant. |
| `settings.json` | Shared settings. Denies `Read` on build output and Playwright artefacts so Claude works from `src/`. | At startup, from the launch directory. |
| `settings.local.json` | Per-machine overrides. Gitignored. | At startup, if present. |

## Install

Start Claude Code (`claude`) from the repo root and everything loads:

- `.claude/CLAUDE.md` is a first-class project-instruction location, so there is no root
`CLAUDE.md` and no `@` import. `CLAUDE.local.md` at the root is gitignored for personal notes.
- Path-scoped rules attach when Claude reads a matching file. Launching from
`packages/bruno-api-docs/` still loads this root `.claude/` from the ancestor directory.
- Skills are discovered from `.claude/skills/`: type `/code-review`, `/write-e2e-test`,
`/new-component`.

Run `/context` in a session to confirm what loaded.

---

## Maintaining this config

For whoever edits the config. The goal is high instruction adherence at the lowest always-loaded
cost: put each instruction in the mechanism that loads it exactly when it is needed, and no
sooner. It follows the Claude Code docs; read them before structural changes:
[Write an effective CLAUDE.md](https://code.claude.com/docs/en/best-practices#write-an-effective-claude-md),
[Memory](https://code.claude.com/docs/en/memory) (loading order, `.claude/rules/`),
[Skills](https://code.claude.com/docs/en/skills).

### Where does a new instruction go?

| Mechanism | Lives in | Loads | Use it for |
|---|---|---|---|
| Coding standard | `CODING_STANDARDS.md` | Read on demand by Claude; ingested by CodeRabbit | Any rule about how code is written. Humans read it too. |
| Project instructions | `.claude/CLAUDE.md` | Every session (full file) | Facts true in nearly every session and not inferable from code: orientation, setup, global invariants, pointers. |
| Path-scoped rule | `.claude/rules/<topic>.md` with `paths:` | When Claude reads a matching file | Judgment calls and repo detail for one area. One topic per file. |
| Skill | `.claude/skills/<name>/SKILL.md` | On `/invoke` or when the description matches | A reusable multi-step procedure (review, scaffold, write a test). Not a fact. |
| Settings / hooks | `.claude/settings.json` | Startup / lifecycle events | Deterministic enforcement. Advisory guidance is not a hook. |
| CI review | `.coderabbit.yaml` | Every PR | Path scope, tone, and what the standards file cannot say. Never a restated standard. |

### Budgets

- `CLAUDE.md`: target ≤ 120 lines, hard cap 200. For every line ask "would removing this cause
Claude to make a recurring project-specific mistake?" If not, cut or relocate it.
- Rules: one topic each. Keep `paths:` accurate against real repo paths.
- Skills: `SKILL.md` under 150 lines; descriptions under ~200 characters, leading with the words
a triggering request would contain.
- Keep the skill catalogue small: names and descriptions cost discovery context even though
bodies load lazily.

### Keep it consistent

- Before writing a fact, `grep -rn "<claim>" .claude CODING_STANDARDS.md .coderabbit.yaml`. If it
already exists, point to it instead of repeating it.
- Verify every rule and example against the actual repo. Grep the source; do not assume.
- Do not hardcode volatile catalogues (component lists, slice names, fixture names beyond the
ones the dev entry hard-wires). Describe the category and say where to read the current set.
- Team-wide requirements belong in these committed files, not only in a contributor's auto
memory, which is machine-local.
- `Read`-deny rules are for build output, not dependencies. `node_modules/` is deliberately not
denied; reading a dependency's types is legitimate.

### Validate a change

- `git diff --check`; `python3 -c "import json;json.load(open('.claude/settings.json'))"`.
- Every rule has `paths:` (an unscoped rule loads in every session):
`grep -L "paths:" .claude/rules/*.md` prints nothing.
- Cross-references resolve: `grep -rno "[A-Za-z0-9_./-]*\.md" .claude CODING_STANDARDS.md`.
- Loading: `/context` in a session; open a file under `src/` and under `e2e/` and confirm the
right rule attaches.
- Skill triggering: phrase a request the skill should catch ("review my changes", "add an e2e
test for search") and confirm it is offered; phrase a near-miss and confirm it is not.

### When to revisit

After Claude repeats the same project-specific mistake, after a repo restructuring (packages
moved, build tooling changed), or after a Claude Code release that changes loading or skill
behaviour. Treat config edits like code: review them in PRs.
68 changes: 68 additions & 0 deletions .claude/rules/app-conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
paths:
- "packages/bruno-api-docs/src/**"
---

# App Conventions

`CODING_STANDARDS.md` is the source of truth for how components, styling, state, and tests are
written; read it. This file holds the judgment calls and repo-specific detail a linter cannot
make. Derived from the existing codebase: match it.

## Components

- Before adding a component, hook, or helper, search `src/ui/`, `src/components/`, `src/hooks/`,
and `src/utils/` by concept, not by the name you would have picked, and read the nearest
sibling that solves the same shape of problem. Reuse is usually a net deletion.
- Where an existing primitive is almost right, widen it rather than standing up a near-duplicate
next to it; two near-identical implementations diverge silently.
- `src/ui/` holds primitives with no knowledge of collections (tables, tabs, modals, editors).
`src/components/` holds collection-aware pieces. `src/pages/` holds routed screens composed
from both. A helper that only one component uses lives next to that component; a shared one
lives in `src/utils/` with its own spec.
- `useEffect` is used throughout the codebase and is not banned; still prefer derived state and
event handlers where they are genuinely simpler. An effect that only mirrors a prop into state
is a smell.

## Styling

- Legacy alias variables (`--text-primary`, `--border-color`, `--bg-secondary`, ...) in
`src/styles/index.css` map onto the generated `--oc-*` tokens. Prefer an existing alias; add
a new alias there rather than reaching for a raw `--oc-*` token in a component.
- Headings inside `.markdown-documentation`: keep `line-height` unitless or at least the font
size, or multi-line headings clip.
- Tailwind utilities appear alongside Emotion for layout (`flex`, spacing). That is fine; colour,
font, and border tokens still come from CSS variables in the wrapper.

## Reading collections

- `description` may be a bare string or a legacy `{ content, type }` object. Display and search
both go through `descriptionText` / `resolveDescription` (`utils/description.ts`),
`getDescription` (`utils/request.ts`), or `getItemDescription` (`utils/schemaHelpers.ts`).
A new description-bearing field follows the same handling.
- Requests come in several protocols (HTTP, GraphQL, gRPC, WebSocket). Check how
`components/PageRouter` and `utils/schemaHelpers.ts` discriminate them before adding a branch.
- Playground state is seeded from the docs collection; changes to one side must keep the other
consistent. Read `store/slices/playground.ts` alongside `store/slices/docs.ts`.

## Test ids

- Components take `testId?: string`; child ids derive from it (`${testId}-row`) and are omitted
when unset. A component reused in several sections gets a distinct `testId` per instance so
e2e locators stay unambiguous.
- If an e2e test needs an element with no stable id, add a `testId` to the component. Never
locate by styling class or text in a spec.

## Hygiene

- Do not strip explanatory JSDoc from non-obvious logic (for example the parsing rules in
`utils/pathParams.ts`) during a refactor; those comments are the contract.
- The `@/*` alias is configured in `tsconfig.json`, `vite.config.ts`, `vite.config.*.ts`, and
`vitest.config.ts`. Keep them in sync if you touch one.

## Before you call a change done

From the root: `npm run lint`. From `packages/bruno-api-docs/`: `npm run test:run`, plus
`npm run test:e2e` when UI behaviour changed. Then list every behaviour the diff adds or changes
and name the test that exercises it. Anything without a test is a gap to fill before the commit,
not a note for the PR.
57 changes: 57 additions & 0 deletions .claude/rules/conventions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
paths:
- "packages/**/*"
- "scripts/**/*"
- "examples/**/*"
---

# Readability and Diff Hygiene

`CODING_STANDARDS.md` is the source of truth for coding standards; read it. This file is the
judgment layer: the readability and hygiene calls a linter cannot make. Code and comments must
read as a natural, permanent part of the project, never as artefacts of the task or session
that produced them.

## Style and formatting

Mechanical style (indent, quotes, semicolons, trailing commas, arrow parens, brace style, line
length) is ESLint-enforced and auto-fixed by `npm run lint:fix`. Note these deviations briefly
rather than dwelling on them. Naming and casing that ESLint cannot repair still warrant attention.

## Readability

- **Names say what they hold.** Concrete subject and type, understandable on first read. Raise an
unclear or misleading name even when the code is otherwise correct.
- **Reuse before you write.** Search for the existing component, hook, or helper by concept, then
read the nearest sibling solving the same shape of problem; its call site shows the intended
composition.
- **Extraction and abstraction.** Extract when it improves readability or serves a clear,
anticipated reuse; this is not gated on a minimum number of call sites. Avoid only indirection
that earns nothing: a utility generalised for one site with no foreseeable second user, or
options added "for later".
- **Single-line indirection.** A one-line function that only forwards to another should be inlined.
- **Optional chaining and falsy defaults.** `?.` only where the null case is handled right there.
`x || default` only where an empty string, `0`, or `false` genuinely means "unset".
- **Functional, but readable.** Obvious, linear pipelines over deep functional machinery.

## Comments

- **No situational comments.** Nothing that references the change, the task, or the review
(`// added to fix ...`, `// as requested`, `// per review`). State a reason as a timeless fact
about the code or link the issue.
- **No obvious comments.** Do not restate the code. If it is self-explanatory, leave it bare.
- **Comment the why.** Non-obvious rationale, invariants, edge cases, a workaround and the
constraint forcing it, units, a pointer to a spec.
- **No scaffolding or narration.** No `// ... existing code ...`, no TODO-for-me notes, no
commented-out code, no step-by-step change log in comments.
- **No comments in `StyledWrapper.ts` files.**

## Beyond comments

- **Anything added needs a live consumer in the same change.** No option nobody passes, payload
field nobody reads, or branch for a state the producer cannot emit.
- **Replacing code leaves nothing behind.** Removing a view or feature also removes its orphaned
components, props, store wiring, styles, and tests. Confirm what the new code actually renders
before calling a leftover dead.
- **Minimal diffs.** No unrelated reformatting or whitespace churn.
- **No ticket identifiers** in source, comments, or test names.
Loading
Loading