CtrlK
BlogDocsLog inGet started
Tessl Logo

vest-error-key-lifecycle

Use when adding, removing, or renaming a Vest validation `test(...)` in this repo's suite.js / *Suite.js files, or when touching a validation error key like "team-members" / "project-officer". Maps the full producer→consumer chain (test → getErrors() → convertCodeForDisplay("validation", key) → error-list render → button gating) so a suite edit updates every dependent label, renderer, and test. Trigger on any Vest suite change.

72

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

SKILL.md
Quality
Evals
Security

Vest Error-Key Lifecycle

Overview

A Vest test("some-key", ...) in this repo does not just validate — it produces an error key ("some-key") that flows through several other files that never import the suite. Add, rename, or delete a test and you touch that whole chain, but nothing links the files at compile time, so a partial edit leaves a broken label, an orphaned rendered error, or a stale button-gate.

Core principle: The Vest error key is a contract string shared across the suite, the display-label map, the error-list renderer, and the button-gating logic. Treat every suite edit as a change to that contract, and update all four ends.

The Chain

For a key like "team-members", the lifecycle is:

1. PRODUCE   test("team-members", "msg", () => enforce(...))   in suite.js / *Suite.js
                    │  emits the key into the result
                    ▼
2. READ      suite.get() / result.getErrors()                  → { "team-members": [...] }
                    │
                    ├─▶ 3a. GATE   res.hasErrors() disables Continue / Send-to-Approval
                    │             (e.g. shouldDisableBtn in AgreementEditForm.hooks.js)
                    │
                    ├─▶ 3b. REVALIDATE  handlers call runValidate("team-members", ...)
                    │             on field change (add/remove item)
                    │
                    └─▶ 3c. RENDER  Object.entries(pageErrors).map(([key]) =>
                                     convertCodeForDisplay("validation", key))
                                     in ReviewAgreement.jsx  (error list)
                                          │
                                          ▼
4. LABEL     codesToDisplayText.validation[key] in src/helpers/utils.js
             maps "team-members" → "Team Members" for display

Each numbered node is a different file. A grep of the key string is how you find them all — imports won't lead you there.

When you edit a suite

Removing a test("key", ...)

Grep the key string repo-wide and clean up every dependent:

grep -rn '"team-members"\|team-members' src/ | grep -v "\.test\."
  • 3a Gate — usually self-heals: hasErrors() just stops seeing the key. Verify no code special-cases the key by name.
  • 3b Revalidate — remove now-dead runValidate("key", ...) calls in change handlers; the test they targeted is gone.
  • 3c Render — getErrors("key") / the error-list map now yields nothing for the key; confirm nothing renders an empty shell.
  • 4 Label — the validation[key] entry in utils.js is now orphaned. Remove it. (ESLint will NOT flag it — it's an object key, not a binding.)
  • Tests — update any co-located suite test asserting toHaveProperty("key"); flip "fails if empty" → "passes if empty" when removing a completeness rule.

Adding a test("key", ...)

  • Add a matching label to codesToDisplayText.validation in src/helpers/utils.js, or the error list renders the raw key (convertCodeForDisplay falls back to the code itself when unmapped).
  • If a change handler should revalidate the field live, add a runValidate("key", value) call.
  • Add a suite unit test asserting the key appears/absent as expected.

Renaming a key

Rename at all four nodes atomically: the test() name, any runValidate/getErrors string, the validation label key, and test assertions.

Vest v6 rules (repo convention — see frontend/CLAUDE.md)

These are easy to break while editing a suite:

  • suite.run(data) — not suite(data) (v5 API throws in v6).
  • isNotBlank() fails on numbers — for numeric fields (ids, amounts) use enforce(n).isNotNullish().greaterThan(0).
  • Do not call only(dataObject) with the whole form object — skipped tests retain prior failed state → phantom errors. Pass a field-name string or omit only().
  • Don't call suite.reset() during render — store results via useEffect (see ReviewAgreement.hooks.js).

Quick Reference

NodeFile (representative)Update on remove?ESLint catches a miss?
Produce (test)pages/agreements/review/suite.js, AgreementEditFormSuite.jsdelete the blockn/a
Gate (hasErrors)AgreementEditForm.hooks.js (shouldDisableBtn)usually self-heals—
Revalidate (runValidate)AgreementEditForm.hooks.js handlersremove dead calls✅ if arg var unused
Render (convertCodeForDisplay)pages/agreements/review/ReviewAgreement.jsxverify no empty render❌
Label (validation map)src/helpers/utils.jsremove orphaned key❌
Testsco-located suite.test.jsflip/remove assertionstest run catches

Common Mistakes

  • Deleting the test() and stopping. The label in utils.js and any runValidate("key") calls are now dead. Grep the key string.
  • Trusting lint to find the orphaned label. validation["team-members"] is object data, not a binding — ESLint never flags it. Grep the string, remove by hand.
  • Adding a test() without a label. The error list will render the raw key (e.g. team-members) instead of Team Members, because convertCodeForDisplay returns the code when unmapped.
  • Confusing completeness with authorization. A Vest test() checks whether the form has data (completeness). "Only team members can send to approval" tooltips are a separate backend authorization check (check_user_association) — do not conflate them when editing a suite.
  • Singular vs plural key drift. utils.js may carry both "team-member" and "team-members". Only remove the one your deleted test actually produced; leave unrelated siblings.

Real-World Impact

OPS-5882 removed the team-members Vest test from two suites. Lint passed and a first review passed, but the "team-members": "Team Members" label in utils.js (node 4) was left orphaned — its only consumer was convertCodeForDisplay("validation", key) in ReviewAgreement.jsx (node 3c). Walking the chain above catches all four nodes in one pass. For the general technique beyond Vest, see the dead-code-after-removal-sweep skill.

Repository
HHS/OPRE-OPS
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.