correction du nixpack pour supprimer les fichiers sources

This commit is contained in:
2026-03-05 08:09:28 +01:00
parent ad606f15fa
commit e12fb751ca
5 changed files with 65 additions and 21 deletions

Binary file not shown.

View File

@@ -5790,3 +5790,15 @@ FAM | META SEQ | SST SEQ | RANGE
0 | 00012935 | 00012934 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh) 0 | 00012935 | 00012934 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
1 | 00012936 | 00012932 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh) 1 | 00012936 | 00012932 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
2 | 00012937 | 00012933 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh) 2 | 00012937 | 00012933 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
Time 2026-03-05T07:09:14.1907086Z
Commit 00013255 4 keys in 8ms 671µs
FAM | META SEQ | SST SEQ | RANGE
0 | 00013253 | 00013252 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
1 | 00013254 | 00013250 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
2 | 00013255 | 00013251 SST | O | 3ffdfb3b7d50fcf1-3ffdfb3b7d50fcf1 (0 MiB, fresh)
Time 2026-03-05T07:09:26.7824463Z
Commit 00013261 4 keys in 7ms 646µs 800ns
FAM | META SEQ | SST SEQ | RANGE
0 | 00013259 | 00013258 SST | [=======================================================================] | 3aefa6fd5cf2deb4-f42f94001fcb5351 (0 MiB, fresh)
1 | 00013260 | 00013256 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)
2 | 00013261 | 00013257 SST | O | b294a4237ccef201-b294a4237ccef201 (0 MiB, fresh)

View File

@@ -1,10 +1,19 @@
[phases.setup]
nixPkgs = ["nodejs_22", "openssl"]
[phases.install]
cmds = ["npm install"]
[phases.build] [phases.build]
cmds = [ cmds = [
"npm run build", "npm run build",
"mkdir -p .next/standalone/public", # On prépare le standalone avec ses assets
"cp -r public/* .next/standalone/public/ || true", "cp -r public .next/standalone/public",
"cp -r .next/static .next/standalone/.next/static" "cp -r .next/static .next/standalone/.next/static",
# NETTOYAGE RADICAL : on supprime tout sauf le standalone
"find . -maxdepth 1 ! -name '.next' ! -name '.' -exec rm -rf {} +"
] ]
[phases.setup] [start]
nixPkgs = ["nodejs_22", "npm-9_x", "openssl"] # On lance le serveur depuis le dossier standalone
cmd = "node .next/standalone/server.js"

View File

