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:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "heicode-desktop",
|
"name": "heicode-desktop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "heicode-desktop"
|
name = "heicode-desktop"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://raw.githubusercontent.com/nicegui/nicegui/main/nicegui/static/tauri-schema-v2.json",
|
"$schema": "https://raw.githubusercontent.com/nicegui/nicegui/main/nicegui/static/tauri-schema-v2.json",
|
||||||
"productName": "HeiCode",
|
"productName": "HeiCode",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"identifier": "com.heicode.desktop",
|
"identifier": "com.heicode.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"frontendDist": "../dist",
|
"frontendDist": "../dist",
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore'
|
|||||||
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window)
|
const isTauri = typeof window !== 'undefined' && ('__TAURI_INTERNALS__' in window || '__TAURI__' in window)
|
||||||
const isWindows = typeof navigator !== 'undefined' && /Win/.test(navigator.platform)
|
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() {
|
export function Sidebar() {
|
||||||
const sessions = useSessionStore((s) => s.sessions)
|
const sessions = useSessionStore((s) => s.sessions)
|
||||||
@@ -131,10 +131,9 @@ export function Sidebar() {
|
|||||||
const t = useTranslation()
|
const t = useTranslation()
|
||||||
|
|
||||||
const timeGroupLabels: Record<TimeGroup, string> = {
|
const timeGroupLabels: Record<TimeGroup, string> = {
|
||||||
today: t('sidebar.timeGroup.today'),
|
within1day: t('sidebar.timeGroup.within1day'),
|
||||||
yesterday: t('sidebar.timeGroup.yesterday'),
|
within3days: t('sidebar.timeGroup.within3days'),
|
||||||
last7days: t('sidebar.timeGroup.last7days'),
|
within7days: t('sidebar.timeGroup.within7days'),
|
||||||
last30days: t('sidebar.timeGroup.last30days'),
|
|
||||||
older: t('sidebar.timeGroup.older'),
|
older: t('sidebar.timeGroup.older'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,19 +453,17 @@ export function Sidebar() {
|
|||||||
|
|
||||||
function groupByTime(sessions: SessionListItem[]): Map<TimeGroup, SessionListItem[]> {
|
function groupByTime(sessions: SessionListItem[]): Map<TimeGroup, SessionListItem[]> {
|
||||||
const groups = new Map<TimeGroup, SessionListItem[]>()
|
const groups = new Map<TimeGroup, SessionListItem[]>()
|
||||||
const now = new Date()
|
const now = Date.now()
|
||||||
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime()
|
const oneDayAgo = now - 86400000
|
||||||
const startOfYesterday = startOfToday - 86400000
|
const threeDaysAgo = now - 3 * 86400000
|
||||||
const sevenDaysAgo = startOfToday - 7 * 86400000
|
const sevenDaysAgo = now - 7 * 86400000
|
||||||
const thirtyDaysAgo = startOfToday - 30 * 86400000
|
|
||||||
|
|
||||||
for (const session of sessions) {
|
for (const session of sessions) {
|
||||||
const ts = new Date(session.modifiedAt).getTime()
|
const ts = new Date(session.modifiedAt).getTime()
|
||||||
let group: TimeGroup
|
let group: TimeGroup
|
||||||
if (ts >= startOfToday) group = 'today'
|
if (ts >= oneDayAgo) group = 'within1day'
|
||||||
else if (ts >= startOfYesterday) group = 'yesterday'
|
else if (ts >= threeDaysAgo) group = 'within3days'
|
||||||
else if (ts >= sevenDaysAgo) group = 'last7days'
|
else if (ts >= sevenDaysAgo) group = 'within7days'
|
||||||
else if (ts >= thirtyDaysAgo) group = 'last30days'
|
|
||||||
else group = 'older'
|
else group = 'older'
|
||||||
|
|
||||||
if (!groups.has(group)) groups.set(group, [])
|
if (!groups.has(group)) groups.set(group, [])
|
||||||
|
|||||||
@@ -31,11 +31,10 @@ export const en = {
|
|||||||
'sidebar.confirmDelete': 'Delete this session? This cannot be undone.',
|
'sidebar.confirmDelete': 'Delete this session? This cannot be undone.',
|
||||||
'sidebar.allProjects': 'All projects',
|
'sidebar.allProjects': 'All projects',
|
||||||
'sidebar.other': 'Other',
|
'sidebar.other': 'Other',
|
||||||
'sidebar.timeGroup.today': 'Today',
|
'sidebar.timeGroup.within1day': 'Past 24 hours',
|
||||||
'sidebar.timeGroup.yesterday': 'Yesterday',
|
'sidebar.timeGroup.within3days': 'Past 3 days',
|
||||||
'sidebar.timeGroup.last7days': 'Last 7 days',
|
'sidebar.timeGroup.within7days': 'Past 7 days',
|
||||||
'sidebar.timeGroup.last30days': 'Last 30 days',
|
'sidebar.timeGroup.older': 'All older',
|
||||||
'sidebar.timeGroup.older': 'Older',
|
|
||||||
'sidebar.collapse': 'Collapse sidebar',
|
'sidebar.collapse': 'Collapse sidebar',
|
||||||
'sidebar.expand': 'Expand sidebar',
|
'sidebar.expand': 'Expand sidebar',
|
||||||
'sidebar.logout': 'Log out',
|
'sidebar.logout': 'Log out',
|
||||||
|
|||||||
@@ -33,11 +33,10 @@ export const zh: Record<TranslationKey, string> = {
|
|||||||
'sidebar.confirmDelete': '确定要删除这个会话吗?此操作不可撤销。',
|
'sidebar.confirmDelete': '确定要删除这个会话吗?此操作不可撤销。',
|
||||||
'sidebar.allProjects': '所有项目',
|
'sidebar.allProjects': '所有项目',
|
||||||
'sidebar.other': '其他',
|
'sidebar.other': '其他',
|
||||||
'sidebar.timeGroup.today': '今天',
|
'sidebar.timeGroup.within1day': '1 天内',
|
||||||
'sidebar.timeGroup.yesterday': '昨天',
|
'sidebar.timeGroup.within3days': '3 天内',
|
||||||
'sidebar.timeGroup.last7days': '最近 7 天',
|
'sidebar.timeGroup.within7days': '7 天内',
|
||||||
'sidebar.timeGroup.last30days': '最近 30 天',
|
'sidebar.timeGroup.older': '全部',
|
||||||
'sidebar.timeGroup.older': '更早',
|
|
||||||
'sidebar.collapse': '折叠侧边栏',
|
'sidebar.collapse': '折叠侧边栏',
|
||||||
'sidebar.expand': '展开侧边栏',
|
'sidebar.expand': '展开侧边栏',
|
||||||
'sidebar.logout': '退出登录',
|
'sidebar.logout': '退出登录',
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ export function ScheduledTasksEmpty() {
|
|||||||
</div>
|
</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">
|
<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 className="material-symbols-outlined">history</span>
|
||||||
<span>{t('sidebar.timeGroup.today')}</span>
|
<span>{t('sidebar.timeGroup.within1day')}</span>
|
||||||
</div>
|
</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">
|
<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 className="material-symbols-outlined">event_note</span>
|
||||||
<span>{t('sidebar.timeGroup.last7days')}</span>
|
<span>{t('sidebar.timeGroup.within7days')}</span>
|
||||||
</div>
|
</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">
|
<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>
|
<span className="material-symbols-outlined">archive</span>
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ export function ScheduledTasksList() {
|
|||||||
</button>
|
</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">
|
<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>
|
<span className="material-symbols-outlined">history</span>
|
||||||
{t('sidebar.timeGroup.today')}
|
{t('sidebar.timeGroup.within1day')}
|
||||||
</button>
|
</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">
|
<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>
|
<span className="material-symbols-outlined">event_note</span>
|
||||||
{t('sidebar.timeGroup.last7days')}
|
{t('sidebar.timeGroup.within7days')}
|
||||||
</button>
|
</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">
|
<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>
|
<span className="material-symbols-outlined">archive</span>
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ type UpdateStore = {
|
|||||||
|
|
||||||
let pendingUpdate: Update | null = null
|
let pendingUpdate: Update | null = null
|
||||||
let startupCheckPromise: Promise<void> | 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 {
|
function readDismissedUpdateVersion(): string | null {
|
||||||
if (typeof window === 'undefined') return null
|
if (typeof window === 'undefined') return null
|
||||||
@@ -100,6 +106,18 @@ export const useUpdateStore = create<UpdateStore>((set, get) => ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await startupCheckPromise
|
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 } = {}) => {
|
checkForUpdates: async ({ silent = false } = {}) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user