demo D297
A logistics operations platform
50,000 shipments, 2,000 vehicles and 5,000 exceptions on one feed: a KPI strip, two charts, a shipments grid, a derived carrier panel, and a work queue read as a grid, a board or a timeline
createDataRouter · router.subscribe · createKanban
A whole logistics operations screen from one feed: 50,000 shipments, 2,000 vehicles and 5,000 exceptions routed to a KPI strip, two charts, a shipments grid, a derived carrier panel and a work queue you can read as a grid, a board or a timeline. Click a shipment and the queue narrows to its exceptions; run the disruption and watch every panel move together.
The configuration
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.50.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.50.0/lattice-grid.min.js"></script>
<div id="kpis"></div>
<div id="shipments" style="height: 400px"></div>
<div id="work-grid" style="height: 420px"></div>
<div id="work-board" style="height: 420px"></div>
<div id="work-timeline" style="height: 420px"></div>
<script type="module">
import { createDataRouter } from '@toclocoinc/lattice-grid/modules/data-router';
import { createKPI } from '@toclocoinc/lattice-grid/modules/kpi';
import { createKanban } from '@toclocoinc/lattice-grid/modules/kanban';
import { createGantt } from '@toclocoinc/lattice-grid/modules/gantt';
const shipments = LatticeGrid.createGrid(document.getElementById('shipments'), {
rowKey: 'id', selection: { mode: 'single' },
// Named states a user can return to, stored in this browser.
views: {
allowSave: true, local: true,
saved: [{ id: 'exposure', name: 'Late and at risk',
state: { filters: { op: 'or', conditions: [
{ col: 'sla', op: 'eq', value: 'Late' },
{ col: 'sla', op: 'eq', value: 'At risk' } ] },
sort: [{ col: 'etaDrift', dir: 'desc' }] } }],
},
columns: [
{ field: 'shipmentId', title: 'Shipment' },
{ field: 'carrier', title: 'Carrier', filter: { type: 'set' } },
{ field: 'sla', title: 'SLA', filter: { type: 'set' } },
{ field: 'etaDrift', title: 'ETA drift', type: 'number' },
// A shadow column: how far each estimate has moved since this grid first
// saw the row. The column it shadows has to be a real column, so declare
// it (hidden is fine) alongside.
{ id: 'drift', title: 'Since you opened this', shadow: { of: 'etaDrift', kind: 'delta' }, type: 'number' },
],
});
// A derived grid reads whatever the shipments grid is filtered to and
// re-groups it, so a filter above rewrites this panel with no second query.
const carriers = LatticeGrid.createGrid(document.getElementById('carriers'), {
source: {
mode: 'derived', from: shipments, follow: 'filtered', refresh: 400,
groupBy: 'carrier',
select: { shipments: { fn: 'count' }, late: { of: 'lateFlag', fn: 'sum' },
onTime: { of: 'onTime', fn: 'avg' } },
sort: [{ col: 'late', dir: 'desc' }],
crossFilter: true,
},
columns: [
{ field: 'carrier', title: 'Carrier' },
{ field: 'shipments', title: 'Shipments', type: 'number' },
{ field: 'late', title: 'Late', type: 'number' },
{ field: 'onTime', title: 'On time', type: 'number' },
],
});
const workGrid = LatticeGrid.createGrid(document.getElementById('work-grid'), {
rowKey: 'id',
columns: [
{ field: 'exceptionId', title: 'Reference' },
{ field: 'type', title: 'Type' },
{ field: 'severity', title: 'Severity' },
{ field: 'status', title: 'Queue' },
{ field: 'owner', title: 'Owner', editable: true },
],
});
const kpi = createKPI(document.getElementById('kpis'), {
rowKey: 'id', columns: 4,
tiles: [
{ id: 'late', label: 'Running late', aggregation: 'count',
filter: (r) => r.kind === 'shipment' && r.sla === 'Late' },
// onTime is 1 or 0 per shipment and utilisation a fraction, so an average
// formatted as a percentage is the rate itself.
{ id: 'sla', label: 'Delivery SLA', aggregation: 'avg', field: 'onTime',
filter: (r) => r.kind === 'shipment', format: { type: 'percent', decimals: 1 } },
{ id: 'fleet', label: 'Fleet in use', aggregation: 'avg', field: 'utilisation',
filter: (r) => r.kind === 'vehicle', format: { type: 'percent', decimals: 1 } },
],
});
const isOpen = (r) => r.kind === 'exception' && r.status !== 'resolved';
const board = createKanban(document.getElementById('work-board'), {
rowKey: 'id', columnProperty: 'status',
columns: [
{ id: 'new', title: 'New' }, { id: 'investigating', title: 'Investigating' },
{ id: 'carrier', title: 'With carrier' }, { id: 'resolved', title: 'Resolved' },
],
card: { title: { field: 'type' }, subtitle: 'lane', badges: 'severity' },
// A drag is a change to the work item, so put it back into the feed and
// every other view learns about it the same way it learns about anything.
onCardMove: (card, from, to) => { router.apply([{ op: 'upsert', row: { ...card.row, status: to } }]); return true; },
});
const timeline = createGantt({ tasks: [], projectStart: '2026-08-26' });
// One partition key, one router, every view. overlap: true because the KPI
// strip, the board and the timeline all read values a grid already claims.
const router = createDataRouter({ key: 'kind', rowKey: 'id', overlap: true });
router.attach(shipments, 'shipment');
router.attach(workGrid, 'exception', { filter: isOpen });
// A KPI panel and a kanban board are drop-in router targets: both consume the
// same keyed diff a grid does, so one attach keeps each of them live.
router.attach(board, 'exception', { filter: isOpen });
router.attach(kpi, (r) => r.kind === 'shipment' || r.kind === 'vehicle' || r.kind === 'exception');
// A view that is not a keyed-diff consumer takes the subscribe path instead:
// the same slice and the same diff, handed to whatever you like.
const items = new Map();
router.subscribe('exception', (change) => {
for (const row of change.add ?? []) items.set(row.id, row);
for (const row of change.update ?? []) isOpen(row) ? items.set(row.id, row) : items.delete(row.id);
for (const key of change.remove ?? []) items.delete(key);
timeline.setTasks([...items.values()].map((x) => ({
id: x.id, name: x.type, start: x.openedDay, duration: x.targetDays, percentComplete: x.percentComplete,
})));
}, { filter: isOpen });
timeline.mount(document.getElementById('work-timeline'), { dateAxis: true, zoom: 'day', groupBy: 'severity' });
// Clicking a shipment narrows the work queue to its own exceptions.
router.link(shipments, workGrid, { from: 'shipmentId', to: 'shipmentId' });
router.load([...shipmentRows, ...vehicleRows, ...exceptionRows]);
socket.onmessage = (event) => router.apply(JSON.parse(event.data));
</script>
The screen a logistics team lives in all day
This is what an operational product looks like when the data layer under it does the work. Fifty thousand shipments, two thousand vehicles reporting position and load, and five thousand exception records arrive as one mixed feed. One Data Router partitions that feed by what each record is, and every panel above it fills with only the slice it is meant to show: the shipments grid, the KPI strip, the delivery performance line, the exceptions chart and the work queue. None of the panels opened a connection of its own, and none of them knows the others exist.
The shipments grid is a working grid, not a display: fifty thousand rows sorted, filtered and searched at full speed, a set filter on every dimension a planner actually uses, a total under the value column, three defined views to jump between and a save form for the reader’s own. Beside it, the carrier panel is a derived grid. It reads whatever the shipments grid is filtered to, groups it by carrier, and re-derives on a short debounce, so filtering the shipments rewrites the panel with no second query and no second copy of the data. Pick a carrier row and the cross-filter sends the choice back the other way, narrowing the shipments to that carrier.
The ETA drift column is an ordinary number: how far the current estimate sits beyond the plan. The column beside it, Since you opened this, is a shadow column, tracking every shipment’s drift against the value it held when the page opened. It sits at zero until something moves, so it reads as a live measure of what has changed on this shift rather than a stale absolute.
The same work, as a grid, a board or a timeline
Under the shipments sits the work queue: the exceptions still open, and the switch that reads them three ways. As a grid, they are a sortable, filterable list whose owner and queue can be changed in place. As a kanban board, the same rows are cards in queue columns, with an age chip on anything sitting too long, dragged between queues by mouse or keyboard. As a timeline, each work item is a bar from the day it was raised to the day it has to be cleared, laid out in severity lanes against a calendar with today marked and anything past its clear-by date outlined.
There is no second copy of the data behind any of that. A kanban board and a KPI panel both consume the same keyed-diff contract a grid does, so each is a single router.attach away from being live. The timeline takes the router’s subscribe path instead, because a schedule is handed a task list rather than a keyed diff, and it receives exactly the same slice and the same diff the two grids do. Drag a card from one queue to another, or change the queue in the grid, and the change goes straight back into the feed, so every other view learns about it the same way it learns about anything else.
Watching a real disruption
The SIMULATE PORT DISRUPTION button holds one origin hub for a few seconds. Every shipment routed through it slips: the ETA drift climbs, the shadow column goes sharply positive, the SLA reading turns from on time to at risk to late, and some divert to customs. Port congestion exceptions open against the worst of them, and because the work queue is fed from that same feed, they appear in the grid, on the board and on the timeline at once. The KPI strip’s late and open-exception counts rise while the delivery SLA falls, the performance line bends downward, and the exceptions chart grows a new bar. Then the hub clears and everything settles back, so the whole thing can be watched again without a reload.
Nothing in that is a separate demo mode. The disruption pushes rows through the identical router.apply path the ambient feed already uses, exactly the way a real port hold is just more of the same feed rather than a different one.
How do I drive a grid, a board, a timeline and a KPI strip from one feed?
Create one router with createDataRouter({ key: 'kind', rowKey: 'id', overlap: true }) and attach each view to the slice it should receive. overlap: true matters the moment more than one view reads the same value: the KPI strip, the exceptions chart, the work grid and the board all read 'exception' records the grids already claim, and without it only the first route on a value would ever see them. A grid, a kanban board and a KPI panel are all drop-in targets, so each is one router.attach(view, value, opts). Anything that is not a keyed-diff consumer, a schedule, a map, a detail pane, takes router.subscribe(value, handler, opts) and receives the same { add, update, remove } diff to apply however it likes. router.link(shipments, workQueue, { from: 'shipmentId', to: 'shipmentId' }) makes a pick in the shipments grid narrow the queue to that shipment’s own exceptions.