v0.4.3: Lock contention overhaul, StoragePool, mobile bottom nav, text scaling

Eliminate all conn_mgr lock holds during network I/O across 14 actor commands
and bi-stream handlers. PostFetch, TcpPunch, PullFromPeer, FetchEngagement,
ResolveAddress, AnchorProbe use brief locks for data gathering only. WormLookup,
ContentSearch, WormQuery use connection snapshots for lock-free cascade fan-out.
RelayIntroduce extracts forwarding data under brief lock, does I/O outside.
BlobRequest, PostFetchRequest, ManifestRefresh use Arc clones instead of conn_mgr
lock. ConnectionActor hoists shared Arcs (storage, blob_store, endpoint) for
lock-free access. ResolveAddress adds 5s per-query timeout (was unbounded).

Initial exchange failure now aborts mesh upgrade (was silently continuing with
broken connection). connect_to_peer/connect_to_anchor use consistent 15s timeout.
Rebalance connects outside the lock via pending_connects pattern.

StoragePool: 8 concurrent SQLite connections in WAL mode replace single
Mutex<Storage>. Reads run fully parallel; writes serialize at SQLite level only.
PRAGMA busy_timeout=5000 for graceful write contention.

Mobile bottom nav bar (<=768px) with icon tabs. Text sizes: XS/S/M/L/XL
(75%/100%/125%/150%/200%), default M. localStorage persistence for instant
restore. Toast repositioned above mobile nav.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Reimers 2026-03-22 21:35:38 -04:00
parent f17535d61d
commit 43adbbdf7d
15 changed files with 1546 additions and 618 deletions

View file

@ -3007,22 +3007,20 @@ $('#circle-profiles-toggle').addEventListener('click', () => {
// --- Notifications popover ---
// Text size toggle
const TEXT_SIZE_SCALES = { small: '100%', normal: '150%', large: '200%' };
// Apply text size immediately (default Normal = 150%)
document.documentElement.style.fontSize = '150%';
(async () => {
const saved = await invoke('get_setting', { key: 'text_size' }).catch(() => null) || 'normal';
document.documentElement.style.fontSize = TEXT_SIZE_SCALES[saved] || '150%';
document.querySelectorAll('.text-size-opt').forEach(b => {
b.classList.toggle('active', b.dataset.size === saved);
});
})();
const TEXT_SIZE_SCALES = { xsmall: '75%', small: '100%', normal: '125%', large: '150%', xlarge: '200%' };
// Apply text size immediately from localStorage cache (no async wait)
const _cachedTextSize = localStorage.getItem('text_size') || 'normal';
document.documentElement.style.fontSize = TEXT_SIZE_SCALES[_cachedTextSize] || '125%';
document.querySelectorAll('.text-size-opt').forEach(b => {
b.classList.toggle('active', b.dataset.size === _cachedTextSize);
});
document.querySelectorAll('.text-size-opt').forEach(btn => {
btn.addEventListener('click', async () => {
const size = btn.dataset.size;
document.documentElement.style.fontSize = TEXT_SIZE_SCALES[size] || '';
document.querySelectorAll('.text-size-opt').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
localStorage.setItem('text_size', size);
await invoke('set_setting', { key: 'text_size', value: size }).catch(() => {});
toast('Text size updated');
});

View file

@ -30,11 +30,11 @@
<main>
<nav id="tabs">
<button class="tab" data-tab="feed">Feed</button>
<button class="tab" data-tab="myposts">My Posts</button>
<button class="tab" data-tab="people">People</button>
<button class="tab" data-tab="messages">Messages</button>
<button class="tab" data-tab="settings">Settings</button>
<button class="tab" data-tab="feed"><span class="tab-icon">&#x1f4f0;</span><span class="tab-label">Feed</span></button>
<button class="tab" data-tab="myposts"><span class="tab-icon">&#x270d;</span><span class="tab-label">My Posts</span></button>
<button class="tab" data-tab="people"><span class="tab-icon">&#x1f465;</span><span class="tab-label">People</span></button>
<button class="tab" data-tab="messages"><span class="tab-icon">&#x1f4ac;</span><span class="tab-label">Messages</span></button>
<button class="tab" data-tab="settings"><span class="tab-icon">&#x2699;</span><span class="tab-label">Settings</span></button>
</nav>
<!-- Welcome (shown on startup) -->
@ -229,9 +229,11 @@
<div class="section-card">
<h3>Text Size</h3>
<div id="text-size-btns" style="display:flex;gap:0.4rem;margin-top:0.3rem">
<button class="notif-opt text-size-opt" data-size="small">Small</button>
<button class="notif-opt text-size-opt active" data-size="normal">Normal</button>
<button class="notif-opt text-size-opt" data-size="large">Large</button>
<button class="notif-opt text-size-opt" data-size="xsmall">XS</button>
<button class="notif-opt text-size-opt" data-size="small">S</button>
<button class="notif-opt text-size-opt active" data-size="normal">M</button>
<button class="notif-opt text-size-opt" data-size="large">L</button>
<button class="notif-opt text-size-opt" data-size="xlarge">XL</button>
</div>
</div>

View file

@ -81,13 +81,25 @@ header h1 { font-size: clamp(1.4rem, 2.5vw, 2rem); color: #7fdbca; margin: 0; }
.compose-right { display: flex; align-items: center; gap: 0.5rem; flex-shrink: 0; }
.compose-left { display: flex; flex-direction: column; gap: 0.25rem; min-width: 0; }
/* Tabs */
/* Tabs — desktop (top bar) */
#tabs { display: flex; gap: 0; margin-bottom: 1rem; border-bottom: 1px solid #333; }
.tab { background: none; border: none; color: #99a; padding: 0.5rem 0.6rem; cursor: pointer; border-bottom: 2px solid transparent; font-size: 0.82rem; transition: color 0.15s, border-color 0.15s; position: relative; flex: 1; text-align: center; white-space: nowrap; }
.tab:hover { color: #ccd; }
.tab.active { color: #7fdbca; border-bottom-color: #7fdbca; }
.tab-icon { display: none; }
.tab-badge { display: inline-flex; align-items: center; justify-content: center; background: #0f3460; color: #7fdbca; font-size: 0.6rem; min-width: 1.1rem; height: 1.1rem; border-radius: 0.55rem; padding: 0 0.3rem; margin-left: 0.25rem; font-family: system-ui, sans-serif; vertical-align: middle; }
/* Tabs — mobile/tablet (bottom nav bar) */
@media (max-width: 768px) {
#tabs { position: fixed; bottom: 0; left: 0; right: 0; z-index: 900; background: #0a0a1a; border-bottom: none; border-top: 1px solid #333; margin-bottom: 0; padding: 0; padding-bottom: env(safe-area-inset-bottom, 0); }
.tab { flex-direction: column; align-items: center; padding: 0.4rem 0.2rem 0.3rem; border-bottom: none; border-top: 2px solid transparent; font-size: 0.6rem; gap: 0.15rem; display: flex; }
.tab.active { border-bottom: none; border-top-color: #7fdbca; }
.tab-icon { display: block; font-size: 1.2rem; line-height: 1; }
.tab-badge { position: absolute; top: 0.1rem; right: 0.2rem; margin-left: 0; font-size: 0.5rem; min-width: 0.9rem; height: 0.9rem; border-radius: 0.45rem; }
main { padding-bottom: 4rem; }
.toast { bottom: 4.5rem; }
}
/* Views / tab content transitions */
.view { display: none; animation: viewFadeIn 0.2s ease-out; }
.view.active { display: block; }