Who this is for:
- Danilo is a developer and blogger running a bilingual (IT/EN) Hugo site at danix.xyz. He built a PyQt6 desktop GUI called my-publisher to centralize his blog publishing workflow and is now maintaining and extending it post-launch.
+ Danilo is a developer and blogger running a bilingual (IT/EN) Hugo site at danix.xyz. He maintains a PyQt6 desktop app called my-publisher that centralizes his blog publishing workflow, and is currently extending it post-v1.0 launch.
What we covered:
- We resumed the project in a post-implementation maintenance session. We generated a TODO.md file tracking three known gaps from the initial build (TaxonomyView Categories tab, MissingTranslationView Traduci button, multi-token tag autocomplete) and committed it to git. We added app-wide font size support by adding a font_size field (default 10) to the Config dataclass, saving/loading it via config.toml, and applying it at startup via app.setFont() in main.py. We added a TODO item for a font size spinner in the UI. We created an assets/ directory with the
- blogilo.svg icon (copied from the Breeze KDE icon theme) and a my-publisher.desktop file with Exec and Icon paths pointing to /opt/my-publisher for system-wide installation. The user also manually added two more TODO items: draft article visual hints and transart backend availability validation. We pushed all commits and tagged the release as v1.0.
+ Two features were implemented this session. First, draft article visual hints: added a draft property to the Article dataclass, amber [DRAFT] prefix in article list items, and a DRAFT badge in the article detail header. Second, article list metadata display: added four properties to Article (meta_type, meta_tags, meta_categories, meta_date), then updated the article list to show a two-line row per article with slug+badge on line 1 and type/date/tags/categories on line 2. The two-line rendering required three iterations to fix: setWordWrap failed, setTextElideMode failed, and the final working solution was a custom QStyledItemDelegate that draws both lines directly onto the canvas bypassing Qt elision entirely. The app was tagged v1.1 and pushed to origin.
What was confirmed:
- All 22 tests pass. The font_size field defaults to 10 and is read from config.toml if present. The desktop file uses /opt/my-publisher paths — the repo at /home/danix/Programming/GIT/my-publisher is the development copy, and the installed copy will live at /opt/my-publisher. Tag v1.0 is pushed to origin (danix_git:publisher). TODO.md now tracks five items total: TaxonomyView Categories tab, MissingTranslationView Traduci button, multi-token tag autocomplete, font size spinner in UI, draft article visual hints, and transart backend availability validation.
+ All 30 tests pass. The delegate approach (ArticleItemDelegate in ui/articles_view.py) is the correct solution for multi-line QListWidget items in PyQt6. Line 2 metadata is stored in Qt.ItemDataRole.UserRole+1 and drawn in dimmed #888 color. Draft items show amber #f59e0b.
+ Missing-translation items show red #ff6b6b. The blog's TOML frontmatter uses lowercase keys: type, date, tags, categories. Tag v1.1 is pushed to danix_git:publisher.
Still in progress:
- None of the TODO items have been implemented yet. The font size is functional but only editable via config.toml — no UI spinner exists yet. The app has been physically copied to /opt/my-publisher.
+ Nothing was left open in this session. The remaining TODO items from TODO.md are: TaxonomyView Categories tab, MissingTranslationView Traduci button, font size spinner in SetupDialog, multi-token tag autocomplete in FrontmatterEditor, transart backend availability validation,
+ taxonomy tag rename propagation to articles, and Ctrl+Q keyboard shortcut.
Next steps:
- Pick any TODO item to implement next. Suggested order by impact: (1) draft article visual hints — visible quality-of-life improvement for daily use; (2) font size spinner in SetupDialog or a Settings dialog; (3) transart backend availability check using OLLAMA_HOST; (4) TaxonomyView Categories tab; (5) MissingTranslationView Traduci button; (6) multi-token tag autocomplete.
\ No newline at end of file
+ Pick the next TODO item to implement. Suggested order by impact: (1) Ctrl+Q keyboard shortcut — trivial, one line in main_window.py; (2) font size spinner in SetupDialog (ui/setup_dialog.py) — moderate, saves via Config.save() and re-applies with app.setFont(); (3) transart backend
+ availability check using OLLAMA_HOST; (4) MissingTranslationView Traduci button — wire translate_requested signal per row; (5) TaxonomyView Categories tab; (6) taxonomy tag rename propagation; (7) multi-token tag autocomplete in ui/frontmatter_editor.py.
\ No newline at end of file
--- /dev/null
+# Article List Metadata Display Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Show type, tags, categories, and date for each article row in the articles list (both IT and EN tabs).
+
+**Architecture:** Extend `ArticleItem` to render a two-line item: line 1 = slug + translation badge (existing), line 2 = metadata fields extracted from `article.frontmatter`. Use `\n` in item text and `setSizeHint` for row height. No new files needed.
+
+**Tech Stack:** PyQt6 QListWidgetItem, existing Article.frontmatter dict.
+
+---
+
+### Task 1: Add metadata helpers to Article model
+
+**Files:**
+- Modify: `core/models.py`
+- Test: `tests/test_models.py`
+
+- [ ] **Step 1: Write failing tests**
+
+Add to `tests/test_models.py`:
+
+```python
+def test_article_draft_property():
+ fm = {"draft": True, "type": "Tech", "tags": ["linux"], "categories": ["DIY"], "date": "2024-01-15T10:00:00+00:00"}
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter=fm, has_translation=False, translation_path=None)
+ assert a.draft is True
+
+def test_article_meta_type():
+ fm = {"type": "Tech"}
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter=fm, has_translation=False, translation_path=None)
+ assert a.meta_type == "Tech"
+
+def test_article_meta_type_missing():
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter={}, has_translation=False, translation_path=None)
+ assert a.meta_type == ""
+
+def test_article_meta_tags():
+ fm = {"tags": ["linux", "python"]}
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter=fm, has_translation=False, translation_path=None)
+ assert a.meta_tags == "linux, python"
+
+def test_article_meta_tags_empty():
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter={}, has_translation=False, translation_path=None)
+ assert a.meta_tags == ""
+
+def test_article_meta_categories():
+ fm = {"categories": ["DIY", "Tech"]}
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter=fm, has_translation=False, translation_path=None)
+ assert a.meta_categories == "DIY, Tech"
+
+def test_article_meta_date():
+ fm = {"date": "2024-01-15T10:00:00+00:00"}
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter=fm, has_translation=False, translation_path=None)
+ assert a.meta_date == "2024-01-15"
+
+def test_article_meta_date_missing():
+ a = Article(slug="test", lang="it", path=Path("/tmp/test"), frontmatter={}, has_translation=False, translation_path=None)
+ assert a.meta_date == ""
+```
+
+- [ ] **Step 2: Run tests to confirm they fail**
+
+```bash
+pytest tests/test_models.py -v
+```
+
+Expected: FAIL — `Article` has no `meta_type`, `meta_tags`, `meta_categories`, `meta_date` attributes.
+
+- [ ] **Step 3: Implement properties in Article**
+
+In `core/models.py`, add four properties after the existing `draft` property:
+
+```python
+@property
+def meta_type(self) -> str:
+ return str(self.frontmatter.get("type", ""))
+
+@property
+def meta_tags(self) -> str:
+ tags = self.frontmatter.get("tags", [])
+ if isinstance(tags, list):
+ return ", ".join(str(t) for t in tags)
+ return str(tags) if tags else ""
+
+@property
+def meta_categories(self) -> str:
+ cats = self.frontmatter.get("categories", [])
+ if isinstance(cats, list):
+ return ", ".join(str(c) for c in cats)
+ return str(cats) if cats else ""
+
+@property
+def meta_date(self) -> str:
+ raw = self.frontmatter.get("date", "")
+ if not raw:
+ return ""
+ return str(raw)[:10] # ISO date → "YYYY-MM-DD"
+```
+
+- [ ] **Step 4: Run tests to confirm they pass**
+
+```bash
+pytest tests/test_models.py -v
+```
+
+Expected: all pass.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add core/models.py tests/test_models.py
+git commit -m "feat: add meta_type, meta_tags, meta_categories, meta_date properties to Article"
+```
+
+---
+
+### Task 2: Render metadata in ArticleItem
+
+**Files:**
+- Modify: `ui/articles_view.py`
+
+No automated tests for UI — verify manually.
+
+- [ ] **Step 1: Update ArticleItem to render two-line text**
+
+In `ui/articles_view.py`, replace the `ArticleItem.__init__` method. The first line keeps slug + translation badge (and `[DRAFT]` prefix). The second line shows metadata fields, omitting empty ones.
+
+Replace the existing `ArticleItem` class (lines 10-26) with:
+
+```python
+class ArticleItem(QListWidgetItem):
+ def __init__(self, article: Article):
+ super().__init__()
+ self.article = article
+ if article.has_translation:
+ badge = f"🇬🇧 ✓" if article.lang == "it" else "🇮🇹 ✓"
+ else:
+ badge = f"🇬🇧 ✗" if article.lang == "it" else "🇮🇹 ✗"
+ line1 = f"{article.slug} [{badge}]"
+ if article.draft:
+ line1 = f"[DRAFT] {line1}"
+
+ parts = []
+ if article.meta_type:
+ parts.append(article.meta_type)
+ if article.meta_date:
+ parts.append(article.meta_date)
+ if article.meta_tags:
+ parts.append(f"#{article.meta_tags.replace(', ', ' #')}")
+ if article.meta_categories:
+ parts.append(f"[{article.meta_categories}]")
+ line2 = " ".join(parts) if parts else ""
+
+ text = f"{line1}\n{line2}" if line2 else line1
+ self.setText(text)
+ self.setSizeHint(QSize(0, 42) if line2 else QSize(0, 26))
+
+ if article.draft:
+ self.setForeground(QColor("#f59e0b"))
+ elif not article.has_translation:
+ self.setForeground(QColor("#ff6b6b"))
+```
+
+Also add `QSize` to the imports at the top of the file:
+
+```python
+from PyQt6.QtCore import Qt, pyqtSignal, QSize
+```
+
+- [ ] **Step 2: Run the app and verify visually**
+
+```bash
+python main.py
+```
+
+Check:
+- Each article row shows two lines: slug + badge on line 1, metadata on line 2
+- Articles with no metadata (missing type/date/tags/categories) show single line
+- Draft articles: amber color on both lines
+- Missing-translation articles: red color
+- Tags rendered as `#tag1 #tag2`
+- Categories rendered as `[Cat1, Cat2]`
+- Date rendered as `YYYY-MM-DD`
+- Row height accommodates two lines without clipping
+
+- [ ] **Step 3: Run full test suite**
+
+```bash
+pytest tests/ -v
+```
+
+Expected: all 22+ tests pass (UI change has no tests, but existing tests must not break).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add ui/articles_view.py
+git commit -m "feat: show type, date, tags, categories in article list rows"
+```