diff --git a/.planning/STATE.md b/.planning/STATE.md index 7006ec7..36134cf 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,9 +3,9 @@ gsd_state_version: 1.0 milestone: v2.5 milestone_name: Audit status: executing -stopped_at: "v2.5 in PAUSA. Modifiche hub: A, B, C1, rifiniture, chat a canali (0021) e modifica messaggi + firma (0022) in prod; C2 (TidyCal) bloccato sulle credenziali API. Niente della chat e' stato provato a mano: .env.local non autentica piu'." -last_updated: "2026-08-21T20:55:00.000Z" -last_activity: 2026-08-21 -- modifica dei messaggi (0022) e firma di chi risponde in chat +stopped_at: "v2.5 in PAUSA. Modifiche hub: A, B, C1, rifiniture, chat a canali (0021) e modifica messaggi + firma (0022) in prod; C2 (TidyCal) bloccato sulle credenziali API. Stati dei task nel portale: scritti, build verde, da vedere a schermo. Niente della chat e' stato provato a mano: .env.local non autentica piu'." +last_updated: "2026-08-22T12:10:00.000Z" +last_activity: 2026-08-22 -- stati dei task leggibili nel portale cliente (icone, pill, legenda) progress: total_phases: 4 completed_phases: 0 @@ -41,6 +41,7 @@ Phase 27 resta a metà — schema e fonti in prod, resto da scrivere. | Portale cliente — stepper compatto/full-width, card offerta | ✅ in prod 2026-08-21 (`0020`); override provato su Caruso Speaker | | Chat a canali — portale + inbox admin per canale | ✅ in prod 2026-08-21 (`0021`); build verde, **mai provata a mano** | | Chat — modifica messaggi + firma admin da `settings` | ✅ in prod 2026-08-21 (`0022`); menzioni rimandate, manca l'attribuzione | +| Portale — stati task leggibili (forma + pill + legenda) | 🔨 scritto 2026-08-22, build verde, **da vedere a schermo** | | D — Whop → audit | ⏸️ dipende dal motore v2.5 | Progress: [███░░░░░░░] 25% (v2.5) @@ -92,7 +93,7 @@ Log completo in `PROJECT.md`. Vive per il lavoro corrente: ## Session Continuity -Last session: 2026-08-21 -Stopped at: **modifica dei messaggi** (stile Slack: illimitata, senza storico, etichetta «modificato» — scelta deliberata, motivata in `STATUS.md`) e **firma di chi risponde**, che era la stringa `iamcavalli` cablata nel pannello. Migration `0022` applicata a prod prima del push e riletta a conferma; il nome e la foto (URL esterno, l'upload su volume non esiste) stanno in `settings`, nessuna migration. La lezione da non perdere: il difficile non era scrivere la modifica ma **propagarla** — il poll filtra su `created_at`, che una modifica non cambia, quindi servono insieme il filtro allargato a `edited_at` **e** il watermark del client sul massimo dei due; una sola delle due e o la modifica non arriva, o arriva a ogni giro per sempre. Build e lint verdi. **Niente di tutto questo è stato provato a mano.** -Next: (1) provare la chat in prod — la verifica che conta è modificare un messaggio admin con la chat del cliente aperta e vedere il testo cambiare da solo entro ~20s senza duplicarsi; (2) sbloccare TidyCal con token o documentazione; (3) `LEAD_WEBHOOK_SECRET` su Coolify — finché manca la route risponde 403 a tutti; (4) poi v2.5 da `src/lib/audit/schema.ts` + `agents/`. +Last session: 2026-08-22 +Stopped at: **stati dei task leggibili nel portale cliente.** Nella Timeline lo stato era solo un cerchietto colorato — ambra e violetto indistinguibili a 20px, e tre stati su quattro senza alcun testo. Ora le quattro icone si distinguono per **forma** (vuoto → punto → spunta vuota → spunta piena), la pill di testo sta solo sui due stati ambigui, l'header della fase conta i task in attesa anche a card chiusa, e una legenda dice chi revisiona e che **ogni pagina include un giro di revisione**. La correzione che conta: **«In revisione» significa «aspetta l'OK del cliente», non controllo qualità interno** come scrive `5547e55` — è uno stato che *richiede un'azione*, e per questo non poteva restare muto. Barra di avanzamento invariata di proposito: cambia il tipo di lavoro, non l'avanzamento. Build, typecheck e lint verdi, nessuna migration. **Non ancora visto a schermo.** +Next: (1) verificare in prod con `?preview=1` su **Caruso Speaker, fase «3 - Esecuzione»** — l'unica dove «In corso» e «In revisione» convivono (4 + 2 su 11), quindi l'unica dove si vede se ora si distinguono; (2) provare la chat in prod: modificare un messaggio admin e vederlo cambiare da solo entro ~20s senza duplicarsi; (3) sbloccare TidyCal con token o documentazione; (4) `LEAD_WEBHOOK_SECRET` su Coolify, senza cui `/api/webhooks/lead` risponde 403 a tutti; (5) poi v2.5 da `src/lib/audit/schema.ts` + `agents/`. Resume file: None diff --git a/STATUS.md b/STATUS.md index 0cf20e0..c25a32e 100644 --- a/STATUS.md +++ b/STATUS.md @@ -1,6 +1,6 @@ # ClientHub (IAMCAVALLI) — Status -_Ultimo aggiornamento: 2026-08-21_ +_Ultimo aggiornamento: 2026-08-22_ Questo è **l'unico documento narrativo** del progetto: a che punto siamo, cosa manca, cosa abbiamo imparato. `.planning/STATE.md` è il digest che leggono i comandi @@ -248,6 +248,51 @@ già annotato in `client-chat.ts` come «un lavoro a sé». Ordine giusto: **att poi notifiche (Resend c'è già), poi eventualmente menzioni. Molto probabilmente vedere *chi* ha scritto risolve gran parte del problema da solo. +### Stati dei task, resi leggibili nel portale (2026-08-22) + +Nella Timeline — la vista di default — lo stato di un task era **solo un cerchietto +colorato**: ambra col puntino da 1.5px per «In corso», violetto col puntino da 2px per +«In revisione». A 20px sono lo stesso oggetto. Solo «In revisione» aveva un `title`; gli +altri tre stati non avevano né testo, né tooltip, né `aria-label`. Il design system chiede +l'opposto, ed è la sua regola UX 3: **colore + testo, mai colore da solo**. + +**«In revisione» non significa quello che dice il commit che l'ha introdotto.** `5547e55` +lo descriveva come controllo qualità interno («finito, ma da controllare prima di +consegnarlo»). Nel flusso reale è **«consegnato, aspetta l'OK del cliente»**: la palla è al +cliente. Un cerchietto muto per uno stato che *richiede un'azione* era il problema vero, +più della somiglianza fra i due colori. + +Cosa è cambiato: + +- **Le quattro icone si distinguono per forma**, non solo per colore, quindi reggono anche + in bianco e nero e con un daltonismo: vuoto → punto → **spunta vuota** → spunta piena. La + spunta compare quando il lavoro è finito e si riempie quando è confermato. +- **Pill di testo solo su «In corso» e «In revisione».** «Da fare» è un cerchio vuoto e + «Fatto» ha il titolo barrato: etichettarli aggiungeva rumore senza aggiungere informazione. +- **Il conteggio dei task in attesa sta nell'header della fase**, accanto a «4 di 8 task», + quindi si legge anche a card chiusa. Serve davvero: l'admin può forzare una fase su + «Completata» dalla select, e in quel caso la card parte collassata con dentro la richiesta. +- **Legenda + micro-copy** sopra entrambe le viste, che dice chi revisiona e che **ogni + pagina include un giro di revisione** — l'informazione commerciale che il cliente non + aveva da nessuna parte. Rimanda alla chat della fase, che esiste già. +- **Colonne del Kanban cliente colorate**, con la stessa tinta dell'icona in Timeline: + prima erano quattro colonne grigie identiche. +- **Lo screen reader legge lo stato su tutti e quattro** (icona `aria-hidden` + `sr-only`), + non solo sui due che portano la pill. + +**La barra di avanzamento non è stata toccata.** Un task in revisione conta come uno in +corso — cioè zero. Cambia il *tipo* di lavoro, non l'avanzamento: gonfiare la percentuale +avrebbe fatto dire alla barra una cosa che il contatore «x di y task» smentiva una riga +sotto. + +Visual e copy stanno in un solo file, `src/components/client/TaskStatusIndicator.tsx`, e la +legenda si genera da `TASK_STATUSES`: aggiungere un quinto stato non lascia indietro una +lista scritta a mano. È la stessa disciplina di `5547e55`, nata proprio perché due liste +fisse avevano fatto sparire dei task dal portale **senza un errore**. + +⚠️ **Scritto e compilato, non ancora visto a schermo.** Build e typecheck verdi, lint +invariato (12 errori preesistenti, nessuno nei file toccati). Nessuna migration: è solo UI. + ### v2.5 — Audit (Phases 27–30), in pausa Il servizio di analisi sito (tre livelli: **Radiografia / Prima-Dopo / Rotta**) diventa diff --git a/design-reference/DESIGN-SYSTEM.md b/design-reference/DESIGN-SYSTEM.md index c7d9ce5..49efc32 100644 --- a/design-reference/DESIGN-SYSTEM.md +++ b/design-reference/DESIGN-SYSTEM.md @@ -389,6 +389,16 @@ The `/client/[token]` portal was migrated to the same "Quiet Luxury" tokens `bg-muted`/`text-muted-foreground` (upcoming) — each with a `dark:` variant. - **Phase cards** (`PhaseCard`): `rounded-xl border-border-light bg-card shadow-card`, progress bar colored per status (emerald/amber/`bg-border`). +- **Stato dei task** (`TaskStatusIndicator`): le quattro forme si distinguono + **senza colore** — `todo` cerchio vuoto `border-border`, `in_progress` cerchio + + punto ambra, `in_review` cerchio + **spunta vuota** violetta, `done` cerchio pieno + + spunta emerald. La spunta compare a lavoro finito e si riempie a lavoro confermato. + La pill di testo compare **solo** su `in_progress` e `in_review`; `todo` e `done` si + leggono dalla forma e dal titolo barrato. Colori come le altre pill del portale (soft + tint `-50/700` + variante `dark:`), tutti raccolti in `TASK_STATUS_VISUALS` e mai + riscritti nei componenti: le stesse tinte colorano le colonne del Kanban cliente e la + legenda, che si genera da `TASK_STATUSES`. L'icona è `aria-hidden` e lo stato viaggia + in uno `sr-only`, così lo screen reader lo annuncia anche sui due senza pill. - Sidebar cards (`OffersSection`, `PaymentStatus`, `DocumentsSection`, `NotesSection`, `TranscriptsSection`) all follow `rounded-xl border-border-light bg-card shadow-card`. diff --git a/src/components/client/PhaseCard.tsx b/src/components/client/PhaseCard.tsx index da7eb9c..00ad613 100644 --- a/src/components/client/PhaseCard.tsx +++ b/src/components/client/PhaseCard.tsx @@ -3,8 +3,8 @@ import { useState } from "react"; import { ApproveButton } from "@/components/client/ApproveButton"; import { useChatContext } from "@/components/client/ChatProvider"; +import { TaskStatusIcon, TaskStatusPill } from "@/components/client/TaskStatusIndicator"; import type { ClientView } from "@/lib/client-view"; -import { TASK_STATUS_LABELS, type TaskStatus } from "@/lib/task-status"; type Phase = ClientView["phases"][number]; @@ -29,36 +29,6 @@ const phaseBarColor: Record<"upcoming" | "active" | "done", string> = { done: "bg-emerald-600", }; -function TaskStatusIcon({ status }: { status: TaskStatus }) { - if (status === "done") { - return ( - - ✓ - - ); - } - if (status === "in_review") { - return ( - - - - ); - } - if (status === "in_progress") { - return ( - - - - ); - } - return ( - - ); -} - export function PhaseCard({ phase, token, @@ -71,6 +41,10 @@ export function PhaseCard({ const [open, setOpen] = useState(defaultOpen); const { openChat } = useChatContext(); const doneCount = phase.tasks.filter((t) => t.status === "done").length; + // I task in revisione aspettano il cliente, non noi. Il conteggio sta nell'header, + // che resta visibile anche a card chiusa: altrimenti la richiesta si nasconde + // dentro una fase collassata e il cliente non sa che tocca a lui. + const reviewCount = phase.tasks.filter((t) => t.status === "in_review").length; return (
{doneCount} di {phase.tasks.length} task
-{phase.progress_pct}%
++ {doneCount} di {phase.tasks.length} task + {reviewCount > 0 && ( + + {" · "} + {reviewCount} in attesa di riscontro + + )} +
+{phase.progress_pct}%
- {task.title} -
+ {/* flex-wrap: su un titolo lungo la pill va a capo invece di + schiacciare il testo */} ++ {task.title} +
+
{task.description}
diff --git a/src/components/client/TaskStatusIndicator.tsx b/src/components/client/TaskStatusIndicator.tsx
new file mode 100644
index 0000000..5c18137
--- /dev/null
+++ b/src/components/client/TaskStatusIndicator.tsx
@@ -0,0 +1,158 @@
+// ── Come lo stato di un task appare al cliente ───────────────────────────────
+// Le etichette stanno in src/lib/task-status.ts e sono condivise con l'admin.
+// Qui c'è solo il livello client-facing: forma, colore e la frase che spiega lo
+// stato a chi non lavora al progetto.
+//
+// Prima di questo file lo stato era un cerchietto senza testo: ambra col puntino
+// da 1.5px per "in corso", violetto col puntino da 2px per "in revisione". A 20px
+// sono lo stesso oggetto, e il design system chiede il contrario — colore + testo,
+// mai colore da solo. Ora le quattro forme si distinguono anche in bianco e nero:
+// vuoto → punto → spunta vuota → spunta piena. La spunta compare quando il lavoro
+// è finito e si riempie quando è confermato.
+
+import { TASK_STATUSES, TASK_STATUS_LABELS, type TaskStatus } from "@/lib/task-status";
+
+type StatusVisual = {
+ /** Classi del cerchio nella lista task. */
+ ring: string;
+ /** Classi della pill di testo. `null` = stato che non ne ha bisogno: "da fare"
+ * è un cerchio vuoto e "fatto" è barrato, si leggono senza etichetta. Tenerlo
+ * come dato della mappa evita che "quali stati hanno la pill" finisca in un if. */
+ pill: string | null;
+ /** Pallino per legenda e intestazioni del kanban. */
+ dot: string;
+ /** Colore dell'etichetta nelle intestazioni del kanban. */
+ label: string;
+ /** Tinta del contatore in cima alla colonna kanban. */
+ count: string;
+ /** Frase client-facing: legge lo screen reader, e sta nella legenda. */
+ hint: string;
+};
+
+// Colori di stato: l'eccezione sanzionata alla regola dei soli token semantici
+// (DESIGN-SYSTEM.md). Soft tint come le altre pill del portale, ogni valore con
+// la sua variante dark — che l'icona "in corso" non aveva.
+export const TASK_STATUS_VISUALS: Record
+ In revisione — il lavoro è pronto e
+ aspetta il tuo riscontro: scrivicelo dalla chat della fase. Ogni pagina include un giro di
+ revisione.
+ Nessun task Nessun task
+ {TASK_STATUSES.map((status) => (
+
+