Skip to content

Commit e29c390

Browse files
author
Eric Wheeler
committed
docs: add enum settings implementation guide
Added detailed documentation for implementing enum/compound settings including: - Type definitions and schema setup - State management integration - UI component implementation - Translation requirements - Type safety considerations Signed-off-by: Eric Wheeler <[email protected]>
1 parent 7fab2f7 commit e29c390

File tree

1 file changed

+223
-0
lines changed

1 file changed

+223
-0
lines changed

cline_docs/settings.md

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,229 @@
171171
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
172172
```
173173

174+
For enum/compound data types, additional type-safety steps are needed:
175+
176+
1. **Define Central Types (schemas/index.ts)**
177+
178+
```typescript
179+
// Define the enum values
180+
export const displayModes = ["compact", "comfortable", "detailed"] as const
181+
182+
// Create the schema and type
183+
export const displayModeSchema = z.enum(displayModes)
184+
export type DisplayMode = z.infer<typeof displayModeSchema>
185+
186+
// Add to global settings schema
187+
export const globalSettingsSchema = z.object({
188+
displayMode: displayModeSchema.optional(),
189+
})
190+
```
191+
192+
2. **Type Definitions Chain**
193+
194+
```typescript
195+
// In ExtensionMessage.ts
196+
export type ExtensionState = Pick<GlobalSettings, "displayMode" | "otherSettings">
197+
198+
// In WebviewMessage.ts
199+
export type WebviewMessage = {
200+
type: "displayMode" | "otherTypes"
201+
text?: string
202+
}
203+
```
204+
205+
3. **State Management**
206+
207+
```typescript
208+
// In webviewMessageHandler.ts
209+
case "displayMode":
210+
const mode = (message.text ?? "compact") as DisplayMode
211+
await provider.contextProxy.updateGlobalState("displayMode", mode)
212+
await provider.postStateToWebview()
213+
break
214+
215+
// In ClineProvider.ts
216+
return {
217+
displayMode: (this.contextProxy.getGlobalState("displayMode") ?? "compact") as DisplayMode,
218+
}
219+
```
220+
221+
4. **UI State Context**
222+
223+
```typescript
224+
// In ExtensionStateContext.tsx
225+
interface ExtensionStateContextType extends ExtensionState {
226+
displayMode: DisplayMode
227+
setDisplayMode: (value: DisplayMode) => void
228+
}
229+
230+
const contextValue: ExtensionStateContextType = {
231+
displayMode: state.displayMode ?? "compact",
232+
setDisplayMode: (value: DisplayMode) => setState((prev) => ({ ...prev, displayMode: value })),
233+
}
234+
```
235+
236+
5. **UI Component**
237+
```typescript
238+
// In settings component
239+
<Select
240+
value={displayMode}
241+
onValueChange={(value: DisplayMode) => setCachedStateField("displayMode", value)}>
242+
<SelectContent>
243+
<SelectGroup>
244+
<SelectItem value="compact">{t("settings:display.mode.compact")}</SelectItem>
245+
<SelectItem value="comfortable">{t("settings:display.mode.comfortable")}</SelectItem>
246+
<SelectItem value="detailed">{t("settings:display.mode.detailed")}</SelectItem>
247+
</SelectGroup>
248+
</SelectContent>
249+
</Select>
250+
```
251+
252+
For enum/compound data types, additional type-safety steps are needed:
253+
254+
1. **Define Central Types (schemas/index.ts)**
255+
256+
```typescript
257+
// Define the enum values
258+
export const displayModes = ["compact", "comfortable", "detailed"] as const
259+
260+
// Create the schema and type
261+
export const displayModeSchema = z.enum(displayModes)
262+
export type DisplayMode = z.infer<typeof displayModeSchema>
263+
264+
// Add to global settings schema
265+
export const globalSettingsSchema = z.object({
266+
displayMode: displayModeSchema.optional(),
267+
})
268+
```
269+
270+
2. **Type Definitions Chain**
271+
272+
```typescript
273+
// In ExtensionMessage.ts
274+
export type ExtensionState = Pick<GlobalSettings, "displayMode" | "otherSettings">
275+
276+
// In WebviewMessage.ts
277+
export type WebviewMessage = {
278+
type: "displayMode" | "otherTypes"
279+
text?: string
280+
}
281+
```
282+
283+
3. **State Management & Type Casting**
284+
285+
```typescript
286+
// In webviewMessageHandler.ts
287+
case "displayMode":
288+
const mode = (message.text ?? "compact") as DisplayMode
289+
await provider.contextProxy.updateGlobalState("displayMode", mode)
290+
await provider.postStateToWebview()
291+
break
292+
293+
// In ClineProvider.ts
294+
return {
295+
displayMode: (this.contextProxy.getGlobalState("displayMode") ?? "compact") as DisplayMode,
296+
}
297+
298+
// In ExtensionStateContextProvider initial state
299+
const [state, setState] = useState<ExtensionState>({
300+
displayMode: ("compact" as DisplayMode),
301+
})
302+
303+
// In SettingsView cached state
304+
const [cachedState, setCachedState] = useState({
305+
displayMode: (extensionState.displayMode ?? "compact") as DisplayMode,
306+
})
307+
308+
// When sending messages from UI
309+
vscode.postMessage({
310+
type: "displayMode",
311+
text: (value as DisplayMode)
312+
})
313+
314+
// When handling state updates
315+
await updateGlobalState(
316+
"displayMode",
317+
(newValue ?? "compact") as DisplayMode
318+
)
319+
```
320+
321+
4. **UI State Context**
322+
323+
```typescript
324+
// In ExtensionStateContext.tsx
325+
interface ExtensionStateContextType extends ExtensionState {
326+
displayMode: DisplayMode
327+
setDisplayMode: (value: DisplayMode) => void
328+
}
329+
330+
const contextValue: ExtensionStateContextType = {
331+
displayMode: state.displayMode ?? "compact",
332+
setDisplayMode: (value: DisplayMode) => setState((prev) => ({ ...prev, displayMode: value })),
333+
}
334+
```
335+
336+
5. **UI Component**
337+
```typescript
338+
// In settings component
339+
<Select
340+
value={displayMode}
341+
onValueChange={(value: DisplayMode) => setCachedStateField("displayMode", value)}>
342+
<SelectContent>
343+
<SelectGroup>
344+
<SelectItem value="compact">{t("settings:display.mode.compact")}</SelectItem>
345+
<SelectItem value="comfortable">{t("settings:display.mode.comfortable")}</SelectItem>
346+
<SelectItem value="detailed">{t("settings:display.mode.detailed")}</SelectItem>
347+
</SelectGroup>
348+
</SelectContent>
349+
</Select>
350+
```
351+
352+
Additionally, there are several special considerations when implementing dropdown/enum settings:
353+
354+
1. **Auto-Generated Type Files**:
355+
356+
- Files like exports/types.ts and roo-code.d.ts are auto-generated
357+
- They'll list out the enum values explicitly rather than reference your type
358+
- Don't manually edit these files - modify the schema definition instead
359+
360+
2. **Translation Requirements**:
361+
362+
- Each enum value needs a dedicated translation key
363+
- Create entries in webview-ui/src/i18n/locales/en/settings.json:
364+
365+
```json
366+
"displayMode": {
367+
"label": "Display Mode",
368+
"description": "Controls how items are displayed in the UI",
369+
"compact": "Compact: Minimal spacing and information",
370+
"comfortable": "Comfortable: Balanced spacing and details",
371+
"detailed": "Detailed: Maximum information and spacing"
372+
}
373+
```
374+
375+
3. **Explicit UI Rendering**:
376+
377+
- Each possible enum value needs an explicit SelectItem entry
378+
- This makes UI maintenance and type safety connected
379+
- When adding new enum values, update both schema and UI
380+
381+
4. **Default Value Consistency**:
382+
383+
- Use the same default value in all locations:
384+
- Initial state in ExtensionStateContextProvider
385+
- Default value in ClineProvider.getStateToPostToWebview
386+
- Default value when handling messages
387+
- Default value when updating state
388+
- Inconsistent defaults cause unpredictable behavior
389+
390+
5. **Type Safety Chain**:
391+
- Message Passing: When passing messages between webview and extension, the type information is lost and values are treated as strings
392+
- State Initialization: Default values need explicit typing to match the enum type
393+
- Optional Handling: When dealing with optional values, the default must be cast to maintain type safety
394+
- UI Events: Event handlers receive string values that must be cast to the enum type
395+
- Global State: VSCode's storage APIs work with basic types and need casting when retrieving values
396+
174397
These steps ensure that:
175398

176399
- The setting's state is properly typed throughout the application

0 commit comments

Comments
 (0)