fix: address community review — system default, clearable schema flag, UTC fallback

- Add 'System default' clear option to SearchableSelect via clearLabel prop
- Add clearable flag to ConfigFieldSchema (schema-driven, not hardcoded)
- Add clearable: true to timezone schema override in web_server.py
- Fix CommandItem value for clear item: use clearLabel instead of '' so
  cmdk can match it during search
- Fix backend: or ['UTC'] fallback for hosts without tzdata where
  available_timezones() returns an empty set (not an exception)
- Add systemDefault i18n key (en, types, zh)
This commit is contained in:
David Metcalfe 2026-07-21 18:48:08 -07:00 committed by Teknium
parent b5b3ed6563
commit c8a4b18d34
7 changed files with 26 additions and 2 deletions

View file

@ -101,6 +101,7 @@ export function ConfigField({
if (selectOptions && schema.searchable) {
return row(
<SearchableSelect
clearLabel={schema.clearable ? c.systemDefault : undefined}
emptyMessage={c.noResults}
onChange={next => onChange(next)}
options={selectOptions.filter(o => o !== '')}

View file

@ -21,13 +21,17 @@ export function SearchableSelect({
onChange,
options,
placeholder = 'Search…',
emptyMessage = 'No results found.'
emptyMessage = 'No results found.',
clearLabel
}: {
value: string
onChange: (value: string) => void
options: string[]
placeholder?: string
emptyMessage?: string
/** When set, prepends a "clear" item that sets the value to ''.
* Matches the existing <Select> pattern of EMPTY_SELECT_VALUE + "(none)". */
clearLabel?: string
}) {
const [open, setOpen] = useState(false)
const triggerRef = useRef<HTMLButtonElement>(null)
@ -85,6 +89,18 @@ export function SearchableSelect({
<CommandList>
<CommandEmpty>{emptyMessage}</CommandEmpty>
<CommandGroup>
{clearLabel && (
<CommandItem
onSelect={() => handleSelect('')}
value={clearLabel}
>
<Codicon
className={cn('mr-2 size-4', value === '' ? 'opacity-100' : 'opacity-0')}
name="check"
/>
{clearLabel}
</CommandItem>
)}
{options.map(option => (
<CommandItem
key={option}

View file

@ -540,6 +540,7 @@ export const en: Translations = {
commaSeparated: 'comma-separated values',
searchPlaceholder: 'Search…',
noResults: 'No results found',
systemDefault: 'System default',
loading: 'Loading Hermes configuration...',
emptyTitle: 'Nothing to configure',
emptyDesc: 'This section has no adjustable settings.',

View file

@ -447,6 +447,7 @@ export interface Translations {
commaSeparated: string
searchPlaceholder: string
noResults: string
systemDefault: string
loading: string
emptyTitle: string
emptyDesc: string

View file

@ -750,6 +750,7 @@ export const zh: Translations = {
commaSeparated: '逗号分隔的值',
searchPlaceholder: '搜索…',
noResults: '未找到结果',
systemDefault: '系统默认',
loading: '正在加载 Hermes 配置...',
emptyTitle: '无可配置项',
emptyDesc: '此分区没有可调整的设置。',

View file

@ -5,6 +5,9 @@ export interface ConfigFieldSchema {
/** When true, renders a SearchableSelect (Popover + cmdk) instead of the
* closed `<Select>` dropdown. For large option lists like IANA timezones. */
searchable?: boolean
/** When true, a searchable select prepends a "clear" item that resets the
* value to ''. Matches the existing <Select> EMPTY_SELECT_VALUE pattern. */
clearable?: boolean
type?: 'boolean' | 'list' | 'number' | 'select' | 'string' | 'text'
}

View file

@ -803,7 +803,7 @@ def _timezone_options() -> List[str]:
"""Return sorted IANA timezone identifiers, cached at import time."""
try:
import zoneinfo
return sorted(zoneinfo.available_timezones())
return sorted(zoneinfo.available_timezones()) or ["UTC"]
except Exception: # pragma: no cover
return ["UTC"]
@ -814,6 +814,7 @@ _SCHEMA_OVERRIDES: Dict[str, Dict[str, Any]] = {
"description": "IANA timezone (e.g. America/New_York). Blank uses the system timezone.",
"options": _timezone_options(),
"searchable": True,
"clearable": True,
},
"memory.provider": {
"type": "select",