From dcd066d953226a7ff133f52d17a10774161cee28 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Thu, 22 Oct 2026 22:45:40 -0400 Subject: [PATCH] phase: 01_fix_history_xss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Phase 01 — final verification pass (all criteria green)** - Verified M-1 fix in `src/terminal.js`: ArrowUp / ArrowDown(`>0`) / ArrowDown-else now route through `updateDisplay()` (`textContent`); no `innerHTML` sink touches `cmdLine`/`commandHistory`. - `grep -n innerHTML src/terminal.js` → 12 hits, all allowed (static prompt literals, `clear`/prank wipes, `outLine.innerHTML = output` gated on the static canned `neofetch` string). **C1 ✓** - XSS regression test at `tests/e2e/02-terminal.spec.js:156` present + passing; re-proved it is a real guard: re-injected the old `innerHTML` recall into `dist/terminal.*.js` → test failed (`#xss-marker` received 1); rebuild → byte-identical to `src/`. **C2 ✓** - `./build.sh` → `dist/` OK; `npm test` → **45 passed (0 failed)**; `bash .agents/validate.sh` → **exit 0** (suite + repo-hygiene gates). **C3 ✓** - Branch coverage of modified code: all 3 recall arms asserted (ArrowUp ×2 specs, ArrowDown `>0` in the existing history spec, ArrowDown-else → empty prompt in the new spec) → >90% gate met; no JS coverage/lint toolchain exists in the locked stack, so lint = `node --check src/*.js tests/e2e/*.js playwright.config.js` + `bash -n build.sh .agents/validate.sh` — clean. - C4 ✓ terminal/vim/achievements/menu specs all green; only the three recall branches changed. - Docs (step 6, full README/AGENTS review): README — run section now states the image already contains the built `dist/` (mount `dist/`, never `src/`); new “Serving & configuration” section for `nginx.conf`, incl. **empirically measured** (built image on 127.0.0.1:8124) that `/`, `/index.html` and hashed assets currently return *only* cache headers — the `add_header` non-merge gap = finding M-4/Phase 03; TLS/HSTS-at-edge rule; Quadlet section fixed (unit is not in this repo; Quadlet generates `homepage.service`, so `… start homepage.service`, and `systemctl enable` is a no-op for transient Quadlet units → `[Install]` + `daemon-reload` + linger); Development gained the Node 20+ floor, `npx playwright install chromium`, and `.agents/validate.sh`. AGENTS.md — added the validate.sh gate, flagged `./scripts/check-headers.sh` as not yet in the repo (Phase 03), and scoped the M-1 rule with the allowed-`innerHTML` list. - Notable: `.agents/PLAN.md` still says “44 tests” (3 places) and its §7 roadmap omits phases 04/05 — left untouched per the no-PLAN-edit rule; flag for the next PLAN review. No live-infra touched (temp podman image built/removed for the header measurement only). - Next pending phase: `02_simulate_fetch_commands`. --- .../01_fix_history_xss/00_phase.md | 0 .../01_fix_history_recall.md | 0 .../02_xss_regression_test.md | 0 .../todo/04_huntington_role/00_phase.md | 50 +++ .../01_add_timeline_entry.md | 65 +++ .../02_experience_regression_test.md | 55 +++ .../05_update_projects_section/00_phase.md | 62 +++ .../01_brain_of_reese_card.md | 76 ++++ .../02_servwaf_card.md | 69 +++ .../03_update_local_ai_stack.md | 59 +++ .../04_projects_regression_test.md | 64 +++ .../01_fix_history_xss__00_phase.a1.err | 0 .../01_fix_history_xss__00_phase.a1.md | 11 + .../01_fix_history_xss__00_phase.a1.md.status | 1 + .../01_fix_history_xss__00_phase.a1.validate | 419 ++++++++++++++++++ ..._history_xss__01_fix_history_recall.a1.err | 0 ...x_history_xss__01_fix_history_recall.a1.md | 19 + ...ry_xss__01_fix_history_recall.a1.md.status | 1 + ...ory_xss__01_fix_history_recall.a1.validate | 410 +++++++++++++++++ ...history_xss__02_xss_regression_test.a1.err | 0 ..._history_xss__02_xss_regression_test.a1.md | 1 + ...y_xss__02_xss_regression_test.a1.md.status | 1 + ...ry_xss__02_xss_regression_test.a1.validate | 419 ++++++++++++++++++ ...history_xss__02_xss_regression_test.a2.err | 0 ..._history_xss__02_xss_regression_test.a2.md | 19 + ...y_xss__02_xss_regression_test.a2.md.status | 1 + ...ry_xss__02_xss_regression_test.a2.validate | 419 ++++++++++++++++++ README.md | 87 +++- src/terminal.js | 20 +- tests/e2e/02-terminal.spec.js | 42 ++ 30 files changed, 2343 insertions(+), 27 deletions(-) rename .agents/phases/{todo => complete}/01_fix_history_xss/00_phase.md (100%) rename .agents/phases/{todo => complete}/01_fix_history_xss/01_fix_history_recall.md (100%) rename .agents/phases/{todo => complete}/01_fix_history_xss/02_xss_regression_test.md (100%) create mode 100644 .agents/phases/todo/04_huntington_role/00_phase.md create mode 100644 .agents/phases/todo/04_huntington_role/01_add_timeline_entry.md create mode 100644 .agents/phases/todo/04_huntington_role/02_experience_regression_test.md create mode 100644 .agents/phases/todo/05_update_projects_section/00_phase.md create mode 100644 .agents/phases/todo/05_update_projects_section/01_brain_of_reese_card.md create mode 100644 .agents/phases/todo/05_update_projects_section/02_servwaf_card.md create mode 100644 .agents/phases/todo/05_update_projects_section/03_update_local_ai_stack.md create mode 100644 .agents/phases/todo/05_update_projects_section/04_projects_regression_test.md create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__00_phase.a1.err create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__00_phase.a1.md create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__00_phase.a1.md.status create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__00_phase.a1.validate create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__01_fix_history_recall.a1.err create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__01_fix_history_recall.a1.md create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__01_fix_history_recall.a1.md.status create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__01_fix_history_recall.a1.validate create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a1.err create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a1.md create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a1.md.status create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a1.validate create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a2.err create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a2.md create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a2.md.status create mode 100644 .agents/reports/01_fix_history_xss/01_fix_history_xss__02_xss_regression_test.a2.validate diff --git a/.agents/phases/todo/01_fix_history_xss/00_phase.md b/.agents/phases/complete/01_fix_history_xss/00_phase.md similarity index 100% rename from .agents/phases/todo/01_fix_history_xss/00_phase.md rename to .agents/phases/complete/01_fix_history_xss/00_phase.md diff --git a/.agents/phases/todo/01_fix_history_xss/01_fix_history_recall.md b/.agents/phases/complete/01_fix_history_xss/01_fix_history_recall.md similarity index 100% rename from .agents/phases/todo/01_fix_history_xss/01_fix_history_recall.md rename to .agents/phases/complete/01_fix_history_xss/01_fix_history_recall.md diff --git a/.agents/phases/todo/01_fix_history_xss/02_xss_regression_test.md b/.agents/phases/complete/01_fix_history_xss/02_xss_regression_test.md similarity index 100% rename from .agents/phases/todo/01_fix_history_xss/02_xss_regression_test.md rename to .agents/phases/complete/01_fix_history_xss/02_xss_regression_test.md diff --git a/.agents/phases/todo/04_huntington_role/00_phase.md b/.agents/phases/todo/04_huntington_role/00_phase.md new file mode 100644 index 0000000..1bf08c9 --- /dev/null +++ b/.agents/phases/todo/04_huntington_role/00_phase.md @@ -0,0 +1,50 @@ +# Phase 04 — Add Huntington National Bank role to work experience + +**Story:** n/a +**Context:** `src/index.html` `#experience` timeline, `.agents/PLAN.md` §2 (static-first — content-only change, no new technology) + +## Objective +Update the work-experience timeline with the user's current role: **Lead MLOps +Engineer at Huntington National Bank (Jun 2026 – Present)**, inserted as the +new first (most recent) timeline entry with three bullet points covering the +agentic vulnerability-remediation toolkit, LLM harness skills for business +automation, and the 6-person offshore team building the developer landing +zone. The previous Nimbis "DevOps Manager" entry is updated from +`Jan 2022 – Present` to `Jan 2022 – Apr 2026`. Lock the change with a +Playwright regression test. + +## Dependencies +— (none) + +## Tasks +1. `01_add_timeline_entry.md` — Insert the new Huntington National Bank timeline entry at the top of the `#experience` timeline and update the Nimbis DevOps Manager end date. +2. `02_experience_regression_test.md` — Add a Playwright test proving the new entry is first, complete, and correctly dated, and that the Nimbis entry no longer claims "Present". + +## Testing & Quality +- Unit/integration: no JS logic changes in this phase — `src/index.html` + content only. The quality gate is the E2E regression test (task 2) plus the + full suite. +- Coverage: **>90%** on new/modified code — vacuously satisfied (no + JS/CSS/TS code is modified); the E2E suite is the binding gate. +- E2E: full Playwright suite (`npm test`) green. +- The phase is "Complete" only when the **entire** project test suite passes + 100% (every test in the project, not just this phase's — pre-existing + failures included) and achieves **>90% code coverage** on new/modified code. + +## Documentation & Consistency +- The final pass reviews `README.md` and `AGENTS.md` in full against the + project as it stands after this phase — fixing every inconsistency found, + **including pre-existing ones not caused by this phase**. +- Tasks update `README.md`/`AGENTS.md` immediately when their own changes are + user-visible (setup, commands, configuration, features, conventions). + +## Completion Criteria +- [ ] `grep -c "Huntington National Bank" dist/index.html` returns 1 and + `grep -n "Jun 2026" dist/index.html` shows the `– Present` date span +- [ ] Nimbis "DevOps Manager" date reads `Jan 2022 – Apr 2026` in + `dist/index.html` (no second `– Present` entry in `#experience`) +- [ ] new regression test present in `tests/e2e/01-page.spec.js` and passing +- [ ] `./build.sh && npm test` fully green (45+ tests) +- [ ] no behavior change in completed work (terminal, vim, achievements, menu) +- [ ] README.md and AGENTS.md are accurate and consistent with the whole + project (verified in the final pass) diff --git a/.agents/phases/todo/04_huntington_role/01_add_timeline_entry.md b/.agents/phases/todo/04_huntington_role/01_add_timeline_entry.md new file mode 100644 index 0000000..2927526 --- /dev/null +++ b/.agents/phases/todo/04_huntington_role/01_add_timeline_entry.md @@ -0,0 +1,65 @@ +# Task 01 — Add Huntington National Bank timeline entry + +**Phase:** `04_huntington_role` · **Story:** n/a + +## Objective +Insert the new top work-experience entry (Lead MLOps Engineer, Huntington +National Bank, Jun 2026 – Present) into the `#experience` timeline and close +out the Nimbis "DevOps Manager" date span. + +## Work +1. `src/index.html` — inside `
`, insert the block + below **immediately before** the existing first + `
` (the Nimbis + "DevOps Manager" entry), keeping the file's existing 16/20/24/28-space + indentation exactly: + + ```html +
+
+
+
+

