Skip to content

Panel rescue: deep-clone the saved Logs configuration before rendering controls #200

Description

@BorisTyshkevich

Problem

During the Logs rescue path, the workbench renders editable Time / Message / Level controls from the saved Logs configuration:

const [controlsArm, controlsCfg] = rescueLogs
  ? [PANEL_TYPES.logs, { ...saved.cfg }]
  : [PANEL_TYPES[resolved.cfg.type], resolved.cfg];

The rescue branch uses a shallow object spread.

The panel configuration contract intentionally preserves unknown fields for forward compatibility, and those unknown fields may be nested objects or arrays. A shallow spread therefore leaves nested values aliased to the live tab.panelCfg.

The core panel module already provides:

clonePanelCfg(cfg)

which performs the required deep clone for JSON-shaped panel configuration.

Current impact

The problem is currently latent rather than user-visible:

  • Logs-owned fields (time, msg, level) are strings;
  • the current Logs controls replace top-level properties rather than mutating nested values;
  • no known control presently mutates an unknown nested field in place.

However, the rescue path violates the panel-config ownership invariant:

Controls must receive a detached configuration clone and must never share mutable nested state with the saved tab configuration.

A future object-valued Logs field or extension field could otherwise mutate the saved configuration before the explicit onChange write-back.

Goal

Use the canonical deep-clone helper before passing a saved Logs configuration to rescue controls.

Implementation

src/ui/panels.js

Import:

clonePanelCfg

from:

../core/panel-cfg.js

Replace:

[PANEL_TYPES.logs, { ...saved.cfg }]

with:

[PANEL_TYPES.logs, clonePanelCfg(saved.cfg)]

The normal non-rescue path continues using resolved.cfg, which is already a normalized deep clone returned by resolvePanel().

No change is required to:

  • Logs role behavior;
  • rescue detection;
  • fallback rendering;
  • panel type switching;
  • dashboard or detached views;
  • saved-query serialization.

Why the Table readonly observation is not included

The Table arm's sorting, resizing, and cell-detail interactions do not edit or persist panel configuration.

In the Panel registry, readonly means:

no panel-authoring controls and no panel-config write-back

It does not mean that a rendered result must be inert.

Local Table interactions are intentionally available on read-only result surfaces:

  • sorting changes local view state only;
  • column widths change local view state only;
  • cell-detail inspection does not modify the query or panel configuration.

Therefore the Table arm ignoring readonly is not a correctness bug and should not be changed as part of this issue.

A future request to make a specific preview completely non-interactive would need a separate capability such as:

interactive: false

rather than redefining readonly.

Tests

Add a rescue-path test with a nested unknown field:

const extension = {
  nested: {
    value: 1,
  },
};

tab.panelCfg = {
  type: 'logs',
  extension,
};

Render a result that triggers Logs rescue, then change a Logs role.

Verify:

  • the new tab.panelCfg preserves extension;
  • tab.panelCfg.extension !== extension;
  • tab.panelCfg.extension.nested !== extension.nested;
  • the original saved object remains unchanged;
  • the selected Logs role is written correctly;
  • the tab is marked dirty;
  • no SQL query is executed.

Also verify that rendering without a control change does not mutate or replace tab.panelCfg.

Files

Expected changes:

  • src/ui/panels.js
  • tests/unit/panels.test.js

No new runtime dependency.

Acceptance criteria

  • Rescue Logs controls receive clonePanelCfg(saved.cfg).
  • Nested unknown fields do not alias the previous live tab configuration.
  • Existing Logs rescue behavior remains unchanged.
  • Rendering alone never writes panel configuration.
  • A role edit still marks the tab dirty and executes no SQL.
  • No Table interaction behavior changes.
  • Coverage gates hold; no new runtime dependency.

Non-goals

  • Disabling Table sorting.
  • Disabling column resizing.
  • Disabling cell-detail inspection.
  • Redefining readonly.
  • Changing the panel configuration schema.
  • Adding object-valued Logs fields.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions