fix(layout): viewport-filling rApp layout — eliminate body scroll

Make #app a flex column with height:100vh so all rApps fill the viewport
exactly. Wrap module body in .rapp-content flex child. Replace all
hardcoded calc(100vh - Npx) with height:100% across 20+ components.
Remove sticky positioning from subnav/tabbar (now flex items).
Generalize mobile body-flex to all pages (not just canvas-layout).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-31 10:57:09 -07:00
parent 0a21caa5e5
commit 2cbff8925d
22 changed files with 63 additions and 97 deletions

View File

@ -1,5 +1,4 @@
/* CrowdSurf module layout */ /* CrowdSurf module layout */
main { main {
min-height: calc(100vh - 56px);
padding: 0; padding: 0;
} }

View File

@ -856,7 +856,6 @@ class FolkCrowdSurfDashboard extends HTMLElement {
.cs-app { .cs-app {
display: flex; flex-direction: column; height: 100%; display: flex; flex-direction: column; height: 100%;
min-height: calc(100vh - 56px);
background: var(--rs-bg-page); background: var(--rs-bg-page);
color: var(--rs-text-primary); color: var(--rs-text-primary);
} }

View File

@ -1,4 +1 @@
/* Books module layout */ /* Books module layout */
main {
min-height: calc(100vh - 56px);
}

View File

@ -365,7 +365,6 @@ export class FolkBookReader extends HTMLElement {
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: calc(100vh - 52px);
background: var(--rs-bg-page); background: var(--rs-bg-page);
} }
@ -418,7 +417,7 @@ export class FolkBookReader extends HTMLElement {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: calc(100vh - 52px); height: 100%;
gap: 1rem; gap: 1rem;
} }
@ -457,7 +456,7 @@ export class FolkBookReader extends HTMLElement {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: calc(100vh - 52px); height: 100%;
gap: 0.5rem; gap: 0.5rem;
} }
.error h3 { color: var(--rs-error); margin: 0; } .error h3 { color: var(--rs-error); margin: 0; }

View File

@ -1,6 +1,5 @@
/* Cart module layout */ /* Cart module layout */
main { main {
min-height: calc(100vh - 56px);
padding: 0; padding: 0;
} }
@ -20,9 +19,3 @@ main:has(folk-payment-page) .rapp-subnav,
main:has(folk-group-buy-page) .rapp-subnav { main:has(folk-group-buy-page) .rapp-subnav {
display: none; display: none;
} }
@media (max-width: 600px) {
main {
min-height: calc(100vh - 44px);
}
}

View File

@ -1,5 +1,4 @@
/* Choices module layout */ /* Choices module layout */
main { main {
min-height: calc(100vh - 56px);
padding: 0; padding: 0;
} }

View File

@ -72,9 +72,8 @@ routes.get("/", (c) => {
}); });
const RDESIGN_CSS = ` const RDESIGN_CSS = `
/* Layout — prevent page scroll */ /* Layout — fills viewport via shell flex layout */
html:has(.rd-app), html:has(.rd-app) body { overflow:hidden; height:100vh; } .rd-app { display:flex; flex-direction:column; height:100%; overflow:hidden; }
.rd-app { display:flex; flex-direction:column; height:calc(100vh - 56px); overflow:hidden; }
.rd-toolbar { display:flex; align-items:center; gap:8px; padding:6px 12px; border-bottom:1px solid var(--rs-border); background:var(--rs-bg-surface); flex-shrink:0; } .rd-toolbar { display:flex; align-items:center; gap:8px; padding:6px 12px; border-bottom:1px solid var(--rs-border); background:var(--rs-bg-surface); flex-shrink:0; }
.rd-split { display:flex; flex:1; overflow:hidden; } .rd-split { display:flex; flex:1; overflow:hidden; }
.rd-chat { flex:1; display:flex; flex-direction:column; min-width:300px; border-right:1px solid var(--rs-border); background:var(--rs-bg-page); } .rd-chat { flex:1; display:flex; flex-direction:column; min-width:300px; border-right:1px solid var(--rs-border); background:var(--rs-bg-page); }

