Skip to content

Commit be678bc

Browse files
vveerrggclaude
andcommitted
fix(ios): Safari extension popup sizing for iPad/iPhone
- iPad popover: fix viewport width (device-width→380px) and add min-height for Safari auto-sizer. Content, QR code, and tab bar all visible. Uses CSS media query + JS innerWidth>500 detection. - iPhone sheet: native half-sheet/full-sheet behavior preserved. - Hide tab bar and locked footer when virtual keyboard opens (visualViewport resize listener). - Add shrink-to-fit=no to viewport meta tag. - Add id="locked-footer" for keyboard-hide targeting. - Toggle switches: use material-style toggles for frame-protection and sync settings (matching existing toggle pattern). - Security page: add encryption status banner, auto-close master password accordion after setting password. - Update QA tablet screenshot sizes in ui-target-map.json. - Add .build/ to .gitignore (Xcode DerivedData). Fixes: W3C WebExtensions #692, Apple Forums 670733/698319/697155 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 41c850f commit be678bc

5 files changed

Lines changed: 75 additions & 11 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ docker/
1010
.DS_Store
1111
**/.DS_Store
1212

13-
# Xcode user data
13+
# Xcode user data and build artifacts
1414
*.xcuserstate
1515
xcuserdata/
16+
.build/
1617

1718
# Distribution builds and zips
1819
distros/

