Skip to content

fix(panel-extension): prevent nested converter settings from resetting - #1367

Open
lucobellic wants to merge 4 commits into
lichtblick-suite:developfrom
lucobellic:bugfix/nested-converter-settings
Open

lucobellic wants to merge 4 commits into
lichtblick-suite:developfrom
lucobellic:bugfix/nested-converter-settings

Conversation

@lucobellic

@lucobellic lucobellic commented Oct 15, 2026 •

Copy link
Copy Markdown

User-Facing Changes

Converter settings defined under SettingsTreeNode.children can reset after camera movement or a later panel save.

Description

lichtblick-settings-reset.mp4

From #1363 (comment):

The root cause is that converter edits update the saved layout but leave the 3D panel’s local config and renderer.config stale.
Any later panel save can therefore overwrite those edits.

Camera movement exposes this because it updates cameraState in the local config, then uses the panel’s shared save mechanism, which persists the full configuration.

This fix routes converter edits through renderer.updateConfig, which also synchronizes the panel’s local state through configChange.
That follows the renderer’s existing update flow and ensures subsequent panel saves preserve the edits, regardless of what triggers the save.

So this addresses the underlying state divergence, rather than adding a camera specific workaround for the symptom.

version: 1.29.1

Fix

Add an optional updater to updatePanelSettingsEditor.
The 3D panel uses it to apply edits through renderer.updateConfig and immediately save the updated config.
Other panels keep the existing save behavior.

Testing

Regression tests cover nested settings and persistence after a later save.

Example

Example of extension configuration

From the recording, add a cube with bottom and top texts

import { SceneUpdate, TextPrimitive } from "@foxglove/schemas";
import { ExtensionContext, Immutable, MessageEvent, SettingsTreeAction } from "@lichtblick/suite";

const fromSchemaName = "example.Cube";
const frameId = "base_link";

type CubeConfig = {
  topText: string;
  bottomText: string;
};

const defaultConfig: CubeConfig = { topText: "", bottomText: "" };

function label(text: string, z: number): TextPrimitive {
  return {
    pose: {
      position: { x: 0, y: 0, z },
      orientation: { x: 0, y: 0, z: 0, w: 1 },
    },
    billboard: true,
    font_size: 0.2,
    scale_invariant: false,
    color: { r: 1, g: 1, b: 1, a: 1 },
    text,
  };
}

export function activate(extensionContext: ExtensionContext): void {
  extensionContext.registerMessageConverter({
    fromSchemaName,
    toSchemaName: "foxglove.SceneUpdate",
    panelSettings: {
      "3D": {
        defaultConfig,
        settings: (config) => {
          const topicConfig = config as CubeConfig | undefined;
          return {
            fields: {
              bottomText: {
                input: "string",
                label: "Bottom text",
                value: topicConfig?.bottomText,
              },
            },
            children: {
              nestedTopText: {
                label: "Nested top text",
                fields: {
                  text: {
                    input: "string",
                    label: "Nested top text",
                    value: topicConfig?.topText,
                  },
                },
              },
            },
          };
        },
        handler: (action: SettingsTreeAction, config?: unknown) => {
          if (config == undefined || action.action !== "update") {
            return;
          }
          const topicConfig = config as CubeConfig;
          const field = action.payload.path.slice(2).join(".");
          if (field === "bottomText") {
            topicConfig.bottomText = action.payload.value as string;
          } else if (field === "nestedTopText.text") {
            topicConfig.topText = action.payload.value as string;
          }
        },
      },
    },

    converter: (_message: unknown, event: Immutable<MessageEvent>): SceneUpdate => {
      const topicConfig = event.topicConfig as Partial<CubeConfig> | undefined;
      const config = { ...defaultConfig, ...topicConfig };
      const texts: TextPrimitive[] = [];
      if (config.topText) {
        texts.push(label(config.topText, 0.8));
      }
      if (config.bottomText) {
        texts.push(label(config.bottomText, -0.8));
      }
      return {
        deletions: [],
        entities: [
          {
            timestamp: event.receiveTime,
            frame_id: frameId,
            id: "cube",
            lifetime: { sec: 0, nsec: 0 },
            frame_locked: false,
            metadata: [],
            arrows: [],
            cubes: [
              {
                pose: { position: { x: 0, y: 0, z: 0 }, orientation: { x: 0, y: 0, z: 0, w: 1 } },
                size: { x: 1, y: 1, z: 1 },
                color: { r: 1, g: 0, b: 0, a: 1 },
              },
            ],
            spheres: [],
            cylinders: [],
            lines: [],
            triangles: [],
            texts,
            models: [],
          },
        ],
      };
    },
  });
}

