mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-16 11:52:25 +02:00
feat(ui): chat frontend, model picker, streaming, admin, a11y
Preview/UI work: chat renderer, streaming/think-trace, model picker
(instant-open/refresh-backoff), admin panel, a11y, gallery landing.
Source: vendor/photocraft (ad1cff44) static files, 3-way merged onto
upstream/dev (a8c147b2). Resolution policy in pr_work/D_conflicts.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
a8c147b238
commit
8fda203d28
@@ -1302,8 +1302,11 @@ let _libSyncStatus = {
|
||||
source: '',
|
||||
warming: false,
|
||||
loading: false,
|
||||
stale: false,
|
||||
};
|
||||
let _libSyncTicker = null;
|
||||
let _libStaleRefetchTimer = null;
|
||||
let _libStaleRefetchCount = 0;
|
||||
|
||||
function _libSyncDateFrom(value) {
|
||||
if (!value) return null;
|
||||
@@ -1358,9 +1361,37 @@ function _setEmailSyncStatus(next = {}) {
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'loading')) {
|
||||
_libSyncStatus.loading = Boolean(next.loading);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(next, 'stale')) {
|
||||
_libSyncStatus.stale = Boolean(next.stale);
|
||||
}
|
||||
_renderEmailSyncStatus();
|
||||
}
|
||||
|
||||
// A server-side stale (sync.stale) response means the SWR snapshot was served
|
||||
// while a background IMAP refresh is still running. Re-fetch in place after a
|
||||
// short delay (bounded) so the fresh rows land with no skeleton and no scroll
|
||||
// jump. We only keep retrying while the response is still marked stale.
|
||||
function _clearStaleEmailRefetch() {
|
||||
if (_libStaleRefetchTimer) {
|
||||
clearTimeout(_libStaleRefetchTimer);
|
||||
_libStaleRefetchTimer = null;
|
||||
}
|
||||
_libStaleRefetchCount = 0;
|
||||
}
|
||||
|
||||
function _scheduleStaleEmailRefetch() {
|
||||
if (_libStaleRefetchTimer || _libStaleRefetchCount >= 3) return;
|
||||
const folderAtSchedule = state._libFolder;
|
||||
const accountAtSchedule = state._libAccountId || '';
|
||||
_libStaleRefetchCount++;
|
||||
_libStaleRefetchTimer = setTimeout(() => {
|
||||
_libStaleRefetchTimer = null;
|
||||
if (document.hidden) return;
|
||||
if (folderAtSchedule !== state._libFolder || accountAtSchedule !== (state._libAccountId || '')) return;
|
||||
_loadEmails({ force: false, useCache: false, skipFastPath: true });
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
function _libCacheKeyFor(accountId, folder, filter, hasAttachments) {
|
||||
return [
|
||||
accountId || '',
|
||||
@@ -2211,7 +2242,7 @@ export function openEmailLibrary(opts = {}) {
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
<button class="close-btn" id="email-lib-close">\u2716</button>
|
||||
<button class="close-btn" id="email-lib-close" aria-label="Close">\u2716</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body" style="display:flex;flex-direction:column;gap:10px;overflow:hidden;">
|
||||
@@ -4794,7 +4825,7 @@ export async function _refreshUnreadBadge({ unreadCountOverride = null, preserve
|
||||
} catch (_) { _syncUnreadTabBadge(0); }
|
||||
}
|
||||
|
||||
export async function _loadEmails({ force = false, useCache = true } = {}) {
|
||||
export async function _loadEmails({ force = false, useCache = true, skipFastPath = false } = {}) {
|
||||
const seq = ++_libLoadSeq;
|
||||
state._libLoading = true;
|
||||
const accountAtStart = state._libAccountId || '';
|
||||
@@ -4848,11 +4879,15 @@ export async function _loadEmails({ force = false, useCache = true } = {}) {
|
||||
}
|
||||
const stats = document.getElementById('email-lib-stats');
|
||||
if (stats) stats.textContent = `${state._libTotal} emails`;
|
||||
const _isStale = Boolean(sync.stale);
|
||||
_setEmailSyncStatus({
|
||||
updatedAt: sync.updated_at || '',
|
||||
source: sync.source || (cacheSource ? 'client_cache' : ''),
|
||||
loading: false,
|
||||
stale: _isStale,
|
||||
});
|
||||
if (_isStale) _scheduleStaleEmailRefetch();
|
||||
else _clearStaleEmailRefetch();
|
||||
if (cacheable && !cacheSource) {
|
||||
_libCachePut(ck, { emails: state._libEmails.slice(), total: state._libTotal, sync });
|
||||
}
|
||||
@@ -4888,7 +4923,7 @@ export async function _loadEmails({ force = false, useCache = true } = {}) {
|
||||
const accountQS = accountAtStart ? `&account_id=${encodeURIComponent(accountAtStart)}` : '';
|
||||
const attQS = hasAttachmentsAtStart ? '&has_attachments=1' : '';
|
||||
const dateQS = `${dateFromAtStart ? `&date_from=${encodeURIComponent(dateFromAtStart)}` : ''}${dateToAtStart ? `&date_to=${encodeURIComponent(dateToAtStart)}` : ''}`;
|
||||
if (!cached && cacheable && !force) {
|
||||
if (!cached && cacheable && !force && !skipFastPath) {
|
||||
const ctrl = new AbortController();
|
||||
const timer = setTimeout(() => ctrl.abort(), 450);
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user