Repository navigation
feat(viz): persist series colour slots in the panel model #71
Description
assignSeriesColors(names, scheme, prior?) from #30 keeps a series on its colour when another is filtered out — but only if the caller hands back the prior slot map. Nothing persists that map today, so it survives a filter and not a reload: close the dashboard, reopen it, and every series can shift a colour.
That is the exact failure the design is built to avoid. "Colour follows the entity, never its rank" has to hold across sessions, not just across a filter toggle.
The change
Add an optional field to PanelModel in src/lib/dashboard/model.ts:
/** Series name → palette slot, so colours survive a reload and a filter. */
seriesColorSlots?: Record<string, number>;Then validate it in src/lib/dashboard/validate.ts and pin it in the round-trip tests.
The validation, which is the actual work
This is a user-controlled object with user-controlled keys arriving from a JSON body. validate.ts is the trust boundary, and there is already a helper for precisely this:
- Every key must pass
isSafeObjectKey.RESERVED_OBJECT_KEYSexists inmodel.tsbecause a key of__proto__orconstructorin a record that later gets spread or assigned is a prototype-pollution vector. Copy the pattern the existing record fields use — do not write a fresh check. - Every value must be an integer in
[0, PALETTE_SIZE). A float, a negative, or an out-of-range slot is rejected, not clamped.assignSeriesColorsalready ignores an out-of-range prior at runtime, but the validator should not be storing garbage for it to ignore. - Cap the entry count. A panel with 50,000 remembered series names is a payload-size attack that the
maxPayloadByteslimit alone does not shape well. Add a limit to theLIMITSobject inmodel.tsnext tomaxTargetsPerPanel— something likemaxColorSlots: 64— and use the constant, never a literal. serializeForSavesorts keys, so this participates in deterministic serialization for free. Add a test that a panel with these slots round-trips byte-identically, since that is what version-conflict detection rests on.
Then wire it
Whoever renders a panel reads panel.seriesColorSlots into the prior argument and writes the returned slots back when it changes. That write is a dashboard edit like any other — it goes through the normal save path with expectedVersion, not a side channel.
Good first issue notes
Self-contained: two files plus tests, with the patterns to copy sitting next to the code you are adding. npm test covers all of it — model.test.ts and validate.test.ts have many examples of exactly this shape. Read the existing record-field validation before writing new code; the guard you need is already there.
Activity
xe-nvdk commentedon Sep 26, 2026
Closing: the dashboards feature has been dropped from Launchpad. The implemented work was removed in #87 and this work will not be picked up. Recoverable from git tag
pre-dashboards-removal-backupif the feature is ever revived.