Let me know if you’d prefer that I open an issue so you can address the fix yourself.

Checklist

  • The web version was tested and it is running ok
  • The desktop version was tested and it is running ok
  • This change is covered by unit tests
  • Files constants.ts, types.ts and *.style.ts have been checked and relevant code snippets have been relocated

Summary by CodeRabbit

  • Bug Fixes
    • Direct and nested converter setting changes in the 3D renderer are saved immediately and retained when a later camera-movement save occurs.
    • Panel extension topic settings now update through the appropriate settings handler, while regular panel settings continue to be handled correctly.
    • Subsequent settings edits no longer overwrite previously saved topic configuration changes.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The panel extension adapter now accepts an optional updater for topic settings. The 3D renderer uses it to save topic changes immediately. The debounced save skips a configuration already saved immediately.

Changes

Nested topic settings

Layer / File(s) Summary
Adapter topic settings update
packages/suite-base/src/components/PanelExtensionAdapter/*
The built-in panel context accepts an optional topic settings updater. The adapter routes matching topic actions through it and forces conversion when topic settings exist. Tests cover nested topic actions and regular panel actions.
Renderer topic persistence
packages/suite-base/src/panels/ThreeDeeRender/*
The renderer saves immediately when the topics object changes and skips a debounced save for a configuration already saved immediately. Integration coverage checks that a later camera-movement save retains the topic settings.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant PanelExtensionAdapter
  participant ThreeDeeRender
  participant SaveConfig
  PanelExtensionAdapter->>ThreeDeeRender: Pass topic settings updater
  ThreeDeeRender->>ThreeDeeRender: Apply settings through renderer.updateConfig
  ThreeDeeRender->>SaveConfig: Save when topics reference changes
Loading

Suggested reviewers: ctw-joao-luis

Merge Risk: Minimal · previous review 75d4b

Nested converter settings are retained across the later camera save in the inspected implementation; no actionable merge-blocking risk remains after normal checks.

Pre-merge checks: Passed 5
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: preventing nested converter settings from resetting.
Description check Passed The description includes all required sections, explains the root cause and fix, documents testing, and completes the checklist.
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 5 files.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: lichtblick-suite/lichtblick/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 11e2b472-4fce-4a12-8800-ae995922565f
📥 Commits

Reviewing files that changed from the base of the PR and between d16f07f and 75d4bc5.

📒 Files selected for processing: 1
  • packages/suite-base/src/components/PanelExtensionAdapter/PanelExtensionAdapter.tsx

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.

@lucobellic
lucobellic marked this pull request as ready for review October 15, 2026 10:52
@lucobellic
lucobellic force-pushed the bugfix/nested-converter-settings branch 5 times, most recently from d16f07f to 75d4bc5 Compare October 18, 2026 21:49
@lucobellic

Copy link
Copy Markdown
Author

Hi, CodeRabbit has suggested @ctw-joao-luis as a reviewer, but @rodrigo-p-rodrigues started reviewing the previous branch.

This branch is no longer under active development, and CodeRabbit has already reviewed the latest commit (75d4bc5).

Please let me know if any further changes are needed. Thanks!

Direct layout saves can leave panel config stale and lose nested edits.
Add an optional updater to updatePanelSettingsEditor so panels can apply
converter edits to their own config. Keep the existing save fallback.
Prevent later camera or panel saves from resetting nested converter edits.
Provide a settings updater that applies edits through renderer.updateConfig
and immediately saves the resulting config to keep both copies in sync.

Detect topic changes across both the scene and converter handlers so direct
field edits are saved immediately, keeping displayed values in sync with
the renderer config.
@lucobellic
lucobellic force-pushed the bugfix/nested-converter-settings branch from 75d4bc5 to d90096a Compare November 3, 2026 14:22
@lucobellic

Copy link
Copy Markdown
Author

Branch rebased on develop to get the CI npm audit fix on Ubuntu to pass

@sonarqubecloud

sonarqubecloud Bot commented Nov 4, 2026

Copy link
Copy Markdown

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant