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
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ numbers below were the site's earlier, hand-kept numbering and are retired; the

### Added

- **Mukoko Lingo has its own page, `/mukoko-lingo`.** Mukoko Lingo is a
partner app developed by Nyuchi Learning, part of Mukoko, Africa's super
app. The page says what it does today, who it is for, what schools get
and how to start, in the extension page's style, with the app's own icon.
Every claim is from Lingo's public repository (mukoko-dev/mukoko-lingo:
README.md, app.json, BRANDING.md and the app's own copy). It sits beside
the Toddle Enhancement Extension wherever the extension appears: a second
product card on the home page, "Mukoko Lingo" in the header (where
"Tools" is now "Toddle extension"), and the footer's "Tools" column, now
"Products". It has a SoftwareApplication node in its JSON-LD, Open Graph
tags, a sitemap entry and an entry under Products in llms.txt.
`mukokoLingo` in `src/data/site.ts` holds its links.
`tests/seo.test.ts` checks all of that.
- **`npm run clean`, and after every merge.** `scripts/clean.mjs` removes
the build output (`dist/`, `.astro/`, `.vercel/output/`), the QA report,
the tools' caches, finished Claude Code worktrees and local branches
Expand All @@ -20,6 +33,27 @@ numbers below were the site's earlier, hand-kept numbering and are retired; the

### Changed

