@focus8/settings-registry 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -34,8 +34,8 @@ export type LocaleCode = 'en' | 'nb';
34
34
  export type CalendarType = 'chronological' | 'time-based';
35
35
  export type ClockType = 'digital' | 'analog';
36
36
  export type TimePickerMode = 'dials' | 'keypad';
37
- export type AlarmTimeout = 1 | 2 | 3 | 5 | 10;
38
- export const ALARM_TIMEOUTS: AlarmTimeout[] = [1, 2, 3, 5, 10];
37
+ export type AlarmTimeout = 5 | 30 | 60 | 120 | 180 | 300 | 600;
38
+ export const ALARM_TIMEOUTS: AlarmTimeout[] = [5, 30, 60, 120, 180, 300, 600];
39
39
  export type CalendarViewMode =
40
40
  | 'day'
41
41
  | '3-days'
@@ -103,6 +103,7 @@ export type SettingUiType =
103
103
  | 'CUSTOM_DISPLAY_DENSITY'
104
104
  | 'CUSTOM_BRIGHTNESS'
105
105
  | 'CUSTOM_SPLIT_VIEW_CONFIG'
106
+ | 'CUSTOM_EVENT_CATEGORIES'
106
107
  | 'HIDDEN';
107
108
 
108
109
  export type SettingOption<T = unknown> = {
@@ -110,6 +111,8 @@ export type SettingOption<T = unknown> = {
110
111
  value: T;
111
112
  /** i18n message key for the option label */
112
113
  labelKey: string;
114
+ /** Optional values to pass to the i18n message formatter */
115
+ labelValues?: Record<string, unknown>;
113
116
  };
114
117
 
115
118
  export type SliderConfig = {
@@ -220,123 +223,149 @@ export function getCategoriesForCalendarType(
220
223
  }
221
224
 
222
225
  // ---------------------------------------------------------------------------
223
- // Settings groupsuser-facing groups that reorganize raw categories
226
+ // Settings sectionstop-level groupings (e.g. "Kalender", "Enhet")
224
227
  // ---------------------------------------------------------------------------
225
228
 
226
- /**
227
- * Settings group IDs matching the mobile app's settings menu structure.
228
- * These provide user-friendly groupings that may pull settings from multiple
229
- * raw registry categories.
230
- */
231
- export const SETTINGS_GROUP_IDS = [
232
- 'appearance',
233
- 'calendarType',
229
+ export const SETTINGS_SECTION_IDS = ['calendar', 'unit'] as const;
230
+ export type SettingsSectionId = (typeof SETTINGS_SECTION_IDS)[number];
231
+
232
+ export type SettingsSectionDef = {
233
+ id: SettingsSectionId;
234
+ labelKey: string;
235
+ };
236
+
237
+ export const SETTINGS_SECTIONS: readonly SettingsSectionDef[] = [
238
+ { id: 'calendar', labelKey: 'Calendar.LabelCalendar' },
239
+ { id: 'unit', labelKey: 'Settings.Unit' },
240
+ ];
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Settings menu items — ordered list of all items in the settings menu
244
+ // ---------------------------------------------------------------------------
245
+
246
+ export const SETTINGS_MENU_ITEM_IDS = [
247
+ // Calendar section
234
248
  'calendarView',
235
- 'activities',
236
- 'dateWeather',
237
- 'soundAlerts',
249
+ 'calendars',
250
+ 'eventCategories',
251
+ 'notification',
238
252
  'timer',
253
+ 'guests',
254
+ 'eventForm',
255
+ 'chronologicalEventForm',
256
+ 'weather',
257
+ 'gallery',
258
+ 'audioClips',
259
+ 'gestures',
260
+ 'calendarType',
261
+ 'sharing',
262
+ // Unit section
263
+ 'screen',
264
+ 'volume',
239
265
  'lockScreen',
240
- 'touch',
241
- 'chronological',
242
- 'device',
243
266
  'language',
267
+ 'wifi',
268
+ 'devices',
269
+ 'profiles',
270
+ 'tts',
271
+ 'updates',
272
+ 'about',
273
+ 'help',
274
+ 'dev',
275
+ // Account (rendered separately)
276
+ 'account',
277
+ 'activityLog',
244
278
  ] as const;
245
279
 
246
- export type SettingsGroupId = (typeof SETTINGS_GROUP_IDS)[number];
280
+ export type SettingsMenuItemId = (typeof SETTINGS_MENU_ITEM_IDS)[number];
247
281
 
248
- export type SettingsGroupDef = {
249
- /** Unique group identifier */
250
- id: SettingsGroupId;
251
- /** i18n message key for the group label */
282
+ export type SettingsMenuItemDef = {
283
+ id: SettingsMenuItemId;
252
284
  labelKey: string;
253
- /** Lucide icon name */
254
285
  icon: string;
286
+ section: SettingsSectionId | 'account';
287
+ /** Screen/page identifiers rendered for this menu item */
288
+ screens: string[];
255
289
  /**
256
- * Setting key matchers. A setting is included if its key:
257
- * - equals a matcher exactly, OR
258
- * - starts with a matcher that ends with '.' (prefix match)
290
+ * Setting key matchers for remote editing (web portal / profile editor).
291
+ * A setting is included if its key equals a matcher exactly, or starts
292
+ * with a matcher that ends with '.' (prefix match).
293
+ * Items without keys are navigation-only (e.g. gallery, guests).
259
294
  */
260
- keys: string[];
295
+ keys?: string[];
261
296
  /** Only show for this calendar type (undefined = always) */
262
297
  calendarType?: CalendarType;
263
298
  /** Only show for this app mode (undefined = always) */
264
299
  appMode?: 'ENROLLED';
300
+ /** If true, don't wrap content in a scroll container */
301
+ noScrollView?: boolean;
265
302
  };
266
303
 
267
304
  /**
268
- * Group definitions matching the mobile app's settings structure.
269
- *
270
- * i18n keys use the format `Settings.GroupLabel.<Id>` and must exist
271
- * in every consumer's i18n catalog.
305
+ * Ordered list of all settings menu items. The array order defines the
306
+ * display order within each section. Items without registry setting keys
307
+ * are navigation-only (e.g. gallery, guests).
272
308
  */
273
- export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
274
- {
275
- id: 'appearance',
276
- labelKey: 'Settings.Appearance',
277
- icon: 'Palette',
278
- keys: [
279
- 'appearance.theme',
280
- 'appearance.clockType',
281
- 'appearance.enableDayColors',
282
- ],
283
- },
284
- {
285
- id: 'calendarType',
286
- labelKey: 'Settings.CalendarType',
287
- icon: 'ArrowLeftRight',
288
- keys: ['calendarView.type'],
289
- },
309
+ export const SETTINGS_MENU_ITEMS: readonly SettingsMenuItemDef[] = [
310
+ // -- Calendar section --
290
311
  {
291
312
  id: 'calendarView',
292
- labelKey: 'Settings.CalendarView',
313
+ labelKey: 'Settings.Calendar',
293
314
  icon: 'Eye',
315
+ section: 'calendar',
316
+ screens: [
317
+ 'calendarDayView',
318
+ 'calendarWeekView',
319
+ 'chronologicalHeader',
320
+ 'chronologicalFooter',
321
+ 'chronologicalMenu',
322
+ 'dayColors',
323
+ 'dateTime',
324
+ ],
294
325
  keys: [
295
326
  'calendarView.showCalendarNames',
296
327
  'calendarView.splitView',
297
328
  'calendarView.dayViewZoom',
298
329
  'calendarView.weekViewZoom',
299
330
  'calendarView.calendarColumns',
331
+ 'appearance.enableDayColors',
300
332
  ],
301
333
  },
302
334
  {
303
- id: 'activities',
304
- labelKey: 'Settings.Activities',
305
- icon: 'CalendarPlus',
306
- keys: ['eventForm.', 'chronologicalEventForm.'],
335
+ id: 'calendars',
336
+ labelKey: 'Common.Calendars',
337
+ icon: 'Calendar',
338
+ section: 'calendar',
339
+ screens: ['calendars'],
307
340
  },
308
341
  {
309
- id: 'dateWeather',
310
- labelKey: 'Settings.DateWeather',
311
- icon: 'CloudSun',
312
- keys: [
313
- 'calendarView.showWeatherOnTimeline',
314
- 'calendarView.weatherLocation',
315
- 'calendarView.showWeatherOnEvents',
316
- ],
342
+ id: 'eventCategories',
343
+ labelKey: 'EventCategory.SettingsTitle',
344
+ icon: 'Tag',
345
+ section: 'calendar',
346
+ screens: ['eventCategories'],
347
+ calendarType: 'chronological',
317
348
  },
318
349
  {
319
- id: 'soundAlerts',
320
- labelKey: 'Settings.SoundAlerts',
321
- icon: 'Volume2',
350
+ id: 'notification',
351
+ labelKey: 'Settings.Notification',
352
+ icon: 'Bell',
353
+ section: 'calendar',
354
+ screens: ['notification'],
322
355
  keys: [
323
- 'sound.reminderVolume',
324
- 'sound.mediaVolume',
325
356
  'sound.reminderAlarmSound',
326
- 'sound.startAlarmSound',
327
- 'sound.endAlarmSound',
328
357
  'sound.reminderAlarmTimeout',
358
+ 'sound.reminderVolume',
329
359
  'sound.allowCustomReminderSounds',
330
- 'notification.',
331
- 'sound.ttsEnabled',
332
- 'sound.ttsRate',
333
- 'sound.alarmSound',
334
360
  ],
335
361
  },
336
362
  {
337
363
  id: 'timer',
338
364
  labelKey: 'Settings.TimerTitle',
339
365
  icon: 'Timer',
366
+ section: 'calendar',
367
+ screens: ['timerFeatures', 'timer'],
368
+ appMode: 'ENROLLED',
340
369
  keys: [
341
370
  'chronological.timer.',
342
371
  'timer.',
@@ -344,32 +373,140 @@ export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
344
373
  'sound.timerAlarmTimeout',
345
374
  'sound.timerVolume',
346
375
  ],
376
+ },
377
+ {
378
+ id: 'guests',
379
+ labelKey: 'Settings.GuestUsers',
380
+ icon: 'Users',
381
+ section: 'calendar',
382
+ screens: ['guests'],
383
+ },
384
+ {
385
+ id: 'eventForm',
386
+ labelKey: 'SettingsEventForm.Title',
387
+ icon: 'ClipboardList',
388
+ section: 'calendar',
389
+ screens: ['eventForm'],
390
+ calendarType: 'time-based',
391
+ keys: ['eventForm.'],
392
+ },
393
+ {
394
+ id: 'chronologicalEventForm',
395
+ labelKey: 'SettingsChronologicalEventForm.Title',
396
+ icon: 'Plus',
397
+ section: 'calendar',
398
+ screens: ['chronologicalEventForm'],
399
+ calendarType: 'chronological',
400
+ keys: ['chronologicalEventForm.'],
401
+ },
402
+ {
403
+ id: 'weather',
404
+ labelKey: 'Settings.WeatherTitle',
405
+ icon: 'CloudSun',
406
+ section: 'calendar',
407
+ screens: ['weather'],
408
+ calendarType: 'time-based',
409
+ keys: [
410
+ 'calendarView.showWeatherOnTimeline',
411
+ 'calendarView.weatherLocation',
412
+ 'calendarView.showWeatherOnEvents',
413
+ ],
414
+ },
415
+ {
416
+ id: 'gallery',
417
+ labelKey: 'Gallery.Title',
418
+ icon: 'Images',
419
+ section: 'calendar',
420
+ screens: ['gallery'],
421
+ noScrollView: true,
422
+ },
423
+ {
424
+ id: 'audioClips',
425
+ labelKey: 'AudioClips.Title',
426
+ icon: 'AudioLines',
427
+ section: 'calendar',
428
+ screens: ['audioClips'],
429
+ noScrollView: true,
430
+ },
431
+ {
432
+ id: 'gestures',
433
+ labelKey: 'Settings.FunctionsTitle',
434
+ icon: 'Hand',
435
+ section: 'calendar',
436
+ screens: ['gestures'],
437
+ keys: ['touch.'],
438
+ },
439
+ {
440
+ id: 'calendarType',
441
+ labelKey: 'Settings.Mode',
442
+ icon: 'SlidersHorizontal',
443
+ section: 'calendar',
444
+ screens: ['calendarType'],
445
+ keys: ['calendarView.type'],
446
+ },
447
+ {
448
+ id: 'sharing',
449
+ labelKey: 'Settings.Sharing',
450
+ icon: 'Share2',
451
+ section: 'calendar',
452
+ screens: ['icalSubscriptions'],
453
+ },
454
+ // -- Unit section --
455
+ {
456
+ id: 'screen',
457
+ labelKey: 'Settings.Screen',
458
+ icon: 'LaptopMinimal',
459
+ section: 'unit',
460
+ screens: ['displayDensity', 'darkMode'],
461
+ keys: ['appearance.theme', 'appearance.clockType'],
462
+ },
463
+ {
464
+ id: 'volume',
465
+ labelKey: 'Common.Volume',
466
+ icon: 'Volume2',
467
+ section: 'unit',
468
+ screens: ['volume'],
347
469
  appMode: 'ENROLLED',
470
+ keys: [
471
+ 'sound.mediaVolume',
472
+ 'sound.startAlarmSound',
473
+ 'sound.endAlarmSound',
474
+ 'sound.ttsEnabled',
475
+ 'sound.ttsRate',
476
+ 'sound.alarmSound',
477
+ ],
348
478
  },
349
479
  {
350
480
  id: 'lockScreen',
351
481
  labelKey: 'Settings.LockScreen',
352
482
  icon: 'Lock',
353
- keys: ['lockScreen.'],
483
+ section: 'unit',
484
+ screens: ['inactivity', 'lockScreen'],
354
485
  appMode: 'ENROLLED',
486
+ keys: ['lockScreen.'],
355
487
  },
356
488
  {
357
- id: 'touch',
358
- labelKey: 'Settings.FunctionsTitle',
359
- icon: 'Hand',
360
- keys: ['touch.'],
489
+ id: 'language',
490
+ labelKey: 'Settings.Language',
491
+ icon: 'Globe',
492
+ section: 'unit',
493
+ screens: ['language'],
494
+ keys: ['language.'],
361
495
  },
362
496
  {
363
- id: 'chronological',
364
- labelKey: 'Settings.Chronological',
365
- icon: 'List',
366
- keys: ['chronological.'],
367
- calendarType: 'chronological',
497
+ id: 'wifi',
498
+ labelKey: 'Settings.WifiTitle',
499
+ icon: 'Wifi',
500
+ section: 'unit',
501
+ screens: ['wifi'],
502
+ appMode: 'ENROLLED',
368
503
  },
369
504
  {
370
- id: 'device',
505
+ id: 'devices',
371
506
  labelKey: 'SettingsDevices.Title',
372
507
  icon: 'Smartphone',
508
+ section: 'unit',
509
+ screens: ['devices'],
373
510
  keys: [
374
511
  'device.',
375
512
  'calendarView.autoReturnToTodayEnabled',
@@ -377,12 +514,72 @@ export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
377
514
  ],
378
515
  },
379
516
  {
380
- id: 'language',
381
- labelKey: 'Settings.Language',
382
- icon: 'Globe',
383
- keys: ['language.'],
517
+ id: 'profiles',
518
+ labelKey: 'SettingsProfiles.Title',
519
+ icon: 'BookCopy',
520
+ section: 'unit',
521
+ screens: ['profiles'],
384
522
  },
385
- ] as const;
523
+ {
524
+ id: 'tts',
525
+ labelKey: 'Settings.TtsTitle',
526
+ icon: 'Speech',
527
+ section: 'unit',
528
+ screens: ['tts'],
529
+ },
530
+ {
531
+ id: 'updates',
532
+ labelKey: 'Settings.UpdatesTitle',
533
+ icon: 'Download',
534
+ section: 'unit',
535
+ screens: ['updates'],
536
+ },
537
+ {
538
+ id: 'about',
539
+ labelKey: 'Settings.About',
540
+ icon: 'Info',
541
+ section: 'unit',
542
+ screens: ['restoreDefaults', 'recycleAppData', 'license', 'version'],
543
+ },
544
+ {
545
+ id: 'help',
546
+ labelKey: 'Settings.HelpTitle',
547
+ icon: 'CircleQuestionMark',
548
+ section: 'unit',
549
+ screens: ['help'],
550
+ },
551
+ {
552
+ id: 'dev',
553
+ labelKey: 'Settings.Dev',
554
+ icon: 'FolderCode',
555
+ section: 'unit',
556
+ screens: [
557
+ 'devDeviceInfo',
558
+ 'devComponents',
559
+ 'devStores',
560
+ 'devActions',
561
+ 'devAuth',
562
+ 'devNavigation',
563
+ 'devNetwork',
564
+ 'devAndroidSettings',
565
+ ],
566
+ },
567
+ // -- Account (rendered separately) --
568
+ {
569
+ id: 'account',
570
+ labelKey: 'Settings.Profile',
571
+ icon: 'User',
572
+ section: 'account',
573
+ screens: ['account', 'mfa'],
574
+ },
575
+ {
576
+ id: 'activityLog',
577
+ labelKey: 'SettingsActivityLog.Title',
578
+ icon: 'ScrollText',
579
+ section: 'account',
580
+ screens: ['activityLog'],
581
+ },
582
+ ];
386
583
 
387
584
  /**
388
585
  * Settings excluded from remote editing (web portal / profile management).
@@ -641,19 +838,16 @@ export const SETTINGS_LABELS: Readonly<Record<string, SettingLabelDef>> = {
641
838
  'eventForm.checklist': { labelKey: 'EventChecklist.DefaultName' },
642
839
  'eventForm.images': { labelKey: 'EventImageGallery.SectionTitle' },
643
840
  'eventForm.audioClips': { labelKey: 'EventAudioGallery.SectionTitle' },
644
- 'eventForm.notificationReceivers': { labelKey: 'EventForm.NotificationReceivers' },
841
+ 'eventForm.notificationReceivers': {
842
+ labelKey: 'EventForm.NotificationReceivers',
843
+ },
645
844
  'eventForm.visibility': { labelKey: 'EventVisibility.Title' },
646
- };
647
845
 
648
- /**
649
- * Check whether a setting key matches a group's key matchers.
650
- */
651
- function matchesGroup(key: string, group: SettingsGroupDef): boolean {
652
- return group.keys.some(
653
- (matcher) =>
654
- key === matcher || (matcher.endsWith('.') && key.startsWith(matcher)),
655
- );
656
- }
846
+ // ── Chronological event form ────────────────────────────────────────────
847
+ 'chronologicalEventForm.field.category': {
848
+ labelKey: 'ChronologicalEventForm.CategoryField',
849
+ },
850
+ };
657
851
 
658
852
  /** Return the index of the first matching key pattern in the group, for sorting. */
659
853
  function groupKeyIndex(key: string, keys: readonly string[]): number {
@@ -683,78 +877,72 @@ function registryKeyOrder(): Map<string, number> {
683
877
  }
684
878
 
685
879
  /**
686
- * Group parsed setting entries into user-facing groups matching the mobile
687
- * app's settings menu structure.
688
- *
689
- * Takes a flat list of parsed settings (from `parseSettingsSnapshot`) and
690
- * buckets them into the standard groups, filtering by calendar type and
691
- * excluded keys.
880
+ * Group parsed setting entries using the app's menu structure (SETTINGS_MENU_ITEMS).
881
+ * Only includes menu items that have `keys` defined (editable settings).
882
+ * This ensures the web editor has the exact same menu items, order, sections,
883
+ * labels, and icons as the mobile app.
692
884
  */
693
- export function groupSettingsForDevice(
885
+ export function groupSettingsForWeb(
694
886
  allSettings: ParsedSettingEntry[],
695
887
  calendarType: CalendarType,
696
888
  appMode?: 'ENROLLED',
697
- ): { id: SettingsGroupId; labelKey: string; icon: string; settings: ParsedSettingEntry[] }[] {
889
+ ): {
890
+ id: SettingsMenuItemId;
891
+ labelKey: string;
892
+ icon: string;
893
+ section: SettingsSectionId | 'account';
894
+ settings: ParsedSettingEntry[];
895
+ }[] {
698
896
  // Filter excluded and hidden settings
699
897
  const settings = allSettings.filter(
700
898
  (s) => !EXCLUDED_DEVICE_SETTINGS.has(s.key) && s.uiType !== 'HIDDEN',
701
899
  );
702
900
 
703
- // Calendar type filtering for event form keys
704
901
  const isChronological = calendarType === 'chronological';
705
902
  const filteredSettings = settings.filter((s) => {
706
- if (isChronological && s.key.startsWith('eventForm.')) {
707
- return false;
708
- }
709
- if (!isChronological && s.key.startsWith('chronologicalEventForm.')) {
903
+ if (isChronological && s.key.startsWith('eventForm.')) return false;
904
+ if (!isChronological && s.key.startsWith('chronologicalEventForm.'))
710
905
  return false;
711
- }
712
- // Per-setting calendar type restriction
713
- if (s.calendarType && s.calendarType !== calendarType) {
714
- return false;
715
- }
716
- // Per-setting app mode restriction
717
- if (s.appMode && s.appMode !== appMode) {
718
- return false;
719
- }
906
+ if (s.calendarType && s.calendarType !== calendarType) return false;
907
+ if (s.appMode && s.appMode !== appMode) return false;
720
908
  return true;
721
909
  });
722
910
 
723
911
  const claimed = new Set<string>();
724
912
  const result: {
725
- id: SettingsGroupId;
913
+ id: SettingsMenuItemId;
726
914
  labelKey: string;
727
915
  icon: string;
916
+ section: SettingsSectionId | 'account';
728
917
  settings: ParsedSettingEntry[];
729
918
  }[] = [];
730
919
 
731
- for (const group of SETTINGS_GROUPS) {
732
- // Skip groups not relevant for current calendar type
733
- if (group.calendarType && group.calendarType !== calendarType) {
734
- continue;
735
- }
920
+ for (const menuItem of SETTINGS_MENU_ITEMS) {
921
+ // Skip items without keys (navigation-only like gallery, guests, etc.)
922
+ if (!menuItem.keys || menuItem.keys.length === 0) continue;
736
923
 
737
- // Skip groups not relevant for current app mode
738
- if (group.appMode && group.appMode !== appMode) {
924
+ // Skip items not relevant for current calendar type
925
+ if (menuItem.calendarType && menuItem.calendarType !== calendarType)
739
926
  continue;
740
- }
741
927
 
742
- const matched = filteredSettings.filter(
743
- (s) => !claimed.has(s.key) && matchesGroup(s.key, group),
744
- );
745
- if (matched.length === 0) {
746
- continue;
747
- }
928
+ // Skip items not relevant for current app mode
929
+ if (menuItem.appMode && menuItem.appMode !== appMode) continue;
930
+
931
+ const matched = filteredSettings.filter((s) => {
932
+ if (claimed.has(s.key)) return false;
933
+ return menuItem.keys!.some((k) =>
934
+ k.endsWith('.') ? s.key.startsWith(k) : s.key === k,
935
+ );
936
+ });
937
+
938
+ if (matched.length === 0) continue;
748
939
 
749
- // Sort matched settings by the order defined in group.keys,
750
- // with registry declaration order as tiebreaker for prefix matches
940
+ // Sort by key order within the menu item's keys array
751
941
  const order = registryKeyOrder();
752
942
  matched.sort((a, b) => {
753
- const aIdx = groupKeyIndex(a.key, group.keys);
754
- const bIdx = groupKeyIndex(b.key, group.keys);
755
- if (aIdx !== bIdx) {
756
- return aIdx - bIdx;
757
- }
943
+ const aIdx = groupKeyIndex(a.key, menuItem.keys!);
944
+ const bIdx = groupKeyIndex(b.key, menuItem.keys!);
945
+ if (aIdx !== bIdx) return aIdx - bIdx;
758
946
  return (order.get(a.key) ?? Infinity) - (order.get(b.key) ?? Infinity);
759
947
  });
760
948
 
@@ -763,9 +951,10 @@ export function groupSettingsForDevice(
763
951
  }
764
952
 
765
953
  result.push({
766
- id: group.id,
767
- labelKey: group.labelKey,
768
- icon: group.icon,
954
+ id: menuItem.id,
955
+ labelKey: menuItem.labelKey,
956
+ icon: menuItem.icon,
957
+ section: menuItem.section,
769
958
  settings: matched,
770
959
  });
771
960
  }
@@ -826,31 +1015,34 @@ export const DEFAULT_REGISTRY_CONFIG: RegistryConfig = {
826
1015
  // ---------------------------------------------------------------------------
827
1016
 
828
1017
  const ALARM_SOUND_OPTIONS: readonly SettingOption<AlarmSound>[] = [
829
- { value: 'none', labelKey: 'Settings.Option.AlarmSound.None' },
830
- { value: 'alarm1', labelKey: 'Settings.Option.AlarmSound.Alarm1' },
831
- { value: 'alarm2', labelKey: 'Settings.Option.AlarmSound.Alarm2' },
832
- { value: 'alarm3', labelKey: 'Settings.Option.AlarmSound.Alarm3' },
833
- { value: 'alarm4', labelKey: 'Settings.Option.AlarmSound.Alarm4' },
834
- { value: 'alarm5', labelKey: 'Settings.Option.AlarmSound.Alarm5' },
835
- { value: 'alarm6', labelKey: 'Settings.Option.AlarmSound.Alarm6' },
836
- { value: 'alarm7', labelKey: 'Settings.Option.AlarmSound.Alarm7' },
837
- { value: 'alarm8', labelKey: 'Settings.Option.AlarmSound.Alarm8' },
838
- { value: 'alarm9', labelKey: 'Settings.Option.AlarmSound.Alarm9' },
1018
+ { value: 'none', labelKey: 'AlarmAsset.none' },
1019
+ { value: 'alarm1', labelKey: 'AlarmAsset.alarm1' },
1020
+ { value: 'alarm2', labelKey: 'AlarmAsset.alarm2' },
1021
+ { value: 'alarm3', labelKey: 'AlarmAsset.alarm3' },
1022
+ { value: 'alarm4', labelKey: 'AlarmAsset.alarm4' },
1023
+ { value: 'alarm5', labelKey: 'AlarmAsset.alarm5' },
1024
+ { value: 'alarm6', labelKey: 'AlarmAsset.alarm6' },
1025
+ { value: 'alarm7', labelKey: 'AlarmAsset.alarm7' },
1026
+ { value: 'alarm8', labelKey: 'AlarmAsset.alarm8' },
1027
+ { value: 'alarm9', labelKey: 'AlarmAsset.alarm9' },
839
1028
  ];
840
1029
 
841
1030
  const ALARM_TIMEOUT_OPTIONS: readonly SettingOption<AlarmTimeout>[] = [
842
- { value: 1, labelKey: 'Settings.Option.AlarmTimeout.1min' },
843
- { value: 2, labelKey: 'Settings.Option.AlarmTimeout.2min' },
844
- { value: 3, labelKey: 'Settings.Option.AlarmTimeout.3min' },
845
- { value: 5, labelKey: 'Settings.Option.AlarmTimeout.5min' },
846
- { value: 10, labelKey: 'Settings.Option.AlarmTimeout.10min' },
1031
+ { value: 5, labelKey: 'Duration.CountSeconds', labelValues: { count: 5 } },
1032
+ { value: 30, labelKey: 'Duration.CountSeconds', labelValues: { count: 30 } },
1033
+ { value: 60, labelKey: 'Duration.CountMinutes', labelValues: { count: 1 } },
1034
+ { value: 120, labelKey: 'Duration.CountMinutes', labelValues: { count: 2 } },
1035
+ { value: 180, labelKey: 'Duration.CountMinutes', labelValues: { count: 3 } },
1036
+ { value: 300, labelKey: 'Duration.CountMinutes', labelValues: { count: 5 } },
1037
+ { value: 600, labelKey: 'Duration.CountMinutes', labelValues: { count: 10 } },
847
1038
  ];
848
1039
 
849
- const DAY_VIEW_ZOOM_OPTIONS: readonly SettingOption<CalendarDayViewCellZoom>[] = [
850
- { value: 15, labelKey: 'Settings.Option.DayViewZoom.15min' },
851
- { value: 30, labelKey: 'Settings.Option.DayViewZoom.30min' },
852
- { value: 60, labelKey: 'Settings.Option.DayViewZoom.1hour' },
853
- ];
1040
+ const DAY_VIEW_ZOOM_OPTIONS: readonly SettingOption<CalendarDayViewCellZoom>[] =
1041
+ [
1042
+ { value: 15, labelKey: 'Settings.Option.DayViewZoom.15min' },
1043
+ { value: 30, labelKey: 'Settings.Option.DayViewZoom.30min' },
1044
+ { value: 60, labelKey: 'Settings.Option.DayViewZoom.1hour' },
1045
+ ];
854
1046
 
855
1047
  const TIME_OF_DAY_SLIDER: SliderConfig = { min: 0, max: 23, step: 1 };
856
1048
 
@@ -908,11 +1100,16 @@ function buildEntries(config: RegistryConfig) {
908
1100
  config.defaultTheme,
909
1101
  'CUSTOM_THEME_PICKER',
910
1102
  {
911
- options: [
912
- { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
913
- { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
914
- { value: 'system', labelKey: 'Settings.Option.Theme.System' },
915
- ],
1103
+ options: config.isEnrolled
1104
+ ? [
1105
+ { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
1106
+ { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
1107
+ ]
1108
+ : [
1109
+ { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
1110
+ { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
1111
+ { value: 'system', labelKey: 'Settings.Option.Theme.System' },
1112
+ ],
916
1113
  },
917
1114
  ),
918
1115
  'appearance.clockType': def<ClockType>(
@@ -1056,36 +1253,70 @@ function buildEntries(config: RegistryConfig) {
1056
1253
  // ═══════════════════════════════════════════════════════════════════════
1057
1254
  // Event form field visibility (time-based)
1058
1255
  // ═══════════════════════════════════════════════════════════════════════
1059
- 'eventForm.recurrence': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1256
+ 'eventForm.recurrence': def<boolean>(
1257
+ 'eventForm',
1258
+ 'boolean',
1259
+ true,
1260
+ 'TOGGLE',
1261
+ ),
1060
1262
  'eventForm.location': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1061
- 'eventForm.travelTime': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1263
+ 'eventForm.travelTime': def<boolean>(
1264
+ 'eventForm',
1265
+ 'boolean',
1266
+ false,
1267
+ 'TOGGLE',
1268
+ ),
1062
1269
  'eventForm.reminders': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1063
- 'eventForm.emailReminders': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1064
- 'eventForm.description': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1270
+ 'eventForm.emailReminders': def<boolean>(
1271
+ 'eventForm',
1272
+ 'boolean',
1273
+ false,
1274
+ 'TOGGLE',
1275
+ ),
1276
+ 'eventForm.description': def<boolean>(
1277
+ 'eventForm',
1278
+ 'boolean',
1279
+ true,
1280
+ 'TOGGLE',
1281
+ ),
1065
1282
  'eventForm.checklist': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1066
1283
  'eventForm.images': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1067
- 'eventForm.audioClips': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1284
+ 'eventForm.audioClips': def<boolean>(
1285
+ 'eventForm',
1286
+ 'boolean',
1287
+ false,
1288
+ 'TOGGLE',
1289
+ ),
1068
1290
  'eventForm.notificationReceivers': def<boolean>(
1069
1291
  'eventForm',
1070
1292
  'boolean',
1071
1293
  true,
1072
1294
  'TOGGLE',
1073
1295
  ),
1074
- 'eventForm.visibility': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1296
+ 'eventForm.visibility': def<boolean>(
1297
+ 'eventForm',
1298
+ 'boolean',
1299
+ false,
1300
+ 'TOGGLE',
1301
+ ),
1075
1302
 
1076
1303
  // ═══════════════════════════════════════════════════════════════════════
1077
1304
  // Sound & alerts
1078
1305
  // ═══════════════════════════════════════════════════════════════════════
1079
1306
  'sound.timerVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER'),
1080
- 'sound.reminderVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
1081
- 'sound.mediaVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
1082
- 'sound.alarmSound': def<AlarmSound>(
1307
+ 'sound.reminderVolume': def<number>(
1083
1308
  'sound',
1084
- 'string',
1085
- 'alarm1',
1086
- 'SELECT',
1087
- { options: ALARM_SOUND_OPTIONS },
1309
+ 'number',
1310
+ 0.5,
1311
+ 'VOLUME_SLIDER',
1312
+ { appMode: 'ENROLLED' },
1088
1313
  ),
1314
+ 'sound.mediaVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', {
1315
+ appMode: 'ENROLLED',
1316
+ }),
1317
+ 'sound.alarmSound': def<AlarmSound>('sound', 'string', 'alarm1', 'SELECT', {
1318
+ options: ALARM_SOUND_OPTIONS,
1319
+ }),
1089
1320
  'sound.reminderAlarmSound': def<AlarmSound>(
1090
1321
  'sound',
1091
1322
  'string',
@@ -1096,16 +1327,16 @@ function buildEntries(config: RegistryConfig) {
1096
1327
  'sound.startAlarmSound': def<AlarmSound>(
1097
1328
  'sound',
1098
1329
  'string',
1099
- 'none',
1330
+ 'alarm1',
1100
1331
  'SELECT',
1101
- { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' },
1332
+ { options: ALARM_SOUND_OPTIONS },
1102
1333
  ),
1103
1334
  'sound.endAlarmSound': def<AlarmSound>(
1104
1335
  'sound',
1105
1336
  'string',
1106
- 'none',
1337
+ 'alarm1',
1107
1338
  'SELECT',
1108
- { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' },
1339
+ { options: ALARM_SOUND_OPTIONS },
1109
1340
  ),
1110
1341
  'sound.timerAlarmSound': def<AlarmSound>(
1111
1342
  'sound',
@@ -1117,14 +1348,14 @@ function buildEntries(config: RegistryConfig) {
1117
1348
  'sound.timerAlarmTimeout': def<AlarmTimeout>(
1118
1349
  'sound',
1119
1350
  'number',
1120
- 3,
1351
+ 180,
1121
1352
  'SELECT',
1122
1353
  { options: ALARM_TIMEOUT_OPTIONS },
1123
1354
  ),
1124
1355
  'sound.reminderAlarmTimeout': def<AlarmTimeout>(
1125
1356
  'sound',
1126
1357
  'number',
1127
- 3,
1358
+ 180,
1128
1359
  'SELECT',
1129
1360
  { options: ALARM_TIMEOUT_OPTIONS },
1130
1361
  ),
@@ -1141,21 +1372,22 @@ function buildEntries(config: RegistryConfig) {
1141
1372
  'TOGGLE',
1142
1373
  { appMode: 'ENROLLED' },
1143
1374
  ),
1144
- 'sound.ttsRate': def<number>(
1145
- 'sound',
1146
- 'number',
1147
- 1.0,
1148
- 'SLIDER',
1149
- { sliderConfig: { min: 0.5, max: 2, step: 0.1 }, appMode: 'ENROLLED' },
1150
- ),
1375
+ 'sound.ttsRate': def<number>('sound', 'number', 1.0, 'SLIDER', {
1376
+ sliderConfig: { min: 0.5, max: 2, step: 0.1 },
1377
+ appMode: 'ENROLLED',
1378
+ }),
1151
1379
 
1152
1380
  // ═══════════════════════════════════════════════════════════════════════
1153
1381
  // Timer
1154
1382
  // ═══════════════════════════════════════════════════════════════════════
1155
1383
  'timer.showTimeRemaining': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
1156
- 'timer.showEndTime': def<boolean>('timer', 'boolean', true, 'TOGGLE', { calendarType: 'time-based' }),
1384
+ 'timer.showEndTime': def<boolean>('timer', 'boolean', true, 'TOGGLE', {
1385
+ calendarType: 'time-based',
1386
+ }),
1157
1387
  'timer.showRestartButton': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
1158
- 'timer.showPauseButton': def<boolean>('timer', 'boolean', true, 'TOGGLE', { calendarType: 'time-based' }),
1388
+ 'timer.showPauseButton': def<boolean>('timer', 'boolean', true, 'TOGGLE', {
1389
+ calendarType: 'time-based',
1390
+ }),
1159
1391
 
1160
1392
  // ═══════════════════════════════════════════════════════════════════════
1161
1393
  // Lock screen
@@ -1264,7 +1496,12 @@ function buildEntries(config: RegistryConfig) {
1264
1496
  // ═══════════════════════════════════════════════════════════════════════
1265
1497
  // Touch / gestures
1266
1498
  // ═══════════════════════════════════════════════════════════════════════
1267
- 'touch.enableTapToCreate': def<boolean>('touch', 'boolean', false, 'TOGGLE'),
1499
+ 'touch.enableTapToCreate': def<boolean>(
1500
+ 'touch',
1501
+ 'boolean',
1502
+ false,
1503
+ 'TOGGLE',
1504
+ ),
1268
1505
  'touch.enableDragDrop': def<boolean>('touch', 'boolean', false, 'TOGGLE'),
1269
1506
 
1270
1507
  // ═══════════════════════════════════════════════════════════════════════
@@ -1533,6 +1770,12 @@ function buildEntries(config: RegistryConfig) {
1533
1770
  // Chronological event form
1534
1771
  // ═══════════════════════════════════════════════════════════════════════
1535
1772
  // Fixed fields
1773
+ 'chronologicalEventForm.fixedField.category': def<boolean>(
1774
+ 'chronologicalEventForm',
1775
+ 'boolean',
1776
+ true,
1777
+ 'TOGGLE',
1778
+ ),
1536
1779
  'chronologicalEventForm.fixedField.allDay': def<boolean>(
1537
1780
  'chronologicalEventForm',
1538
1781
  'boolean',
@@ -1545,6 +1788,12 @@ function buildEntries(config: RegistryConfig) {
1545
1788
  true,
1546
1789
  'TOGGLE',
1547
1790
  ),
1791
+ 'chronologicalEventForm.fixedField.alarm': def<boolean>(
1792
+ 'chronologicalEventForm',
1793
+ 'boolean',
1794
+ true,
1795
+ 'TOGGLE',
1796
+ ),
1548
1797
  'chronologicalEventForm.fixedField.visibility': def<boolean>(
1549
1798
  'chronologicalEventForm',
1550
1799
  'boolean',
@@ -1594,6 +1843,12 @@ function buildEntries(config: RegistryConfig) {
1594
1843
  true,
1595
1844
  'TOGGLE',
1596
1845
  ),
1846
+ 'chronologicalEventForm.field.category': def<boolean>(
1847
+ 'chronologicalEventForm',
1848
+ 'boolean',
1849
+ true,
1850
+ 'TOGGLE',
1851
+ ),
1597
1852
  // Suggest end time
1598
1853
  'chronologicalEventForm.suggestEndTime.enabled': def<boolean>(
1599
1854
  'chronologicalEventForm',
@@ -1640,6 +1895,19 @@ function buildEntries(config: RegistryConfig) {
1640
1895
  ],
1641
1896
  },
1642
1897
  ),
1898
+ // Default alarm toggles
1899
+ 'chronologicalEventForm.defaultAlarm.atStart': def<boolean>(
1900
+ 'chronologicalEventForm',
1901
+ 'boolean',
1902
+ true,
1903
+ 'TOGGLE',
1904
+ ),
1905
+ 'chronologicalEventForm.defaultAlarm.atEnd': def<boolean>(
1906
+ 'chronologicalEventForm',
1907
+ 'boolean',
1908
+ false,
1909
+ 'TOGGLE',
1910
+ ),
1643
1911
  // Reminder presets
1644
1912
  'chronologicalEventForm.reminderPresets.timed': def<number[]>(
1645
1913
  'chronologicalEventForm',
@@ -1907,9 +2175,8 @@ export function parseSettingsSnapshot(
1907
2175
  }
1908
2176
 
1909
2177
  const labelDef = SETTINGS_LABELS[key];
1910
- const entryDef = key in registry.entries
1911
- ? registry.entries[key as SettingKey]
1912
- : undefined;
2178
+ const entryDef =
2179
+ key in registry.entries ? registry.entries[key as SettingKey] : undefined;
1913
2180
  groups.get(category)!.push({
1914
2181
  key,
1915
2182
  name,
@@ -1918,7 +2185,9 @@ export function parseSettingsSnapshot(
1918
2185
  descriptionKey: labelDef?.descriptionKey,
1919
2186
  value,
1920
2187
  uiType: entryDef?.uiType ?? 'TEXT_INPUT',
1921
- ...(entryDef?.options && { options: entryDef.options as readonly SettingOption[] }),
2188
+ ...(entryDef?.options && {
2189
+ options: entryDef.options as readonly SettingOption[],
2190
+ }),
1922
2191
  ...(entryDef?.sliderConfig && { sliderConfig: entryDef.sliderConfig }),
1923
2192
  ...(entryDef?.appMode && { appMode: entryDef.appMode }),
1924
2193
  ...(entryDef?.calendarType && { calendarType: entryDef.calendarType }),