BLOCKING rules every AI agent (Claude Code, Cursor, Codex, opencode, …) must follow when working in this repo. Derived from real failure patterns in PR review history.
When a rule is violated, the PR must stay in draft until fixed.
Before opening a feat: plan … or any planning PR, cite an existing peer pattern in:
packages/ui/src/components/(component family precedent), orapps/registry/registry.ts(registry category precedent), orspecs/shipped/(prior spec that solved a comparable shape).
No inventing categories or families that don't exist in the design system.
Why: PRs #124–#131 (
feat: plan {system|brand|data|canvas} icon family) were all closed for: "Wrong direction: no icon family in the design system."
Before gh pr ready (or marking a draft ready), run:
git diff --stat origin/main...HEADZero diff → close the branch. Don't ship a no-op.
Why: PRs #121, #146 closed because they had zero diff vs
main.
After every push, rewrite the PR body so it matches the current head:
- Validation commands actually run on this commit.
- Screenshots / recordings from this commit.
- CI status as-of-now.
- Listed components match the actual diff.
Stale claims block ship.
Why: PR #103 needed "removed stale body claims that visual regression and root quality gates already pass."
For UI behavior changes, paste the verification command + its output, or attach a screenshot/recording of the verified behavior. Bare ticked checkboxes don't count.
Why: PR #123 reviewer flagged: "the spec calls for explicit manual verification … I did not find that evidence attached to the PR."
Every PR body must contain Closes #N, Fixes #N, or Related to #N. Mirrors the CI gate (.github/workflows/pr-issue-link.yml, issue #152, PR #153).
These must all pass on the PR head before requesting merge:
pnpm -F @vllnt/ui lint
pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json
pnpm build
pnpm test:onceTouched-file passes alone are not ship-OK. If tsc is red package-wide, the PR is red package-wide.
Why: PRs #122, #123, #145 each shipped while
pnpm -F @vllnt/ui exec tsc --noEmitwas red on the branch. Reviewers had to backstop.
Don't paste "Codex unavailable: OPENAI_API_KEY unset" boilerplate into PR comments. Either:
- Codex ran → include findings,
- Codex didn't run → omit the section entirely.
Why: Boilerplate noise across PRs #140, #141, #145, #146, #149, #150, #153.
If a branch becomes orphaned (rebased away, superseded, or zero-diff), close the PR with a 1-liner pointing at the canonical PR. Don't leave parallel ghost branches.
See BRANCHING.md.
Why: PR pairs #138/#143, #142/#123, #146/#145 each had review churn from orphaned branches.
See COMPONENTS.md for the full pattern catalog. Summary of what's BLOCKING:
Every named export — including compound subcomponents (Card.Header, Conversation.Title, Form.Field) — has React.forwardRef and a set displayName.
Why: PR #150 needed an auto-fix to add
forwardRefto all 8 compound parts ofConversationThread.
If the component name implies an HTML element (Form, Nav, List, Article, Header), the root renders that element. <div role="…"> is a smell — rename, or use the real tag.
Why: PR #145:
Formshipped as a styled<div>while docs presented it as the form primitive.
aria-describedby and aria-labelledby only emit when the referenced node will render. Generated ids must be conditional, not unconditional.
Why: PR #145
FormControlcomposedaria-describedbywith ids that pointed at nothing when noFormDescription/FormMessagewas rendered.
For container components (canvas, scroll wrappers, key-shortcut hosts): before preventDefault() on wheel / key / pointer handlers, check event.target is not a nested scroll container or interactive element. Defer first, hijack last.
Why: PR #139 —
canvas-viewglobally prevented wheel and stole arrow/zoom keys, breaking nested scrollers and inputs.
Keeping a prop name but changing its layout/behavior is a breaking change. Either preserve the documented behavior, or rename + bump major + write a migration note.
Why: PR #141 —
leftRail/rightDock/bottomSlotonCanvasShellsurvived as names but lost their grid/full-width contract.
Event-handler prop names map to the actual trigger. onSend fires on send, not on suggestion click. If unsure, qualify (onSuggestionClick).
Why: PR #150 —
onSendonly fired on suggestion clicks; reviewer flagged the misleading name.
Pick roles from Radix's pattern + WAI-ARIA Authoring Practices, not intuition. When introducing a non-obvious role, cite the source in the PR body.
Why: PR #139 (
role="button"on a complex workspace host), PR #140 (role="status"on a static marker).
- Don't modify
LICENSE,CODE_OF_CONDUCT.md, orSECURITY.mdwithout explicit human authorization. - Don't add new dependencies without explaining the tradeoff (bundle size, maintenance, security surface).
- Don't publish, push to
main, or merge PRs without explicit authorization. - Don't hand-edit generated files (
apps/registry/lib/component-metadata.json, dist outputs). Regenerate via the documented script.