# 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: ()"`. Sezione "Articoli eliminati" in `git_view` → `git log --diff-filter=D --pretty=...` → pulsante "Ripristina" → `git checkout -- `. 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