From 40bd19b6cf98a041e53fe35750655a633e309325 Mon Sep 17 00:00:00 2001 From: "n.siegfried" Date: Mon, 3 Aug 2026 21:27:53 -0700 Subject: [PATCH] Stop the embedded creator collapsing to a 300x150 box; serve code network-first MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Creator rendered as a tiny double-scrolling square in the WP tab. My fault, and the mechanism matters more than the symptom: I had moved the iframe's sizing (width:100%, border:0, min-height) out of its inline style attribute and into work-package-suite-styles.css. The service worker cached the HTML and the stylesheet as INDEPENDENT entries, cache-first — so a browser could hold the new HTML together with the old CSS. With the inline sizing gone and the new rule absent, the iframe fell back to the HTML default 300x150 box and the whole tool collapsed. Moving self-contained markup into a separately-cached file created that window; nothing about the layout itself was wrong. Three layers so it cannot recur: - The iframe's width/border/min-height are inline again, on purpose, with a comment saying why. An iframe with no intrinsic size has a catastrophic failure mode, so its sizing must not depend on another file being in step. - applyEmbedLayout() now sets the fill height and width as INLINE styles via sizeWPFrame(). Inline beats any stylesheet, including a stale cached one, so the class is a refinement rather than a requirement. - sw.js: HTML/CSS/JS are now fetched NETWORK-FIRST with the cache as offline fallback; images/icons/manifest stay stale-while-revalidate. These files reference each other, so a page must never run against a stale sibling — this same staleness had already masked two other fixes during development. Cache bumped to v4. Verified: at 2560x1440 the tool spans the window with a single scrollbar; with work-package-suite-styles.css removed entirely (strictly worse than stale) the frame still measures 1469x662 instead of 300x150, and re-running the layout pass keeps it there; 12 checks across sop -> wp -> dashboard -> sop confirm body.embed-full, the content-area class, the fill class and the inline height are all cleared on the way out, so the wizard never ends up unscrollable. Co-Authored-By: Claude Opus 5 --- html/sw.js | 37 ++++++++++++++++++++++++++++++---- html/work-package-suite-app.js | 33 ++++++++++++++++++++++++------ html/work-package-suite.html | 8 +++++++- 3 files changed, 67 insertions(+), 11 deletions(-) diff --git a/html/sw.js b/html/sw.js index fc74eb8..0a2074d 100644 --- a/html/sw.js +++ b/html/sw.js @@ -5,15 +5,16 @@ worker only caches the static app shell so the pages open without a network. Strategy: - • /api/* and non-GET → never touched (pass straight to the network; offline + • /api/* and non-GET → never touched (pass straight to the network; offline reads fall back to the app's localStorage cache, writes queue in the outbox). - • same-origin GET → stale-while-revalidate (instant from cache, refreshed - in the background when online). + • HTML / CSS / JS → network-first, cache as fallback. These reference each + other, so a page must never run against a stale sibling. + • images / icons / manifest → stale-while-revalidate (instant from cache). */ 'use strict'; // Bumped when the shell file list changes, so clients fetch the new assets // instead of serving a half-old shell from the previous cache. -const CACHE = 'wp-suite-shell-v3'; +const CACHE = 'wp-suite-shell-v4'; const SHELL = [ '/', '/index.html', '/work-package-suite.html', '/wp-creation-index.html', '/field.html', '/login.html', '/admin.html', @@ -43,6 +44,17 @@ self.addEventListener('activate', (e) => { ); }); +// Code (HTML / CSS / JS) is fetched NETWORK-FIRST, falling back to the cache when +// offline. Everything else (images, icons, the manifest) stays cache-first, which is +// where offline speed actually comes from. +// +// Why not cache-first for code: these files reference each other, and the cache +// stores them as independent entries. Cache-first served whichever copy of each file +// happened to be stored, so a browser could run new HTML against old CSS — which is +// exactly how the embedded creator once collapsed to a 300x150 iframe. A page must +// only ever run against the stylesheet and scripts it shipped with. +const CODE_RE = /\.(html|css|js)$|\/$/i; + self.addEventListener('fetch', (e) => { const req = e.request; if (req.method !== 'GET') return; // outbox owns writes @@ -50,6 +62,23 @@ self.addEventListener('fetch', (e) => { if (url.origin !== self.location.origin) return; // third-party: default if (url.pathname.startsWith('/api/')) return; // never cache the API + const isCode = CODE_RE.test(url.pathname); + + if (isCode) { + e.respondWith( + fetch(req) + .then((res) => { + if (res && res.ok) { + const copy = res.clone(); + caches.open(CACHE).then((c) => c.put(req, copy)); + } + return res; + }) + .catch(() => caches.match(req)) // offline → last good copy + ); + return; + } + e.respondWith( caches.match(req).then((cached) => { const network = fetch(req) diff --git a/html/work-package-suite-app.js b/html/work-package-suite-app.js index 3812046..e11ecce 100644 --- a/html/work-package-suite-app.js +++ b/html/work-package-suite-app.js @@ -494,17 +494,38 @@ function applyEmbedLayout(on){ if(area) area.classList.toggle('embed-full', !!on); if(frame) frame.classList.toggle('fill', !!on); document.body.classList.toggle('embed-full', !!on); - if(on) measureChrome(); + sizeWPFrame(on); } -// Height of the app bar + tab strip, so the iframe can be exactly the rest. -function measureChrome(){ +// Size the frame with INLINE styles, not only CSS classes. Inline wins over any +// stylesheet — including a stale cached one — so the creator can't collapse to the +// 300x150 default iframe box if the CSS and the HTML are ever out of step. +function sizeWPFrame(on){ + const frame = document.getElementById('wp-frame'); + if(!frame) return; + frame.style.width = '100%'; + frame.style.border = '0'; + if(on){ + const h = viewportMinusChrome(); + document.documentElement.style.setProperty('--wp-chrome-h', (window.innerHeight - h) + 'px'); + frame.style.height = h + 'px'; + frame.style.minHeight = '0'; + } else { + frame.style.height = ''; + frame.style.minHeight = 'calc(100vh - 200px)'; + } +} + +// Whatever is left of the window below the app bar + tab strip. +function viewportMinusChrome(){ const hdr = document.querySelector('.header'); const nav = document.querySelector('.main-nav'); - const h = (hdr ? hdr.offsetHeight : 48) + (nav ? nav.offsetHeight : 48); - document.documentElement.style.setProperty('--wp-chrome-h', h + 'px'); + const chrome = (hdr ? hdr.offsetHeight : 48) + (nav ? nav.offsetHeight : 48); + return Math.max(320, window.innerHeight - chrome); } -window.addEventListener('resize', () => { if(document.body.classList.contains('embed-full')) measureChrome(); }, {passive:true}); +window.addEventListener('resize', () => { + if(document.body.classList.contains('embed-full')) sizeWPFrame(true); +}, {passive:true}); // Show the gate or the embedded Work Package Creator depending on SOP status. // wantDash=true opens the creator straight to the dashboard view. diff --git a/html/work-package-suite.html b/html/work-package-suite.html index 5ec603d..fb46063 100644 --- a/html/work-package-suite.html +++ b/html/work-package-suite.html @@ -372,7 +372,13 @@ - + +