Toolaby Wall
Reference

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(): void

Mounts 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(): void

The 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): void

Puts 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): void

The 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-panelThe grounds: the page, the frame around a panel, a panel.
--pw-line, --pw-line-softRules and borders.
--pw-ink, --pw-body, --pw-muted, --pw-faintText, from the strongest.
--pw-on, --pw-on-fgYour brand's accent, and the text on it.
--pw-ok, --pw-ok-soft, --pw-warn, --pw-warn-softStates.
--pw-r-frame, --pw-r-panel, --pw-r-ctlCorner radii: a frame, a panel, a control.
--pw-shadow, --pw-ease, --pw-fontThe lift, the easing and the font.

renderMark()

renderMark(node: HTMLElement, name: string): void

The header's mark: the extension's own icon from its manifest, else the initial of the name.

refusalOf()

refusalOf(user: User, permit: Permit | null): Refusal

Why 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): void

The 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 }): void

The 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): void

A 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): void

The 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(): void

Defines <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.

On this page