From 808b03b71cbd813fa0745fd8ac9e213b471667ef Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sun, 27 Sep 2026 19:52:36 +0000 Subject: [PATCH] Rudder's corner panel scrolls on a phone and keeps the draft in view The panel clipped its content, so at 390x844 the lower controls and the draft could not be reached. The widget element now scrolls inside the panel, the panel height follows the dynamic viewport minus the safe-area insets, the corner host sits inside the safe area, and on a narrow screen Rudder uses less padding and a shorter draft so the draft stays visible below the controls. --- plugin-designs/plugin-studio/plugin-studio.vue | 8 +++++--- plugin-designs/rudder-voice/rudder-voice.vue | 8 +++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugin-designs/plugin-studio/plugin-studio.vue b/plugin-designs/plugin-studio/plugin-studio.vue index 6650e56c7..ef2882cf9 100644 --- a/plugin-designs/plugin-studio/plugin-studio.vue +++ b/plugin-designs/plugin-studio/plugin-studio.vue @@ -1073,7 +1073,7 @@ export default defineComponent({ const h = document.createElement('div') h.setAttribute('data-studio-widgets', corner) const [v, x] = corner.split('-') - h.style.cssText = `position:fixed;${v}:12px;${x}:12px;z-index:2147483647;display:flex;flex-direction:column;align-items:${x === 'right' ? 'flex-end' : 'flex-start'};gap:8px;` + h.style.cssText = `position:fixed;${v}:max(12px,env(safe-area-inset-${v}));${x}:max(12px,env(safe-area-inset-${x}));z-index:2147483647;display:flex;flex-direction:column;align-items:${x === 'right' ? 'flex-end' : 'flex-start'};gap:8px;` document.body.appendChild(h) W.hosts[corner] = h return h @@ -1089,7 +1089,7 @@ export default defineComponent({ box.style.cssText = 'display:flex;flex-direction:column;align-items:inherit;gap:6px;' const panel = document.createElement('div') panel.setAttribute('data-widget-panel', '') - panel.style.cssText = 'display:none;width:min(380px,calc(100vw - 24px));height:min(520px,calc(100vh - 80px));position:relative;background:#fff;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.25);overflow:hidden;' + panel.style.cssText = 'display:none;width:min(380px,calc(100vw - 24px));height:min(520px,calc(100vh - 80px));height:min(520px,calc(100dvh - 80px - env(safe-area-inset-top) - env(safe-area-inset-bottom)));position:relative;background:#fff;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.25);overflow:hidden;' const btn = document.createElement('button') btn.type = 'button' // A catalogue label per widget ("widget:"), not the row key (rega / repo: the button read @@ -1124,7 +1124,9 @@ export default defineComponent({ const el = document.createElement(row.tag) el.setAttribute('profile-config', JSON.stringify(this.paneConfig(row, this.locale))) if (this.base) el.setAttribute('tree-base', this.base.replace(/\/+$/, '')) - el.style.cssText = 'display:block;position:absolute;inset:0;width:100%;height:100%;' + // The panel is a fixed size; on a phone the widget's content is taller than it, so the element scrolls + // inside the panel rather than being clipped by it. + el.style.cssText = 'display:block;position:absolute;inset:0;width:100%;height:100%;overflow-y:auto;overscroll-behavior:contain;' // LIVE MIC, VISIBLE WHEN COLLAPSED: a widget that emits `recording` (Rudder does) turns its corner // button red with a dot while it listens, so closing the panel never hides that the mic is on. el.addEventListener('recording', (e) => { diff --git a/plugin-designs/rudder-voice/rudder-voice.vue b/plugin-designs/rudder-voice/rudder-voice.vue index a5393e36c..6467ba235 100644 --- a/plugin-designs/rudder-voice/rudder-voice.vue +++ b/plugin-designs/rudder-voice/rudder-voice.vue @@ -590,7 +590,7 @@ const statusText = computed(() => ({ .rudder { --bg: #0b0f14; --panel: #111823; --line: #1f2937; --text: #e5e7eb; --muted: #9ca3af; --accent: #38bdf8; --rec: #ef4444; --ok: #22c55e; --warn: #f59e0b; - box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; min-height: 480px; height: 100%; + box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; min-height: max(100%, 480px); padding: 18px; background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; font-size: 15px; } @@ -658,4 +658,10 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1 .debug dl { display: grid; grid-template-columns: 90px 1fr; gap: 6px 10px; margin: 0 0 10px; } .debug dt { color: var(--muted); } .debug dd { margin: 0; overflow-wrap: anywhere; } +/* A phone-sized panel: less padding, and a shorter draft so it stays in view below the controls. */ +@media (max-width: 480px) { + .rudder { min-height: 100%; padding: 12px; gap: 10px; } + .compose { min-height: 200px; } + .draft { min-height: 180px; } +}