Install it
There is no hosted Wendmark. You run the portal, it serves the snippet, and your application loads it. Two steps: get the portal running, then point your app at it.
Run the portal locally
Section titled “Run the portal locally”Requirements: Node 22 (see .nvmrc), pnpm 9, and Docker for the
database.
git clone https://github.com/Wendmark/wendmark.gitcd wendmarkcorepack enablepnpm installpnpm bootstrappnpm devpnpm bootstrap starts Postgres, writes apps/portal/.env with a generated
AUTH_SECRET, applies the migrations and seeds an example organization, app
and process. Run it as often as you like: it never overwrites an existing
.env, and the migration and seed are both idempotent.
Already have a Postgres you would rather use? Copy
apps/portal/.env.example, point DATABASE_URL at it, and pnpm bootstrap
leaves your database alone and does the rest.
That gives you:
- Portal at
http://localhost:3100— sign in asdev@example.comwith the password the seed printed - Fika at
http://localhost:5174— a pretend coffee-subscription app to try guidance against, with the snippet already installed
Add your own app
Section titled “Add your own app”In the portal, add an app and give it the origins its snippet may be used
from — https://app.example.com, your staging address, http://localhost:…
for development. Anything not on that list is refused, so a key lifted from
your page is no use anywhere else.
The portal then gives you a tag:
<script src="https://your-portal.example.com/snippet.js" data-app="wm_…"></script>Paste it into your application and reload. The portal’s page for that app stops saying “waiting” once it has seen the snippet, so you know the tag is live before you record anything.
The key is not a secret. It identifies an app; it does not authenticate anyone. Permission to change a process always comes from a portal session.
What the snippet does to your page
Section titled “What the snippet does to your page”As little as possible, by design:
- Shadow DOM. Everything renders inside one host element with an open
shadow root and
:host { all: initial }. Your CSS cannot reach it and its CSS cannot reach you. - One global.
window.Wendmark, and nothing else. No prototype patching, no polyfills touching built-ins. - No framework, and a 30 KB gzipped budget enforced in CI. It is currently 18.7 KB.
- Nothing in your interface unless you ask. The launcher button is off by default.
Showing people the processes
Section titled “Showing people the processes”Either turn on the launcher in the portal — a button in the corner, named whatever suits your product — or leave it off and call the API from your own help menu:
Wendmark.openLauncher() // the list of processesWendmark.listFlows() // if you would rather render it yourselfWendmark.start('<flow-id>') // start one directlyWendmark.reportProblem() // let them report what went wrongA process can also be hidden, which keeps it out of the launcher while
start(id) still plays it. That is how you attach guidance to one of your own
buttons without it appearing in a general list.
reportProblem() only does something once problem reports are switched on for
the app. See Let users report a problem.
Running it for real
Section titled “Running it for real”cp .env.prod.example .env.prod # three values to fill indocker compose -f docker-compose.prod.yml --env-file .env.prod up -dDeploying covers the settings, the Content Security Policy your customers’ apps need, key rotation, what is stored and upgrading.