* feat(settings): per-user date and time display format settings Add ui.date_format (auto, DD/MM/YYYY, MM/DD/YYYY, YYYY-MM-DD) and ui.time_format (auto, 12h, 24h) as validated user-scoped settings, a shared preference-aware formatter module (web/src/lib/datetime.ts) synced via DateTimeFormatProvider, a Date & time section in Appearance settings, and convert all absolute date/time display call sites to the shared formatters. Closes #303 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): make loaded API settings authoritative for date/time formats Address adversarial review: once the authenticated settings request resolves, a missing ui.date_format/ui.time_format key means "auto" instead of falling back to device-wide localStorage (which could carry another user's preference), and failed saves roll back through the query cache. Layout and AdminLayout subscribe to the format store so all routed pages re-render live when the preference changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): reliable re-render and rollback for date/time format changes ReactiveAppRoutes re-renders the routed page tree when the format preference changes (a Layout-level subscription cannot re-render stable children elements); memoized AdminLogs rows and the out-of-route PlayingNextScreen subscribe directly. useSetSetting now rolls back only the mutated key on error and invalidates the settings list on settle so overlapping saves cannot resurrect stale values. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): guard same-key rollback against newer optimistic saves Roll back a failed setting save only while its optimistic value is still current in the cache, invalidate the detail query on settle, and add a regression test for overlapping same-key mutations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(settings): owner-bind the datetime format warm start, pad 24h hours Address PR review: the localStorage warm start is now tagged with the user id that mirrored it and is ignored for a different authenticated user, so a failed settings request can no longer leak another account's format on a shared browser. The 24h branch of formatTime defaults to 2-digit hours ("09:04") since h23 alone does not guarantee padding in every locale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
39 lines
1.1 KiB
Go
39 lines
1.1 KiB
Go
package handlers
|
|
|
|
import "testing"
|
|
|
|
func TestDateTimeFormatSettingValidation(t *testing.T) {
|
|
valid := map[string][]string{
|
|
dateFormatSettingKey: {"auto", "DD/MM/YYYY", "MM/DD/YYYY", "YYYY-MM-DD"},
|
|
timeFormatSettingKey: {"auto", "12h", "24h"},
|
|
}
|
|
for key, values := range valid {
|
|
for _, value := range values {
|
|
if err := validateRegisteredSetting(key, value, scopeUser); err != nil {
|
|
t.Errorf("expected %s=%q to validate, got %v", key, value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
invalid := map[string][]string{
|
|
dateFormatSettingKey: {"", "YYYY/MM/DD", "dd/mm/yyyy", "iso"},
|
|
timeFormatSettingKey: {"", "12", "24", "12H"},
|
|
}
|
|
for key, values := range invalid {
|
|
for _, value := range values {
|
|
if err := validateRegisteredSetting(key, value, scopeUser); err == nil {
|
|
t.Errorf("expected %s=%q to be rejected", key, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, key := range []string{dateFormatSettingKey, timeFormatSettingKey} {
|
|
if !keyUsesUserScope(key) {
|
|
t.Errorf("expected %s to be user-scoped", key)
|
|
}
|
|
if err := validateRegisteredSetting(key, "auto", scopeDevice); err == nil {
|
|
t.Errorf("expected %s to reject device scope", key)
|
|
}
|
|
}
|
|
}
|