// ========================================== // Armature — Workflow Demo Surface // ========================================== // Interactive walkthrough of the four example workflow packages. // Shows workflow cards with feature badges, stage flow diagrams, // Starlark code viewer, and API curl examples. // ========================================== (function () { 'use strict'; const SURFACE_ID = 'workflow-demo'; if (window.__SURFACE__ !== SURFACE_ID) return; const base = window.__BASE__ || ''; // ── Workflow catalog ────────────────────────── const WORKFLOWS = [ { slug: 'bug-report-triage', title: 'Bug Report Triage', icon: '🐛', description: 'Public bug report submission with severity-based routing, team assignment, and SLA timers.', badges: ['Public Entry', 'Branch Rules', 'SLA Timer', 'Team Assignment'], tier: 'browser', stages: [ { name: 'submit', mode: 'form', audience: 'public' }, { name: 'classify', mode: 'form', audience: 'team', note: 'branch rules' }, { name: 'fix-critical', mode: 'form', audience: 'team', note: 'SLA 1h', branch: true }, { name: 'fix-normal', mode: 'form', audience: 'team', branch: true }, { name: 'verify', mode: 'review', audience: 'team' }, ], curl: [ '# 1. Start public instance (no auth required)\ncurl -X POST $BASE/api/v1/public/workflows/$WF_ID/start \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"reporter_email": "user@example.com", "summary": "Login broken", "component": "ui", "severity": "critical", "steps": "1. Click login", "expected": "Login page", "actual": "500 error"}}\'', '# 2. Resume (check status)\ncurl $BASE/api/v1/public/workflows/resume/$TOKEN', '# 3. Team member claims classify assignment\ncurl -X POST $BASE/api/v1/teams/$TEAM/assignments/$ASSIGN_ID/claim \\\n -H "Authorization: Bearer $TOKEN"', '# 4. Advance classify (triggers branch rule)\ncurl -X POST $BASE/api/v1/teams/$TEAM/instances/$INST/advance \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"severity": "critical", "notes": "Confirmed production issue"}}\'', ], }, { slug: 'employee-onboarding', title: 'Employee Onboarding', icon: '🏢', description: 'Automated provisioning with Starlark hooks, manager signoff gate, and welcome notification.', badges: ['Starlark', 'Signoff Gate', 'Rejection Reroute', 'db.insert', 'notifications.send'], tier: 'starlark', stages: [ { name: 'new-hire-info', mode: 'form', audience: 'team' }, { name: 'provision-accounts', mode: 'automated', audience: 'system', note: 'Starlark' }, { name: 'manager-signoff', mode: 'review', audience: 'team', note: 'signoff gate' }, { name: 'it-setup', mode: 'form', audience: 'team' }, { name: 'welcome', mode: 'automated', audience: 'system', note: 'Starlark' }, ], starlark: 'on_provision(ctx): db.insert("provisions", ...), notifications.send(...)\non_welcome(ctx): db.insert("onboarding_log", ...), notifications.send(...)', curl: [ '# 1. Start instance (team auth required)\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"full_name": "Alice Smith", "department": "engineering", "needs_vpn": "true", "needs_admin": "false", "equipment": "developer workstation"}}\'', '# 2. After auto-provision, submit manager signoff\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $MANAGER_TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"action": "approve"}\'', ], }, { slug: 'content-approval', title: 'Content Approval', icon: '📝', description: 'Multi-party review with quorum signoff (2 approvals) and revision cycle loop.', badges: ['Multi-party Signoff', 'Quorum', 'Rejection Reroute', 'Review Cycle'], tier: 'browser', stages: [ { name: 'draft', mode: 'form', audience: 'team' }, { name: 'review', mode: 'review', audience: 'team', note: '2 approvals' }, { name: 'revision', mode: 'form', audience: 'team', note: 'loops to review' }, { name: 'publish', mode: 'form', audience: 'team' }, ], curl: [ '# 1. Submit draft\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"title": "Q1 Update", "body": "...", "category": "blog"}}\'', '# 2. Reviewer A approves\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_A" \\\n -d \'{"action": "approve"}\'', '# 3. Reviewer B rejects -> routes to revision\ncurl -X POST $BASE/api/v1/instances/$INST/signoffs \\\n -H "Authorization: Bearer $REVIEWER_B" \\\n -d \'{"action": "reject"}\'', ], }, { slug: 'webhook-notifier', title: 'Webhook Notifier', icon: '🔔', description: 'Outbound HTTP delivery via Starlark with connection credentials and delivery logging.', badges: ['Starlark', 'http.post', 'connections.get', 'db.insert'], tier: 'starlark', stages: [ { name: 'configure', mode: 'form', audience: 'team' }, { name: 'fire-webhook', mode: 'automated', audience: 'system', note: 'http.post' }, { name: 'result', mode: 'review', audience: 'team' }, ], starlark: 'on_fire(ctx): http.post(url, body=payload), db.insert("webhook_log", ...)', curl: [ '# 1. Configure and start\ncurl -X POST $BASE/api/v1/teams/$TEAM/workflows/$WF_ID/start \\\n -H "Authorization: Bearer $TOKEN" \\\n -H "Content-Type: application/json" \\\n -d \'{"data": {"webhook_url": "https://webhook.site/test", "event_name": "deploy.complete", "payload": "{}"}}\'', ], }, ]; // ── Init ───────────────────────────────────── async function _init() { const mount = document.getElementById('extension-mount'); if (!mount) return; const surface = document.createElement('div'); surface.className = 'surface-workflow-demo'; mount.appendChild(surface); // Topbar with user menu const topbar = document.createElement('div'); topbar.className = 'demo-topbar'; topbar.innerHTML = '
' + _esc(wf.starlark) + ''));
}
// API examples
if (wf.curl && wf.curl.length) {
const curlHtml = wf.curl.map(function (c) { return '' + _esc(c) + ''; }).join('');
card.appendChild(_buildCollapsible('API Examples', curlHtml));
}
// Status + action
const actions = document.createElement('div');
actions.className = 'card-actions';
if (installedWf) {
const isActive = installedWf.is_active;
const version = installedWf.version || 0;
// version > 1 means published at least once (version increments on edit, publish snapshots it)
const isPublished = version >= 2;
const isReady = isActive && isPublished;
// Installed badge
const status = document.createElement('span');
status.className = 'badge badge-installed';
status.textContent = 'Installed';
actions.appendChild(status);
if (!isActive) {
const hint = document.createElement('span');
hint.className = 'badge badge-needs-publish';
hint.textContent = 'Not Active';
actions.appendChild(hint);
} else if (!isPublished) {
const hint = document.createElement('span');
hint.className = 'badge badge-needs-publish';
hint.textContent = 'Not Published';
actions.appendChild(hint);
}
const tryBtn = document.createElement('button');
tryBtn.className = 'btn btn-primary';
tryBtn.textContent = 'Try It';
tryBtn.disabled = !isReady;
if (!isReady) {
tryBtn.title = 'Activate and publish this workflow in Team Admin first';
}
tryBtn.onclick = function () {
if (!isReady) return;
if (wf.slug === 'bug-report-triage') {
window.open(base + '/api/v1/public/workflows/' + installedWf.id + '/form', '_blank');
} else {
sw.toast('Navigate to your team admin to start a ' + wf.title + ' instance.', 'info');
}
};
actions.appendChild(tryBtn);
// Public link for public_link workflows
if (installedWf.entry_mode === 'public_link' && isReady) {
const linkRow = document.createElement('div');
linkRow.className = 'public-link-row';
const linkInput = document.createElement('input');
linkInput.type = 'text';
linkInput.readOnly = true;
linkInput.value = window.location.origin + base + '/api/v1/public/workflows/' + installedWf.id + '/start';
linkRow.appendChild(linkInput);
const copyBtn = document.createElement('button');
copyBtn.className = 'btn-copy';
copyBtn.textContent = 'Copy';
copyBtn.onclick = function () {
navigator.clipboard.writeText(linkInput.value).then(function () {
copyBtn.textContent = 'Copied!';
setTimeout(function () { copyBtn.textContent = 'Copy'; }, 1500);
});
};
linkRow.appendChild(copyBtn);
card.appendChild(linkRow);
}
} else {
const status = document.createElement('span');
status.className = 'badge badge-not-installed';
status.textContent = 'Not Installed';
actions.appendChild(status);
}
card.appendChild(actions);
return card;
}
// ── Stage flow diagram ───────────────────────
function _buildStageDiagram(wf) {
const container = document.createElement('div');
container.className = 'stage-flow';
const stages = wf.stages;
let hasBranch = false;
for (let i = 0; i < stages.length; i++) {
const s = stages[i];
const node = document.createElement('div');
node.className = 'stage-node stage-' + s.audience;
const label = document.createElement('div');
label.className = 'stage-label';
label.textContent = s.name;
node.appendChild(label);
const meta = document.createElement('div');
meta.className = 'stage-meta';
meta.textContent = s.mode + (s.audience !== 'team' ? ' (' + s.audience + ')' : '');
node.appendChild(meta);
if (s.note) {
const note = document.createElement('div');
note.className = 'stage-note';
note.textContent = s.note;
node.appendChild(note);
}
container.appendChild(node);
// Arrow (except after last stage)
if (i < stages.length - 1 && !stages[i + 1].branch) {
const arrow = document.createElement('div');
arrow.className = 'stage-arrow';
arrow.textContent = '\u2192';
container.appendChild(arrow);
} else if (stages[i + 1] && stages[i + 1].branch) {
if (!hasBranch) {
const split = document.createElement('div');
split.className = 'stage-arrow stage-branch';
split.textContent = '\u2193\u2197';
container.appendChild(split);
hasBranch = true;
}
}
}
return container;
}
// ── Collapsible section ──────────────────────
function _buildCollapsible(title, innerHtml) {
const details = document.createElement('details');
details.className = 'card-collapsible';
const summary = document.createElement('summary');
summary.textContent = title;
details.appendChild(summary);
const content = document.createElement('div');
content.className = 'collapsible-content';
content.innerHTML = innerHtml;
details.appendChild(content);
return details;
}
// ── Helpers ───────────────────────────────────
function _esc(s) {
const d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
// ── Boot ──────────────────────────────────────
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', _init);
} else {
_init();
}
})();