refactor: centralise user timezone as a standalone setting
Browser timezone is now synced to user_settings["user_timezone"] on every login/page load (App.vue). The briefing scheduler and LLM context both read from this single source, falling back to the legacy briefing_config.timezone for existing users during migration. - App.vue: PUT /api/settings with browser IANA timezone on startAppServices - routes/chat.py: fall back to stored user_timezone when not sent in request - briefing_scheduler: read user_timezone setting; briefing_config.timezone kept as fallback only - routes/briefing.py: pass tz_override from user_timezone to live-patched scheduler - Remove timezone field from BriefingConfig interface and all briefing UI Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { useShortcuts } from "@/composables/useShortcuts";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useChatStore } from "@/stores/chat";
|
||||
import { useSettingsStore } from "@/stores/settings";
|
||||
import { apiGet } from "@/api/client";
|
||||
import { apiGet, apiPut } from "@/api/client";
|
||||
|
||||
useTheme();
|
||||
|
||||
@@ -22,6 +22,8 @@ const { showShortcuts, toggleShortcuts, closeShortcuts } = useShortcuts();
|
||||
function startAppServices() {
|
||||
chatStore.startStatusPolling();
|
||||
settingsStore.fetchSettings();
|
||||
// Sync browser timezone to the server on every login/page load.
|
||||
apiPut("/api/settings", { user_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }).catch(() => {});
|
||||
}
|
||||
|
||||
function stopAppServices() {
|
||||
|
||||
@@ -329,7 +329,6 @@ export interface BriefingConfig {
|
||||
slots: BriefingSlots;
|
||||
notifications: boolean;
|
||||
temp_unit: 'C' | 'F';
|
||||
timezone: string;
|
||||
}
|
||||
|
||||
export interface BriefingFeed {
|
||||
@@ -364,7 +363,6 @@ const DEFAULT_BRIEFING_CONFIG: BriefingConfig = {
|
||||
slots: { compilation: true, morning: true, midday: false, afternoon: false },
|
||||
notifications: true,
|
||||
temp_unit: 'C',
|
||||
timezone: '',
|
||||
};
|
||||
|
||||
export async function getBriefingConfig(): Promise<BriefingConfig> {
|
||||
|
||||
@@ -20,7 +20,6 @@ const config = reactive<BriefingConfig>({
|
||||
slots: { compilation: true, morning: true, midday: false, afternoon: false },
|
||||
notifications: true,
|
||||
temp_unit: 'C',
|
||||
timezone: '',
|
||||
})
|
||||
|
||||
// Step 2 — locations
|
||||
|
||||
@@ -258,7 +258,6 @@ const briefingConfig = ref<BriefingConfig>({
|
||||
slots: { compilation: true, morning: true, midday: false, afternoon: false },
|
||||
notifications: true,
|
||||
temp_unit: 'C',
|
||||
timezone: '',
|
||||
});
|
||||
const briefingFeeds = ref<BriefingFeed[]>([]);
|
||||
const briefingSaving = ref(false);
|
||||
@@ -284,10 +283,6 @@ function _parseTopics(raw: unknown): string[] {
|
||||
async function loadBriefingTab() {
|
||||
briefingConfig.value = await getBriefingConfig();
|
||||
briefingFeeds.value = await getBriefingFeeds();
|
||||
// Auto-populate timezone from browser if not already stored.
|
||||
if (!briefingConfig.value.timezone) {
|
||||
briefingConfig.value.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
}
|
||||
const allSettings = await apiGet<Record<string, string>>('/api/settings').catch(() => ({} as Record<string, string>));
|
||||
briefingIncludeTopics.value = _parseTopics(allSettings['briefing_include_topics'] ?? '[]');
|
||||
briefingExcludeTopics.value = _parseTopics(allSettings['briefing_exclude_topics'] ?? '[]');
|
||||
@@ -1694,35 +1689,6 @@ function formatUserDate(iso: string): string {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Timezone -->
|
||||
<section class="settings-section full-width">
|
||||
<h2>Timezone</h2>
|
||||
<p class="section-desc">
|
||||
Briefing slots fire at the times below in this timezone.
|
||||
Auto-detected from your browser — override if the server should use a different zone.
|
||||
</p>
|
||||
<div class="briefing-timezone-row">
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
v-model="briefingConfig.timezone"
|
||||
placeholder="e.g. America/New_York"
|
||||
style="flex: 1"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-secondary"
|
||||
@click="briefingConfig.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone"
|
||||
>Detect</button>
|
||||
</div>
|
||||
<p class="field-hint">
|
||||
Use an
|
||||
<a href="https://en.wikipedia.org/wiki/List_of_tz_database_time_zones" target="_blank" rel="noopener">IANA timezone name</a>
|
||||
(e.g. <code>Europe/London</code>, <code>America/Chicago</code>).
|
||||
Your browser reports: <strong>{{ Intl.DateTimeFormat().resolvedOptions().timeZone }}</strong>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<!-- Slots -->
|
||||
<section class="settings-section full-width">
|
||||
<h2>Scheduled Slots</h2>
|
||||
@@ -1747,8 +1713,8 @@ function formatUserDate(iso: string): string {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p v-if="briefingConfig.timezone" class="field-hint" style="margin-top: 0.5rem">
|
||||
Firing in timezone: <strong>{{ briefingConfig.timezone }}</strong>
|
||||
<p class="field-hint" style="margin-top: 0.5rem">
|
||||
Firing in timezone: <strong>{{ Intl.DateTimeFormat().resolvedOptions().timeZone }}</strong>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -3313,12 +3279,6 @@ FABLE_API_KEY=<your-api-key></pre>
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.briefing-timezone-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.briefing-unit-toggle {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
|
||||
Reference in New Issue
Block a user