Inline contracts for your UI. Verified on every save.
Wrap a <button> — declare the API it should call. Wrap a <form> — declare what it submits and where it redirects. Wrap an <input> — declare its validation rules.
A Playwright-driven agent boots your app on every save, drives every contract, and reports every drift between expected and observed — in a dashboard at localhost:3777, inside your repo. No SaaS, no signup, no test files.
import { SayncButton } from 'saync-web/react'; export function AddToCartButton({ productId }) { return ( <SayncButton name="add-to-cart" expects={{ onClick: { apiCall: { method: 'POST', url: '/api/cart', expectedStatus: 200, maxDuration: 500, }, responseShape: { cartCount: 'number' }, }, }} onClick={() => addToCart(productId)} > Add to cart </SayncButton> ); }
What the agent verifies
Every contract you declare on a component is a real assertion. The agent drives the app via Playwright, observes the resulting DOM and network activity, and reports precise expected vs observed diffs.
apiCallButton, Form, Input, Select, Checkbox, …method, URL, expectedStatus, maxDuration{ method: 'POST', url: '/api/cart', expectedStatus: 200, maxDuration: 500 }responseShapeany contract with apiCallJSON field types match the declared shape{ orderId: 'string', total: 'number' }validationInput, Textarea, Select, Slider, FileInputrequired, pattern, minLength, maxLength, message{ required: true, minLength: 8, pattern: /^[a-z]+$/ }onSubmitFormsubmit fires the API, response matches, optional reset{ apiCall: { method: 'POST', url: '/login' }, resetAfterSubmit: true }navigationLink, NavLinkclick lands at the declared URL{ to: '/dashboard', preventDefault: true }disclosureModal, Dialog, Drawer, Popover, Menuopen/close on Escape, outside-click, backdrop{ closesOnEscape: true, hasBackdrop: true }flowmulti-step user journeysevery step reaches the declared end state[{ interact: 'add' }, { fill: 'email', with: 'a@b.co' }, { expect: { url: '/done' } }]
Anatomy of a verification
One contract. Three artifacts. Source, agent action, dashboard issue — all linked.
<SayncButton
name="checkout"
expects={{
onClick: {
apiCall: {
method: 'POST',
url: '/api/orders',
expectedStatus: 200,
maxDuration: 400,
},
},
}}
>
Place order
</SayncButton>- [01] register data-saync-name="checkout"
- [02] navigate http://localhost:3000
- [03] playwright.click('checkout')
- [04] capture network: POST /api/orders
- [05] observed status 500
- [06] observed duration 1.2s
- [07] screenshot.png · 18 KB
- [08] post to /api/runs/:id/results
200 · <400ms500 · 1.2sThe honest Q&A
Hard questions, terse answers. No marketing.
How is this different from a Playwright test suite?+
Where does my data go?+
Can I run this in CI?+
What about production traffic?+
60-second install
Four lines. No config file required. Add a Provider, wrap a component, you're done.
- [01]Installbash
npm install --save-dev saync-web
- [02]Scriptjson
{ "scripts": { "saync": "saync start" } } - [03]Wraptsx
import { Saync } from 'saync-web/react'; <Saync.Provider mode="log">{children}</Saync.Provider> - [04]Bootbash
npm run saync
Open localhost:3777. Your data lives at .saync/saync.db in your repo.