diff options
| -rw-r--r-- | docs/superpowers/plans/2026-05-14-packages-shortcodes.md | 683 |
1 files changed, 683 insertions, 0 deletions
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" -}} + +<div + x-data="pkgList({ loading: {{ $loading | jsonify }}, error: {{ $error | jsonify }}, filter: {{ $filter | jsonify }}, view: {{ $view | jsonify }} })" + x-init="init()" + class="not-prose my-6" +> + {{/* Loading state */}} + <div x-show="state === 'loading'" class="flex items-center gap-2 text-text-dim py-4"> + <svg class="animate-spin w-4 h-4 text-accent" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"> + <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> + <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path> + </svg> + <span x-text="i18n.loading"></span> + </div> + + {{/* Error state */}} + <div x-show="state === 'error'" class="px-4 py-3 rounded-lg bg-surface border border-border text-sm text-text"> + <span x-text="i18n.error"></span> + <a href="https://packages.danix.xyz" target="_blank" rel="noopener" class="ml-2 text-accent underline">packages.danix.xyz</a> + </div> + + {{/* Loaded state */}} + <div x-show="state === 'loaded'"> + <div class="mb-3"> + <input + type="text" + x-model="filter" + :placeholder="i18n.filter" + class="w-full sm:w-72 px-3 py-2 text-sm bg-bg border border-border/50 rounded-lg text-text placeholder-text-dim focus:outline-none focus:border-accent focus:ring-1 focus:ring-accent transition-colors" + aria-label="Filter packages" + /> + </div> + <div class="overflow-x-auto rounded-lg border border-border/50"> + <table class="w-full text-sm text-left"> + <thead class="bg-surface text-text-dim uppercase text-xs tracking-wide"> + <tr> + <th class="px-4 py-3">Package</th> + <th class="px-4 py-3">Version</th> + <th class="px-4 py-3 text-right">Link</th> + </tr> + </thead> + <tbody class="divide-y divide-border/30"> + <template x-for="pkg in filtered" :key="pkg.label"> + <tr class="hover:bg-surface/50 transition-colors"> + <td class="px-4 py-2 font-mono font-medium text-text" x-text="pkg.name"></td> + <td class="px-4 py-2 font-mono text-text-dim" x-text="pkg.version"></td> + <td class="px-4 py-2 text-right"> + <a + :href="pkg.url" + target="_blank" + rel="noopener" + class="inline-flex items-center gap-1 text-accent hover:underline text-xs" + :aria-label="'View ' + pkg.name + ' in repository'" + > + <span x-text="pkg.label"></span> + <i data-feather="external-link" class="w-3 h-3" aria-hidden="true"></i> + </a> + </td> + </tr> + </template> + <tr x-show="filtered.length === 0"> + <td colspan="3" class="px-4 py-4 text-center text-text-dim text-sm">No packages match your filter.</td> + </tr> + </tbody> + </table> + </div> + <p class="mt-2 text-xs text-text-dim" x-text="filtered.length + ' / ' + packages.length + ' packages'"></p> + </div> +</div> +``` + +- [ ] **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." -}} + +<div + x-data="pkgChangelog({{ $count }}, { loading: {{ $loading | jsonify }}, error: {{ $error | jsonify }} })" + x-init="init()" + class="not-prose my-6" +> + {{/* Loading state */}} + <div x-show="state === 'loading'" class="flex items-center gap-2 text-text-dim py-4"> + <svg class="animate-spin w-4 h-4 text-accent" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true"> + <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle> + <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z"></path> + </svg> + <span x-text="i18n.loading"></span> + </div> + + {{/* Error state */}} + <div x-show="state === 'error'" class="px-4 py-3 rounded-lg bg-surface border border-border text-sm text-text"> + <span x-text="i18n.error"></span> + <a href="https://packages.danix.xyz/ChangeLog.txt" target="_blank" rel="noopener" class="ml-2 text-accent underline">ChangeLog.txt</a> + </div> + + {{/* Timeline */}} + <div x-show="state === 'loaded'" class="relative"> + {{/* Vertical line */}} + <div class="absolute left-3 top-0 bottom-0 w-px bg-border/40" aria-hidden="true"></div> + + <ul class="space-y-6 pl-10" role="list"> + <template x-for="(entry, idx) in entries" :key="idx"> + <li class="relative"> + {{/* Timeline dot */}} + <span + class="absolute -left-7 top-1 w-3 h-3 rounded-full bg-accent ring-2 ring-bg" + aria-hidden="true" + ></span> + <time + class="block text-xs font-semibold text-accent mb-1" + x-text="entry.timestamp" + ></time> + <pre + class="text-xs text-text-dim font-mono whitespace-pre-wrap break-words bg-surface/50 rounded px-3 py-2 border border-border/30" + x-text="entry.changes" + ></pre> + </li> + </template> + </ul> + </div> +</div> +``` + +- [ ] **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 +<!-- Contact form script --> +{{ $contactScript := resources.Get "js/contact-form.js" | minify }} +<script src="{{ $contactScript.RelPermalink }}"></script> +``` + +- [ ] **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 (`<script src="{{ $contactScript.RelPermalink }}"></script>`), add: + +```html +<!-- Package repository shortcodes --> +{{ $pkgListScript := resources.Get "js/pkg-list.js" | minify }} +<script src="{{ $pkgListScript.RelPermalink }}"></script> +{{ $pkgChangelogScript := resources.Get "js/pkg-changelog.js" | minify }} +<script src="{{ $pkgChangelogScript.RelPermalink }}"></script> +``` + +- [ ] **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 +<IfModule mod_headers.c> + Header set Access-Control-Allow-Origin "https://danix.xyz" +</IfModule> +``` + +- [ ] **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 | |
