# Design Sistema visivo di Tabularium, registrato dal codice costruito (non dalle intenzioni). Direzione: **alta densità giapponese**, sfidante vincente del registro *bolder*, seed `f6d9f98e`. Modo: **Operate**. Sorgente della verità: `frontend/src/index.css` e `frontend/src/app/`. ## Tesi Un mosaico di moduli rigati, ciascuno con la sua linguetta, impacchettati bordo a bordo. Rifiuta la dashboard admin scura a card fluttuanti che questa categoria spedisce di default: qui la densità è onesta, non gonfiata di spazio bianco, perché l'utente reale fa ~1000 edit discreti su 200 pagine e ha bisogno di vedere molto insieme. **Gerarchia:** ogni zona di lavoro ha un nome accessibile; evitare linguette e titoli duplicati. I contenitori di sola impaginazione non richiedono una testata. Nessun pulsante affidato alla sola icona, nessuna azione che appare solo al passaggio del mouse. ## Superficie Chiaro, per una ragione materiale: il contenuto è inchiostro su carta (scansioni di giornali e di volumi statistici, spesso ingialliti) e si legge su fondo carta. Non c'è tema scuro — è una scelta, non un'omissione. I neutri sono **caldi**: il bianco e il rosso del marchio stanno con grigi tendenti al bruno, non con grigi freddi, e accanto alle scansioni ingiallite non stonano. | Ruolo | Token | Valore | |---|---|---| | Foglio | `--color-sheet` | `#ffffff` | | Riempimento (testate, log) | `--color-fill` | `#f6f4f2` | | Filetto | `--color-rule` | `#d6d0cc` | | Filetto forte (divisioni maggiori, modali) | `--color-rule-strong` | `#111111` | | Inchiostro | `--color-ink` | `#111111` | | Inchiostro secondario | `--color-ink-2` | `#55504c` | | Inchiostro terziario | `--color-ink-3` | `#736d69` | | Tavolo luminoso (fondo di una scansione) | `--color-table` | `#e4e0da` | Niente gradienti. Filetti da 1px: la pagina è un foglio stampato, non una superficie con oggetti sopra. **Angoli misurati, dati ad angolo vivo.** Gli angoli vivi ovunque davano un'aria meccanica; ora i comandi e i contenitori sono arrotondati con una scala sola (`--radius-sm` 4px per i pulsanti piccoli, `--radius-md` 6px per pulsanti e campi, `--radius-lg` 10px per moduli, menu e dialoghi), i distintivi di stato sono pillole e la linguetta del modulo segue l'angolo del modulo. Restano ad angolo vivo i **dati**: la scansione, le celle delle tabelle, il foglio di calcolo, le righe rigate — lì l'angolo è precisione. **Un'ombra sola, per i piani sospesi.** `.float-layer` (menu «Altro», dialoghi, barra dello zoom e avvisi sopra il canvas) ha un'ombra morbida: dice che il piano sta sopra la pagina e si chiude. Nient'altro ha ombra. ## Il segnale ha un ruolo solo `--color-sig` `#a3000e` significa **«qui è vivo»** e nient'altro: linguetta del modulo attivo, linguetta di navigazione corrente, azione primaria, selezione corrente, testo selezionato, cursore di testo, anello di messa a fuoco. **Un'eccezione, voluta: il marchio.** Nel logotipo «Tabularium**Lab**» (testata, login, primo avvio) «Tabularium» è inchiostro `--color-ink` e «Lab» è `--color-sig-text`: è la *rubrica*, il rosso con cui gli scribi scrivevano titoli e intestazioni (lat. *rubrica*, ocra rossa), e il *tabularium* era l'archivio dei documenti di Roma. Lì il rosso non significa «vivo» ma «titolo», e resta lo stesso rosso della famiglia — mai un secondo rosso. Scelto fra quattro varianti (rubrica, bronzo, peperino, tutto bronzo) l'11 ottobre 2026. **Un rosso solo, profondo** (dal 12 ottobre 2026; prima era il rosso vivo `#e60012`): segnale, piastre e testo rosso sono lo stesso `#a3000e`, 8,2:1 sul bianco e per il bianco sopra, quindi regge anche a corpo piccolo. I token restano tre perché i ruoli sono tre: - `--color-sig-plate` — piastre piene con testo bianco. - `--color-sig-text` — rosso su bianco a corpo piccolo. - `--color-sig-hover` `#86000b` — il pulsante primario al passaggio: si scurisce, perché schiarire tornerebbe al rosso vivo. - `--color-sig-wash` `#fdecec` — fondo della riga o della cella viva. Gli **allarmi** non competono col segnale: usano la piastra piena rossa più il segno `IconAlert` (triangolo), quindi si distinguono per forma prima che per colore. Canali separati e distinti: avviso `--color-warn` `#8a5a00` su `--color-warn-wash`, conferma `--color-ok` `#0a6c3d` su `--color-ok-wash`. **Regola di accessibilità:** nessuno stato è comunicato dal solo colore. Ogni stato è una parola in un `.badge`; il colore rinforza, non informa (`app/ui.tsx` → `Badge`, `lib/vocab.ts` → `STATUS_TONE`). ## Le classi sulla scansione I riquadri delle classi (`services/labeling.py`) sono **inchiostri d'epoca**: terre, bruni, verdi — umber `#5c4033` per Title, muschio `#4b5d16` per Text, verderame `#1f6b5e` per Table, ocra scura `#7a5a12` per Section-header e così via. Appartengono alla stessa famiglia calda della carta e ci si posano sopra come annotazioni a penna; i blu e i viola, provati sulle scansioni reali, la contrastavano e stonavano. I gialli, il bianco e il rosa di prima scendevano a 1,1–2,0:1 sulla carta ingiallita: ora ogni classe sta fra 4,3 e 7,3:1 (misurati su `#e9dcb0`), sopra il 3:1 richiesto ai contorni. Nessuna classe è rossa o rosa: sul canvas il rosso è solo della **selezione** (contorno, cornice di ridimensionamento, linguetta) e delle frecce del flusso di lettura, così il riquadro selezionato si vede subito. Il contorno è pieno, il riempimento appena accennato (~8%, ~5% per le bozze) perché la stampa sotto resti leggibile. Gli inchiostri sono vicini fra loro, quindi ogni riquadro porta il **nome della classe** in una linguetta: la classe non si affida al solo colore. ## Tipografia Un solo gotico compatto a corpi piccoli e interlinea stretta, più una voce macchina. Entrambi **self-hosted** in `public/fonts/` — l'app è locale-first e non deve dipendere dalla rete. - **Archivio (UI)**: `Archivo` 400–700 variabile. `--font-ui`. - **Macchina**: `Chivo Mono` 400–600, con `font-feature-settings: 'zero' 1`. `--font-mono`. Solo per ciò che la macchina produce o misura: percorsi, coordinate, JSONL, OTSL, log, identificativi di run, dimensioni in pixel. Mai come costume «tecnico». Cifre tabulari globali (`font-variant-numeric: tabular-nums`): è una superficie di misura, le colonne di numeri si incolonnano. | Ruolo | Corpo | Peso | |---|---|---| | Titolo del corpus (home) | 34px, `tracking -0.035em` | 700 | | Titolo di sezione | 26px, `tracking -0.03em` | 700 | | Titolo dentro un modulo | 19px | 700 | | Corpo | 13px | 400 | | Denso / tabelle | 12px | 400 | | Etichetta (`.lbl`), linguetta, micro | 11px maiuscoletto, `tracking 0.04–0.06em` | 600 | Nessun testo funzionale sotto gli 11px, e 11px solo per etichette; il testo che porta informazione sta a 12–13px. ## Il modulo Il contenitore universale: `.mod` + `.mod-head` + `.mod-tab` (+ `.mod-head-aux`) + `.mod-body`. Un form da 16 campi, una tabella di metriche, la rassegna dell'archivio e un pannello dello studio sono lo stesso oggetto a densità diverse. - `.mod-tab` — piastra rossa: la regione è viva o primaria. - `.mod-tab-quiet` — piastra nera: la regione è strutturale. - `Collapsible` — stesso modulo, corpo a soffietto. È la forma della *disclosure*: i 12 iperparametri avanzati del training vivono qui, non nel form principale. Componenti in `frontend/src/app/ui.tsx`: `Module`, `Collapsible`, `ErrorNotice`, `WarnNotice`, `Notice`, `Badge`, `Progress`, `Modal`, `Field`, `Menu`, `Select`. `Menu` è la forma delle azioni **rare**: un pulsante con nome («Altro ▾», il nome utente) che si apre al clic — mai al passaggio del mouse — e tiene i comandi scritti per esteso. In vista resta ciò che serve a ogni pagina; vista, flusso di lettura, prefill, salvataggio manuale e «riparti da zero» dello Studio, e lingua, impostazioni, utenti e uscita della testata, stanno lì. Lo stesso principio vale per gli stati: un problema è un badge, ciò che va bene è un segno quieto (✓) con il dettaglio nel titolo, e un blocco già verificato si riduce a una riga. `Notice` è l'esito di un'azione detto in una riga dentro una piastra — un solo canale di ritorno per zona, invece di cinque avvisi resi ognuno a modo suo. Per ciò che si rompe resta `ErrorNotice`, che dice anche cosa fare adesso; le eccezioni grezze non arrivano mai allo schermo (`lib/errors.ts`). `Progress` è un filetto che si riempie, mai da solo: accanto c'è sempre la misura scritta (`123 MB di ~1,8 GB`), come per i `Badge`. Senza un totale attendibile la barra è indeterminata — dice «sta procedendo», che è l'unica cosa vera in quel caso — e non si inventa mai un «passo N di M» quando i passi dipendono da cosa manca davvero. Vale la stessa disciplina degli stati: un processo vivo non è un server pronto, e finché l'endpoint tace la UI dice «caricamento», non «in servizio». ## Navigazione Rail multi-riga persistente in alto, non una sidebar da sette link. Riga 1: identità, lingua, stato del backend. Riga 2: le sezioni come linguette bordo a bordo (`.navtab`), l'attiva su fondo nero con la piastra rossa da 3px sotto. Le destinazioni globali sono **Riconosci, Annotazione, Risultati, Progetti, Fine-tuning, Modelli**. Progetti porta all’elenco e alla creazione; la rassegna Archivio si apre dal progetto. Fine-tuning è raggiungibile direttamente e dichiara tre sottosezioni persistenti: **Panoramica → Prepara il dataset → Addestra → Valuta la qualità**. La panoramica (`/fine-tuning`) espone il prossimo passo dallo stato del progetto; non avanza per click e non scambia errori di lettura per assenza di dati. La valutazione può misurare anche un modello di base prima dell’addestramento. Modelli mantiene la configurazione e la sottosezione **Prova modello** (la rotta storica resta `/playground`): una prova temporanea su una pagina, con un collegamento esplicito a Riconosci per salvare i risultati. Ogni sottosezione mantiene attiva la sua destinazione globale, anche per gli screen reader. La colonna sinistra resta libera per il **contesto** (pagine, progetti), mai per i link globali. Modello e luogo di esecuzione sono sempre visibili nello stesso indicatore globale. Cambiare da locale a un provider remoto non cambia il flusso operativo: selezione pagine, avanzamento, risultati, revisione ed export conservano componenti, stati e vocabolario. Nell'hub Modelli **la configurazione è un percorso in tre passi — modello, dove, configura — non una libreria con sette pulsanti per riga.** In testa sta sempre «In uso ora» (modello, destinazione, endpoint, stato), poi tre linguette bordo a bordo: l'attiva su fondo nero con la piastra rossa, le compiute portano scritta la scelta fatta, le future sono spente. Ogni passo mostra solo ciò che gli serve: il catalogo non offre download né porte, perché dipendono da dove si eseguirà il modello, che non è ancora stato deciso. Le destinazioni sono schede che dichiarano prima del click se accolgono *quel* modello (e se no perché), cosa comportano e «Ti servirà:»; la prima praticabile porta «Consigliato». La scheda Locale **dichiara prima cosa può fare questa macchina**: piattaforma, memoria, runtime ospitabili. «Locale» non è un'opzione sempre vera — dipende dall'hardware e dal modello — e quando non lo è la scheda scrive la causa invece di lasciare al click il compito di scoprirla. I **parametri del modello**, sotto la lista di controllo, sono linguette per scopo (Essenziali, Immagine, Testo, Pipeline del modello, Server · destinazione), con la stessa grammatica delle linguette di Impostazioni. Ogni valore porta accanto quello ufficiale; ciò che ne differisce ha il fondo `--color-sig-wash`, la parola «Ripristina» e il conteggio sulla sua linguetta, e una striscia sotto le linguette li elenca tutti: una differenza non resta mai nascosta in una linguetta chiusa (`docs/MODELS_UX.md`). Il passo «Configura» è una **lista di controllo**: voci numerate, ciascuna con il suo stato scritto e un gesto solo, attenuate finché la precedente non è compiuta (`app/models/Checklist.tsx`). In locale: pesi, server, verifica. Su Vast.ai: account, GPU, server, collegamento. Lo stato di ogni voce si ricava da ciò che è vero adesso — account, istanza, log remoto, tunnel — e mai da ciò che si è cliccato, così un refresh non fa regredire nulla e un lavoro lungo (una preparazione di 15 minuti) si ritrova a che punto è. **Dove gira il locale non è una proprietà dell'OS, è una proprietà della coppia macchina + modello.** Ogni riga del catalogo porta il verdetto per *questa* macchina: «in locale · MLX», oppure «solo remoto» con la ragione. Su Apple Silicon girano i modelli con un port MLX e non gli altri, e la riga di un modello senza percorso locale **non offre il download**: scaricare pesi che non si potranno servire è un vicolo cieco, quindi la riga indica la destinazione remota. Il modello in uso porta il badge «In uso ora». Nello studio il rail destro è **una zona sola**: il contenuto della pagina. L'output del modello arriva in diretta in cima, i blocchi si correggono riga per riga sotto, e l'ordine di lettura si governa dalle righe stesse — non da un secondo elenco che ripete gli stessi blocchi. A run conclusa il riquadro dell'output **si chiude da solo** e resta una riga da aprire a richiesta: la generazione verbatim serve a confrontarla con il parsing già strutturato sotto, non a occupare il rail quando il risultato c'è. Le regole di trascrizione sono materiale di consultazione e stanno dietro il loro pulsante, non in un pannello che occupa il rail per sempre. La barra sopra il canvas (`.studio-bar`) ha **un'altezza sola**, 28px per ogni controllo, e **resta su una riga**: a sinistra come si disegna (strumenti, classe — sempre presente, spenta quando non si disegna — e Annulla), a destra cosa viene dopo (Altro, Prossimo compito). Quando la colonna è stretta la barra misura sé stessa e toglie per gradi (`lib/fitRow.ts`, livelli in `index.css`): il nome della pagina, le lettere dei tasti, la parola «Annulla», i nomi degli strumenti non attivi, tutti i nomi, poi stringe classe e spazi. Misurare e non fissare soglie regge le tre lingue. **Eccezione dichiarata** alla regola «nessun pulsante affidato alla sola icona»: a barra stretta gli strumenti restano solo icona, ma lo strumento attivo tiene il nome, e ogni nome resta al passaggio del mouse e ai lettori di schermo. Lo stato del salvataggio sta fermo nell'angolo in alto a destra del foglio: nella barra, cambiando parola, ne cambiava la larghezza. Quella lista è anche l'equivalente DOM del canvas: i blocchi disegnati su Konva non esistono nel DOM, quindi devono vivere qui come righe vere, navigabili da tastiera (frecce, Alt+frecce per riordinare, Canc) e leggibili da uno screen reader. E dentro una pagina, quando gli argomenti sono più d'uno: Impostazioni ha quattro zone (Account / Istanza / Dati e backup / Ambiente) rese con le stesse linguette, la scelta persistita nell'URL (`?s=`). Modello e provider non sono duplicati qui: appartengono all'hub Modelli. La densità regge un form fitto, non quattro argomenti in fila. ## Il percorso Il percorso primario è **seleziona pagine → riconosci → controlla i risultati → correggi o esporta**. Le sessioni bulk appartengono al backend, sono persistite pagina per pagina e continuano anche se il browser cambia schermata. Terminata la generazione, l'inferenza può essere disattivata: risultati e correzioni non dipendono più dalla GPU. Il corpus però non si lavora in una fila di tappe obbligate. L'Archivio dichiara **tre percorsi indipendenti** e il loro stato reale: **Riconosci** (anche da solo: registri, riconosci, correggi, esporti), **Annota** (la preparazione manuale, che vale di per sé in ogni percorso) e **Raffina il modello** (il ramo opzionale: dataset → training → valutazione, e poi di nuovo inferenza col modello affinato). Nessuno è obbligatorio e nessuno è «la fase corrente»: chi vuole soltanto riconoscere ed esportare non attraversa mai dataset, training e valutazione. Solo il percorso primario porta la piastra piena; gli altri sono righe con la loro azione. Le pagine di fase non ripetono la mappa: quando il prerequisito manca lo dichiarano in una riga sola con il collegamento che lo sblocca, altrimenti fanno il loro lavoro. Il vecchio percorso obbligato in sei fasi — Progetto, Scansione, Annotazione, Dataset, Training, Valutazione — non compare più: imponeva il fine-tuning anche a chi voleva soltanto riconoscere ed esportare. Nota di composizione: la fase corrente non usa un bordo sinistro colorato — è il tell del template. Il rilievo viene dalla piastra e dal fondo, cioè dalla grammatica del mondo. ## Icone Disegnate, mai glifi Unicode: `frontend/src/app/icons.tsx`. Una sola famiglia di tratto — griglia 16, `stroke-width 1.5`, `stroke-linecap="square"`, `stroke-linejoin="miter"` — così i segni appartengono allo stesso sistema dei filetti da 1px. Sono sempre `aria-hidden`: il nome accessibile sta sul controllo. ## Movimento Minimo e funzionale, con la stessa curva ovunque. Il primo momento è `.swap` — il cambio di contenuto in posto, 140ms, `cubic-bezier(0.16, 1, 0.3, 1)`, con un `clip-path` che scopre da sinistra (il gesto della piastra che viene stampata). Sotto `prefers-reduced-motion` diventa uno scambio istantaneo e ogni transizione dell'app si azzera. Il secondo è lo streaming: `.stream-in` (220ms) fa entrare ogni pezzo di output che il modello produce senza far saltare quello che stai leggendo, e `.caret` è l'unico segnale che lampeggia — solo mentre lo stream è aperto, perché dice «la riga non è finita». Sotto `prefers-reduced-motion` il testo compare e basta, e il cursore resta fermo e visibile. **Ingressi, sottili.** Ciò che arriva entra invece di comparire di colpo, sempre sotto i 200 ms con la stessa curva: i contenuti locali (`.enter`, dissolvenza con 4px di salita), le righe delle liste una dopo l'altra (`.cascade`, 18 ms di scarto, tutte insieme dalla decima), le immagini con `data-fade` quando sono davvero arrivate (e anche se falliscono, così nessuna resta invisibile), la scansione dello Studio sul canvas. Mentre i dati arrivano si vede la loro **forma** (`SkeletonLines`, `PageSkeleton`, le righe della lista pagine, il riquadro della scansione), non la parola «Caricamento…»; la testata e le linguette restano ferme. Sotto `prefers-reduced-motion` niente entra: tutto compare quando è pronto. **Risposta al gesto, rapida.** Un pulsante premuto cede al 97% (100 ms): dice «preso» prima dell'esito. I dialoghi entrano con il velo che si scurisce e il pannello che cresce dal 97% (`.modal-backdrop`, `.modal-panel`, ≤ 200 ms) ed escono di colpo; un `Collapsible` si apre con `.swap` e si chiude ripiegandosi (`.collapse-out`, 140 ms). La piastra rossa del rail è una sola (`NavMark` in `Layout.tsx`) e cambiando sezione **scorre** verso la linguetta nuova (220 ms) invece di saltare. Le transizioni nominano sempre le proprietà, mai `all`. **Scorrimento.** I moduli restano visibili subito, senza comparsa legata allo scroll. L’area principale cambia pagina senza animazione d’ingresso o scorrimento forzato. La testata mantiene l’ombra quando il contenuto le passa sotto. **Cambi di forma.** Una riga del contenuto che si chiude una volta verificata (o si riapre) scorre dalla vecchia alla nuova altezza, e le righe che cambiano posto — una bozza confermata, un blocco tolto, un riordino con Alt+frecce — scivolano al posto nuovo (FLIP), 260 ms con la curva di sempre (`lib/motion.ts`). Niente sotto `prefers-reduced-motion`. **La vista della tabella si carica in un gesto solo** (dialogo o seconda finestra): finché i dati arrivano c'è uno scheletro con la forma della vista — barra, azioni, pagina, foglio — così nulla salta; Univer parte appena i dati e il contenitore sono disponibili. Il foglio compare quando il canvas è pronto, senza ritardo di 220 ms o maschera riga per riga. Selezione e scorrimento non ricostruiscono il `TableGrid`: soltanto le mutazioni dei dati serializzano il workbook. I comandi strutturali sono quelli di Tabularium, che conservano celle unite e provenienza; le voci native duplicate sono nascoste. **Menu immediati.** Account, Altro e Select mostrano tutte le voci al clic e si chiudono subito. Nessuno srotolamento o ritardo a cascata: la richiesta di navigazione rapida prevale sulla precedente animazione decorativa. **Testate compatte.** Un titolo di pagina da 24 px, le azioni e Guida chiusa di default. Le istruzioni generali restano disponibili senza occupare l’area di lavoro a ogni ingresso. La panoramica Fine-tuning evita una seconda navigazione identica alle tre righe delle fasi. **Tavolozza dei comandi.** Una barra larga e bassa in alto al centro, come Spotlight: il campo con la lente, sotto un elenco breve (circa sei voci, poi scorre) con a destra ciò che ogni voce è (Vai a, Progetto, Pagina, Azione). ⌘K (Ctrl+K) da qualunque schermata, o «Cerca» in testata: sezioni, progetti, pagine del progetto attivo (solo cercando), lingua, uscita (`app/CommandPalette.tsx`). Non sostituisce rail e menu: ogni comando resta raggiungibile anche col mouse. ## Superfici del browser Tematizzate dalla palette, non lasciate ai default: `::selection`, `caret-color`, `accent-color`, `scrollbar-color` e le scrollbar WebKit, l'offset delle sottolineature. Anello di messa a fuoco unico: `outline: 2px solid var(--color-sig)` + `box-shadow: 0 0 0 1px` del foglio, così resta visibile sia sul bianco sia dentro una piastra rossa piena. ## Lingua L'interfaccia parla italiano (con `en`/`fr` via `src/i18n`); il backend parla inglese. Nessun enum grezzo raggiunge lo schermo: passa da `lib/vocab.ts`. Gli errori non sono mai eccezioni grezze: `lib/errors.ts` li traduce in `{titolo, messaggio, suggerimento}` — cosa è successo e **cosa fare adesso** — con il testo tecnico richiuso dietro «dettaglio tecnico». ## Rischio dichiarato Densità senza gerarchia diventa rumore. Ciò che tiene la densità leggibile è la linguetta (ogni regione si nomina) e la disciplina del rosso (un ruolo solo). Un modulo senza linguetta, o un secondo rosso con un altro significato, rompe il sistema più di qualsiasi errore di spaziatura. ## Progetti e orientamento L’elenco Progetti è la superficie di ritorno al lavoro: ricerca e azione Nuovo progetto in testata. La creazione vive in una modale, anche da link diretto `?nuovo=1`; il rail Progetti torna sempre all’elenco. Le descrizioni di pagina vanno a capo: le istruzioni essenziali restano leggibili anche senza un tooltip. L’eliminazione è un’azione rara nel menu del progetto, con la sua conferma già esistente. La revisione basata su schermate e flussi Mobbin è documentata in `docs/UX_MOBBIN_REVIEW.md`, con collegamenti ai riferimenti e schermate del risultato.