# Packages Shortcodes 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:** Add two Alpine.js shortcodes (`pkg-list` and `pkg-changelog`) to the theme that fetch live data from `packages.danix.xyz` and render a filterable package table and a changelog timeline on the repository page. **Architecture:** Each shortcode is a self-contained Alpine.js component registered via `alpine:init` in its own JS file (same pattern as `contact-form.js`). The shortcode HTML template renders the mount point; the JS file contains all fetch/parse/render logic. Hugo renders i18n strings into `data-*` attributes so JS can read them without knowing about Hugo. **Tech Stack:** Hugo (shortcode templates + i18n), Alpine.js 3.x, vanilla `fetch()`, Tailwind CSS utility classes already present in `main.min.css`. --- ## File Map | File | Action | Responsibility | |------|--------|---------------| | `themes/danix-xyz-hacker/layouts/shortcodes/pkg-list.html` | Create | Mount point + i18n data attrs for package table | | `themes/danix-xyz-hacker/layouts/shortcodes/pkg-changelog.html` | Create | Mount point + i18n data attrs for changelog timeline | | `themes/danix-xyz-hacker/assets/js/pkg-list.js` | Create | Alpine component: fetch PACKAGES.TXT, parse, filter, render | | `themes/danix-xyz-hacker/assets/js/pkg-changelog.js` | Create | Alpine component: fetch ChangeLog.txt, parse N entries, render timeline | | `themes/danix-xyz-hacker/i18n/en.yaml` | Modify | Add 6 new i18n keys | | `themes/danix-xyz-hacker/i18n/it.yaml` | Modify | Add 6 new i18n keys (Italian) | | `themes/danix-xyz-hacker/layouts/partials/head.html` | Modify | Conditionally load pkg-list.js and pkg-changelog.js | | `content/en/repository/index.md` | Modify | Add shortcode calls | | `content/it/repository/index.md` | Modify | Add shortcode calls | --- ## Task 1: Add i18n keys **Files:** - Modify: `themes/danix-xyz-hacker/i18n/en.yaml` - Modify: `themes/danix-xyz-hacker/i18n/it.yaml` - [ ] **Step 1: Add keys to en.yaml** Open `themes/danix-xyz-hacker/i18n/en.yaml` and append at the end: ```yaml # Package repository shortcodes pkg_list_loading: "Loading packages..." pkg_list_error: "Could not load packages. Visit the repository directly." pkg_list_filter: "Filter packages..." pkg_list_link_label: "View" pkg_changelog_loading: "Loading changelog..." pkg_changelog_error: "Could not load changelog. Visit the repository directly." ``` - [ ] **Step 2: Add keys to it.yaml** Open `themes/danix-xyz-hacker/i18n/it.yaml` and append at the end: ```yaml # Shortcode repository pacchetti pkg_list_loading: "Caricamento pacchetti..." pkg_list_error: "Impossibile caricare i pacchetti. Visita il repository direttamente." pkg_list_filter: "Filtra pacchetti..." pkg_list_link_label: "Visualizza" pkg_changelog_loading: "Caricamento changelog..." pkg_changelog_error: "Impossibile caricare il changelog. Visita il repository direttamente." ``` - [ ] **Step 3: Verify Hugo renders them** Run from content repo root: ```bash hugo server --buildDrafts 2>&1 | grep -i "error\|warn" | head -20 ``` Expected: no i18n-related errors. - [ ] **Step 4: Commit** ```bash cd themes/danix-xyz-hacker git add i18n/en.yaml i18n/it.yaml git commit -m "feat: add i18n keys for pkg-list and pkg-changelog shortcodes" ``` --- ## Task 2: Create `pkg-list.js` Alpine component **Files:** - Create: `themes/danix-xyz-hacker/assets/js/pkg-list.js` - [ ] **Step 1: Create the file** Create `themes/danix-xyz-hacker/assets/js/pkg-list.js` with this content: ```javascript document.addEventListener('alpine:init', () => { Alpine.data('pkgList', (i18n) => ({ state: 'loading', // 'loading' | 'loaded' | 'error' packages: [], filter: '', get filtered() { if (!this.filter) return this.packages; const q = this.filter.toLowerCase(); return this.packages.filter(p => p.name.toLowerCase().includes(q)); }, async init() { try { const res = await fetch('https://packages.danix.xyz/PACKAGES.TXT'); if (!res.ok) throw new Error('HTTP ' + res.status); const text = await res.text(); this.packages = parsePkgTxt(text); this.state = 'loaded'; } catch (e) { console.error('pkg-list fetch error:', e); this.state = 'error'; } } })); }); function parsePkgTxt(text) { const packages = []; const nameRe = /^PACKAGE NAME:\s+(.+\.txz)\s*$/m; const locRe = /^PACKAGE LOCATION:\s+(.+)\s*$/m; // Split on blank lines between blocks const blocks = text.split(/\n{2,}/); for (const block of blocks) { const nameMatch = block.match(nameRe); const locMatch = block.match(locRe); if (!nameMatch || !locMatch) continue; const filename = nameMatch[1].trim(); const location = locMatch[1].trim(); // e.g. ./desktop/waybar // Parse name and version from filename: name-version-arch-build_tag.txz // Strategy: version segment starts with a digit after a hyphen const parts = filename.replace(/\.txz$/, '').split('-'); let nameEnd = 1; for (let i = 1; i < parts.length; i++) { if (/^\d/.test(parts[i])) { nameEnd = i; break; } } const name = parts.slice(0, nameEnd).join('-'); const version = parts[nameEnd] || ''; // Derive folder URL: ./desktop/waybar -> https://packages.danix.xyz/desktop/waybar/ const folder = location.replace(/^\.\//, ''); const url = 'https://packages.danix.xyz/' + folder + '/'; const label = folder; // e.g. desktop/waybar packages.push({ name, version, url, label }); } // Sort alphabetically by name packages.sort((a, b) => a.name.localeCompare(b.name)); return packages; } ``` - [ ] **Step 2: Verify parse logic manually** In browser console or Node REPL, test with one block from PACKAGES.TXT: ```javascript // Expected output for input block: // PACKAGE NAME: waybar-0.14.0-x86_64-2_danix.txz // PACKAGE LOCATION: ./desktop/waybar // → { name: 'waybar', version: '0.14.0', url: 'https://packages.danix.xyz/desktop/waybar/', label: 'desktop/waybar' } // Edge case: package starting with capital letter // PACKAGE NAME: Catch2-3.14.0-x86_64-1_danix.txz // → { name: 'Catch2', version: '3.14.0', ... } // Edge case: multi-hyphen name // PACKAGE NAME: slack-wallpapers-1.0-noarch-2_danix.txz // → { name: 'slack-wallpapers', version: '1.0', ... } ``` - [ ] **Step 3: Commit** ```bash cd themes/danix-xyz-hacker git add assets/js/pkg-list.js git commit -m "feat: add pkg-list Alpine component for PACKAGES.TXT" ``` --- ## Task 3: Create `pkg-changelog.js` Alpine component **Files:** - Create: `themes/danix-xyz-hacker/assets/js/pkg-changelog.js` - [ ] **Step 1: Create the file** Create `themes/danix-xyz-hacker/assets/js/pkg-changelog.js` with this content: ```javascript document.addEventListener('alpine:init', () => { Alpine.data('pkgChangelog', (count) => ({ state: 'loading', // 'loading' | 'loaded' | 'error' entries: [], async init() { try { const res = await fetch('https://packages.danix.xyz/ChangeLog.txt'); if (!res.ok) throw new Error('HTTP ' + res.status); const text = await res.text(); this.entries = parseChangelog(text, count); this.state = 'loaded'; } catch (e) { console.error('pkg-changelog fetch error:', e); this.state = 'error'; } } })); }); function parseChangelog(text, maxEntries) { const SEPARATOR = '+--------------------------+'; const chunks = text.split(SEPARATOR); const entries = []; for (const chunk of chunks) { if (entries.length >= maxEntries) break; const lines = chunk.split('\n').map(l => l.trimEnd()).filter(l => l.trim() !== ''); if (lines.length < 2) continue; const timestamp = lines[0].trim(); // Validate it looks like a timestamp (starts with a day name or date pattern) if (!/^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)/.test(timestamp)) continue; const changes = lines.slice(1).join('\n').trim(); entries.push({ timestamp, changes }); } return entries; } ``` - [ ] **Step 2: Verify parse logic manually** Test with sample ChangeLog.txt content: ```javascript // Input (between two SEPARATOR lines): // Thu May 14 17:17:42 UTC 2026 // personal/python3-platformdirs/python3-platformdirs-4.9.6-x86_64-2_danix.txz: // Updated for git 7836e58 // Expected output: // { timestamp: 'Thu May 14 17:17:42 UTC 2026', // changes: 'personal/python3-platformdirs/...: Updated for git 7836e58' } // Edge case: multi-package entry (multiple lines in changes) // Should join all lines after timestamp into changes string ``` - [ ] **Step 3: Commit** ```bash cd themes/danix-xyz-hacker git add assets/js/pkg-changelog.js git commit -m "feat: add pkg-changelog Alpine component for ChangeLog.txt" ``` --- ## Task 4: Create `pkg-list.html` shortcode template **Files:** - Create: `themes/danix-xyz-hacker/layouts/shortcodes/pkg-list.html` - [ ] **Step 1: Create the shortcode** Create `themes/danix-xyz-hacker/layouts/shortcodes/pkg-list.html`: ```html {{- $loading := i18n "pkg_list_loading" | default "Loading packages..." -}} {{- $error := i18n "pkg_list_error" | default "Could not load packages." -}} {{- $filter := i18n "pkg_list_filter" | default "Filter packages..." -}} {{- $view := i18n "pkg_list_link_label" | default "View" -}}