2 231 pages publiées lues une à une par script, 27 pages ouvertes dans un vrai Chrome, 13 807 médias uniques contrôlés par requête. Défauts classés par cause dans le générateur, jamais par page.
168bcff0 du projet asa-compass-site, mis en ligne le 31 août 2026 vers 23 h (heure de Montréal). Le contenu servi est identique octet pour octet au dossier site-public/ engendré le 31 août à 22 h 50 (pages) et 22 h 53 (fichiers recopiés) : vérifié par empreinte MD5 sur huit fichiers dont index.html (31f541d1…), collections.html (d7ce2fc9…), pieces.json, portefeuille.js et une page de collection.sonde/site.mjs dans son état du 1er septembre à 06 h 38. Il a bougé depuis la génération publiée (exemple : le registre des ventes d'une page de collection montre 16 lignes en ligne, la source dit aujourd'hui slice(0, 12)). Les numéros de ligne cités renvoient à cet état de la source ; chaque défaut a été constaté sur le site en ligne, pas déduit de la source.| # | Cause | Touche | Ce que le visiteur voit |
|---|---|---|---|
| 1 | Année de première frappe calculée avec un seuil de round différent de celui de l'index | 996 pages | « first minted 2022 » sur une page dont toutes les pièces sont datées d'octobre à décembre 2021, et que l'index range sous 2021 |
| 2 | Historique par compte engendré avant la reconstruction du catalogue, avec des adresses de pages figées | 1 184 portefeuilles, 2 022 lignes | Dans le Portfolio, onglet History, des liens vers 37 collections dont la page n'existe plus ; Cloudflare y sert l'accueil |
| 3 | Le tri « Most traded » de l'index n'a aucune donnée : toutes les tuiles portent un volume de 0 | 2 222 tuiles | Choisir « Most traded » ne change rien à l'ordre |
| 4 | L'index des pièces cherche les collections regroupées sous un titre qui n'existe pas dans la base | 31 collections, 2 810 pièces | Le Portfolio ne reconnaît jamais ces pièces, même quand le visiteur les détient |
| 5 | Un compteur de la page « Read me » compare un tableau à zéro | 1 page | « Spread across 0 collections » sous les 51 579 ventes |
| 6 | 18 575 fichiers déployés sur une limite de 20 000, dont 15 314 images de repli jamais demandées | tout le déploiement | Rien encore ; le prochain millier d'images fera échouer la mise en ligne entière |
| 7 | Aucune page 404 : tout chemin inconnu rend l'accueil avec un code 200 | toute adresse fausse | Un lien mort ressemble à un lien qui marche ; c'est ce qui a caché les défauts 2 et « /c/null.html » |
| 8 | Image Open Graph en chemin relatif | 2 231 pages | Aucune vignette quand un lien du site est partagé sur un réseau social ou une messagerie |
| 9 | Les quatre onglets de l'index reçoivent deux écouteurs de clic | 1 page, 4 boutons | Rien aujourd'hui ; c'est le motif exact du bouton de filtres, prêt à se rejouer |
| 10 | Page d'une pièce : l'adresse de l'actif est prise pour une image, et les lectures de chaîne n'ont pas de délai | cas limites | Un cadre vide et « 0 holder » sur un jeton fongible, « Reading the chain… » pendant 6 à 8 s |
Ce qui se voit. La page /c/algopumpkin annonce « first minted 2022 » dans son bandeau et « minted in 2022 » dans sa description, alors que ses huit pièces portent « Oct 2021 » et que l'index la range sous l'onglet 2021. Sur 2 222 pages de collection, 1 161 disent 2022 ; l'index n'en compte que 165 sous 2022. 912 pages se contredisent visiblement (l'année du bandeau n'apparaît sur aucune de leurs pièces), et 996 sont touchées au total.
La cause. Deux calculs de la même année. pageCollection() (site.mjs ligne 4675) écrit c.premier_round < 17000000 ? 2021 : 2022, un seuil posé à la main. Tout le reste du site (index, onglets, mois, dates des pièces) passe par anneeDeCollection() et dateDuRound(), dont la borne de 2022 est le round 18 363 444. Les collections nées entre les rounds 17 000 000 et 18 363 443, soit de la fin octobre à la fin décembre 2021, tombent entre les deux : la première est algopumpkin (round 17 000 521), la dernière degen-forever-series (18 363 421), et monsti (16 999 873) est correcte à 648 rounds près.
Reproduire. grep -l "first minted 2022" site-public/c/*.html | wc -l donne 1 161 ; la même commande sur l'index (data-annee="2022") donne 165.
Correction. Une ligne : remplacer le seuil par anneeDeCollection(c), qui existe déjà et sert partout ailleurs. Vérifier ensuite que les 2 222 pages tombent dans la même année que leur tuile d'index (data-annee).
Ce qui se voit. Un visiteur qui connecte un portefeuille ayant échangé des AlgoDroid, des Yieldling ou des YldYcorn voit dans l'onglet History du Portfolio des lignes qui mènent à /c/algodroid.html, /c/yieldling-ultra-rare.html, /c/yldycorn-generation-a.html. Ces pages n'existent pas dans la version publiée : Cloudflare y répond l'accueil, avec un code 200. 37 adresses de collection sont dans ce cas, sur 2 022 lignes d'historique réparties dans 1 184 portefeuilles (sur 103 158 lignes au total). Aucun des 37 titres n'existe sous une autre adresse dans le catalogue actuel : ces collections ont été absorbées ou renommées par la reconstruction.
La cause. C'est le motif de /c/null.html, une troisième fois. Les tranches sonde/resultats/comptes/*.json datent du 31 août à 09 h 18 ; le catalogue a été reconstruit à 17 h 20 et le site engendré à 22 h 50. index-comptes.mjs recopie l'adresse de page depuis ventes.json (l: v.limace ?? null, ligne 44), où elle avait été figée par collecte-ventes-completes.mjs (lignes 88 et 148) au moment de la collecte. site.mjs, lui, recalcule l'adresse de chaque vente à la génération, par titre (ligne 5535), ce qui est la bonne méthode ; mais il recopie comptes/ tel quel (lignes 5679 à 5686). Et reconstruire.sh ne relance jamais index-comptes.mjs : ce n'est pas l'une de ses sept étapes.
Reproduire. https://asa-compass-site.pages.dev/c/algodroid rend le titre de l'accueil. Dans site-public/comptes/22.json, la première entrée porte "l":"algodroid".
Correction. Que index-comptes.mjs recalcule l'adresse par titre depuis catalogue-public.json, comme le fait site.mjs, au lieu de lire celle de ventes.json ; et qu'il devienne une étape de reconstruire.sh, après site.mjs, puisqu'il dépend des adresses que celui-ci attribue. Même chose pour le champ limace de ventes.json, qui ne devrait pas exister : une adresse de page est une donnée dérivée.
Ce qui se voit. Sur l'index, choisir « Most traded » dans la roulette laisse la grille dans l'ordre chronologique : première tuile AlgoKitten, dernière ALGOegg Fellowship, exactement comme « Oldest first ». Constaté dans Chrome, à 1 280 comme à 375 points.
La cause. Les 2 222 tuiles portent data-volume="0". pageCollections() remplit ce champ avec c.volume ?? 0 (ligne 5291), et aucune collection du catalogue ne possède de champ volume : ses clés sont cle, titre, pieces, collections, createurs, listeCreateurs, note, premier_round, ids, rarete, derniereActivite, rang, limace, ventes, vignettes. Le comparateur du navigateur (ligne 2511) fait 0 - 0 puis retombe sur le round.
Reproduire. grep -o 'data-volume="[0-9]*"' site-public/collections.html | sort | uniq -c : une seule valeur, 0, 2 222 fois.
Correction. Le volume se recalcule à la génération depuis c.ventes, qui est rattaché à chaque collection (ligne 5629) avant l'appel de pageCollections() : la somme des prix suffit, ou le nombre de ventes si c'est ce que Karl entend par « traded ». Ne pas l'inscrire dans le catalogue.
Ce qui se voit. Un visiteur qui détient un Moon Dude (547 pièces publiées), un World Chess (578), un Astro Buddies (203) ou l'une des 28 autres voit « none of them belong to the 2 222 collections published here », alors que la page de la collection existe et montre ses pièces. 31 collections, 2 810 pièces.
La cause. pieces-index.mjs retrouve les pièces d'une collection publiée en cherchant son titre exact dans la table collections de catalogue.sqlite (lignes 31 à 41). Les collections issues du regroupement par famille portent un titre de famille, « Moon Dude », « 16bit Tarot », qui n'existe pas dans la base : elle ne connaît que « Moon Dude Basic », « Moon Dude Fine », « 16bit Tarot - The Tower ». La requête ne rend rien, et pieces.json ne contient aucun de leurs identifiants.
Reproduire. La première vignette de /c/moon-dude est l'actif 307771525 ; pieces.json n'a pas de clé 307771525. Témoin : l'actif 197227528 d'AlgoKitten y est, rang 0.
Correction. Faire chercher les pièces par la liste des collections membres (le champ collections ou ids du catalogue) plutôt que par le titre affiché. À noter aussi : pieces.json date du 31 août à 17 h 43 et n'a pas été régénéré avec le site de 22 h 50 ; les adresses y correspondent encore toutes à des pages, mais rien ne le garantit à la prochaine reconstruction si l'étape 7 de reconstruire.sh est sautée.
Ce qui se voit. Sur Read me, sous « 51 579 trades recovered » : « Spread across 0 collections. Everything else never changed hands on-chain. » Le site compte 646 collections avec des ventes.
La cause. pageLisezMoi() ligne 5038 : collections.filter((c) => (c.ventes || 0) > 0). Au moment de l'appel, c.ventes est un tableau (rattaché ligne 5629). Un tableau vide vaut false devant || et donne 0 ; un tableau plein comparé à 0 donne NaN > 0, faux. Le compteur est donc toujours 0.
À côté, deux nombres qui divergent pour la même chose. L'accueil annonce 50 568 ventes (ventesLiees, celles rattachées à une page) ; Read me en annonce 51 579 (ventes, toutes). Et l'accueil affiche « 1,891,551 items found on-chain » et 37 624 créateurs depuis une constante figée dans la source (ligne 5635, const totaux = { pieces: 1891551, createurs: 37624 }) qu'aucune reconstruction ne met à jour.
Correction. (c.ventes?.length ?? 0) > 0. Décider quelle liste de ventes fait foi et la passer aux deux pages. Recalculer les totaux de chaîne à la génération ou les dater.
Ce qui se voit. Rien encore. Mais Cloudflare Pages refuse un déploiement de plus de 20 000 fichiers (limite documentée, non mesurée ici), et site-public/ en compte 18 575 : 2 231 pages, 1 024 tranches d'historique, 15 314 fichiers dans img/ (564 Mo). Le catalogue a doublé le 31 août ; le prochain palier d'extraction d'images fera échouer la mise en ligne entière, avec un message que le projet n'a encore jamais vu.
Ce que img/ fait vraiment. Aucune des 2 231 pages n'y fait référence directement : toutes les vignettes viennent du cache asa-compass-medias.karlgrandmaison.workers.dev/media/vignette/. Le dossier n'est demandé que par un gestionnaire d'erreur du script commun, qui remplace l'adresse du cache par /img/… quand une image échoue (ligne 3421, el.dataset.repli = '1'). Or le contrôle de ce balayage a interrogé les 13 807 médias uniques du site : 13 807 réponses 200 avec le bon type (13 482 image/jpeg, 325 video/mp4) et une taille non nulle (la plus petite, 782 octets, est une vraie image). Le repli n'a jamais été sollicité. Le pied de page dit d'ailleurs encore « images served from IPFS », ce qui n'est plus vrai.
Correction. Cesser de recopier img/ dans le site (site.mjs lignes 5651 à 5657) et faire porter le repli par le cache lui-même, qui sait déjà rattraper un média manquant auprès des passerelles. À défaut, ne recopier que les vignettes réellement référencées. Dans les deux cas, faire compter les fichiers par la génération et refuser au-delà d'un seuil.
Ce qui se voit. /page-inexistante-xyz, /c/null, /c/algodroid : l'accueil, code 200, sans un mot. Le projet le sait et s'y est fait prendre deux fois ; le défaut 2 s'y cache encore aujourd'hui.
La cause. Cloudflare Pages sert index.html pour tout chemin inconnu tant qu'il n'existe pas de 404.html à la racine du dossier déployé. Le générateur n'en écrit pas.
Correction. Écrire un 404.html dans site.mjs, à côté de help.html : Cloudflare le sert alors avec un vrai code 404, et tout contrôle de liens, y compris celui de Karl en cliquant, voit enfin la différence entre une page et une absence. C'est la correction qui protège toutes les autres.
Ce qui se voit. Un lien du site collé dans Discord, X, iMessage ou Telegram n'affiche aucune vignette. TETE (ligne 2031) écrit <meta property="og:image" content="/marque/logo.png"> ; le protocole Open Graph exige une adresse absolue, et les lecteurs de prévisualisation ignorent un chemin relatif. Toutes les pages sont touchées.
Correction. Préfixer par l'origine publique du site. Elle n'est pas encore choisie (adresse pages.dev aujourd'hui, domaine plus tard) : la lire d'une variable d'environnement à la génération.
Ce qui se voit. Rien aujourd'hui. Un espion posé avant le chargement de /collections compte deux écouteurs click sur chacun des boutons All, Favourites, 2021 et 2022. Sur l'accueil et sur les pages de collection, aucun élément n'en porte deux.
La cause. Deux scripts branchent la même barre. Le bloc du pied de page (ligne 2286) branche toute barre .periodes et bascule le panneau désigné par data-cible, ce qui sert au palmarès de l'accueil ; le script de l'index (ligne 2492) branche les mêmes boutons par data-onglet. Comme les onglets de l'index n'ont pas de data-cible, le premier écouteur cherche getElementById(undefined) et ne fait rien. Le jour où un onglet de l'index recevra un data-cible, ou où le pied de page changera de critère, la bascule se fera deux fois et s'annulera : c'est exactement ce qui est arrivé au bouton de filtres.
Correction. Restreindre le bloc du pied de page aux barres qui ont un data-cible (.periodes:has([data-cible]) ou un attribut sur la barre), ou retirer les onglets de l'index de la classe .periodes.
Ce qui se voit. /a?id=31566704 (USDC) affiche « Reading the chain… » pendant six à huit secondes, puis un cadre média vide et « HELD BY 0 WALLET », « 0 holder ». Le cadre est vide parce que la page place l'adresse de l'actif (https://www.centre.io/usdc) dans un <img>. Le compte de détenteurs est faux parce que la lecture des soldes est plafonnée. Sur les NFT du catalogue, la page fonctionne : AlgoKitten_001 charge son image pleine par bit.ly (6 220 pixels de large) en six secondes, Gravelight retombe sur la vignette du cache. Un identifiant absent, inexistant ou non numérique donne chacun un message clair.
Pourquoi c'est mineur. On n'arrive sur cette page que depuis une collection, et le catalogue ne contient pas de jetons fongibles. Cela devient un défaut le jour où l'adresse d'une pièce est partagée à la main, ce qui est le but de la page.
Correction. Un délai sur les lectures de chaîne, avec message ; ne mettre dans le cadre média que ce qui répond avec un type image/ ou video/ ; dire « fungible asset » quand le tirage n'est pas 1.
Chaque ligne est un contrôle réel, pas une lecture de source.
href et src interne a été résolu contre le dossier publié (12 pages de premier niveau, 2 222 collections, redirection .html comprise). 0 ancre manquante, y compris /read-me.html#market et les 24 mois de la chronologie.src="" dans le balisage des 2 231 pages.node --check. Aucun accent grave n'a refermé le gabarit dans cette version.image/jpeg ou video/mp4, taille non nulle) ; 13 570 servis depuis R2, 237 depuis la bordure. Aucune image cassée dans les pages ouvertes.search.json et de pieces.json ont une page ; comptes/ non (défaut 2). Les 1 024 tranches d'historique existent (deux caractères de base 32) et sont servies en application/json.help.html redirige vers Read me.city-1), 16 titres à guillemets ou esperluette, la collection aux 66 ventes. 1 576 collections sans vente n'ont pas de section Trades plutôt qu'une section vide ; aucune n'a 0 vignette.comptes/, pieces.json) et non en le manipulant./a?id=…) : six identifiants ouverts, dont les cas limites ; chaque page interroge l'indexeur en direct et le vérifier pour tous serait une campagne de requêtes que les indexeurs publics freinent par rafale. Le défaut 10 vient de ces six./img/ : jamais déclenché, puisque le cache a répondu 13 807 fois sur 13 807. Son fonctionnement en cas de panne du cache n'a pas été observé.loading="lazy"). Les autres ont été contrôlées par requête directe, ce qui vérifie le fichier et non son affichage.Tout vit dans balayage-site/, à côté du projet, sans rien modifier ailleurs.
balayer.mjs : lit les 2 231 pages de site-public/ (identique au site en ligne), résout chaque lien et chaque ancre, compte les identifiants doublés, cherche les textes suspects, relève la structure de chaque page de collection, et isole les scripts par empreinte. Sortie : resultats/releve.json.images.mjs : requête HEAD sur les 13 807 médias uniques, avec trois essais et attente croissante ; vérifie statut, type et taille. Sortie : resultats/images.json.chrome.sh et essais/*.js : ouvrent 27 pages avec sonde/verifier-chrome.mjs, l'espion d'écouteurs posé avant le chargement (espion-elements.js, qui compte par élément et non seulement par identifiant), et manipulent boutons, onglets, filtres, tris, recherche. Sorties : resultats/chrome/*.txt.comptes/, pieces.json, catalogue, base SQLite) sont des commandes node -e reproduites dans ce rapport.Le chantier régénère le site dans les heures qui viennent. Pour savoir si un défaut a survécu, rejouer node balayer.mjs puis node images.mjs depuis balayage-site/ sur le nouveau site-public/, et comparer les compteurs anneeFrappeContreditParToutesPieces2021 (défaut 1, doit tomber à 0), la présence de data-volume="0" (défaut 3), et la commande de comparaison des adresses de comptes/ (défaut 2).
Rapport de la session de balayage, 1er septembre 2026. Aucun fichier de sonde/, site-public/ ni contrat/ n'a été modifié.