CtrlK
BlogDocsLog inGet started
Tessl Logo

devtools-setting-migration

Workflow for splitting an existing SettingRegistration into a SettingDescriptor (placed in the lowest layer where used: core/, models/, or ui/settings/) and SettingUIDescriptor (registered in a higher-level -meta.ts file, outside of core/ and models/).

64

Quality

76%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./.agents/skills/devtools-setting-migration/SKILL.md
SKILL.md
Quality
Evals
Security

DevTools Setting Migration Guide

This skill guide describes how to migrate an existing legacy SettingRegistration in DevTools by splitting it into a SettingDescriptor (non-UI descriptor) and a SettingUIDescriptor (UI descriptor).


Key Principles & Layer Boundaries

1. SettingDescriptor Location ("Lowest Layer")

  • Lowest Layer Rule: A SettingDescriptor MUST be placed in the lowest architectural layer where the setting is used.
    • Core / Model Settings: If a setting is used by core/sdk (or models/), its SettingDescriptor should be located in core/sdk (or models/).
    • Panel / UI Settings: If a setting is only used by UI components or a specific panel (e.g. panels/console/), its SettingDescriptor MUST GO INTO ui/settings/FooSettings.ts (e.g. ui/settings/ConsoleSettings.ts where Foo is the panel name).
  • CRITICAL RULE — PANEL DESCRIPTORS MUST NOT BE PLACED IN panels/:
    • A SettingDescriptor MUST NOT be placed inside a panels/ directory (e.g. panels/console/ConsoleSettings.ts).
    • Reason: Panel -meta.ts files (e.g. panels/console/console-meta.ts) need to import the SettingDescriptor to call SettingsUI.SettingUIRegistration.register(descriptor, uiDescriptor). -meta.ts files are loaded early and MUST NOT import from panels/ (which would break lazy-loading of panel bundles). Since ui/settings/ is in the ui/ layer below panels/, -meta.ts files can safely import from ui/settings/.
  • CRITICAL RULE — MUST NOT BE IN A -meta.ts FILE:
    • A SettingDescriptor MUST NOT be placed in a -meta.ts file.
    • Reason: Actual runtime code (e.g. models, panels, SDKs) needs to import the descriptor directly to call Settings.instance().resolve(descriptor). Preload -meta.ts files are meant for lazy-loaded extension registrations and must not be imported by runtime code to avoid circular dependencies and module boundary violations.

