release: 0.1.3 — periodic update check + sidebar 1/3/7/all groups

- updateStore: arm a 2-hour periodic checkForUpdates after the
  startup pass so users who keep the client open for days still
  see new releases without restarting. Idempotent, silent — only
  the top-right popup surface fires.
- Sidebar: bucket session history into Past 24h / Past 3 days /
  Past 7 days / All older (was Today/Yesterday/7d/30d/Older).
  Rolling windows from `Date.now()` instead of calendar bounds.
- i18n: replace `today/yesterday/last7days/last30days` keys with
  `within1day/within3days/within7days`; ScheduledTasksList and
  ScheduledTasksEmpty migrated to the new keys.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 15:23:48 +08:00
co-authored by Claude Opus 4.7
parent f888163ba7
commit 66b47f61be
9 changed files with 45 additions and 32 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "heicode-desktop",
"private": true,
"version": "0.1.2",
"version": "0.1.3",
"type": "module",
"scripts": {
"dev": "vite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "heicode-desktop"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
[lib]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/nicegui/nicegui/main/nicegui/static/tauri-schema-v2.json",
"productName": "HeiCode",
"version": "0.1.2",
"version": "0.1.3",
"identifier": "com.heicode.desktop",
"build": {
"frontendDist": "../dist",
@@ -17,9 +17,9 @@ import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore'
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window)
const isWindows = typeof navigator !== 'undefined' && /Win/.test(navigator.platform)
type TimeGroup = 'today' | 'yesterday' | 'last7days' | 'last30days' | 'older'
type TimeGroup = 'within1day' | 'within3days' | 'within7days' | 'older'
const TIME_GROUP_ORDER: TimeGroup[] = ['today', 'yesterday', 'last7days', 'last30days', 'older']
const TIME_GROUP_ORDER: TimeGroup[] = ['within1day', 'within3days', 'within7days', 'older']
export function Sidebar() {
const sessions = useSessionStore((s) => s.sessions)
@@ -131,10 +131,9 @@ export function Sidebar() {
const t = useTranslation()
const timeGroupLabels: Record<TimeGroup, string> = {
today: t('sidebar.timeGroup.today'),
yesterday: t('sidebar.timeGroup.yesterday'),
last7days: t('sidebar.timeGroup.last7days'),
last30days: t('sidebar.timeGroup.last30days'),
within1day: t('sidebar.timeGroup.within1day'),
within3days: t('sidebar.timeGroup.within3days'),
within7days: t('sidebar.timeGroup.within7days'),
older: t('sidebar.timeGroup.older'),
}
@@ -454,19 +453,17 @@ export function Sidebar() {
function groupByTime(sessions: SessionListItem[]): Map<TimeGroup, SessionListItem[]> {
const groups = new Map<TimeGroup, SessionListItem[]>()
const now = new Date()
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
const startOfYesterday = startOfToday - 86400000
const sevenDaysAgo = startOfToday - 7 * 86400000
const thirtyDaysAgo = startOfToday - 30 * 86400000
const now = Date.now()
const oneDayAgo = now - 86400000
const threeDaysAgo = now - 3 * 86400000
const sevenDaysAgo = now - 7 * 86400000
for (const session of sessions) {
const ts = new Date(session.modifiedAt).getTime()
let group: TimeGroup
if (ts >= startOfToday) group = 'today'
else if (ts >= startOfYesterday) group = 'yesterday'
else if (ts >= sevenDaysAgo) group = 'last7days'
else if (ts >= thirtyDaysAgo) group = 'last30days'
if (ts >= oneDayAgo) group = 'within1day'
else if (ts >= threeDaysAgo) group = 'within3days'
else if (ts >= sevenDaysAgo) group = 'within7days'
else group = 'older'
if (!groups.has(group)) groups.set(group, [])
+4 -5
View File
@@ -31,11 +31,10 @@ export const en = {
'sidebar.confirmDelete': 'Delete this session? This cannot be undone.',
'sidebar.allProjects': 'All projects',
'sidebar.other': 'Other',
'sidebar.timeGroup.today': 'Today',
'sidebar.timeGroup.yesterday': 'Yesterday',
'sidebar.timeGroup.last7days': 'Last 7 days',
'sidebar.timeGroup.last30days': 'Last 30 days',
'sidebar.timeGroup.older': 'Older',
'sidebar.timeGroup.within1day': 'Past 24 hours',
'sidebar.timeGroup.within3days': 'Past 3 days',
'sidebar.timeGroup.within7days': 'Past 7 days',
'sidebar.timeGroup.older': 'All older',
'sidebar.collapse': 'Collapse sidebar',
'sidebar.expand': 'Expand sidebar',
'sidebar.logout': 'Log out',
+4 -5
View File
@@ -33,11 +33,10 @@ export const zh: Record<TranslationKey, string> = {
'sidebar.confirmDelete': '确定要删除这个会话吗?此操作不可撤销。',
'sidebar.allProjects': '所有项目',
'sidebar.other': '其他',
'sidebar.timeGroup.today': '今天',
'sidebar.timeGroup.yesterday': '昨天',
'sidebar.timeGroup.last7days': '最近 7 天',
'sidebar.timeGroup.last30days': '最近 30 天',
'sidebar.timeGroup.older': '更早',
'sidebar.timeGroup.within1day': '1 天内',
'sidebar.timeGroup.within3days': '3 天内',
'sidebar.timeGroup.within7days': '7 天内',
'sidebar.timeGroup.older': '全部',
'sidebar.collapse': '折叠侧边栏',
'sidebar.expand': '展开侧边栏',
'sidebar.logout': '退出登录',
@@ -29,11 +29,11 @@ export function ScheduledTasksEmpty() {
</div>
<div className="px-3 py-2 text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">history</span>
<span>{t('sidebar.timeGroup.today')}</span>
<span>{t('sidebar.timeGroup.within1day')}</span>
</div>
<div className="px-3 py-2 text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">event_note</span>
<span>{t('sidebar.timeGroup.last7days')}</span>
<span>{t('sidebar.timeGroup.within7days')}</span>
</div>
<div className="px-3 py-2 text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg cursor-pointer duration-200 ease-in-out flex items-center gap-3">
<span className="material-symbols-outlined">archive</span>
@@ -42,11 +42,11 @@ export function ScheduledTasksList() {
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">history</span>
{t('sidebar.timeGroup.today')}
{t('sidebar.timeGroup.within1day')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">event_note</span>
{t('sidebar.timeGroup.last7days')}
{t('sidebar.timeGroup.within7days')}
</button>
<button className="flex items-center gap-3 px-3 py-2 w-full text-[var(--color-text-secondary)] hover:bg-[#EBEBE6] transition-all rounded-lg font-medium text-sm duration-200 ease-in-out">
<span className="material-symbols-outlined">archive</span>
+18
View File
@@ -35,6 +35,12 @@ type UpdateStore = {
let pendingUpdate: Update | null = null
let startupCheckPromise: Promise<void> | null = null
let periodicCheckTimer: ReturnType<typeof setInterval> | null = null
// How often the long-running client re-polls the updater manifest while
// idle. 2 hours strikes a balance between "user sees a new release the
// same day it ships" and "we don't hammer the blob endpoint".
const PERIODIC_CHECK_INTERVAL_MS = 2 * 60 * 60 * 1000
function readDismissedUpdateVersion(): string | null {
if (typeof window === 'undefined') return null
@@ -100,6 +106,18 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
}
await startupCheckPromise
// After the startup check settles, arm a periodic re-check so users
// who keep the app open for hours/days still see new releases without
// having to restart. Idempotent — only one timer ever exists.
if (!periodicCheckTimer) {
periodicCheckTimer = setInterval(() => {
// silent:true → background errors don't paint the red banner;
// a successful hit still flips shouldPrompt and triggers the
// top-right popup via the existing render path.
void get().checkForUpdates({ silent: true })
}, PERIODIC_CHECK_INTERVAL_MS)
}
},
checkForUpdates: async ({ silent = false } = {}) => {