View File

@ -2206,7 +2206,7 @@ class FolkMapViewer extends HTMLElement {
.room-name { font-size: 15px; font-weight: 600; } .room-name { font-size: 15px; font-weight: 600; }
.map-container { .map-container {
width: 100%; height: calc(100vh - 220px); min-height: 300px; width: 100%; height: 100%; min-height: 300px;
border-radius: 10px; border-radius: 10px;
background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border); background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border);
position: relative; overflow: hidden; position: relative; overflow: hidden;
@ -2219,7 +2219,7 @@ class FolkMapViewer extends HTMLElement {
.map-sidebar { .map-sidebar {
width: 220px; flex-shrink: 0; width: 220px; flex-shrink: 0;
background: var(--rs-glass-bg); border: 1px solid var(--rs-border); border-radius: 10px; background: var(--rs-glass-bg); border: 1px solid var(--rs-border); border-radius: 10px;
padding: 12px; max-height: calc(100vh - 280px); overflow-y: auto; padding: 12px; max-height: 100%; overflow-y: auto;
} }
.sidebar-title { .sidebar-title {
font-size: 11px; font-weight: 600; color: var(--rs-text-secondary); font-size: 11px; font-weight: 600; color: var(--rs-text-secondary);
@ -2279,7 +2279,7 @@ class FolkMapViewer extends HTMLElement {
.mobile-bottom-sheet { display: none; } .mobile-bottom-sheet { display: none; }
@media (max-width: 768px) { @media (max-width: 768px) {
.map-container { height: calc(100vh - 48px); min-height: 250px; max-height: none; border-radius: 0; border: none; } .map-container { height: 100%; min-height: 250px; max-height: none; border-radius: 0; border: none; }
.map-layout { flex-direction: column; } .map-layout { flex-direction: column; }
.map-sidebar { display: none; } .map-sidebar { display: none; }
.controls { display: none; } .controls { display: none; }

View File

@ -61,10 +61,10 @@ class FolkJitsiRoom extends HTMLElement {
private render() { private render() {
this.shadow.innerHTML = ` this.shadow.innerHTML = `
<style> <style>
:host { display: block; width: 100%; min-height: 70vh; } :host { display: flex; flex-direction: column; width: 100%; height: 100%; }
.jitsi-container { width: 100%; height: 70vh; border-radius: 12px; overflow: hidden; background: #000; } .jitsi-container { width: 100%; flex: 1; min-height: 0; border-radius: 12px; overflow: hidden; background: #000; }
.jitsi-container iframe { border: none !important; } .jitsi-container iframe { border: none !important; }
.loading { display: flex; align-items: center; justify-content: center; height: 70vh; color: var(--rs-text-muted, #888); font-family: system-ui, sans-serif; } .loading { display: flex; align-items: center; justify-content: center; flex: 1; min-height: 0; color: var(--rs-text-muted, #888); font-family: system-ui, sans-serif; }
.director-strip { display: flex; gap: 6px; padding: 10px; background: var(--rs-bg-surface, #1a1a2e); border: 1px solid var(--rs-border, #333); border-radius: 0 0 12px 12px; overflow-x: auto; align-items: center; } .director-strip { display: flex; gap: 6px; padding: 10px; background: var(--rs-bg-surface, #1a1a2e); border: 1px solid var(--rs-border, #333); border-radius: 0 0 12px 12px; overflow-x: auto; align-items: center; }
.director-thumb { position: relative; width: 160px; min-width: 160px; aspect-ratio: 16/9; border-radius: 6px; overflow: hidden; cursor: pointer; border: 2px solid transparent; background: #111; } .director-thumb { position: relative; width: 160px; min-width: 160px; aspect-ratio: 16/9; border-radius: 6px; overflow: hidden; cursor: pointer; border: 2px solid transparent; background: #111; }
.director-thumb.active { border-color: #ef4444; } .director-thumb.active { border-color: #ef4444; }

View File

@ -4,7 +4,7 @@ folk-graph-viewer {
flex-direction: column; flex-direction: column;
min-height: 400px; min-height: 400px;
padding: 20px; padding: 20px;
height: calc(100vh - 60px); height: 100%;
} }
/* Canvas cursor states */ /* Canvas cursor states */

View File

@ -3543,7 +3543,7 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
display: grid; display: grid;
grid-template-columns: 260px 1fr; grid-template-columns: 260px 1fr;
min-height: 400px; min-height: 400px;
height: calc(100vh - 120px); height: 100%;
position: relative; position: relative;
transition: grid-template-columns 0.2s ease; transition: grid-template-columns 0.2s ease;
} }

View File

@ -820,9 +820,10 @@ export class FolkPubsEditor extends HTMLElement {
return `<style> return `<style>
:host { :host {
display: block; display: block;
height: calc(100vh - 140px); height: 100%;
background: var(--rs-bg-page); background: var(--rs-bg-page);
color: var(--rs-text-primary); color: var(--rs-text-primary);
overflow: hidden;
} }
.wizard-layout { .wizard-layout {
@ -1267,7 +1268,7 @@ export class FolkPubsEditor extends HTMLElement {
/* ── Responsive ── */ /* ── Responsive ── */
@media (max-width: 640px) { @media (max-width: 640px) {
:host { height: auto; min-height: calc(100vh - 56px); } :host { height: auto; min-height: 100%; }
.editor-toolbar { gap: 0.5rem; } .editor-toolbar { gap: 0.5rem; }
.toolbar-left { flex-direction: column; gap: 0.375rem; } .toolbar-left { flex-direction: column; gap: 0.375rem; }
.title-input, .author-input { max-width: 100%; flex: 1; } .title-input, .author-input { max-width: 100%; flex: 1; }

View File

@ -1,5 +1,4 @@
/* Pubs module layout */ /* Pubs module layout */
main { main {
min-height: calc(100vh - 56px);
padding: 0; padding: 0;
} }

View File

@ -1,7 +1,7 @@
/* rSchedule Automation Canvas — n8n-style workflow builder */ /* rSchedule Automation Canvas — n8n-style workflow builder */
folk-automation-canvas { folk-automation-canvas {
display: block; display: block;
height: calc(100vh - 60px); height: 100%;
} }
.ac-root { .ac-root {

View File

@ -1,7 +1,7 @@
/* rSocials Campaign Planner — n8n-style flow canvas */ /* rSocials Campaign Planner — n8n-style flow canvas */
folk-campaign-planner { folk-campaign-planner {
display: block; display: block;
height: calc(100vh - 60px); height: 100%;
} }
.cp-root { .cp-root {
@ -675,7 +675,7 @@ folk-campaign-planner {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
max-height: calc(100vh - 180px); max-height: 100%;
} }
.cp-pv-card { .cp-pv-card {

View File

@ -1,7 +1,7 @@
/* rSocials Campaign Workflow — n8n-style workflow builder */ /* rSocials Campaign Workflow — n8n-style workflow builder */
folk-campaign-workflow { folk-campaign-workflow {
display: block; display: block;
height: calc(100vh - 92px); height: 100%;
} }
.cw-root { .cw-root {
@ -578,7 +578,7 @@ folk-campaign-workflow {
/* ── Mobile ── */ /* ── Mobile ── */
@media (max-width: 768px) { @media (max-width: 768px) {
folk-campaign-workflow { folk-campaign-workflow {
height: calc(100vh - 56px); height: 100%;
} }
.cw-palette { .cw-palette {

View File

@ -18,7 +18,7 @@
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
color: var(--splat-text); color: var(--splat-text);
min-height: calc(100vh - 56px); min-height: 100%;
background: var(--splat-bg); background: var(--splat-bg);
} }
@ -427,7 +427,7 @@ button.splat-viewer__save {
.splat-viewer { .splat-viewer {
position: relative; position: relative;
width: 100%; width: 100%;
height: calc(100vh - 56px); height: 100%;
background: var(--splat-bg); background: var(--splat-bg);
overflow: hidden; overflow: hidden;
} }

View File

@ -1,6 +1,5 @@
/* Swag module layout */ /* Swag module layout */
main { main {
min-height: calc(100vh - 56px);
padding: 0; padding: 0;
} }

View File

@ -2,6 +2,6 @@
folk-timebank-app { folk-timebank-app {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: calc(100vh - var(--rs-header-height, 56px)); height: 100%;
min-height: 0; min-height: 0;
} }

View File

@ -429,7 +429,7 @@ class FolkTripsPlanner extends HTMLElement {
if (this._mapInstance) { try { this._mapInstance.remove(); } catch {} this._mapInstance = null; this._mapMarkers = []; } if (this._mapInstance) { try { this._mapInstance.remove(); } catch {} this._mapInstance = null; this._mapMarkers = []; }
this.shadow.innerHTML = ` this.shadow.innerHTML = `
<style> <style>
:host { display: flex; flex-direction: column; height: calc(100vh - 112px); font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); overflow: hidden; } :host { display: flex; flex-direction: column; height: 100%; font-family: system-ui, -apple-system, sans-serif; color: var(--rs-text-primary); overflow: hidden; }
* { box-sizing: border-box; } * { box-sizing: border-box; }
.rtrips-shell { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; } .rtrips-shell { display: flex; flex-direction: column; height: 100%; min-height: 0; overflow: hidden; }
.rtrips-scroll { flex: 1; overflow-y: auto; min-height: 0; padding-bottom: 8px; } .rtrips-scroll { flex: 1; overflow-y: auto; min-height: 0; padding-bottom: 8px; }

View File

@ -309,10 +309,7 @@ export function renderShell(opts: ShellOptions): string {
<button class="rstack-header__history-btn" id="history-btn" title="History"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></button> <button class="rstack-header__history-btn" id="history-btn" title="History"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg></button>
<rstack-history-panel></rstack-history-panel> <rstack-history-panel></rstack-history-panel>
</div> </div>
<div class="rstack-header__dropdown-wrap">
<button class="rstack-header__settings-btn" id="settings-btn" title="Space Settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button> <button class="rstack-header__settings-btn" id="settings-btn" title="Space Settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
<rstack-space-settings space="${escapeAttr(spaceSlug)}" module-id="${escapeAttr(moduleId)}"></rstack-space-settings>
</div>
</div> </div>
<div class="rstack-header__center"> <div class="rstack-header__center">
<rstack-mi></rstack-mi> <rstack-mi></rstack-mi>
@ -346,7 +343,7 @@ export function renderShell(opts: ShellOptions): string {
<main id="app"${moduleId === "rspace" ? ' class="canvas-layout"' : ''}> <main id="app"${moduleId === "rspace" ? ' class="canvas-layout"' : ''}>
${renderModuleSubNav(moduleId, spaceSlug, visibleModules, opts.isSubdomain ?? IS_PRODUCTION)} ${renderModuleSubNav(moduleId, spaceSlug, visibleModules, opts.isSubdomain ?? IS_PRODUCTION)}
${opts.tabs ? renderTabBar(opts.tabs, opts.activeTab, opts.tabBasePath || ((opts.isSubdomain ?? IS_PRODUCTION) ? `/${escapeAttr(moduleId)}` : `/${escapeAttr(spaceSlug)}/${escapeAttr(moduleId)}`)) : ''} ${opts.tabs ? renderTabBar(opts.tabs, opts.activeTab, opts.tabBasePath || ((opts.isSubdomain ?? IS_PRODUCTION) ? `/${escapeAttr(moduleId)}` : `/${escapeAttr(spaceSlug)}/${escapeAttr(moduleId)}`)) : ''}
${body} <div class="rapp-content">${body}</div>
</main> </main>
${renderWelcomeOverlay()} ${renderWelcomeOverlay()}
@ -422,15 +419,15 @@ export function renderShell(opts: ShellOptions): string {
const minimized = document.body.classList.toggle('rspace-headers-minimized'); const minimized = document.body.classList.toggle('rspace-headers-minimized');
localStorage.setItem('rspace_headers_minimized', minimized ? '1' : '0'); localStorage.setItem('rspace_headers_minimized', minimized ? '1' : '0');
}); });
// ── Settings panel toggle (close history first) ── // ── Settings modal (close history first) ──
document.getElementById('settings-btn')?.addEventListener('click', () => { document.getElementById('settings-btn')?.addEventListener('click', () => {
document.querySelector('rstack-history-panel')?.close(); document.querySelector('rstack-history-panel')?.close();
document.querySelector('rstack-space-settings')?.toggle(); const sw = document.querySelector('rstack-space-switcher');
if (sw) sw.openSettingsModal('${spaceSlug}', '${spaceName || spaceSlug}');
}); });
// ── History panel toggle (close settings first) ── // ── History panel toggle ──
document.getElementById('history-btn')?.addEventListener('click', () => { document.getElementById('history-btn')?.addEventListener('click', () => {
document.querySelector('rstack-space-settings')?.close();
document.querySelector('rstack-history-panel')?.toggle(); document.querySelector('rstack-history-panel')?.toggle();
}); });
@ -1105,9 +1102,6 @@ export function renderShell(opts: ShellOptions): string {
console.log('[shell] layer-switch:', moduleId, 'layerId:', layerId, 'tabCache:', !!tabCache, 'layers:', layers.length); console.log('[shell] layer-switch:', moduleId, 'layerId:', layerId, 'tabCache:', !!tabCache, 'layers:', layers.length);
currentModuleId = moduleId; currentModuleId = moduleId;
saveTabs(); saveTabs();
// Update settings panel to show config for the newly active module
const sp = document.querySelector('rstack-space-settings');
if (sp) sp.setAttribute('module-id', moduleId);
if (tabCache) { if (tabCache) {
const switchId = moduleId; // capture for staleness check const switchId = moduleId; // capture for staleness check
tabCache.switchTo(moduleId).then(ok => { tabCache.switchTo(moduleId).then(ok => {
@ -2039,15 +2033,12 @@ const SUBNAV_CSS = `
border-bottom: 1px solid var(--rs-border); border-bottom: 1px solid var(--rs-border);
background: var(--rs-bg-surface); background: var(--rs-bg-surface);
scrollbar-width: none; scrollbar-width: none;
position: sticky; flex-shrink: 0;
top: 93px;
z-index: 100; z-index: 100;
} }
.rapp-subnav::-webkit-scrollbar { display: none; } .rapp-subnav::-webkit-scrollbar { display: none; }
@media (max-width: 640px) { @media (max-width: 640px) {
.rapp-subnav { .rapp-subnav {
position: relative;
top: auto;
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
gap: 0.25rem; gap: 0.25rem;
} }
@ -2134,20 +2125,15 @@ const TABBAR_CSS = `
border-bottom: 1px solid var(--rs-border); border-bottom: 1px solid var(--rs-border);
background: var(--rs-bg-surface); background: var(--rs-bg-surface);
scrollbar-width: none; scrollbar-width: none;
position: sticky; flex-shrink: 0;
top: 92px;
z-index: 100; z-index: 100;
} }
.rapp-tabbar::-webkit-scrollbar { display: none; } .rapp-tabbar::-webkit-scrollbar { display: none; }
.rapp-subnav + .rapp-tabbar { top: 130px; }
@media (max-width: 640px) { @media (max-width: 640px) {
.rapp-tabbar { .rapp-tabbar {
position: relative;
top: auto;
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
gap: 0.25rem; gap: 0.25rem;
} }
.rapp-subnav + .rapp-tabbar { top: auto; }
} }
`; `;

View File

@ -78,9 +78,6 @@ body.rspace-banner-visible .rstack-tab-row {
body.rspace-banner-visible #app { body.rspace-banner-visible #app {
padding-top: 128px; /* 36px banner + 92px normal */ padding-top: 128px; /* 36px banner + 92px normal */
} }
body.rspace-banner-visible .rapp-subnav {
top: 129px;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.rspace-banner { .rspace-banner {
font-size: 0.75rem; font-size: 0.75rem;
@ -92,7 +89,6 @@ body.rspace-banner-visible .rapp-subnav {
body.rspace-banner-visible .rstack-header { top: 0; } body.rspace-banner-visible .rstack-header { top: 0; }
body.rspace-banner-visible .rstack-tab-row { top: 0; } body.rspace-banner-visible .rstack-tab-row { top: 0; }
body.rspace-banner-visible #app { padding-top: 0; } body.rspace-banner-visible #app { padding-top: 0; }
body.rspace-banner-visible .rapp-subnav { top: auto; }
.rspace-banner { position: sticky; } .rspace-banner { position: sticky; }
} }
@ -229,14 +225,30 @@ body.rspace-banner-visible .rapp-subnav {
#app { #app {
padding-top: 92px; /* Below fixed header (56px) + tab row (36px) */ padding-top: 92px; /* Below fixed header (56px) + tab row (36px) */
min-height: 100vh; height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
} }
/* When canvas module is active, make it fill the viewport */ /* .rapp-content fills below subnav/tabbar — scrollable for landing pages, app views fill exactly */
#app.canvas-layout { .rapp-content {
padding-top: 92px; flex: 1;
height: 100vh; min-height: 0;
overflow: hidden; overflow: auto;
position: relative;
display: flex;
flex-direction: column;
}
/* When a single child (app view component), fill the viewport */
.rapp-content > :only-child {
flex: 1;
min-height: 0;
}
/* Tab panes: active pane fills, hidden ones don't participate in flex */
.rapp-content > .rspace-tab-pane--active {
flex: 1;
min-height: 0;
} }
/* ── Standalone mode (no app/space switcher) ── */ /* ── Standalone mode (no app/space switcher) ── */
@ -481,28 +493,23 @@ body.rstack-sidebar-open #toolbar {
position: sticky; position: sticky;
top: 0; top: 0;
} }
#app { /* Mobile: body becomes flex column so #app fills below sticky header/tabs */
padding-top: 0; html, body {
}
/* Canvas on mobile: header/tab-row are sticky (in-flow), so body must
become a flex column to prevent the 100vh #app from overflowing. */
html:has(#app.canvas-layout),
body:has(#app.canvas-layout) {
height: 100dvh; height: 100dvh;
overflow: hidden; overflow: hidden;
overscroll-behavior: none; overscroll-behavior: none;
} }
body:has(#app.canvas-layout) { body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
body:has(#app.canvas-layout) .rstack-header { .rstack-header {
flex-shrink: 0; flex-shrink: 0;
} }
body:has(#app.canvas-layout) .rstack-tab-row { .rstack-tab-row {
flex-shrink: 0; flex-shrink: 0;
} }
#app.canvas-layout { #app {
padding-top: 0; padding-top: 0;
height: auto; height: auto;
flex: 1; flex: 1;
@ -512,7 +519,7 @@ body.rstack-sidebar-open #toolbar {
position: relative; position: relative;
top: 0; top: 0;
width: 100%; width: 100%;
height: calc(100vh - 80px); flex: 1;
min-height: 400px; min-height: 400px;
} }
.rapp-nav { .rapp-nav {
@ -548,16 +555,12 @@ body.rstack-sidebar-open #toolbar {
display: block; display: block;
} }
/* When canvas is active, the tab pane must fill the viewport so #canvas height:100% works */
#app.canvas-layout > .rspace-tab-pane--active {
height: 100%;
}
.rspace-tab-loading { .rspace-tab-loading {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: calc(100vh - 92px); flex: 1;
min-height: 0;
} }
.rspace-tab-spinner { .rspace-tab-spinner {
@ -612,9 +615,6 @@ body.rspace-headers-minimized #app.canvas-layout {
padding-top: 0; padding-top: 0;
} }
body.rspace-headers-minimized .rapp-subnav { body.rspace-headers-minimized .rapp-subnav {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 9997;
height: 28px; height: 28px;
padding: 2px 8px; padding: 2px 8px;
overflow: hidden; overflow: hidden;
@ -645,8 +645,4 @@ body.rspace-headers-minimized .rapp-subnav {
overflow: hidden; overflow: hidden;
transform: none; transform: none;
} }
body.rspace-headers-minimized .rapp-subnav {
position: sticky;
top: 0;
}
} }