summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans/2026-05-14-packages-shortcodes.md
blob: 16277f43ba7070a5917fafa507f309ed19838fae (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
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 |