Panel
The pages the platform mounts, and the functions that draw its panel in a page of your own.
toolaby.js carries the client and the panel in one file. Most extensions never call anything here: the wiring puts the platform's own page in front of yours and it draws itself. These are for a page of your own that draws the panel — a popup the platform cannot stand in front of (Plasmo), or a layout you want to own. Surfaces says which case is yours.
Everything on this page is drawn from what the platform sends: the plans as your checkout words them, your brand's tokens, the buyer's language. None of it is yours to design — change Pricing or Branding in the dashboard and every panel follows.
mountPopup()
mountPopup(): voidMounts the platform's popup page. toolaby-popup.html — the shell the wiring writes — is two lines that call this. At every open it asks the gate and either opens your popup (appPopup) or draws the panel. Expects #app, #mark, #name, #status and #panel in the page.
mountSidePanel()
mountSidePanel(): voidThe same page on the side panel, at the panel's width: toolaby-sidepanel.html calls it, and it hands over to appSidePanel.
loadPaywall()
loadPaywall(config: PanelConfig): Promise<Paywall | null>The panel's facts for this tool in the buyer's language — the plans, the words, your brand's tokens. Cached on the device for a day, so it draws at once and offline; fetched again at the open after you change Pricing or Branding. config is what the background answers to { action: 'toolaby.config' }.
applyBrand()
applyBrand(paywall: Paywall | null): voidPuts your brand's tokens — accent, tint, corners, colour mode — on the document, where the panel's stylesheet turns them into the --pw-* variables below. A value of any other shape than the Wall sends — a colour as #rrggbb, corners in px — is left out, and the stylesheet's own stands. Call it once the facts have loaded; your own CSS can use the same variables.
ensureStyles()
ensureStyles(doc?: Document): voidThe panel's stylesheet, once. Call it before your own CSS so its tokens are there from the first paint.
It sizes the page as a 336px popup unless the page says otherwise:
<html data-toolaby-surface="sidepanel">which makes it fill a side panel instead. That attribute is the only difference between the two surfaces.
Your own CSS can use the panel's tokens, in your brand and in dark mode alike:
| Tokens | |
|---|---|
--pw-bg, --pw-frame, --pw-panel | The grounds: the page, the frame around a panel, a panel. |
--pw-line, --pw-line-soft | Rules and borders. |
--pw-ink, --pw-body, --pw-muted, --pw-faint | Text, from the strongest. |
--pw-on, --pw-on-fg | Your brand's accent, and the text on it. |
--pw-ok, --pw-ok-soft, --pw-warn, --pw-warn-soft | States. |
--pw-r-frame, --pw-r-panel, --pw-r-ctl | Corner radii: a frame, a panel, a control. |
--pw-shadow, --pw-ease, --pw-font | The lift, the easing and the font. |
renderMark()
renderMark(node: HTMLElement, name: string): voidThe header's mark: the extension's own icon from its manifest, else the initial of the name.
refusalOf()
refusalOf(user: User, permit: Permit | null): RefusalWhy this device may not continue, from the user and the permit that refused — sign_in, paid, limit or feature. Hand it to renderPaywall.
renderPaywall()
renderPaywall(root: HTMLElement, paywall: Paywall | null, user: User, config: PanelConfig, actions: PanelActions, refusal?: Refusal): voidThe panel for a device that is not unlocked: the sign-in doors, the free-use meter, the plans, or the plan that holds a refused feature.
renderUnlocked()
renderUnlocked(root: HTMLElement, paywall: Paywall | null, user: User, config: PanelConfig, actions: PanelActions, opts?: { celebrate?: boolean }): voidThe panel once unlocked: the plan held and how it stands — Renews 22 Oct, Lifetime licence, Trial until 29 Sep, Payment failed — the account it is on, and the way to the account page. celebrate pops the mark once, for the moment a purchase lands while the page is open.
On the Free plan of a tool whose plans sell a feature, it says which — Export to CSV is in Pro — with a button to that plan, rather than Unlocked beside a locked button.
renderNotice()
renderNotice(root: HTMLElement, title: string, body: string): voidA notice in place of everything else. What a build below the minimum version shows. See Versions.
plansWith()
plansWith(paywall: Paywall | null, feature: string, needs?: string[] | null): PaywallPlan[]The plans that hold a feature, the ones gate() named first, each { id, name, tier, features }; the price is in the paywall's options, as the checkout page words it. For a page that sells a feature in its own words rather than through renderPaywall.
renderAccountBar()
renderAccountBar(host: HTMLElement, paywall: Paywall | null, user: User, actions: PanelActions): voidThe account bar drawn into an element of your own, when the <toolaby-account> tag does not sit where you want it. One strip: the buyer's initial and email, the plan they hold, their account page, sign out.
defineAccountBar()
defineAccountBar(): voidDefines <toolaby-account>. The bundle calls it as soon as it is on a page, so the tag works with no call of yours; this is for a page that imports nothing else from the file.
Paywall
type Paywall = { brand: { accent, accentFg, darkAccent, darkAccentFg, radiusPx, mode, iconUrl } | null; words: Record<string, string>; … }What the platform sends for the panel. Opaque: hand it to the functions above. words is the panel's language; brand is Configure → Branding.
PanelConfig
type PanelConfig = { name: string; freeLimit?: number; slug?: string; siteUrl?: string }The tool as the background resolved it from your key — { action: 'toolaby.config' } answers with it, and it also carries appPopup and appSidePanel.
PanelActions
type PanelActions = { buy: (planId: string) => void; signIn: (provider?: 'google') => void; account?: () => void }What the panel's buttons do. The usual three are the client's own:
const actions = {
buy: (plan) => toolaby.openPaymentPage(plan),
signIn: (provider) => toolaby.openLoginPage(provider),
account: () => toolaby.openAccountPage(),
};Refusal
type Refusal = { kind: 'sign_in' } | { kind: 'paid' } | { kind: 'limit' } | { kind: 'feature'; feature: string; needs: string[] | null }Which screen the panel draws. refusalOf() works it out; you rarely build one by hand.