Help on every option and every page

- Each option's explanation moves from a line of text under its label to
  a small tooltip on an ⓘ beside it, so forms read calmer and the help is
  still one hover away.
- Help text is ours where written (src/help/texts.ts: domains, people,
  DNS providers, blocked addresses, and the main pages), and the schema's
  description elsewhere.
- A "?" on every list and form opens a side panel: what the page is for,
  what people usually do there, and every option explained.
- Every tooltip and panel carries a stable help id (x:Domain.dnsManagement,
  x:Domain), and manual.ts turns an id into a link to the admin manual
  once one is configured (VITE_MANUAL_URL, or <meta name="manual-url">).
  Until then no link shows.
This commit is contained in:
2026-09-19 01:59:14 -07:00
parent 7ab0b8099c
commit 824427ef46
12 changed files with 512 additions and 18 deletions
+153
View File
@@ -0,0 +1,153 @@
/*
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* INBUXA's own help, in plain words, keyed by help id. Options without an
* entry here fall back to the description the server's schema gives. Write
* new entries in our own words: say what the option does for the person
* using it, and what goes wrong if it's set badly. Keep a tooltip to one
* or two sentences; the manual is where the detail goes.
*
* Keys: `object` for a page, `object.field` for an option on it.
*/
export const FIELD_HELP: Record<string, string> = {
// Domains
'x:Domain.name': 'The domain peoples addresses end in, like example.com.',
'x:Domain.aliases':
'Other domains that deliver to the same people. Mail to [email protected] lands in [email protected].',
'x:Domain.isEnabled': 'Turn off to stop accepting mail for this domain without deleting anything.',
'x:Domain.catchAllAddress':
'Where mail to addresses that dont exist goes. Handy for small teams; on a busy domain it collects spam.',
'x:Domain.subAddressing':
'Lets people use [email protected] to sort or trace their mail. On for most domains.',
'x:Domain.allowRelaying':
'Forward mail for unknown people to another server, for domains split between two systems. Leave off otherwise.',
'x:Domain.dkimManagement':
'Signing keys that prove mail from this domain is really yours. Automatic creates and rotates them for you.',
'x:Domain.certificateManagement':
'The TLS certificate for this domains mail and web addresses. Automatic gets and renews one for you.',
'x:Domain.dnsManagement':
'Whether the server writes this domains DNS records itself through your DNS host, or you add them by hand.',
'x:Domain.reportAddressUri':
'Where other mail servers send reports about mail claiming to be from you (DMARC, TLS). Postmaster is a good choice.',
'x:Domain.memberTenantId': 'The customer or organization this domain belongs to, if you host more than one.',
'x:Domain.directoryId': 'Where this domains accounts and passwords are kept: here, or an outside directory.',
'x:Domain.logo': 'A logo for this domains sign-in page and mail apps. A link or an uploaded image.',
// People
'x:UserAccount.name': 'The part before the @. Together with the domain it makes the persons address.',
'x:UserAccount.description': 'The persons full name, as others see it.',
'x:UserAccount.aliases': 'More addresses that deliver to this person.',
'x:UserAccount.quotas':
'Limits for this person, like how much storage they may use. Empty means the servers defaults.',
'x:UserAccount.roles': 'What this person may do. Most people are plain users; admins manage the server.',
'x:UserAccount.memberGroupIds': 'Groups this person belongs to. They share the groups mail and can send as it.',
'x:UserAccount.credentials': 'How this person signs in: a password, app passwords for mail apps, and more.',
'x:UserAccount.locale': 'The language for messages the server sends this person.',
'x:UserAccount.timeZone': 'Used for calendar invitations and scheduled messages.',
'x:GroupAccount.name': 'The groups address, before the @. Mail to it reaches every member.',
// DNS providers
'x:DnsServerCloudflare.secret':
'A Cloudflare API token that can edit DNS for your zone. Make it with the “Edit zone DNS” template.',
'x:DnsServerCloudflare.email': 'Only for the old Global API Key. Leave empty when you use an API token.',
'x:DnsServerCloud.secret': 'The API token or key from your DNS host. Give it DNS access for this domain only.',
'x:DnsServerCloudflare.ttl': 'How long other servers may cache the records written. Five minutes is a good default.',
'x:DnsServerCloud.ttl': 'How long other servers may cache the records written. Five minutes is a good default.',
// Security
'x:BlockedIp.address': 'An address or network, like 203.0.113.7 or 203.0.113.0/24, refused before it can talk.',
'x:BlockedIp.reason': 'A note for yourself on why it was blocked.',
'x:BlockedIp.expiresAt': 'When the block lifts by itself. Empty means it stays until you remove it.',
'x:AllowedIp.address': 'An address or network that is never blocked automatically, like your office or monitoring.',
};
export interface PageHelp {
/** What the page is for, in a sentence or two. */
about: string;
/** The things people come here to do. */
tasks?: string[];
}
export const PAGE_HELP: Record<string, PageHelp> = {
'x:Domain': {
about: 'The domains this server receives and sends mail for. Each persons address belongs to one of them.',
tasks: [
'Add a domain, then publish its DNS records so mail can find you.',
'Let the server publish DNS for you: open a domain and use “Set it up” in its DNS section.',
'Turn on automatic DKIM and certificates so keys and certificates renew themselves.',
],
},
'x:Account/User': {
about: 'Everyone with a mailbox here. Each person has an address, a password and, optionally, limits.',
tasks: [
'Add a person and give them a password.',
'Give someone more addresses with aliases.',
'Set a storage limit under quotas.',
],
},
'x:Account/Group': {
about: 'Shared mailboxes, like sales@ or support@, that several people read and send from.',
tasks: ['Create a group, then add people to it from their own page under Groups.'],
},
'x:MailingList': {
about: 'Addresses that pass each message on to a list of recipients, inside or outside this server.',
},
'x:Tenant': {
about: 'Separate customers or organizations on one server, each with their own domains, people and limits.',
},
'x:Role': {
about: 'Named sets of permissions. Give a role to a person to let them do more, or less.',
},
'x:OAuthClient': {
about: 'Apps allowed to sign people in through this server, like INBUXA webmail and INBUXA Admin.',
},
'x:DkimSignature': {
about: 'The keys that sign outgoing mail so receivers can check it really came from you.',
tasks: ['Let domains manage their own keys: set DKIM to automatic on the domain.'],
},
'x:QueuedMessage': {
about:
'Mail waiting to go out. Most leaves within seconds; what stays here is waiting for a server that isnt answering.',
tasks: [
'See why a message is stuck: open it and look at each recipients status.',
'Retry now, or cancel mail that will never be delivered.',
],
},
'x:DnsServer': {
about: 'Connections to your DNS hosts, so the server can publish and update its own DNS records.',
tasks: ['Connect one the easy way: open a domain and use “Set it up” in its DNS section.'],
},
'x:BlockedIp': {
about: 'Addresses refused before they can talk to the server. The server adds some itself when it spots attacks.',
tasks: ['Unblock someone: find their address and delete the entry.'],
},
'x:AllowedIp': {
about: 'Addresses the server never blocks by itself, like your office network or monitoring.',
},
'x:DmarcExternalReport': {
about:
'Reports from other mail providers on mail they received claiming to be from your domains, and whether it passed.',
},
'x:TlsExternalReport': {
about: 'Reports from other mail providers on whether they could reach you over an encrypted connection.',
},
'x:Task': {
about: 'Background work the server has scheduled: DNS updates, key rotation, certificate renewal and upkeep.',
},
'x:Task/TaskFailed': {
about: 'Background work that failed. Each entry says why; most retry by themselves once the cause is fixed.',
},
'x:Log': {
about: 'What the server has been doing, newest first. Useful for tracing a problem back to its cause.',
},
};
/** The help text for an option: ours when written, else the schema's. */
export function fieldHelp(id: string | undefined, fallback?: string | null): string | null {
return (id && FIELD_HELP[id]) || fallback || null;
}