tessary / frontend
AGENTS.md20 starsChanged 2 months agoApache-2.0View on GitHub
Imperative rules for any agent touching frontend/. React 19 + Vite + TypeScript + Tailwind v4, styled by the Tessary Design System. Read DESIGN_SYSTEM.md before your first styling change and follow it. It is the reference for the palette, the type scale, the radii, what the accent means and when a hue is allowed; every value it describes is defined in tokens.css. Those rules are deliberately NOT restated here. Two copies drift, and the copy an agent happens to read…
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository7 other files it gives its agents.
- AGENTS.mdAGENTS.md
- backend/AGENTS.mdAGENTS.md
- docs/AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- groundedness-setupSkill
- instrumentSkill
- setupSkill
The file
We split this AGENTS.md into the rules it gives an agent
7 rules. About 0 are common, 1 is distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 74
All responses unwrap from ApiResponse<T> in api/client.ts (http<T>()).
- Very specific: line 86
Default staleTime is 30 s (main.tsx).
- Very specific: line 116
Add the lazy import at the top of App.tsx via the named(() => import("./views/X"), "X") helper, and a plain <Route path="segment" …
- Distinctive: line 128
Every user-visible string follows the handbook: the product copy guide for surfaces, states, and actions, the writing style guide for …
- Very specific: line 137
When to add one: root AGENTS.md.
- Very specific: line 141
No component internals, no container.querySelector, no whole-tree snapshots.
- Very specific: line 152
Run task check -- frontend from the repo root (type-check + all-routes render smoke test + production bundle.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Frontend agent guide
Imperative rules for any agent touching frontend/. React 19 + Vite +
TypeScript + Tailwind v4, styled by the Tessary Design System.
Design system
Read DESIGN_SYSTEM.md before your first styling change
and follow it. It is the reference for the palette, the type scale, the radii,
what the accent means and when a hue is allowed; every value it describes is
defined in tokens.css.
Those rules are deliberately NOT restated here. Two copies drift, and the copy an agent happens to read first wins. That is exactly how this file came to describe a mint accent and a light mode for months after both were removed.
The one imperative that belongs in this file: never write a raw value for
anything the tokens own. No hex, no rgb(), no Tailwind palette class
(bg-zinc-800, text-white), no pixel font size or radius, and no
lineHeight / fontWeight / tracking-* sitting next to a text-* token.
Nothing in the gate catches any of that — it all type-checks and bundles
cleanly — so this document and review are the only things standing between the
system and a slow drift back out of it.
Two consequences worth knowing before you reach for a workaround:
- Dark only. There is no light mode and no
data-theme="light"block.ThemeProvider(src/ui/ThemeContext.tsx) hard-sets dark. Don't add a competing theme-init path. - Compose
src/ui/, don't hand-roll.import { Button, Card, Table } from "../ui". A primitive built by hand is a primitive that will miss the next token change.
Where things live
- Tokens →
tokens.css, imported once bysrc/index.css. Its@themeblock emits the--color-*/--text-*/--radius-*Tailwind utilities;src/index.cssitself owns only base rules, keyframes and scoped component CSS (.payload-md,.payload-json). - Component primitives →
src/ui/(barrel:src/ui/index.ts).
Structure (src/)
api/—client.ts(fetch layer),types.ts(+ApiError),types-auth.ts,generated/schema.d.ts(OpenAPI-generated — see Types below).auth/—AuthContext.tsx(/auth/me poller),ProtectedRoute.tsx,signOut.ts(the one sign-out path).tenant/—TenantContext.tsx:TenantProvider,useTenant,useProjectApi.shell/— navigation shell:nav.tsx(the IA source of truth),Sidebar.tsx,ShellChrome.tsx(layers the project-scoped providers),CommandPalette.tsx+PaletteContext.tsx+commands.tsx(⌘K),ShellActions.tsx,useCases.ts(the Triage badge count),recents.ts,useSidebarCollapsed.ts. Import the IA fromshell/nav.tsxand its capability-filtered view fromshell/useNavigation.ts.ui/— design-system primitives barrel (import { Button, Card, Table } from "../ui"),ThemeContext.tsx,density.tsx,escStack.ts(the shared overlay ESC stack — a rail under a modal must not also close on the same keypress),useDropdown.ts(menu open/close, outside click and ESC).capabilities/—useCapabilities()/CapabilityGate. ONE gating axis: the backend resolves every capability per session and the SPA reads the answer. Fail-closed on the client while the read is in flight (the browser has no defaults of its own). There is no frontend flag client and no plan logic.views/— surfaces, mostly one directory per IA surface (triage/,traces/,classifiers/,metrics/,Settings/).graders/,review/,datasets/,runs/,synth/andreposync/were deleted along with the whole Calibrate nav group; their old paths fall through to the project catch-all at the bottom ofApp.tsx, which lands on Triage.views/traceLinker.tsbuilds trace, span and classifier hrefs;views/classifiers/useListPaging.ts(infinite scroll across filter switches) anduseSkeletonFlag.ts(delayed, held list skeleton) serve the classifier lists. Surface-specific shared widgets go inviews/components/(PayloadViewer,ConnectRepositoryDialog,SourceConnect, …). Generic primitives belong insrc/ui/, not here.lib/—relativeTime.ts,ledgerTime.ts(credential created/last-used times),usd.ts(spend formatting),routePreload.ts.
API + data conventions
- All responses unwrap from
ApiResponse<T>inapi/client.ts(http<T>()). Failures throwApiError(status,code,detail) — rendererr.code+err.detail(ErrorNotedoes), never "something went wrong". projectApi(orgSlug, projectSlug)is the per-project client factory; views read it viauseProjectApi()insideTenantProvider. There is no globalapi— don't import one.- Types are generated, not hand-written.
pnpm run generate:apirunsopenapi-typescriptover the checked-in backend spec intoapi/generated/schema.d.ts;api/types.tsre-exportsS["..."]aliases. Consume new backend DTOs via those aliases (backend side:task contract:openapifirst). - TanStack Query keys:
[resource, api.base]for project-scoped data (['pipeline', api.base],['classifiers', api.base]) so a project switch invalidates correctly; auth-scoped keys use slugs. AfterPOST .../import, invalidate['pipeline']. - Default
staleTimeis 30 s (main.tsx). One-shot LLM mutations useuseMutationwith no cache key — single-fire, result in component state. - The old progressive onboarding gate is gone, but a first-run gate exists again.
useOnboarding/OnboardingContext/<Gate need="pipeline"|"full">were removed with the Triage · Monitor · Calibrate redesign and stay gone — don't reintroduce a call to those. What replaced them is narrower and one-shot:ProjectShell(App.tsx) renders<ConnectGate>(views/onboarding/ConnectGate.tsx) instead of<ShellChrome>/routes for any non-sample project until its substrate reportshas_tagged_span, at which point it flips once to the real shell and never gates again. It blocks every route (Triage included), which the old mechanism also did — the difference is scope (one first-run wait state, keyed on the substrate read, not a per-surface capability check) and that a project can dodge it entirely via the gate's own "Start with a sample project" escape hatch.<CapabilityGate capability=…>is unchanged and still the only per-surface wrapper. Once connected, Triage reads the onboarding ladder (views/onboarding/useOnboarding.ts, a passive read of the backend's derived stage, not the old gate) to pick which empty state to show.
Rendering trace payloads
views/components/PayloadViewer.tsx renders trace media inline with an XSS guard at src
construction: image_url accepts only https:// or data:image/<allowed>; base64 is gated
by a MIME allowlist (png|jpeg|gif|webp) that rejects image/svg+xml (active document).
A recognised-but-unrenderable block becomes a neutral "unsupported image" chip. The
image-detection shapes mirror the backend ContentExtractor — keep them in sync.
New view
- Declare the surface once in
shell/nav.tsx, as an item inside the rightNAV_GROUPSentry (id= route segment,label,icon,description,keywords).LIVE_NAVis derived from those — never append to it directly. Sidebar and ⌘K both read the derived lists, so one declaration lights up both. - Add the lazy import at the top of
App.tsxvia thenamed(() => import("./views/X"), "X")helper, and a plain<Route path="segment" element={<X/>} />insideProjectShell's nested<Routes>. Wrap gated surfaces in<CapabilityGate capability=…>— that is the only route wrapper — and set the samecapabilityon the nav item so the sidebar and palette agree. - Moved/renamed an old path? The project catch-all at the bottom of
ProjectShellsends any unmatched path to Triage, and the settings catch-all sends one to Sources. Add a<Navigate … replace />above them only when an old link has to land on the new page instead. - Settings sub-pages: add an item to the right group in
SETTINGS_GROUPSinnav.tsxand a child<Route>undersettingsin App.tsx.
Copy
Every user-visible string follows the handbook: the product copy guide for surfaces, states, and actions, the writing style guide for language rules, and the product glossary for terms (a classifier creates a finding; triage turns a sound finding into a case, while a high-confidence secret leak and frustration rule their own; "detector" is a wire key, never a label). Those docs are the only home for the rules.
Tests
When to add one: root AGENTS.md. The backend rules on exact assertions and doubles
(backend/AGENTS.md) apply here too.
- Test what the user sees.
render, query withgetByRole/getByLabelText, act withfireEvent. No component internals, nocontainer.querySelector, no whole-tree snapshots. - Mock at the API seam only:
src/test/api.tsauto-mocksuseTenant/projectApi; opt in withvi.mock("<rel>/tenant/TenantContext", () => import("<rel>/test/api")). Never mock our other hooks or child components. - Fixtures must be able to fail the test. A "no secret in the DOM" test needs a secret in the fixture; a page test needs real data, not a 404.
- Absence needs a settled query.
awaitsomething rendered from the same response, then assertqueryBy…is null. - No real waiting.
await findBy…for appearance;vi.useFakeTimers()for timed UI.
Before you commit
Run task check -- frontend from the repo root (type-check + all-routes render
smoke test + production bundle; no Maven, no Docker). CI runs the same gate
(test-suite.md), but nothing blocks a merge, so your local run
comes first.
The gate does not check design tokens. A hardcoded hex, an off-scale font
size and a tracking-wider fighting its token all compile and all bundle
cleanly. Nothing but this document and review catches them, so read the rules
above before you reach for a raw value, and look at the surface you changed.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