- **Mukoko Lingo, described as it is.** The home page card and llms.txt
said Lingo was "free for personal use", "Shona and Ndebele primary" and
taught French and Portuguese. Its repository says it teaches Shona,
Ndebele, English and Chinese, and that starting is free; the copy now
says that, and links to the live app at lingo.mukoko.com rather than
mukoko.com/lingo. The home page's "three surfaces" are now the two
products and two destinations.
- **0.11.0 is on the Chrome Web Store, and is the current version (#85).**
It is 0.10.0 with a security fix to how licence keys are checked. The
Releases page lists it first, marked on the store, from the extension's
CHANGELOG; the test builds tagged since 0.10.0 (0.10.1 to 0.10.5, 0.11.1
and 0.11.2) open its notes, and 0.12.0, not released, is not listed.
`extensionVersions.current` is 0.11.0; the new `changedIn` (0.9.0) keeps
"versions before 0.9.0" true where the old licence host and flags key are
named. The product page, the Security page (which now describes the
0.11.0 fix), the Releases description and llms.txt name 0.11.0. The
anchors for 0.9.6 and 0.9.7 the issue asked for were already there; the
test that every tagged test build has one now covers the 0.10.x and
0.11.x builds too.
- **`webStore`'s type is a named `Url`**, so Prettier 3.3 and 3.9 format
`src/data/site.ts` the same way; the org lint gate (3.9.4) had flagged it.
- **After paying: a claim link, not a key.** The extension page's checkout
note now says the licence email carries a one-time link (never the key),
that it works once for 72 hours and a new one can be asked for, that
Expand Down
25 changes: 13 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ three predictable places.

## What gets changed where

| Change | File |
| ------------------------------------------------------- | ----------------------------------------------------- |
| Nav links, footer columns, legal links, the wordmark | `src/data/site.ts` |
| Links to the extension (store listing, releases, guide) | `src/data/site.ts` (`toddleExtension`) |
| The extension's release notes | `src/data/releases.ts` |
| Facts on the legal pages (versions, prices, contacts) | `src/data/legal.ts` |
| Consent categories and the banner's version | `src/data/consent.ts` |
| The three destination cards | the `surfaces` array in `src/pages/index.astro` |
| Home page copy | `src/pages/index.astro` |
| Extension page copy | `src/pages/toddle-enhancement-extension.astro` |
| Header / footer structure | `src/components/SiteHeader.astro`, `SiteFooter.astro` |
| `<head>`, metadata, theme bootstrap | `src/layouts/BaseLayout.astro` |
| Change | File |
| ------------------------------------------------------- | ------------------------------------------------------------------- |
| Nav links, footer columns, legal links, the wordmark | `src/data/site.ts` |
| Links to the extension (store listing, releases, guide) | `src/data/site.ts` (`toddleExtension`) |
| The extension's release notes | `src/data/releases.ts` |
| Facts on the legal pages (versions, prices, contacts) | `src/data/legal.ts` |
| Consent categories and the banner's version | `src/data/consent.ts` |
| The destination cards | the `surfaces` array in `src/pages/index.astro` |
| Home page copy | `src/pages/index.astro` |
| Extension page copy | `src/pages/toddle-enhancement-extension.astro` |
| Mukoko Lingo's links / page copy | `src/data/site.ts` (`mukokoLingo`) / `src/pages/mukoko-lingo.astro` |
| Header / footer structure | `src/components/SiteHeader.astro`, `SiteFooter.astro` |
| `<head>`, metadata, theme bootstrap | `src/layouts/BaseLayout.astro` |

## What must not be changed here

Expand Down
41 changes: 34 additions & 7 deletions public/llms.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Nyuchi Learning

> learning.nyuchi.com — the Nyuchi Learning surface of Nyuchi Africa. Two
> things live here: signposts to where the learning content moved, and the
> classroom software Nyuchi Web Services builds for schools.
> learning.nyuchi.com — the Nyuchi Learning surface of Nyuchi Africa. It is
> the home of Nyuchi Learning's two products, the Toddle Enhancement
> Extension and Mukoko Lingo, and of signposts to where the learning content
> moved.

Nyuchi Africa is a technology company in Zimbabwe building open-source and
community-based platforms. Nyuchi Web Services is its development division.
Expand Down Expand Up @@ -106,8 +107,10 @@ community-based platforms. Nyuchi Web Services is its development division.
a copy. Schools deploy it from the Chrome Web Store by extension ID, with
a Google Admin console force-install (or Group Policy, Intune or a macOS
profile); an organisation key is pasted in once by each teacher, since it
cannot be pushed by policy. Teachers get each minor release (0.9.0 is
the current one) from the Chrome Web Store; versions 0.8.3 to 0.8.13 were
cannot be pushed by policy. Teachers get each minor release (0.11.0 is
the current one: 0.10.0 with a security fix to how licence keys are
checked, so that a cancelled key cannot be made to work again by
writing it differently) from the Chrome Web Store; versions 0.8.3 to 0.8.13 were
test builds for schools piloting it, and their changes arrived together in
0.9.0. Release
notes for every version, in plain words:
Expand All @@ -123,15 +126,39 @@ community-based platforms. Nyuchi Web Services is its development division.
https://support.nyuchi.com/en/collections/19764027-how-to-use-the-toddle-enhancement-extension
Security: https://learning.nyuchi.com/legal/security

- [Mukoko Lingo](https://learning.nyuchi.com/mukoko-lingo): a partner app
developed by Nyuchi Learning, part of Mukoko, Africa's super app.
AI-assisted, skills-based language learning for individuals, schools and
businesses across Africa. Use it in a web browser at
https://lingo.mukoko.com; schools, businesses and admins use the console
at https://lingo.nyuchi.com/console. It teaches Shona, Ndebele, Chinese
and English (Swahili is in the language picker but unfinished, and is not
a supported language). A learner gets five phrases a day, drilled with
flash cards and a short quiz, with spaced repetition scheduling what comes
back and when. Shamwari ("friend" in Shona), the AI tutor, answers
questions in chat and adapts to the learner's measured proficiency rather
than a level they picked; Shamwari never claims to be human. Pronunciation
guides, cultural context, and progress tracking (streaks, XP, bookmarks,
skill proficiency). For schools: teachers create classes, set assignments
against specific phrases and skills, and see progress per student;
assessments are diagnostic, formative and summative, graded on the server;
rosters sync over OneRoster from Clever, ClassLink and PowerSchool;
organisation admins manage users, content, moderation, guardrails,
analytics and API keys. Moderation runs in three layers: rule-based
guardrails, an AI-based check, and prompt-injection detection on anything
a learner types to the tutor. Sign-in is WorkOS AuthKit; the tutor runs on
Cloudflare Workers AI, called from Lingo's servers. Starting is free.
Open source under the MIT licence, (c) Nyuchi Learning:
https://github.com/mukoko-dev/mukoko-lingo. Enquiries:
support@nyuchi.com.

## Where the learning content went

- [Bundu Education](https://bundu.org/education): open, evidence-based K-12
frameworks published by the Bundu Foundation. Free to copy and adapt.
- [Nyuchi Learning programmes](https://nyuchi.com/learning): commercial
cohort-based and self-paced training in software, AI and digital skills,
localised for Africa with offline study packs.
- [Mukoko Lingo](https://mukoko.com/lingo): African language learning, free for
personal use. Shona and Ndebele primary.

## Legal

Expand Down
Binary file added src/assets/lingo/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 14 additions & 10 deletions src/data/legal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,29 @@ export const pricing = [
* (0.9.0, …) is released from the extension's `main` branch and published to
* the store. Teachers get minors.
*
* `current` is what teachers have from the Chrome Web Store: 0.9.0, published
* on 7 October 2026. `previous` is the store release before it, named only
* where something it did still matters to a teacher updating from it (flags
* it hid stay concealed), said as history. What changed in the test builds
* between the two (0.8.3 carried 0.8.2's code) is said as "versions before
* `current`": the old licence host and the old flags storage key. There is no `next` while no minor is reviewed and waiting for the
* `current` is what teachers have from the Chrome Web Store: 0.11.0, a
* security fix on top of 0.10.0 (published 13 October 2026). `previous` is
* the store release named only where something it did still matters to a
* teacher updating from it (flags 0.8.2 hid stay concealed), said as history.
* `changedIn` is the release that moved the licence host to licenses.nyuchi.com
* and renamed the flags storage key: "versions before `changedIn`" asked the
* old host and used the old key. It stays 0.9.0 when `current` moves.
* There is no `next` while no minor is reviewed and waiting for the
* store. When one is, add `next` here, say "from version <next>" and what
* `current` does wherever the two differ, so each claim stays true on both
* sides of the release; when it ships, move it to `current` and drop that
* wording again.
*/
export const extensionVersions = {
current: "0.9.0",
current: "0.11.0",
previous: "0.8.2",
/** The release that moved the licence host and renamed the flags key. */
changedIn: "0.9.0",
/** The oldest version security reports are accepted for. */
supportedFrom: "0.8.2",
} as const;

const { current, previous } = extensionVersions;
const { changedIn, previous } = extensionVersions;

/**
* The licence key and Chrome sync, said once. Since 0.9.0 the key alone is
Expand Down Expand Up @@ -229,7 +233,7 @@ export const extensionDataFacts = [
},
{
claim: "What it stores: settings and licence details, and no student data.",
detail: `In Chrome's storage for the extension, on your device: your switches (the extension on or off, student flags, the student sidebar, My classes, primary teacher names, gradebook tools, and student details on the Attendance dashboard); your licence key, if you entered one; the result of the last check for cancelled keys (whether your key is on the list, the list's date and when the check was made, never the list itself); and the email address of the Toddle account last seen signed in, with the time, so the extension can check who a licence is for. That email is read on every Toddle page, with or without a licence key, and stays on the device. ${licenceSync.detail} In the browser's storage for Toddle's own site: tee-settings, a copy of your switches and of which features the licence allows (no key and no email); tee-blur-flags (gbx-hide-flags in versions before ${current}), whether flags are concealed, so they are concealed before the page is drawn; tee-course-view, whether you chose My classes in the home page filter; and tee-academic-year, the number of the academic year Toddle last asked for. Nothing it reads about students, classes, results or attendance is ever written to storage.`,
detail: `In Chrome's storage for the extension, on your device: your switches (the extension on or off, student flags, the student sidebar, My classes, primary teacher names, gradebook tools, and student details on the Attendance dashboard); your licence key, if you entered one; the result of the last check for cancelled keys (whether your key is on the list, the list's date and when the check was made, never the list itself); and the email address of the Toddle account last seen signed in, with the time, so the extension can check who a licence is for. That email is read on every Toddle page, with or without a licence key, and stays on the device. ${licenceSync.detail} In the browser's storage for Toddle's own site: tee-settings, a copy of your switches and of which features the licence allows (no key and no email); tee-blur-flags (gbx-hide-flags in versions before ${changedIn}), whether flags are concealed, so they are concealed before the page is drawn; tee-course-view, whether you chose My classes in the home page filter; and tee-academic-year, the number of the academic year Toddle last asked for. Nothing it reads about students, classes, results or attendance is ever written to storage.`,
},
{
claim: "What it holds in memory, and for how long.",
Expand All @@ -244,7 +248,7 @@ export const extensionDataFacts = [
{
claim:
"Outside connection two, only while a licence is entered: a daily check for cancelled keys.",
detail: `At most once a day, and only while a licence key is entered, the extension's background worker makes one plain request to https://licenses.nyuchi.com/v1/revocations (versions before ${current} asked licences.nyuchi.dev, the same server); a key just entered is checked within 5 minutes. It sends no licence key, no identifiers, no cookies and no Toddle data. It downloads a list, signed by Nyuchi, of the fingerprints (SHA-256 hashes) of cancelled keys, checks its own key against that list on your machine, and keeps only the result. Cloudflare, which runs the server, sees your IP address as with any web request; Nyuchi does not log it. If the list cannot be fetched, the last result stands, so a network outage never switches a licence off.`,
detail: `At most once a day, and only while a licence key is entered, the extension's background worker makes one plain request to https://licenses.nyuchi.com/v1/revocations (versions before ${changedIn} asked licences.nyuchi.dev, the same server); a key just entered is checked within 5 minutes. It sends no licence key, no identifiers, no cookies and no Toddle data. It downloads a list, signed by Nyuchi, of the fingerprints (SHA-256 hashes) of cancelled keys, checks its own key against that list on your machine, and keeps only the result. Cloudflare, which runs the server, sees your IP address as with any web request; Nyuchi does not log it. If the list cannot be fetched, the last result stands, so a network outage never switches a licence off.`,
},
{
claim: "Exports are local.",
Expand Down
40 changes: 40 additions & 0 deletions src/data/releases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,50 @@ export const taggedTestBuilds: readonly string[] = [
"0.9.8",
"0.9.9",
"0.9.10",
"0.10.1",
"0.10.2",
"0.10.3",
"0.10.4",
"0.10.5",
"0.11.1",
"0.11.2",
];

/** Newest first. */
export const releases: Release[] = [
{
/* No date: the CHANGELOG does not give the day 0.11.0 reached the store.
Add it when it is confirmed. The test builds after 0.10.0 open these
notes; their own changes are in neither 0.10.0 nor 0.11.0. */
version: "0.11.0",
status: "store",
summary:
"A security fix: a licence key that was cancelled or refunded can no longer be made to work again by editing it. Nothing else changes for teachers, and every key that works today keeps working.",
sections: [
{
heading: "Fixed",
items: [
"A cancelled licence key could be made to work again by typing it slightly differently. A key must now be in exactly the form it was issued in; spaces before or after it are still ignored. A key stored in any other form stops working the next time Toddle loads.",
"Only a licence is accepted as a licence. The signed list of cancelled keys can no longer pass for a key. Keys issued so far keep working.",
],
},
{
heading: "Test builds",
items: [
"Versions 0.10.1 to 0.10.5, 0.11.1 and 0.11.2 are test builds for schools piloting the extension. Their changes are not in this release; they will reach the Chrome Web Store in a later one.",
],
},
],
alsoIds: [
"0.10.1",
"0.10.2",
"0.10.3",
"0.10.4",
"0.10.5",
"0.11.1",
"0.11.2",
],
},
{
version: "0.10.0",
status: "store",
Expand Down
Loading
Loading