Phase 2: fix the confirmed bugs #3

Merged
pjotr merged 12 commits from fix/phase-2-bugs into main 2026-10-28 10:48:19 +00:00
Owner

This PR fixes bugs and adds the missing basics (404 page, page titles, lang). Every live URL stays the same, and nothing changes on the server: same Dockerfile, compose file, pipeline and Traefik labels.

One commit per fix:

  1. 404 instead of 500. An unknown category or atelier (/aanbod/x/y) crashed with a 500; an unknown atelier in a known category (/aanbod/focus/nope) gave an empty page with a 200. Both now give a 404. A new error.vue shows the error inside the normal header and footer, in Dutch ("Deze pagina bestaat niet"), with a button back to the start page.
  2. Stable atelier URLs. URLs were built from the titles, so renaming an atelier moved its URL. Every category and atelier in content/aanbod/nl now has a slug field, set to exactly the slug its live URL uses today. Links and routing read slug; one slugify() (utils/slug.js) replaces the two sanitize() copies and is only a fallback. Tested: renaming "Modeltekenen" in a local build left all 15 URLs unchanged.
  3. Unique async-data keys. pages/index.vue and elements/news.vue both cached under news, with different queries. Now news-home and news-by-id-<ids>. (The page-${route} key from the audit went with the extra-pages route in Phase 1.)
  4. text.vue side2. v-if:markdownString="side2" was a typo, so a side2 could never render. Fixed and the props typed. No visible change today: academie.yml has no side2 (and never had one in git).
  5. news.vue and banner.vue. News: an empty list no longer throws, and the id branch sorts the data, not the Ref. Banner: a changed image id now shows (the watcher replaced a plain variable), and a missing image record no longer crashes.
  6. Persons load once. person.vue loaded all persons once per person. The list now loads them once and hands each person its record: same people, same order, and the /academie HTML drops from 290 kB to 208 kB. The server-side console.logs ("Person …", "here") are gone.
  7. Kunstkuur videos from content. The three Vimeo iframes were hard-coded for title === 'Kunstkuur'. The Kunstkuur atelier now lists them in a videos field (ids from content/media/videos), in the same order, with the same markup.
  8. The red thead debug border in assets/css/main.css is removed. display: none stays: it hides the empty header row of the price table on /inschrijven.
  9. Page titles, description, lang. <html lang="nl">, a <title> per page ("Aanbod · Sint Lukas Academie Brussel"; the home page is the site name only) and a meta description, both from content/site/info.json.
  10. A dead image reference. Volwassenen pointed its background at /assets/uploads/1.jpg, deleted through the CMS on 29 Jan 2026. No page reads a category's background, so the line is removed and nothing on the page changes.
  11. README: a content-field reference for the aanbod: what slug is and why it never changes, and the fields a category and an atelier need.

Not in this PR: the Facebook and Instagram icons in the footer are still not links. Their URLs are not anywhere in this repo, its history or the old repo, and we don't want to guess them. Once the school gives the two addresses, it's a two-line change.

Size: 27 files changed, 212 lines added, 88 removed.

Checked locally with Node 22.14.0 and pnpm 9.1.2 (Corepack):

  • A clean pnpm install --frozen-lockfile and pnpm build pass.
  • All 20 live routes return 200 before and after: home, /aanbod, /academie, /inschrijven, /contact and the 15 atelier pages. The 15 atelier URLs on /aanbod are identical before and after. All 75 image and asset URLs on those pages still return 200.
  • /aanbod/x/y and /aanbod/nope/expo-traject went from 500 to 404; /aanbod/focus/nope and /aanbod/volwassenen/Modeltekenen (wrong case) went from an empty 200 to 404. No route returns a 500.
  • The rendered HTML of home, /aanbod, one atelier, /academie, /inschrijven and /contact has the same body markup and the same CSS classes before and after. The only differences are lang="nl", the <title>, the meta description, and the script and data changes listed above (the new slug and videos fields, one persons list instead of one per person).

Not checked: Docker and Woodpecker (unchanged by this PR).

🤖 Generated with Claude Code

