KQ-1747 · SYNC/PAIRS
Sync Pairs
A two-way field mapping between two apps whose fields face each other across a gutter of soft cables. Dragging from a field draws a live cable 1:1 under the pointer; plugged into a compatible field its sag drops in slack and swings taut on springs.recoil, two visible bounces of a rope catching. Pressing a cable's knot cycles its direction — right, left, both — and the arrowhead turns on springs.flick and slides along the curve to its new end on springs.glide, a second chevron sliding out for both ways; lossy flows draw the cable in warn and name what is lost, and types that do not convert refuse to pair. Each side is a listbox: Enter opens a partner list, D turns a pair, Delete unpairs, and narrow containers turn the cables into rows with direction chips.
Field sync
Coldbrook CRM ⇄ Fernworks Desk
Drag a field to its partner · press a knot to turn it
Up and Down move through a side, Left and Right cross to the other side. Enter opens the list of partners; D changes a pair's direction; Delete unpairs.
5 pairs
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/sync-pairs
Props
- cableStyle
- "soft" | "taut" | "dashed"
- default "soft"
- How a cable is drawn: a sagging cable with a soft under-stroke that swings on recoil, a nearly straight one that settles on snap, or a dotted lead.
- density
- "compact" | "regular" | "roomy"
- default "regular"
- Row height (36, 44 or 52px) and type; regular and roomy rows also name the partner.
- accent
- "cobalt" | "moss" | "plum" | "ember"
- default "cobalt"
- The cables' and ports' pigment at a fixed lightness; lossy cables are always warn.
- leftApp
- SyncApp
- default defaultSyncLeftApp
- The app on the left: { name, initials }.
- rightApp
- SyncApp
- default defaultSyncRightApp
- The app on the right: { name, initials }.
- leftFields
- SyncField[]
- default defaultSyncLeftFields
- The left app's fields: { id, name, type } with type text, email, phone, number, money, date, datetime, picklist or boolean.
- rightFields
- SyncField[]
- default defaultSyncRightFields
- The right app's fields.
- pairs / defaultPairs
- SyncPair[]
- default — / defaultSyncPairs
- Each { left, right, direction: "right" | "left" | "both" }; one partner per field. Controlled or initial.
- onPairsChange
- (pairs) => void
- default —
- Fires with every pair after a pairing, an unpairing or a direction change.
- onPair
- (pair) => void
- default —
- Two fields were paired; any pair it replaced is reported through onUnpair first.
- onUnpair
- (pair) => void
- default —
- A pair was taken apart.
- onDirectionChange
- (pair, direction) => void
- default —
- A pair's direction was cycled; directions a type cannot flow in are skipped.
- status
- "ready" | "loading" | "error"
- default "ready"
- Loading shows placeholder rows; error explains and offers Try again.
- onRetry
- () => void
- default —
- Try again was pressed after the fields failed to load.
- title
- string
- default "Field sync"
- The heading.
- height
- number
- default 540
- Height of the surface in px; the board scrolls inside it.
- disabled
- boolean
- default false
- Read only: nothing can be paired, unpaired or turned.
- className
- string
- default —
- Classes for the surface.
| Prop | Type | Default | Description |
|---|---|---|---|
| cableStyle | "soft" | "taut" | "dashed" | "soft" | How a cable is drawn: a sagging cable with a soft under-stroke that swings on recoil, a nearly straight one that settles on snap, or a dotted lead. |
| density | "compact" | "regular" | "roomy" | "regular" | Row height (36, 44 or 52px) and type; regular and roomy rows also name the partner. |
| accent | "cobalt" | "moss" | "plum" | "ember" | "cobalt" | The cables' and ports' pigment at a fixed lightness; lossy cables are always warn. |
| leftApp | SyncApp | defaultSyncLeftApp | The app on the left: { name, initials }. |
| rightApp | SyncApp | defaultSyncRightApp | The app on the right: { name, initials }. |
| leftFields | SyncField[] | defaultSyncLeftFields | The left app's fields: { id, name, type } with type text, email, phone, number, money, date, datetime, picklist or boolean. |
| rightFields | SyncField[] | defaultSyncRightFields | The right app's fields. |
| pairs / defaultPairs | SyncPair[] | — / defaultSyncPairs | Each { left, right, direction: "right" | "left" | "both" }; one partner per field. Controlled or initial. |
| onPairsChange | (pairs) => void | — | Fires with every pair after a pairing, an unpairing or a direction change. |
| onPair | (pair) => void | — | Two fields were paired; any pair it replaced is reported through onUnpair first. |
| onUnpair | (pair) => void | — | A pair was taken apart. |
| onDirectionChange | (pair, direction) => void | — | A pair's direction was cycled; directions a type cannot flow in are skipped. |
| status | "ready" | "loading" | "error" | "ready" | Loading shows placeholder rows; error explains and offers Try again. |
| onRetry | () => void | — | Try again was pressed after the fields failed to load. |
| title | string | "Field sync" | The heading. |
| height | number | 540 | Height of the surface in px; the board scrolls inside it. |
| disabled | boolean | false | Read only: nothing can be paired, unpaired or turned. |
| className | string | — | Classes for the surface. |