@focus8/settings-registry 0.8.3 → 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,76 +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 = [
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 = [
232
247
  // Calendar section
233
248
  'calendarView',
249
+ 'calendars',
250
+ 'eventCategories',
251
+ 'notification',
234
252
  'timer',
235
- 'activities',
236
- 'dateWeather',
237
- 'touch',
253
+ 'guests',
254
+ 'eventForm',
255
+ 'chronologicalEventForm',
256
+ 'weather',
257
+ 'gallery',
258
+ 'audioClips',
259
+ 'gestures',
238
260
  'calendarType',
239
- 'chronological',
261
+ 'sharing',
240
262
  // Unit section
241
- 'appearance',
242
- 'soundAlerts',
263
+ 'screen',
264
+ 'volume',
243
265
  'lockScreen',
244
266
  'language',
245
- 'device',
267
+ 'wifi',
268
+ 'devices',
269
+ 'profiles',
270
+ 'tts',
271
+ 'updates',
272
+ 'about',
273
+ 'help',
274
+ 'dev',
275
+ // Account (rendered separately)
276
+ 'account',
277
+ 'activityLog',
246
278
  ] as const;
247
279
 
248
- export type SettingsGroupId = (typeof SETTINGS_GROUP_IDS)[number];
280
+ export type SettingsMenuItemId = (typeof SETTINGS_MENU_ITEM_IDS)[number];
249
281
 
250
- export type SettingsGroupDef = {
251
- /** Unique group identifier */
252
- id: SettingsGroupId;
253
- /** i18n message key for the group label */
282
+ export type SettingsMenuItemDef = {
283
+ id: SettingsMenuItemId;
254
284
  labelKey: string;
255
- /** Lucide icon name */
256
285
  icon: string;
286
+ section: SettingsSectionId | 'account';
287
+ /** Screen/page identifiers rendered for this menu item */
288
+ screens: string[];
257
289
  /**
258
- * Setting key matchers. A setting is included if its key:
259
- * - equals a matcher exactly, OR
260
- * - 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).
261
294
  */
262
- keys: string[];
295
+ keys?: string[];
263
296
  /** Only show for this calendar type (undefined = always) */
264
297
  calendarType?: CalendarType;
265
298
  /** Only show for this app mode (undefined = always) */
266
299
  appMode?: 'ENROLLED';
300
+ /** If true, don't wrap content in a scroll container */
301
+ noScrollView?: boolean;
267
302
  };
268
303
 
269
304
  /**
270
- * Group definitions matching the mobile app's settings structure.
271
- *
272
- * i18n keys use the format `Settings.GroupLabel.<Id>` and must exist
273
- * 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).
274
308
  */
275
- export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
276
- // ── Calendar section ──────────────────────────────────────────────────
309
+ export const SETTINGS_MENU_ITEMS: readonly SettingsMenuItemDef[] = [
310
+ // -- Calendar section --
277
311
  {
278
312
  id: 'calendarView',
279
- labelKey: 'Settings.CalendarView',
313
+ labelKey: 'Settings.Calendar',
280
314
  icon: 'Eye',
315
+ section: 'calendar',
316
+ screens: [
317
+ 'calendarDayView',
318
+ 'calendarWeekView',
319
+ 'chronologicalHeader',
320
+ 'chronologicalFooter',
321
+ 'chronologicalMenu',
322
+ 'dayColors',
323
+ 'dateTime',
324
+ ],
281
325
  keys: [
282
326
  'calendarView.showCalendarNames',
283
327
  'calendarView.splitView',
284
328
  'calendarView.dayViewZoom',
285
329
  'calendarView.weekViewZoom',
286
330
  'calendarView.calendarColumns',
331
+ 'appearance.enableDayColors',
332
+ ],
333
+ },
334
+ {
335
+ id: 'calendars',
336
+ labelKey: 'Common.Calendars',
337
+ icon: 'Calendar',
338
+ section: 'calendar',
339
+ screens: ['calendars'],
340
+ },
341
+ {
342
+ id: 'eventCategories',
343
+ labelKey: 'EventCategory.SettingsTitle',
344
+ icon: 'Tag',
345
+ section: 'calendar',
346
+ screens: ['eventCategories'],
347
+ calendarType: 'chronological',
348
+ },
349
+ {
350
+ id: 'notification',
351
+ labelKey: 'Settings.Notification',
352
+ icon: 'Bell',
353
+ section: 'calendar',
354
+ screens: ['notification'],
355
+ keys: [
356
+ 'sound.reminderAlarmSound',
357
+ 'sound.reminderAlarmTimeout',
358
+ 'sound.reminderVolume',
359
+ 'sound.allowCustomReminderSounds',
287
360
  ],
288
361
  },
289
362
  {
290
363
  id: 'timer',
291
364
  labelKey: 'Settings.TimerTitle',
292
365
  icon: 'Timer',
366
+ section: 'calendar',
367
+ screens: ['timerFeatures', 'timer'],
368
+ appMode: 'ENROLLED',
293
369
  keys: [
294
370
  'chronological.timer.',
295
371
  'timer.',
@@ -297,18 +373,39 @@ export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
297
373
  'sound.timerAlarmTimeout',
298
374
  'sound.timerVolume',
299
375
  ],
300
- appMode: 'ENROLLED',
301
376
  },
302
377
  {
303
- id: 'activities',
304
- labelKey: 'Settings.Activities',
305
- icon: 'CalendarPlus',
306
- keys: ['eventForm.', 'chronologicalEventForm.'],
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.'],
307
392
  },
308
393
  {
309
- id: 'dateWeather',
310
- labelKey: 'Settings.DateWeather',
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',
311
405
  icon: 'CloudSun',
406
+ section: 'calendar',
407
+ screens: ['weather'],
408
+ calendarType: 'time-based',
312
409
  keys: [
313
410
  'calendarView.showWeatherOnTimeline',
314
411
  'calendarView.weatherLocation',
@@ -316,48 +413,64 @@ export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
316
413
  ],
317
414
  },
318
415
  {
319
- id: 'touch',
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',
320
433
  labelKey: 'Settings.FunctionsTitle',
321
434
  icon: 'Hand',
435
+ section: 'calendar',
436
+ screens: ['gestures'],
322
437
  keys: ['touch.'],
323
438
  },
324
439
  {
325
440
  id: 'calendarType',
326
- labelKey: 'Settings.CalendarType',
327
- icon: 'ArrowLeftRight',
441
+ labelKey: 'Settings.Mode',
442
+ icon: 'SlidersHorizontal',
443
+ section: 'calendar',
444
+ screens: ['calendarType'],
328
445
  keys: ['calendarView.type'],
329
446
  },
330
447
  {
331
- id: 'chronological',
332
- labelKey: 'Settings.Chronological',
333
- icon: 'List',
334
- keys: ['chronological.'],
335
- calendarType: 'chronological',
448
+ id: 'sharing',
449
+ labelKey: 'Settings.Sharing',
450
+ icon: 'Share2',
451
+ section: 'calendar',
452
+ screens: ['icalSubscriptions'],
336
453
  },
337
- // ── Unit section ──────────────────────────────────────────────────────
454
+ // -- Unit section --
338
455
  {
339
- id: 'appearance',
340
- labelKey: 'Settings.Appearance',
341
- icon: 'Palette',
342
- keys: [
343
- 'appearance.theme',
344
- 'appearance.clockType',
345
- 'appearance.enableDayColors',
346
- ],
456
+ id: 'screen',
457
+ labelKey: 'Settings.Screen',
458
+ icon: 'LaptopMinimal',
459
+ section: 'unit',
460
+ screens: ['displayDensity', 'darkMode'],
461
+ keys: ['appearance.theme', 'appearance.clockType'],
347
462
  },
348
463
  {
349
- id: 'soundAlerts',
350
- labelKey: 'Settings.SoundAlerts',
464
+ id: 'volume',
465
+ labelKey: 'Common.Volume',
351
466
  icon: 'Volume2',
467
+ section: 'unit',
468
+ screens: ['volume'],
469
+ appMode: 'ENROLLED',
352
470
  keys: [
353
- 'sound.reminderVolume',
354
471
  'sound.mediaVolume',
355
- 'sound.reminderAlarmSound',
356
472
  'sound.startAlarmSound',
357
473
  'sound.endAlarmSound',
358
- 'sound.reminderAlarmTimeout',
359
- 'sound.allowCustomReminderSounds',
360
- 'notification.',
361
474
  'sound.ttsEnabled',
362
475
  'sound.ttsRate',
363
476
  'sound.alarmSound',
@@ -367,26 +480,106 @@ export const SETTINGS_GROUPS: readonly SettingsGroupDef[] = [
367
480
  id: 'lockScreen',
368
481
  labelKey: 'Settings.LockScreen',
369
482
  icon: 'Lock',
370
- keys: ['lockScreen.'],
483
+ section: 'unit',
484
+ screens: ['inactivity', 'lockScreen'],
371
485
  appMode: 'ENROLLED',
486
+ keys: ['lockScreen.'],
372
487
  },
373
488
  {
374
489
  id: 'language',
375
490
  labelKey: 'Settings.Language',
376
491
  icon: 'Globe',
492
+ section: 'unit',
493
+ screens: ['language'],
377
494
  keys: ['language.'],
378
495
  },
379
496
  {
380
- id: 'device',
497
+ id: 'wifi',
498
+ labelKey: 'Settings.WifiTitle',
499
+ icon: 'Wifi',
500
+ section: 'unit',
501
+ screens: ['wifi'],
502
+ appMode: 'ENROLLED',
503
+ },
504
+ {
505
+ id: 'devices',
381
506
  labelKey: 'SettingsDevices.Title',
382
507
  icon: 'Smartphone',
508
+ section: 'unit',
509
+ screens: ['devices'],
383
510
  keys: [
384
511
  'device.',
385
512
  'calendarView.autoReturnToTodayEnabled',
386
513
  'calendarView.autoReturnToTodayTimeoutSeconds',
387
514
  ],
388
515
  },
389
- ] as const;
516
+ {
517
+ id: 'profiles',
518
+ labelKey: 'SettingsProfiles.Title',
519
+ icon: 'BookCopy',
520
+ section: 'unit',
521
+ screens: ['profiles'],
522
+ },
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
+ ];
390
583
 
391
584
  /**
392
585
  * Settings excluded from remote editing (web portal / profile management).
@@ -645,19 +838,16 @@ export const SETTINGS_LABELS: Readonly<Record<string, SettingLabelDef>> = {
645
838
  'eventForm.checklist': { labelKey: 'EventChecklist.DefaultName' },
646
839
  'eventForm.images': { labelKey: 'EventImageGallery.SectionTitle' },
647
840
  'eventForm.audioClips': { labelKey: 'EventAudioGallery.SectionTitle' },
648
- 'eventForm.notificationReceivers': { labelKey: 'EventForm.NotificationReceivers' },
841
+ 'eventForm.notificationReceivers': {
842
+ labelKey: 'EventForm.NotificationReceivers',
843
+ },
649
844
  'eventForm.visibility': { labelKey: 'EventVisibility.Title' },
650
- };
651
845
 
652
- /**
653
- * Check whether a setting key matches a group's key matchers.
654
- */
655
- function matchesGroup(key: string, group: SettingsGroupDef): boolean {
656
- return group.keys.some(
657
- (matcher) =>
658
- key === matcher || (matcher.endsWith('.') && key.startsWith(matcher)),
659
- );
660
- }
846
+ // ── Chronological event form ────────────────────────────────────────────
847
+ 'chronologicalEventForm.field.category': {
848
+ labelKey: 'ChronologicalEventForm.CategoryField',
849
+ },
850
+ };
661
851
 
662
852
  /** Return the index of the first matching key pattern in the group, for sorting. */
663
853
  function groupKeyIndex(key: string, keys: readonly string[]): number {
@@ -687,78 +877,72 @@ function registryKeyOrder(): Map<string, number> {
687
877
  }
688
878
 
689
879
  /**
690
- * Group parsed setting entries into user-facing groups matching the mobile
691
- * app's settings menu structure.
692
- *
693
- * Takes a flat list of parsed settings (from `parseSettingsSnapshot`) and
694
- * buckets them into the standard groups, filtering by calendar type and
695
- * 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.
696
884
  */
697
- export function groupSettingsForDevice(
885
+ export function groupSettingsForWeb(
698
886
  allSettings: ParsedSettingEntry[],
699
887
  calendarType: CalendarType,
700
888
  appMode?: 'ENROLLED',
701
- ): { 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
+ }[] {
702
896
  // Filter excluded and hidden settings
703
897
  const settings = allSettings.filter(
704
898
  (s) => !EXCLUDED_DEVICE_SETTINGS.has(s.key) && s.uiType !== 'HIDDEN',
705
899
  );
706
900
 
707
- // Calendar type filtering for event form keys
708
901
  const isChronological = calendarType === 'chronological';
709
902
  const filteredSettings = settings.filter((s) => {
710
- if (isChronological && s.key.startsWith('eventForm.')) {
903
+ if (isChronological && s.key.startsWith('eventForm.')) return false;
904
+ if (!isChronological && s.key.startsWith('chronologicalEventForm.'))
711
905
  return false;
712
- }
713
- if (!isChronological && s.key.startsWith('chronologicalEventForm.')) {
714
- return false;
715
- }
716
- // Per-setting calendar type restriction
717
- if (s.calendarType && s.calendarType !== calendarType) {
718
- return false;
719
- }
720
- // Per-setting app mode restriction
721
- if (s.appMode && s.appMode !== appMode) {
722
- return false;
723
- }
906
+ if (s.calendarType && s.calendarType !== calendarType) return false;
907
+ if (s.appMode && s.appMode !== appMode) return false;
724
908
  return true;
725
909
  });
726
910
 
727
911
  const claimed = new Set<string>();
728
912
  const result: {
729
- id: SettingsGroupId;
913
+ id: SettingsMenuItemId;
730
914
  labelKey: string;
731
915
  icon: string;
916
+ section: SettingsSectionId | 'account';
732
917
  settings: ParsedSettingEntry[];
733
918
  }[] = [];
734
919
 
735
- for (const group of SETTINGS_GROUPS) {
736
- // Skip groups not relevant for current calendar type
737
- if (group.calendarType && group.calendarType !== calendarType) {
738
- continue;
739
- }
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;
740
923
 
741
- // Skip groups not relevant for current app mode
742
- if (group.appMode && group.appMode !== appMode) {
924
+ // Skip items not relevant for current calendar type
925
+ if (menuItem.calendarType && menuItem.calendarType !== calendarType)
743
926
  continue;
744
- }
745
927
 
746
- const matched = filteredSettings.filter(
747
- (s) => !claimed.has(s.key) && matchesGroup(s.key, group),
748
- );
749
- if (matched.length === 0) {
750
- continue;
751
- }
928
+ // Skip items not relevant for current app mode
929
+ if (menuItem.appMode && menuItem.appMode !== appMode) continue;
752
930
 
753
- // Sort matched settings by the order defined in group.keys,
754
- // with registry declaration order as tiebreaker for prefix matches
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;
939
+
940
+ // Sort by key order within the menu item's keys array
755
941
  const order = registryKeyOrder();
756
942
  matched.sort((a, b) => {
757
- const aIdx = groupKeyIndex(a.key, group.keys);
758
- const bIdx = groupKeyIndex(b.key, group.keys);
759
- if (aIdx !== bIdx) {
760
- return aIdx - bIdx;
761
- }
943
+ const aIdx = groupKeyIndex(a.key, menuItem.keys!);
944
+ const bIdx = groupKeyIndex(b.key, menuItem.keys!);
945
+ if (aIdx !== bIdx) return aIdx - bIdx;
762
946
  return (order.get(a.key) ?? Infinity) - (order.get(b.key) ?? Infinity);
763
947
  });
764
948
 
@@ -767,9 +951,10 @@ export function groupSettingsForDevice(
767
951
  }
768
952
 
769
953
  result.push({
770
- id: group.id,
771
- labelKey: group.labelKey,
772
- icon: group.icon,
954
+ id: menuItem.id,
955
+ labelKey: menuItem.labelKey,
956
+ icon: menuItem.icon,
957
+ section: menuItem.section,
773
958
  settings: matched,
774
959
  });
775
960
  }
@@ -830,31 +1015,34 @@ export const DEFAULT_REGISTRY_CONFIG: RegistryConfig = {
830
1015
  // ---------------------------------------------------------------------------
831
1016
 
832
1017
  const ALARM_SOUND_OPTIONS: readonly SettingOption<AlarmSound>[] = [
833
- { value: 'none', labelKey: 'Settings.Option.AlarmSound.None' },
834
- { value: 'alarm1', labelKey: 'Settings.Option.AlarmSound.Alarm1' },
835
- { value: 'alarm2', labelKey: 'Settings.Option.AlarmSound.Alarm2' },
836
- { value: 'alarm3', labelKey: 'Settings.Option.AlarmSound.Alarm3' },
837
- { value: 'alarm4', labelKey: 'Settings.Option.AlarmSound.Alarm4' },
838
- { value: 'alarm5', labelKey: 'Settings.Option.AlarmSound.Alarm5' },
839
- { value: 'alarm6', labelKey: 'Settings.Option.AlarmSound.Alarm6' },
840
- { value: 'alarm7', labelKey: 'Settings.Option.AlarmSound.Alarm7' },
841
- { value: 'alarm8', labelKey: 'Settings.Option.AlarmSound.Alarm8' },
842
- { 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' },
843
1028
  ];
844
1029
 
845
1030
  const ALARM_TIMEOUT_OPTIONS: readonly SettingOption<AlarmTimeout>[] = [
846
- { value: 1, labelKey: 'Settings.Option.AlarmTimeout.1min' },
847
- { value: 2, labelKey: 'Settings.Option.AlarmTimeout.2min' },
848
- { value: 3, labelKey: 'Settings.Option.AlarmTimeout.3min' },
849
- { value: 5, labelKey: 'Settings.Option.AlarmTimeout.5min' },
850
- { 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 } },
851
1038
  ];
852
1039
 
853
- const DAY_VIEW_ZOOM_OPTIONS: readonly SettingOption<CalendarDayViewCellZoom>[] = [
854
- { value: 15, labelKey: 'Settings.Option.DayViewZoom.15min' },
855
- { value: 30, labelKey: 'Settings.Option.DayViewZoom.30min' },
856
- { value: 60, labelKey: 'Settings.Option.DayViewZoom.1hour' },
857
- ];
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
+ ];
858
1046
 
859
1047
  const TIME_OF_DAY_SLIDER: SliderConfig = { min: 0, max: 23, step: 1 };
860
1048
 
@@ -912,11 +1100,16 @@ function buildEntries(config: RegistryConfig) {
912
1100
  config.defaultTheme,
913
1101
  'CUSTOM_THEME_PICKER',
914
1102
  {
915
- options: [
916
- { value: 'light', labelKey: 'Settings.Option.Theme.Light' },
917
- { value: 'dark', labelKey: 'Settings.Option.Theme.Dark' },
918
- { value: 'system', labelKey: 'Settings.Option.Theme.System' },
919
- ],
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
+ ],
920
1113
  },
921
1114
  ),
922
1115
  'appearance.clockType': def<ClockType>(
@@ -1060,36 +1253,70 @@ function buildEntries(config: RegistryConfig) {
1060
1253
  // ═══════════════════════════════════════════════════════════════════════
1061
1254
  // Event form field visibility (time-based)
1062
1255
  // ═══════════════════════════════════════════════════════════════════════
1063
- 'eventForm.recurrence': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1256
+ 'eventForm.recurrence': def<boolean>(
1257
+ 'eventForm',
1258
+ 'boolean',
1259
+ true,
1260
+ 'TOGGLE',
1261
+ ),
1064
1262
  'eventForm.location': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1065
- 'eventForm.travelTime': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1263
+ 'eventForm.travelTime': def<boolean>(
1264
+ 'eventForm',
1265
+ 'boolean',
1266
+ false,
1267
+ 'TOGGLE',
1268
+ ),
1066
1269
  'eventForm.reminders': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1067
- 'eventForm.emailReminders': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1068
- '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
+ ),
1069
1282
  'eventForm.checklist': def<boolean>('eventForm', 'boolean', true, 'TOGGLE'),
1070
1283
  'eventForm.images': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1071
- 'eventForm.audioClips': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1284
+ 'eventForm.audioClips': def<boolean>(
1285
+ 'eventForm',
1286
+ 'boolean',
1287
+ false,
1288
+ 'TOGGLE',
1289
+ ),
1072
1290
  'eventForm.notificationReceivers': def<boolean>(
1073
1291
  'eventForm',
1074
1292
  'boolean',
1075
1293
  true,
1076
1294
  'TOGGLE',
1077
1295
  ),
1078
- 'eventForm.visibility': def<boolean>('eventForm', 'boolean', false, 'TOGGLE'),
1296
+ 'eventForm.visibility': def<boolean>(
1297
+ 'eventForm',
1298
+ 'boolean',
1299
+ false,
1300
+ 'TOGGLE',
1301
+ ),
1079
1302
 
1080
1303
  // ═══════════════════════════════════════════════════════════════════════
1081
1304
  // Sound & alerts
1082
1305
  // ═══════════════════════════════════════════════════════════════════════
1083
1306
  'sound.timerVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER'),
1084
- 'sound.reminderVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
1085
- 'sound.mediaVolume': def<number>('sound', 'number', 0.5, 'VOLUME_SLIDER', { appMode: 'ENROLLED' }),
1086
- 'sound.alarmSound': def<AlarmSound>(
1307
+ 'sound.reminderVolume': def<number>(
1087
1308
  'sound',
1088
- 'string',
1089
- 'alarm1',
1090
- 'SELECT',
1091
- { options: ALARM_SOUND_OPTIONS },
1309
+ 'number',
1310
+ 0.5,
1311
+ 'VOLUME_SLIDER',
1312
+ { appMode: 'ENROLLED' },
1092
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
+ }),
1093
1320
  'sound.reminderAlarmSound': def<AlarmSound>(
1094
1321
  'sound',
1095
1322
  'string',
@@ -1100,16 +1327,16 @@ function buildEntries(config: RegistryConfig) {
1100
1327
  'sound.startAlarmSound': def<AlarmSound>(
1101
1328
  'sound',
1102
1329
  'string',
1103
- 'none',
1330
+ 'alarm1',
1104
1331
  'SELECT',
1105
- { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' },
1332
+ { options: ALARM_SOUND_OPTIONS },
1106
1333
  ),
1107
1334
  'sound.endAlarmSound': def<AlarmSound>(
1108
1335
  'sound',
1109
1336
  'string',
1110
- 'none',
1337
+ 'alarm1',
1111
1338
  'SELECT',
1112
- { options: ALARM_SOUND_OPTIONS, appMode: 'ENROLLED' },
1339
+ { options: ALARM_SOUND_OPTIONS },
1113
1340
  ),
1114
1341
  'sound.timerAlarmSound': def<AlarmSound>(
1115
1342
  'sound',
@@ -1121,14 +1348,14 @@ function buildEntries(config: RegistryConfig) {
1121
1348
  'sound.timerAlarmTimeout': def<AlarmTimeout>(
1122
1349
  'sound',
1123
1350
  'number',
1124
- 3,
1351
+ 180,
1125
1352
  'SELECT',
1126
1353
  { options: ALARM_TIMEOUT_OPTIONS },
1127
1354
  ),
1128
1355
  'sound.reminderAlarmTimeout': def<AlarmTimeout>(
1129
1356
  'sound',
1130
1357
  'number',
1131
- 3,
1358
+ 180,
1132
1359
  'SELECT',
1133
1360
  { options: ALARM_TIMEOUT_OPTIONS },
1134
1361
  ),
@@ -1145,21 +1372,22 @@ function buildEntries(config: RegistryConfig) {
1145
1372
  'TOGGLE',
1146
1373
  { appMode: 'ENROLLED' },
1147
1374
  ),
1148
- 'sound.ttsRate': def<number>(
1149
- 'sound',
1150
- 'number',
1151
- 1.0,
1152
- 'SLIDER',
1153
- { sliderConfig: { min: 0.5, max: 2, step: 0.1 }, appMode: 'ENROLLED' },
1154
- ),
1375
+ 'sound.ttsRate': def<number>('sound', 'number', 1.0, 'SLIDER', {
1376
+ sliderConfig: { min: 0.5, max: 2, step: 0.1 },
1377
+ appMode: 'ENROLLED',
1378
+ }),
1155
1379
 
1156
1380
  // ═══════════════════════════════════════════════════════════════════════
1157
1381
  // Timer
1158
1382
  // ═══════════════════════════════════════════════════════════════════════
1159
1383
  'timer.showTimeRemaining': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
1160
- '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
+ }),
1161
1387
  'timer.showRestartButton': def<boolean>('timer', 'boolean', true, 'TOGGLE'),
1162
- '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
+ }),
1163
1391
 
1164
1392
  // ═══════════════════════════════════════════════════════════════════════
1165
1393
  // Lock screen
@@ -1268,7 +1496,12 @@ function buildEntries(config: RegistryConfig) {
1268
1496
  // ═══════════════════════════════════════════════════════════════════════
1269
1497
  // Touch / gestures
1270
1498
  // ═══════════════════════════════════════════════════════════════════════
1271
- 'touch.enableTapToCreate': def<boolean>('touch', 'boolean', false, 'TOGGLE'),
1499
+ 'touch.enableTapToCreate': def<boolean>(
1500
+ 'touch',
1501
+ 'boolean',
1502
+ false,
1503
+ 'TOGGLE',
1504
+ ),
1272
1505
  'touch.enableDragDrop': def<boolean>('touch', 'boolean', false, 'TOGGLE'),
1273
1506
 
1274
1507
  // ═══════════════════════════════════════════════════════════════════════
@@ -1537,6 +1770,12 @@ function buildEntries(config: RegistryConfig) {
1537
1770
  // Chronological event form
1538
1771
  // ═══════════════════════════════════════════════════════════════════════
1539
1772
  // Fixed fields
1773
+ 'chronologicalEventForm.fixedField.category': def<boolean>(
1774
+ 'chronologicalEventForm',
1775
+ 'boolean',
1776
+ true,
1777
+ 'TOGGLE',
1778
+ ),
1540
1779
  'chronologicalEventForm.fixedField.allDay': def<boolean>(
1541
1780
  'chronologicalEventForm',
1542
1781
  'boolean',
@@ -1604,6 +1843,12 @@ function buildEntries(config: RegistryConfig) {
1604
1843
  true,
1605
1844
  'TOGGLE',
1606
1845
  ),
1846
+ 'chronologicalEventForm.field.category': def<boolean>(
1847
+ 'chronologicalEventForm',
1848
+ 'boolean',
1849
+ true,
1850
+ 'TOGGLE',
1851
+ ),
1607
1852
  // Suggest end time
1608
1853
  'chronologicalEventForm.suggestEndTime.enabled': def<boolean>(
1609
1854
  'chronologicalEventForm',
@@ -1650,6 +1895,19 @@ function buildEntries(config: RegistryConfig) {
1650
1895
  ],
1651
1896
  },
1652
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
+ ),
1653
1911
  // Reminder presets
1654
1912
  'chronologicalEventForm.reminderPresets.timed': def<number[]>(
1655
1913
  'chronologicalEventForm',
@@ -1917,9 +2175,8 @@ export function parseSettingsSnapshot(
1917
2175
  }
1918
2176
 
1919
2177
  const labelDef = SETTINGS_LABELS[key];
1920
- const entryDef = key in registry.entries
1921
- ? registry.entries[key as SettingKey]
1922
- : undefined;
2178
+ const entryDef =
2179
+ key in registry.entries ? registry.entries[key as SettingKey] : undefined;
1923
2180
  groups.get(category)!.push({
1924
2181
  key,
1925
2182
  name,
@@ -1928,7 +2185,9 @@ export function parseSettingsSnapshot(
1928
2185
  descriptionKey: labelDef?.descriptionKey,
1929
2186
  value,
1930
2187
  uiType: entryDef?.uiType ?? 'TEXT_INPUT',
1931
- ...(entryDef?.options && { options: entryDef.options as readonly SettingOption[] }),
2188
+ ...(entryDef?.options && {
2189
+ options: entryDef.options as readonly SettingOption[],
2190
+ }),
1932
2191
  ...(entryDef?.sliderConfig && { sliderConfig: entryDef.sliderConfig }),
1933
2192
  ...(entryDef?.appMode && { appMode: entryDef.appMode }),
1934
2193
  ...(entryDef?.calendarType && { calendarType: entryDef.calendarType }),