server+web: member apps get border-box sizing and the full pane (#1413)
* server+web: member apps get border-box sizing and the full pane Two sizing fixes for sandboxed member apps: - The /app-sandbox document now carries the same box-sizing:border-box reset as the parent app (style.css). App CSS is written against that environment, so under the default content-box a common idiom like `height:100%; padding:16px` made the app's root 32px taller than the frame, cutting off its bottom edge (buttons ~16px below the fold). - The app-pane's 16px inset (apps.js framework CSS) no longer applies to sandbox mounts: .sandbox-pane on the pane zeroes it, so a member app's surface runs edge-to-edge and owns its own padding. renderApp clears the tag when a built-in app reuses the pane, so the padding drop never leaks onto non-sandbox mounts. Co-Authored-By: Claude <noreply@anthropic.com> * web: native-embed keeps only the bar insets around sandbox mounts The Mac/iOS webview pads #app-pane by 16px + the native bar insets so an app's UI starts clear of the floating bars — right for DOM-rendered apps, but around a sandboxed member-app iframe the 16px reads as bare strips above and below the surface. Sandbox mounts now keep only the insets. Co-Authored-By: Claude <noreply@anthropic.com> ---------
Co-authored-by: Claude <noreply@anthropic.com>
5 files changed,
+31
−1
changelog/2026-08-04-member-apps-fill-the-pane.md+5 −0memberapps.go+5 −1web/static/apps.js+4 −0web/static/sandbox.js+5 −0web/static/style.css+12 −0
diff --git a/changelog/2026-08-04-member-apps-fill-the-pane.md b/changelog/2026-08-04-member-apps-fill-the-pane.md| new file mode 100644 |
| index 0000000..5b0b6b0 |
| --- /dev/null |
| +++ b/changelog/2026-08-04-member-apps-fill-the-pane.md |
| @@ -0,0 +1,5 @@ |
| +🤖 Member-built apps now fill their whole pane. Apps you've asked a bot to |
| +build used to sit inside a small margin, and some had their bottom edge — |
| +buttons included — cut off just below the window. Both are fixed: an app's |
| +surface now runs edge-to-edge and everything fits on screen, with no changes |
| +needed to the apps themselves. |
diff --git a/memberapps.go b/memberapps.go| index e135b4c..174cd6c 100644 |
| --- a/memberapps.go |
| +++ b/memberapps.go |
| @@ -295,9 +295,13 @@ func (s *Server) serveAppSandbox(w http.ResponseWriter, r *http.Request) { |
| // embedded in — NOT --bg (the page/sidebar colour). applyTheme forwards |
| // --panel/--ink and color-scheme (so the light-dark() resolves right); with no |
| // theme yet it falls back to transparent, never the black UA canvas. |
| + // box-sizing matches the parent app's global reset (style.css) — app CSS is |
| + // written against that environment, and under content-box a common idiom |
| + // like `height:100%; padding:16px` overflows the frame by its padding. |
| w.Write([]byte(`<!doctype html><html><head><meta charset="utf-8">` + |
| `<meta name="referrer" content="no-referrer">` + |
| - `<style>html,body{margin:0;padding:0;height:100%;background:var(--panel);color:var(--ink)}</style></head><body>` + |
| + `<style>*,*::before,*::after{box-sizing:border-box}` + |
| + `html,body{margin:0;padding:0;height:100%;background:var(--panel);color:var(--ink)}</style></head><body>` + |
| `<script type="module">` + string(runtime) + |
| `</script></body></html>`)) |
| } |
diff --git a/web/static/apps.js b/web/static/apps.js| index dbd0d53..ab29616 100644 |
| --- a/web/static/apps.js |
| +++ b/web/static/apps.js |
| @@ -2545,6 +2545,10 @@ export function renderApp(threadId, appName, el) { |
| const app = appByName(appName); |
| if (!app || !app.render || !el) return; |
| runCleanups(threadId, app.name); |
| + // A member-app mount tags the pane .sandbox-pane (sandbox.js), which now |
| + // strips the pane's padding; the tag must not survive onto a built-in app |
| + // reusing the same pane. The sandbox render re-adds it every time. |
| + if (!app.memberApp) el.classList.remove("sandbox-pane"); |
| if (!app.incremental) el.textContent = ""; |
| // A mini-app's render must NEVER take down the host. This surface can be the |
| // whole thread, and it's reached on boot (reopening your last thread) — an |
diff --git a/web/static/sandbox.js b/web/static/sandbox.js| index 3409057..c5e4cfe 100644 |
| --- a/web/static/sandbox.js |
| +++ b/web/static/sandbox.js |
| @@ -251,6 +251,11 @@ function injectSandboxStyles() { |
| st.id = "sandbox-style"; |
| st.textContent = ` |
| .sandbox-pane { display: flex; flex-direction: column; height: 100%; } |
| +/* Sandboxed apps own their full surface edge-to-edge — drop the app-pane's |
| + 16px inset (apps.js), which is meant for built-in apps rendering DOM |
| + directly into the pane. The app adds its own padding inside the frame. |
| + (render() puts .sandbox-pane on the pane element itself.) */ |
| +.thread-view.app-mode .app-pane.sandbox-pane { padding: 0; gap: 0; } |
| .sandbox-note { padding: 8px 12px; font-size: 13px; color: var(--muted); border-bottom: 1px solid var(--border); } |
| .sandbox-note.err { color: var(--danger); } |
| .sandbox-frame { flex: 1 1 auto; width: 100%; border: 0; display: block; } |
diff --git a/web/static/style.css b/web/static/style.css| index dc69fa7..ae62725 100644 |
| --- a/web/static/style.css |
| +++ b/web/static/style.css |
| @@ -2865,6 +2865,18 @@ html.native .thread-head { |
| flex: none; |
| height: calc(16px + var(--native-bottom-inset, 0px)); |
| } |
| +/* A sandboxed member app (.sandbox-pane — the pane itself is tagged) is an |
| + iframe surface that owns its own padding, so the 16px breathing room above |
| + reads as a bare strip around it. Keep only the native-bar insets: the app |
| + still starts clear of the floating bars, but runs edge-to-edge otherwise. */ |
| +.native-embed #interface-pane.sandbox-pane, |
| +.native-embed #app-pane.sandbox-pane { |
| + padding-top: var(--native-top-inset, 0px); |
| +} |
| +.native-embed #interface-pane.sandbox-pane::after, |
| +.native-embed #app-pane.sandbox-pane::after { |
| + height: var(--native-bottom-inset, 0px); |
| +} |
| /* Command-host embed (embed.mode === "command"): the page the native Mac shell |
| keeps beside each thread purely to run slash commands — everything except a |