The Firefox extension
Firefox on the desktop cannot install hypercal as an app the way Chrome can. The extension fills that gap: your agenda in the toolbar and the sidebar, quick add from anywhere, reminders as Firefox notifications, and .ics links that import with a right click. It runs on Firefox 140 or later, and on Firefox for Android 142 or later (everything except the sidebar).
It talks only to your own hypercal server, with an app password. Nothing goes anywhere else.
Installing it
Every release has the extension as hypercal-<version>.xpi among its assets.
The .xpi is unsigned
It is not on addons.mozilla.org yet, and Mozilla signs only what goes through there. Firefox release and ESR refuse an unsigned add-on, with no way around it. LibreWolf, Firefox Developer Edition and Nightly accept one once you allow it:
- Open
about:configand setxpinstall.signatures.requiredtofalse. To keep it across updates, putuser_pref("xpinstall.signatures.required", false);in the profile'suser.jsinstead. - Open the downloaded
.xpi(drag it into a window, or open it as a file) and confirm the install prompt.
That setting lets any unsigned add-on install in that profile, not only this one. In Firefox release, load it as a temporary add-on instead (see Building it); Firefox removes it when it restarts.
Setting it up
- An admin turns on Settings → Admin → Browser extension access. It is off by default. Turning it on lets every app password on the instance reach the extension API at
/ext, including ones made for a phone, so say so on a shared instance. Each person still only ever sees their own calendar. - Make an app password in Settings → Connectors → App passwords. Choose Read & write to add events from the browser, or Read only for an agenda you cannot change from there.
- Open the extension's settings (they open by themselves on first install), enter the server address and the app password, and press Save and connect. Firefox asks for permission to reach that one host. The permission covers every port on it, because Firefox permissions cannot name a port.
To stop, press Disconnect, then revoke the app password in hypercal. Disconnecting alone leaves the password valid.
What it does
| Where | What |
|---|---|
| Toolbar button | Today and the next week. The badge counts down to your next timed event: minutes under an hour, hours under a day, now while it runs. Click an event to open it in hypercal. |
| Sidebar | The same agenda, kept open beside whatever you are reading. Alt+Shift+Y toggles it. |
| Quick add | The box at the top of the popup and the sidebar, and cal in the address bar: cal fri 14:00 standup. The line is read back to you before it is saved. A link in it becomes the location. |
| Join | A Join button on events with a meeting link (Zoom, Meet, Teams, Jitsi, Whereby, Webex and a few more). Alt+Shift+J opens the one that is on now or starts within 15 minutes. |
.ics links | Right-click: Import into hypercal copies the events in; Subscribe in hypercal adds the feed as a calendar that stays up to date. Import asks for permission to read that one site. |
| Selected text | Right-click, Add … to hypercal: hypercal's new-event editor opens in a tab, filled in from the text. Nothing is saved until you save there. |
| Reminders | Your reminders, worked out exactly as the server does, shown as Firefox notifications. Click one to join the meeting or open the event. |
Quick add understands today, tomorrow, weekdays (fri, next fri), 2026-10-03, 3.10., 3 oct, times such as 14:00, 3pm or at 9, ranges such as 9-10:30 or 2pm to 4pm, durations such as 90m or 1h30, and all day. A line with no time is an all-day event. Anything else stays in the title. For anything more, open the event in hypercal.
Adding selected text
Select an invitation, a booking confirmation or a line on a page, right-click it and choose Add … to hypercal. The editor opens on the day the text names, with:
- the date: the first one in the text. It can be written as
2026-10-03,3.10.,03.10.2026,3 October,October 3rd, 2026,3. OktoberorSamstag, 3. Oktober, or astoday,tomorrow,Friday,next Friday,heute,morgen,übermorgenornächsten Freitag. A day and month that have passed this year mean next year; - the time, when one is written on the same line as the date or the line after:
14:00,2pm,2:30 p.m.,14 Uhr,14.30 Uhr,at 9,um 9, and ranges such as2pm to 4pm,14:00 - 15:30orvon 9 bis 10 Uhr. A zone after it (6 PM EST,18:00 CEST) is honoured. A date with no time, or withall dayorganztägig, makes an all-day event; - the title: the first line of the text, without the date and time;
- the location: the first link in the text, such as a meeting URL;
- the description: the whole selection.
Plain text with no date opens the editor at its usual default, titled with the text. If you are signed out, hypercal asks you to sign in first, then opens the editor.
A bare 9-10 or 14.30 is not read as a time, since it is as likely a room or a price; nor are short words such as sun, sat, so or mi read as weekdays, unless written Sat. or Mi.,. Anything missed is one correction in the editor away.
Two things to know
- Double reminders. If this browser also gets hypercal's own push notifications, every reminder arrives twice. Turn one of them off: the extension's is in its settings, hypercal's in Settings → Notifications.
- Encrypted calendars show as "Busy". Their text is encrypted with a key that only a signed-in hypercal tab unlocks, and the extension holds an app password, not that key. For the same reason quick add cannot file into an encrypted calendar.
Troubleshooting
"This add-on could not be installed because it has not been verified." The browser requires signed add-ons and the .xpi is unsigned. Set xpinstall.signatures.required to false, as in Installing it. Firefox release and ESR ignore that setting.
"Could not reach the server." The request never got an answer. In order of likelihood:
- HTTPS-Only Mode. It rewrites
http://addresses tohttps://, where a server without TLS, such asnpm run dev, is not listening. Firefox leaves local addresses (localhost,127.0.0.1, the local network) alone by default. LibreWolf upgrades them too. Setdom.security.https_only_mode.upgrade_localtofalse, inabout:configor as auser_prefinuser.js, to exempt local addresses only. Every other site is still upgraded. A public server should be onhttps://anyway, and then this does not come up. - A permission from an older build. The first builds asked Firefox for the server with its port, such as
http://127.0.0.1:5173/*. Firefox stores a pattern like that but never matches it, since its permissions cannot name a port. The requests then went out without the permission, as ordinary cross-origin ones the server does not allow, and failed. Current builds ask for the host without a port. After updating, press Save and connect again so the extension asks for the permission it now needs. - The address is wrong, or the server is down. Open the address in a tab.
"That address answered, but not as a hypercal server with the extension API." Something answered with a web page instead: a different site, a hypercal server from before the extension API, or a dev server that does not pass /ext through. With npm run dev, use http://localhost:5173, which does.
"Browser extension access is switched off on this server." An admin turns it on in Settings → Admin → Browser extension access.
"The app password was not accepted." It was revoked, mistyped, or belongs to another server. Make a new one and paste it in.
Building it
The extension is the extension/ workspace. It needs nothing beyond the repository's own npm install.
npm run build -w extension # writes extension/dist
npm start -w extension # builds, then runs Firefox with it loaded (web-ext)
npm run lint:ext -w extension # Mozilla's addons-linter over the build
npm run package -w extension # extension/release/hypercal-<version>.xpiTo load a build by hand, open about:debugging#/runtime/this-firefox, choose Load Temporary Add-on, and pick extension/dist/manifest.json.
Against npm run dev, use the address you open the app at, http://localhost:5173: Vite passes /ext through to the server on 3001, as it does /api. In LibreWolf that needs one setting first; see Troubleshooting.
A release builds the same .xpi in the build-extension job and publishes it with the other packages; see the release workflow.
The bundle is not minified, so addons.mozilla.org can review it as it is. web-ext is fetched on demand with npx rather than installed with the workspace, since only these scripts need it.
How it is built
src/background.tsis the event page: it fetches the agenda intostorage.local, draws the badge, follows the live-update stream, sets one alarm per reminder, and owns the menus, the address-bar keyword and the shortcuts. Firefox unloads it when idle, so everything it knows is in storage, and alarms every minute and every five minutes wake it to redraw and refetch.src/popup.tsdraws both the popup and the sidebar from that cache, so they open at once and redraw when it changes.src/lib/is the tested part: the quick-add parser (quickAdd.ts), the selection reader (extract.ts) and the date and time primitives both use (when.ts), agenda grouping, reminder resolution, join-link detection and the/extclient.- Selected text opens a link of the form
/?view=day&date=…&new=1&title=…, whichweb/src/lib/createLink.tsreads once and removes from the address, so a reload does not open the editor again. - It reuses
resolveRemindersandnormalizeFeedUrlfromsharedand the colour palette fromweb, importing those files directly: thesharedpackage's entry point would bring zod into the bundle for two small functions.
The server side is the extension API, and the security model covers what an app password can reach there.