@myandgo/help
v0.1.0
Published
The AndGo fleet's embeddable in-app help surface. One widget, every product embeds it.
Readme
@myandgo/help
The AndGo fleet's embeddable in-app help surface. One widget, every product embeds it.
A user asks a question inside your product. Your assistant answers from your knowledge base if it can. If it cannot, the conversation becomes a tracked SupportAndGo ticket, and the agent's reply comes back into the same thread the user is already looking at.
- ~11 kB gzipped, Preact, no host framework required
- Renders in a closed shadow root, so your CSS and the widget's cannot touch each other
- Config, not forks: branding, copy, suggestions and page context are all passed in
- React binding included; also mounts into Vue, Svelte, or plain HTML
Install
npm install @myandgo/helpUse it
React:
import { HelpWidget } from "@myandgo/help/react";
<HelpWidget
baseUrl="/api"
branding={{ productName: "WorkAndGo", colour: "#1e3a5f", assistantName: "Jeeves" }}
getHeaders={async () => ({ Authorization: `Bearer ${await getIdToken()}` })}
getPageContext={() => ({ route: location.pathname })}
suggestions={["How do I submit a timesheet?", "Why is my shift missing?"]}
/>;Anything else:
import { mountHelp } from "@myandgo/help";
const unmount = mountHelp({
baseUrl: "/api",
branding: { productName: "WorkAndGo", colour: "#1e3a5f" },
});The host contract
The widget is a thin client. Your backend implements three endpoints, and owns the conversation, the knowledge base, the escalation decision and the user's identity.
GET {baseUrl}/support/help/config
{ "enabled": true }Whether this user gets the surface at all. The widget renders nothing unless this
returns enabled: true — a 404, a 401, a network failure or a missing field all mean no.
Fails closed on purpose: a help button that errors when clicked is worse than no button.
Use it as your pilot-cohort flag.
GET {baseUrl}/support/help
{
"conversation_id": "sup_a1b2c3",
"status": "escalated",
"ticket_status": "pending",
"turns": [
{ "role": "user", "text": "My shift is missing", "at": "2026-07-26T01:00:00Z" },
{ "role": "assistant", "text": "Let me check that…", "at": "2026-07-26T01:00:01Z" },
{ "role": "agent", "text": "Fixed — refresh.", "at": "2026-07-26T01:04:00Z", "from_name": null }
]
}The user's current thread. Polled every 8 s while the panel is open and the tab is visible (never in a background tab). Return the whole thread every time — the widget dedupes.
role—user,agent(a human on the desk), or anything else, which is treated as your assistant and labelled with yourassistantName.at— ISO 8601. The sort key. Stamp every turn, or ordering falls back to arrival order and messages can shuffle between polls.from_name— the agent's name, ornullwhile agents are unnamed (renders "Support").ticket_status—open|pending|closed, once escalated. There is deliberately noresolved: closed is resolved. An unrecognised value renders no status at all rather than a wrong one.
POST {baseUrl}/support/help/message
{ "text": "My shift is missing", "context": { "route": "/timesheets" } }Returns the updated thread, same shape as above. context is present only if you passed
getPageContext — if your request schema is strict, simply don't pass it and it will never
appear.
Identity — read this bit
The widget never tells you who the user is, by design. It sends your session
credentials (cookies by default, or whatever getHeaders returns) and nothing else. Your
backend derives the identity from that authenticated session and sets, when it forwards to
SupportAndGo:
requester.uid— the platform myandgo_uid. This is the fleet's canonical join key. Email is loose: it changes, it collides across products, and it is not a stable identity.requester.orgId— your product's business/tenant reference.
A uid asserted by the browser would be spoofable, and because that uid is the join key for
cross-product support history, one product trusting a client-supplied value would corrupt
attribution for every product. Deriving it server-side from a verified session costs
nothing and cannot be forged. WorkAndGo's relay already does exactly this
(requester_uid=user["myandgo_uid"]).
The widget carries no secrets and never talks to SupportAndGo directly. It cannot: the ingest endpoint is HMAC-signed with a per-product secret, and a browser is not a place a secret can live.
Options
| Option | Default | What it does |
| --- | --- | --- |
| baseUrl | — | Where your three endpoints live. Required. |
| branding.productName | — | Header text. Required. |
| branding.colour | #111827 | Launcher, user bubbles, focus rings. |
| branding.logoUrl | — | Shown in the panel header. |
| branding.assistantName | Assistant | What you call your bot. |
| branding.agentFallbackName | Support | Label for an unnamed human. |
| copy | sensible defaults | Every user-facing string. |
| position | right | left or right. |
| getHeaders | — | Resolved on every request, so refreshed tokens keep working. |
| credentials | include | Passed to fetch. |
| getPageContext | — | Opt-in context sent with each message. |
| suggestions | — | Opening prompts, shown on an empty thread. |
| pollIntervalMs | 8000 | Minimum 2000. |
| defaultOpen | false | Start with the panel open. |
| onOpenChange | — | For your own analytics. |
| target | document.body | Mount point. |
Behaviour worth knowing
- Optimistic send. Your message appears instantly and is reconciled with the server's copy when it echoes back. If the send fails, the bubble is removed and your text is put back in the box — nobody loses what they typed.
- Polling pauses when the tab is hidden and resumes (with an immediate refresh) when you come back.
- Enter sends, Shift+Enter is a newline.
- Mobile: full-screen panel under 460 px.
- Accessibility: the thread is an
aria-livelog, the panel is a labelled dialog, focus rings use your brand colour, and animation respectsprefers-reduced-motion.
Adopting it if you already piloted your own surface
The three endpoints above are the shape WorkAndGo's pilot relay already serves
(/api/support/help/config, /api/support/help, /api/support/help/message). If yours
matches, delete your bespoke UI and mount this — no backend change. If your roles differ,
they still work: anything that isn't user or agent renders as your assistant.
Development
npm install
npm test # 32 tests: thread merge/dedupe/order, status mapping, transport contract
npm run build # dist/help.js + dist/react.js + types
npm run demo # a mock host on http://localhost:5174 — no backend needed