developer guide
Licensing, the model layer and recipes
How the licence key works, driving the grid from a language model through the intent layer, and worked recipes for the shapes that come up most.
Developer guide › Licensing, the model layer and recipes
Licensing
There is one Lattice Grid and every copy is feature-identical. No community edition, no pro tier, no feature held back behind a key. A licence removes the trial watermark; that is the whole of what it does.
Free to develop against, licensed to deploy. A grid on localhost, or any loopback host: needs no key at all. On any other domain an unlicensed grid still renders everything and carries a small trial watermark linking to latticegrid.dev.
| Where it runs | No key | Valid key |
|---|---|---|
| localhost, *.localhost, 127.0.0.0/8, ::1 | everything, no mark | everything, no mark |
| any other domain | everything, trial watermark | everything, no mark |
.local, .internal and private IP ranges are not exempt. They are ordinary LAN names, and a corporate intranet is a deployment like any other.
Installing a key
LatticeGrid.setLicence('LG1.…'); // your key; setLicense also works
grid.licence.state(); // 'licensed' | 'localhost' | 'trial'
Call it once, before creating a grid. Setting a key later still works, the watermark comes
off and licence:changed fires, but the first frames of the grid will carry it.
Keys come from latticegrid.dev and are issued per domain rather than per developer or per seat: name the domains the grid will run on and
one key covers every developer, every build and every user on them. A key names the domains
it covers as you would expect: *.acme.com matches app.acme.com,
a.b.acme.com and acme.com itself.
Checking a key needs no network. There is no licence server, no call home, and nothing that can fail at three in the morning, a key carries its own answer and the grid reads it locally, so a grid on an air-gapped network behaves exactly like one on the open internet.
Nothing ever refuses to render, and nothing is ever withheld. An expired key, a wrong domain, a key that will not read: all of them log one warning and show the watermark. Every feature keeps working. The failure to avoid is a customer's production screen going blank because a licence lapsed over a weekend, and a grid that quietly drops a feature is the same failure wearing a disguise.
Driving the grid with a model
The grid describes its own columns and operators, you send that to whichever model you like, and it validates the reply before anything is applied. It makes no network call and has no default provider.
The loop
ai: {
async ask({ message }) {
const res = await yourModelClient.complete({
model: 'your-model-of-choice', messages: [{ role: 'user', content: message }],
});
return res.text;
},
}
That mounts a prompt bar. The user types "EMEA circuits over 500 gigs, biggest first"; the grid composes a message including its schema; your callback returns the model's reply; the grid validates it and shows what it would do in plain English, "Filter Region is EMEA and Capacity is more than 500, sort Capacity descending", with Apply and Discard.
Nothing is executed on trust. The vocabulary is seven actions,
setFilters, setSort, groupBy,
showColumns, hideColumns, setQuick,
clear, and a reply naming a column that does not exist is rejected with a
reason while the valid actions in the same reply are kept. A model cannot be talked into an
operation the vocabulary does not contain, because there is nothing else to call.
Applying is one undo entry, labelled with what it did. docs/AI-SKILL.md is the
reference to hand your model.
Recipes
Put the view in the URL
grid.on('state:changed', () => {
const encoded = btoa(JSON.stringify(grid.state.get()));
history.replaceState(null, '', `?view=${encoded}`);
});
const saved = new URLSearchParams(location.search).get('view');
if (saved) grid.state.apply(JSON.parse(atob(saved)));
Save edits as they happen
grid.on('cell:changed', async (e) => {
if (e.undo) return; // a rollback, not a new change
grid.highlight({ key: e.key, colId: e.colId }, { colour: '#fff3cd', duration: 0 });
try {
await api.patch(`/rows/${e.key}`, { [e.colId]: e.value });
grid.highlight({ key: e.key, colId: e.colId }, { colour: '#d4edda', duration: 900 });
} catch {
grid.highlight({ key: e.key, colId: e.colId }, { colour: '#f8d7da', duration: 0 });
grid.history.undo();
}
});
A read-only grid
createGrid(el, {
columns, rows, rowKey: 'id',
edit: false,
contextMenu: false, // the default menu offers Paste, Clear and Fill down
columnMenu: false, // optional: the header's 3-dot menu
selection: { ranges: false },
});
A dashboard grid, no chrome
createGrid(el, {
columns, rows, rowKey: 'id',
edit: false, contextMenu: false, selection: 'none',
rowHeight: 24, density: 'compact',
grandTotalRow: 'bottom',
});