Lattice Grid Buy a licence

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 runsNo keyValid key
localhost, *.localhost, 127.0.0.0/8, ::1everything, no markeverything, no mark
any other domaineverything, trial watermarkeverything, 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',
});