# 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" -}}
{{/* Loading state */}}
{{/* Error state */}}
packages.danix.xyz
{{/* Loaded state */}}
Package Version Link
No packages match your filter.

``` - [ ] **Step 2: Update `pkg-list.js` to accept i18n object** The component definition in `pkg-list.js` must accept the `i18n` parameter and store it. Open `themes/danix-xyz-hacker/assets/js/pkg-list.js` and confirm the `Alpine.data` call matches: ```javascript Alpine.data('pkgList', (i18n) => ({ state: 'loading', i18n: i18n, // store it so template can use i18n.loading, i18n.error, etc. packages: [], filter: '', // ... rest unchanged })); ``` If the `i18n:` property line is missing, add it after `state: 'loading',`. - [ ] **Step 3: Commit** ```bash cd themes/danix-xyz-hacker git add layouts/shortcodes/pkg-list.html assets/js/pkg-list.js git commit -m "feat: add pkg-list shortcode template" ``` --- ## Task 5: Create `pkg-changelog.html` shortcode template **Files:** - Create: `themes/danix-xyz-hacker/layouts/shortcodes/pkg-changelog.html` - [ ] **Step 1: Create the shortcode** Create `themes/danix-xyz-hacker/layouts/shortcodes/pkg-changelog.html`: ```html {{- $count := .Get "count" | default "10" -}} {{- $loading := i18n "pkg_changelog_loading" | default "Loading changelog..." -}} {{- $error := i18n "pkg_changelog_error" | default "Could not load changelog." -}}
{{/* Loading state */}}
{{/* Error state */}}
ChangeLog.txt
{{/* Timeline */}}
{{/* Vertical line */}}
``` - [ ] **Step 2: Update `pkg-changelog.js` to accept i18n object** Open `themes/danix-xyz-hacker/assets/js/pkg-changelog.js` and confirm the `Alpine.data` call signature and stores i18n: ```javascript Alpine.data('pkgChangelog', (count, i18n) => ({ state: 'loading', i18n: i18n, entries: [], // ... rest unchanged })); ``` If `i18n` parameter or `i18n:` property line is missing, add them. - [ ] **Step 3: Commit** ```bash cd themes/danix-xyz-hacker git add layouts/shortcodes/pkg-changelog.html assets/js/pkg-changelog.js git commit -m "feat: add pkg-changelog shortcode template" ``` --- ## Task 6: Load JS files in baseof.html **Files:** - Modify: `themes/danix-xyz-hacker/layouts/_default/baseof.html` The theme loads all JS unconditionally via `resources.Get "js/..." | minify` in `baseof.html`. The contact form script is at lines 90-92 as a reference: ```html {{ $contactScript := resources.Get "js/contact-form.js" | minify }} ``` - [ ] **Step 1: Add pkg-list.js and pkg-changelog.js after the contact form block** Open `themes/danix-xyz-hacker/layouts/_default/baseof.html`. After line 92 (``), add: ```html {{ $pkgListScript := resources.Get "js/pkg-list.js" | minify }} {{ $pkgChangelogScript := resources.Get "js/pkg-changelog.js" | minify }} ``` - [ ] **Step 2: Verify Hugo builds without error** ```bash cd ~/Programming/GIT/danix.xyz-hacker-theme hugo server --buildDrafts 2>&1 | grep -i "error\|warn" | grep -v "^W " | head -20 ``` Expected: no errors about missing resources or template syntax. - [ ] **Step 3: Commit** ```bash cd themes/danix-xyz-hacker git add layouts/_default/baseof.html git commit -m "feat: load pkg-list and pkg-changelog JS in baseof.html" ``` --- ## Task 7: Add shortcodes to repository content pages **Files:** - Modify: `content/en/repository/index.md` - Modify: `content/it/repository/index.md` - [ ] **Step 1: Add shortcodes to English repository page** Open `content/en/repository/index.md`. Replace the "Available Packages" section at the bottom: ```markdown ## Available Packages {{< pkg-list >}} ## Recent Changes {{< pkg-changelog count="10" >}} ``` Remove the old placeholder text: `Check the repository for the latest available packages. See the GitHub SlackBuild repositories below for build information and source files.` - [ ] **Step 2: Add shortcodes to Italian repository page** Open `content/it/repository/index.md`. Replace the "Pacchetti disponibili" section: ```markdown ## Pacchetti disponibili {{< pkg-list >}} ## Modifiche recenti {{< pkg-changelog count="10" >}} ``` Remove the old placeholder text: `Dai un'occhiata al repository per gli ultimi pacchetti disponibili. Vedi i repository SlackBuild su GitHub qui sotto per i dettagli sulle build e i file sorgente.` - [ ] **Step 3: Verify pages render in dev server** ```bash cd ~/Programming/GIT/danix.xyz-hacker-theme hugo server --buildDrafts ``` Visit `http://localhost:1313/en/repository/` and `http://localhost:1313/it/repository/`. Expect to see loading spinners (fetch will fail without CORS header; that's expected in this step). Open browser devtools → Network tab. Confirm `PACKAGES.TXT` and `ChangeLog.txt` fetch requests are made. Confirm they fail with CORS error (expected until Apache is configured). - [ ] **Step 4: Commit content changes** ```bash cd ~/Programming/GIT/danix.xyz-hacker-theme # NOT in submodule git add content/en/repository/index.md content/it/repository/index.md git commit -m "feat: add pkg-list and pkg-changelog shortcodes to repository pages" ``` --- ## Task 8: Configure CORS on packages.danix.xyz > **Note:** This is a server-side change outside the theme. Do it on the `packages.danix.xyz` Apache server. - [ ] **Step 1: Enable mod_headers if not already enabled** ```bash # On the packages.danix.xyz server apachectl -M | grep headers ``` Expected output includes `headers_module`. If not present: ```bash # Slackware: edit /etc/httpd/httpd.conf, uncomment: # LoadModule headers_module lib64/httpd/modules/mod_headers.so ``` - [ ] **Step 2: Add CORS header to Apache vhost config** In the VirtualHost block for `packages.danix.xyz`, add: ```apache Header set Access-Control-Allow-Origin "https://danix.xyz" ``` - [ ] **Step 3: Reload Apache** ```bash apachectl graceful ``` - [ ] **Step 4: Verify CORS header** ```bash curl -sI https://packages.danix.xyz/PACKAGES.TXT | grep -i access-control ``` Expected: ``` Access-Control-Allow-Origin: https://danix.xyz ``` - [ ] **Step 5: Test in browser** Visit `http://localhost:1313/en/repository/` (dev server still running). Reload. Expect: - Loading spinner appears briefly - Package table renders with 132 packages - Filter input works - Changelog timeline renders with 10 entries - Timeline dots are purple, timestamps in accent color --- ## Task 9: Build CSS and final submodule bump - [ ] **Step 1: Build CSS from content repo root** ```bash cd ~/Programming/GIT/danix.xyz-hacker-theme npm run build ``` Expected: `themes/danix-xyz-hacker/assets/css/main.min.css` updated. - [ ] **Step 2: Commit compiled CSS in submodule** ```bash cd themes/danix-xyz-hacker git add assets/css/main.min.css git commit -m "build: recompile CSS for pkg-list and pkg-changelog shortcodes" git push origin master ``` - [ ] **Step 3: Bump submodule pointer in content repo** ```bash cd ~/Programming/GIT/danix.xyz-hacker-theme git add themes/danix-xyz-hacker git commit -m "chore: bump theme submodule (pkg-list and pkg-changelog shortcodes)" git push origin master ``` - [ ] **Step 4: Final smoke test** ```bash hugo server --buildDrafts ``` Visit both repository pages. Confirm: - Package table loads and is filterable - Changelog timeline shows 10 entries with purple dots - No console errors - Both EN and IT pages work - i18n strings correct in each language --- ## Spec Coverage Check | Spec requirement | Task | |-----------------|------| | `pkg-list` shortcode, fetches PACKAGES.TXT | Tasks 2, 4 | | Parse name + version from filename | Task 2 | | Table columns: name, version, link to folder | Tasks 2, 4 | | Client-side filter | Tasks 2, 4 | | `pkg-changelog` shortcode, fetches ChangeLog.txt | Tasks 3, 5 | | `count` param | Tasks 3, 5 | | Timeline style with accent dots | Task 5 | | Loading / error / loaded states | Tasks 4, 5 | | i18n keys for all UI strings | Task 1 | | CORS header on packages.danix.xyz | Task 8 | | Shortcodes added to EN and IT repository pages | Task 7 | | CSS rebuild | Task 9 | | Submodule bump | Task 9 |