docs: Reorganize documentation by replacing Claude-specific instructions and a general plan with new development and Gemini guides, and remove temporary database files.

This commit is contained in:
2025-11-29 16:14:23 +01:00
parent 1ca759add5
commit 98963905ee
6 changed files with 2079 additions and 2420 deletions

2078
CLAUDE.md

File diff suppressed because it is too large Load Diff

2077
DEVELOPMENT.md Normal file

File diff suppressed because it is too large Load Diff

1
GEMINI.md Normal file
View File

@@ -0,0 +1 @@
only use DEVELOPMENT.md

343
PLAN.md
View File

@@ -1,343 +0,0 @@
# Piano: Collaborazione Real-Time nel Report Designer
## Obiettivo
Implementare la collaborazione in tempo reale nel Report Designer, simile a Google Docs o Excel Online. Quando un utente modifica un template, tutti gli altri utenti collegati allo stesso template vedono le modifiche istantaneamente.
---
## Architettura Proposta
### Concetti Chiave
1. **Room-based Collaboration**: Ogni template ha una "room" SignalR dedicata
2. **Operational Transformation Semplificata**: Invece di OT completo (complesso), usiamo un modello "last-write-wins" con sync frequente
3. **Presence Awareness**: Gli utenti vedono chi altro sta modificando il template
4. **Cursor/Selection Sharing**: Mostra quale elemento sta selezionando ogni utente
### Flusso Dati
```
User A modifica elemento
Template locale aggiornato (come ora)
SignalR invia delta alla room
Backend riceve e ritrasmette a tutti nella room (eccetto mittente)
User B/C/... ricevono delta
Applicano delta al loro template locale
Canvas si aggiorna automaticamente (già funziona così)
```
---
## Implementazione Dettagliata
### FASE 1: Backend - ReportCollaborationHub
**File:** `/src/Apollinare.API/Hubs/ReportCollaborationHub.cs`
Nuovo Hub dedicato per la collaborazione sui report:
```csharp
public class ReportCollaborationHub : Hub
{
// Stato in-memory degli utenti per template
private static ConcurrentDictionary<string, HashSet<CollaboratorInfo>> _templateUsers = new();
// Join a template room
public async Task JoinTemplate(int templateId, string userName, string userColor)
// Leave template room
public async Task LeaveTemplate(int templateId)
// Broadcast element change to room
public async Task ElementChanged(int templateId, ElementChangeDto change)
// Broadcast element added
public async Task ElementAdded(int templateId, AprtElement element)
// Broadcast element deleted
public async Task ElementDeleted(int templateId, string elementId)
// Broadcast page changes
public async Task PageChanged(int templateId, PageChangeDto change)
// Broadcast selection change (which element user is editing)
public async Task SelectionChanged(int templateId, string? elementId)
// Broadcast cursor position (optional, for live cursors)
public async Task CursorMoved(int templateId, float x, float y)
// Request full template sync (when joining late or after reconnect)
public async Task RequestSync(int templateId)
// Send full template state (host responds to sync requests)
public async Task SendSync(int templateId, string connectionId, AprtTemplate template)
}
```
**DTOs:**
```csharp
public record CollaboratorInfo(string ConnectionId, string UserName, string Color, string? SelectedElementId);
public record ElementChangeDto(
string ElementId,
string ChangeType, // "position", "style", "content", "visibility", etc.
object NewValue
);
public record PageChangeDto(
string PageId,
string ChangeType, // "added", "deleted", "reordered", "renamed", "settings"
object? Data
);
```
### FASE 2: Frontend - Servizio Collaborazione
**File:** `/frontend/src/services/reportCollaboration.ts`
```typescript
class ReportCollaborationService {
private connection: HubConnection | null = null;
private templateId: number | null = null;
private listeners: Map<string, Set<Function>> = new Map();
// Connessione e gestione room
async joinTemplate(
templateId: number,
userName: string,
userColor: string,
): Promise<void>;
async leaveTemplate(): Promise<void>;
// Invio modifiche (chiamati dal ReportEditorPage)
sendElementChange(elementId: string, changeType: string, newValue: any): void;
sendElementAdded(element: AprtElement): void;
sendElementDeleted(elementId: string): void;
sendPageChange(pageId: string, changeType: string, data?: any): void;
sendSelectionChange(elementId: string | null): void;
// Sottoscrizione eventi (per ricevere modifiche da altri)
onElementChanged(callback: (change: ElementChange) => void): () => void;
onElementAdded(callback: (element: AprtElement) => void): () => void;
onElementDeleted(callback: (elementId: string) => void): () => void;
onPageChanged(callback: (change: PageChange) => void): () => void;
onCollaboratorsChanged(
callback: (collaborators: Collaborator[]) => void,
): () => void;
onSelectionChanged(
callback: (userId: string, elementId: string | null) => void,
): () => void;
onSyncRequested(callback: (requesterId: string) => void): () => void;
// Sync
requestSync(): void;
sendSync(connectionId: string, template: AprtTemplate): void;
}
```
### FASE 3: Frontend - Integrazione in ReportEditorPage
**Modifiche a:** `/frontend/src/pages/ReportEditorPage.tsx`
1. **Nuovo State per collaborazione:**
```typescript
const [collaborators, setCollaborators] = useState<Collaborator[]>([]);
const [remoteSelections, setRemoteSelections] = useState<Map<string, string>>(
new Map(),
);
const [isCollaborating, setIsCollaborating] = useState(false);
```
2. **Join/Leave room al mount/unmount:**
```typescript
useEffect(() => {
if (!isNew && id) {
const userName = getCurrentUserName(); // Da auth context
const userColor = generateUserColor(userName);
reportCollaborationService
.joinTemplate(Number(id), userName, userColor)
.then(() => setIsCollaborating(true));
return () => {
reportCollaborationService.leaveTemplate();
};
}
}, [id, isNew]);
```
3. **Sottoscrizione eventi remoti:**
```typescript
useEffect(() => {
if (!isCollaborating) return;
const unsubscribers = [
reportCollaborationService.onElementChanged((change) => {
// Applica modifica senza creare history entry
historyActions.setWithoutHistory((prev) => {
// ... applica change.newValue a element con change.elementId
});
}),
reportCollaborationService.onElementAdded((element) => {
historyActions.setWithoutHistory((prev) => ({
...prev,
elements: [...prev.elements, element],
}));
}),
// ... altri handler
reportCollaborationService.onCollaboratorsChanged(setCollaborators),
];
return () => unsubscribers.forEach((unsub) => unsub());
}, [isCollaborating]);
```
4. **Invio modifiche locali:**
Modificare `handleUpdateElement` per inviare anche via SignalR:
```typescript
const handleUpdateElement = useCallback(
(elementId: string, updates: Partial<AprtElement>) => {
// Aggiorna stato locale (come ora)
historyActions.set((prev) => ({...}));
// Invia a collaboratori
if (isCollaborating) {
reportCollaborationService.sendElementChange(elementId, "update", updates);
}
},
[historyActions, isCollaborating],
);
```
### FASE 4: UI Collaborazione
**File:** `/frontend/src/components/reportEditor/CollaboratorsBar.tsx`
Barra che mostra gli utenti connessi:
```typescript
interface CollaboratorsBarProps {
collaborators: Collaborator[];
remoteSelections: Map<string, string>; // userId -> elementId
}
// Mostra:
// - Avatar circolari colorati per ogni collaboratore
// - Tooltip con nome utente
// - Indicatore "sta modificando [elemento]"
// - Badge con conteggio totale collaboratori
```
**Modifiche a EditorCanvas:** Evidenziare elementi selezionati da altri utenti con bordo colorato.
### FASE 5: Gestione Conflitti
Per semplicità, usiamo strategia **"last-write-wins"** con alcune ottimizzazioni:
1. **Elementi diversi**: Nessun conflitto, modifiche applicate indipendentemente
2. **Stesso elemento, proprietà diverse**: Merge delle proprietà
3. **Stesso elemento, stessa proprietà**: Ultima modifica vince
4. **Lock visivo**: Quando un utente seleziona un elemento, gli altri vedono un indicatore
**Opzionale (Fase futura):** Lock pessimistico - solo un utente alla volta può modificare un elemento.
---
## File da Creare/Modificare
### Nuovi File
| File | Descrizione |
| ----------------------------------------------------------- | ----------------------------------- |
| `src/Apollinare.API/Hubs/ReportCollaborationHub.cs` | Hub SignalR per collaborazione |
| `src/Apollinare.API/Models/CollaborationDtos.cs` | DTOs per messaggi collaborazione |
| `frontend/src/services/reportCollaboration.ts` | Client SignalR per collaborazione |
| `frontend/src/components/reportEditor/CollaboratorsBar.tsx` | UI collaboratori connessi |
| `frontend/src/types/collaboration.ts` | Types TypeScript per collaborazione |
### File da Modificare
| File | Modifiche |
| -------------------------------------------------------- | ------------------------------------------------- |
| `src/Apollinare.API/Program.cs` | Registrare nuovo hub `/hubs/report-collaboration` |
| `frontend/src/pages/ReportEditorPage.tsx` | Integrare collaborazione, stato collaboratori |
| `frontend/src/components/reportEditor/EditorCanvas.tsx` | Mostrare selezioni remote |
| `frontend/src/components/reportEditor/EditorToolbar.tsx` | Mostrare CollaboratorsBar |
---
## Stima Complessità
| Fase | Complessità | Note |
| ---------------------- | ----------- | ---------------------------------------- |
| 1. Backend Hub | Media | SignalR groups, gestione stato in-memory |
| 2. Frontend Service | Media | Gestione connessione, eventi |
| 3. Integrazione Editor | Alta | Molti handler da modificare |
| 4. UI Collaboratori | Bassa | Componente semplice |
| 5. Gestione Conflitti | Media | Merge logic |
---
## Considerazioni Aggiuntive
### Performance
- Throttling degli aggiornamenti durante drag (ogni 50-100ms invece di ogni frame)
- Debounce per modifiche testo (300ms)
- Batch di modifiche multiple in singolo messaggio
### Scalabilità
- Per deployment multi-server: usare Redis backplane per SignalR
- Considerare Azure SignalR Service per produzione
### Autenticazione
- Aggiungere autenticazione al hub (verificare che utente abbia accesso al template)
- Usare JWT token per identificare utente
### Edge Cases
- Reconnessione dopo disconnessione: richiedere sync completo
- Template eliminato mentre utenti connessi: notificare e chiudere
- Conflitto salvataggio: merge o "force save" con conferma
---
## Ordine di Implementazione Consigliato
1. **Backend Hub base** - Join/Leave room, broadcast semplice
2. **Frontend service base** - Connessione, invio/ricezione messaggi
3. **Integrazione minima** - Solo sync modifiche elementi (no UI collaboratori)
4. **Test funzionale** - Verificare che modifiche si propaghino
5. **UI Collaboratori** - Mostrare chi è connesso
6. **Selezioni remote** - Evidenziare elementi selezionati da altri
7. **Ottimizzazioni** - Throttling, batching, gestione conflitti
---
## Domande per l'Utente
Prima di procedere, confermare:
1. **Autenticazione**: Il sistema ha già autenticazione utenti? Devo usare un sistema mock per ora?
2. **Persistenza stato**: Le modifiche devono essere salvate automaticamente o solo quando l'utente clicca "Salva"?
3. **Lock elementi**: Vuoi che solo un utente alla volta possa modificare un elemento (lock pessimistico)?
4. **Cursori live**: Vuoi vedere il cursore degli altri utenti in tempo reale (come Google Docs)?
5. **Nome utente**: Da dove prendo il nome utente da mostrare? (localStorage, auth context, prompt?)

Binary file not shown.

Binary file not shown.