@@ -168,18 +168,20 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
useEffect(() => { useEffect(() => {
if (!contentRef.current || initialContent === undefined) return; if (!contentRef.current || initialContent === undefined) return;
// Ignore exact loopbacks from our own saves // 1. Si le contenu entrant est identique à ce qu'on a déjà, on ne touche à rien
if (initialContent === syncRef.current) return; if (initialContent === contentRef.current.innerHTML) return;
// Safety: never overwrite real content with an empty string from a stale/placeholder source // 2. LOGIQUE CRUCIALE : On ne met à jour le DOM que si :
const hasRealContent = latestContentRef.current && latestContentRef.current.trim().length > 0; // - L'éditeur est vide (premier chargement)
if (!initialContent && hasRealContent) return; // - OU le document a changé (si vous gérez des IDs de documents)
// - OU si l'utilisateur n'est PAS en train de focus l'éditeur
const isUserEditing = document.activeElement === contentRef.current;
// We reached here, so initialContent is genuinely NEW data we didn't know about. if (!isUserEditing || (contentRef.current.innerHTML === "" && initialContent !== "")) {
// E.g. clicked another chapter, or data was modified in another tab/device.
contentRef.current.innerHTML = initialContent; contentRef.current.innerHTML = initialContent;
syncRef.current = initialContent; syncRef.current = initialContent;
latestContentRef.current = initialContent; latestContentRef.current = initialContent;
}
}, [initialContent]); }, [initialContent]);
// Flush pending save on unmount // Flush pending save on unmount
@@ -187,9 +189,11 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
return () => { return () => {
if (saveTimeoutRef.current) { if (saveTimeoutRef.current) {
clearTimeout(saveTimeoutRef.current); clearTimeout(saveTimeoutRef.current);
if (latestContentRef.current !== syncRef.current && onSave) {
onSave(latestContentRef.current);
} }
// Always save if there are unsaved changes, regardless of timer
if (latestContentRef.current !== syncRef.current && onSave) {
syncRef.current = latestContentRef.current;
onSave(latestContentRef.current);
} }
}; };
}, [onSave]); }, [onSave]);
@@ -217,8 +221,15 @@ const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorProps>(({
saveTimeoutRef.current = setTimeout(async () => { saveTimeoutRef.current = setTimeout(async () => {
setSaveStatus('saving'); setSaveStatus('saving');
const htmlToSave = latestContentRef.current; const htmlToSave = latestContentRef.current;
// Update syncRef BEFORE calling onSave, because onSave triggers setProjects
// which causes a re-render. The useEffect must see the updated syncRef
// to avoid re-writing innerHTML unnecessarily.
syncRef.current = htmlToSave;
try {
await onSave(htmlToSave); await onSave(htmlToSave);
syncRef.current = htmlToSave; // Record that we've synced this exact string to the server } catch (err) {
console.error('Auto-save failed:', err);
}
setSaveStatus('saved'); setSaveStatus('saved');
}, 2000); // 2 seconds }, 2000); // 2 seconds
} }

View File

@@ -4,7 +4,7 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { BookProject, UserProfile, ViewMode, ChatMessage } from '@/lib/types'; import { BookProject, UserProfile, ViewMode, ChatMessage } from '@/lib/types';
import AIPanel from '@/components/AIPanel'; import AIPanel from '@/components/AIPanel';
import { Book, FileText, Globe, GitGraph, Lightbulb, Settings, Menu, ChevronRight, ChevronLeft, Share2, HelpCircle, LogOut, LayoutDashboard, User, Plus, Trash2 } from 'lucide-react'; import { Book, FileText, Globe, GitGraph, Lightbulb, Settings, Menu, ChevronRight, ChevronLeft, Share2, HelpCircle, LogOut, LayoutDashboard, User, Plus, Trash2, Wand2 } from 'lucide-react';
import { useLanguage } from '@/providers/LanguageProvider'; import { useLanguage } from '@/providers/LanguageProvider';
import { LanguageSwitcher } from '@/components/LanguageSwitcher'; import { LanguageSwitcher } from '@/components/LanguageSwitcher';
@@ -157,6 +157,18 @@ const EditorShell: React.FC<EditorShellProps> = (props) => {
<div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-theme-border flex flex-col bg-theme-panel absolute right-0 lg:relative z-40 shadow-2xl lg:shadow-none`}> <div className={`${isAiPanelOpen ? 'w-80 lg:w-96' : 'w-0'} transition-all duration-300 flex-shrink-0 h-full border-l border-theme-border flex flex-col bg-theme-panel absolute right-0 lg:relative z-40 shadow-2xl lg:shadow-none`}>
{isAiPanelOpen && <AIPanel chatHistory={props.chatHistory} onSendMessage={props.onSendMessage} onInsertText={props.onInsertText} selectedText="" isGenerating={props.isGenerating} usage={user.usage} />} {isAiPanelOpen && <AIPanel chatHistory={props.chatHistory} onSendMessage={props.onSendMessage} onInsertText={props.onInsertText} selectedText="" isGenerating={props.isGenerating} usage={user.usage} />}
</div> </div>
{/* Floating Mobile Button for AI Panel */}
{!isAiPanelOpen && (
<button
onClick={() => setIsAiPanelOpen(true)}
className="lg:hidden fixed bottom-6 right-6 z-40 bg-indigo-600 text-white p-3 rounded-full shadow-lg hover:bg-indigo-700 transition-colors flex items-center justify-center pointer-events-auto"
aria-label="Ouvrir le panneau IA"
title="Ouvrir le panneau IA"
>
<Wand2 size={24} />
</button>
)}
</div> </div>
); );
}; };