One live feed, a whole screen of views
Picture the screen an operations desk actually watches. It is never one table. It is orders landing on the left, returns on the right, a throughput line along the bottom, and all three moving because the same events are arriving behind them. One thing is happening in the business, and the screen is three views of it at once.
The grids above are exactly that screen, running on one feed. Orders go to the orders grid, returns go to the returns grid, and a throughput reading goes to the chart, each from the same stream, each seeing only what belongs to it. Watch for a moment and every panel moves on its own. Then click a few orders on the left and the returns grid narrows to the same regions. Nothing on the screen knows the rest of the screen exists.
The two things people build instead
When a screen has to show several related things from one live source, there are usually two ways it gets built, and both go wrong in the same week.
The first is a connection per panel. The orders table opens its own socket, the returns table opens another, the chart opens a third. Now you are running three feeds of the same truth, and they drift: one reconnects and the others do not, one is a few seconds behind, and the numbers on one screen no longer add up because they were read at three different moments.
The second is one giant grid that holds everything and gets filtered six ways to pull each view out of it. That table is doing every job at once, so it is slow to paint, awkward to sort, and impossible to reason about, because a change to the returns view is a change to the same object the orders view is reading.
Both start as a shortcut and end as the thing nobody wants to touch.
A router in front, plain grids behind
The idea is to stop teaching each grid where its data comes from. You build plain grids that only know how to show rows, and you put one router in front of them that owns the routing. The router takes the single feed, splits it by a property you name, and hands each record only to the views it belongs in.
It is a small amount of code, and it reads the way the screen looks:
<!-- the grid, as a global: window.LatticeGrid -->
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid/lattice-grid.umd.js"></script>
<!-- the data router, as a global: window.LatticeGridDataRouter -->
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid/modules/data-router.umd.js"></script>
<script>
const { createGrid, createChart, createHeadlessGrid } = LatticeGrid;
const { createDataRouter } = LatticeGridDataRouter;
// Plain grids. None of them knows about the feed or about each other.
const orders = createGrid(document.querySelector('#orders'), { rowKey: 'id', columns: orderCols });
const returns = createGrid(document.querySelector('#returns'), { rowKey: 'id', columns: returnCols });
const metrics = createHeadlessGrid({ rowKey: 'ts', columns: metricCols });
createChart({ grid: metrics, container: document.querySelector('#chart'), type: 'line', x: 'ts', y: 'value' });
// One router. Partition the feed on "type", then attach each view to its value.
const router = createDataRouter({ key: 'type', rowKey: 'id' });
router.attach(orders, 'order');
router.attach(returns, 'return');
router.attach(metrics, 'metric', { rowKey: 'ts' });
// The opening snapshot: one mixed list, split across every view in one call.
router.load(snapshot);
// The live feed: a mixed batch each tick, each record delivered only where it belongs.
socket.onmessage = (e) => router.apply(JSON.parse(e.data));
</script>
That is the whole wiring. load takes one snapshot and fans it out. apply
takes a mixed batch of changes and delivers each record to the matching views,
through each grid’s own keyed update path, so a snapshot becomes a diff and a
delta lands on the one row it touches rather than repainting the table. A record
that matches nothing is counted and set aside rather than dropped, so a feed that
grows a new kind of event never fails quietly.
Picking a row that narrows another view
The one connection you usually do want is between views: pick something in one grid and the related grid should follow. That is a single line.
// Select orders on the left and the returns grid narrows to their regions.
router.link(orders, returns, { from: 'region', to: 'region' });
Now a selection in the orders grid pushes a filter onto the returns grid on the shared field, re-derived through the same keyed path, so the returns grid stays a plain grid that shows the rows it is given. Try it on the grids above: select a run of orders and the returns beside them close in on the same regions. Clear the selection and the returns open back up.
The same shape, four different desks
Once the feed and the views are separate, the same pattern fits a lot of screens that look nothing alike.
The operations console. This is the screen above. One ops stream splits into orders, returns and a throughput chart, and picking a region draws the related grids in behind it. The person watching it gets one coherent picture that always adds up, because every panel is reading the same tick of the same feed.
The trading terminal. A trader watches a blotter of fills, a positions grid, and a depth ladder, and all three are the same market feed seen three ways. Route the ticks by what each one is, and the blotter, the book and the ladder each take their own share of every message without three subscriptions fighting to stay in step.
The fleet operator. Telemetry from a whole fleet comes up one pipe, and the control room wants a lane per device class, each with its own grid and a sparkline of how it is trending. Partition the single feed by class and every lane fills itself from the one stream, so adding a class is adding a grid, not adding a connection.
The service desk. One ticket stream, and the team wants a board with a lane per queue or per priority so nobody is scrolling to find their work. Split the stream by queue and each lane is a plain grid of just its tickets, live, off the one source everyone is already on.
Every one of these is the same sentence: one feed, many views. The desk changes, the partition key changes, the code does not.
What you would otherwise be building
If you write this by hand, the first version is a switch statement that reads each message and pushes it into the right array, and it works on the demo. Then the feed sends a snapshot and a delta in the same second and they land out of order, so you add sequence numbers. Then a record changes which panel it belongs to and you find it living in two grids at once, so you add a remove-from-the-old-one step. Then a new kind of event arrives that your switch statement has never seen and it vanishes with no trace, so you add a fallback. Then someone selects a row and expects the next grid to follow, and you are writing a filter bus.
Each of those is a real problem, and each is already handled here. Records route to exactly the views they belong in, a moved record moves rather than duplicating, a snapshot is applied as a diff, an unmatched record is counted and kept, and one selection can narrow another view. The grids stay plain and testable on their own, and the router owns the part that used to rot.
Watch the grids above run, then follow the build along in the operations console tutorial, see it on its own demo page, or read the full reference. To build a grid live and try the ideas for yourself, open the sandbox editor.