# my-publisher — Design Spec

**Date:** 2026-05-01  
**Status:** Approved

---

## Context

Workflow attuale per pubblicare articoli sul blog danix.xyz è frammentato: git manuale da terminale, hugo server lanciato a mano, traduzione via script CLI, Typora aperto separatamente, frontmatter editato a mano. L'obiettivo è centralizzare tutto in una GUI desktop Linux che automatizzi le operazioni ripetitive e riduca il friction nel publishing workflow.

Blog: Hugo bilingual IT/EN, repo `danix.xyz-hacker-theme`, due branch (`master` = staging, `production` = live via post-receive hook). Script traduzione: `/home/danix/bin/transart.py` (RunPod + TranslateGemma 27b).

---

## Architettura

**Stack:** Python 3 + PyQt6. Processo singolo con `QThread`/`QProcess` per operazioni async — UI non blocca mai.

```
my-publisher/
├── main.py
├── ui/
│   ├── main_window.py        # finestra principale, sidebar + QStackedWidget
│   ├── articles_view.py      # lista articoli tab IT/EN + voce "Senza Traduzione"
│   ├── article_detail.py     # pannello dettaglio (frontmatter + preview + azioni)
│   ├── frontmatter_editor.py # dialog editing TOML frontmatter
│   ├── translation_view.py   # progress log + preview + azioni post-traduzione
│   ├── git_view.py           # stato repo + operazioni git
│   ├── media_view.py         # upload file in static/uppies/YYYY/MM/
│   └── hugo_panel.py         # start/stop hugo server + log
├── workers/
│   ├── git_worker.py         # QThread: pull, push master, push production, git rm, restore
│   ├── hugo_worker.py        # QProcess: hugo server start/stop, streaming log
│   └── translation_worker.py # QProcess: transart.py, streaming stdout
├── core/
│   ├── article_scanner.py    # scansiona content/it/ e content/en/, costruisce ArticleModel
│   ├── frontmatter.py        # parse/write TOML frontmatter (tomlkit)
│   └── config.py             # percorsi configurabili
└── docs/superpowers/specs/
```

**Configurazione:** `~/.config/my-publisher/config.toml`
```toml
blog_repo = "/home/danix/Programming/GIT/danix.xyz-hacker-theme"
transart_script = "/home/danix/bin/transart.py"
typora_bin = "typora"
```
Prima apertura → dialog setup se config mancante.

---

## Layout UI

**Struttura:** sidebar sinistra con gruppi + area contenuto destra (QStackedWidget).

### Sidebar

```
📰 my-publisher         [🔄]

CONTENUTO
  📋 Articoli
  ⚠️ Senza Traduzione   [4]
  ➕ Nuovo articolo
  🏷 Tassonomia
  🖼 Media

WORKFLOW
  🌍 Traduzioni
  🔧 Git ops
  🚀 Hugo server

DEPLOY
  🧪 Test (master)
  🚢 Production
```

Icona 🔄 in cima → refresh manuale `article_scanner`. `QFileSystemWatcher` su `content/it/` e `content/en/` → re-scan silenzioso automatico su modifiche esterne (Typora, terminale).

---

## Componenti

### Lista Articoli (`articles_view`)

Tab IT / EN. In ogni tab: lista articoli con badge stato traduzione.
- Articolo con traduzione: `🇬🇧 ✓` verde
- Articolo senza traduzione: `🇬🇧 ✗` rosso + bordo sinistro rosso

Voce sidebar "Senza Traduzione" → lista cross-lingua di tutti gli articoli privi di traduzione, con badge "manca 🇬🇧" / "manca 🇮🇹" + pulsante "Traduci" diretto.

Click articolo → emette `article_selected(Article)` → carica `article_detail`.

### Pannello Dettaglio (`article_detail`)

Layout: header + split colonne.

**Header:** path articolo + stato traduzione + 5 pulsanti:
| Pulsante | Azione |
|---|---|
| ✏️ Typora | `subprocess.Popen(["typora", path])` |
| 🔧 Frontmatter | apre `frontmatter_editor` dialog |
| 🌍 Traduci | apre `translation_view` |
| 🧪 Push master | `git_worker` → push master |
| 🚢 Pubblica | `git_worker` → push production |

**Colonna sinistra:** frontmatter leggibile (campi + valori) + bottone "Modifica frontmatter".

**Colonna destra:** preview markdown (`QTextBrowser`, renderer Qt built-in). Shortcodes Hugo strippati via regex prima del render.

### Editor Frontmatter (`frontmatter_editor`)

Dialog modale. Form generato dinamicamente dai campi TOML dell'articolo. Salva con `tomlkit` per preservare commenti e formato originale.

Il campo `type` è un `QComboBox` con i 5 tipi predefiniti del sito (non editabile liberamente):

```python
ARTICLE_TYPES = ["Life", "Photo", "Link", "Quote", "Tech"]
```

