VRAI bug (que tu as raison de souligner, j'aurais pas dû revert) :
la fonction applySelectionFromMessage rejetait TOUT message si pageUrl
n'était pas encore settée, ou si les URLs différaient sur un trailing
slash / fragment / query. Et pageUrl se populate de façon async via
chrome.tabs.query dans refreshPageContext — donc le premier SELECTION_CHANGED
arrivait avant pageUrl, était rejeté, et la sélection n'était jamais
appliquée.
Fixes dans sidepanel.js :
- applySelectionFromMessage : si pageUrl vide, on amorce depuis le
message (pageUrl, pageTitle, pageDomain, pageFavicon). Sans ce
fallback, le bouton « Clipper la sélection » n'apparaît jamais.
- Comparaison d'URL tolérante (origin + pathname, sans trailing slash,
hash, query).
- refreshPageContext : ne pas marquer tab?.url undefined comme restricted.
Version bumpée 0.4.1 → 0.4.2 (à chaque livraison comme demandé).
Le rollback complet de content.js, background.js et sidepanel.js vers
la v0.3.1 (que tu confirmes fonctionner) règle la régression. Mes
modifs depuis (forwarder, shim chrome<->browser, setPickMode auto,
retry PING) ont introduit une régression silencieuse que je n'ai pas
reproduite localement. Je remets le code qui marchait.
Manifest = v0.3.1 + ajout sidebar_action + browser_specific_settings.gecko
pour le build Firefox. Version bumpée 0.3.1 → 0.4.1.
Bug réel : la bannière 'Surlignez le texte à clipper' n'apparaissait
jamais en pratique parce que le side panel envoyait SET_PICK_MODE
avant que le content script ne soit prêt (race condition MV3 entre
chrome.scripting.executeScript et la mise en place du listener
chrome.runtime.onMessage). Le retry PING dans ensureContentScript
ne suffisait pas : le listener sidepanel peut aussi être en retard.
Fix radical : content.js affiche la bannière IMMÉDIATEMENT à
l'initialisation, sans attendre SET_PICK_MODE. L'utilisateur voit
immédiatement que l'extension est active. Un bouton ✕ permet de
la fermer (state local au tab). Pas de dépendance sur le side panel
pour le feedback visuel.
+ manifest : retrait de optional_host_permissions (redondant avec
host_permissions http://*/* et https://*/*, peut perturber Chrome
en dev).
+ bannière rediseignée : max-width 640px responsive, animation
slideIn, bouton ✕ accessible.
+ nouvelles clés i18n bannerDismiss dans les 15 locales.
+ build : exclusion de store-assets/ du zip et du xpi (sinon le
package Store faisait 327 Ko au lieu de 47 Ko).
Bannière pick mode :
- ensureContentScript() : retry le PING 8x (avec backoff 80→290ms) après
chrome.scripting.executeScript, parce que l'injection est async côté page
et le listener n'est pas enregistré immédiatement.
- Avant : SET_PICK_MODE partait dans le vide, la bannière « surlignez le
texte à clipper » n'apparaissait jamais.
Responsive :
- Media queries : <380px (Chrome Side Panel narrow), 380-420px (default),
>420px (Firefox sidebar), <320px (very small).
- Padding/margin/font-size adaptatifs selon la largeur du panneau.
- .selection-body : min-height 80px, plus de max-height fixe, scroll si
trop long. overflow-wrap: anywhere pour le texte long.
- .btn : padding 16px / min-height 48px pour le tactile.
- .page-card : word-break + overflow-wrap pour les URLs et titres longs.
- Firefox : media query dédié >480px pour aérer la sidebar Firefox
(plus large que Chrome Side Panel).
Rebuilt :
- memento-web-clipper-chrome-store.zip
- memento-web-clipper-firefox.xpi
Bug réel : chrome.runtime.sendMessage depuis un content script ne va
QU'au background service worker en MV3. Le side panel qui écoute via
chrome.runtime.onMessage ne reçoit jamais rien directement. Résultat :
la sélection, le titre de la page et le contexte n'atteignaient jamais
le panneau.
Fix :
- background.js : ajouter un onMessage qui forward SELECTION_CHANGED et
PAGE_CONTEXT_CHANGED vers toutes les surfaces (side panel).
- sidepanel.js applySelectionFromMessage : normaliser les URLs
(ignorer query/fragment/trailing slash), amorcer pageUrl depuis le
premier message si tabs.query a échoué, accepter une sélection qui
arrive avant refreshPageContext.
La v0.3.1 souffrait du même bug — la sélection fonctionnait parfois par
chance quand Chrome routait le message avant que le side panel ne soit
complètement initialisé. Maintenant c'est garanti.
Traductions:
- 'Tondeuse Web' (FR), 'Cortadora web' (ES), 'Schneiden Sie aus' (DE),
'ritagliare' (IT) etc. — le terme 'clip' était traduit littéralement
(tondeuse = mower, ausschneiden = cut out, ritagliare = trim).
- Garder 'clip' comme terme technique dans les 15 locales, sauf les
formulations verbe qui prennent une conjugaison locale correcte.
- 87 chaînes corrigées dans le bundle de traductions embarqué.
Diagnostic 401:
- Le message 'Veuillez vous connecter à Memento' s'affichait même
quand l'utilisateur était connecté, simplement sur une URL différente
(localhost vs 127.0.0.1 vs LAN IP — les cookies sont scopés par host).
- Nouveau message multi-ligne incluant l'URL configurée + bouton
'Ouvrir Memento' qui ouvre l'onglet directement à cette URL.
- Nouvelle clé i18n errLoginHintUrl (placeholder $URL$) traduite
dans les 15 locales.
Rebuilt :
- memento-web-clipper-chrome-store.zip (44 Ko)
- memento-web-clipper-firefox.xpi (44 Ko)
- extension/scripts/generate-store-assets.py : générateur Python/PIL avec
la palette x.ai premium (BG #0B0A09, TEXT #F4F1EA, OCHRE #D4A373).
- extension/store-assets/ :
* icon-128.png (128×128)
* promo-tile-small.png (440×280)
* promo-tile-large.png (920×680)
* marquee.png (1400×560)
* screenshot-1.png (1280×800) — sidebar idle avec sélecteur de carnet
* screenshot-2.png (1280×800) — confirmation IA avec résumé + tags
* screenshot-3.png (1280×800) — vue RTL/persan (font DejaVu pour glyphes)
Tous les assets respectent les dimensions Chrome Web Store et sont prêts
pour la soumission.
- Nouvelle page /privacy bilingue (FR/EN) avec sélection par Accept-Language
ou ?lang=, sections complètes (données collectées, IA/BYOK, RGPD, sécurité,
contact). Style sombre aligné sur la landing (x.ai premium).
- Liens footer landing.footer.legal.* mis à jour vers /privacy dans les 15 locales.
- Extension Web Clipper rendue cross-browser :
* background.js détecte chrome.sidePanel vs browser.sidebarAction
* sidepanel.js ajoute un shim chrome<->browser pour Firefox
* manifest.json inclut sidebar_action + browser_specific_settings.gecko
+ optional_host_permissions (Chrome les ignore)
- Script scripts/build-firefox.mjs pour produire un .xpi signé pour AMO,
symétrique de build-chrome-store.mjs. Exclusion mutuelle des artefacts
Chrome/Firefox pour éviter la cross-contamination.
- Branding corrigé : 176 occurrences de 'Momento' -> 'Memento' dans le
bundle de traductions embarqué.
- Version bumpée 0.3.1 -> 0.4.0.
Un pot de crédits global (BASIC 100 / PRO 1 000 / BUSINESS 4 000) remplace
les seaux par fonction côté débit. Packs one-shot S/M/L, admin sans seaux
legacy, usage multi-features, hints de coût, anti-flash thème et hydratation
admin. Inclut aussi le pipeline slides renforcé et la page publique polishée.
- Ne plus auto-scroller vers Memory Echo : pastille bas de page + reset scroll au titre
- Badges notes/carnets générés par l’IA dans la sidebar
- Wizard : langue du contenu = langue de la requête ; refresh carnet après création
- Voix : erreurs not-allowed/no-speech moins bruyantes
Centralise la réserve via ai-quota, corrige admin unavailable (-1), brancher les routes sans quota et le host-pays brainstorm, avec usage-meter élargi, noms de clusters, MCP et ajustements dashboard/insights.
Co-authored-by: Cursor <cursoragent@cursor.com>
Ajoute une poignée drag pour la largeur de la sidebar (persistée) et sépare inbox/arbre carnets avec slider vertical ; supprime le bouton Note du jour devenu inutile.
Co-authored-by: Cursor <cursoragent@cursor.com>
Mobile fixes (CRITIQUE):
- theme.ts: C.surface + emerald/blue/amber/purple ajoutés (crash Révision fix)
- note/[id].tsx: édition préserve le HTML (titre seulement, contenu read-only)
+ message explicatif 'utilisez la version web'
- store.ts: logout appelle POST /api/mobile/auth/logout avant clearToken
Mobile auth (SÉCURITÉ):
- Nouvelle route /api/mobile/auth/logout — blacklist Redis du token (TTL 90j)
- verifyMobileToken devient async + check Redis blacklist
- getMobileUserId devient async
Note: Publication IA mode + templates déjà dans note-editor-toolbar.tsx (pas manquant)
Note: Structured Views Wizard déjà branché via StructuredViewsIntro (pas orphelin)
Vrai problème (hauteur, pas largeur) : le conteneur scrollable externe
était flex-1 + overflow-y-auto mais PAS flex flex-col. Du coup le
flex-1 de la vue carnets ne s'appliquait pas → la liste prenait la
hauteur de son contenu et s'arrêtait au milieu (trait flottant + vide
en dessous) quand il y avait peu de carnets.
Fix : ajoute `flex flex-col min-h-0` au conteneur externe pour que la
chaîne flex fonctionne — la liste des carnets remplit désormais toute
la hauteur jusqu'au pied de page (Pack Pro), quel que soit le nombre
de carnets.
Co-authored-by: Cursor <cursoragent@cursor.com>
L'écart précédent (320->352, +32px) était imperceptible. La sidebar
étant ancrée dès md (768px), on applique une largeur franche à ce
breakpoint pour un résultat indiscutable :
- < 768px (mobile overlay) : w-80 (320px)
- >= 768px : w-[26rem] (416px) [+96px]
- >= 1536px : w-[30rem] (480px)
Co-authored-by: Cursor <cursoragent@cursor.com>
La sidebar restait à 320px en production car l'élargissement ne se
déclenchait qu'à partir du breakpoint xl (1280px). Abaisse le seuil à
md (768px) pour couvrir tous les écrans desktop en production.
- < 768px (mobile, overlay) : w-80 (320px)
- >= 768px : w-[22rem] (352px)
- >= 1536px : w-[26rem] (416px)
Le fallback Suspense du layout est aligné pour éviter tout décalage.
Co-authored-by: Cursor <cursoragent@cursor.com>
h-full dépendait du parent pour la hauteur — ne cascadaît pas en prod.
h-screen (100vh) + self-stretch force la hauteur pleine indépendamment.
Suspense fallback aussi mis à h-screen pour éviter le flash.
Global (globals.css):
- prefers-reduced-motion: désactive toutes les animations/transitions
- focus-visible:ring global sur tous les éléments interactifs
Search modal:
- role="dialog" + aria-modal="true" + aria-label
- onClick backdrop ferme la modale
Insights peek:
- DOMPurify.sanitize() sur dangerouslySetInnerHTML (XSS fix)
Login form:
- autoComplete="email" / "current-password"
- htmlFor sur tous les labels
Register form:
- autoComplete="name" / "email" / "new-password"
- htmlFor sur tous les labels
GridCard (notes-list-views):
- Actions visibles sur mobile (opacity-100 sm:opacity-0)
- aria-sort sur colonne triable
- role="button" + tabIndex + onKeyDown sur lignes table
Editorial view:
- role="button" + tabIndex + onKeyDown sur articles
- Menu trigger aria-label + visible mobile
Toolbar:
- aria-label voice i18n
Root cause: le mode grille utilise GridCard dans notes-list-views.tsx,
PAS NoteCard de note-card.tsx. Toutes les fixes précédentes étaient
sur le mauvais composant.
Fix:
- onOpenHistory ajouté à GridCardSharedProps
- Badge History en haut-droite si historyEnabled=true
- Bouton History dans la barre d'actions (vert si activé, gris sinon)
- Prop threadé: NotesListViews → NotesMasonryGrid → GridCard
Avant: History caché dans le menu '...' (MoreVertical) — invisible
Maintenant: bouton History standalone comme Pin/Reminder/Color
- Vert emerald si versioning activé
- Gris discret si versioning désactivé
- Cliquer ouvre l'historique ou l'activation
Root cause: home-client.tsx n'écoutait PAS NOTE_CHANGE_EVENT.
Quand versioning était activé depuis l'éditeur, emitNoteChange était dispatché
mais personne ne mettait à jour la liste des notes → historyEnabled restait false
sur les cartes/liste.
Fix: useEffect qui écoute NOTE_CHANGE_EVENT et patch les notes + pinnedNotes
avec les champs mis à jour (historyEnabled, etc.).
Card view:
- Badge plus visible: bg-emerald-500/15 + border + backdrop-blur
- Position top-2.5 end-2.5 (top-right corner, bien visible)
Table/list view (notes-list-views.tsx):
- Icône History 11px text-emerald-500 à côté du titre (comme Pin)
- Present sur toutes les lignes du tableau
Editorial view (notes-editorial-view.tsx):
- Icône History 14px text-emerald-500 à côté du titre + Pin
- Visible sans hover (contrairement au menu dropdown)
Sidebar (UX: User Freedom):
- Visible par défaut sur /insights (pas caché par le système)
- Toggle via bouton hamburger: toggle-insights-sidebar event
- Préférence persistée en localStorage (insights-sidebar-collapsed)
- User décide: voir ou cacher le sidebar
Cluster chips:
- Triées par taille (plus grand cluster en premier)
- Compteur de notes par cluster (badge arrondi)
- max-w-[120px] sur le nom pour éviter overflow
- padding/gap plus compacts (px-2.5 py-1 gap-1.5)
AGENTS.md: 'pas la liste carnets sur /insights'
- sidebar.tsx: isImmersiveRoute sur /insights → fixed overlay au lieu de relative
- Toutes tailles d'écran: sidebar caché par défaut, s'ouvre en drawer
- Bouton hamburger visible sur desktop + mobile (pas lg:hidden)
- Insights prend 100% de la largeur → graphe + dashboard plus spacious
- handleNoteClick fetch la note + ouvre peek panel au lieu de naviguer
- Panel fixed right (left en RTL), spring animation (stiffness 340 damping 34)
- Header avec titre + boutons Maximize2 (ouvrir note complète) et X (fermer)
- Content rendu en read-only via dangerouslySetInnerHTML
- prefers-reduced-motion: animation désactivée
- responsive: w-full sur mobile, 50vw/640px max sur desktop
Le volume postgres a ete init avec un mot de passe different de .env.docker.
ALTER USER depuis l'interieur du conteneur (pas besoin d'auth sur socket local)
aligne le mot de passe DB avec POSTGRES_PASSWORD de .env.docker.