Skip to content

Commit 8ed870e

Browse files
JSKittyclaude
andcommitted
fix: UX polish + legacy-account heal for missing encryption flags
- Mini-profile placeholder avatar: size mismatch (72 in a 64 wrapper) clipped the icon top-left of the circle. Match container size. - PIN entry: Backspace on an empty cell now also wipes the digit it lands on, not just moves focus. Left/Right arrows navigate between cells. - @everyone mentions + admin badges on first chat open were missing because the admin list arrives async from get_mls_group_members and messages render before it lands. Re-render rows from now-known admins in refreshGroupMemberCount when the chat is open. - Legacy accounts predating the encryption_enabled / security_type settings rows would brick on the multi-account branch — the new resolver treats missing-both as "not encrypted" and routes to the direct-login path which then fails to parse the ciphertext pkey. Self-heal: detect non-nsec1 pkey on boot, backfill encryption_enabled = "true" and security_type = "pin" (pre-Feb-2026 only had PIN). One- time fix; modern accounts go through commit_account_setup which writes both flags atomically. - Login screen: subtext + credits hid too eagerly (300px / 600px) given the 650px default window. Bumped to 325px / 625px so neither folds at default size. When credits hide, reclaim 16px bottom padding on #login-start so the Login button doesn't kiss the viewport edge. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 9fbbf8b commit 8ed870e

4 files changed

Lines changed: 70 additions & 12 deletions

File tree

src-tauri/src/commands/encryption.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,40 @@ pub fn get_encryption_and_key<R: Runtime>(handle: AppHandle<R>) -> Result<BootEn
156156
// delegate to `state::resolve_encryption_enabled_from_db` so this is
157157
// robust against either being called independently.
158158
crate::state::init_encryption_enabled();
159-
let enabled = vector_core::state::is_encryption_enabled_fast();
159+
let mut enabled = vector_core::state::is_encryption_enabled_fast();
160+
161+
// Self-heal: legacy accounts predating the `encryption_enabled` row
162+
// store an encrypted pkey with no flag set. A pkey that doesn't begin
163+
// with `nsec1` is ciphertext — backfill the flags so the boot routes
164+
// to the PIN screen. Default to "pin" because passwords didn't exist
165+
// before the security_type row was introduced.
166+
let mut healed_security_type: Option<String> = None;
167+
if !enabled {
168+
if let Ok(Some(stored)) = vector_core::db::get_pkey() {
169+
if !stored.starts_with("nsec1") {
170+
let _ = vector_core::db::set_sql_setting(
171+
"encryption_enabled".to_string(),
172+
"true".to_string(),
173+
);
174+
if crate::db::get_sql_setting("security_type".to_string())
175+
.ok().flatten().is_none()
176+
{
177+
let _ = vector_core::db::set_sql_setting(
178+
"security_type".to_string(),
179+
"pin".to_string(),
180+
);
181+
healed_security_type = Some("pin".to_string());
182+
}
183+
vector_core::state::set_encryption_enabled(true);
184+
enabled = true;
185+
}
186+
}
187+
}
160188

161189
let security_type = if enabled {
162-
crate::db::get_sql_setting("security_type".to_string())
163-
.ok().flatten().unwrap_or_else(|| "pin".to_string())
190+
healed_security_type
191+
.or_else(|| crate::db::get_sql_setting("security_type".to_string()).ok().flatten())
192+
.unwrap_or_else(|| "pin".to_string())
164193
} else {
165194
"pin".to_string()
166195
};

src/js/render/mini-profile.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,11 @@ function _populateMiniProfile(popup, npub, profile) {
137137
img.alt = '';
138138
img.draggable = false;
139139
img.onerror = () => {
140-
img.replaceWith(createPlaceholderAvatar(false, 72));
140+
img.replaceWith(createPlaceholderAvatar(false, 64));
141141
};
142142
avatarWrap.appendChild(img);
143143
} else {
144-
avatarWrap.appendChild(createPlaceholderAvatar(false, 72));
144+
avatarWrap.appendChild(createPlaceholderAvatar(false, 64));
145145
}
146146
popup.appendChild(avatarWrap);
147147

src/main.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,6 +1557,22 @@ async function refreshGroupMemberCount(groupId) {
15571557
if (strOpenChat === groupId) {
15581558
// Update the chat header subtext (respects typing indicators)
15591559
updateChatHeaderSubtext(chat);
1560+
1561+
// Admin list arrives async — re-render rows from now-known
1562+
// admins so admin badges + @everyone treatment apply.
1563+
const admins = chat.metadata?.admins;
1564+
if (Array.isArray(admins) && admins.length > 0 && domChatMessages) {
1565+
const adminSet = new Set(admins);
1566+
const rows = domChatMessages.querySelectorAll('.dmsg');
1567+
rows.forEach(row => {
1568+
const m = row._dmsgMsg;
1569+
if (!m || m.mine) return;
1570+
const senderNpub = m.npub || '';
1571+
if (!adminSet.has(senderNpub)) return;
1572+
const profile = getProfile(senderNpub);
1573+
row.replaceWith(renderMessage(m, profile, m.id));
1574+
});
1575+
}
15601576
}
15611577
} catch (e) {
15621578
console.warn('Failed to refresh group member count for', groupId, e);
@@ -4366,9 +4382,21 @@ function openEncryptionFlow(fUnlock = false, securityType = 'pin') {
43664382
const newTitle = fUnlock ? DECRYPTION_PROMPT : (strPinLast.length > 0 ? RE_ENTER_PROMPT : INITIAL_ENCRYPTION_PROMPT);
43674383
updateStatusMessage(newTitle);
43684384
}
4369-
input.value = '';
4370-
strPinCurrent[nIndex] = '-';
4385+
if (input.value !== '') {
4386+
input.value = '';
4387+
strPinCurrent[nIndex] = '-';
4388+
} else if (nIndex > 0) {
4389+
const prev = arrPinDOMs[nIndex - 1];
4390+
prev.value = '';
4391+
strPinCurrent[nIndex - 1] = '-';
4392+
prev.focus();
4393+
}
4394+
} else if (event.key === 'ArrowLeft') {
4395+
event.preventDefault();
43714396
if (nIndex > 0) arrPinDOMs[nIndex - 1].focus();
4397+
} else if (event.key === 'ArrowRight') {
4398+
event.preventDefault();
4399+
if (nIndex + 1 < arrPinDOMs.length) arrPinDOMs[nIndex + 1].focus();
43724400
} else if (event.key.length === 1 && !event.key.match(/^[0-9]$/)) {
43734401
event.preventDefault();
43744402
}

src/styles.css

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5723,7 +5723,7 @@ select:disabled:hover {
57235723
font-size: clamp(10px, 2.4vh, 16px);
57245724
}
57255725

5726-
@media (max-height: 300px) {
5726+
@media (max-height: 325px) {
57275727
.login-logo {
57285728
display: none;
57295729
}
@@ -5755,14 +5755,15 @@ select:disabled:hover {
57555755
}
57565756
}
57575757

5758-
@media (max-height: 600px) {
5758+
@media (max-height: 625px) {
57595759
.login-credits {
57605760
display: none;
57615761
}
57625762

5763-
#login-start,
5764-
.login-encrypt-container {
5765-
padding-bottom: 20px;
5763+
/* Credits provided the bottom breathing room; reclaim it on the
5764+
button stack so Login doesn't kiss the viewport edge. */
5765+
#login-start {
5766+
padding-bottom: 16px;
57665767
}
57675768
}
57685769

0 commit comments

Comments
 (0)