docs: chat a canali — design system, STATUS, STATE
Il pattern che vale la pena rileggere: la chiave del canale è derivata, non è una colonna, e sta in un modulo condiviso perché le due sponde devono concordare al carattere. Più il perché del letto/non-letto asimmetrico (tabella per il cliente, timestamp per l'admin). Chiude anche il caveat in STATUS.md sulla risposta admin che finiva sempre sul thread generale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -145,9 +145,43 @@ Cosa manca, e perché:
|
||||
usato, ora che dashboard e progetti sono in produzione.
|
||||
- **Whop → audit.** Dipende dal motore: oggi sarebbe un innesco che non innesca nulla.
|
||||
|
||||
⚠️ Il tab Commenti permetteva di rispondere sulla **singola entità**;
|
||||
`replyToConversation` salva sempre sul thread generale. Non è una regressione introdotta
|
||||
ora — era già una scelta di prodotto — ma da oggi è l'unica via.
|
||||
✅ ~~Il tab Commenti permetteva di rispondere sulla singola entità;
|
||||
`replyToConversation` salva sempre sul thread generale.~~ **Chiuso dalla chat a canali**
|
||||
(sotto): la risposta va sull'entità del canale attivo.
|
||||
|
||||
### Chat a canali (2026-08-21, in produzione)
|
||||
|
||||
Il portale aveva **una** conversazione con un selettore di fase in un dropdown. Due
|
||||
problemi veri: il cliente non aveva modo di vedere *dove* c'era del non letto, e una
|
||||
risposta dell'admin poteva atterrare su un'entità diversa da quella della domanda.
|
||||
|
||||
Ora i messaggi si organizzano in **canali** — "Generale" più uno per fase. Il canale non
|
||||
è una colonna: `comments` resta polimorfica (`entity_type` + `entity_id`) e la chiave si
|
||||
*deriva*. La derivazione sta in un posto solo, `src/lib/chat-channels.ts`, funzioni pure
|
||||
importate da entrambe le sponde — **perché se cliente e admin la calcolassero ognuno per
|
||||
conto suo, una risposta finirebbe in un tab diverso da quello della domanda, che è
|
||||
esattamente il bug che questo giro chiude.** Task e deliverable non sono più scrivibili,
|
||||
ma lo storico non resta orfano: rientra nel canale della fase proprietaria, conservando
|
||||
il nome dell'entità come badge sul messaggio.
|
||||
|
||||
**Il letto/non-letto è asimmetrico fra le due sponde, di proposito.** Il cliente ha una
|
||||
tabella nuova, `client_channel_reads`, una riga per `(client_id, channel_key)`: un
|
||||
singolo `client_last_read_at` segnerebbe letti *tutti* i canali all'apertura della chat,
|
||||
e il pallino sul singolo tab — l'unica cosa che dice al cliente dove guardare — perderebbe
|
||||
senso. L'admin invece resta su `clients.admin_last_read_at` (già esistente dalla `0014`):
|
||||
lì la lettura è per conversazione e il pallino per canale si deriva da quel timestamp,
|
||||
quindi nessuna tabella nuova. Il thread admin porta `adminLastReadAt` come **snapshot**
|
||||
congelato al caricamento: senza, l'auto-mark-read all'apertura cancellerebbe i pallini
|
||||
sotto gli occhi di chi sta leggendo.
|
||||
|
||||
Migration `0021`, additiva, applicata a prod **prima** del push e tabella riletta a
|
||||
conferma. Dentro anche un indice su `comments (entity_id, created_at)`: la tabella non ne
|
||||
ha mai avuto uno, **nemmeno su `entity_id`**, dal giorno 0 — e il feed si legge esattamente
|
||||
così. Deploy atterrato, immagine taggata `ac74a81` = HEAD.
|
||||
|
||||
⚠️ **Buildata, non provata a mano** — stesso motivo di sopra (`.env.local` scaduto).
|
||||
**Da fare:** aprire il portale di un cliente, scrivere su una fase, rispondere da
|
||||
`/admin/conversazioni` e verificare che la risposta torni **in quel tab**.
|
||||
|
||||
### v2.5 — Audit (Phases 27–30), in pausa
|
||||
|
||||
|
||||
Reference in New Issue
Block a user