Feat v0.6.10 viewport foundation
Some checks failed
CI/CD / test-frontend (pull_request) Has been cancelled
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / detect-changes (pull_request) Has been cancelled
Some checks failed
CI/CD / test-frontend (pull_request) Has been cancelled
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / detect-changes (pull_request) Has been cancelled
Single layout model: body → shell → surface. CSS zoom replaces transform: scale() — no more getBoundingClientRect hacks. Extension surfaces use height: 100% instead of 100vh. All viewport-height declarations use 100dvh fallbacks for mobile browsers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,6 +2,47 @@
|
|||||||
|
|
||||||
All notable changes to Armature are documented here.
|
All notable changes to Armature are documented here.
|
||||||
|
|
||||||
|
## v0.6.10 — Viewport Foundation
|
||||||
|
|
||||||
|
Single layout model. Every surface renders inside one containment chain:
|
||||||
|
`body → shell → surface`. No dual systems. No transform hacks.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **CSS `zoom` replaces `transform: scale()`**: UI scale (80%–175%) now
|
||||||
|
uses CSS `zoom` on `#surfaceInner` instead of `transform: scale()`.
|
||||||
|
`zoom` reflows layout correctly — `getBoundingClientRect()` returns
|
||||||
|
accurate values, eliminating the scale-correction hack in `menu.js`.
|
||||||
|
Supported in all evergreen browsers (Firefox 126+, June 2024).
|
||||||
|
- **Single layout root**: `<body>` in `base.html` is the authoritative
|
||||||
|
flex column layout. `.sw-shell` CSS demoted from viewport-level
|
||||||
|
container (`height: 100vh`) to fill-parent (`height: 100%`).
|
||||||
|
Safe-area insets moved from `.sw-shell` to `<body>`.
|
||||||
|
- **Banner single source of truth**: Template banners measure their own
|
||||||
|
height via inline `<script>` and set `--banner-top-height` /
|
||||||
|
`--banner-bottom-height` CSS variables. Removed `--banner-h: 28px`
|
||||||
|
fixed variable. `ShellBanner` Preact component's `useEffect`
|
||||||
|
measurement removed (dead code — no surface imports `AppShell`).
|
||||||
|
- **`sw-shell__banner` position**: Changed from `position: fixed` to
|
||||||
|
`position: static` — template banners are in-flow elements.
|
||||||
|
- **`sw-shell__body` padding**: Removed `padding-top/bottom` for
|
||||||
|
banner offsets — unnecessary with in-flow banners.
|
||||||
|
- **Extension surfaces `100vh` → `100%`**: `chat-app`, `chat-loading`,
|
||||||
|
`surface-dashboard` now use `height: 100%` to inherit from the
|
||||||
|
extension mount container (like Notes). Fixes overflow behind banners.
|
||||||
|
- **`100vh` → `100dvh` fallbacks**: All viewport-height declarations
|
||||||
|
use `height: 100vh; height: 100dvh;` pattern for correct behavior on
|
||||||
|
mobile browsers. Affects: `base.html`, `sw-login.css`,
|
||||||
|
`workflow.html`, `workflow-landing.html`, `primitives.css`,
|
||||||
|
`git-board/css/main.css`.
|
||||||
|
- **`sw.shell.getScale()` deprecated**: Returns `1` always — CSS `zoom`
|
||||||
|
handles layout reflow without manual correction.
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- `src/js/sw/shell/app-shell.js`, `app.js`, `surface-viewport.js` —
|
||||||
|
no surface imports these. Layout root is `<body>` in `base.html`.
|
||||||
|
|
||||||
## v0.6.9 — Session Lifetime Config
|
## v0.6.9 — Session Lifetime Config
|
||||||
|
|
||||||
Admin-configurable session durations, "keep me logged in" opt-in, and
|
Admin-configurable session durations, "keep me logged in" opt-in, and
|
||||||
|
|||||||
@@ -45,19 +45,11 @@
|
|||||||
Shipped. Admin-configurable TTLs, "keep me logged in" checkbox, idle timeout,
|
Shipped. Admin-configurable TTLs, "keep me logged in" checkbox, idle timeout,
|
||||||
config-driven `generateTokens()`, 7 new tests. See CHANGELOG.md for details.
|
config-driven `generateTokens()`, 7 new tests. See CHANGELOG.md for details.
|
||||||
|
|
||||||
### v0.6.10 — Viewport Foundation
|
### v0.6.10 — Viewport Foundation ✅
|
||||||
|
|
||||||
Establish a single, correct layout model. Every surface must render inside one
|
Shipped. Single layout model: `body → shell → surface`. CSS `zoom` replaces
|
||||||
containment chain: `body → shell → surface`. No dual systems. No transform hacks.
|
`transform: scale()`. `100dvh` fallbacks everywhere. Extension surfaces use
|
||||||
|
`100%` instead of `100vh`. Dead shell layout code deprecated. See CHANGELOG.md.
|
||||||
| Step | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| Unify shell layout | Remove the inline `<style>` block from `base.html` that creates a parallel column layout. Make `base.html` render a single `<div class="sw-shell">` wrapper. Banners, announcements, footer, and surface all live inside the Preact shell's flex column OR the template column — pick one, kill the other. **Recommendation**: keep the Go template column (it works without JS) and delete `sw-shell.css`'s parallel layout. The Preact `AppShell` becomes a component that renders *inside* `#surfaceInner`, not a viewport-level container. |
|
|
||||||
| Banner single source of truth | One banner rendering path. Template banners (`base.html:72-107`) set `--banner-top-height` and `--banner-bottom-height` via a `<script>` that measures after DOMContentLoaded. Remove `ShellBanner`'s JS measurement. Surfaces use `calc(100vh - var(--banner-top-height) - var(--banner-bottom-height))` or pure flex containment — not both. |
|
|
||||||
| Replace transform-scale with CSS `zoom` | CSS `zoom` is now supported in all evergreen browsers (including Firefox 126+, June 2024). `zoom` actually reflows layout — `getBoundingClientRect` returns correct values, scroll containers work, pointer events are accurate. Remove all `transform: scale()` code from `base.html`, `appearance.js`, `menu.js` scale-correction hacks. Apply `zoom` on `#surfaceInner`. The `sw.shell.getScale()` API becomes a no-op returning 1 (or reads `getComputedStyle().zoom`). |
|
|
||||||
| Fix `100vh` → `100dvh` | Replace all `height: 100vh` with `height: 100dvh` (fallback: `height: 100vh; height: 100dvh;`). Affects: `sw-shell.css`, `sw-login.css`, `chat/css/main.css`. |
|
|
||||||
| Chat `100vh` → `100%` | `.chat-app` and `.chat-loading` use `height: 100%` like Notes. They inherit their height from the extension mount container which is already correctly sized. |
|
|
||||||
| Validation | Manual test matrix: banner on/off × scale 80%/100%/150% × mobile/desktop × light/dark. Every surface (admin, settings, team-admin, docs, welcome, notes, chat). No overflow, no gaps, menus positioned correctly. |
|
|
||||||
|
|
||||||
### v0.6.11 — CSS Deduplication
|
### v0.6.11 — CSS Deduplication
|
||||||
|
|
||||||
@@ -124,7 +116,7 @@ Make the UI machine-auditable so Claude Code can run an automated survey.
|
|||||||
```
|
```
|
||||||
v0.6.9 Session Lifetime Config ✅ SHIPPED
|
v0.6.9 Session Lifetime Config ✅ SHIPPED
|
||||||
↓
|
↓
|
||||||
v0.6.10 Viewport Foundation ← Everything depends on correct containment
|
v0.6.10 Viewport Foundation ✅ SHIPPED
|
||||||
↓
|
↓
|
||||||
v0.6.11 CSS Deduplication ← Can't audit styling until there's one source of truth
|
v0.6.11 CSS Deduplication ← Can't audit styling until there's one source of truth
|
||||||
↓
|
↓
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Armature — Roadmap
|
# Armature — Roadmap
|
||||||
|
|
||||||
## Current: v0.6.7 — Native mTLS
|
## Current: v0.6.10 — Viewport Foundation
|
||||||
|
|
||||||
Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
|
Self-hosted extensible platform. Auth, identity, packages, Starlark sandbox,
|
||||||
storage, realtime, and ops are kernel primitives. Everything else is an extension.
|
storage, realtime, and ops are kernel primitives. Everything else is an extension.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
.chat-app {
|
.chat-app {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-body {
|
.chat-body {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
.surface-dashboard {
|
.surface-dashboard {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -258,6 +258,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 680px;
|
max-width: 680px;
|
||||||
max-height: calc(100vh - 80px);
|
max-height: calc(100vh - 80px);
|
||||||
|
max-height: calc(100dvh - 80px);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
box-shadow: 0 8px 32px rgba(0,0,0,0.3);
|
||||||
|
|||||||
@@ -27,12 +27,9 @@
|
|||||||
<link rel="stylesheet" href="{{.BasePath}}/surfaces/{{.Surface}}/css/main.css?v={{.Version}}">
|
<link rel="stylesheet" href="{{.BasePath}}/surfaces/{{.Surface}}/css/main.css?v={{.Version}}">
|
||||||
{{end}}
|
{{end}}
|
||||||
<style>
|
<style>
|
||||||
:root {
|
body { margin: 0; background: var(--bg); color: var(--text); display: flex; flex-direction: column; height: 100vh; height: 100dvh; overflow: hidden; padding: env(safe-area-inset-top, 0) env(safe-area-inset-right, 0) env(safe-area-inset-bottom, 0) env(safe-area-inset-left, 0); }
|
||||||
--banner-h: 28px;
|
|
||||||
}
|
|
||||||
body { margin: 0; background: var(--bg); color: var(--text); display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
|
|
||||||
.surface { flex: 1; min-height: 0; overflow: hidden; }
|
.surface { flex: 1; min-height: 0; overflow: hidden; }
|
||||||
.surface-inner { width: 100%; height: 100%; transform-origin: top left; }
|
.surface-inner { width: 100%; height: 100%; }
|
||||||
.banner { flex-shrink: 0; }
|
.banner { flex-shrink: 0; }
|
||||||
</style>
|
</style>
|
||||||
<meta name="theme-color" content="#0e0e10" id="metaThemeColor">
|
<meta name="theme-color" content="#0e0e10" id="metaThemeColor">
|
||||||
@@ -52,7 +49,7 @@
|
|||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
})();
|
})();
|
||||||
// Early appearance — apply scale from localStorage on all surfaces.
|
// Early appearance — apply scale from localStorage on all surfaces.
|
||||||
// Scale uses transform on .surface-inner so shell stays fixed, content fills viewport.
|
// CSS zoom reflows layout correctly (no getBoundingClientRect hacks needed).
|
||||||
(function() {
|
(function() {
|
||||||
try {
|
try {
|
||||||
var p = JSON.parse(localStorage.getItem('sb-appearance') || '{}');
|
var p = JSON.parse(localStorage.getItem('sb-appearance') || '{}');
|
||||||
@@ -60,7 +57,7 @@
|
|||||||
var s = p.scale / 100;
|
var s = p.scale / 100;
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var el = document.getElementById('surfaceInner');
|
var el = document.getElementById('surfaceInner');
|
||||||
if (el) { el.style.transform = 'scale('+s+')'; el.style.transformOrigin = 'top left'; el.style.width = (100/s)+'%'; el.style.height = (100/s)+'%'; }
|
if (el) { el.style.zoom = s; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
@@ -69,9 +66,12 @@
|
|||||||
</head>
|
</head>
|
||||||
<body data-surface="{{.Surface}}" data-base-path="{{.BasePath}}">
|
<body data-surface="{{.Surface}}" data-base-path="{{.BasePath}}">
|
||||||
{{if .Banner.Visible}}
|
{{if .Banner.Visible}}
|
||||||
<div class="banner banner-top active" style="background:{{.Banner.Background}};color:{{.Banner.Color}};height:var(--banner-h);line-height:var(--banner-h);text-align:center;font-size:12px;font-weight:600;overflow:hidden;">
|
<div class="banner banner-top active" style="background:{{.Banner.Background}};color:{{.Banner.Color}};padding:6px 12px;text-align:center;font-size:12px;font-weight:600;line-height:1.3;">
|
||||||
{{.Banner.Text}}
|
{{.Banner.Text}}
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){ var b=document.querySelector('.banner-top.active'); if(b) document.documentElement.style.setProperty('--banner-top-height',b.offsetHeight+'px'); })();
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Message.Visible}}
|
{{if .Message.Visible}}
|
||||||
@@ -101,9 +101,12 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .Banner.Visible}}
|
{{if .Banner.Visible}}
|
||||||
<div class="banner banner-bottom active" style="background:{{.Banner.Background}};color:{{.Banner.Color}};height:var(--banner-h);line-height:var(--banner-h);text-align:center;font-size:12px;font-weight:600;overflow:hidden;">
|
<div class="banner banner-bottom active" style="background:{{.Banner.Background}};color:{{.Banner.Color}};padding:6px 12px;text-align:center;font-size:12px;font-weight:600;line-height:1.3;">
|
||||||
{{.Banner.Text}}
|
{{.Banner.Text}}
|
||||||
</div>
|
</div>
|
||||||
|
<script>
|
||||||
|
(function(){ var b=document.querySelector('.banner-bottom.active'); if(b) document.documentElement.style.setProperty('--banner-bottom-height',b.offsetHeight+'px'); })();
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<script nonce="{{.CSPNonce}}">
|
<script nonce="{{.CSPNonce}}">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body { font-family: var(--font); background: var(--bg); color: var(--text); }
|
body { font-family: var(--font); background: var(--bg); color: var(--text); }
|
||||||
|
|
||||||
.wf-shell { display: flex; flex-direction: column; height: 100vh; }
|
.wf-shell { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
|
||||||
|
|
||||||
.wf-header {
|
.wf-header {
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
|||||||
@@ -491,6 +491,7 @@ select option { background: var(--bg-surface); color: var(--text); }
|
|||||||
width: calc(100vw - 32px);
|
width: calc(100vw - 32px);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: calc(100vh - 64px);
|
max-height: calc(100vh - 64px);
|
||||||
|
max-height: calc(100dvh - 64px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toast: full width, centered */
|
/* Toast: full width, centered */
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
──────────────────────────────────────────── */
|
──────────────────────────────────────────── */
|
||||||
|
|
||||||
/* ── Layout ──────────────────────────────────── */
|
/* ── Layout ──────────────────────────────────── */
|
||||||
.login-shell { display: flex; flex-direction: column; height: 100vh; }
|
.login-shell { display: flex; flex-direction: column; height: 100vh; height: 100dvh; }
|
||||||
.login-main { display: flex; flex: 1; min-height: 0; }
|
.login-main { display: flex; flex: 1; min-height: 0; }
|
||||||
|
|
||||||
/* ── Hero Panel (left) ─────────────────────── */
|
/* ── Hero Panel (left) ─────────────────────── */
|
||||||
|
|||||||
@@ -3,26 +3,23 @@
|
|||||||
fixed banners, announcement, surface, and footer.
|
fixed banners, announcement, surface, and footer.
|
||||||
──────────────────────────────────────────── */
|
──────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* v0.6.10: .sw-shell is no longer a viewport-level container.
|
||||||
|
The single layout root is <body> in base.html.
|
||||||
|
.sw-shell fills its parent if any surface uses it. */
|
||||||
.sw-shell {
|
.sw-shell {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
/* Safe-area insets for notched devices */
|
|
||||||
padding-top: env(safe-area-inset-top, 0);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
|
||||||
padding-left: env(safe-area-inset-left, 0);
|
|
||||||
padding-right: env(safe-area-inset-right, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Banner (fixed top / bottom — identical component) ── */
|
/* ── Banner (fixed top / bottom — identical component) ── */
|
||||||
|
|
||||||
|
/* v0.6.10: banners are in-flow (template renders them), not fixed. */
|
||||||
.sw-shell__banner {
|
.sw-shell__banner {
|
||||||
position: fixed;
|
position: static;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 900;
|
z-index: 900;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -60,13 +57,12 @@
|
|||||||
|
|
||||||
/* ── Shell body — insets for fixed banners ─── */
|
/* ── Shell body — insets for fixed banners ─── */
|
||||||
|
|
||||||
|
/* v0.6.10: template banners are in-flow — no padding offset needed. */
|
||||||
.sw-shell__body {
|
.sw-shell__body {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-top: var(--banner-top-height, 0px);
|
|
||||||
padding-bottom: var(--banner-bottom-height, 0px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Announcement bar ─────────────────────── */
|
/* ── Announcement bar ─────────────────────── */
|
||||||
|
|||||||
@@ -16,21 +16,14 @@ export function Menu({ items = [], anchor, open, direction = 'down-right', onSel
|
|||||||
|
|
||||||
const selectableItems = items.filter(i => !i.divider && !i.disabled);
|
const selectableItems = items.filter(i => !i.divider && !i.disabled);
|
||||||
|
|
||||||
// Position relative to anchor
|
// Position relative to anchor (CSS zoom reflows layout correctly)
|
||||||
// When the surface uses CSS transform: scale(), getBoundingClientRect()
|
|
||||||
// returns scaled coordinates but position:fixed uses viewport coordinates.
|
|
||||||
// Dividing by the scale factor converts back to true viewport pixels.
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open || !anchor) return;
|
if (!open || !anchor) return;
|
||||||
const scale = window.sw?.shell?.getScale?.() || 1;
|
const rect = typeof anchor.getBoundingClientRect === 'function'
|
||||||
const raw = typeof anchor.getBoundingClientRect === 'function'
|
|
||||||
? anchor.getBoundingClientRect()
|
? anchor.getBoundingClientRect()
|
||||||
: anchor;
|
: anchor;
|
||||||
const rect = scale !== 1
|
const vw = window.innerWidth;
|
||||||
? { top: raw.top / scale, bottom: raw.bottom / scale, left: raw.left / scale, right: raw.right / scale }
|
const vh = window.innerHeight;
|
||||||
: raw;
|
|
||||||
const vw = window.innerWidth / scale;
|
|
||||||
const vh = window.innerHeight / scale;
|
|
||||||
|
|
||||||
let top, left;
|
let top, left;
|
||||||
const [vDir, hDir] = direction.split('-');
|
const [vDir, hDir] = direction.split('-');
|
||||||
|
|||||||
@@ -123,15 +123,8 @@ export async function boot() {
|
|||||||
|
|
||||||
// Shell — layout utilities (decouples surface code from shell DOM)
|
// Shell — layout utilities (decouples surface code from shell DOM)
|
||||||
const _shell = {
|
const _shell = {
|
||||||
/** CSS transform scale on #surfaceInner (appearance zoom). */
|
/** @deprecated v0.6.10 — CSS zoom handles reflow; no scale correction needed. */
|
||||||
getScale() {
|
getScale() { return 1; },
|
||||||
const el = document.getElementById('surfaceInner');
|
|
||||||
if (!el) return 1;
|
|
||||||
const t = getComputedStyle(el).transform;
|
|
||||||
if (!t || t === 'none') return 1;
|
|
||||||
const m = t.match(/matrix\(([^,]+)/);
|
|
||||||
return m ? parseFloat(m[1]) || 1 : 1;
|
|
||||||
},
|
|
||||||
/** Topbar — set after dynamic import below */
|
/** Topbar — set after dynamic import below */
|
||||||
Topbar: null,
|
Topbar: null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/**
|
/** @deprecated v0.6.10 — No surface imports AppShell. Layout root is <body> in base.html.
|
||||||
|
* Kept for reference; will be removed in a future version.
|
||||||
|
*
|
||||||
* AppShell — Layer 1 layout scaffold
|
* AppShell — Layer 1 layout scaffold
|
||||||
*
|
*
|
||||||
* ┌─────────────────────────────────┐ ← fixed banner (top, optional)
|
* ┌─────────────────────────────────┐ ← fixed banner (top, optional)
|
||||||
@@ -22,20 +24,10 @@
|
|||||||
const { html } = window;
|
const { html } = window;
|
||||||
const { useState, useEffect, useRef, useReducer } = hooks;
|
const { useState, useEffect, useRef, useReducer } = hooks;
|
||||||
|
|
||||||
|
/** @deprecated v0.6.10 — Banner height is measured by template scripts in base.html. */
|
||||||
function ShellBanner({ position, text, variant = 'info' }) {
|
function ShellBanner({ position, text, variant = 'info' }) {
|
||||||
const ref = useRef(null);
|
|
||||||
const prop = position === 'top' ? '--banner-top-height' : '--banner-bottom-height';
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (ref.current) {
|
|
||||||
const h = ref.current.offsetHeight + 'px';
|
|
||||||
document.documentElement.style.setProperty(prop, h);
|
|
||||||
}
|
|
||||||
return () => document.documentElement.style.setProperty(prop, '0px');
|
|
||||||
}, [text, prop]);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="sw-shell__banner sw-shell__banner--${position} sw-shell__banner--${variant}" ref=${ref}>
|
<div class="sw-shell__banner sw-shell__banner--${position} sw-shell__banner--${variant}">
|
||||||
<span class="sw-shell__banner-text">${text}</span>
|
<span class="sw-shell__banner-text">${text}</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
/**
|
/** @deprecated v0.6.10 — No surface imports App. Layout root is <body> in base.html.
|
||||||
|
* Kept for reference; will be removed in a future version.
|
||||||
|
*
|
||||||
* App — root component composing the full shell tree
|
* App — root component composing the full shell tree
|
||||||
*
|
*
|
||||||
* ┌───────────────────────────────┐
|
* ┌───────────────────────────────┐
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/**
|
/** @deprecated v0.6.10 — No surface imports SurfaceViewport. Layout root is <body> in base.html.
|
||||||
|
*
|
||||||
* SurfaceViewport — container where the active surface renders
|
* SurfaceViewport — container where the active surface renders
|
||||||
*
|
*
|
||||||
* Deliberately thin. Error boundaries and
|
* Deliberately thin. Error boundaries and
|
||||||
|
|||||||
@@ -37,20 +37,11 @@ export function AppearanceSection() {
|
|||||||
p.scale = scale;
|
p.scale = scale;
|
||||||
localStorage.setItem('sb-appearance', JSON.stringify(p));
|
localStorage.setItem('sb-appearance', JSON.stringify(p));
|
||||||
|
|
||||||
// Apply scale to content area only (not shell/nav)
|
// Apply scale to content area only (not shell/nav).
|
||||||
|
// CSS zoom reflows layout correctly — no getBoundingClientRect hacks needed.
|
||||||
const inner = document.getElementById('surfaceInner');
|
const inner = document.getElementById('surfaceInner');
|
||||||
if (inner) {
|
if (inner) {
|
||||||
if (scale !== 100) {
|
inner.style.zoom = scale !== 100 ? (scale / 100) : '';
|
||||||
const s = scale / 100;
|
|
||||||
inner.style.transform = `scale(${s})`;
|
|
||||||
inner.style.transformOrigin = 'top left';
|
|
||||||
inner.style.width = (100 / s) + '%';
|
|
||||||
inner.style.height = (100 / s) + '%';
|
|
||||||
} else {
|
|
||||||
inner.style.transform = '';
|
|
||||||
inner.style.width = '';
|
|
||||||
inner.style.height = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sw.emit('toast', { message: 'Appearance saved', variant: 'success' });
|
sw.emit('toast', { message: 'Appearance saved', variant: 'success' });
|
||||||
|
|||||||
Reference in New Issue
Block a user