This PR fixes bugs and adds the missing basics (404 page, page titles, `lang`). Every live URL stays the same, and nothing changes on the server: same Dockerfile, compose file, pipeline and Traefik labels. One commit per fix: 1. **404 instead of 500.** An unknown category or atelier (`/aanbod/x/y`) crashed with a 500; an unknown atelier in a known category (`/aanbod/focus/nope`) gave an empty page with a 200. Both now give a 404. A new `error.vue` shows the error inside the normal header and footer, in Dutch ("Deze pagina bestaat niet"), with a button back to the start page. 2. **Stable atelier URLs.** URLs were built from the titles, so renaming an atelier moved its URL. Every category and atelier in `content/aanbod/nl` now has a `slug` field, set to exactly the slug its live URL uses today. Links and routing read `slug`; one `slugify()` (`utils/slug.js`) replaces the two `sanitize()` copies and is only a fallback. Tested: renaming "Modeltekenen" in a local build left all 15 URLs unchanged. 3. **Unique async-data keys.** `pages/index.vue` and `elements/news.vue` both cached under `news`, with different queries. Now `news-home` and `news-by-id-<ids>`. (The `page-${route}` key from the audit went with the extra-pages route in Phase 1.) 4. **`text.vue` side2.** `v-if:markdownString="side2"` was a typo, so a `side2` could never render. Fixed and the props typed. No visible change today: `academie.yml` has no `side2` (and never had one in git). 5. **`news.vue` and `banner.vue`.** News: an empty list no longer throws, and the id branch sorts the data, not the Ref. Banner: a changed image id now shows (the watcher replaced a plain variable), and a missing image record no longer crashes. 6. **Persons load once.** `person.vue` loaded all persons once per person. The list now loads them once and hands each person its record: same people, same order, and the `/academie` HTML drops from 290 kB to 208 kB. The server-side `console.log`s ("Person …", "here") are gone. 7. **Kunstkuur videos from content.** The three Vimeo iframes were hard-coded for `title === 'Kunstkuur'`. The Kunstkuur atelier now lists them in a `videos` field (ids from `content/media/videos`), in the same order, with the same markup. 8. **The red `thead` debug border** in `assets/css/main.css` is removed. `display: none` stays: it hides the empty header row of the price table on `/inschrijven`. 9. **Page titles, description, `lang`.** `<html lang="nl">`, a `<title>` per page ("Aanbod · Sint Lukas Academie Brussel"; the home page is the site name only) and a meta description, both from `content/site/info.json`. 10. **A dead image reference.** Volwassenen pointed its `background` at `/assets/uploads/1.jpg`, deleted through the CMS on 29 Jan 2026. No page reads a category's `background`, so the line is removed and nothing on the page changes. 11. **README:** a content-field reference for the aanbod: what `slug` is and why it never changes, and the fields a category and an atelier need. **Not in this PR:** the Facebook and Instagram icons in the footer are still not links. Their URLs are not anywhere in this repo, its history or the old repo, and we don't want to guess them. Once the school gives the two addresses, it's a two-line change. Size: 27 files changed, 212 lines added, 88 removed. Checked locally with Node 22.14.0 and pnpm 9.1.2 (Corepack): - A clean `pnpm install --frozen-lockfile` and `pnpm build` pass. - All 20 live routes return 200 before and after: home, `/aanbod`, `/academie`, `/inschrijven`, `/contact` and the 15 atelier pages. The 15 atelier URLs on `/aanbod` are identical before and after. All 75 image and asset URLs on those pages still return 200. - `/aanbod/x/y` and `/aanbod/nope/expo-traject` went from 500 to 404; `/aanbod/focus/nope` and `/aanbod/volwassenen/Modeltekenen` (wrong case) went from an empty 200 to 404. No route returns a 500. - The rendered HTML of home, `/aanbod`, one atelier, `/academie`, `/inschrijven` and `/contact` has the same body markup and the same CSS classes before and after. The only differences are `lang="nl"`, the `<title>`, the meta description, and the script and data changes listed above (the new `slug` and `videos` fields, one persons list instead of one per person). Not checked: Docker and Woodpecker (unchanged by this PR). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
/aanbod/<unknown>/<x> crashed with a 500 (theCat undefined) and an unknown
atelier in a known category rendered an empty 200 page. Both now throw a
fatal 404. error.vue renders inside the default layout (header, footer),
with a Dutch message and a link to the start page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Atelier URLs were built from the titles by two copies of sanitize(), so a
title edit moved the URL and broke every link to it. Every category and
atelier in content/aanbod/nl now carries a `slug`, set to exactly the slug
its live URL uses today (15 ateliers, 6 categories: no URL changes).
Routing and the aanbod cards read `slug`; one slugify() in utils/slug.js
replaces both sanitize() copies and is only a fallback for an entry
without a slug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pages/index.vue and elements/news.vue both cached under the key `news`,
with different queries, so one could serve the other's data on client
navigation. They are now `news-home` and `news-by-id-<ids>`. The
`page-${route}` key ("page-[object Object]") from the audit went with the
extra-pages route in Phase 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`<elements-markdown v-if:markdownString="side2" />` was a typo: it only
tested side2 and never passed it, so a side2 could not render. Now
`v-if="side2" :markdownString="side2"`. The props are typed with an empty
string default instead of `String || ''`.

