Skip to main content

add-localstorage-setting

Persist a new setting in LocalStorage via the ketcher-opts mechanism and wire it into a React component in ketcher-react. Use when: a component needs to remember user preferences, UI state, or editor options across page reloads; adding a new field to SettingsService and reading/writing it through the useSettings hook; replacing direct localStorage access with the standard settings store.

설치로 이동

소스 정보

저장소
epam/ketcher
최근 소스 활동
2026년 7월 22일 06:46
감지된 SKILL.md 언어
영어
스타
877
포크
262

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
add-localstorage-setting
description
Persist a new setting in LocalStorage via the ketcher-opts mechanism and wire it into a React component in ketcher-react. Use when: a component needs to remember user preferences, UI state, or editor options across page reloads; adding a new field to SettingsService and reading/writing it through the useSettings hook; replacing direct localStorage access with the standard settings store.
argument-hint
<setting-name> <type> <default> in <component-file-path> — e.g. colorPickerCustomColors string[] [] in src/script/ui/component/form/colorPicker/ColorPicker.tsx
# Add a New LocalStorage Setting ## Before You Start This skill needs four pieces of information. If any are missing from the request, **ask the user before writing any code**: | # | What you need | Example | | --- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | | 1 | **Setting name** (camelCase) | `colorPickerCustomColors` | | 2 | **TypeScript type** | `readonly string[]`, `boolean`, `'px' \| 'pt'` | | 3 | **Default value** | `[]`, `false`, `'px'` | | 4 | **Target component path** and how it uses the setting (read-only? writes on which event?) | `src/script/ui/component/form/colorPicker/ColorPicker.tsx` — reads on open, writes on Apply/Delete | Only proceed once all four are clear. --- ## Overview All persistent settings live under the `ketcher-opts` key in `localStorage`, managed through a centralized `SettingsService` in `ketcher-core`. React components read and write settings via the `useSettings` hook. A complete addition touches **four locations**: | # | File | What to add | | --- | ---------------------------------------------------------- | --------------------------------------------------------------- | | 1 | `packages/ketcher-core/src/application/settings/types.ts` | Field declaration in `Settings` interface | | 2 | `packages/ketcher-core/src/application/settings/schema.ts` | Default value in `DEFAULT_SETTINGS` + JSON Schema entry | | 3 | React component (or hook) | Read via `settings?.fieldName`; write via `updateSettings(...)` | | 4 | Component test file | Mock `useSettings`; assert `updateSettings` calls | --- ## Step 1 — Declare the field in the `Settings` interface File: `packages/ketcher-core/src/application/settings/types.ts` Add a `readonly` field at the bottom of the interface, grouped by domain: ```typescript // My feature readonly myNewSetting: string; // scalar readonly myNewArraySetting: readonly string[]; // array — always readonly ``` **Rules** - Mark every field `readonly` (settings objects are frozen by the service). - Use the narrowest type possible (e.g. `'px' | 'pt'` instead of `string`). - Arrays must be `readonly T[]`, not `T[]`. --- ## Step 2 — Register the default value and JSON Schema File: `packages/ketcher-core/src/application/settings/schema.ts` ### 2a — Default value in `DEFAULT_SETTINGS` Add inside the matching domain comment block (or create a new one): ```typescript // My feature myNewSetting: 'defaultValue', myNewArraySetting: [], ``` ### 2b — JSON Schema entry in `SCHEMA.properties` Add next to the default value block: ```typescript // My feature myNewSetting: { type: 'string' }, myNewArraySetting: { type: 'array', items: { type: 'string' } }, ``` Common schema patterns: | TypeScript type | JSON Schema | |-----------------|-------------| | `string` | `{ type: 'string' }` | | `number` | `{ type: 'number', minimum: 0, maximum: 100 }` | | `boolean` | `{ type: 'boolean' }` | | `'a' \| 'b' \| 'c'` | `{ enum: ['a', 'b', 'c'] }` | | `readonly string[]` | `{ type: 'array', items: { type: 'string' } }` | | `Record<string, number>` | `{ type: 'object' }` | --- ## Step 3 — Read and write in a React component ### 3a — Import the hook ```typescript import { useSettings } from 'src/hooks'; ``` ### 3b — Destructure inside the component ```typescript const { settings, updateSettings } = useSettings(); ``` ### 3c — Read the setting ```typescript // settings is null until the service initializes — always use optional chaining const value = settings?.myNewSetting ?? defaultFallback; // For arrays — spread to get a mutable copy if needed const items = [...(settings?.myNewArraySetting ?? [])]; ``` ### 3d — Write the setting `updateSettings` is async and performs a deep merge — pass only the fields that changed: ```typescript // In an event handler or callback updateSettings({ myNewSetting: newValue }); // For arrays, compute the new array first, then update state and service together const newItems = computeNewItems(currentItems, input); setLocalState(newItems); updateSettings({ myNewArraySetting: newItems }); ``` **Important** - Do **not** use `void` or `.catch(() => {})` — both trigger ESLint errors (`no-confusing-void-expression`, `no-empty-function`). Just call `updateSettings(...)` without awaiting unless you need the result. - Do **not** read from or write to `localStorage` directly. Always go through `updateSettings` / `settings`. - `settings` is `null` on the first render (async init). Always guard with `?? fallback`. - If you need a local UI state that mirrors the setting, initialize it to `[]` / `null` / `''` and populate it on first meaningful interaction (e.g., when a picker opens), reading from `settings` at that point. --- ## Step 4 — Test the component ### 4a — Mock `useSettings` at the top of the test file ```javascript import { useSettings } from 'src/hooks'; jest.mock('src/hooks', () => ({ useSettings: jest.fn(), })); ``` ### 4b — Set up mock state in `beforeEach` ```javascript let mockSettings; let mockUpdateSettings; beforeEach(() => { mockSettings = { myNewSetting: 'default', myNewArraySetting: [] }; mockUpdateSettings = jest.fn().mockResolvedValue({}); useSettings.mockReturnValue({ settings: mockSettings, updateSettings: mockUpdateSettings, }); }); ``` ### 4c — Test initial state (reading from service) Set the desired value on `mockSettings` **before** rendering: ```javascript it('should display value from settings', () => { mockSettings.myNewSetting = 'custom'; renderComponent(); // assert UI reflects 'custom' }); ``` ### 4d — Test persistence (writing to service) Assert that `updateSettings` was called with the expected partial: ```javascript it('should persist new value via SettingsService', async () => { renderComponent(); // perform user interaction that changes the setting expect(mockUpdateSettings).toHaveBeenCalledWith( expect.objectContaining({ myNewSetting: 'expectedValue' }), ); }); ``` ### 4e — Test service update between renders To simulate the service returning different data (e.g., cross-tab sync), update `mockSettings` and call `useSettings.mockReturnValue` again before triggering a re-render: ```javascript mockSettings.myNewArraySetting = ['newValue']; useSettings.mockReturnValue({ settings: mockSettings, updateSettings: mockUpdateSettings, }); // trigger re-render / re-open ``` --- ## Checklist - [ ] Field added to `Settings` interface (`types.ts`) as `readonly` - [ ] Default value added to `DEFAULT_SETTINGS` (`schema.ts`) - [ ] JSON Schema entry added to `SCHEMA.properties` (`schema.ts`) - [ ] Component reads via `settings?.fieldName ?? fallback` - [ ] Component writes via `updateSettings({ fieldName: newValue })` - [ ] No direct `localStorage` access in the component - [ ] Test mocks `useSettings` with `jest.mock` - [ ] Test verifies initial render from `mockSettings` - [ ] Test asserts `mockUpdateSettings` call arguments - [ ] TypeScript: no new compile errors in `types.ts` or `schema.ts` --- ## Key Files Reference | Purpose | Path | | ---------------------- | ----------------------------------------------------------------------- | | Settings interface | `packages/ketcher-core/src/application/settings/types.ts` | | Defaults + schema | `packages/ketcher-core/src/application/settings/schema.ts` | | Service implementation | `packages/ketcher-core/src/application/settings/SettingsService.ts` | | LocalStorage adapter | `packages/ketcher-core/src/application/settings/LocalStorageAdapter.ts` | | React hook | `packages/ketcher-react/src/hooks/useSettings.ts` | | Hooks barrel export | `packages/ketcher-react/src/hooks/index.ts` |
GitHub에서 보기