Found while re-deriving #11642's population — a sweep of the repo for renders that truncate a list and say nothing. This one is not a printer and not a display cut: the truncation is on the column inference, and the data it loses never appears anywhere in the exported artifact.
Measured (read from source at origin/main945ffbea8; not executed)
packages/plugins/plugin-reports/src/report-service.ts:
functionpickFields(rows: any[],explicit?: string[]): string[]{if(explicit&&explicit.length>0)returnexplicit;constseen=newSet<string>();for(constrofrows.slice(0,50)){if(r&&typeofr==='object')for(constkofObject.keys(r))seen.add(k);}returnArray.from(seen);}functionrenderCsv(rows: any[],fields?: string[]): string{constcols=pickFields(rows,fields);consthead=cols.join(',');constbody=rows.map(r=>cols.map(c=>escapeCsvCell(r?.[c])).join(',')).join('\r\n');returnbody.length>0 ? `${head}\r\n${body}` : head;}The sample is the first 50 rows; the projection is applied to all of them. So when fields is not given explicitly, any key whose first occurrence is at row 51 or later is absent from the header and its values are dropped from every row of the output — including the rows that do carry it.
Two renderers are affected, and renderReport's default: branch is CSV:
renderCsv (:130)renderHtmlTable (:147)
Why this is not merely cosmetic
Sparse columns are the normal shape of report output: an optional field, a formula that only some records satisfy, a lookup that resolves for a subset. The first 50 rows are not a random sample either — they are the query's first page in its sort order, which for a report sorted by, say, status or created date is precisely the correlated case. And unlike a truncated print, the export gives no signal at all: the CSV is well-formed, every row has the same arity, and nothing says a column was inferred rather than declared. A recipient of a scheduled report attachment cannot tell.
Not proposed here
Scanning every row is the obvious fix and is O(rows x keys) on the export path, which is a real cost decision for large reports; a declared column set from the saved report's own schema may be the better answer. Either way it is a behaviour change on an export surface, so it is filed rather than folded into an unrelated card.
Filed unassigned, no labels — severity and route are triage's call.
Found while re-deriving #11642's population — a sweep of the repo for renders that truncate a list and say nothing. This one is not a printer and not a display cut: the truncation is on the column inference, and the data it loses never appears anywhere in the exported artifact.
Measured (read from source at
origin/main945ffbea8; not executed)packages/plugins/plugin-reports/src/report-service.ts:The sample is the first 50 rows; the projection is applied to all of them. So when
fieldsis not given explicitly, any key whose first occurrence is at row 51 or later is absent from the header and its values are dropped from every row of the output — including the rows that do carry it.Two renderers are affected, and
renderReport'sdefault:branch is CSV:renderCsv(:130)renderHtmlTable(:147)Why this is not merely cosmetic
Sparse columns are the normal shape of report output: an optional field, a formula that only some records satisfy, a lookup that resolves for a subset. The first 50 rows are not a random sample either — they are the query's first page in its sort order, which for a report sorted by, say, status or created date is precisely the correlated case. And unlike a truncated print, the export gives no signal at all: the CSV is well-formed, every row has the same arity, and nothing says a column was inferred rather than declared. A recipient of a scheduled report attachment cannot tell.
Not proposed here
Scanning every row is the obvious fix and is O(rows x keys) on the export path, which is a real cost decision for large reports; a declared column set from the saved report's own schema may be the better answer. Either way it is a behaviour change on an export surface, so it is filed rather than folded into an unrelated card.
Filed unassigned, no labels — severity and route are triage's call.