···252526264. **Sources:** English dictionary = **ENABLE** (Public Domain); French dictionary =
2727 **an-array-of-french-words** (MIT); commonness ranking for both = **hermitdave/FrequencyWords**
2828- (MIT, OpenSubtitles). The French list is sourced independently with **no relation to** the
2929- `wordle-fr` / “Le Mot” project, as required.
2828+ (MIT, OpenSubtitles).
302931305. **Validity vs. commonness are separated.** Frequency lists alone leak proper nouns, foreign
3231 words, and apostrophe-mangled contraction fragments. So the *dictionary* decides validity and the
···66656766## Design
68676969-12. **Type = Overused Grotesk (display/body) + IBM Plex Mono (data/provenance)** — the exact
7070- skypress.blog stack, self-hosted (both OFL).
6868+12. **Type = Overused Grotesk (display/body) + IBM Plex Mono (data/provenance)**, self-hosted
6969+ (both OFL).
7170727113. **Board palette:** teal `#0F766E` (correct), burnt amber `#B45309` (present), slate `#5B6776`
7373- (absent) — deliberately not Wordle's green/yellow. The same saturated chips are used in both
7272+ (absent). The same saturated chips are used in both
7473 light and dark themes, which guarantees AA contrast for the white tile letters in either theme.
7574 Secondary non-hue cue: an inner ring on "present" tiles plus per-tile aria labels announcing the
7675 state, so colour-blind and screen-reader players read the board clearly. The shared emoji grid
···95949695## Tooling
97969898-18. **Dev tooling (Vite/Vitest) bumped to latest majors** to clear all `npm audit` advisories
9999- (currently **0 vulnerabilities**). The advisories were dev-server-only (esbuild), never in
100100- shipped code.
9797+18. **Dev tooling (Vite/Vitest) bumped to latest majors** to clear all `npm audit` advisories. The
9898+ advisories were dev-server-only (esbuild), never in shipped code.
1019910210019. **atcute versions:** `@atcute/client@^5`, `@atcute/oauth-browser-client@^4`,
103101 `@atcute/identity-resolver@^2` (newer than the spec's illustrative snippets; the API used is
104102 equivalent).
105103104104+## PWA / installability
105105+106106+20. **Manifest-only PWA — deliberately no service worker.** The app is installable
107107+ to the home screen via `public/manifest.webmanifest` + committed PNG icons
108108+ (rasterized once from `icon.svg`, flattened onto `#0e1116`) + apple/mobile
109109+ web-app meta tags. There is intentionally **no service worker and no caching
110110+ layer**: this is a daily game with dynamic reads (Constellation leaderboard,
111111+ PDS writes, OAuth), and an over-eager cache is exactly what would serve a
112112+ stale puzzle or stale bundle. Consequence: iOS add-to-home-screen and
113113+ Android manual "Add to Home Screen" both work from the manifest alone; the
114114+ proactive Android install banner (which needs a service worker) is forgone on
115115+ purpose. Loading speed is unchanged (CDN + ~94 KB bundle) with no new failure
116116+ modes. The regression guard `tests/pwa.test.ts` uses `node:*` imports, so
117117+ `"node"` was added to the shared `tsconfig.json` `types` allowlist — a
118118+ conscious trade-off: Node globals become visible to `src/` typechecking, but
119119+ `noEmit` + Vite bundling mean it can't affect runtime; narrow later by giving
120120+ `tests` its own node-typed project if that boundary is wanted.
121121+106122## Anti-cheat
107123108108-22. **The daily-answer sequence ships obfuscated, not in plaintext.** The ordered
124124+21. **The daily-answer sequence ships obfuscated, not in plaintext.** The ordered
109125 `answers` array used to ship as plain English words, so anyone could open the
110126 JSON (in the repo or the JS bundle / network tab), find their puzzle number,
111127 and read today's — or any future day's — word. It now ships as `answersEnc`:
···123139 reveals no day mapping); (b) French answer words that carry accents remain
124140 individually visible as keys in the plaintext `accents` display map — but not
125141 their day-order, so the daily mapping stays hidden.
126126-127127-## Handoff note
128128-129129-20. **No git remote is configured.** Everything is committed to the **`trunk`** branch, which is the
130130- repository's current/default branch locally (HEAD points at it). Creating a remote and marking
131131- `trunk` as the default branch *on the host* (GitHub/Tangled/etc.) is a one-step manual action for
132132- the owner, since no remote exists to push to yet.
133133-134134-## PWA / installability
135135-136136-21. **Manifest-only PWA — deliberately no service worker.** The app is installable
137137- to the home screen via `public/manifest.webmanifest` + committed PNG icons
138138- (rasterized once from `icon.svg`, flattened onto `#0e1116`) + apple/mobile
139139- web-app meta tags. There is intentionally **no service worker and no caching
140140- layer**: this is a daily game with dynamic reads (Constellation leaderboard,
141141- PDS writes, OAuth), and an over-eager cache is exactly what would serve a
142142- stale puzzle or stale bundle. Consequence: iOS add-to-home-screen and
143143- Android manual "Add to Home Screen" both work from the manifest alone; the
144144- proactive Android install banner (which needs a service worker) is forgone on
145145- purpose. Loading speed is unchanged (CDN + ~94 KB bundle) with no new failure
146146- modes. The regression guard `tests/pwa.test.ts` uses `node:*` imports, so
147147- `"node"` was added to the shared `tsconfig.json` `types` allowlist — a
148148- conscious trade-off: Node globals become visible to `src/` typechecking, but
149149- `noEmit` + Vite bundling mean it can't affect runtime; narrow later by giving
150150- `tests` its own node-typed project if that boundary is wanted.
···33**A bilingual (English / French) daily word game, native to the [AT Protocol](https://atproto.com).**
44Guess the hidden five-letter word in six tries. Your results live in **your own PDS**, and the
55leaderboard is read from the public **[Constellation](https://constellation.microcosm.blue)**
66-backlink index — there is **no application database and no bespoke backend.**
66+backlink index.
7788🌐 **[atmot.herve.bzh](https://atmot.herve.bzh)**
99···11111212---
13131414-## Why it's interesting
1515-1616-AT Mot is a working example of the **“data on your PDS, reads via Constellation, no AppView”**
1717-architecture for atproto apps:
1414+AT Mot uses a **”data on your PDS, reads via Constellation, no AppView”** architecture for atproto
1515+apps:
18161917- **Your data is yours.** Each finished puzzle is written as a `bzh.herve.atmot.result` record to
2018 your repo via browser OAuth. There is no server storing your games — there is no server.
···2624 gets the same word on the same calendar day, with no server in the loop.
2725- **Standards-conformant lexicons** under the `bzh.herve.atmot.*` authority, following the
2826 [Lexicon Style Guide](https://atproto.com/guides/lexicon-style-guide).
2929-- **Installs to your home screen, no strings attached.** A web manifest makes it installable as a
3030- standalone app — with **no service worker and no caching**, so there's none of the stale-content
3131- machinery that fights against a daily game whose reads are always live.
3232-3333-It is **not** a clone of any commercial word game: it ships its own word lists, its own brand and
3434-look-and-feel (a sky/atproto-leaning **teal / amber / slate** board palette, deliberately distinct),
3535-and its own lexicons. The five-in-a-row word-guessing mechanic is generic; the implementation,
3636-data, and design here are original.
2727+- **Installable to your home screen.** A web manifest makes it installable as a standalone app.
37283829## Architecture at a glance
3930···9687| English | [ENABLE](https://github.com/dolph/dictionary) — **Public Domain** | hermitdave/FrequencyWords (MIT) |
9788| French | [an-array-of-french-words](https://github.com/words/an-array-of-french-words) — **MIT** | hermitdave/FrequencyWords (MIT) |
98899999-The French list is sourced **independently** and has **no relation to** the `wordle-fr` / “Le Mot”
100100-project. Lists are generated by `npm run build:words` and committed (the generated JSON _is_ the game
9090+Lists are generated by `npm run build:words` and committed (the generated JSON _is_ the game
10191content); the build is reproducible (same sources + fixed seeds → identical output).
1029210393## Develop
-3
src/i18n.ts
···4545 backToGame: string;
4646 langName: string;
4747 about: string;
4848- sourceCode: string;
4948 profile: string;
5049}
5150···9796 backToGame: 'Back to today’s game',
9897 langName: 'English',
9998 about: 'About',
100100- sourceCode: 'Source code on tangled.org',
10199 profile: '@jeremy.herve.bzh on Bluesky',
102100};
103101···149147 backToGame: 'Retour au mot du jour',
150148 langName: 'Français',
151149 about: 'À propos',
152152- sourceCode: 'Code source sur tangled.org',
153150 profile: '@jeremy.herve.bzh sur Bluesky',
154151};
155152