Feat v0.6.10 viewport foundation (#45)
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 5s
CI/CD / test-sqlite (push) Successful in 3m5s
CI/CD / test-go-pg (push) Successful in 3m14s
CI/CD / build-and-deploy (push) Successful in 1m44s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #45.
This commit is contained in:
2026-04-01 10:42:44 +00:00
committed by xcaliber
parent 617d81e7d4
commit ca3f845c34
19 changed files with 94 additions and 87 deletions

View File

@@ -16,21 +16,14 @@ export function Menu({ items = [], anchor, open, direction = 'down-right', onSel
const selectableItems = items.filter(i => !i.divider && !i.disabled);
// Position relative to anchor
// 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.
// Position relative to anchor (CSS zoom reflows layout correctly)
useEffect(() => {
if (!open || !anchor) return;
const scale = window.sw?.shell?.getScale?.() || 1;
const raw = typeof anchor.getBoundingClientRect === 'function'
const rect = typeof anchor.getBoundingClientRect === 'function'
? anchor.getBoundingClientRect()
: anchor;
const rect = scale !== 1
? { top: raw.top / scale, bottom: raw.bottom / scale, left: raw.left / scale, right: raw.right / scale }
: raw;
const vw = window.innerWidth / scale;
const vh = window.innerHeight / scale;
const vw = window.innerWidth;
const vh = window.innerHeight;
let top, left;
const [vDir, hDir] = direction.split('-');

View File

@@ -123,15 +123,8 @@ export async function boot() {
// Shell — layout utilities (decouples surface code from shell DOM)
const _shell = {
/** CSS transform scale on #surfaceInner (appearance zoom). */
getScale() {
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;
},
/** @deprecated v0.6.10 — CSS zoom handles reflow; no scale correction needed. */
getScale() { return 1; },
/** Topbar — set after dynamic import below */
Topbar: null,
};

View File

@@ -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
*
* ┌─────────────────────────────────┐ ← fixed banner (top, optional)
@@ -22,20 +24,10 @@
const { html } = window;
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' }) {
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`
<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>
</div>
`;

View File

@@ -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
*
* ┌───────────────────────────────┐

View File

@@ -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
*
* Deliberately thin. Error boundaries and

View File

@@ -37,20 +37,11 @@ export function AppearanceSection() {
p.scale = scale;
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');
if (inner) {
if (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 = '';
}
inner.style.zoom = scale !== 100 ? (scale / 100) : '';
}
sw.emit('toast', { message: 'Appearance saved', variant: 'success' });