flowweaver · Install
Embed Flowweaver
How much you have to install depends on what you want out of it, and for most people that is less than they expect. This page goes in three steps: the answers in your inbox, the viewer on your page, and last what a developer needs. Many can stop reading after the first.
One thing to know before you wire it up: this is built for the person who edits, not for the person who installs. That decides more of what follows than any technical choice does.
Step 1 — do you only want the answers? Choose email
A form needs somewhere to leave its answers. That place already exists with you, and it is your inbox.
In the editor, the author ends the guide in a node called Email result. Recipient, subject and body are written once, with the answers put in as variables. When the visitor arrives, the guide fills them in and hands over a finished message. No database, no receiver on a server, nothing anyone has to build for you.
The library sends nothing itself — and says so plainly in the editor: Produces email data – does not send. The message is handed to the page, which decides what becomes of it. The cheapest answer is to open the visitor's own mail program with everything filled in; the browser does that for you.
<script>
const guide = document.querySelector("guide-preview");
guide.addEventListener("preview-node-changed", () => {
// null right up until the guide reaches its email node
const letter = guide.getOutput();
if (!letter) return;
window.location.href =
"mailto:" + letter.resolved.to +
"?subject=" + encodeURIComponent(letter.resolved.subject) +
"&body=" + encodeURIComponent(letter.resolved.body);
});
</script>
The body travels as plain text — a mailto: carries no
formatting. If you want it formatted, or want to send from your own
server rather than from the visitor's program, it is the same data you
read: getOutput() hands you recipient, subject and body
already filled in.
The email result example runs such a guide and shows the message it produces, line by line. The node itself is built in the Advanced editor.
On the way, not finished yet: receivers that leave the answer straight into WordPress, into a file on your web host, or as a row in a spreadsheet. They do not exist today. Until they do, email is the route that asks nobody to install anything for you.
Step 2 — one line puts the viewer in any page
The viewer is a script tag. It works in a CMS where you can paste HTML, on a page in a publishing tool, in a file on your web host. No account, no token, no build step.
<script src="https://flowweaver.se/lib/0.11/flowweaver-viewer.global.js"></script>
The rest is your own guide. The author exports it from the editor as a file, you put the file next to the page, and the page fetches it:
<link rel="stylesheet" href="https://flowweaver.se/lib/tokens.css">
<guide-preview></guide-preview>
<script src="https://flowweaver.se/lib/0.11/flowweaver-viewer.global.js"></script>
<script>
fetch("my-guide.json")
.then((response) => response.json())
.then((guide) => { document.querySelector("guide-preview").graph = guide; });
</script>
The file is yours. An older file opens in a newer viewer — the migrations run when the graph is set — so a guide you exported in the spring still works after the library is updated.
That is the whole installation for someone without a developer. The rest of this page is for someone who has one.
Step 3 — the versions, npm and the editor
Flowweaver is two ordinary custom elements — <guide-preview> which runs a finished guide, and <guide-editor> which an author builds it in. No framework required. From here down is what a developer needs to know: which version to point at, the packages, and the editor.
Two ways to get hold of the library. Take the first if you just want to get going; the second suits you if you already have a build step and want the dependency under version control.
-
Script tag
Load the file straight from this site. No account, no token, no build step.
-
npm package
@johanfuruskog-create/flowweaver-viewer(MIT) and…/flowweaver-editor(BUSL-1.1) from GitHub Packages. Requires a token withread:packages.
Route 1 — a script tag
Two files: the bundle that registers the elements, and tokens.css with the theme variables. Then you set the graph on the element. The same setup as in step 2, but with the graph written by hand rather than fetched from an exported file.
Works everywhere, including CMSes where all you can paste is an ordinary script tag. The file ends in .global.js — that is the classic build.
The addresses below are rolling. /lib/ is rebuilt from main on every change, so you always get the latest — including when the latest breaks something. Good for trying things, wrong for anything you run in production.
In production: point at a line. 0.10 is every 0.10.x build. It carries a fix to you without you touching anything, and never a breaking change — one of those raises the number. Want nothing to move at all? Name the whole version: 0.10.2.
<script src="https://flowweaver.se/lib/0.11/flowweaver-viewer.global.js"></script>
Change the digit deliberately, and read CHANGELOG first — it says what you have to do when something breaks.
<link rel="stylesheet" href="https://flowweaver.se/lib/tokens.css">
<guide-preview></guide-preview>
<script src="https://flowweaver.se/lib/flowweaver-viewer.global.js"></script>
<script>
document.querySelector("guide-preview").graph = {
startNodeId: "q1",
nodes: [
{ id: "q1", type: "question", position: { x: 0, y: 0 }, data: {
title: "Are you 18 or older?",
variableName: "isAdult",
options: [
{ id: "yes", label: "Yes", value: "yes" },
{ id: "no", label: "No", value: "no" },
] } },
{ id: "ok", type: "result", position: { x: 400, y: 0 }, data: {
title: "You can continue" } },
],
connections: [
{ id: "c1", from: { nodeId: "q1", portId: "yes" }, to: { nodeId: "ok", portId: "input" } },
],
};
</script>
The order of the tags does not matter — if you set the graph before the bundle has loaded, the value is picked up when the element registers.
That is not a made-up example — the box below runs exactly that code, with the bundle fetched from this site.
Loading …
As an ES module
If you already have a build step, or would rather avoid a global name, the same bundle exists without .global:
<script type="module">
import "https://flowweaver.se/lib/flowweaver-viewer.js";
document.querySelector("guide-preview").graph = {
startNodeId: "q1",
nodes: [
{ id: "q1", type: "question", position: { x: 0, y: 0 }, data: {
title: "Are you 18 or older?",
variableName: "isAdult",
options: [
{ id: "yes", label: "Yes", value: "yes" },
{ id: "no", label: "No", value: "no" },
] } },
{ id: "ok", type: "result", position: { x: 400, y: 0 }, data: {
title: "You can continue" } },
],
connections: [
{ id: "c1", from: { nodeId: "q1", portId: "yes" }, to: { nodeId: "ok", portId: "input" } },
],
};
</script>
The files follow the latest version. If you need a pinned one, take it from a GitHub Release and put it on your own site.
Route 2 — npm
The package lives on GitHub Packages. Point the scope there and put a token with
read:packages in your .npmrc:
@johanfuruskog-create:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_TOKEN
npm install @johanfuruskog-create/flowweaver-viewer
npm install @johanfuruskog-create/flowweaver-editor
Install either the viewer or the editor — the editor already embeds the viewer for its preview, and pulls the viewer package in as a dependency for its types and tokens.css.
import "@johanfuruskog-create/flowweaver-viewer";
// or
import "@johanfuruskog-create/flowweaver-editor";
TypeScript types are included. GraphData and the other graph types are re-exported from both entry points.
The editor
The same pattern, but you listen for changes and save them yourself.
feature-level controls how much the author sees.
<guide-editor feature-level="advanced"></guide-editor>
<script type="module">
import "@johanfuruskog-create/flowweaver-editor";
const editor = document.querySelector("guide-editor");
editor.graph = existingGraph;
editor.addEventListener("graph-changed", (event) => save(event.detail));
</script>
basic gives the stripped-down editor, advanced is the standard mode with variables and rules, and service is the profile for e-services. Hidden fields are never removed from the graph — an advanced guide can be opened in simple mode and then in advanced again, with no data lost.
Built for the editor, not the developer
Think about who opens this on an ordinary Tuesday. A rule changed this morning, someone is waiting for an answer, and the guide has to be right before it is seen by anyone who needs it. They are not short of knowledge — they know exactly what a visitor has to be asked. They are short of certainty about what the tool will do if they touch it.
Tools for this job usually end up developer-first, and it is rarely anyone's fault: the people writing the tool understand the model best, so the shortest path is to expose it and let the interface follow. It reads perfectly to whoever built it. Everyone else is quietly asked to learn a data structure before they may ask a question.
So they are careful. Careful looks like diligence and costs like delay: the change waits for a person who is not afraid of the system, and the guide drifts a little further from the rules it describes. A guide nobody dares to edit is a guide that goes out of date.
The goal is not that editing is possible. It is that it feels safe.
Most of that comes down to one thing: it has to be easy to go
back. Try it, look at what it did, take it back if it was
wrong. Every change is undoable. Hidden fields survive a switch of
feature-level, so nothing is lost by looking. Unticking a
language leaves the translations where they are. Nothing is destroyed,
so nothing has to be confirmed — and what the editor shows is what
gets saved, which is the promise everything else rests on.
Export and import
The toolbar writes the whole guide out as a JSON file and reads one back. It is there for the same reason as undo, one size larger: an author who can take a copy before a big change is an author who makes the change.
Worth showing your editors early, because the copy is theirs rather than yours. They can keep it, hand it to a colleague, sit on it over a weekend, or bring it back after an experiment that went nowhere — without asking anyone for a database restore or explaining what happened. Importing runs the same version migrations as loading, so a file from an older release opens in a newer one.
Languages
Three settings, and they move independently. Most of the mistakes made against this library come from treating them as one.
| Setting | Whose | Set with |
|---|---|---|
| The tool's language — palette, panel, menus | yours | editor-locale |
| The guide's content — questions, answers, results | the editor's and translator's | active-locale, and the toolbar |
| Which languages may be offered | yours | declareLocales() |
Translating the content into a language must never require the tool to be translated into the same language. Story 017, the rule the three settings exist to keep
A Somali guide does not wait for a Somali palette — that is what keeping the settings apart buys you.
Swedish and English are built in
Every text the package ships — buttons, field labels, validation messages, the whole editor — exists in both, complete. You do not register them and you do not maintain them. Nothing below is needed if those two are the languages you offer.
Declaring which languages exist
The editor shows one checkbox per declared language, and an editor ticks the ones a particular guide is offered in. The source language is always ticked and cannot be unticked.
import { init } from "@johanfuruskog-create/flowweaver-editor";
init(document.querySelector("guide-editor"), {
graph,
mode: "administrator",
languages: {
offer: ["sv", "en", "fi"], // what an editor may tick
source: "sv", // what a NEW guide is written in
content: "sv", // which language the editor opens showing
tool: "sv", // the interface's own language
},
onSave: (graph) => save(graph),
});
Leaving offer out is not the same as offering
nothing. An editor then sees only the languages a guide
already carries and cannot add one — which is right, because the list
is yours to decide, but it does mean nobody can add a language until
you have said which exist.
A third language
For a language the package does not ship, supply the texts. Split by
who reads them: viewer is what a visitor reads,
editor is the tool's own words. You can supply one without
the other — a visitor-facing pack is often all that is wanted.
const wiring = init(element, {
languages: { offer: ["sv", "en", "fi"], source: "sv" },
packs: {
fi: {
viewer: { "nav.next": "Seuraava", "nav.previous": "Edellinen" },
},
},
});
wiring.coverage.fi.viewer; // { filled: 2, total: 148, missing: [...] }
init hands back what each pack actually covers, so you
learn that a pack reaches 2 of 50 when you hand it over rather than
when a visitor meets the gap. localeStrings("sv") gives
you the package's own texts as a template to translate from.
The whole viewer, in a file of its own
Two keys inline is how it starts. A language you actually offer wants all of it, and that belongs in a file you can hand to a translator and keep in version control rather than in the middle of your setup code.
// locales/fi.js — one file per language, in your own repo.
export default {
viewer: {
"step.number": "Vaihe",
"step.result": "Tulos",
"nav.next": "Seuraava",
"nav.previous": "Edellinen",
"nav.restart": "Aloita alusta",
"validation.required": "Kenttä on pakollinen.",
// … 50 in all
},
};
import fi from "./locales/fi.js";
const wiring = init(editor, {
languages: { offer: ["sv", "en", "fi"], source: "sv" },
packs: { fi },
});
wiring.coverage.fi.viewer; // { filled: 148, total: 148, missing: [] }
148 keys is the whole visitor-facing surface — buttons, field labels, validation messages, the lot. Ask for the list before you start:
import { coverageOf } from "@johanfuruskog-create/flowweaver-editor";
coverageOf("fi").viewer.missing; // the 148 keys, as an array
coverageOf("fi").editor.missing; // the tool's own 686 — a different job
The second line is the one worth reading twice. Translating the viewer is 148 texts and gives a visitor the whole guide in their language. Translating the editor is 686 and gives an author a Finnish palette — worth doing one day, never a condition for the first. That is the split the two halves of a pack exist to make obvious.
What a visitor sees when something is missing
Nothing breaks and no box is empty. A missing text falls back, in this order:
the language asked for
→ its base language (en-GB falls to en)
→ the guide's own source (settings.sourceLocale)
→ English
→ whatever the text has
Note the third step: a Finnish reader of a Swedish guide gets Swedish for anything untranslated, not English. The source is the text the author actually wrote and the only one guaranteed to be complete.
And it is said rather than hidden. A visitor who asked for a language the guide does not have reads why:
This guide has not been translated into Finnish. It is shown in Swedish.
The notice appears on the steps where the language actually changed under them, and prefers English over the source — its whole purpose is to be read by someone who could not read the guide.
Checking a setup
checkSetup(element) reports what is missing
and what it leads to, including languages a guide is
offered in with no texts behind them.
import { checkSetup } from "@johanfuruskog-create/flowweaver-editor";
checkSetup(editor);
// { ok: false, findings: [{ id, severity, what, consequence }] }
The live version of all of this is on the languages page, which runs the real calls and prints what they returned.
What your page owns
The library deliberately has no side effects outside its components. So there are three things it does not do for you:
| Responsibility | What you have to do |
|---|---|
| Storage |
The editor does not autosave, and never claims to. Listen for graph-changed and save the graph wherever you want it. Ctrl+S sends a cancelable save-request: call preventDefault(), save, and say what happened — otherwise the editor only says the page handles saving.
|
| Versions |
<guide-versions> draws a guide's versions and raises an intent when
somebody wants something — it keeps nothing and decides nothing. Where the versions
live is yours to pick, and you answer eight functions over it: list, read, add,
update, copy, rename, remove, note. There are worked examples to copy from: the
versions page keeps them in the browser, and the
SiteVision module in the repository puts the same eight over three different storages.
|
| Theme |
tokens.css follows the operating system's light/dark setting. To control it yourself, set data-theme="dark" or "light" on
<html>.
|
Size
The viewer is 53 kB gzipped and the editor 127 kB. Only the viewer has to be loaded by end users — that is exactly why the bundles are separate.
Height
The editor's height is set with CSS variables, if the defaults do not suit you:
<guide-editor
style="--flowweaver-height: 800px; --flowweaver-min-height: 600px"
></guide-editor>
Further
Getting started with the editor describes how a guide is built. The start page collects more examples, from a simple guide to an e-service with calculations.