
The Agentic Quality Platform
" +
'
";
mount.appendChild(host);
ui = {
host: host, root: root,
wrap: root.querySelector(".wrap"),
log: root.querySelector(".log"),
msgs: root.querySelector(".msgs"),
input: root.querySelector(".input"),
form: root.querySelector(".composer"),
sendBtn: root.querySelector(".send"),
clearBtn: root.querySelector('[data-act="clear"]'),
fullBtn: root.querySelector('[data-act="full"]')
};
return ui;
}
function markActive() { ui.wrap.classList.add("active"); }
function addMsg(role, text) {
markActive();
var el = document.createElement("div");
el.className = "msg " + (role === "user" ? "user" : "bot");
el.innerHTML = render(text);
ui.msgs.appendChild(el);
ui.log.scrollTop = ui.log.scrollHeight;
return el;
}
function addTyping() {
markActive();
var el = document.createElement("div");
el.className = "msg bot";
el.setAttribute("aria-busy", "true");
el.innerHTML = "";
ui.msgs.appendChild(el);
ui.log.scrollTop = ui.log.scrollHeight;
return el;
}
function autoGrow() {
var el = ui.input;
el.style.height = "auto";
el.style.height = Math.min(el.scrollHeight, 160) + "px";
}
// ---------- fullscreen ----------
function setExpanded(on) {
if (on === expanded) return;
expanded = on;
// Move the widget to while expanded. position:fixed resolves against the
// nearest transformed/filtered ancestor rather than the viewport, and host pages
// (Webflow animations especially) routinely have one, which would pin fullscreen
// to a section's box. Moving the node keeps the shadow root, state and listeners.
if (on) {
hostAnchor = document.createComment("fz-hero-chat");
ui.host.parentNode.insertBefore(hostAnchor, ui.host);
document.body.appendChild(ui.host);
} else if (hostAnchor && hostAnchor.parentNode) {
hostAnchor.parentNode.insertBefore(ui.host, hostAnchor);
hostAnchor.parentNode.removeChild(hostAnchor);
hostAnchor = null;
}
ui.host.classList.toggle("fz-expanded", on);
ui.fullBtn.innerHTML = on ? ICONS.collapse : ICONS.expand;
ui.fullBtn.setAttribute("aria-pressed", on ? "true" : "false");
ui.fullBtn.setAttribute("aria-label", on ? "Exit full screen" : "Expand chat to full screen");
ui.fullBtn.setAttribute("title", on ? "Exit full screen" : "Expand");
var html = document.documentElement;
if (on) { prevHtmlOverflow = html.style.overflow; html.style.overflow = "hidden"; }
else { html.style.overflow = prevHtmlOverflow; }
ui.log.scrollTop = ui.log.scrollHeight;
ui.input.focus();
}
function clearChat() {
messages = [];
try { localStorage.removeItem(HIST_KEY); localStorage.removeItem(STASH_KEY); } catch (e) {}
if (!ui) return;
ui.msgs.innerHTML = "";
ui.wrap.classList.remove("active");
setExpanded(false);
ui.input.focus();
}
// ---------- send / stream ----------
function send() {
var text = (ui.input.value || "").trim();
if (!text || streaming) return;
ui.input.value = "";
autoGrow();
// Over-limit input: don't send the full text. Stash it locally, show a short
// placeholder, and tell the agent a large request came in so it can pivot to
// signing the visitor up. The agent's reply streams as normal.
if (text.length > MAX_INPUT) {
stashLargeRequest(text);
var snippet = text.slice(0, 200).replace(/\s+/g, " ").trim();
var shown = snippet + "\u2026 (long request saved)";
addMsg("user", shown);
messages.push({ role: "user", content: shown });
saveHistory();
var note = "(I pasted a very long request, about " + text.length + " characters, it's been saved. " +
"Opening snippet: \"" + snippet + "\u2026\". It's a substantial, real testing task.)";
stream(messages.slice(0, -1).concat([{ role: "user", content: note }]));
return;
}
addMsg("user", text);
messages.push({ role: "user", content: text });
saveHistory();
stream(messages);
}
function stream(outbound) {
streaming = true;
var body = (outbound || messages).slice(-HISTORY_DEPTH);
var typing = addTyping(), botEl = null, acc = "";
var doneCalled = false, redirecting = false;
function bot() {
if (botEl) return;
if (typing && typing.parentNode) typing.parentNode.removeChild(typing);
botEl = addMsg("assistant", "");
botEl.setAttribute("aria-busy", "true");
}
function doFetch() {
return fetch(ENDPOINT + "/v1/chat", {
method: "POST", credentials: "include",
headers: { "Content-Type": "application/json", "X-FZ-Fingerprint": FINGERPRINT, "X-FZ-Token": TOKEN },
body: JSON.stringify({ messages: body, fingerprint: FINGERPRINT, page: pageContext() })
});
}
function handle(block) {
var ev = "message", data = [];
block.split("\n").forEach(function (l) {
if (l.indexOf("event:") === 0) ev = l.slice(6).trim();
else if (l.indexOf("data:") === 0) data.push(l.slice(5).trim());
});
if (!data.length) return;
var p = {};
try { p = JSON.parse(data.join("\n")); } catch (e) { return; }
if (ev === "token") {
bot();
acc += p.text || "";
botEl.innerHTML = render(acc);
ui.log.scrollTop = ui.log.scrollHeight;
} else if (ev === "redirect") {
bot();
if (p.message) { acc += (acc ? "\n\n" : "") + p.message; botEl.innerHTML = render(acc); }
if (p.url) {
redirecting = true;
try { messages = []; localStorage.removeItem(HIST_KEY); localStorage.removeItem(STASH_KEY); } catch (e) {}
// Carry attribution onto the handoff URL ourselves rather than via the
// page's global, so a stale copy of the old script can't drop params.
var target = decorate(p.url);
setTimeout(function () { window.location.href = target; }, 1200);
}
} else if (ev === "error") {
bot();
botEl.innerHTML = render(p.message || "Something went wrong. Please try again.");
}
}
function done() {
if (doneCalled) return;
doneCalled = true;
if (typing && typing.parentNode) typing.parentNode.removeChild(typing);
if (botEl) botEl.removeAttribute("aria-busy");
// On conversion the redirect branch already cleared history and `acc` is just
// the farewell message, so don't re-save it.
if (acc && !redirecting) { messages.push({ role: "assistant", content: acc }); saveHistory(); }
streaming = false;
ui.input.focus();
}
ensureToken().then(doFetch)
.then(function (resp) { return resp.status === 401 ? refreshToken().then(doFetch) : resp; })
.then(function (resp) {
if (!resp.ok || !resp.body) throw new Error("HTTP " + resp.status);
var reader = resp.body.getReader(), dec = new TextDecoder(), buf = "";
function pump() {
return reader.read().then(function (res) {
if (res.done) return done();
buf += dec.decode(res.value, { stream: true });
var blocks = buf.split("\n\n");
buf = blocks.pop();
blocks.forEach(handle);
return pump();
});
}
return pump();
})
.catch(function () {
bot();
botEl.innerHTML = render("Sorry, I couldn't reach the assistant just now. Please try again in a moment.");
done();
});
}
// ---------- wiring ----------
function wire() {
// Placeholder clears on focus and comes back when the field is left empty.
// Native placeholders stay visible until the first character is typed.
ui.input.addEventListener("focus", function () { ui.input.placeholder = ""; });
ui.input.addEventListener("blur", function () {
if (!ui.input.value.trim()) ui.input.placeholder = PLACEHOLDER;
});
ui.input.addEventListener("input", autoGrow);
ui.input.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); }
});
ui.form.addEventListener("submit", function (e) { e.preventDefault(); send(); });
ui.clearBtn.addEventListener("click", clearChat);
ui.fullBtn.addEventListener("click", function () { setExpanded(!expanded); });
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && expanded) setExpanded(false);
});
}
function restore() {
messages = loadHistory();
if (messages.length) {
messages.forEach(function (m) { addMsg(m.role === "user" ? "user" : "assistant", m.content); });
} else if (GREETING) {
addMsg("assistant", GREETING);
}
}
function init() {
if (PLACEHOLDER_ENDPOINT) {
console.warn("[fz-hero-chat] No data-endpoint set on the script tag; widget not mounted.");
return true; // stop retrying
}
var mount = document.querySelector(MOUNT_SEL);
if (!mount) return false;
if (mount.hasAttribute("data-fz-mounted")) return true;
mount.setAttribute("data-fz-mounted", "");
build(mount);
wire();
restore();
initAttribution();
window.FZHeroChat = {
clear: clearChat,
history: function () { return messages.slice(); },
stash: function () { try { return JSON.parse(localStorage.getItem(STASH_KEY) || "[]"); } catch (e) { return []; } },
expand: function () { setExpanded(true); },
collapse: function () { setExpanded(false); },
isExpanded: function () { return expanded; }
};
return true;
}
function boot() {
if (init()) return;
// The mount may render late (Webflow interactions, lazy sections) - retry briefly.
var tries = 0;
var t = setInterval(function () { if (init() || ++tries > 40) clearInterval(t); }, 150);
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
else boot();
})();
' +
'" +
'
Press Enter to send
' +
"
Meet Studio
The fight for quality is real: agents ship code faster than you can check it. What you need is another agent, one that answers to you, not to the code.
Our intelligence
The models, the data layer, and the execution engine are ours. Built for testing, sharpened on your app.
Knows your app
Every run trains Studio on your app, so coverage sharpens with use.
Directed by you
You set what good means. You make the release call. Studio does the work in between.
An independent agent with no loyalty to your code. Only to the quality of your application.

