Skip to content

feat(viz): persist series colour slots in the panel model #71

Description

@xe-nvdk

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_KEYS exists in model.ts because a key of __proto__ or constructor in 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. assignSeriesColors already 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 maxPayloadBytes limit alone does not shape well. Add a limit to the LIMITS object in model.ts next to maxTargetsPerPanel — something like maxColorSlots: 64 — and use the constant, never a literal.
  • serializeForSave sorts 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions