demo D290
Governed AI Edits: Change Grid Data in Plain Language, With Approval in ESM
Ask in plain language to change data and approve it first: the model proposes a change, the grid finds the row and previews a before/after diff, nothing is written until you approve, and every change goes through the same gate a person’s edit does, so it can be blocked and it is reversible
createAI · ai.propose · ai.applyProposal · ai.actorBar
This lets you ask in plain language to change data, then approve it first: the model proposes a change, the grid finds the row and previews a before and after diff, and nothing is written until you say so. Every change goes through the same gate a person's edit does, so it can be blocked and it is reversible.
This is the ES module version. The grid is imported straight from a CDN as a module, the same configuration as the vanilla tab without a global script tag, so it drops into any bundler or a bare module script.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<div id="act"></div>
<div id="grid" style="height: 300px"></div>
<script type="module">
import { createGrid } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.esm.min.js';
import { createAI } from '@toclocoinc/lattice-grid/modules/ai';
const STATUS_OPTIONS = [
{ id: 'planned', label: 'Planned' },
{ id: 'in-progress', label: 'In Progress' },
{ id: 'blocked', label: 'Blocked' },
{ id: 'done', label: 'Done' },
];
const grid = createGrid(document.getElementById('grid'), {
rowKey: 'id',
columns: [
{ field: 'id', title: 'Ref', layout: { width: 96, pin: 'start' } },
{ field: 'name', title: 'Project', layout: { flex: 1, min: 180 } },
{ field: 'owner', title: 'Owner', filter: { type: 'set' } },
{ field: 'status', title: 'Status',
lookup: { options: STATUS_OPTIONS },
edit: { enabled: true, editor: 'select' },
cell: { decoration: 'pill', variant: { map: { planned: 'info', 'in-progress': 'warning', blocked: 'danger', done: 'success' } } } },
],
rows, // projects: id, name, owner, status
});
// The one non-negotiable: an AI write goes through the same gate a person's edit
// does. Veto origin 'ai' here and the AI is refused while a hand edit still works.
grid.on('beforeEdit', (e) => {
if (blockAiWrites && e.origin === 'ai') e.preventDefault('Only people can change data here');
});
// Your model, your key. ask() returns a PROPOSAL naming the row and the new
// value, never a write and never rows. The grid finds the row, resolves the
// label to the value it stores, and shows a before/after diff.
const ask = async ({ message, prompt }) => {
const proposal = await myProvider.toProposal(message || prompt); // swap in your model
return proposal; // e.g. { structured: { edits: [{ match: 'Network Upgrade', column: 'status', value: 'In Progress' }] } }
};
const ai = createAI(grid, { ask, tools: false });
// The actor bar: an input, the diff preview, and Approve / Discard. Nothing is
// written until Approve, and the approved change applies through the gated edit
// path a person's edit takes, so it is reversible with grid.history.undo().
ai.actorBar(document.getElementById('act'), {
placeholder: 'Ask the grid to change data: "set the Network Upgrade project to In Progress"',
});
</script>
Ask the grid to change data, and approve it before anything happens
Type the change the way you would say it out loud, “set the Network Upgrade project to In Progress” or, on a board, “move Network Upgrade to In Progress”, and the grid shows you exactly what it would do before it does anything. It finds the row you named, works out that “In Progress” is the value your data stores for it, and lays out a before and after so you can see which rows would change and from what to what. Nothing is written until you press Approve, and Discard throws the whole thing away untouched.
This works on what you are looking at. If your view is filtered, the change is bounded to the rows in front of you, and the preview says so. If an instruction names more than one row, the grid shows you the choices instead of guessing which one you meant.
Nothing changes until you approve
The model never writes your data. It proposes a change, and the proposal is just a description of a row and a new value. The grid takes that description, checks it against its own columns, resolves the label you used to the value it actually stores, and turns it into a before and after diff. You read the diff, and only then do you approve it. Until you do, your data is exactly as it was.
A label that does not match one of a column’s real values is refused rather than forced in, and a value that a column’s own rules reject is refused too, so the diff you approve is always a change the grid would have accepted from a person.
The same gate as a person’s edit
Every approved change goes through the same approval step one of your own people’s edits goes through. That means the rules you already have keep working: a rule that says a status cannot move straight to Done, or that a locked row cannot be touched, applies to a change made this way just as it applies to one typed by hand.
It also means you can treat a change made this way differently from a person’s. In this demo, turn on “Only people can change data” and approving a proposal writes nothing, while you can still edit a status by hand, because the block is set to refuse the AI’s change and allow a person’s. Every change is reversible, so Undo takes the last one back out the way it took any edit back out.
Your model, your key, and it is part of the grid
Changing data in plain language is part of the grid you already have, not a separate product and not an added cost. The grid ships no model and holds no key, and it makes no AI call of its own. You supply one small function, ask(), that wraps the model and key you already use, and everything the grid does with the reply, finding the row, previewing the diff, gating the write, happens here in the browser against your real data.
How do I let people change grid data in plain language?
Load the AI module and call createAI(grid, { ask }), passing the live grid and your own model callback. Then call ai.actorBar(element) to drop an instruction bar above the grid: a person types a change, sees the before and after diff, and presses Approve. For your own interface, ai.propose(instruction) returns the reviewable proposal and ai.applyProposal(result) applies it through the gate, so you can render the preview and the Approve button however you like. For a board, pass the board to ai.actorBar(element, { board }) and a change to the board’s column becomes a card move.
How does changing data this way stay safe?
The model proposes a change, never a write, and the grid validates it before showing you anything: an unknown column, an unknown label, or a value a column’s rules reject is refused. What survives is shown as a before and after diff, bounded to the rows you are looking at, and nothing is written until you approve it. Every approved change then goes through the same gate a person’s edit does, so your own rules can allow a person’s change and block or review the one made in plain language, and any change can be undone.
Wire your own model in production
The demo on this page uses a stand-in ask() that runs in your browser with no key, so the page works offline: it recognises the shape of a change instruction and turns it into a proposal the grid then validates, previews and gates. In a product you replace that one function with a call to the model you choose. Everything else stays the same, and the guarantees are the same whichever model you wire in, because the grid finds the row, previews the diff and gates the write here against your own data.