- Fichiers statiques servis avant la session (plus de requêtes SQL par
asset), /assets en cache immuable 1 an, index.html en no-cache, et
compression gzip/brotli des réponses (dépendance compression, externe
dans le bundle esbuild du Dockerfile).
- req.user (déjà chargé par deserializeUser) réutilisé dans les handlers
au lieu de relire l'utilisateur et ses magasins à chaque appel ;
GET /api/user ne refait plus de requête.
- Listes : le magasin joint est réduit aux champs lus par l'interface
(plus de logo base64 ni de configuration SMTP/NocoDB dans chaque ligne),
plus aucune empreinte de mot de passe dans les créateurs/auteurs ni dans
/api/users.
- N+1 supprimés (/api/users, annonces, historique SAV, caches de
vérification des factures), requêtes indépendantes en parallèle (stats,
analytics, météo, getDelivery, getUserWithGroups), jointure
multiplicative des statistiques par magasin corrigée.
- Échéancier limité au magasin demandé ; filtre status sur
GET /api/deliveries.
- Index de performance créés en arrière-plan au démarrage
(CREATE INDEX CONCURRENTLY, reliquats invalides purgés sans verrou
exclusif).
- La connexion n'attend plus la sauvegarde quotidienne ; purge du cache
des factures active en production ; logs volumineux retirés des
chemins chauds ; NODE_ENV fixé dans l'image.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
Annexe générée par un audit multi-agents : pour chaque page, rôle,
tâches utilisateur, appels API tracés jusqu'au handler serveur, et
constats UX / lisibilité / performance / bugs avec preuve fichier:ligne,
vérifiés de façon contradictoire.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MsDJjQrAggcJwbbBtKhgyb
The dashboard widget lost the previous-year temperature and the difference:
when the day's previous-year row is not yet cached and the Visual Crossing
history call fails (quota exhausted, outage, or a plan without history
access), the route silently returned previousYear: null and the widget
hid the comparison.
The weather cache accumulates one previous-year row per day, so the route
now falls back to the nearest cached previous-year entry within 7 days of
the weekday-aligned target date, keeping the comparison on screen through
API failures. The exact API error remains logged server-side
(🌤️ [ERROR] / [FETCH-HISTORY]) for diagnosis.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
The stacked form outgrew the viewport even with internal scrolling. The
dialog widens to max-w-4xl and the form becomes a two-column grid on
desktop: identity and NocoDB configuration on the left, store contact
details and SMTP settings on the right — the SMTP section is now visible
without scrolling. Single column is preserved on small screens, and the
90vh cap with internal scroll stays as a safety net.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
The store contact and SMTP sections made the form taller than the viewport
and the dialog had no scroll, cutting off the bottom fields and buttons.
The dialog now caps at 85vh with internal scrolling, slightly widened so
the two-column SMTP fields breathe.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
Continues the cleanup from 164 errors down to 88, all of which now sit in
MemStorage — the in-memory dev mock — documented as known debt. Production
server code and the entire client are at zero errors.
Three genuine defects surfaced by the types and fixed:
- deleteAnnouncement returned void while routes check the result and answer
404 "not found" on falsy: deleting an announcement succeeded in DB but the
API reported failure on the fallback paths; it now returns a real boolean
- AnnouncementMemoryStorage declared getAnnouncement twice; the first
implementation was dead at runtime (second definition wins) and is removed
- one route called storage.getDeliveryById(), a method that does not exist,
crashing with a TypeError whenever hit; it now calls getDelivery()
Everything else is type-level only (annotations, null-to-undefined for
inline styles, honest signatures for markClientCalled's comment parameter
and the invoice verification result's invoiceAmountTTC field), verified
behavior-neutral: client build, production server bundle and the SMTP
end-to-end test all pass unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
- POST /api/emergency-admin-reset accepted a fallback secret hardcoded in
the repository, letting anyone who read the source reset the production
admin account to admin/admin (EMERGENCY_SECRET is not set in the shipped
docker-compose, so the fallback was live). The route now returns 404
unless EMERGENCY_SECRET is explicitly configured, and invalid attempts
are logged.
- POST /api/admin/emergency-migration ran database migrations with no
authentication at all; it now requires an authenticated admin.
A sweep of the remaining API surface found no other unauthenticated
mutation or read routes beyond /api/health. .env.example documents
EMERGENCY_SECRET and ENCRYPTION_KEY.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
- POST /api/groups logged the full request body, which now carries the
store's SMTP password; a redactBody() helper masks smtpPassword, apiToken
and password in the six log sites that print request bodies
- Groups.tsx logged a debug object on every render; removed
- BLReconciliation's auto-fill mutation logged five lines per verified
delivery; collapsed to one DEV-gated line, keeping console.error
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
- manualChunks separates react/wouter, react-query, recharts and lucide
from application code: the main bundle drops from 1.73 MB to 1.08 MB and
vendor chunks stay browser-cached across deployments since they only
change on dependency upgrades
- esbuild "pure" removes console.log/console.debug from production builds
(console.warn/error kept); the Dockerfile now builds the frontend with
NODE_ENV=production so the setting actually applies — this silences the
debug-log flood users saw in the browser console in production
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
The tsconfig "types" array forced @types/date-fns@2.5.3 — a deprecated stub
for date-fns v2.5 — over the real types shipped by date-fns 3.6, producing
phantom errors everywhere dates are formatted (Locale unknown, format()
refusing its options argument, parseISO missing). Removing the stub and the
types entry (plus the unused node-fetch entry) clears them at the root.
Also applies the non-breaking npm audit fixes: 16 production vulnerabilities
down to 2 (drizzle-orm and xlsx, both needing review — see commit history).
TypeScript errors: 261 → 177.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
Repo hygiene pass:
- cookie.txt / cookies.txt held real session cookies and must never be
committed; .gitignore now blocks them along with .env files
- half a megabyte of debug screenshots, one-shot production hotfix scripts
(all superseded by the automatic startup migrations), scratch files and
the unused attached_assets folder (with its dangling @assets vite alias)
- dead code: server/storage-old.ts (unreferenced, 167 of the project's 430
TypeScript errors) and five client pages no route ever imported
(BLReconciliationNative, Avoirs_backup, TasksSimplified, TasksListSimple,
TasksProductionSimple)
TypeScript error count drops from 430 to 261 with no behavior change; the
client build is unaffected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
Encryption (server/crypto.ts):
- AES-256-GCM with a key derived from ENCRYPTION_KEY, falling back to
SESSION_SECRET so existing deployments need no new configuration
- stored format enc:v1:<iv>:<tag>:<data>; encryption is idempotent and
decryption passes legacy plaintext through unchanged, so nothing breaks
mid-migration
- tampered data or a changed key raises an explicit error instead of
returning garbage
- encrypt/decrypt is confined to the storage layer: group writes encrypt
smtpPassword (decrypted only in emailService at connection time, never sent
to the client), NocoDB config writes encrypt apiToken and reads decrypt it
so the invoice verification and the admin page behave as before
- startup migration sweep encrypts secrets already stored in plaintext,
idempotently; the active-config log line no longer prints the token
Mail history (supplier_mail_logs):
- every send attempt is recorded: delivery, store, supplier, recipient,
subject, status sent/failed with error, message id, user id and name;
a logging failure never fails the send itself
- GET /api/supplier-mail-logs restricted to the user's stores (admin may
filter by store)
- on the reconciliation page the mail icon turns green once a request has
been sent, with the date and sender in the tooltip; clicking again resends
- table created in init.sql, versioned migrations and the production
startup migration, with delivery/group indexes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
Replaces the mailto: link with a server-side send, so the message carries the
store's signature and logo instead of depending on each workstation's Outlook.
Store record (groups):
- address, phone and logo (data URI, 200 KB cap) feed the mail signature
- per-store SMTP settings: host, port, SSL/STARTTLS, credentials, sender
address and display name, with an enable switch
- "test connection" button verifies the server without sending anything
- the empty-form literal, previously repeated five times, becomes one constant
Sending:
- nodemailer transport built per store from its own settings
- multipart mail: plain-text alternative plus HTML whose signature embeds the
logo as an inline CID attachment, which Outlook renders without the remote
image blocking that a data: URI would hit
- delivery details are HTML-escaped
- Reply-To set to the store address; the row shows a spinner and refuses a
second click while a send is in flight
Credentials:
- the SMTP password is never returned to the client; a response-layer
sanitizer strips it from every /api payload and replaces it with a
smtpPasswordSet flag, covering the ten-plus queries that join full group
rows into deliveries, orders and user relations
- an empty password field on save keeps the stored one rather than clearing it
Verified end-to-end against a local SMTP server: transport, auth, From/Reply-To,
multipart structure and the inline logo attachment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
The generated body stopped with the brand name and the store, which would be
repeated by the Outlook signature configured on each workstation. It now ends
at "Cordialement," and lets that signature carry the brand, the store details
and the logo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
The reconciliation page saturated the browser with concurrent requests
(net::ERR_INSUFFICIENT_RESOURCES), re-verifying the same deliveries dozens
of times. Three compounding causes:
- the auto-verification effect depended on verificationResults and
verifyingDeliveries, so every incoming result re-ran it and re-scheduled
the same deliveries; the effect now depends only on the data, and
de-duplication uses a ref applied synchronously instead of state
- verifications fired all at once; they now go through a queue capped at 3
concurrent requests, which also drops the random 0-1s scatter and the
200ms stagger of "verify all"
- each auto-filled delivery invalidated the deliveries cache, triggering a
refetch that re-ran the effect; invalidation now happens once, when the
queue drains
Also fixes cached results computed after the setState that was supposed to
apply them, so deliveries with a known invoice amount never got their green
check and were re-examined on every pass, and silences toasts for automatic
verifications (one toast per row on a network outage).
Mail signature is now the LaFoir'Fouille brand plus the store recorded on
the delivery, instead of the current user's name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe
Each reconciliation row now shows a mail icon that opens the default mail
client (Outlook) with the supplier address, subject and body pre-filled,
asking for the invoice as PDF or the delivery note as Excel.
- new client/src/lib/supplierMail.ts helper building the subject, body and
mailto URL from the delivery (supplier, store, delivery date, BL number,
BL amount, invoice reference) with the current user as signature
- body line breaks encoded as CRLF per RFC 6068 so Outlook keeps the layout
- supplier email read from the delivery join, with fallback to the suppliers
list; when no email is set the button explains where to add it
- button added to both the manual and validated tabs
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FHdE9nEh8zHhQNGpCgrtYe