No visible change today: academie.yml (the one page that uses this
component) has no side2, and never had one in git history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
news.vue: an empty list no longer throws (`props.articles[0].constructor`
on undefined), and the id branch sorts `data.value` instead of the Ref
itself (which threw).

banner.vue: the watcher replaced a plain variable (`image = newImage`), so
a changed image id never showed. `image` is now a ref that the first fetch
and the watcher both assign to. A missing image record gives an empty
banner instead of a crash on `null.cover`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
person.vue loaded the whole persons collection once per person (and kept
a payload copy per person), then filtered by title. persons.vue now loads
the collection once (key `persons-nl`) and hands each person its record;
person.vue only fetches on its own when used without a record (the
`person` dispatch block, unused in content today). Same people, same
order, same markup; the /academie HTML payload drops from 290 kB to 208 kB.

Also removes the server-side console.log calls ("Person <id>" per person,
"here" in locations.vue). No other console calls remain in components,
pages or layouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three Vimeo iframes were hard-coded in videos.vue and shown when
`title === 'Kunstkuur'`. The Kunstkuur atelier now lists them in a
`videos` field (ids from content/media/videos, which already held the same
three URLs), in the order the page showed them. Any atelier with a
`videos` list now shows the block; same three iframes, same order, same
markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`thead { border: thin solid red !important }` was a debug leftover. The
`display: none` next to it stays on purpose: the price table in
inschrijven.yml has an empty header row (Markdown tables need one), and
the rule hides it. No visible change: a hidden thead showed no border.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every page had no <title>, no description and no lang. Now:
- `<html lang="nl">` (nuxt.config app.head)
- the default layout reads content/site/info.json once and sets the
  title template "<page> · <sitename>" and the description from
  `sitedescription`
- each page names itself: Aanbod, the academie title, Inschrijven,
  Contact, the atelier's title, "Pagina niet gevonden" on the 404; the
  home page is just the site name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`background: /assets/uploads/1.jpg` pointed to a file deleted through the
CMS on 29 Jan 2026 (5fd7295). No component reads a category's
`background` field, so nothing requested the file and nothing on the page
changes. The original (a 1300x867 photo) is still in git history at
5fd7295^ if it is ever wanted back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What `slug` is and why it never changes, how a new one is picked, and the
fields a category and an atelier need (required or not, what each does),
including the new `videos` field. Notes that a category's `thumb`,
`texture` and `background` are not read, and where the page titles come
from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The error page rendered elements-banner with an empty title, leaving a
blank 240 px band (plus its bottom margin) between the header and the h1.
The banner goes; the content block gets pt-12 md:pt-16, the same gap the
banner's own mb-12 md:mb-16 gives the other pages' content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pjotr merged commit 4b1caad91a into main 2026-10-28 10:48:19 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sintlukas/sla-website!3
No description provided.