Lead MLOps Engineer

+ Huntington National Bank +
+ Jun 2026 – Present +
    +
  • Building an enterprise agentic vulnerability remediation + toolkit and workflow to resolve millions of vulnerabilities.
  • +
  • Creating and distributing LLM harness skills that automate + business problems — including ServiceNow incident response — + at machine speed.
  • +
  • Leading a team of 6 offshore engineers to build a developer + landing zone with agentic tools that build pipelines, plan + work, and make documentation searchable.
  • +
+
+
+ ``` + + (Use the en dash `–` U+2013 in the date span, matching every other + `timeline-date` value; use the em dash `—` only in the middle bullet, + matching the copy above. Wrap long `
  • ` lines at the same width as + neighboring entries, 4-space continuation indent.) +2. `src/index.html` — in the Nimbis "DevOps Manager" entry, change its + `Jan 2022 – Present` to + `Jan 2022 – Apr 2026`. +3. `./build.sh` — rebuild `dist/`. + +## Testing & Quality +- Unit/integration: content-only change — no new JS logic; the E2E regression + test is task 2, and the full suite is the gate. +- Coverage: **>90%** on this task's new/modified code — vacuously satisfied + (HTML content only, no JS/CSS modified). + +## Completion Criteria +- [ ] `grep -c "Huntington National Bank" src/index.html` returns 1 +- [ ] `grep -n "Jun 2026 – Present" src/index.html` matches the new entry +- [ ] `grep -n "Jan 2022 – Apr 2026" src/index.html` matches the Nimbis + "DevOps Manager" entry; `grep -c "– Present" src/index.html` returns 1 +- [ ] `./build.sh` succeeds and the same three greps pass on + `dist/index.html` +- [ ] `npm test` fully green (44 tests — the new test lands in task 2) +- [ ] README.md/AGENTS.md updated for anything user-visible this task changes + (the phase's final pass re-checks both project-wide) +- [ ] no behavior change in completed work diff --git a/.agents/phases/todo/04_huntington_role/02_experience_regression_test.md b/.agents/phases/todo/04_huntington_role/02_experience_regression_test.md new file mode 100644 index 0000000..0d7272e --- /dev/null +++ b/.agents/phases/todo/04_huntington_role/02_experience_regression_test.md @@ -0,0 +1,55 @@ +# Task 02 — Experience timeline regression test + +**Phase:** `04_huntington_role` · **Story:** n/a + +## Objective +Lock the work-experience change with a Playwright regression test: the +Huntington National Bank entry is the first, most-recent timeline item with +its three bullets and `Jun 2026 – Present` date, and the Nimbis +"DevOps Manager" entry now ends `Apr 2026` instead of `Present`. + +## Work +1. `tests/e2e/01-page.spec.js` — inside the existing + `test.describe('Page structure', ...)` block (after the + "all major sections exist and are in the DOM" test), add one test, + matching the file's existing style: + + ```js + test('experience timeline leads with Huntington National Bank role', async ({ page }) => { + const items = page.locator('#experience .timeline-item'); + await expect(items).toHaveCount(5); + const first = items.first().locator('.timeline-content'); + await expect(first.locator('h3')).toHaveText('Lead MLOps Engineer'); + await expect(first.locator('.timeline-company')).toHaveText('Huntington National Bank'); + await expect(first.locator('.timeline-date')).toHaveText('Jun 2026 – Present'); + const bullets = first.locator('.timeline-list li'); + await expect(bullets).toHaveCount(3); + await expect(bullets.nth(0)).toContainText('agentic vulnerability remediation'); + await expect(bullets.nth(1)).toContainText('LLM harness skills'); + await expect(bullets.nth(1)).toContainText('ServiceNow'); + await expect(bullets.nth(2)).toContainText('developer landing zone'); + // previous role's end date was closed out + const nimbisManager = items.nth(1).locator('.timeline-content'); + await expect(nimbisManager.locator('h3')).toHaveText('DevOps Manager'); + await expect(nimbisManager.locator('.timeline-date')).toHaveText('Jan 2022 – Apr 2026'); + }); + ``` + + (Use the en dash `–` U+2013 in the expected date strings to match the + markup.) +2. `./build.sh && npm test` — rebuild and run the full suite. + +## Testing & Quality +- Unit/integration: the new test covers (a) entry order (HNB first, Nimbis + DevOps Manager second, 5 entries total), (b) title/company/date of the new + entry, (c) all three bullets' key phrases, and (d) the Nimbis entry's + closed-out end date. +- Coverage: **>90%** on this task's new/modified code — test-only change; + the E2E suite is the binding gate. + +## Completion Criteria +- [ ] the new test is present in `tests/e2e/01-page.spec.js` and passes +- [ ] `./build.sh && npm test` fully green (45 tests) +- [ ] README.md/AGENTS.md updated for anything user-visible this task changes + (the phase's final pass re-checks both project-wide) +- [ ] no behavior change in completed work diff --git a/.agents/phases/todo/05_update_projects_section/00_phase.md b/.agents/phases/todo/05_update_projects_section/00_phase.md new file mode 100644 index 0000000..b64351b --- /dev/null +++ b/.agents/phases/todo/05_update_projects_section/00_phase.md @@ -0,0 +1,62 @@ +# Phase 05 — Update Featured Projects (Brain of Reese, ServWaf, Local AI Stack) + +**Story:** n/a +**Context:** `src/index.html` `#projects` section (6 `project-card`s), `~/Projects/Personal/brain_of_reese/README.md`, `~/Projects/StackExpected/ServWall/README.md`, `~/Projects/Experimental/gufo/README.md` + +## Objective +Refresh the Featured Projects grid: (a) replace the "Self-Hosted Services" +card with a **Brain of Reese** card (RAG chatbot over homelab docs; +pgvector ∪ Postgres full-text hybrid search with RRF fusion; self-hosted +LLM; source `https://gitea.reeseapps.com/Experimental/brain-of-reese` — +verified live) and move it to the **first** slot; (b) replace the +"Kubernetes Cluster" card with a **ServWaf** card (Go network WAF — +declarative YAML rules, rate limiting, attack-pattern inspection, opt-in +local-AI verdict, TLS/HTTP3 edge — **closed source: no source link**, +user decision) in its place; (c) update the **Local AI Stack** card to +replace Ollama with **vLLM, Gufo, and llama.cpp** (copy, tech tags, and +code preview). Lock with a Playwright regression test. + +## Dependencies +— (none) + +## Tasks +1. `01_brain_of_reese_card.md` — Delete the Self-Hosted Services card; insert the Brain of Reese card as the first card in `#projects`. +2. `02_servwaf_card.md` — Replace the Kubernetes Cluster card with the ServWaf card (description only, no source link). +3. `03_update_local_ai_stack.md` — Update the Local AI Stack card: replace Ollama with vLLM/Gufo/llama.cpp in the copy, tech tags, and code preview. +4. `04_projects_regression_test.md` — Add a Playwright test locking card count, order, the updated Local AI Stack tags, and the absence of the two removed card titles. + +## Testing & Quality +- Unit/integration: no JS logic changes in this phase — `src/index.html` + content only. The quality gate is the E2E regression test (task 4) plus + the full suite. +- Coverage: **>90%** on new/modified code — vacuously satisfied (no + JS/CSS/TS code is modified); the E2E suite is the binding gate. +- E2E: full Playwright suite (`npm test`) green. +- The phase is "Complete" only when the **entire** project test suite passes + 100% (every test in the project, not just this phase's — pre-existing + failures included) and achieves **>90% code coverage** on new/modified code. + +## Documentation & Consistency +- The final pass reviews `README.md` and `AGENTS.md` in full against the + project as it stands after this phase — fixing every inconsistency found, + **including pre-existing ones not caused by this phase**. +- Tasks update `README.md`/`AGENTS.md` immediately when their own changes are + user-visible (setup, commands, configuration, features, conventions). + +## Completion Criteria +- [ ] `grep -c "project-card fade-in" src/index.html` returns 6, and the + same holds in `dist/index.html` +- [ ] first `project-card` in `#projects` (in `dist/index.html`) is + "Brain of Reese" and its card links to + `https://gitea.reeseapps.com/Experimental/brain-of-reese` +- [ ] `grep -c "Self-Hosted Services\|Kubernetes Cluster" src/index.html` + returns 0 (project-card titles removed; the words may still appear in + the out-of-scope About/Skills sections — e.g. "Kubernetes" skill tag) +- [ ] the Local AI Stack card (in `dist/index.html`) contains vLLM, Gufo, + and llama.cpp and no "Ollama" +- [ ] new regression test present in `tests/e2e/01-page.spec.js` and passing +- [ ] `./build.sh && npm test` fully green (45+ tests) +- [ ] no change to the Skills section, About section, or any other section + outside `#projects` +- [ ] README.md and AGENTS.md are accurate and consistent with the whole + project (verified in the final pass) diff --git a/.agents/phases/todo/05_update_projects_section/01_brain_of_reese_card.md b/.agents/phases/todo/05_update_projects_section/01_brain_of_reese_card.md new file mode 100644 index 0000000..5e2b64c --- /dev/null +++ b/.agents/phases/todo/05_update_projects_section/01_brain_of_reese_card.md @@ -0,0 +1,76 @@ +# Task 01 — Brain of Reese card (first slot) + +**Phase:** `05_update_projects_section` · **Story:** n/a + +## Objective +Remove the "Self-Hosted Services" project card and insert the new +**Brain of Reese** card as the **first** card in `#projects` (immediately +before the "Reverse Proxy Infrastructure" card). + +## Work +1. `src/index.html` — inside `
    `, delete the entire + `project-card` block whose `

    ` is "Self-Hosted Services" (the card + containing the `- Immich / - Jellyfin / …` code block). +2. `src/index.html` — insert the block below **immediately before** the + existing first `project-card` (the "Reverse Proxy Infrastructure" + card), matching the file's 16/20/24/28-space indentation. For the + `` (Gitea SVG) block, copy the + existing one from the "Reverse Proxy Infrastructure" card verbatim and + change only the `href` to + `https://gitea.reeseapps.com/Experimental/brain-of-reese`: + + ```html +
    +
    +
    +
    # Hybrid search: pgvector cosine ∪ Postgres full-text
    +
    def retrieve(query, k=5):
    +
      vec = pgvector.cosine(query)
    +
      fts = postgres.full_text(query)
    +
      chunks = rrf_fuse(vec, fts, k)
    +
      return llm.chat(document(chunks))
    +
     
    +
    +
    +
    +

    Brain of Reese

    +

    A chippy, honest RAG chatbot over homelab documentation — git repositories, local + directories, and uploaded archives. Hybrid search (pgvector cosine ∪ Postgres + full-text search, Reciprocal Rank Fusion) feeds the whole relevant document to a + self-hosted LLM and streams back a grounded answer; when it has no notes it + admits it and suggests questions it can answer.

    +
    +
    + FastAPI + pgvector + Postgres + Playwright +
    + +
    +
    +
    + ``` + +3. `./build.sh` — rebuild `dist/`. + +## Testing & Quality +- Unit/integration: content-only change — no new JS logic; the E2E + regression test is task 4, and the full suite is the gate. +- Coverage: **>90%** on this task's new/modified code — vacuously satisfied + (HTML content only, no JS/CSS modified). + +## Completion Criteria +- [ ] `grep -c "project-card fade-in" src/index.html` returns 6 +- [ ] `grep -n "Brain of Reese" src/index.html` matches once, and that card + is the first `project-card` inside `#projects` +- [ ] `grep -c "Self-Hosted Services" src/index.html` returns 0 +- [ ] `grep -n "gitea.reeseapps.com/Experimental/brain-of-reese" + src/index.html` matches inside the new card +- [ ] `./build.sh` succeeds and the same greps pass on `dist/index.html` +- [ ] `npm test` fully green (44 tests — new test lands in task 4) +- [ ] README.md/AGENTS.md updated for anything user-visible this task changes + (the phase's final pass re-checks both project-wide) +- [ ] no behavior change in completed work diff --git a/.agents/phases/todo/05_update_projects_section/02_servwaf_card.md b/.agents/phases/todo/05_update_projects_section/02_servwaf_card.md new file mode 100644 index 0000000..cecd625 --- /dev/null +++ b/.agents/phases/todo/05_update_projects_section/02_servwaf_card.md @@ -0,0 +1,69 @@ +# Task 02 — ServWaf card (replaces Kubernetes Cluster) + +**Phase:** `05_update_projects_section` · **Story:** n/a + +## Objective +Replace the "Kubernetes Cluster" project card, in place (last slot), with +the **ServWaf** card. ServWaf is **closed source** (user decision): the +card carries **no** `project-links` block — description only. + +## Work +1. `src/index.html` — inside `
    `, replace the entire + `project-card` block whose `

    ` is "Kubernetes Cluster" (the card + containing the `# Kubernetes with k3s` code block) with the block below, + matching the file's 16/20/24/28-space indentation. Note: this card has + **no** `