Lattice Grid Buy a licence

diagnostics

Every warning, explained

A grid prints one line to the console for a mistake it can name on the spot rather than fail on silently. Every one of them is catalogued here: 474 messages across the grid and every module, what each means, and the specific change that clears it.

The grid (core) (240)

  • *.unknown:* '{key}' is not a configuration key this grid recognises, so it had no effect on {column|column group} "{name}". Check the spelling against the reference.
  • apply:* column "{colId}" has no `field` and no `value.apply`, so an edit cannot be written back.
  • bus.beforeReject:* a '{type}' before-handler rejected; the action was cancelled.
  • bus.beforeThrow:*:* a '{type}' before-handler threw; the action was cancelled.
  • bus.handler:* {on|once}('{type}') needs a function; ignored.
  • bus.throw:*:* a '{type}' handler threw; other handlers were unaffected.
  • bus.unknown:* {on|once}('{type}') subscribes to an event the grid never raises, so the handler will never fire. Check the name against the EventName list - a common cause is a near miss such as 'row:click' for 'row:clicked'.
  • changes.unkeyed rows.apply needs a rowKey to identify rows; without one, updates and removals cannot be matched.
  • clipboard/no-read the clipboard could not be read; handle the paste event and pass event.clipboardData instead
  • column.editor-not-enabled:* column '{id}' names the editor '{editor}' but does not enable editing, so it stays read-only. Add `enabled: true`, or use `edit: true` and set `editor` alongside it.
  • columns.duplicate:* two columns resolve to the id '{id}', so the second is ignored. An id defaults to the column's field, so declare an explicit `id` when two columns read the same field.
  • columns.fit-overflow:* columns.fit(): the columns it does not size (resizable: false, and the grid's own checkbox, expander, group and tree columns) take {n}px of a {m}px viewport, leaving {k}px for {c} column(s) that need at least {f}px. They are set to their minimum widths and the grid scrolls
  • columns.group.dissolve:* no band "{id}" to dissolve.
  • columns.group.empty groupColumns was given no known column ids; nothing was grouped.
  • columns.group.idtaken:* band id "{id}" is already in use; nothing was grouped.
  • columns.group.locked:* column "{id}" is position-locked and cannot be grouped.
  • columns.group.move:* no band "{id}" to move.
  • columns.group.rename:* no band "{id}" to rename.
  • columns.group.unknown:* no band "{id}" to add to; nothing was grouped.
  • columns.ungroup.unknown:* no column "{id}" to ungroup.
  • comments.provider comments provider is missing {methods}; those operations are unavailable.
  • compareGroups:by compareGroups needs a "by" column to split the rows into two groups.
  • compareGroups:degenerate compareGroups: the chosen test had too little to work with (too few values, or no variation).
  • compareGroups:distinct compareGroups: the two groups must be two distinct, present values.
  • compareGroups:empty compareGroups: one of the two groups has no rows in the current set.
  • compareGroups:groups compareGroups: the "by" column has fewer than two distinct values to group by.
  • compareGroups:numeric-override compareGroups: {test} needs a numeric column, but the column is not numeric.
  • config.rows.duplicated rows were declared twice - as the top-level `rows` option and inside the `source` block - so the top-level rows were used and the {n} row(s) in `source.rows` were ignored. Declare them once.
  • config.unknown:* '{key}' is not a configuration key this grid recognises, so it had no effect. Check the spelling against the reference.
  • config.value:* '{key}' was given {value}, which is not one of the shapes it accepts. Check the type against the reference.
  • count:* column "{id}" is bitset-backed with no declared row count; assuming {n}
  • date.pattern.token:* the date pattern "{pattern}" contains '{ch}', which is not a supported token, so it is rendered as text. Quote it as a literal to silence this. Supported: {tokens}.
  • defaults:type defaults() expects a configuration object, or null to clear it, but got {type}. The call was ignored and the defaults are unchanged.
  • deps:missing:* computed column "{colId}" declares no `value.deps`; treating it as `deps: '*'` (a whole-row dependency). Declare the dependencies to get precise invalidation.
  • detail.rows detail is enabled but neither `rows` nor `render` was supplied; expanding a master would show an empty region, so it stays off.
  • diff.compare.throw:* the comparator for column '{colId}' threw while diffing; the two values were compared by identity instead.
  • diff.keyOf.throw the diff `keyOf` threw; the row was skipped.
  • diff.norows the diff was asked about the whole data set but has no current rows: pass them in, or supply `currentRows` when constructing the DiffModel.
  • diff.override.throw:* the diff comparator override threw; the column comparator was used instead.
  • diff.read.throw:* reading column '{colId}' threw while diffing.
  • diff.unkeyed {n} snapshot row(s) produced no key and were dropped from the diff; `keyOf` must be the grid's own `rowKey`.
  • edit.apply.throw:* value.apply threw for column '{colId}'; the edit was discarded.
  • edit.confirm.nocommit edit.confirm "manual" needs an edit.commit hook to send the write; optimistic tracking is off.
  • edit.confirm.unknown edit.confirm '{value}' is not a mode; use 'auto' or 'manual'. Treated as 'auto'.
  • edit.enabled.throw:* edit.enabled threw for column '{colId}'; the cell was treated as read-only.
  • edit.getValue.throw:* getValue threw for column '{colId}'.
  • edit.nofield:* column '{colId}' is editable but has neither a field nor value.apply, so the edit had nowhere to go.
  • edit.parse.throw:* value.parse threw for column '{colId}'; the raw editor output was used.
  • edit.true.nocommit edit: true was set over a remote source but nothing can persist the write: no edit.commit hook and the source synthesised none (a read-only adapter, or one that does not declare mutate.update). Cells would change on screen
  • edit.validate.throw:* edit.validate threw for column '{colId}'; the edit was rejected.
  • explode:* column "{id}" has `group.explode`, so rows appear under each of their values and totals double-count. Group footers should be annotated accordingly.
  • export.excel.toExcelValue:* toExcelValue threw for column '{colId}'; the stored value was exported instead.
  • export/no-download download requested but this environment has no DOM; returning the data instead
  • export/no-worker no export worker supplied; streaming on the main thread instead
  • filter:col:* filter references unknown column "{col}"; the condition passes every row
  • filter:op:*:* filter operator "{op}" is unknown for column "{col}"; the condition is refused rather than matching every row. Valid operators here: {list}.
  • filter:relative:*:* filter condition on column "{col}" uses op "relative" with {an unknown relative date token "…"|no relative date token}; the condition is refused rather than matching every row.
  • filters.reapply:* filters.reapply({name}) names no registered where predicate; nothing was re-run.
  • filters.where.condition:* filters.where("{name}", fn, { condition }) needs a FilterSet; the predicate is registered but nothing is pushed to the source.
  • filters.where.deps:* filters.where("{name}", fn, { deps }) needs an array of column ids; the predicate is registered but re-runs on every pass instead.
  • filters.where.fn:* filters.where("{name}", fn) needs a function; the predicate was not registered. Pass null to remove a registered predicate.
  • filters.where.name filters.where(name, fn) needs a non-empty string name; the predicate was not registered.
  • filters.where.threw:* the where predicate "{name}" threw; its rows are treated as not matching:
  • find:disabled find.open(): the find bar is off (config.find is false); grid.find(text) still searches, and find.matches() lists the results.
  • forecast.holtWinters.period forecast: holtWinters needs a period (opts.period >= 2)
  • format:* unrecognised format string "{spec}"; the value will be stringified
  • format:inapplicable:*:* column {col} has format key "{key}", which applies to a {other} column, not the {kind} column it was applied to; it is ignored.
  • format:shape format must be a spec object or a string; ignoring
  • format:unknown:*:* column {col} has an unrecognised format key "{key}" for its {kind} format; it is ignored.
  • formatting.scale.mid.even a scale's "mid" needs an odd number of colours (a middle stop to pin); {n} were given, so "mid" was ignored.
  • formatting.scale.mid.range a scale's "mid" ({mid}) is outside its min..max range, so it was ignored.
  • grid.destroy teardown failed
  • grid.edit.addRow.unavailable edit.addRow was called but this source cannot append rows: it is not a remote source whose adapter declares mutate.append. The row was not added rather than shown on screen and never saved. Use a createPushdownSource over an adapter with mutate: { append: true }.
  • grid.edit.deleteRow.unavailable edit.deleteRow was called but this source cannot delete rows: it is not a remote source whose adapter declares mutate.delete. The row was not removed rather than hidden and then reappearing. Use a createPushdownSource over an adapter with mutate: { delete: true }.
  • grid.edit.deleteRows.disabled edit.deleteRows (and the Delete-key / "Delete row" gestures) were called but row deletion is off. Set config.rowDelete: true to enable it; deleting data on a keystroke is opt-in. Deletion still flows through beforeDelete so it can be confirmed or vetoed.
  • grid.rolling.orderBy:*:* a '{kind}' rolling column needs an 'orderBy' column to define its order - the screen sort is never used, because a rolling figure would then change on every header click. It reported null until an orderBy is set.
  • grid.rolling.window:*:* a '{kind}' rolling column needs a positive window span (a count of rows, or a time span in ms or 'minutes'); it had none, so the column reported null.
  • grid.sort.shape.* sort ignores an entry with keys { {keys} }; expected { col, dir } (AG Grid's sort model uses "colId"/"direction" - this grid uses "col"/"dir").
  • grid.sort.unknown.* sort ignores unknown column "{col}".
  • grid.structural.compositekey a structural append/delete needs a single-field rowKey so the optimistic row can be rekeyed to the server id; with a composite or function rowKey the temp key cannot be written back onto the row. Set rowKey to one field name to use add-row / delete-row.
  • grid.ts.multiplicative:* a multiplicative decomposition of '{col}' meets a non-positive value, where the ratio value ÷ trend is undefined; those rows report null. Use the additive model for a series that crosses or sits at zero.
  • grid.ts.period:*:* a '{kind}' decomposition column needs a declared integer 'period' of at least 2 (the season length - 7 for a weekly cycle in daily data, say); there is no auto-detection, so it reported null until a period is set.
  • groupDefaultExpanded.threw config.groupDefaultExpanded threw: {error}; the group starts expanded.
  • grouping.cardinality:* grouping by {cols} produced {g} groups for {n} rows: nearly one group per row. Grouping a continuous value such as a percentage or a timestamp rarely reads usefully; consider a computed banding column.
  • history.apply.throw applying the {direction} of '{label}' threw.
  • history.capture.throw history capture threw; the action was not recorded.
  • history.edit.throw edit {direction} threw; the entry was dropped.
  • i18n:unknown:* message keys not in the catalogue and so ignored: {keys}
  • iconSet:* unknown icon set '{name}'; falling back to arrows.
  • import.apply.bytes grid.import.apply is synchronous and cannot inflate an .xlsx: await grid.import.xlsx(bytes) for the records (or grid.import.previewXlsx for a preview), then pass those to apply.
  • import.apply.input grid.import.apply was given something it cannot import: pass delimited text, a preview from grid.import.preview, or an array of record objects.
  • import.apply.source grid.import.apply changes the grid's own rows, which only a memory-source grid holds. On a remote, paged or stream source the rows come from the backend; import the file there, or load a memory grid from grid.import.csv(text).
  • import.xlsx.nosheet grid.import.xlsx found no worksheet in the .xlsx; nothing to import.
  • impure:*:* column "{colId}" is an impure computed column (`value.pure: false`) and is being used to {operation}. The result is not stable across passes.
  • infer:* column "{colId}" has mixed or ambiguous values and was inferred as "text". Declare `type` on the column to get correct sorting, filtering and export.
  • ingest.impure.* column "{id}" is an impure computed column and is not materialised into the store
  • ingest.mixed.* column "{id}" holds mixed or unrecognised value types; storing as an object array. Declare a type to avoid this.
  • licence.* no licence key supplied. The grid renders in full and carries a trial watermark on any host other than localhost. Get a key at latticegrid.dev.
  • licence.algorithm this host does not provide crypto.subtle Ed25519, so the licence signature could not be verified. A structural check was used instead.
  • licence.domain licence key is not valid for {host}; it covers {domains}. The grid will render with a watermark.
  • licence.expired licence expired on {date}. The grid will render with a watermark. Renew at latticegrid.dev.
  • licence.product licence key is for '{got}', not '{want}'. The grid will render with a watermark.
  • licence.signature licence signature is not valid. The grid will render with a watermark.
  • lockVisible:* column "{id}" is `layout.lockVisible` and cannot be hidden
  • lookup:load:* column "{colId}" failed to load lookup options
  • lookup:options:* column "{colId}" has an invalid `lookup.options`; expected an array or a function
  • lookup:unknown:* column "{colId}" holds value {value} which is not in its lookup options. Showing the raw value; set `lookup.unknownLabel` or `lookup.allowCustom` to change that.
  • move:* column "{id}" is not movable; the move was ignored
  • move.married:* band "{id}" declares marryChildren, so its columns stay together; the move was ignored
  • op:* unknown filter operator "{op}"; the condition passes every row
  • pending.reconcile.throw writing a server-authoritative value back threw; the cell may still show the optimistic value.
  • pending.revert.throw rolling back a rejected write threw; the cell may still show the rejected value.
  • pending.rows.append.nokey an appended row was confirmed without a server key (the adapter's mutate returned no keys and returning is not 'key'/'row'), so the row keeps its client temp key. It is persisted but cannot be addressed by its server id. Declare returning: 'key' or 'row' on the adapter so appended rows can be rekeyed.
  • pending.rows.rekey.throw rekeying an appended row from its temp key to the server key threw; selection, expansion, focus or in-flight cell writes on the row may still point at the temp key.
  • pending.rows.revert.throw rolling back a rejected structural write threw; the grid may still show the optimistic append or the tombstoned row.
  • pending.rows.stale a structural {kind} has been pending for over {n}s ({key}). With edit.confirm "manual" the host must call settle(id, ok); with "auto" the mutate promise never settled.
  • pending.stale a write has been pending for over {n}s ({key}/{colId}). With edit.confirm "manual" the host must call grid.edit.settle(id, ok); with "auto" the commit promise never settled.
  • perm* the permissions callback threw for column "{colId}"; that column and any later failure were treated as "{closed}". Permissions fail closed, so a broken callback removes columns rather than exposing them. This is reported once.
  • permissions.level:* {where} is "{value}", which is not one of {levels}; the column was treated as "{closed}" (permissions fail closed)
  • permissions.shape config.permissions is a {type}, which is not a level, a map, a list, a callback or a policy object; every column was treated as "hidden" (permissions fail closed)
  • permissions.throw:* the permissions callback threw for column "{colId}"; the column was treated as "{closed}". Permissions fail closed, so a broken callback removes columns rather than exposing them.
  • pivot:handles pivot was given total column ids but no handle(colId) resolver, so no cell values were reduced. Pass values: [{ colId, handle, fn }] or opts.handle.
  • pivot.maxColumns:* pivot would generate {n} columns, above the limit of {max}. Pivot on a lower-cardinality column, or raise pivot.maxColumns.
  • presence.subscribe presence provider has no subscribe(); peers will never appear.
  • presentation.spotlight-inactive setSpotlight was called while no presentation is running, so nothing is dimmed. Start the presentation first, and note that stepping to a view clears the spotlight, so arm it after the deck has started rather than before.
  • preset:* unknown column preset "{name}"; ignoring
  • print/too-many-rows print refused: {rows} rows exceeds the ceiling of {max}. Export to Excel or CSV instead
  • radix:base:* unsupported radix {value}; use 2, 8, 16, 'binary', 'octal' or 'hex'. Falling back to hex
  • radix:preset:* unknown radix preset "{name}"; known presets are {list}
  • radix:unsigned:width signed: false needs a bitWidth to render a two's-complement pattern; defaulting to 32
  • radix:width:* unsupported bitWidth {value}; use 8, 16, 32 or 53. Ignoring it
  • redecorate:* columns.decorate('{id}', …): no column with id '{id}'.
  • regex:* filter operator "matches" received an invalid pattern: {pattern}
  • registry.kind:* unknown registry kind '{kind}'; expected one of {list}.
  • registry.module.install:* module '{name}' has no install() and was ignored.
  • registry.module.reconfigure:* module '{name}' was re-registered with different options; the first registration is kept and this call was ignored. Unregister it first if you mean to replace it.
  • registry.module.shape registerModules received something that is not a module; ignored.
  • registry.module.throw:* module '{name}' threw during install; it was not registered.
  • registry.module.uninstall:* module '{name}' threw during uninstall.
  • registry.name:* register('{kind}', ...) needs a name.
  • registry.onChange.throw a registry change handler threw.
  • registry.replace:*:* '{name}' was already registered as a {kind} and has been replaced.
  • resize:* column "{id}" is not resizable; the resize was ignored
  • rowHeight.throw rowHeight function threw; the default height was used.
  • rowKey.emptyValue:* rowKey {named} is missing on {n} of {m} rows; the first such row has: {fields}. Rows without a key value collapse onto one key, so selection, edits and rows.apply updates land on whichever row now shares it. Set rowKey to a field present on every row, or fix the source data.
  • rowKey.missing no rowKey configured, so rows are identified by object identity. This disables: {features}. Set rowKey to a field name, an array of field names, or a function.
  • rowKey.throw rowKey function threw; falling back to index identity for the affected rows.
  • rows.forEachAll.partial rows.forEachAll walked only the rows this source has loaded: a remote or paged source holds the current page, not the whole set. Total on the server, or use a memory source.
  • rows.forEachExcept.unsupported rows.forEachExcept fell back to the filtered rows: this source cannot exclude one column's filter, so the result is narrowed by the column you asked to leave out.
  • rows.leavesOf.unsupported rows.leavesOf() needs a source that groups in memory; this grid groups elsewhere, so it returned no rows.
  • selection.unstable selection is being tracked by index because no rowKey is configured; it will not survive a sort, filter or reload.
  • shadow.history.window.kind:* a 'history' shadow's 'window' only accepts {kind: 'time', span}; '{kind}' is not honoured (a plain count is already what 'depth' means, and a session has no span to divide into buckets), so it reported the original, unwindowed history.
  • shadow.history.window.span:* a 'history' shadow's time window needs a positive 'span' (ms) or 'minutes'; it had none, so it reported the original, unwindowed history.
  • source.context.unserialisable config.context is not JSON-serialisable; cache keys ignore it.
  • source.derived.cycle a derived grid is deriving from itself, directly or through a chain. The cycle was refused; check the `from` of each grid in the chain.
  • source.derived.follow.* unknown follow "{mode}"; using "filtered". Known: {list}.
  • source.derived.from source.mode "derived" needs `from`: the grid to derive from.
  • source.derived.producer.conflict source.mode "derived": `profile` and `statistics` are both terminal producers and cannot be used together - each replaces the pipeline with its own output builder. Declare one, or chain a second derived grid whose `from` is the first.
  • source.derived.producer.ignored.* source.mode "derived": `{producer}` is a terminal producer and replaces the pipeline, so {keys} {is|are} ignored. Sort, filter or limit the derived grid itself, or chain a second derived grid whose `from` is this one.
  • source.derived.readonly a derived grid is read-only: its rows are computed from another grid. Write to the source grid instead, and the derived one follows.
  • source.derived.statistics.columns source.mode "derived": `statistics: { fn: 'correlation' }` needs `columns`: at least two column ids to correlate pairwise.
  • source.derived.statistics.fn.* source.mode "derived": unknown `statistics.fn` "{fn}". Known: {list}. Every *single-column* statistic is reached through `select` instead, e.g. `select: { p95: { of: 'amount', fn: 'p95' } }`.
  • source.derived.statistics.orient.* source.mode "derived": unknown `statistics.orient` "{orient}" for `fn: 'correlation'`; using "pairs". Known: pairs, matrix.
  • source.derived.statistics.series source.mode "derived": `statistics: { fn: 'series' }` needs both `of` (the column to summarise) and `by` (the column that orders it). `by` is never guessed - kernels see rows in arrival order, which is not the grid's sort.
  • source.derived.statistics.shape source.mode "derived": `statistics` needs an object naming the statistic, e.g. `{ fn: 'correlation', columns: ['a', 'b'] }`. Known fn: {list}.
  • source.derived.statistics.with source.mode "derived": `statistics: { fn: 'datasetVsDataset' }` needs `with`: the second grid to compare this one against.
  • source.derived.union.crossfilter source.mode "derived": `crossFilter` is not supported when `from` is a union of several grids - it has no single target once there is more than one parent. Push a filter onto the individual source grid you mean instead.
  • source.derived.union.cycle.* source.mode "derived": union source "{label}" was refused - it derives from the grid being derived, directly or through a chain ({path}). Remove it from `from`.
  • source.derived.union.failed.* source.mode "derived": union source "{label}" failed to read ({error}) and was skipped this pass.
  • source.derived.union.follow source.mode "derived": `follow` is ignored when `from` is a union of several grids - each entry carries its own `follow` instead.
  • source.derived.union.label.* source.mode "derived": two union sources share the label "{label}"; give each a distinct `label` or __source/__key will not distinguish them.
  • source.derived.union.missing.* source.mode "derived": union source "{label}" has no grid and was skipped.
  • source.derived.union.profile source.mode "derived": `profile` is not supported when `from` is a union of several grids; profile a single grid instead.
  • source.derived.union.statistics source.mode "derived": `statistics` is not supported when `from` is a union of several grids - a relational statistic reduces one grid's own columns, and a union has no single set of them. Derive the statistics of a single grid instead.
  • source.dfql.protected a DFQL update tried to write protected field(s) {fields}; they are server-assigned and immutable, so they were dropped from the PATCH.
  • source.dropped.* column "{colId}" cannot be served under ingest.dropSourceRows: it is an impure computed column (a shadow or a positional/rank column) that is never stored, so it can only be read from the caller's row objects
  • source.duckdb.close a prepared statement against "{from}" could not be closed. The query itself succeeded, so this is a resource leak in the engine connection rather than a wrong answer - but a connection that cannot close statements will eventually refuse to prepare new ones.
  • source.duckdb.count.unreadable the count statement against "{from}" returned no readable count, so the grid has no total for this query and will scroll open-ended rather than show the page size as the whole. This is an engine or driver result-shape problem, not a query problem.
  • source.duckdb.describe DESCRIBE of "{from}" failed ({error}), so filter values are typed from the grid column types alone. A timestamp or date filter on a column whose grid type is not declared may be refused by the engine.
  • source.duckdb.unprepared this DuckDB connection has no prepare(), so filter values cannot be bound. Filters are not sent rather than being interpolated into SQL.
  • source.duckdb.unprepared.aggregates this DuckDB connection has no prepare(), so filter values cannot be bound. Aggregates are not pushed rather than being interpolated into SQL.
  • source.duckdb.unprepared.group this DuckDB connection has no prepare(), so filter values cannot be bound. A grouped level is not fetched rather than having its values interpolated into SQL.
  • source.duckdb.unprepared.grouped this DuckDB connection has no prepare(), so filter values cannot be bound. Grouped aggregates are not pushed rather than being interpolated into SQL.
  • source.graphql.nototal {url} was asked for totalCount and answered without one, so the grid has no total for this query and will scroll open-ended rather than present the page size as the whole. The schema may not expose totalCount on this connection; pass `count: false` to stop asking, or a `parseResponse` that reads whatever the schema does call it.
  • source.graphql.selection the graphql adapter was built without `fields` or `selection`, so the default query selects only `id`. Name the fields your grid shows, or pass a custom `buildQuery`.
  • source.group.* grouping ignores unknown column "{col}".
  • source.mode.* unknown source.mode "{mode}"; falling back to "memory". Known modes: {list}.
  • source.moveRow.dropped rows.move() is not supported under ingest.dropSourceRows: the source objects have been released and the store cannot reorder its columns in place. Enable retainSource (leave dropSourceRows off) to reorder rows.
  • source.odata.nocount {base} was asked for $count=true and answered without an @odata.count, so the grid has no total for this query and will scroll open-ended rather than present the page size as the whole. The endpoint does not support $count, or filters it out.
  • source.paged.fetch source.mode "paged" requires a fetch function; the grid will stay empty.
  • source.pipeline.sort off-thread sort failed; recomputed on the main thread.
  • source.pivot.kernel pivot requested but ctx.compute.pivot is not available; showing unpivoted rows.
  • source.pushdown.adapter createPushdownSource needs an `adapter` with an `execute(query)` function.
  • source.pushdown.aggregates.** the {adapter} adapter cannot compute {grouped }aggregates, so every requested statistic is computed client-side. Correct, and slower than it needs to be.
  • source.pushdown.group.*.* the {adapter} adapter was not asked to group this query because {reason}. The engine returns rows and the grouping is the grid's to do, which needs every matching row in the browser - correct where the grid holds them, and wrong-looking where it holds a window. `lastPlan().unpushed` names it.
  • source.pushdown.group.method.* the {adapter} adapter declares the `group` capability but has no `executeGroupLevel(query, aggregates, request)`, so grouping is not pushed and the grid is left to group whatever rows it holds. Implement the method or drop the capability.
  • source.pushdown.group.totals.*.* the {adapter} adapter cannot compute the group subtotal for {columns}, so those group rows carry no value for that column. A figure computed over anything other than the group would be wrong, so none is shown; `lastPlan().aggregates.client` names them.
  • source.pushdown.group.unfiltered.* the {adapter} adapter could not count the unfiltered set ({error}), so "N of M" falls back to the display count for its denominator.
  • source.pushdown.partial.* the {adapter} adapter returned {n} of {total} matching rows when asked for the whole result. The adapter must follow the engine's own paging before returning.
  • source.pushdown.partial.residual.* the {adapter} adapter returned {n} of {total} matching rows when asked for the whole result, and residual work will run over that fraction. allowPartialResults is set, so the result is used as-is - it may be wrong. The adapter must follow the engine's own paging to be correct.
  • source.pushdown.readonly.* edit is enabled but the {adapter} adapter declares mutate: {false|without update} (read-only by declaration), so no commit is synthesised and the grid stays read-only. Declare `mutate: { update: true }` on the adapter to allow cell edits.
  • source.pushdown.residual.*.* the {adapter} adapter cannot push {work}, so every matching row is fetched and the rest is applied in the browser. Correct, and slower than it needs to be: widening the adapter is the fix.
  • source.relative.* unknown relative date token "{token}"; sending the condition unresolved.
  • source.remote.fetch source.mode "remote" requires a fetch function; the grid will stay empty.
  • source.remote.hostFilter config.hostFilter is not sent to a server-delegated source (paged/remote) and is not applied: filtering only the fetched window would give wrong counts and totals. Express the filter as a FilterSet condition, or send it through config.context.
  • source.rowKey.missing no config.rowKey; falling back to index identity. Row changes, streaming dedupe, selection persistence and remote reload are disabled.
  • source.sort.* sort ignores unknown column "{col}".
  • source.sort.running.* sort ignores "{col}": a running total is a function of the sort order, so it cannot also decide it. Sort by the column it runs over instead.
  • source.stream.columnize worker columnization failed; columnizing on the main thread
  • source.stream.ingest-worker stream ingest.useWorker is on but a column reads through a closure (a computed or date column); columnizing on the main thread instead.
  • source.stream.open source.mode "stream" requires an open function; the grid will stay empty.
  • source.stream.remove removing rows from a live stream is not supported; reload instead.
  • source.stream.testValue ctx.compute.testValue is unavailable; streamed rows are not filtered client-side.
  • source.url.badline.* skipping a malformed NDJSON line (line {n}) from {url} - {error}
  • source.url.url createUrlSource needs a URL string; the grid will stay empty.
  • source.where.notApplied:* filters.where("{name}") is registered but the {mode} source holds only its fetched window and cannot run the predicate over it: the predicate is not applied there, and rows it would exclude stay on screen. Give it a { condition } twin so the engine can narrow the fetch itself - that is the supported route for a paged or remote source.
  • source.where.overThreshold:* filters.where ({names}) is not applied to the {adapter} pushdown source: {the size of the matching set is unknown|its matching set is N rows, at or past the whereRowLimit}. Running the predicate needs every matching row fetched and held here, which is the whole download a pushdown source exists to avoid, so it is refused and the rows the predicate would exclude stay on screen.
  • source.where.pageRelative filters.where ({names}) runs over the rows the {mode} source fetched, not the whole dataset: match counts and totals are page-relative. Give the predicate a { condition } twin so the engine can narrow the fetch itself.
  • stat.windowed:*:* `{stat}` on `{col}` (source #{n}) computed over {covered} of {total} matching rows (windowed source); hold the data in a memory source for whole-dataset statistics.
  • store.dup.* duplicate column id "{id}" in the store schema; ignoring the second
  • store.kind.* column "{id}" declares unknown storage kind "{kind}"; falling back to object
  • store.null.* column "{id}" is declared non-nullable but received null; storing a filler value
  • store.set.* set() on unknown column "{colId}"
  • total:* unknown total function "{fn}"; register it in config.totalFns
  • total.unsafe:*:* column "{id}" is typed {type}, for which the total "{fn}" is not meaningful. Meaningful: {list}. Ignored.
  • tree.cycle tree.parentKey produced a cycle; the affected rows were attached to the root.
  • tree.grouped grouping is active on a grid configured with `tree`; the grouping wins and the hierarchy is not shown.
  • tree.loadChildren tree.loadChildren failed for "{key}": {error}
  • tree.path.duplicate two rows share a tree.path; the later ones were nested beneath the first.
  • tree.path.empty tree.path returned an empty path; the affected rows were placed at the root.
  • tree.path.throw tree.path threw; the affected rows were dropped from the hierarchy.
  • tree.shape tree is configured with neither `path` nor `parentKey`; rows stay flat.
  • type:* unknown data type "{type}"; falling back to "text"
  • undeclared:*:* computed column "{colId}" read undeclared dependency "{field}". Add it to `value.deps` or the value will go stale.
  • unit:compound:*:* unknown compound unit "{symbol}" in system "{system}"; ignoring it
  • unit:display:* unknown display unit "{symbol}"; rendering in {base}
  • unit:system:* unknown unit system "{system}"; known systems are {list}
  • unit:unit:*:* unknown unit "{unit}" in system "{system}"; falling back to {base}
  • views:read saved views could not be read: {error}
  • views:write saved views could not be persisted: {error}

The grid (rendering) (85)

  • annotate.add.invalid annotate.add ignored a mark: it needs a known `type` ({types}) and at least two finite {x, y} points in content coordinates.
  • autoHeight-limit autoHeight is off above {n} rows; use autoHeight: 'visible' to measure only rendered rows
  • board.empty config.board is on but the grid has no columns and no template, so a card would draw nothing. Supply board: { template: '<div>{{cell.name}}</div>' } or add columns.
  • cell-type:* Column '{colId}' cell must be an object or a renderer name.
  • celledit.noeditor:* column "{colId}" has no editor.
  • celledit.offscreen cannot edit a row that is not rendered; scroll to it first.
  • class-fn:* Column '{colId}' cell.class threw.
  • classwhen-fn:*:* Column '{colId}' classWhen['{class}'] threw.
  • classwhen-type:*:* Column '{colId}' classWhen['{class}'] must be a function or a rule string.
  • classwhen:*:* Column '{colId}' classWhen['{class}'] could not be read as a rule: '{text}'. Use a function, or a rule such as 'lt 0', '>= 1000', 'eq Open', 'contains urgent', 'blank'. Valid operators (spec 9.3): {list}.
  • columns.pinned-overflow pinned columns need {n}px of a {m}px viewport, so the {k} unpinned column(s) have no room and cannot be seen. Unpin some, narrow them, or widen the grid.
  • css-escape:* Column '{colId}' uses cell.css, which writes style properties per cell and leaves the class fast path. It is unsuitable for large datasets; prefer a decoration and variant.
  • date-editor:time-format:* column "{colId}" is type "{type}" but its format shows a time. The editor is including one so the time is not overwritten with midnight, but the column should be `type: "datetime"`: `date` means a calendar day.
  • dec-conflict:* Column '{colId}' sets both a decoration and a {renderer|template}. A {renderer|template} owns the cell's content, so the decoration is ignored. Use cell.classWhen alongside a template instead.
  • dec-icon:* Column '{colId}' uses decoration 'icon' without a 'name', 'bands' or 'iconSet'. Give an icon name, a value->name map, a bands list, or a built-in iconSet ({sets}).
  • dec-iconset:*:* Column '{colId}' uses unknown iconSet '{name}'. Valid sets: {list}.
  • dec-name:*:* Column '{colId}' uses unknown decoration '{type}'. Valid decorations (spec 8.7): {list}.
  • dec-shape:*:* Column '{colId}' decoration shape '{shape}' is not one of pill, rounded, square.
  • dec-size:*:* Column '{colId}' decoration size '{size}' is not one of sm, md, lg.
  • dec-type:* Column '{colId}' has a decoration of unsupported type '{type}'.
  • destroy:* Column '{colId}' renderer threw during destroy().
  • detail.rows:failed detail.rows() rejected for row "{key}": {error}
  • detail.target:* detail.target "{selector}" matched no element; details will not be shown.
  • diff-before-format:* the formatter for column "{colId}" threw while formatting a previous value, so the diff hover shows the raw one.
  • editbar.noeditor:* column "{colId}" has no editor.
  • editor:missing:* unknown editor "{name}"; falling back to the data type default
  • filter:missing:* unknown filter "{name}"; falling back to the data type default
  • filter:regex:* invalid regular expression in a "matches" filter: {pattern}
  • filtermenu:* column "{colId}" has no filter to open.
  • fullWidth.render.missing config.fullWidth.when is set but config.fullWidth.render is not; full-width rows are off. Supply render(params) returning a string or a node.
  • fullWidth.render.threw config.{fullWidth.render} threw: {error}; the row is left empty.
  • fullWidth.when.threw config.fullWidth.when threw: {error}; the row is rendered normally.
  • gallery.empty config.gallery is on but the grid has no columns and no template, so a tile would draw nothing. Supply gallery: { template: '<div>{{cell.name}}</div>' } or add columns.
  • groupRenderer.threw config.{groupRenderer} threw: {error}; the row is left empty.
  • header-name:*:* {kind} '{id}' names header renderer '{name}', which is not registered. Register it in config.components.
  • header-render:* the header renderer for {kind} '{id}' threw; the stock heading is shown instead.
  • height.autoHeight-and-rowHeight both `autoHeight` and a `rowHeight` function are set; `autoHeight` wins and the function is not used for row geometry. Drop one.
  • icon:* Unknown icon '{name}'; drew a blank glyph in its place. Register it with grid.icons or config.icons.
  • icon:empty:* An empty icon value at {key} renders nothing; omit `icon` instead.
  • icon:type:* An icon must be a name, a character, or element markup; got {type} at {key}.
  • maximise.detached the grid cannot be maximised because its element is not in the document.
  • menu-layer:* {where} must be a boolean, an array of items or a (params, defaults) => items function; got {type}. Ignoring it.
  • pipe:*:* Column '{colId}' template uses unknown pipe '{name}'. Built-in pipes: {list}. Register others with config.pipes.
  • presentation.conflict More than one card presentation is set ({list}); only {first} draws. Set exactly one of rowTemplate, gallery, recordCard, board or pivotView.
  • qrcode:capacity:* column "{colId}" has values too long for a QR code at level {level}; the maximum this build encodes is {n} bytes (version {v}).
  • qrcode:level:* unknown QR error-correction level "{level}"; using M. Valid levels are L, M, Q and H.
  • recordCard.empty config.recordCard is on but the grid has no columns and no template, so a card would draw nothing. Supply recordCard: { template: '…' } or add columns.
  • render-escape:* Column '{colId}' cell.render returned markup, which was written as text. Set allowUnsafeTemplates: true at grid level to insert it as HTML, or return an HTMLElement.
  • render-fn:* Column '{colId}' cell.render threw; the cell falls back to its formatted text.
  • render-name:*:* Column '{colId}' names renderer '{name}', which is not registered. Register it in config.components, or use a built-in: group, checkbox, progress, link, pill, icon, skeleton.
  • render-type:* Column '{colId}' cell.render must be a name, a function or a class.
  • resize-observer ResizeObserver is unavailable; the grid will not follow container size changes
  • responsive.missing config.responsive is set but carries no template; the grid stays a table at every width. Supply responsive: { maxWidth: 640, template: '<div>{{cell.name}}</div>' }.
  • rowform:container rowForm.container could not be resolved; the form opens over the grid instead.
  • rowTemplate.missing config.rowTemplate is set but carries no template string; the card presentation is off. Supply rowTemplate: '<div>{{data.name}}</div>' or { template: '…' }.
  • rowTemplate.protected a card template binds {fields} on a protected column; the masked text is shown instead. Use {{cell.<column>}} to show what the grid shows.
  • scrollbars-mixed-custom scrollbars is '{x}' on x and '{y}' on y. Drawing one axis hides the native scrollbar on both, because no browser lets one axis be hidden on its own, so the other axis will scroll with no bar. Set scrollbars: 'custom' on both axes, or on neither.
  • scrollToColumn:* scroll.toColumn: no visible column '{id}'
  • span-lookback:* column '{colId}' spans {n} rows, more than the {limit}-row span look-back; it will be clipped once its origin scrolls past that distance
  • stat.container createStat needs a `container`: an element or a selector.
  • stat.document createStat needs a document; it draws DOM.
  • stat.show.* `show` needs `fn: 'min'` or `fn: 'max'`: "{fn}" reduces to a value that no single row holds, so there is no row to report from.
  • template-binding:*:* column '{colId}' has a template binding {{ {name} }} that resolves to nothing and will render empty. Bindings are value, text, index, colId, data.*, row.* and anything in cell.props, a field is reached as {{ data.{root} }}.
  • toolpanel:* unknown tool panel "{name}"
  • tooltip-content:* Column '{colId}' cell.tooltip render returned a value that is not an element, a { title, rows, note } spec, a { html } wrapper or a string, so nothing was shown.
  • tooltip-mount:* Column '{colId}' cell.tooltip mount threw; the tooltip was closed.
  • tooltip-render:* Column '{colId}' cell.tooltip render threw; no tooltip was shown.
  • tooltip-unmount A cell.tooltip unmount threw while the tooltip was closing.
  • tpl-attr:*:* Column '{colId}' template sets '{attr}'. Inline event handlers are not allowed in templates; use grid events or a component renderer. The attribute was dropped.
  • tpl-control:* Column '{colId}' template looks like it contains control flow. Templates have none by design: use cell.classWhen for conditional styling and a function renderer for conditional content.
  • tpl-open:* Column '{colId}' template has an unclosed '{{'. The rest is treated as text.
  • tpl-tag:*:* Column '{colId}' template contains <{tag}>, which is not allowed in a cell template. It was dropped.
  • tpl-unsafe:* Column '{colId}' template uses {{{{expr}}}} (unescaped output), which requires allowUnsafeTemplates: true at grid level. The value will be escaped instead. Turning escaping off must be a deliberate, auditable choice.
  • tpl-url:*:* Column '{colId}' template uses a '{attr}' with a refused scheme. The attribute was dropped.
  • variant-def:*:* (the specific problem found in the definition: a missing theme, a missing role, or a contrast failure with the measured ratio)
  • variant-fn-token:*:* Column '{colId}' variant function returned unknown token '{token}'; falling back to '{fallback}'.
  • variant-fn:* Column '{colId}' variant function threw; falling back to '{fallback}'.
  • variant-op:*:* Column '{colId}' variant.when[{i}] uses unknown operator '{op}'. Valid operators (spec 9.3): {list}.
  • variant-override:* Custom variant '{name}' overrides a built-in token. The built-in colours are contrast-checked in both themes; the override is now your responsibility.
  • variant-shape:* Column '{colId}' has a variant object with neither 'map' nor 'when'. Use a token string, { map }, { when } or a function.
  • variant-type:* Column '{colId}' has a variant of unsupported type '{type}'.
  • variant:*:* Column '{colId}' {where} uses unknown variant '{token}'. Register it with config.variants (light and dark together) or use one of: {list}.
  • views:local:ignored views.local is ignored because views.storage was also supplied; the explicit adapter wins.
  • views:local:parse saved views under localStorage key "{key}" could not be parsed and were ignored: {error}
  • views:local:unavailable localStorage is not reachable; a grid configured with views.local will not persist views across reloads.

Data Router (20)

  • data-router:bad-query-source router.query(adapter) needs an adapter with an execute(query) function (a DFQL/DuckDB adapter or a pushdown source); nothing was fetched.
  • data-router:bad-target attach/subscribe target has no rows.apply and is not a function; its slice goes nowhere. Pass a grid or a handler(change) function.
  • data-router:broadcast-channel router.broadcast({ channel }) needs a string channel name; nothing was mirrored.
  • data-router:buffer-default-cap buffer() called with no window or max; applying a default cap of {n} deltas so the buffer stays bounded. Pass { window } or { max } to size it.
  • data-router:devtools-target router.mountDevtools(el) needs a router with metrics()/on() and a DOM element to render into; the panel was not mounted.
  • data-router:duplicate-source addSource("{id}") was already registered; returning the existing source handle. Use a distinct id per feed.
  • data-router:join-bad-missing join.missing "{value}" is not hold|passthrough|null; using "passthrough".
  • data-router:join-no-fields join has no `fields`/`select`; rows pass through unenriched.
  • data-router:join-no-foreignkey join with a fn `localKey` needs an explicit string/fn `foreignKey`; ignoring the join.
  • data-router:join-no-from join needs a `from` (the lookup source id); ignoring the join.
  • data-router:join-no-localkey join needs a `localKey` (a field name or fn); ignoring the join.
  • data-router:mutual-fn relate({ mutual: true }) with a function relation cannot be inverted; the same predicate is applied in both directions. Use a { from, to } key map for a mutual edge.
  • data-router:no-broadcastchannel router.broadcast() needs the BroadcastChannel API, which this runtime does not provide; cross-tab mirroring is off.
  • data-router:no-buffer {method}() needs a buffered window; call router.buffer({ window, max }) first to record one.
  • data-router:no-indexeddb router.persist() found no usable IndexedDB (a non-browser runtime, private mode, or blocked storage); the router runs in-memory with no durable resume. Pass { storage } to use another backend.
  • data-router:no-onwrite a route attached with { writable: true } has no onWrite handler (per-route or router-global); the edit was left in place but not persisted. Pass onWrite to route the write.
  • data-router:overlap:* Data Router: value {value} already routes to another viewer; with overlap:false only the first receives rows. Pass createDataRouter({ overlap: true }) to fan one value to several viewers.
  • data-router:readonly-write an edit on a derived (rollup/transform) route is read-only and was reverted; a derived row cannot be inverted to a source row. Make the source route writable instead.
  • data-router:remove-rejected:* route "{name}" asked its target to remove {n} row(s) the target does not have (for example "{id}"). The rows are still on screen. This is a key mismatch: the key the route derives for a row is not the key the grid derives for it. Check that the route's `rowKey` names the same value as the grid's.
  • data-router:writable-target attach({ writable: true }) needs a grid exposing grid.on and grid.edit.setCells; write-back is off for this route.

Charts (18)

  • chart:*:empty chart bound nothing from the {n} row(s) the grid is showing, so it shows its empty state. The binding named no series to draw: check that the spec names the columns this chart needs - a flow type needs source and target, a measure needs a column with values in it.
  • chart:*:incomplete a candlestick chart needs four measures - open, high, low and close, in that order - and only {n} were bound. Pass them as measures: [open, high, low, close].
  • chart:*:missing-column chart {key}: "{col}" names a column this grid does not have, so there is nothing to read and nothing to draw. The columns it does have: … Bind a column the grid declares, or declare the column.
  • chart:*:no-domain chart x column "{col}" is drawn on a {time|continuous} axis, and not one of its {n} value(s) reads as a {date|number} - so the axis would be drawn over a domain invented from nothing rather than measured from the data. Remove axis.x.scale to let the column choose its own scale, or type the column so its values parse.
  • chart:*:x:array chart "x" expects one column id (a string) and was given an array [{ids}]. A chart takes one category column on "x". For a hierarchy, group the grid - grid.columns.group([...]) - and the hierarchical types (pie, donut, sunburst, treemap) read the grouping.
  • chart:*:x:window:ahead {n} reading(s) on the x axis{ column} were not drawn: stamped up to {lead} ahead of this device's clock, which is more than the {limit} ({share} of the {span} rolling window) a reading may run ahead and still move the window. This is a producer whose clock is wrong, not a window that is too narrow
  • chart:*:x:window:stale:* the rolling x window covers the last {span} ms, and the newest of {n} x value(s) is {age}s old, so every mark would be drawn outside the plot. The chart shows its empty state instead. Seed it with data inside the window, widen axis.x.window.span, or drop the window while showing history.
  • chart:annotation:dropped:*:*:* chart annotation{ (label)} of kind "{kind}", reading the {axis} axis, was not drawn: {reason}.
  • chart:geomap:pack:* geometry pack "{name}" has not been loaded, so the map fell back to the schematic continents. Import the pack module and pass it as `shapes`
  • chart:geomap:projection:* (the projection resolver's own explanation of why the named projection could not be used)
  • chart:icon:* Unknown icon '{name}' on a network node; drew a plain disc instead. Register it with registerIcon or config.icons, or name a built-in.
  • chart:icons:* A network node asked for the icon '{name}', but this grid publishes no icon registry - a headless grid has no sprites. The node drew as a plain disc.
  • chart:measure:fn:* chart measure fn {fn} is not a known aggregation; using '{default}'. Supported: {list}.
  • chart:x:banded:* chart x column "{col}" is typed "{type}", so its {n} values are drawn as categories on a band scale even though they all read as {dates|numbers}. Type the column {'date'|'number'} for a continuous axis, or pin it with axis: { x: { scale: '{kind}' } }.
  • chart:x:date-bucket:* chart x column "{col}" is a 'date', which stores a calendar day, so all {n} rows collapsed into one point and were reduced by '{fn}'. For a series inside a single day, type the column 'datetime' or bind epoch milliseconds.
  • chart:x:scale:unknown:* chart axis.x.scale "{scale}" is not a scale this chart draws. Use one of: {list}. The scale was chosen from the column instead.
  • chart.axis.window.band a rolling time window needs a continuous x axis; this chart bands its x values, so the window is ignored. Bind a date or numeric column to x.
  • chart.axis.window.kind axis window kind "{kind}" is not supported on a chart axis; only { kind: "time", span } rolls the domain. Bound the row count with the source's maxRows instead.

Gantt (17)

  • gantt-cycle-* gantt: dependency cycle detected ({path}); schedule refused.
  • gantt-delete-unknown-* gantt: deleteTask for unknown task "{id}" ignored.
  • gantt-destroyed gantt: {method} on a destroyed controller ignored.
  • gantt-edit-mapped-field gantt: this plan reads {fields} through a `fields` FUNCTION, which has no inverse, so applyEdit writes the canonical property name and the edit will not be read back. Map those fields to a field NAME instead, or edit the host row through your own writer and feed the result in with rows.apply.
  • gantt-edit-unknown-* gantt: applyEdit for unknown task "{id}" ignored.
  • gantt-level-mapped-start gantt: level() is not available on a plan that maps `fields.start`, because leveling has to write a new start back and a field mapping only says how to READ one. Level the plan with its own `start` field, or apply the returned moves yourself.
  • gantt-level-unresolved gantt: resource leveling hit its {n}-iteration cap with {m} over-allocation(s) unresolved; returning the partial result.
  • gantt-link-shorthand-*-* gantt: dependency type {type} carries a lag of {lag} but lag: {explicit} was also given; the explicit lag wins. Give one or the other.
  • gantt-mspdi-not-project gantt: importMSPDI input is not an MSPDI <Project> document.
  • gantt-state-no-container gantt: setState was asked to show a view but no container is known yet; call mount()/mountSplit() at least once first.
  • gantt-state-version gantt: setState received a state from a newer version ({got} > {known}); unrecognised fields are ignored.
  • gantt-summary-link-*-*-* gantt: {type} link with a summary endpoint ({from} -> {to}) is expanded to descendant leaves - a conservative v1 reading; only FS is exact for summary endpoints.
  • gantt.beforeReject:* gantt: a '{event}' before-handler rejected; the action was cancelled.
  • gantt.beforeThrow:* gantt: a '{event}' before-handler threw; the action was cancelled.
  • gantt.projectEpoch:* gantt: projectEpoch {value} is not a date or a day number; the plan is rendered against the Unix epoch (day 0 = 1970-01-01).
  • gantt.today:* gantt: today {value} is not a date or a day number; no today line is drawn.
  • gantt.zoomWidth:* gantt: mount was given both zoom ({zoom}) and width ({width}); zoom fixes the pixels-per-day, so the width is ignored and the plot scrolls. Drop one of the two.

Kanban (17)

  • kanban:addRow the bound grid cannot append a row (no mutate.append).
  • kanban:apply-gridbound rows.apply on a grid-bound board is ignored; route to the bound grid instead.
  • kanban:beforeReject:* a '{event}' before-handler rejected; the action was cancelled.
  • kanban:beforeThrow:* a '{event}' before-handler threw; the action was cancelled.
  • kanban:children-factory a card pop-out needs children.factory (a createGrid), children.render, or children.asBoard.
  • kanban:columnProperty a board needs a "columnProperty" to group cards into columns.
  • kanban:event:* "{name}" is not a board event; expected one of {list}.
  • kanban:flow-chart-deps flow.renderChart needs createChart and createGrid handed in (e.g. from 'lattice-grid/modules/charts' and 'lattice-grid').
  • kanban:flow-chart-kind:* "{kind}" is not a flow chart; expected cfd, wip, throughput, cycleTime or leadTime.
  • kanban:setRows-gridbound setRows on a grid-bound board is ignored; the rows come from the bound grid.
  • kanban:sla:no-threshold sla is enabled but no warn/breach threshold is set (globally, per column or per lane); no card will ever age.
  • kanban:unplaced:* {n} card(s) have a "{columnProperty}" value outside the configured columns and are not shown; add the column or map the value.
  • kanban:wip:* a move into "{column}" was refused: it is at its WIP limit of {limit}.
  • kanban:writeback-noop an inline edit to "{field}" did not persist through the bound grid (a read-only, non-editable, permission-blocked or unresolvable column).
  • kanban.filters.reapply:* board.filters.reapply({name}) names no registered predicate; nothing was re-run.
  • kanban.filters.where.fn:* board.filters.where("{name}", fn) needs a function; the predicate was not registered. Pass null to remove a registered predicate.
  • kanban.filters.where.name board.filters.where(name, fn) needs a non-empty string name; the predicate was not registered.

KPI (15)

  • kpi:age-clock:* config.ageBy {named} read no time from any of the panel's {n} rows, so the panel cannot tell a silent feed from a live one and grades every tile as usual - check it against the rows (or the bound grid's column ids), or drop `ageBy` to measure arrival time instead.
  • kpi:ageBy-no-maxAge config.ageBy names the clock `maxAge` reads, and this panel sets no `maxAge`, so nothing reads it - add `maxAge` (ms) to say how long the panel waits before it stops grading what it holds.
  • kpi:agg:* "{aggregation}" is not a KPI aggregation; expected one of {list}. Falling back to count.
  • kpi:apply-gridbound rows.apply on a grid-bound KPI panel is ignored: the panel follows the bound grid, so route the change to the grid instead and the panel will follow it.
  • kpi:clock:ignored:*:* the "{label}" clock tile does not take `{key}`; a clock tile shows the device clock and `{key}` is ignored.
  • kpi:clock:tz:* the "{label}" clock tile's timeZone `{zone}` is not a recognised IANA zone; falling back to the local time zone.
  • kpi:delta:* kpi: tile "{id}" has delta: {value}; expected one of {list}. Falling back to 'both'.
  • kpi:event:* "{name}" is not a KPI event; expected one of {list}.
  • kpi:fields:type config.fields must be an array of column names; ignoring it.
  • kpi:no-column::* config.fields names `{field}`, which is not a column on the bound grid - check it against the grid's column ids.
  • kpi:no-column:*:* the "{label}" tile's field `{field}` names no column on the bound grid, so the tile has nothing to measure - check it against the grid's column ids.
  • kpi:setRows-gridbound setRows on a grid-bound KPI panel is ignored; the rows come from the bound grid.
  • kpi:tree:dotted-id kpi: tile ids contain dots; set `tree` to render them as a hierarchy.
  • kpi:tree:unplaced {n} KPI tile(s) are not on the tree and will not render: {ids}. Two tiles cannot share one path; a path names the tile's own place, last segment included.
  • kpi:unprojected:*:* the "{label}" tile's filter read `{field}`, which is a column on the bound grid but is not projected - add it to `fields`: createKPI({ grid, fields: ['{field}'], … }).

Tabs (15)

  • tabs.activate.unknown tabs: activate("{id}") - no such tab.
  • tabs.badge.build:* tabs: tab "{id}" - its headless count grid could not be built ({error}); no badge is shown for it.
  • tabs.badge.fn:* tabs: tab "{id}" - its badge function threw ({error}); no badge is shown.
  • tabs.badge.headless tabs: a tab configured with `badge` has never been activated, so it has no grid to count and shows no badge until it is. Pass `config.createHeadlessGrid` - e.g. `import { createHeadlessGrid } from 'lattice-grid'` - to give every tab a live count from first paint.
  • tabs.badge.type:* tabs: tab "{id}" - badge must be true, a number, a string, or a function; got {type}. No badge is shown for this tab.
  • tabs.badgeTone.fn:* tabs: tab "{id}" - its badgeTone function threw ({error}); the badge is drawn untoned.
  • tabs.badgeTone.type:* tabs: tab "{id}" - badgeTone must be one of {tones}, or a function returning one; got {value}. The badge is drawn untoned.
  • tabs.badgeTone.value:* tabs: tab "{id}" - badgeTone returned "{value}", which is not one of {tones}. The badge is drawn untoned.
  • tabs.createHeadlessGrid.type tabs: config.createHeadlessGrid must be the headless grid factory (a function); got {type}. It is ignored, so a tab that has never been activated shows no badge until it is.
  • tabs.icon:* tabs: tab "{id}" - icon must be a single character or emoji, or an Element; got {got}. It is ignored. Markup is never parsed here: build the element and pass it instead.
  • tabs.on.unknown:* tabs: "{name}" is not an event this module emits.
  • tabs.source.overridden tab "{id}": config.source is replaced by the derived source built from from: "{parent}" - set the narrowing (where/group/join/…) on the tab descriptor itself rather than on config.source.
  • tabs.view.build:* tabs: tab "{id}" - the headless grid deriving its rows could not be built ({error}); the body mounts with its own config's rows and does not follow.
  • tabs.view.headless tabs: tab "{id}" derives from "{parent}" and its body is not a grid, so its rows have to be derived by a headless grid. Pass `config.createHeadlessGrid` - e.g. `import { createHeadlessGrid } from 'lattice-grid'` - or the body mounts with whatever rows its own config carries and does not follow its parent.
  • tabs.view.type:* tabs: tab "{id}" - view must be a factory function, (el, config) => instance, such as createKanban or createKPI; got {type}. This tab is mounted as an ordinary grid.

Layout (11)

AI (8)

  • ai:actorbar:no-el ai.actorBar() needs a DOM element to mount into; none was given and there is no document.
  • ai:askbar:no-el ai.askBar() needs a DOM element to mount into; none was given and there is no document.
  • ai:event:* "{name}" is not an AI event; expected one of {list}.
  • ai:explain:no-doc attachExplain() needs a document to build a button.
  • ai:insights:no-el ai.insights() needs a DOM element to mount into; none was given and there is no document.
  • ai:narrative:empty the AI ask() returned no narrative text; nothing to show. Ensure your ask() resolves to a string or { text } for a narrative prompt.
  • ai:no-grid createAI needs a Lattice grid instance to ground on (grid.statistics/grid.rows). Pass the grid returned by createGrid or createHeadlessGrid.
  • ai:risk:no-sources a risk summary needs a Gantt and/or a Kanban board to ground on. Pass { kind: "risk", gantt, board } (or precomputed earnedValue/schedule/breaches); there is nothing to summarise.

The grid (background worker) (8)

  • worker/blob-failed could not construct an inline worker; compute will run on the main thread
  • worker/error compute worker failed; falling back to the main thread
  • worker/kernel-missing (the worker's own error message, forwarded verbatim)
  • worker/local-* {op} received a function argument and cannot cross the worker boundary; running on the main thread
  • worker/no-blob no blob: URL support and no config.workerUrl; compute will run on the main thread
  • worker/no-compute compute kernels could not be loaded; sorting and filtering are unavailable
  • worker/ready-no-compute worker started without compute kernels; compute falls back to the main thread (columnization still offloads)
  • worker/shared-unavailable sharedMemory was requested but the page is not cross-origin isolated; falling back to the copying transport

Shared across modules (4)

Alarms (4)

  • alarms:grid-no-columns alarms.attach(grid) was given no `columns`, so no cell can raise anything. Declare the columns to watch: attach(grid, { columns: { cpu: { thresholds: { warn: 80, critical: 95, direction: 'lowerIsBetter' } } } }).
  • alarms:publish-no-router alarms.publish() needs a data router with addSource(); nothing was published.
  • alarms:unknown-event:* alarms.on('{name}') will never fire; an alarm set emits {events}.
  • alarms:unknown-source alarms.attach() was given something that is not a KPI panel, a grid or a data router; nothing was attached and no alarm can be raised from it.

dhtmlx-compat (4)

  • dhtmlx.*.dropped:* dhtmlx-compat does not translate the {kind} key '{key}', so it has no effect. Nothing was applied for it.
  • dhtmlx.editorType.unknown:* dhtmlx-compat does not recognise editorType '{editorType}'; the default text editor was used instead.
  • dhtmlx.header.multirow dhtmlx-compat collapsed a {n}-row header to its first row. Lattice titles are a single string; the remaining rows were not applied.
  • dhtmlx.span.dropped:* dhtmlx-compat does not translate the span key '{key}', so it has no effect. Lattice spans carry only geometry (rowspan/colspan); the cell's value, css and tooltip come from its own column, not the span.

Angular adapter (2)

  • angular.deprecated modules/angular is deprecated: it needs the JIT compiler. Use @toclocoinc/lattice-grid/angular.
  • angular.tabs.mountOnly.tabs <lattice-tabs>: `tabs` changed (a label, a badge or a descriptor), but the strip takes it only when created - the module has no way to repaint an existing tab. Nothing was applied and the strip was NOT rebuilt, because rebuilding it would lose whichever tab is open and any state its content holds. Put the strip inside an `@if` keyed on what changed if a genuine rebuild is wanted; a live badge otherwise belongs in your own template.

React adapter (2)

  • react.tabs.mountOnly.tabs LatticeTabs: `tabs` changed (a label, a badge or a descriptor), but the strip takes it only when created - the module has no way to repaint an existing tab. Nothing was applied and the strip was NOT rebuilt - rebuilding it would lose whichever tab is open and any state its content holds. Give the component a `key` that changes when a genuine rebuild is wanted; a live badge otherwise needs to live in your own render, as `tab.badge` on this component's own props does today.
  • react.viewer.*.mountOnly.* {Viewer}: `{prop}` changed, but {Viewer} takes {it|them} only when the viewer is created. Nothing was applied and the viewer was NOT rebuilt - rebuilding it silently would throw away scroll position, selection and expansion. Give the component a `key` that changes when this must take effect, so the rebuild is yours and visible, or drive the instance through the ref.

Svelte adapter (2)

  • svelte.grid.mountOnly.name LatticeGrid: `name` changed from "{was}" to "{now}", but a grid publishes itself under the name it had when it mounted. Nothing was republished and the grid was NOT rebuilt - rebuilding it would throw away scroll position, selection and expansion. Give the component a `key`-style `{#if}` when a genuine rebuild is wanted.
  • svelte.tabs.mountOnly.tabs LatticeTabs: `tabs` changed (a label, a badge or a descriptor), but the strip takes it only when created - the module has no way to repaint an existing tab. Nothing was applied and the strip was NOT rebuilt, which would have lost whichever tab is open and any state its content holds. Wrap the strip in an `{#if}` you toggle when a genuine rebuild is wanted; a live badge otherwise belongs in your own markup.

Vue adapter (1)

  • vue.tabs.mountOnly.tabs LatticeTabs: `tabs` changed (a label, a badge or a descriptor), but the strip takes it only when created - the module has no way to repaint an existing tab. Nothing was applied and the strip was NOT rebuilt - rebuilding it would lose whichever tab is open and any state its content holds. Give the component a `key` that changes when a genuine rebuild is wanted; a live badge otherwise needs to live in your own render.

Web component (1)

  • webcomponent.router.mountOnly.config <lattice-router>: `config` changed after the router was built. Nothing was applied and the router was NOT rebuilt - rebuilding it would detach every grid and drop every row it holds. Replace the <lattice-router> element itself when a genuinely different router is wanted.