Plans
Free
$0, always free
For seeing what Studio does on your app
Growth
$20/mo
For a product you're actively building coverage for
Scale
$100/mo
For teams building across multiple products and releasing more often
Enterprise
custom
For organizations making agentic testing the standard across every team
Built here. Not wrapped.
Studio judges how your product runs on models Functionize built for this work, not a general model wrapped in a testing prompt. The difference shows up in three things you can measure.
TRUSTED OUTPUTS
Fidelity
Studio resolves each element from 200+ data points, with models that carry no other job. A general model does this as one task among thousands
MINUTES, NOT HOURS
Speed
Execution runs on small models built for the work. No step waits on a general model reasoning toward an answer
THE PROMPT SHRINKS
Input
A wrapped product needs more instruction as flows get complex. Studio needs less, because its models already know how applications behave
Teams that stopped chasing quality
Proof in the pipeline
10x
Productivity Increase
75%
Faster Time
to Market
80%
Less maintenance burden
Powering quality at scale
Mission-critical apps need testing that doesn't flinch
Built for teams. Scaled for the org.
Coverage under pressure
Complex workflows. High-stakes releases.
Consistency at scale
Same rigorous standard. Every team. Every deploy.

Contextual knowledge of your enterprise stack
✓ Salesforce
✓ Servicenow
✓ Workday
✓ SAP
✓ Bespoke Apps
Infrastructure your security and compliance teams already trust


