AT Mot — a bilingual (EN/FR) daily word game native to the AT Protocol.
0

Configure Feed

Select the types of activity you want to include in your feed.

Trim defensive positioning from docs and drop footer source link

Remove the bold "not something else" framing and sales-style copy from
README, NOTICE, and DECISIONS: the no-database/no-backend tails, the
"Why it's interesting" heading, the not-a-clone paragraph, the
wordle-fr / Le Mot comparisons, the skypress.blog mentions, the
no-git-remote handoff note, and the redundant 0-vulnerabilities aside.

Also remove the tangled.org source link (and its inlined mark) from the
app footer, along with the now-unused sourceCode i18n strings, and
update the footer tests accordingly.

+23 -66
+7 -16
DECISIONS.md
··· 25 25 26 26 4. **Sources:** English dictionary = **ENABLE** (Public Domain); French dictionary = 27 27 **an-array-of-french-words** (MIT); commonness ranking for both = **hermitdave/FrequencyWords** 28 - (MIT, OpenSubtitles). The French list is sourced independently with **no relation to** the 29 - `wordle-fr` / “Le Mot” project, as required. 28 + (MIT, OpenSubtitles). 30 29 31 30 5. **Validity vs. commonness are separated.** Frequency lists alone leak proper nouns, foreign 32 31 words, and apostrophe-mangled contraction fragments. So the *dictionary* decides validity and the ··· 66 65 67 66 ## Design 68 67 69 - 12. **Type = Overused Grotesk (display/body) + IBM Plex Mono (data/provenance)** — the exact 70 - skypress.blog stack, self-hosted (both OFL). 68 + 12. **Type = Overused Grotesk (display/body) + IBM Plex Mono (data/provenance)**, self-hosted 69 + (both OFL). 71 70 72 71 13. **Board palette:** teal `#0F766E` (correct), burnt amber `#B45309` (present), slate `#5B6776` 73 - (absent) — deliberately not Wordle's green/yellow. The same saturated chips are used in both 72 + (absent). The same saturated chips are used in both 74 73 light and dark themes, which guarantees AA contrast for the white tile letters in either theme. 75 74 Secondary non-hue cue: an inner ring on "present" tiles plus per-tile aria labels announcing the 76 75 state, so colour-blind and screen-reader players read the board clearly. The shared emoji grid ··· 95 94 96 95 ## Tooling 97 96 98 - 18. **Dev tooling (Vite/Vitest) bumped to latest majors** to clear all `npm audit` advisories 99 - (currently **0 vulnerabilities**). The advisories were dev-server-only (esbuild), never in 100 - shipped code. 97 + 18. **Dev tooling (Vite/Vitest) bumped to latest majors** to clear all `npm audit` advisories. The 98 + advisories were dev-server-only (esbuild), never in shipped code. 101 99 102 100 19. **atcute versions:** `@atcute/client@^5`, `@atcute/oauth-browser-client@^4`, 103 101 `@atcute/identity-resolver@^2` (newer than the spec's illustrative snippets; the API used is 104 102 equivalent). 105 103 106 - ## Handoff note 107 - 108 - 20. **No git remote is configured.** Everything is committed to the **`trunk`** branch, which is the 109 - repository's current/default branch locally (HEAD points at it). Creating a remote and marking 110 - `trunk` as the default branch *on the host* (GitHub/Tangled/etc.) is a one-step manual action for 111 - the owner, since no remote exists to push to yet. 112 - 113 104 ## PWA / installability 114 105 115 - 21. **Manifest-only PWA — deliberately no service worker.** The app is installable 106 + 20. **Manifest-only PWA — deliberately no service worker.** The app is installable 116 107 to the home screen via `public/manifest.webmanifest` + committed PNG icons 117 108 (rasterized once from `icon.svg`, flattened onto `#0e1116`) + apple/mobile 118 109 web-app meta tags. There is intentionally **no service worker and no caching
+1 -5
NOTICE.md
··· 13 13 - **hermitdave/FrequencyWords** — English & French commonness ranking (OpenSubtitles). 14 14 Source: <https://github.com/hermitdave/FrequencyWords>. **MIT** © Hermit Dave. 15 15 16 - The French list is sourced independently and has no relation to, and reuses no code or data from, 17 - the `wordle-fr` / “Le Mot” project. 18 - 19 16 ## Fonts (bundled in `public/fonts/`) 20 17 21 18 - **Overused Grotesk** (`OverusedGrotesk-VF.woff2`). ··· 23 20 - **IBM Plex Mono** (`ibm-plex-mono-latin-*.woff2`). 24 21 By IBM. **SIL Open Font License 1.1.** <https://github.com/IBM/plex> 25 22 26 - These match the typography of [skypress.blog](https://skypress.blog) for family consistency across 27 - the author's properties. Both are OFL-licensed and freely redistributable. 23 + Both are OFL-licensed and freely redistributable. 28 24 29 25 ## Runtime dependencies 30 26
+5 -15
README.md
··· 3 3 **A bilingual (English / French) daily word game, native to the [AT Protocol](https://atproto.com).** 4 4 Guess the hidden five-letter word in six tries. Your results live in **your own PDS**, and the 5 5 leaderboard is read from the public **[Constellation](https://constellation.microcosm.blue)** 6 - backlink index — there is **no application database and no bespoke backend.** 6 + backlink index. 7 7 8 8 🌐 **[atmot.herve.bzh](https://atmot.herve.bzh)** 9 9 ··· 11 11 12 12 --- 13 13 14 - ## Why it's interesting 15 - 16 - AT Mot is a working example of the **“data on your PDS, reads via Constellation, no AppView”** 17 - architecture for atproto apps: 14 + AT Mot uses a **”data on your PDS, reads via Constellation, no AppView”** architecture for atproto 15 + apps: 18 16 19 17 - **Your data is yours.** Each finished puzzle is written as a `bzh.herve.atmot.result` record to 20 18 your repo via browser OAuth. There is no server storing your games — there is no server. ··· 26 24 gets the same word on the same calendar day, with no server in the loop. 27 25 - **Standards-conformant lexicons** under the `bzh.herve.atmot.*` authority, following the 28 26 [Lexicon Style Guide](https://atproto.com/guides/lexicon-style-guide). 29 - - **Installs to your home screen, no strings attached.** A web manifest makes it installable as a 30 - standalone app — with **no service worker and no caching**, so there's none of the stale-content 31 - machinery that fights against a daily game whose reads are always live. 32 - 33 - It is **not** a clone of any commercial word game: it ships its own word lists, its own brand and 34 - look-and-feel (a sky/atproto-leaning **teal / amber / slate** board palette, deliberately distinct), 35 - and its own lexicons. The five-in-a-row word-guessing mechanic is generic; the implementation, 36 - data, and design here are original. 27 + - **Installable to your home screen.** A web manifest makes it installable as a standalone app. 37 28 38 29 ## Architecture at a glance 39 30 ··· 96 87 | English | [ENABLE](https://github.com/dolph/dictionary) — **Public Domain** | hermitdave/FrequencyWords (MIT) | 97 88 | French | [an-array-of-french-words](https://github.com/words/an-array-of-french-words) — **MIT** | hermitdave/FrequencyWords (MIT) | 98 89 99 - The French list is sourced **independently** and has **no relation to** the `wordle-fr` / “Le Mot” 100 - project. Lists are generated by `npm run build:words` and committed (the generated JSON _is_ the game 90 + Lists are generated by `npm run build:words` and committed (the generated JSON _is_ the game 101 91 content); the build is reproducible (same sources + fixed seeds → identical output). 102 92 103 93 ## Develop
-3
src/i18n.ts
··· 45 45 backToGame: string; 46 46 langName: string; 47 47 about: string; 48 - sourceCode: string; 49 48 profile: string; 50 49 } 51 50 ··· 97 96 backToGame: 'Back to today’s game', 98 97 langName: 'English', 99 98 about: 'About', 100 - sourceCode: 'Source code on tangled.org', 101 99 profile: '@jeremy.herve.bzh on Bluesky', 102 100 }; 103 101 ··· 149 147 backToGame: 'Retour au mot du jour', 150 148 langName: 'Français', 151 149 about: 'À propos', 152 - sourceCode: 'Code source sur tangled.org', 153 150 profile: '@jeremy.herve.bzh sur Bluesky', 154 151 }; 155 152
+3 -23
src/ui/footer.ts
··· 3 3 import { navTo } from './header.js'; 4 4 import type { Ctx } from './context.js'; 5 5 6 - const SOURCE_URL = 'https://tangled.org/jeremy.herve.bzh/atmot'; 7 6 const PROFILE_URL = 'https://bsky.app/profile/jeremy.herve.bzh'; 8 7 9 8 /** 10 - * The tangled.org mark, inlined so it inherits the footer text colour 11 - * (and therefore tracks light/dark mode) via `currentColor`. Matches the icon 12 - * skypress.blog uses for its source link. 13 - */ 14 - const TANGLED_ICON = 15 - '<svg viewBox="0 0 24.122343 23.274094" fill="currentColor" aria-hidden="true" focusable="false">' + 16 - '<path transform="translate(-0.4388285,-0.8629527)" d="m 16.348974,24.09935 -0.06485,-0.03766 -0.202005,-0.0106 -0.202008,-0.01048 -0.275736,-0.02601 -0.275734,-0.02602 v -0.02649 -0.02648 l -0.204577,-0.04019 -0.204578,-0.04019 -0.167616,-0.08035 -0.167617,-0.08035 -0.0014,-0.04137 -0.0014,-0.04137 -0.266473,-0.143735 -0.266475,-0.143735 -0.276098,-0.20335 -0.2761,-0.203347 -0.262064,-0.251949 -0.262064,-0.25195 -0.22095,-0.284628 -0.220948,-0.284629 -0.170253,-0.284631 -0.170252,-0.284628 -0.01341,-0.0144 -0.0134,-0.0144 -0.141982,0.161297 -0.14198,0.1613 -0.22313,0.21426 -0.223132,0.214264 -0.186025,0.146053 -0.186023,0.14605 -0.252501,0.163342 -0.252502,0.163342 -0.249014,0.115348 -0.249013,0.115336 0.0053,0.03241 0.0053,0.03241 -0.1716725,0.04599 -0.171669,0.046 -0.3379966,0.101058 -0.3379972,0.101058 -0.1778925,0.04506 -0.1778935,0.04508 -0.3913655,0.02601 -0.3913643,0.02603 -0.3557868,-0.03514 -0.3557863,-0.03514 -0.037426,-0.03029 -0.037427,-0.03029 -0.076924,0.02011 -0.076924,0.02011 -0.050508,-0.05051 -0.050405,-0.05056 L 6.6604532,23.110188 6.451745,23.063961 6.1546135,22.960559 5.8574835,22.857156 5.5319879,22.694039 5.2064938,22.530922 4.8793922,22.302961 4.5522905,22.075005 4.247598,21.786585 3.9429055,21.49817 3.7185335,21.208777 3.4941628,20.919385 3.3669822,20.705914 3.239803,20.492443 3.1335213,20.278969 3.0272397,20.065499 2.9015252,19.7275 2.7758105,19.389504 2.6925225,18.998139 2.6092345,18.606774 2.6096814,17.91299 2.6101284,17.219208 2.6744634,16.90029 2.7387984,16.581374 2.8474286,16.242088 2.9560588,15.9028 3.1137374,15.583492 3.2714148,15.264182 3.3415068,15.150766 3.4115988,15.03735 3.3127798,14.96945 3.2139618,14.90157 3.0360685,14.800239 2.8581753,14.698908 2.5913347,14.503228 2.3244955,14.307547 2.0621238,14.055599 1.7997507,13.803651 1.6111953,13.56878 1.4226411,13.333906 1.2632237,13.087474 1.1038089,12.841042 0.97442,12.575195 0.8450307,12.30935 0.724603,11.971351 0.6041766,11.633356 0.52150365,11.241991 0.4388285,10.850626 0.44091592,10.156842 0.44300333,9.4630594 0.54235911,9.0369608 0.6417149,8.6108622 0.7741173,8.2694368 0.9065196,7.9280115 1.0736303,7.6214262 1.2407515,7.3148397 1.45931,7.0191718 1.6778685,6.7235039 1.9300326,6.4611321 2.1821966,6.1987592 2.4134579,6.0137228 2.6447193,5.8286865 2.8759792,5.6776409 3.1072406,5.526594 3.4282004,5.3713977 3.7491603,5.2162016 3.9263009,5.1508695 4.1034416,5.0855373 4.2813348,4.7481598 4.4592292,4.4107823 4.6718,4.108422 4.8843733,3.8060618 5.198353,3.4805372 5.5123313,3.155014 5.7685095,2.9596425 6.0246877,2.7642722 6.329187,2.5851365 6.6336863,2.406002 6.9497657,2.2751596 7.2658453,2.1443184 7.4756394,2.0772947 7.6854348,2.01027 8.0825241,1.931086 8.4796139,1.851902 l 0.5870477,0.00291 0.5870469,0.00291 0.4447315,0.092455 0.444734,0.092455 0.302419,0.1105495 0.302417,0.1105495 0.329929,0.1646046 0.32993,0.1646033 0.239329,-0.2316919 0.239329,-0.2316919 0.160103,-0.1256767 0.160105,-0.1256767 0.160102,-0.1021909 0.160105,-0.1021899 0.142315,-0.082328 0.142314,-0.082328 0.231262,-0.1090091 0.231259,-0.1090091 0.26684,-0.098743 0.266839,-0.098743 0.320208,-0.073514 0.320209,-0.073527 0.355787,-0.041833 0.355785,-0.041834 0.426942,0.023827 0.426945,0.023828 0.355785,0.071179 0.355788,0.0711791 0.284627,0.09267 0.284629,0.09267 0.28514,0.1310267 0.28514,0.1310255 0.238179,0.1446969 0.238174,0.1446979 0.259413,0.1955332 0.259413,0.1955319 0.290757,0.296774 0.290758,0.2967753 0.151736,0.1941581 0.151734,0.1941594 0.135326,0.2149951 0.135327,0.2149952 0.154755,0.3202073 0.154758,0.3202085 0.09409,0.2677358 0.09409,0.267737 0.06948,0.3319087 0.06948,0.3319099 0.01111,0.00808 0.01111,0.00808 0.444734,0.2173653 0.444734,0.2173665 0.309499,0.2161102 0.309497,0.2161101 0.309694,0.2930023 0.309694,0.2930037 0.18752,0.2348726 0.187524,0.2348727 0.166516,0.2574092 0.166519,0.2574108 0.15273,0.3260252 0.152734,0.3260262 0.08972,0.2668403 0.08971,0.2668391 0.08295,0.3913655 0.08295,0.3913652 -6.21e-4,0.6582049 -6.21e-4,0.658204 -0.06362,0.315725 -0.06362,0.315725 -0.09046,0.289112 -0.09046,0.289112 -0.122759,0.281358 -0.12276,0.281356 -0.146626,0.252323 -0.146629,0.252322 -0.190443,0.258668 -0.190448,0.258671 -0.254911,0.268356 -0.254911,0.268355 -0.286872,0.223127 -0.286874,0.223127 -0.320203,0.187693 -0.320209,0.187693 -0.04347,0.03519 -0.04347,0.03521 0.0564,0.12989 0.0564,0.129892 0.08728,0.213472 0.08728,0.213471 0.189755,0.729363 0.189753,0.729362 0.0652,0.302417 0.0652,0.302419 -0.0018,0.675994 -0.0018,0.675995 -0.0801,0.373573 -0.08009,0.373577 -0.09,0.266839 -0.09,0.26684 -0.190389,0.391364 -0.19039,0.391366 -0.223169,0.320207 -0.223167,0.320209 -0.303585,0.315294 -0.303584,0.315291 -0.284631,0.220665 -0.284629,0.220663 -0.220128,0.132359 -0.220127,0.132358 -0.242395,0.106698 -0.242394,0.106699 -0.08895,0.04734 -0.08895,0.04733 -0.249052,0.07247 -0.24905,0.07247 -0.322042,0.0574 -0.322044,0.0574 -0.282794,-0.003 -0.282795,-0.003 -0.07115,-0.0031 -0.07115,-0.0031 -0.177894,-0.0033 -0.177893,-0.0033 -0.124528,0.02555 -0.124528,0.02555 z m -4.470079,-5.349839 0.214838,-0.01739 0.206601,-0.06782 0.206602,-0.06782 0.244389,-0.117874 0.244393,-0.11786 0.274473,-0.206822 0.27447,-0.20682 0.229308,-0.257201 0.229306,-0.2572 0.219161,-0.28463 0.219159,-0.284629 0.188541,-0.284628 0.188543,-0.28463 0.214594,-0.373574 0.214593,-0.373577 0.133861,-0.312006 0.133865,-0.312007 0.02861,-0.01769 0.02861,-0.01769 0.197275,0.26212 0.197278,0.262119 0.163613,0.150814 0.163614,0.150814 0.201914,0.09276 0.201914,0.09276 0.302417,0.01421 0.302418,0.01421 0.213472,-0.08025 0.213471,-0.08025 0.200606,-0.204641 0.200606,-0.204642 0.09242,-0.278887 0.09241,-0.278888 0.05765,-0.302418 0.05764,-0.302416 L 18.41327,13.768114 18.39502,13.34117 18.31849,12.915185 18.24196,12.4892 18.15595,12.168033 18.06994,11.846867 17.928869,11.444534 17.787801,11.042201 17.621278,10.73296 17.454757,10.423723 17.337388,10.263619 17.220021,10.103516 17.095645,9.9837986 16.971268,9.8640816 16.990048,9.6813736 17.008828,9.4986654 16.947568,9.249616 16.886308,9.0005655 16.752419,8.7159355 16.618521,8.4313217 16.435707,8.2294676 16.252892,8.0276114 16.079629,7.9004245 15.906366,7.773238 l -0.20429,0.1230127 -0.204289,0.1230121 -0.26702,0.059413 -0.267022,0.059413 -0.205761,-0.021508 -0.205766,-0.021508 -0.23495,-0.08844 -0.234953,-0.08844 -0.118429,-0.090334 -0.118428,-0.090333 h -0.03944 -0.03944 L 13.711268,7.8540732 13.655958,7.9706205 13.497227,8.1520709 13.338499,8.3335203 13.168394,8.4419112 12.998289,8.550301 12.777045,8.624223 12.5558,8.698155 H 12.275611 11.995429 L 11.799973,8.6309015 11.604513,8.5636472 11.491311,8.5051061 11.37811,8.446565 11.138172,8.2254579 10.898231,8.0043497 l -0.09565,-0.084618 -0.09565,-0.084613 -0.218822,0.198024 -0.218822,0.1980231 -0.165392,0.078387 -0.1653925,0.078387 -0.177894,0.047948 -0.177892,0.047948 L 9.3635263,8.4842631 9.144328,8.4846889 8.9195029,8.4147138 8.6946778,8.3447386 8.5931214,8.4414036 8.491565,8.5380686 8.3707618,8.7019598 8.2499597,8.8658478 8.0802403,8.9290726 7.9105231,8.9922974 7.7952769,9.0780061 7.6800299,9.1637148 7.5706169,9.2778257 7.4612038,9.3919481 7.1059768,9.9205267 6.7507497,10.449105 l -0.2159851,0.449834 -0.2159839,0.449834 -0.2216572,0.462522 -0.2216559,0.462523 -0.1459343,0.337996 -0.1459342,0.337998 -0.055483,0.220042 -0.055483,0.220041 -0.015885,0.206903 -0.015872,0.206901 0.034307,0.242939 0.034307,0.24294 0.096281,0.196632 0.096281,0.196634 0.143607,0.125222 0.1436071,0.125222 0.1873143,0.08737 0.1873141,0.08737 0.2752084,0.002 0.2752084,0.002 0.2312297,-0.09773 0.231231,-0.09772 0.1067615,-0.07603 0.1067614,-0.07603 0.3679062,-0.29377 0.3679065,-0.293771 0.026804,0.01656 0.026804,0.01656 0.023626,0.466819 0.023626,0.466815 0.088326,0.513195 0.088326,0.513193 0.08897,0.364413 0.08897,0.364411 0.1315362,0.302418 0.1315352,0.302418 0.1051964,0.160105 0.1051954,0.160103 0.1104741,0.11877 0.1104731,0.118769 0.2846284,0.205644 0.2846305,0.205642 0.144448,0.07312 0.144448,0.07312 0.214787,0.05566 0.214787,0.05566 0.245601,0.03075 0.245602,0.03075 0.204577,-0.0125 0.204578,-0.0125 z m 0.686342,-3.497495 -0.11281,-0.06077 -0.106155,-0.134033 -0.106155,-0.134031 -0.04406,-0.18371 -0.04406,-0.183707 0.02417,-0.553937 0.02417,-0.553936 0.03513,-0.426945 0.03513,-0.426942 0.07225,-0.373576 0.07225,-0.373575 0.05417,-0.211338 0.05417,-0.211339 0.0674,-0.132112 0.0674,-0.132112 0.132437,-0.10916 0.132437,-0.109161 0.187436,-0.04195 0.187438,-0.04195 0.170366,0.06469 0.170364,0.06469 0.114312,0.124073 0.114313,0.124086 0.04139,0.18495 0.04139,0.184951 -0.111218,0.459845 -0.111219,0.459844 -0.03383,0.26584 -0.03382,0.265841 -0.03986,0.818307 -0.03986,0.818309 -0.0378,0.15162 -0.03779,0.151621 -0.11089,0.110562 -0.110891,0.110561 -0.114489,0.04913 -0.114489,0.04913 -0.187932,-0.0016 -0.187929,-0.0016 z m -2.8087655,-0.358124 -0.146445,-0.06848 -0.088025,-0.119502 -0.088024,-0.119502 -0.038581,-0.106736 -0.038581,-0.106736 -0.02237,-0.134956 -0.02239,-0.134957 -0.031955,-0.46988 -0.031955,-0.469881 0.036203,-0.444733 0.036203,-0.444731 0.048862,-0.215257 0.048862,-0.215255 0.076082,-0.203349 0.076081,-0.203348 0.0936,-0.111244 0.0936,-0.111245 0.143787,-0.06531 0.1437865,-0.06532 h 0.142315 0.142314 l 0.142314,0.06588 0.142316,0.06588 0.093,0.102325 0.093,0.102325 0.04042,0.120942 0.04042,0.120942 v 0.152479 0.152477 l -0.03347,0.08804 -0.03347,0.08805 -0.05693,0.275653 -0.05693,0.275651 2.11e-4,0.430246 2.12e-4,0.430243 0.04294,0.392646 0.04295,0.392647 -0.09189,0.200702 -0.09189,0.200702 -0.148688,0.0984 -0.148687,0.0984 -0.20136,0.01212 -0.2013595,0.01212 z"></path>' + 17 - '</svg>'; 18 - 19 - /** 20 9 * Monospace status line — the quiet "developer tool" signature. Carries the 21 - * puzzle lineage plus links out to the source and the author's profile. The 22 - * architecture details (records, backlink reads, lexicon authority) now live on 23 - * the About page rather than crowding every footer. 10 + * puzzle lineage plus a link to the author's profile. The architecture details 11 + * (records, backlink reads, lexicon authority) now live on the About page 12 + * rather than crowding every footer. 24 13 */ 25 14 export function renderFooter(ctx: Ctx, puzzleNumber?: number): HTMLElement { 26 15 const lines: HTMLElement[] = []; ··· 36 25 { class: 'line links', style: puzzleNumber != null ? 'margin-top:6px' : undefined }, 37 26 el('a', { href: '/about', onClick: navTo(ctx, '/about') }, t(ctx.lang).about), 38 27 el('span', { class: 'spacer' }), 39 - el('a', { 40 - href: SOURCE_URL, 41 - target: '_blank', 42 - rel: 'noopener noreferrer', 43 - class: 'iconlink', 44 - title: t(ctx.lang).sourceCode, 45 - ariaLabel: t(ctx.lang).sourceCode, 46 - html: TANGLED_ICON, 47 - }), 48 28 el( 49 29 'a', 50 30 {
+7 -4
tests/footer.test.ts
··· 18 18 } 19 19 20 20 describe('renderFooter', () => { 21 - it('links to the source repo and the Bluesky profile', () => { 21 + it('links to the Bluesky profile', () => { 22 22 const footer = renderFooter(makeCtx()); 23 23 const hrefs = [...footer.querySelectorAll('a')].map((a) => a.getAttribute('href')); 24 - expect(hrefs).toContain('https://tangled.org/jeremy.herve.bzh/atmot'); 25 24 expect(hrefs).toContain('https://bsky.app/profile/jeremy.herve.bzh'); 26 25 }); 27 26 28 - it('renders the tangled mark inline and the avatar as an image', () => { 27 + it('does not link out to the source repository', () => { 28 + const footer = renderFooter(makeCtx()); 29 + expect(footer.querySelector('a[href*="tangled.org"]')).toBeNull(); 30 + }); 31 + 32 + it('renders the avatar as an image', () => { 29 33 const footer = renderFooter(makeCtx()); 30 - expect(footer.querySelector('a[href*="tangled.org"] svg')).not.toBeNull(); 31 34 const avatar = footer.querySelector('a[href*="bsky.app"] img'); 32 35 expect(avatar?.getAttribute('src')).toBe('/jeremy-herve.svg'); 33 36 });