QA-AUTOMATION/ui-target-map.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"cellular_bars": 4
3030
},
3131
"appstore_sizes": {
32-
"tablet-11": { "width": 1668, "height": 2388 }
32+
"tablet-12.9": { "width": 2048, "height": 2732 },
33+
"tablet-13": { "width": 2064, "height": 2752 }
3334
},
3435
"screenshots_sequence": [
3536
{

src/security/security.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,16 @@ <h3 class="subsection-header">Remove Master Password</h3>
331331
</div>
332332
</details>
333333

334+
<!-- Encryption status (shown when master password is active) -->
335+
<div id="encryption-status" class="flex items-center gap-2 mt-2" style="display:none;padding:12px 16px;border-radius:4px;background:#1a2e1a;border:1px solid #49483e;">
336+
<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
337+
<path d="M16 12V8a4 4 0 10-8 0v4" stroke="#a6e22e" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path>
338+
<rect x="5" y="12" width="14" height="10" rx="2" stroke="#a6e22e" stroke-width="1.5"></rect>
339+
<circle cx="12" cy="17" r="1.5" fill="#a6e22e"></circle>
340+
</svg>
341+
<span class="text-sm font-bold" style="color:#a6e22e;">Master password is active — keys are encrypted at rest.</span>
342+
</div>
343+
334344
</div><!-- end unlocked-view -->
335345
</body>
336346
</html>

src/security/security.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ function render() {
115115
const rmErr = $('remove-error');
116116
if (rmErr) { rmErr.textContent = state.removeError; rmErr.style.display = state.removeError ? 'block' : 'none'; }
117117

118+
// Encryption status banner
119+
const encryptionStatus = $('encryption-status');
120+
if (encryptionStatus) encryptionStatus.style.display = state.hasPassword ? 'flex' : 'none';
121+
118122
// Auto-lock section
119123
const autolockDisabled = $('autolock-disabled-msg');
120124
const autolockControls = $('autolock-controls');
@@ -181,6 +185,9 @@ async function handleSetPassword() {
181185
state.hasPassword = true;
182186
state.newPassword = '';
183187
state.confirmPassword = '';
188+
// Close the master password accordion
189+
const mp = document.getElementById('master-password');
190+
if (mp && mp.open) mp.open = false;
184191
showPageSuccess('Master password set. Your keys are now encrypted at rest.');
185192
} else {
186193
state.securityError = (result && result.error) || 'Failed to set password.';

src/sidepanel.html

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html>
33
<head>
44
<meta charset="UTF-8" />
5-
<meta name="viewport" content="width=device-width,initial-scale=1" />
5+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no" />
66
<link rel="stylesheet" href="options.build.css" />
77
<script defer src="sidepanel.build.js"></script>
88
<style>
@@ -157,6 +157,15 @@
157157
background-color: #a6e22e;
158158
}
159159
.hidden { display: none !important; }
160+
/* iOS Safari extension popup sizing.
161+
iPad: min-height added via JS for popover auto-sizer.
162+
iPhone: native half-sheet / full-sheet behavior.
163+
See: W3C WebExtensions #692, Apple Forums 670733, 698319, 697155. */
164+
@media (pointer: coarse) and (hover: none) {
165+
.sidepanel-layout {
166+
height: 100%;
167+
}
168+
}
160169
@media (prefers-reduced-motion: reduce) {
161170
*, *::before, *::after {
162171
animation-duration: 0.01ms !important;
@@ -168,6 +177,37 @@
168177
</head>
169178

170179
<body>
180+
<script>
181+
// iOS Safari extension popup fixes.
182+
// iPad: popover — fix viewport width + add min-height for auto-sizer.
183+
// iPhone: sheet — set explicit pixel height so layout fills the sheet.
184+
// innerWidth > 500 distinguishes iPad (820px) from iPhone (375-430px).
185+
(function() {
186+
if (!window.matchMedia('(pointer: coarse) and (hover: none)').matches) return;
187+
if (window.innerWidth > 500) {
188+
// iPad: fix viewport scaling
189+
var vp = document.querySelector('meta[name="viewport"]');
190+
if (vp) vp.setAttribute('content', 'width=380,initial-scale=1,shrink-to-fit=no');
191+
// iPad popover needs min-height so Safari auto-sizer allocates enough space
192+
var s = document.createElement('style');
193+
s.textContent = '.sidepanel-layout{min-height:580px}';
194+
document.head.appendChild(s);
195+
}
196+
})();
197+
198+
// iOS: hide tab bar and locked footer when virtual keyboard is open.
199+
(function() {
200+
if (!window.visualViewport) return;
201+
var threshold = 0.75;
202+
window.visualViewport.addEventListener('resize', function() {
203+
var kbOpen = window.visualViewport.height < window.innerHeight * threshold;
204+
var tabs = document.querySelector('.sidepanel-tabs');
205+
var footer = document.getElementById('locked-footer');
206+
if (tabs) tabs.style.display = kbOpen ? 'none' : '';
207+
if (footer) footer.style.display = kbOpen ? 'none' : '';
208+
});
209+
})();
210+
</script>
171211
<div class="sidepanel-layout">
172212
<!-- LOCKED STATE -->
173213
<div id="locked-view" class="hidden" style="display:flex;flex-direction:column;height:100%;">
@@ -221,18 +261,18 @@
221261
</svg>
222262
</button>
223263
</div>
224-
<label class="flex items-center gap-3 cursor-pointer" style="padding:4px 0;">
264+
<label class="flex items-center justify-between cursor-pointer" style="padding:4px 0;">
265+
<span style="color:#f8f8f2;font-size:14px;">Allow Nostr access while locked</span>
225266
<span class="toggle-switch">
226267
<input id="nostr-access-toggle" type="checkbox" />
227268
<span class="toggle-slider"></span>
228269
</span>
229-
<span style="color:#f8f8f2;font-size:14px;">Allow Nostr access while locked</span>
230270
</label>
231271
<p id="nostr-access-status" style="color:#b0b0a8;font-size:0.8rem;margin-top:6px;line-height:1.4;"></p>
232272
</div>
233273
</div>
234274
</div>
235-
<div style="flex-shrink:0;padding:12px 16px;border-top:1px solid #49483e;background:#3e3d32;text-align:center;font-size:0.75rem;">
275+
<div id="locked-footer" style="flex-shrink:0;padding:12px 16px;border-top:1px solid #49483e;background:#3e3d32;text-align:center;font-size:0.75rem;">
236276
<a id="footer-home-link" href="https://nostrkey.com" style="color:#b0b0a8;text-decoration:none;cursor:pointer;">NostrKey.com</a>
237277
<span style="color:#49483e;margin:0 6px;">|</span>
238278
<a id="footer-terms-link" href="https://nostrkey.com/terms.html" style="color:#b0b0a8;text-decoration:none;cursor:pointer;">Ts &amp; Cs</a>
@@ -515,18 +555,23 @@ <h2 class="section-title">Nostr Keys</h2>
515555
<h2 class="section-title">Security</h2>
516556
<button id="settings-security-btn" class="button w-full mb-2">Master Password</button>
517557
<button id="settings-autolock-btn" class="button w-full">Auto-lock Settings</button>
518-
<label class="flex items-center gap-3 cursor-pointer" style="padding:4px 0;margin-top:8px;">
519-
<input id="frame-protection-toggle" type="checkbox"
520-
style="width:18px;height:18px;accent-color:#a6e22e;" />
558+
<label class="flex items-center justify-between cursor-pointer" style="padding:4px 0;margin-top:8px;">
521559
<span style="color:#f8f8f2;font-size:14px;">Block cross-origin frames</span>
560+
<span class="toggle-switch">
561+
<input id="frame-protection-toggle" type="checkbox" />
562+
<span class="toggle-slider"></span>
563+
</span>
522564
</label>
523565
<p id="frame-protection-status" style="color:#b0b0a8;font-size:0.8rem;margin-top:4px;"></p>
524566
</div>
525567
<div class="section-card">
526568
<h2 class="section-title">Sync</h2>
527-
<label class="flex items-center gap-3 cursor-pointer" style="padding:4px 0;">
528-
<input id="sync-toggle" type="checkbox" style="width:18px;height:18px;accent-color:#a6e22e;" />
569+
<label class="flex items-center justify-between cursor-pointer" style="padding:4px 0;">
529570
<span style="color:#f8f8f2;font-size:14px;">Sync across devices</span>
571+
<span class="toggle-switch">
572+
<input id="sync-toggle" type="checkbox" />
573+
<span class="toggle-slider"></span>
574+
</span>
530575
</label>
531576
<p id="sync-status-text" style="color:#b0b0a8;font-size:0.8rem;margin-top:4px;"></p>
532577
</div>

0 commit comments

Comments
 (0)