Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ numbers below were the site's earlier, hand-kept numbering and are retired; the

### Added

- **Firefox.** The extension is on Firefox Add-ons (version 0.16.3). Every page now says it runs in Chrome, Chromium browsers and Firefox, the extension page offers "Add to Firefox" beside "Add to Chrome", and the privacy policy has an "In Firefox" section (claim links are pasted; Firefox's storage and Sync; school policy through policies.json; what the listing declares to Mozilla). The terms and the vulnerability disclosure scope cover Firefox too.
- **The Chrome Web Store numbers on the home page.** The proof section
says what the store listing shows, dated: rated 5.0 out of 5 (1 rating),
27 users, as of 29 October 2026, from `toddleExtension.storeStats` in
Expand Down Expand Up @@ -68,6 +69,11 @@ numbers below were the site's earlier, hand-kept numbering and are retired; the

### Changed

- **@bundu/ui 0.9.0** (was 0.5.0), for the latest Nyuchi branding and
styling. `globals.css` now brings in Bundu's density rules, which the
site already imports. Nothing the site uses was removed. The one new
dependency is `uqr`, the QR-code library behind Bundu's new
address cards.
- **Firefox is coming, and the pages say so.** The extension now builds for
Firefox; until its addons.mozilla.org listing is live
(`toddleExtension.firefoxStore` in `src/data/site.ts`, null for now) the
Expand Down
23 changes: 15 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"ci:check": "astro check && vp fmt --check"
},
"dependencies": {
"@bundu/ui": "^0.5.0",
"@bundu/ui": "^0.9.0",
"astro": "^7.3.5"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/data/legal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
export const legal = {
/** Last substantive change, for every legal page. Update when a claim
changes, not on typos. */
updated: "2026-10-29",
updated: "2026-11-07",
entity: "Nyuchi Africa (Private) Limited",
shortEntity: "Nyuchi",
country: "Zimbabwe",
Expand Down
12 changes: 6 additions & 6 deletions src/data/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,9 @@ export const MINERALS = [
* fall back to an enquiry, with no other edit.)
*
* `firefoxStore` is Firefox's: the addons.mozilla.org listing. The extension
* builds for Firefox from 0.17.0 (the extension's docs/firefox.md); until the
* listing is live it stays null and the pages say Firefox is coming. Set it
* and every page says Chrome and Firefox, and offers "Add to Firefox" beside
* "Add to Chrome", with no other edit.
* builds for Firefox (the extension's docs/firefox.md), listed since 0.16.3.
* Set, every page says Chrome and Firefox and offers "Add to Firefox" beside
* "Add to Chrome"; null, and the pages say Firefox is coming.
*/
/** A link that may not exist yet. Named so that every Prettier version
formats the cast below the same way. */
Expand All @@ -184,8 +183,9 @@ export const toddleExtension = {
/** The Chrome Web Store listing (live since version 0.7.1). */
webStore:
"https://chromewebstore.google.com/detail/ofliokikjmkkdkinbdnadbjjdmkjdjfi" as Url,
/** The addons.mozilla.org listing: null until it is live. */
firefoxStore: null as Url,
/** The addons.mozilla.org listing (live since version 0.16.3). */
firefoxStore:
"https://addons.mozilla.org/firefox/addon/toddle-enhancement-extension/" as Url,
/** What the Chrome Web Store listing showed, read by the owner on the
date given. Update all three together; never round up. */
storeStats: {
Expand Down
39 changes: 39 additions & 0 deletions src/pages/legal/privacy.astro
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,45 @@ const description =
sees or pastes it; an organisation key typed into the extension is refused.
</p>

<h2 id="firefox" class="font-serif text-h3 text-foreground mt-12 scroll-mt-24">
In Firefox
</h2>
<p class="mt-4 text-body text-muted-foreground text-pretty">
The extension is also on
<a href={toddleExtension.firefoxStore} class="link" rel="noopener" target="_blank"
>Firefox Add-ons</a
>. It is the same extension, built from the same code, and everything on this page
applies to it in the same way, with these differences:
</p>
<ul
class="mt-4 space-y-2 text-body text-muted-foreground list-disc list-outside pl-5 marker:text-foreground/40"
>
<li>
<strong class="text-foreground">Claim links.</strong> The licence claim page cannot
hand a link to the extension in Firefox, so you paste the link from your licence
email into the extension's licence page. The request it makes is the same one,
carrying the link's code and nothing else.
</li>
<li>
<strong class="text-foreground">Storage and sync.</strong> Where this page says
Chrome's storage, in Firefox it is Firefox's storage for the extension. If you use
Firefox Sync, Firefox may carry your licence key to your other computers through
your own Firefox account; the extension never sends it anywhere.
</li>
<li>
<strong class="text-foreground">Schools.</strong> A school's IT sets the licence and
the modules for every teacher through Firefox enterprise policy
(<code>policies.json</code>) instead of Chrome policy. The extension reads it on the
computer, and nothing about it is sent anywhere.
</li>
<li>
<strong class="text-foreground">What Mozilla is told.</strong> The listing declares
that the extension collects nothing by itself, and that feedback and Toddle
compatibility reports, which you choose to send, contain technical information and
never student data.
</li>
</ul>

<h2 id="security" class="font-serif text-h3 text-foreground mt-12 scroll-mt-24">
Security
</h2>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/legal/terms.astro
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const description =
<h2 class="font-serif text-h3 text-foreground mt-12">How licence keys work</h2>
<p class="mt-4 text-body text-muted-foreground text-pretty">
An individual licence's email carries a one-time link, not the key: open it in Chrome, click
<em>Add it to Chrome</em>, and the extension fetches the key once from our licence
<em>Add it to Chrome</em> (in Firefox, paste the link into the extension's licence page), and the extension fetches the key once from our licence
server, in exchange for the link's code alone. The link works once, for 72 hours.
A licence key is a signed statement that the extension checks on your own device, so
checking it contacts nothing and works offline. A key carries the buyer's email
Expand Down
2 changes: 1 addition & 1 deletion src/pages/legal/vulnerability-disclosure.astro
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const description =
>
<li>This website, <code>learning.nyuchi.com</code>.</li>
<li>
The {toddleExtension.name}, version {security.supportedFrom} and later from the Chrome Web Store
The {toddleExtension.name}, version {security.supportedFrom} and later from the Chrome Web Store, and from Firefox Add-ons
(earlier versions are not supported; extension ID
<code class="break-all">{toddleExtension.extensionId}</code>).
</li>
Expand Down
2 changes: 1 addition & 1 deletion vercel.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; script-src 'self' https://app.intercom.io https://widget.intercom.io https://js.intercomcdn.com https://www.googletagmanager.com 'sha256-Brc4B09cZ9aNF4dBE7dQSlIzUMTvBIzHAPqI3zLF8+I=' 'sha256-FFGFlD4m+iq+1VF7UwfmwBt3tn7qHB2INa+7rAycUC8=' 'sha256-H7lTFTHP+LM756VatSdCPdQqI5KgMvle+T0W7x3MQnM=' 'sha256-VIgVtD7fAVrU4eaS8Crl3kwF00ggST1rFqthmXLJELQ='; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://js.intercomcdn.com https://fonts.intercomcdn.com; img-src 'self' data: https://js.intercomcdn.com https://static.intercomassets.com https://downloads.intercomcdn.com https://uploads.intercomusercontent.com https://gifs.intercomcdn.com https://video-messages.intercomcdn.com https://messenger-apps.intercomcdn.com https://www.google-analytics.com https://*.google-analytics.com https://*.googletagmanager.com; connect-src 'self' https://formspree.io https://via.intercom.io https://api.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://*.intercom-messenger.com wss://*.intercom-messenger.com https://nexus-websocket-a.intercom.io wss://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io wss://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://www.google-analytics.com https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com; form-action 'self' https://formspree.io; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; media-src 'self' https://js.intercomcdn.com; frame-src 'self' https://intercom-sheets.com https://www.intercom-reporting.com"
"value": "default-src 'self'; script-src 'self' https://app.intercom.io https://widget.intercom.io https://js.intercomcdn.com https://www.googletagmanager.com 'sha256-Brc4B09cZ9aNF4dBE7dQSlIzUMTvBIzHAPqI3zLF8+I=' 'sha256-FFGFlD4m+iq+1VF7UwfmwBt3tn7qHB2INa+7rAycUC8=' 'sha256-H7lTFTHP+LM756VatSdCPdQqI5KgMvle+T0W7x3MQnM=' 'sha256-k/SaY/US23qudvvmBdO1E+8Kcnq0gtOWaQxF58YESag='; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com https://js.intercomcdn.com https://fonts.intercomcdn.com; img-src 'self' data: https://js.intercomcdn.com https://static.intercomassets.com https://downloads.intercomcdn.com https://uploads.intercomusercontent.com https://gifs.intercomcdn.com https://video-messages.intercomcdn.com https://messenger-apps.intercomcdn.com https://www.google-analytics.com https://*.google-analytics.com https://*.googletagmanager.com; connect-src 'self' https://formspree.io https://via.intercom.io https://api.intercom.io https://api-iam.intercom.io https://api-ping.intercom.io https://*.intercom-messenger.com wss://*.intercom-messenger.com https://nexus-websocket-a.intercom.io wss://nexus-websocket-a.intercom.io https://nexus-websocket-b.intercom.io wss://nexus-websocket-b.intercom.io https://uploads.intercomcdn.com https://uploads.intercomusercontent.com https://www.google-analytics.com https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com; form-action 'self' https://formspree.io; frame-ancestors 'none'; base-uri 'self'; object-src 'none'; media-src 'self' https://js.intercomcdn.com; frame-src 'self' https://intercom-sheets.com https://www.intercom-reporting.com"
}
]
}
Expand Down
Loading