2. SettingUIDescriptor Location (Higher-Level -meta.ts File)

  • A SettingUIDescriptor defines UI-specific properties (category, title, tags, options, reload requirement, etc.).
  • Higher-Level Meta File Rule: UI registrations MUST BE PLACED IN A -meta.ts FILE ON A HIGHER LEVEL (e.g., entrypoints/main/main-meta.ts, entrypoints/inspector_main/inspector_main-meta.ts, panels/settings/settings-meta.ts, or the specific panel's -meta.ts file).
  • CRITICAL RULE — MUST NOT REMAIN IN core/ OR models/:
    • UI descriptors MUST NOT remain in or be added to -meta.ts files under core/ or models/ (such as core/sdk/sdk-meta.ts or models/*/*-meta.ts).
    • Goal: A major goal of this migration is to completely eliminate -meta.ts files in core/ and models/.

3. Resolving Settings vs moduleSetting(name)

  • Legacy code retrieves settings using string identifiers: Settings.instance().moduleSetting('setting-name').
  • Refactored code should replace moduleSetting('setting-name') with Settings.instance().resolve(settingDescriptor).

Step-by-Step Migration Workflow

Given a setting name (e.g., 'preserve-console-log' or 'network-messages'):

Step 1: Locate Existing Registration & Analyze Use-Sites

  1. Search for the setting name in -meta.ts files to find its Common.Settings.registerSettingExtension block.
  2. Search for all occurrences of 'setting-name' or moduleSetting('setting-name') across the codebase to identify all use-sites.
  3. Determine the lowest layer among all use-sites:
    • If used in core/sdk -> Target directory is core/sdk/.
    • If used in models/ -> Target directory is models/<module>/.
    • If used in a panel (panels/foo) or UI -> Target directory is ui/settings/ (file: ui/settings/FooSettings.ts). NEVER place descriptors in panels/.

Step 2: Extract & Define SettingDescriptor in the Lowest Layer

Decision Strategy: Create vs. Update File

When placing a SettingDescriptor in the target directory, decide whether to create or update a file using these rules:

  1. For Panel / UI Settings (Target is ui/settings/):

    • UPDATE: Check if ui/settings/FooSettings.ts already exists (e.g., ui/settings/ConsoleSettings.ts). If so, add and export the SettingDescriptor there.
    • CREATE: If ui/settings/FooSettings.ts does not exist:
      • Create ui/settings/FooSettings.ts.
      • Add FooSettings.ts to sources in ui/settings/BUILD.gn.
      • Export FooSettings.ts from ui/settings/settings.ts.
  2. For Core / Model Settings (Target is core/ or models/):

    • UPDATE: Check if a module-wide settings file exists in that directory (e.g. core/sdk/SDKSettings.ts, models/workspace/WorkspaceSettings.ts). If so, add and export the SettingDescriptor there.
    • UPDATE: If no module settings file exists and the setting is strictly used inside a single file (e.g., ResourceTreeModel.ts), update that .ts file by exporting the SettingDescriptor at the top.
    • CREATE: Otherwise, create <Module>Settings.ts (e.g., core/sdk/SDKSettings.ts), add it to sources in BUILD.gn, and export it from the module's entrypoint (sdk.ts).

Code Definition Example:

Define and export the SettingDescriptor in the target file:

import type * as Common from '../core/common/common.js';

export const preserveConsoleLogSettingDescriptor: Common.Settings.SettingDescriptor<boolean> = {
  name: 'preserve-console-log',
  type: Common.Settings.SettingType.BOOLEAN,
  defaultValue: false,
  storageType: Common.Settings.SettingStorageType.SYNCED,
};

Note: For conditional settings (dependent on hostConfig), use Common.Settings.ConditionalSettingDescriptor<ValueT, ReasonT> with an isAvailable function.


Step 3: Move UI Registration to a Higher-Level -meta.ts File

If the original registration was in a core/ or models/ -meta.ts file (e.g., core/sdk/sdk-meta.ts), MOVE the UI registration to a higher-level -meta.ts file (e.g., entrypoints/main/main-meta.ts or panels/console/console-meta.ts).

In the higher-level -meta.ts file:

  1. Import SettingsUI from ui/settings/settings.js (e.g., import * as SettingsUI from '../../ui/settings/settings.js';).
  2. Import the SettingDescriptor (from core/sdk/, models/, or ui/settings/).
  3. Register using SettingsUI.SettingUIRegistration.register(...):
import * as SettingsUI from '../../ui/settings/settings.js';
import * as SDK from '../../core/sdk/sdk.js';

SettingsUI.SettingUIRegistration.register(SDK.SDKSettings.preserveConsoleLogSettingDescriptor, {
  category: Common.Settings.SettingCategory.CONSOLE,
  title: i18nLazyString(UIStrings.preserveLogUponNavigation),
  options: [
    {
      value: true,
      title: i18nLazyString(UIStrings.preserveLogUponNavigation),
    },
    {
      value: false,
      title: i18nLazyString(UIStrings.doNotPreserveLogUponNavigation),
    },
  ],
});
  1. Delete the old Common.Settings.registerSettingExtension call from the core/ or models/ -meta.ts file. (If the -meta.ts file becomes empty, delete the file and clean up its build references).

Step 4: Update Call Sites (moduleSetting to resolve)

Find all call sites referencing the setting via moduleSetting:

// BEFORE:
const setting = Common.Settings.Settings.instance().moduleSetting('preserve-console-log');

// AFTER:
import { preserveConsoleLogSettingDescriptor } from './SDKSettings.js';
...
const setting = Common.Settings.Settings.instance().resolve(preserveConsoleLogSettingDescriptor);

For conditional settings, use Common.Settings.Settings.instance().maybeResolve(descriptor) instead of resolve(descriptor).


Step 5: Update BUILD.gn Files and Module Entrypoints

  1. If a new .ts file was created (e.g., SDKSettings.ts or ui/settings/ConsoleSettings.ts):
    • Add the file to sources in its module's BUILD.gn.
    • Export the file from the module's entrypoint (sdk.ts, settings.ts, etc.).
  2. If a core/ or models/ -meta.ts file was deleted, remove it from BUILD.gn and devtools_grd_files.gni.
  3. Verify module imports strictly follow DevTools import rules (refer to devtools-imports skill).

Step 6: Verify Changes

  1. Run autoninja -C out/Default to check GN build.
  2. Run npm run lint to check style and formatting rules.
  3. Run relevant unit tests for the modified module.

Concrete Examples

Example 1: Core/SDK Setting Migration (preserve-console-log)

Before Migration

Setting registered in front_end/core/sdk/sdk-meta.ts (Legacy core meta file):

Common.Settings.registerSettingExtension({
  category: Common.Settings.SettingCategory.CONSOLE,
  storageType: Common.Settings.SettingStorageType.SYNCED,
  title: i18nLazyString(UIStrings.preserveLogUponNavigation),
  settingName: 'preserve-console-log',
  settingType: Common.Settings.SettingType.BOOLEAN,
  defaultValue: false,
  options: [...],
});

Setting used in front_end/core/sdk/ResourceTreeModel.ts:

const setting = Common.Settings.Settings.instance().moduleSetting('preserve-console-log');

After Migration

  1. front_end/core/sdk/SDKSettings.ts (Lowest Layer in Core — NOT a -meta.ts file):
import type * as Common from '../common/common.js';

export const preserveConsoleLogSettingDescriptor: Common.Settings.SettingDescriptor<boolean> = {
  name: 'preserve-console-log',
  type: Common.Settings.SettingType.BOOLEAN,
  defaultValue: false,
  storageType: Common.Settings.SettingStorageType.SYNCED,
};
  1. front_end/entrypoints/main/main-meta.ts (Higher-Level Meta File — NOT in core/ or models/):
import * as SDK from '../../core/sdk/sdk.js';
import * as SettingsUI from '../../ui/settings/settings.js';

SettingsUI.SettingUIRegistration.register(SDK.SDKSettings.preserveConsoleLogSettingDescriptor, {
  category: Common.Settings.SettingCategory.CONSOLE,
  title: i18nLazyString(UIStrings.preserveLogUponNavigation),
  options: [...],
});
  1. front_end/core/sdk/ResourceTreeModel.ts (Call Site in core/sdk):
import { preserveConsoleLogSettingDescriptor } from './SDKSettings.js';

const setting = Common.Settings.Settings.instance().resolve(preserveConsoleLogSettingDescriptor);
  1. front_end/core/sdk/sdk-meta.ts: Registration for 'preserve-console-log' removed.

Example 2: Panel UI Setting Migration (network-messages)

Before Migration

Setting registered in front_end/panels/console/console-meta.ts:

Common.Settings.registerSettingExtension({
  category: Common.Settings.SettingCategory.CONSOLE,
  storageType: Common.Settings.SettingStorageType.SYNCED,
  title: i18nLazyString(UIStrings.networkMessages),
  settingName: 'network-messages',
  settingType: Common.Settings.SettingType.BOOLEAN,
  defaultValue: true,
  options: [...],
});

Setting used in front_end/panels/console/ConsoleView.ts:

const setting = Common.Settings.Settings.instance().moduleSetting('network-messages');

After Migration

  1. front_end/ui/settings/ConsoleSettings.ts (Lowest Layer for UI/Panel Descriptor — NOT in panels/console/!):
import type * as Common from '../../core/common/common.js';

export const networkMessagesSettingDescriptor: Common.Settings.SettingDescriptor<boolean> = {
  name: 'network-messages',
  type: Common.Settings.SettingType.BOOLEAN,
  defaultValue: true,
  storageType: Common.Settings.SettingStorageType.SYNCED,
};
  1. front_end/panels/console/console-meta.ts (Panel Meta File — Imports Descriptor from ui/settings/):
import * as SettingsUI from '../../ui/settings/settings.js';

SettingsUI.SettingUIRegistration.register(SettingsUI.ConsoleSettings.networkMessagesSettingDescriptor, {
  category: Common.Settings.SettingCategory.CONSOLE,
  title: i18nLazyString(UIStrings.networkMessages),
  options: [...],
});
  1. front_end/panels/console/ConsoleView.ts (Call Site in panels/console):
import * as SettingsUI from '../../ui/settings/settings.js';

const setting = Common.Settings.Settings.instance().resolve(SettingsUI.ConsoleSettings.networkMessagesSettingDescriptor);

Interface Reference Summary

SettingDescriptor<T>

Defined in core/common/Settings.ts:

  • name: string: Unique setting name (kebab-case).
  • type: SettingType: BOOLEAN, ENUM, ARRAY, or REGEX.
  • defaultValue: ValueT | ((hostConfig: HostConfig) => ValueT): Default setting value.
  • storageType?: SettingStorageType: SYNCED, LOCAL, GLOBAL, or SESSION.

SettingUIDescriptor

Defined in ui/settings/SettingUIRegistration.ts:

  • category?: SettingCategory: Category under which setting is listed in Settings UI.
  • order?: number: Sorting order.
  • title?: () => LocalizedString: Title string displayed in Settings UI.
  • tags?: Array<() => LocalizedString>: Search tags for Command Menu.
  • options?: SettingExtensionOption[]: Enum / boolean option descriptions.
  • reloadRequired?: boolean: Whether setting change requires DevTools reload.
  • deprecationNotice?: { disabled: boolean, warning: () => LocalizedString, experiment?: string }: Deprecation notice.
  • learnMore?: LearnMore: Help link or tooltip info.
Repository
ChromeDevTools/devtools-frontend
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.