From cee6fc2f9659ddd05a1dbaf124e677af1546e73d Mon Sep 17 00:00:00 2001 From: Stuart Date: Wed, 14 Oct 2026 01:17:22 +1100 Subject: [PATCH] fix(ui): preserve canonical typography and home restoration --- app.js | 16 +- .../{index-CrZGgSTH.js => index-vWxoLxnc.js} | 2 +- dist/index.html | 7 +- e2e/fixtures/canonicalReference.js | 34 + e2e/fixtures/custom-code-connect-hero.html | 2537 +++++++++++++++++ e2e/stu445-exact-fidelity.spec.js | 194 +- index.html | 5 - 7 files changed, 2770 insertions(+), 25 deletions(-) rename dist/assets/{index-CrZGgSTH.js => index-vWxoLxnc.js} (99%) create mode 100644 e2e/fixtures/canonicalReference.js create mode 100644 e2e/fixtures/custom-code-connect-hero.html diff --git a/app.js b/app.js index 2a520a6..5503148 100644 --- a/app.js +++ b/app.js @@ -9621,7 +9621,7 @@ function initExactShell() { mobile.addEventListener("change", order); } -function setGenerationStageVisible(visible) { +function setGenerationStageVisible(visible, { preservePanel = false } = {}) { const stage = document.getElementById("generation-stage"); if (!stage) return; const hero = document.getElementById("hero"); @@ -9632,6 +9632,9 @@ function setGenerationStageVisible(visible) { stage.classList.add("is-active"); } else { cancelPipelineMorph(); + // Returning to the composer retires the Home restoration marker, even + // after a run has settled. Only navigation preserves a hidden panel. + if (!preservePanel) document.getElementById("main-stage-container")?.classList.remove("visible"); document.body.classList.remove("results-fullscreen", "results-with-sidebar"); stage.classList.remove("is-active"); stage.inert = true; @@ -9705,6 +9708,7 @@ function switchView(view, pushState = true, moveFocus = true) { } else { document.getElementById("results-view")?.classList.remove("visible"); document.getElementById("ready-state")?.classList.remove("hidden"); + document.getElementById("main-stage-container")?.classList.remove("visible"); } } } @@ -9729,10 +9733,16 @@ function switchView(view, pushState = true, moveFocus = true) { }); // Generation overlay only belongs to the home surface. - if (view !== "home") setGenerationStageVisible(false); + if (view !== "home") setGenerationStageVisible(false, { preservePanel: true }); else { const stage = document.getElementById("main-stage-container"); - if (stage && stage.classList.contains("visible")) setGenerationStageVisible(true); + if (stage && stage.classList.contains("visible")) { + // Navigation hides the surface, not the retained result or selected tab. + if (document.getElementById("results-view")?.classList.contains("visible")) { + document.body.classList.add("results-fullscreen", "results-with-sidebar"); + } + setGenerationStageVisible(true); + } } if (pushState) { diff --git a/dist/assets/index-CrZGgSTH.js b/dist/assets/index-vWxoLxnc.js similarity index 99% rename from dist/assets/index-CrZGgSTH.js rename to dist/assets/index-vWxoLxnc.js index a9ff094..3782cd6 100644 --- a/dist/assets/index-CrZGgSTH.js +++ b/dist/assets/index-vWxoLxnc.js @@ -325,4 +325,4 @@ Error: ${g} `}).join(""),i.onclick=s=>{const o=s.target.closest(".artifact-tab");o?.dataset?.artifactId&&Tc(o.dataset.artifactId)},Ud()),e&&e.classList.add("visible")}function Ud(){const t=document.getElementById("bundle-strip");!t||t.dataset.tabKeyboard==="bound"||(t.dataset.tabKeyboard="bound",t.addEventListener("keydown",e=>{if(e.defaultPrevented)return;const n=[document.getElementById("results-summary-tab"),...Array.from(document.querySelectorAll("#artifact-tabs .artifact-tab"))].filter(Boolean);if(!n.length||!n.includes(document.activeElement))return;let i=-1;const r=n.indexOf(document.activeElement);if(e.key==="ArrowRight")i=(r+1)%n.length;else if(e.key==="ArrowLeft")i=(r-1+n.length)%n.length;else if(e.key==="Home")i=0;else if(e.key==="End")i=n.length-1;else return;e.preventDefault();const s=n[i];if(!s)return;if(s.id==="results-summary-tab")fg();else{const a=s.dataset.artifactId;a&&Tc(a)}const o=s.id==="results-summary-tab"?document.getElementById("results-summary-tab"):document.querySelector(`#artifact-tabs .artifact-tab[data-artifact-id="${s.dataset.artifactId}"]`);o&&o.focus()}))}function Pr(){document.body.classList.add("results-fullscreen"),document.body.classList.add("results-with-sidebar");const t=document.getElementById("results-view"),e=document.getElementById("results-code-output"),n=document.getElementById("results-audit-output"),i=document.getElementById("results-summary-detail"),r=document.getElementById("artifact-results-split"),s=Hb(),o=fc();if(i&&(i.setAttribute("role","tabpanel"),i.setAttribute("aria-labelledby","results-summary-tab")),r){r.setAttribute("role","tabpanel");const a=s?.artifacts?.find(l=>l.id===_.selectedArtifactId);r.setAttribute("aria-labelledby",a?`artifact-tab-${a.id}`:"results-summary-tab")}zb(s),Gb(s),e&&(e.textContent=""),e&&(e.replaceChildren(),o.split(` -`).forEach((a,l)=>{const u=document.createElement("span");u.className="code-line",u.innerHTML=lp(a),!Ob()&&_.resultsViewMode==="file"&&(u.classList.add("is-in"),u.style.setProperty("--d",`${l*10}ms`),u.addEventListener("animationend",()=>u.classList.remove("is-in"),{once:!0})),e.appendChild(u)})),n&&(n.innerHTML=Vb(s)),i&&i.classList.toggle("hidden",_.resultsViewMode!=="summary"),r&&r.classList.toggle("hidden",_.resultsViewMode!=="file"),t&&t.classList.add("visible")}function Fr(t,e){const n=document.getElementById("main-stage-container"),i=E0(n),r=document.body.classList.contains("results-fullscreen");Ar(),document.getElementById("pipeline-progress")?.classList.remove("visible"),Wt(!0),_.selectedArtifactId||(_.selectedArtifactId=_i(_.artifactBundle).id),_.resultsViewMode="summary",document.body.classList.add("results-fullscreen");const s=document.getElementById("step3-output");s&&(s.textContent=""),Pr(),vn(),Kb(),r||up(i,n,["#bundle-strip",".summary-heading",".review-summary-text",".summary-spec",".review-score-column",".results-action-bar"].map(o=>document.querySelector(o)),{results:!0})}function Kb(){const t=document.getElementById("btn-feedback-up"),e=document.getElementById("btn-feedback-down"),n=document.getElementById("results-feedback-status");t&&(t.className="feedback-btn",t.setAttribute("aria-pressed","false"),t.disabled=!1,t.removeAttribute("aria-disabled")),e&&(e.className="feedback-btn",e.setAttribute("aria-pressed","false"),e.disabled=!1,e.removeAttribute("aria-disabled")),n&&(n.textContent=""),El=!1}function Tc(t){_.selectedArtifactId=t,_.resultsViewMode="file",Pr()}function fg(){_.resultsViewMode="summary",Pr()}function Yb(){const t=document.getElementById("btn-copy-results"),e=document.getElementById("results-copy-status"),n=fc(),i=(r,s)=>{e&&(e.textContent=r,e.classList.toggle("ok",!!s),e.classList.toggle("fail",!s))};if(!navigator.clipboard?.writeText){i("Copying isn't supported in this browser.",!1);return}navigator.clipboard.writeText(n).then(()=>{if(i("Copied to clipboard",!0),t){t.classList.add("copied");const r=t.querySelector("span");if(r){const s=r.textContent;r.textContent="Copied!",setTimeout(()=>{t.classList.remove("copied"),r.textContent=s},2e3)}}}).catch(()=>{i("Clipboard permission was denied. Select the code to copy it manually.",!1)})}let El=!1;async function Jb(t){const e=document.getElementById("btn-feedback-up"),n=document.getElementById("btn-feedback-down"),i=document.getElementById("results-feedback-status");if(!e||!n||El)return;const r=l=>{i&&(i.textContent="",i.offsetWidth,i.textContent=l)},s=l=>{El=l,[e,n].forEach(u=>{u.disabled=l,u.setAttribute("aria-disabled",String(l))})},o=t==="up"?"thumbsUp":"thumbsDown";s(!0),r("Submitting review feedback…");const a={type:o,code:_.step2Result,input:_.step1Result};try{const l=await Ec(a.type,a.code,a.input);if(!l||l.success!==!0){s(!1),r("Review feedback could not be sent. Tap again to retry.");return}e.classList.toggle("active-up",t==="up"),n.classList.toggle("active-down",t==="down"),e.setAttribute("aria-pressed",String(t==="up")),n.setAttribute("aria-pressed",String(t==="down")),s(!1),r("Review feedback saved.")}catch{s(!1),r("Review feedback could not be sent. Tap again to retry.")}}function Zb(){const t=document.getElementById("error-input-panel");if(t){t.classList.remove("hidden"),t.style.display="flex";const e=document.getElementById("ff-error-paste-input");e&&setTimeout(()=>e.focus(),100)}}function hg(){const t=document.getElementById("error-input-panel");t&&(t.classList.add("hidden"),t.style.display="none")}function Xb(){const t=document.getElementById("shell-stage"),e=document.getElementById("main-stage-container"),n=e?.querySelector(".pipeline-prompt-recap"),i=window.matchMedia("(max-width: 920px)"),r=()=>t?.style.setProperty("--fit",Math.min(1,document.documentElement.clientWidth/1920)),s=()=>{if(!n)return;const o=n.contains(document.activeElement)?document.activeElement:null;i.matches?e.appendChild(n):e.prepend(n),o?.focus({preventScroll:!0})};r(),s(),window.addEventListener("resize",r),i.addEventListener("change",s)}function Wt(t){const e=document.getElementById("generation-stage");if(!e)return;const n=document.getElementById("hero");n&&(n.hidden=t,n.inert=t),t?(e.hidden=!1,e.inert=!1,e.classList.add("is-active")):(An(),document.body.classList.remove("results-fullscreen","results-with-sidebar"),e.classList.remove("is-active"),e.inert=!0,e.hidden=!0)}function pg(){const t=H.isVerified&&!!H.email,e=he.tier||"free",n=t&&ki(),i=!t||Ht(),s=n?"Checking…":i?Ul[e]||"Free":"—",o=document.getElementById("topbar-plan");o&&(o.textContent=s);const a=document.getElementById("topbar-avatar");a&&(a.textContent=(H.email||"?")[0].toUpperCase()),xc();const l=document.getElementById("plans-free-current");l&&l.classList.toggle("hidden",e!=="free"||!i);const u=document.getElementById("plans-checkout-btn-professional");if(u){const d=e==="professional"&&i;u.disabled=d,u.textContent=d?"Current plan":"Subscribe"}const c=document.getElementById("plans-checkout-btn-power");if(c){const d=e==="power"&&i;c.disabled=d,c.textContent=d?"Current plan":"Subscribe"}}function Qb(){pg();const t=document.getElementById("credits-modal");t&&Ke(t)}function eE(t){if(t&&t.target!==t.currentTarget)return;const e=document.getElementById("credits-modal");e&&Ye(e)}function gg(t,e=!0,n=!0){if(t=["home","account","plans"].includes(t)?t:"home",document.body.dataset.shellView=t,t!=="home"&&(An(),ec?.cancelTyping?.(),_.isRunning&&(og(),Gp(),_.isRunning=!1,Gs(!1),Ci(),_.artifactBundle?Pr():(document.getElementById("results-view")?.classList.remove("visible"),document.getElementById("ready-state")?.classList.remove("hidden")))),document.querySelectorAll(".view[data-view]").forEach(r=>{r.dataset.view===t?(r.hidden=!1,r.removeAttribute("inert"),requestAnimationFrame(()=>{r.classList.add("is-active"),n&&r.focus({preventScroll:!0})})):(r.classList.remove("is-active"),r.setAttribute("inert","true"),setTimeout(()=>{r.classList.contains("is-active")||(r.hidden=!0)},260))}),t!=="home")Wt(!1);else{const r=document.getElementById("main-stage-container");r&&r.classList.contains("visible")&&Wt(!0)}if(e){const r=t==="home"?"":`#${t}`;window.location.hash!==r&&window.history.pushState({view:t},"",r||"#")}}let Hd=!1;function Mc(){const t=window.location.hash.replace(/^#/,"");gg(t||"home",!1,Hd),Hd=!0}window.addEventListener("popstate",t=>{Mc()});window.addEventListener("hashchange",()=>{Mc()});window.copyResultsCode=Yb;window.selectArtifact=Tc;window.selectResultsSummary=fg;window.submitResultsFeedback=Jb;window.showErrorInputPanel=Zb;window.hideErrorInputPanel=hg;window.setGenerationStageVisible=Wt;window.switchView=gg;window.selectPipelineStage=Lb;window.editPipelinePrompt=bl;window.retryPipelineRun=ug;window.openCreditsModal=Qb;window.closeCreditsModal=eE; +`).forEach((a,l)=>{const u=document.createElement("span");u.className="code-line",u.innerHTML=lp(a),!Ob()&&_.resultsViewMode==="file"&&(u.classList.add("is-in"),u.style.setProperty("--d",`${l*10}ms`),u.addEventListener("animationend",()=>u.classList.remove("is-in"),{once:!0})),e.appendChild(u)})),n&&(n.innerHTML=Vb(s)),i&&i.classList.toggle("hidden",_.resultsViewMode!=="summary"),r&&r.classList.toggle("hidden",_.resultsViewMode!=="file"),t&&t.classList.add("visible")}function Fr(t,e){const n=document.getElementById("main-stage-container"),i=E0(n),r=document.body.classList.contains("results-fullscreen");Ar(),document.getElementById("pipeline-progress")?.classList.remove("visible"),Wt(!0),_.selectedArtifactId||(_.selectedArtifactId=_i(_.artifactBundle).id),_.resultsViewMode="summary",document.body.classList.add("results-fullscreen");const s=document.getElementById("step3-output");s&&(s.textContent=""),Pr(),vn(),Kb(),r||up(i,n,["#bundle-strip",".summary-heading",".review-summary-text",".summary-spec",".review-score-column",".results-action-bar"].map(o=>document.querySelector(o)),{results:!0})}function Kb(){const t=document.getElementById("btn-feedback-up"),e=document.getElementById("btn-feedback-down"),n=document.getElementById("results-feedback-status");t&&(t.className="feedback-btn",t.setAttribute("aria-pressed","false"),t.disabled=!1,t.removeAttribute("aria-disabled")),e&&(e.className="feedback-btn",e.setAttribute("aria-pressed","false"),e.disabled=!1,e.removeAttribute("aria-disabled")),n&&(n.textContent=""),El=!1}function Tc(t){_.selectedArtifactId=t,_.resultsViewMode="file",Pr()}function fg(){_.resultsViewMode="summary",Pr()}function Yb(){const t=document.getElementById("btn-copy-results"),e=document.getElementById("results-copy-status"),n=fc(),i=(r,s)=>{e&&(e.textContent=r,e.classList.toggle("ok",!!s),e.classList.toggle("fail",!s))};if(!navigator.clipboard?.writeText){i("Copying isn't supported in this browser.",!1);return}navigator.clipboard.writeText(n).then(()=>{if(i("Copied to clipboard",!0),t){t.classList.add("copied");const r=t.querySelector("span");if(r){const s=r.textContent;r.textContent="Copied!",setTimeout(()=>{t.classList.remove("copied"),r.textContent=s},2e3)}}}).catch(()=>{i("Clipboard permission was denied. Select the code to copy it manually.",!1)})}let El=!1;async function Jb(t){const e=document.getElementById("btn-feedback-up"),n=document.getElementById("btn-feedback-down"),i=document.getElementById("results-feedback-status");if(!e||!n||El)return;const r=l=>{i&&(i.textContent="",i.offsetWidth,i.textContent=l)},s=l=>{El=l,[e,n].forEach(u=>{u.disabled=l,u.setAttribute("aria-disabled",String(l))})},o=t==="up"?"thumbsUp":"thumbsDown";s(!0),r("Submitting review feedback…");const a={type:o,code:_.step2Result,input:_.step1Result};try{const l=await Ec(a.type,a.code,a.input);if(!l||l.success!==!0){s(!1),r("Review feedback could not be sent. Tap again to retry.");return}e.classList.toggle("active-up",t==="up"),n.classList.toggle("active-down",t==="down"),e.setAttribute("aria-pressed",String(t==="up")),n.setAttribute("aria-pressed",String(t==="down")),s(!1),r("Review feedback saved.")}catch{s(!1),r("Review feedback could not be sent. Tap again to retry.")}}function Zb(){const t=document.getElementById("error-input-panel");if(t){t.classList.remove("hidden"),t.style.display="flex";const e=document.getElementById("ff-error-paste-input");e&&setTimeout(()=>e.focus(),100)}}function hg(){const t=document.getElementById("error-input-panel");t&&(t.classList.add("hidden"),t.style.display="none")}function Xb(){const t=document.getElementById("shell-stage"),e=document.getElementById("main-stage-container"),n=e?.querySelector(".pipeline-prompt-recap"),i=window.matchMedia("(max-width: 920px)"),r=()=>t?.style.setProperty("--fit",Math.min(1,document.documentElement.clientWidth/1920)),s=()=>{if(!n)return;const o=n.contains(document.activeElement)?document.activeElement:null;i.matches?e.appendChild(n):e.prepend(n),o?.focus({preventScroll:!0})};r(),s(),window.addEventListener("resize",r),i.addEventListener("change",s)}function Wt(t,{preservePanel:e=!1}={}){const n=document.getElementById("generation-stage");if(!n)return;const i=document.getElementById("hero");i&&(i.hidden=t,i.inert=t),t?(n.hidden=!1,n.inert=!1,n.classList.add("is-active")):(An(),e||document.getElementById("main-stage-container")?.classList.remove("visible"),document.body.classList.remove("results-fullscreen","results-with-sidebar"),n.classList.remove("is-active"),n.inert=!0,n.hidden=!0)}function pg(){const t=H.isVerified&&!!H.email,e=he.tier||"free",n=t&&ki(),i=!t||Ht(),s=n?"Checking…":i?Ul[e]||"Free":"—",o=document.getElementById("topbar-plan");o&&(o.textContent=s);const a=document.getElementById("topbar-avatar");a&&(a.textContent=(H.email||"?")[0].toUpperCase()),xc();const l=document.getElementById("plans-free-current");l&&l.classList.toggle("hidden",e!=="free"||!i);const u=document.getElementById("plans-checkout-btn-professional");if(u){const d=e==="professional"&&i;u.disabled=d,u.textContent=d?"Current plan":"Subscribe"}const c=document.getElementById("plans-checkout-btn-power");if(c){const d=e==="power"&&i;c.disabled=d,c.textContent=d?"Current plan":"Subscribe"}}function Qb(){pg();const t=document.getElementById("credits-modal");t&&Ke(t)}function eE(t){if(t&&t.target!==t.currentTarget)return;const e=document.getElementById("credits-modal");e&&Ye(e)}function gg(t,e=!0,n=!0){if(t=["home","account","plans"].includes(t)?t:"home",document.body.dataset.shellView=t,t!=="home"&&(An(),ec?.cancelTyping?.(),_.isRunning&&(og(),Gp(),_.isRunning=!1,Gs(!1),Ci(),_.artifactBundle?Pr():(document.getElementById("results-view")?.classList.remove("visible"),document.getElementById("ready-state")?.classList.remove("hidden"),document.getElementById("main-stage-container")?.classList.remove("visible")))),document.querySelectorAll(".view[data-view]").forEach(r=>{r.dataset.view===t?(r.hidden=!1,r.removeAttribute("inert"),requestAnimationFrame(()=>{r.classList.add("is-active"),n&&r.focus({preventScroll:!0})})):(r.classList.remove("is-active"),r.setAttribute("inert","true"),setTimeout(()=>{r.classList.contains("is-active")||(r.hidden=!0)},260))}),t!=="home")Wt(!1,{preservePanel:!0});else{const r=document.getElementById("main-stage-container");r&&r.classList.contains("visible")&&(document.getElementById("results-view")?.classList.contains("visible")&&document.body.classList.add("results-fullscreen","results-with-sidebar"),Wt(!0))}if(e){const r=t==="home"?"":`#${t}`;window.location.hash!==r&&window.history.pushState({view:t},"",r||"#")}}let Hd=!1;function Mc(){const t=window.location.hash.replace(/^#/,"");gg(t||"home",!1,Hd),Hd=!0}window.addEventListener("popstate",t=>{Mc()});window.addEventListener("hashchange",()=>{Mc()});window.copyResultsCode=Yb;window.selectArtifact=Tc;window.selectResultsSummary=fg;window.submitResultsFeedback=Jb;window.showErrorInputPanel=Zb;window.hideErrorInputPanel=hg;window.setGenerationStageVisible=Wt;window.switchView=gg;window.selectPipelineStage=Lb;window.editPipelinePrompt=bl;window.retryPipelineRun=ug;window.openCreditsModal=Qb;window.closeCreditsModal=eE; diff --git a/dist/index.html b/dist/index.html index 8e2e158..a61883f 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1994,11 +1994,6 @@ } .pipeline-view h3 { font-size: 25px; - /* The longest phase title ("Understanding your prompt") measures - ~304px at this size, past the 298px content column, so it wrapped - to two lines and pushed every later section down 30px. Tightening - tracking keeps the phase title to the canonical single line. */ - letter-spacing: -0.045em; } .pipeline-progress .pipeline-glyph.logo-loop { margin-bottom: 16px; } .pipeline-prompt-recap { @@ -3310,7 +3305,7 @@ .send.is-busy .send-spin { animation: none; opacity: 0.6; } } - + diff --git a/e2e/fixtures/canonicalReference.js b/e2e/fixtures/canonicalReference.js new file mode 100644 index 0000000..1a58840 --- /dev/null +++ b/e2e/fixtures/canonicalReference.js @@ -0,0 +1,34 @@ +import { readFileSync } from 'node:fs'; +import { createHash } from 'node:crypto'; +import { applyDefaultRoutes } from './apiFixtures.js'; + +// Byte-identical, test-only snapshot of the user's authoritative HTML. Never +// derive this oracle from app markup/CSS or replace its render-affecting assets. +function pinnedFile(path, sha256, contentType) { + const body = readFileSync(new URL(path, import.meta.url)); + if (createHash('sha256').update(body).digest('hex') !== sha256) { + throw new Error(`Canonical reference hash mismatch: ${path}`); + } + return { body, contentType }; +} + +const assets = { + '/': pinnedFile('./custom-code-connect-hero.html', '548e628fe94f2b5e647cb4849fbc3200b43dcead60ea53c076f0085aca8917dc', 'text/html'), + '/fonts/Delight-VF.ttf': pinnedFile('../../fonts/Delight-VF.ttf', 'c034c57b430cc5e394eb6d1a88655c694a98c8f32ee888255f41964f58c19075', 'font/ttf'), + '/assets/flutterflow-icon.svg': pinnedFile('../../public/assets/flutterflow-icon.svg', 'b3d33379c329ac9aff23d80c002103a149625aa8cc0a850451915d1289fc6a98', 'image/svg+xml'), +}; + +export async function openCanonicalReference(page, viewport) { + await page.setViewportSize(viewport); + await page.emulateMedia({ reducedMotion:'no-preference' }); + await applyDefaultRoutes(page); + // This origin is fulfilled entirely in-process: no reference server or live + // API is needed. Unknown requests still reach the hermetic route guard. + const origin = 'http://localhost:5432'; + await page.route(`${origin}/**`, route => { + const asset = assets[new URL(route.request().url()).pathname]; + return asset ? route.fulfill(asset) : route.fallback(); + }); + await page.goto(origin); + await page.evaluate(() => document.fonts.ready); +} diff --git a/e2e/fixtures/custom-code-connect-hero.html b/e2e/fixtures/custom-code-connect-hero.html new file mode 100644 index 0000000..04a85a2 --- /dev/null +++ b/e2e/fixtures/custom-code-connect-hero.html @@ -0,0 +1,2537 @@ + + + + +Custom Code Connect — FlutterFlow custom code, minus the code. + + + + + +
+
+ +
+
+
+
+ +
+ + + + + + + + + + +
+ + + + + +

Your workspace

Credits

+
50credits remaining
+

Your available balance for creating custom code.

+

This preview shows your current demo balance. Usage history and top-ups are not connected.

+ +
+ + +

Your workspace

Your plan

+
+
+

Free

+
A$0/ month
+

No card required

+
    +
  • 3 runs per month
  • +
  • Custom widgets only
  • +
  • Copy the Dart by hand
  • +
+
+
+
+

Pro Most popular

+
A$11/ month
+

Billed monthly · cancel anytime

+

Your current plan · 50 credits remaining this month

+
    +
  • 50 runs per month
  • +
  • Every artifact type and model
  • +
  • Deploy straight to FlutterFlow
  • +
  • Code review score and manual steps
  • +
+
+
+
+

Power

+
A$29/ month
+

Billed monthly · cancel anytime

+
    +
  • Unlimited runs
  • +
  • Priority queue
  • +
  • Multiple FlutterFlow projects
  • +
+
+
+
+

+

Subscription changes are not connected in this preview.

+ +
+ + +

Your workspace

Billing

+

Manage the subscription and invoices in Stripe.

+
+
Current plan
Pro · A$11 / mo
+
Payment method
Card held by Stripe
+
Status
Not connected
+
+

Invoices and payment methods are held by Stripe. Nothing here is connected in this preview.

+

+ +
+ + +
+
+ +
+

FlutterFlow custom code.
(minus the code)

+

Custom widgets and actions, expertly crafted, fully reviewed and +ready to deploy to your project in one click.

+ +
+ +
+ + + +
+ + +
+ +
+ +
+ + + +
+ + +
+ +
+
+
+

Your prompt

+

+ + + +
+
+
+
+ + + + + + +
+

Understanding your prompt

+

Turning your idea into a clear FlutterFlow specification.

+
+
Step 1 of 3
+
+
+ +
+ + + +
+
+
+

Your gauge, ready to inspect.

+

This example combines a gradient progress arc with an animated percentage label. Width, height and progress are exposed as FlutterFlow widget parameters.

+
Artifact
Custom Widget
Parameters
width · height · progress
Manual Steps Required
  1. Add the width, height and progress parameters in FlutterFlow.
  2. Paste the Dart into the widget as CircularProgressGauge.
  3. Preview it in FlutterFlow and confirm the gauge fills.
Review
Illustrative, not a live audit
+
+
+
+ Score + 100 + out of 100 + Strong +
+
+ Is the generated code correct? +
+ + +
+
+
+
+
+
+
Generated Dart Code
+
+
+
+

File Review

+ ✓ Reviewed · demo +

A FlutterFlow-compatible widget with a bounded progress value and an animated label.

+
  • ✓Named width and height parameters
  • ✓Progress clamped between 0 and 1
  • ✓Reduced motion respected
+

Before deploying: add the widget parameters and test in FlutterFlow preview.

+
+
+
+
+ + + +
+
+
+
+
+
+ + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/e2e/stu445-exact-fidelity.spec.js b/e2e/stu445-exact-fidelity.spec.js index e342bea..1fe2b0b 100644 --- a/e2e/stu445-exact-fidelity.spec.js +++ b/e2e/stu445-exact-fidelity.spec.js @@ -1,5 +1,6 @@ import { test, expect } from '@playwright/test'; import { applyDefaultRoutes, ENDPOINTS, oneArtifact, reviewPassed, professionalSubscription, providerError } from './fixtures/apiFixtures.js'; +import { openCanonicalReference } from './fixtures/canonicalReference.js'; const PROMPT = 'A circular progress gauge with a gradient stroke and an animated percentage label'; @@ -79,12 +80,22 @@ async function box(page, selector, expected) { actual.forEach((value,i) => expect(Math.abs(value - expected[i]), `${selector} component ${i}: ${actual} vs ${expected}`).toBeLessThanOrEqual(1)); } -// Coordinates transcribed from the unchanged reference, not implementation -// selectors/styles: reference SHA 548e628f…91dc, preflight geometry JSON. -for (const width of [1920,1440,1366,921,920,390]) { - test(`reference stage and panel geometry at ${width}px`, async ({ page }) => { - await page.setViewportSize({ width, height:width === 390 ? 844 : width === 1366 ? 768 : width === 1440 ? 900 : 1080 }); +async function accountRoundTrip(page) { + await page.locator('#topbar-avatar').click(); + await expect(page.locator('#account-view')).toBeVisible(); + await page.locator('a.brand').click(); + await expect(page.locator('#home-view')).toBeVisible(); +} + +// Fixed structural coordinates remain independent of either render. Intrinsic +// text geometry comes from the pinned source in this same browser/viewport, +// not a platform-specific line-wrap measurement. Both use real frozen fonts. +for (const width of [1920,1440,1366,921,920,390,389]) { + test(`reference stage and panel geometry at ${width}px`, async ({ page, context }, testInfo) => { + await page.setViewportSize({ width, height:width <= 390 ? 844 : width === 1366 ? 768 : width === 1440 ? 900 : 1080 }); await open(page); + const reference = await context.newPage(); + await openCanonicalReference(reference, page.viewportSize()); const run = await gates(page); await page.waitForTimeout(800); // authored first-load reveal const scale = Math.min(1,width/1920); @@ -98,19 +109,41 @@ for (const width of [1920,1440,1366,921,920,390]) { await box(page, '#pipeline-input', [40,426.859375,310,94]); await box(page, '#hero-send', [314,500.859375,44,44]); } - await submit(page); + await reference.locator('#prompt').fill(PROMPT); + await Promise.all([submit(page), reference.locator('.send').click()]); await expect.poll(() => run.calls).toEqual(['architect']); await page.waitForTimeout(2300); + await expect(reference.locator('#pipeline-title')).toHaveText('Understanding your prompt'); + await expect(page.locator('#progress-title-text')).toHaveText('Understanding your prompt'); + await expect(reference.locator('#submitted-prompt')).toHaveText(PROMPT); + await expect(page.locator('#pipeline-submitted-prompt')).toHaveText(PROMPT); + for (const [appSelector, referenceSelector] of [ + ['#main-stage-container', '.walkthrough-layout'], + ['#progress-title-text', '#pipeline-title'], + ['#progress-description-text', '#pipeline-description'], + ['.progress-track', '.pipeline-track'], + ]) await box(page, appSelector, await rect(reference, referenceSelector)); + for (const [render, selector] of [[page, '#progress-title-text'], [reference, '#pipeline-title']]) { + await expect(render.locator(selector)).toHaveCSS('letter-spacing', width <= 920 ? '-0.625px' : '-0.85px'); // -.025em + expect(await render.evaluate(() => document.fonts.check('500 25px Delight'))).toBe(true); + } if (width > 920) { await box(page, '#main-stage-container', [250*scale,164*scale,1420*scale,590*scale]); await box(page, '.pipeline-prompt-recap', [252*scale,166*scale,310*scale,586*scale]); await box(page, '.progress-track', [626*scale,553.1875*scale,978*scale,4*scale]); - } else if (width === 390) { - await box(page, '#main-stage-container', [20,92,350,497.1875]); - await box(page, '.progress-track', [46,337.1875,298,4]); } else { - await box(page, '#main-stage-container', [20,92,880, ...(await rect(page,'#main-stage-container')).slice(3)]); + const panel = await rect(reference, '.walkthrough-layout'); + const track = await rect(reference, '.pipeline-track'); + await box(page, '#main-stage-container', [20,92,width-40,panel[3]]); + await box(page, '.progress-track', [46,track[1],width-92,4]); + await expect(page.locator('#progress-title-text')).toHaveCSS('font-size','25px'); + await expect(page.locator('#progress-title-text')).toHaveCSS('line-height','30px'); + } + if (width === 1920 || width <= 390) { + await page.screenshot({ path:testInfo.outputPath('app-pipeline.png'), fullPage:true }); + await reference.screenshot({ path:testInfo.outputPath('reference-pipeline.png'), fullPage:true }); } + await reference.close(); const sidebar = await page.locator('.pipeline-prompt-recap').elementHandle(); run.release('architect'); await expect.poll(() => run.calls).toEqual(['architect','generator']); @@ -132,6 +165,147 @@ for (const width of [1920,1440,1366,921,920,390]) { }); } +for (const reducedMotion of ['no-preference','reduce']) { + for (const outcome of ['held Architect','backend failure']) { + test(`Edit after ${outcome} preserves the composer through two navigation cycles and a successful retry (${reducedMotion})`, async ({ page }, testInfo) => { + await page.setViewportSize({ width:390,height:844 }); + await open(page, { reducedMotion }); + for (const cycle of [1,2]) { + const run = await gates(page); + await submit(page); + await expect.poll(() => run.calls).toEqual(['architect']); + if (outcome === 'backend failure') { + run.release('architect', providerError()); + await expect(page.locator('#pipeline-failure')).toBeVisible(); + await page.locator('#pipeline-failure-actions [data-action="edit"]').click(); + } else { + await page.locator('#pipeline-edit-prompt').click(); + run.release('architect'); + } + await expect(page.locator('#composer')).toBeVisible(); + await accountRoundTrip(page); + await page.waitForTimeout(1500); // stale response and cancelled handoff must stay retired + await page.screenshot({ path:testInfo.outputPath(`edited-home-${cycle}.png`), fullPage:true }); + await expect(page.locator('#composer')).toBeVisible(); + await expect(page.locator('#pipeline-input')).toHaveValue(PROMPT); + await expect(page.locator('#hero-send')).toBeEnabled(); + await expect(page.locator('#generation-stage')).toBeHidden(); + await expect(page.locator('#pipeline-progress')).toHaveAttribute('data-run-state','settled'); + await expect(page.locator('#pipeline-progress')).toBeHidden(); + await expect(page.locator('#results-view')).toBeHidden(); + await expect(page.locator('.composer-morph')).toHaveCount(0); + expect(run.calls).toEqual(['architect']); + } + + const retry = await gates(page); + await submit(page); + retry.release('architect'); retry.release('generator'); retry.release('review'); + await expect(page.locator('#results-view')).toBeVisible(); + const expectedCode = JSON.parse(JSON.parse(oneArtifact().body).output).artifacts[0].code; + for (const cycle of [1,2]) { + if (cycle === 2) await page.locator('#artifact-tabs [role=tab]').first().click(); + await accountRoundTrip(page); + await expect(page.locator('#results-view')).toBeVisible(); + await expect(page.locator('#composer')).toBeHidden(); + await expect(page.locator('#pipeline-submitted-prompt')).toHaveText(PROMPT); + await expect(page.locator('.review-score strong')).toHaveText('96'); + expect((await page.locator('#results-code-output .code-line').allTextContents()).join('\n')).toBe(expectedCode); + await expect(page.locator(cycle === 1 ? '#results-summary-tab' : '#artifact-tabs [role=tab]')).toHaveAttribute('aria-selected','true'); + await expect(page.locator('.composer-morph')).toHaveCount(0); + } + expect(retry.calls).toEqual(['architect','generator','review']); + await page.screenshot({ path:testInfo.outputPath('retry-results-retained.png'), fullPage:true }); + }); + } + + for (const returnAction of ['Edit prompt','focusPromptInput']) { + test(`explicit composer return via ${returnAction} is not undone by navigation after Results (${reducedMotion})`, async ({ page }, testInfo) => { + await page.setViewportSize({ width:390,height:844 }); + await open(page, { reducedMotion }); + const run = await gates(page); + await submit(page); + run.release('architect'); run.release('generator'); run.release('review'); + await expect(page.locator('#results-view')).toBeVisible(); + await expect(page.locator('.review-score strong')).toHaveText('96'); + if (returnAction === 'Edit prompt') await page.locator('#pipeline-edit-prompt').click(); + else await page.evaluate(() => window.focusPromptInput()); + await expect(page.locator('#composer')).toBeVisible(); + for (const cycle of [1,2]) { + await accountRoundTrip(page); + await expect(page.locator('#composer')).toBeVisible(); + await expect(page.locator('#pipeline-input')).toHaveValue(PROMPT); + await expect(page.locator('#hero-send')).toBeEnabled(); + await expect(page.locator('#generation-stage')).toBeHidden(); + await expect(page.locator('.composer-morph')).toHaveCount(0); + } + expect(run.calls).toEqual(['architect','generator','review']); + await page.screenshot({ path:testInfo.outputPath('completed-result-return-home.png'), fullPage:true }); + }); + } + + test(`abandoned Architect returns to the preserved composer on both navigation cycles (${reducedMotion})`, async ({ page }, testInfo) => { + await page.setViewportSize({ width:1920,height:1080 }); + await open(page, { reducedMotion }); + for (const cycle of [1,2]) { + const run = await gates(page); + await submit(page); + await expect.poll(() => run.calls).toEqual(['architect']); + await page.locator('#topbar-avatar').click(); + await expect(page.locator('#account-view')).toBeVisible(); + await page.locator('a.brand').click(); + await expect(page.locator('#home-view')).toBeVisible(); + run.release('architect'); + await page.waitForTimeout(1500); // late response and all abandoned handoff timers + await page.screenshot({ path:testInfo.outputPath(`abandoned-home-${cycle}.png`), fullPage:true }); + expect(run.calls).toEqual(['architect']); + await expect(page.locator('#pipeline-progress')).toHaveAttribute('data-run-state','settled'); + await expect(page.locator('#pipeline-progress')).toBeHidden(); + await expect(page.locator('#generation-stage')).toBeHidden(); + await expect(page.locator('#composer')).toBeVisible(); + await expect(page.locator('#pipeline-input')).toHaveValue(PROMPT); + await expect(page.locator('#hero-send')).toBeEnabled(); + await expect(page.locator('.composer-morph')).toHaveCount(0); + await expect(page.locator('#results-view')).toBeHidden(); + } + }); + + test(`completed Results retain fullscreen layout and raw code on both navigation cycles (${reducedMotion})`, async ({ page }, testInfo) => { + await page.setViewportSize({ width:1920,height:1080 }); + await open(page, { reducedMotion }); + const run = await gates(page); + await submit(page); + run.release('architect'); run.release('generator'); run.release('review'); + await expect(page.locator('#results-view')).toBeVisible(); + await page.waitForTimeout(2200); + await box(page, '#main-stage-container', [40,104,1840,936]); + const expectedCode = JSON.parse(JSON.parse(oneArtifact().body).output).artifacts[0].code; + for (const cycle of [1,2]) { + // First preserve Summary, then preserve the selected file on the second visit. + if (cycle === 2) await page.locator('#artifact-tabs [role=tab]').first().click(); + await page.locator('#topbar-avatar').click(); + await expect(page.locator('#account-view')).toBeVisible(); + await expect(page.locator('#generation-stage')).toBeHidden(); + await page.locator('a.brand').click(); + await expect(page.locator('#results-view')).toBeVisible(); + await page.waitForTimeout(300); // router visibility transition + await page.screenshot({ path:testInfo.outputPath(`results-home-${cycle}.png`), fullPage:true }); + await box(page, '#main-stage-container', [40,104,1840,936]); + await box(page, '.results-action-bar', [400,941,1430,57]); + await expect(page.locator('body')).toHaveClass(/results-fullscreen/); + await expect(page.locator('body')).toHaveClass(/results-with-sidebar/); + await expect(page.locator('#main-stage-container')).toHaveCSS('visibility','visible'); + await expect(page.locator('#pipeline-submitted-prompt')).toHaveText(PROMPT); + await expect(page.locator('.review-score strong')).toHaveText('96'); + expect((await page.locator('#results-code-output .code-line').allTextContents()).join('\n')).toBe(expectedCode); + await expect(page.locator(cycle === 1 ? '#results-summary-tab' : '#artifact-tabs [role=tab]')).toHaveAttribute('aria-selected','true'); + await expect(page.locator('#pipeline-progress')).toBeHidden(); + await expect(page.locator('#composer')).toBeHidden(); + await expect(page.locator('.composer-morph')).toHaveCount(0); + expect(run.calls).toEqual(['architect','generator','review']); + } + }); +} + test('morph keyframes, opaque ghost, hidden destination and reading-order reveal', async ({ page }) => { await page.setViewportSize({ width:1920,height:1080 }); await open(page); diff --git a/index.html b/index.html index fd8e502..c4d9b70 100644 --- a/index.html +++ b/index.html @@ -1998,11 +1998,6 @@ } .pipeline-view h3 { font-size: 25px; - /* The longest phase title ("Understanding your prompt") measures - ~304px at this size, past the 298px content column, so it wrapped - to two lines and pushed every later section down 30px. Tightening - tracking keeps the phase title to the canonical single line. */ - letter-spacing: -0.045em; } .pipeline-progress .pipeline-glyph.logo-loop { margin-bottom: 16px; } .pipeline-prompt-recap {