Tutti gli altri campi sono field testuali liberi. Tags e categorie mostrano i valori esistenti come chip con possibilità di aggiungere/rimuovere — solo termini già presenti nella tassonomia sono accettati (autocompletamento dal file `tags-{lang}.txt`).

### Vista Traduzione (`translation_view`)

Sostituisce `article_detail` durante e dopo la traduzione.

1. **In corso:** `QProcess` lancia `transart.py`, ogni riga stdout → append in `QPlainTextEdit` (log streaming in tempo reale).
2. **Completata:** preview markdown della traduzione + 4 pulsanti:
   - ✏️ Apri in Typora
   - 🔄 Rigenera traduzione
   - 🧪 Push master
   - 🚢 Pubblica

### Git ops (`git_view`)

Stato repo: branch corrente, `git status` (file modificati), `git log --oneline -5`.

Pulsanti: Pull, Push master, Push production. Output in `QPlainTextEdit`.

**Eliminazione articoli:** pulsante "Elimina" in `article_detail` → `git rm` + commit automatico `"remove: <slug> (<lang>)"`. Sezione "Articoli eliminati" in `git_view` → `git log --diff-filter=D --pretty=...` → pulsante "Ripristina" → `git checkout <hash> -- <path>`.

Refresh automatico all'apertura della scheda.

Le voci sidebar "🧪 Test (master)" e "🚢 Production" nel gruppo DEPLOY aprono `git_view` con focus sul branch corrispondente e pulsante azione primario evidenziato. Equivalenti ai pulsanti "Push master" / "Pubblica" nell'`article_detail` — shortcut rapidi senza dover aprire un articolo.

### Tassonomia (`taxonomy_view`)

Scheda dedicata nella sidebar sotto CONTENUTO. Gestisce tag e categorie in modo bilingue.

**Visualizzazione:** due tab — Tags / Categorie. Ogni tab mostra lista di termini con colonne IT | EN. Evidenziati in rosso i termini privi di corrispondenza nell'altra lingua (non presenti in `docs/tags-it.txt` / `docs/tags-en.txt`).

**Operazioni:**
- Aggiungere nuovo termine (IT + EN insieme)
- Modificare termine esistente
- Proposta traduzione automatica: pulsante "Traduci mancanti" → lancia `transart.py` o chiamata diretta all'API Ollama per tradurre i termini orfani in batch
- Salva → aggiorna `docs/tags-it.txt` e `docs/tags-en.txt` mantenendo ordine alfabetico e allineamento 1:1 tra i due file

**Integrità:** `article_scanner` segnala nel dettaglio articolo se un tag usato non è presente nel file tassonomia.

### Media (`media_view`)

Upload file in `static/uppies/YYYY/MM/` (anno/mese corrente automatico).
- Drag & drop nella finestra
- Pulsante "Aggiungi file" → file dialog
- Post-copia: path relativo copiato in clipboard automaticamente (`/uppies/YYYY/MM/file.ext`)
- Lista file presenti nella cartella del mese corrente

### Hugo Server (`hugo_panel`)

`QProcess` per `hugo server -D`. Start/stop dalla sidebar con icona status (🟢 running / 🔴 stopped). Log accessibile nel pannello. URL `http://localhost:1313` cliccabile.

---

## Modello Dati

```python
@dataclass
class Article:
    slug: str
    lang: str          # "it" | "en"
    path: Path         # path assoluto a index.md
    frontmatter: dict
    has_translation: bool
    translation_path: Path | None
```

`ArticleModel` = lista di `Article`. `article_scanner` scansiona `content/{it,en}/articles/*/index.md`, costruisce pairs per slug, determina `has_translation`.

```python
ARTICLE_TYPES = ["Life", "Photo", "Link", "Quote", "Tech"]
```

`TaxonomyModel`: dizionario `{term_it: term_en}` caricato da `docs/tags-it.txt` e `docs/tags-en.txt` (file allineati riga per riga). Stesso schema per categorie.

---

## Gestione Errori

Ogni worker emette `error(str)` → statusbar + dialog non-bloccante. Git push fallito → stderr visibile. Traduzione fallita → log completo + pulsante "Riprova".

---

## Dipendenze Python

```
PyQt6
tomlkit       # parse/write TOML preservando formato
mistune       # markdown → HTML per preview (fallback a QTextBrowser nativo)
```

---

## Verifica (test end-to-end)

1. Avviare app → sidebar popolata con articoli del blog reale
2. Click articolo → frontmatter corretto + preview markdown visibile
3. Modificare frontmatter → salvare → rileggere file su disco con `tomlkit`
4. Aprire Typora → modificare articolo → tornare in app → refresh automatico aggiorna preview
5. Tradurre articolo → log streaming visibile → preview traduzione al termine
6. Push master → `git log` sul repo mostra commit
7. Upload media → file in `static/uppies/YYYY/MM/` → path in clipboard
8. Eliminare articolo → scompare dalla lista → ripristino da Git ops funziona
