diff --git a/.gitignore b/.gitignore
index 7f40959..ab46839 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,3 +50,6 @@ docker-data/
# Misc
*.tsbuildinfo
next-env.d.ts
+
+# Monitoring secrets (generate at deploy)
+monitoring/metrics-token
diff --git a/architectural-grid/server.ts b/architectural-grid/server.ts
index d1b46e5..2c6cb42 100644
--- a/architectural-grid/server.ts
+++ b/architectural-grid/server.ts
@@ -36,7 +36,7 @@ async function startServer() {
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
- const PORT = 3000;
+ const PORT = 4000;
app.use(express.json());
diff --git a/architectural-grid/vite.config.ts b/architectural-grid/vite.config.ts
index 0506f1b..9b26838 100644
--- a/architectural-grid/vite.config.ts
+++ b/architectural-grid/vite.config.ts
@@ -1,15 +1,11 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
-import {defineConfig, loadEnv} from 'vite';
+import {defineConfig} from 'vite';
export default defineConfig(({mode}) => {
- const env = loadEnv(mode, '.', '');
return {
plugins: [react(), tailwindcss()],
- define: {
- 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
- },
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
diff --git a/architectural-grid1/server.ts b/architectural-grid1/server.ts
index d1b46e5..2c6cb42 100644
--- a/architectural-grid1/server.ts
+++ b/architectural-grid1/server.ts
@@ -36,7 +36,7 @@ async function startServer() {
const app = express();
const server = createServer(app);
const wss = new WebSocketServer({ server });
- const PORT = 3000;
+ const PORT = 4000;
app.use(express.json());
diff --git a/architectural-grid1/vite.config.ts b/architectural-grid1/vite.config.ts
index 0506f1b..3636b89 100644
--- a/architectural-grid1/vite.config.ts
+++ b/architectural-grid1/vite.config.ts
@@ -1,15 +1,11 @@
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import path from 'path';
-import {defineConfig, loadEnv} from 'vite';
+import {defineConfig} from 'vite';
-export default defineConfig(({mode}) => {
- const env = loadEnv(mode, '.', '');
+export default defineConfig(() => {
return {
plugins: [react(), tailwindcss()],
- define: {
- 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY),
- },
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
diff --git a/memento-mobile/assets/adaptive-icon.png b/memento-mobile/assets/adaptive-icon.png
index 08cd6f2..6b825f4 100644
Binary files a/memento-mobile/assets/adaptive-icon.png and b/memento-mobile/assets/adaptive-icon.png differ
diff --git a/memento-mobile/assets/icon.png b/memento-mobile/assets/icon.png
index 08cd6f2..6b825f4 100644
Binary files a/memento-mobile/assets/icon.png and b/memento-mobile/assets/icon.png differ
diff --git a/memento-mobile/assets/splash.png b/memento-mobile/assets/splash.png
index 08cd6f2..6b825f4 100644
Binary files a/memento-mobile/assets/splash.png and b/memento-mobile/assets/splash.png differ
diff --git a/memento-mobile/tailwind.config.js b/memento-mobile/tailwind.config.js
index 0596dd2..701bd15 100644
--- a/memento-mobile/tailwind.config.js
+++ b/memento-mobile/tailwind.config.js
@@ -1,7 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./app/**/*.{js,jsx,ts,tsx}', './components/**/*.{js,jsx,ts,tsx}'],
- presets: [require('nativewind/preset')],
theme: {
extend: {
colors: {
diff --git a/memento-note/components/rich-text-editor.tsx b/memento-note/components/rich-text-editor.tsx
index eff5cf4..c06c4cf 100644
--- a/memento-note/components/rich-text-editor.tsx
+++ b/memento-note/components/rich-text-editor.tsx
@@ -3,6 +3,7 @@
import { useEffect, useRef, useState, useCallback, forwardRef, useImperativeHandle } from 'react'
import { createPortal } from 'react-dom'
import { useLanguage } from '@/lib/i18n'
+import { sanitizeRichHtml } from '@/lib/sanitize-content'
import { useEditor, EditorContent, useEditorState } from '@tiptap/react'
import { BubbleMenu } from '@tiptap/react/menus'
import StarterKit from '@tiptap/starter-kit'
@@ -1572,7 +1573,7 @@ function BubbleToolbar({ editor, onSuggestCharts }: { editor: Editor | null; onS
)}
{aiModal.type === 'preview' &&
{t('ai.result.suggestion') || 'Suggestion'}}
-
+
diff --git a/memento-note/extension/dist-chrome-store/diagnose.js b/memento-note/extension/dist-chrome-store/diagnose.js
new file mode 100644
index 0000000..b98cdf8
--- /dev/null
+++ b/memento-note/extension/dist-chrome-store/diagnose.js
@@ -0,0 +1,160 @@
+#!/usr/bin/env node
+/**
+ * Script de diagnostic pour l'extension Momento
+ * Vérifie tous les fichiers et identifie les problèmes potentiels
+ */
+
+import fs from 'fs'
+import path from 'path'
+import { fileURLToPath } from 'url'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const extDir = __dirname
+
+console.log('🔍 Diagnostic Extension Momento\n')
+
+const issues = []
+const warnings = []
+
+// Vérifier la syntaxe des fichiers JS
+function checkSyntax(filePath) {
+ try {
+ const content = fs.readFileSync(filePath, 'utf8')
+ // Pas de vérification syntaxique simple en Node.js sans eval
+ // On vérifie juste que le fichier est lisible
+ return true
+ } catch (error) {
+ issues.push(`Fichier illisible: ${filePath} - ${error.message}`)
+ return false
+ }
+}
+
+// Vérifier les event handlers inline dans le HTML
+function checkInlineHandlers(htmlPath) {
+ try {
+ const content = fs.readFileSync(htmlPath, 'utf8')
+ const inlineHandlers = []
+
+ if (content.match(/onerror=/i)) inlineHandlers.push('onerror')
+ if (content.match(/onclick=/i)) inlineHandlers.push('onclick')
+ if (content.match(/onload=/i)) inlineHandlers.push('onload')
+
+ if (inlineHandlers.length > 0) {
+ issues.push(`Event handlers inline trouvés dans ${htmlPath}: ${inlineHandlers.join(', ')}`)
+ } else {
+ console.log('✓ Pas d\'event handlers inline dans le HTML')
+ }
+ } catch (error) {
+ issues.push(`Impossible de lire ${htmlPath}: ${error.message}`)
+ }
+}
+
+// Vérifier les fixes CSP dans sidepanel.js
+function checkCSPFixes(jsPath) {
+ try {
+ const content = fs.readFileSync(jsPath, 'utf8')
+
+ // Vérifier l'absence de onerror inline
+ if (content.match(/onerror=/)) {
+ issues.push('Event handler onerror trouvé dans sidepanel.js')
+ } else {
+ console.log('✓ Pas de onerror inline dans sidepanel.js')
+ }
+
+ // Vérifier la présence du fix avec data-fallback
+ if (content.includes('data-fallback')) {
+ console.log('✓ Fix CSP data-favicon présent')
+ } else {
+ warnings.push('Fix CSP data-favicon可能缺失')
+ }
+
+ // Vérifier le handler de favicon
+ if (content.includes("querySelector('.page-favicon')")) {
+ console.log('✓ Handler error pour favicon présent')
+ } else {
+ warnings.push('Handler error pour favicon可能缺失')
+ }
+
+ } catch (error) {
+ issues.push(`Impossible de lire ${jsPath}: ${error.message}`)
+ }
+}
+
+// Vérifier le fix pick mode
+function checkPickModeFix(jsPath) {
+ try {
+ const content = fs.readFileSync(jsPath, 'utf8')
+
+ // Vérifier le handler visibilitychange
+ if (content.includes('visibilityState === \'hidden\'')) {
+ console.log('✓ Handler visibilitychange pour hidden présent')
+ } else {
+ issues.push('Handler visibilitychange pour hidden manquant')
+ }
+
+ // Vérifier l'appel à setPickModeOnTab(false)
+ if (content.match(/visibilityState === 'hidden'.*setPickModeOnTab\(false\)/s)) {
+ console.log('✓ Appel setPickModeOnTab(false) dans visibilitychange présent')
+ } else {
+ issues.push('Appel setPickModeOnTab(false) dans visibilitychange可能缺失')
+ }
+
+ } catch (error) {
+ issues.push(`Impossible de lire ${jsPath}: ${error.message}`)
+ }
+}
+
+// Vérifier le manifest
+function checkManifest(manifestPath) {
+ try {
+ const content = fs.readFileSync(manifestPath, 'utf8')
+ const manifest = JSON.parse(content)
+
+ console.log('✓ Manifest.json valide')
+ console.log(` Version: ${manifest.version}`)
+ console.log(` Permissions: ${manifest.permissions.join(', ')}`)
+ console.log(` Host permissions: ${manifest.host_permissions.length}`)
+
+ } catch (error) {
+ issues.push(`Manifest.json invalide: ${error.message}`)
+ }
+}
+
+// Exécuter les tests
+console.log('📋 Vérification des fichiers...\n')
+
+checkSyntax(path.join(extDir, 'sidepanel.js'))
+checkSyntax(path.join(extDir, 'content.js'))
+checkSyntax(path.join(extDir, 'background.js'))
+
+console.log('\n🔒 Vérification CSP...\n')
+checkInlineHandlers(path.join(extDir, 'sidepanel.html'))
+checkCSPFixes(path.join(extDir, 'sidepanel.js'))
+
+console.log('\n🎯 Vérification fix pick mode...\n')
+checkPickModeFix(path.join(extDir, 'sidepanel.js'))
+
+console.log('\n📦 Vérification manifest...\n')
+checkManifest(path.join(extDir, 'manifest.json'))
+
+// Résumé
+console.log('\n' + '='.repeat(50))
+if (issues.length === 0 && warnings.length === 0) {
+ console.log('✅ Aucun problème détecté !')
+} else {
+ if (issues.length > 0) {
+ console.log('\n❌ Problèmes détectés:')
+ issues.forEach(issue => console.log(` • ${issue}`))
+ }
+ if (warnings.length > 0) {
+ console.log('\n⚠️ Warnings:')
+ warnings.forEach(warning => console.log(` • ${warning}`))
+ }
+}
+
+console.log('\n📝 Instructions:')
+console.log('1. Rechargez l\'extension dans chrome://extensions (bouton 🔄)')
+console.log('2. Ouvrez une page web normale')
+console.log('3. Cliquez sur l\'icône Momento')
+console.log('4. Fermez le sidepanel - la bannière doit disparaître')
+console.log('5. Ouvrez la console (F12) - pas d\'erreur CSP')
diff --git a/memento-note/extension/dist-chrome-store/manifest.json b/memento-note/extension/dist-chrome-store/manifest.json
index 54d9395..9b1dfc5 100644
--- a/memento-note/extension/dist-chrome-store/manifest.json
+++ b/memento-note/extension/dist-chrome-store/manifest.json
@@ -13,8 +13,7 @@
],
"host_permissions": [
"https://memento-note.com/*",
- "http://*/*",
- "https://*/*"
+ "https://www.memento-note.com/*"
],
"background": {
"service_worker": "background.js"
@@ -25,8 +24,8 @@
"content_scripts": [
{
"matches": [
- "http://*/*",
- "https://*/*"
+ "https://memento-note.com/*",
+ "https://www.memento-note.com/*"
],
"js": [
"content.js"
diff --git a/memento-note/extension/dist-chrome-store/memento-web-clipper-chrome-store.zip b/memento-note/extension/dist-chrome-store/memento-web-clipper-chrome-store.zip
index 4973318..20eb25d 100644
Binary files a/memento-note/extension/dist-chrome-store/memento-web-clipper-chrome-store.zip and b/memento-note/extension/dist-chrome-store/memento-web-clipper-chrome-store.zip differ
diff --git a/memento-note/extension/dist-chrome-store/sidepanel.js b/memento-note/extension/dist-chrome-store/sidepanel.js
index c334286..eaa63b3 100644
--- a/memento-note/extension/dist-chrome-store/sidepanel.js
+++ b/memento-note/extension/dist-chrome-store/sidepanel.js
@@ -246,6 +246,14 @@ function bindIdleHandlers() {
document.getElementById('clipSelBtn')?.addEventListener('click', () => void runAnalyze('selection'))
document.getElementById('clipPageBtn')?.addEventListener('click', () => void runAnalyze('page'))
document.getElementById('clipLinkBtn')?.addEventListener('click', () => void runAnalyze('link'))
+
+ // Gérer l'erreur de chargement du favicon
+ document.querySelector('.page-favicon')?.addEventListener('error', function() {
+ const fallback = this.getAttribute('data-fallback')
+ if (fallback && this.src !== fallback) {
+ this.src = fallback
+ }
+ })
}
async function clearSelection() {
@@ -410,7 +418,7 @@ function renderIdle() {
${escapeHtml(t('activePage'))}
-
})
+
${escapeHtml(pageTitle || '—')}
${escapeHtml(pageUrl || '—')}
diff --git a/memento-note/extension/dist-chrome-store/test-sidepanel.html b/memento-note/extension/dist-chrome-store/test-sidepanel.html
new file mode 100644
index 0000000..e8aa574
--- /dev/null
+++ b/memento-note/extension/dist-chrome-store/test-sidepanel.html
@@ -0,0 +1,78 @@
+
+
+
+
+
Test Sidepanel
+
+
+
+
Test Extension Momento
+
+
+
+
+
diff --git a/memento-note/extension/memento-web-clipper-chrome-store.zip b/memento-note/extension/memento-web-clipper-chrome-store.zip
index 20eb25d..a23c08c 100644
Binary files a/memento-note/extension/memento-web-clipper-chrome-store.zip and b/memento-note/extension/memento-web-clipper-chrome-store.zip differ
diff --git a/memento-note/extension/scripts/build-chrome-store.mjs b/memento-note/extension/scripts/build-chrome-store.mjs
index 2754dca..8ba8c96 100755
--- a/memento-note/extension/scripts/build-chrome-store.mjs
+++ b/memento-note/extension/scripts/build-chrome-store.mjs
@@ -76,11 +76,13 @@ function processSidepanelJs(content) {
function processManifestJson(content) {
const manifest = JSON.parse(content)
- // Remove localhost from host_permissions
- if (manifest.host_permissions) {
- manifest.host_permissions = manifest.host_permissions.filter(
- perm => !perm.includes('localhost:3000') && !perm.includes('127.0.0.1:3000')
- )
+ manifest.host_permissions = [
+ 'https://memento-note.com/*',
+ 'https://www.memento-note.com/*',
+ ]
+
+ if (manifest.content_scripts?.[0]) {
+ manifest.content_scripts[0].matches = ['https://memento-note.com/*', 'https://www.memento-note.com/*']
}
return JSON.stringify(manifest, null, 2)
diff --git a/memento-note/lib/ai/services/semantic-search.service.ts b/memento-note/lib/ai/services/semantic-search.service.ts
index c311d13..074a2b5 100644
--- a/memento-note/lib/ai/services/semantic-search.service.ts
+++ b/memento-note/lib/ai/services/semantic-search.service.ts
@@ -100,20 +100,60 @@ export class SemanticSearchService {
const fusedResults = await this.reciprocalRankFusion(keywordResults, semanticResults)
- return fusedResults
+ const topResults = fusedResults
.sort((a, b) => b.score - a.score)
.slice(0, opts.limit)
- .map(result => ({
- ...result,
- title: result.title || opts.defaultTitle,
- matchType: result.score > 0.8 ? 'exact' as const : 'related' as const
- }))
+
+ // Fetch chunk snippets for top results (display only — no ranking change)
+ const noteIds = topResults.map(r => r.noteId)
+ const snippetMap = await this.fetchChunkSnippets(query, noteIds)
+
+ return topResults.map(result => ({
+ ...result,
+ title: result.title || opts.defaultTitle,
+ matchType: result.score > 0.8 ? 'exact' as const : 'related' as const,
+ matchedSnippets: snippetMap.get(result.noteId),
+ }))
} catch (error) {
console.error('Error in hybrid search:', error)
return this._ftsFallback(query, userId, opts)
}
}
+ /**
+ * Fetch matching chunk snippets for display (no ranking impact).
+ * Finds chunks whose content contains the query terms.
+ */
+ private async fetchChunkSnippets(
+ query: string,
+ noteIds: string[]
+ ): Promise