From e83e1959e8a6040c4526a6a3c573710b80732cd1 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Thu, 14 May 2026 20:22:24 +0200 Subject: docs: add implementation plan for packages shortcodes 9-task plan covering both Alpine.js shortcodes, i18n keys, JS loading, CORS setup, and CSS rebuild workflow. Co-Authored-By: Claude Sonnet 4.6 --- .../plans/2026-05-14-packages-shortcodes.md | 683 +++++++++++++++++++++ 1 file changed, 683 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-14-packages-shortcodes.md (limited to 'docs/superpowers/plans') diff --git a/docs/superpowers/plans/2026-05-14-packages-shortcodes.md b/docs/superpowers/plans/2026-05-14-packages-shortcodes.md new file mode 100644 index 0000000..16277f4 --- /dev/null +++ b/docs/superpowers/plans/2026-05-14-packages-shortcodes.md @@ -0,0 +1,683 @@ +# 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 */}} + + + {{/* Loaded state */}} +
+
+ +
+
+ + + + + + + + + + + + + + +
PackageVersionLink
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 */}} + + + {{/* 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 | -- cgit v1.2.3