Skip to content

fix(training): fetch TeSS listings at build time - #332

Merged
yasinmiran merged 2 commits into
mainfrom
fix/training-tess-build-time
Aug 24, 2026
Merged

fix(training): fetch TeSS listings at build time#332
yasinmiran merged 2 commits into
mainfrom
fix/training-tess-build-time

Conversation

@yasinmiran

Copy link
Copy Markdown
Collaborator

/training has three listings on it. All three have been failing silently on production, showing nothing.

What was broken

The listings were driven by the TeSS browser widget (elixirtess.github.io/TeSS_widgets). It fires its XHRs without an Accept header, so TeSS answers with an HTML 403 that carries no CORS headers, and the browser surfaces that as a CORS violation. Add the header and the identical URL returns 200 with access-control-allow-origin: *, so CORS was never really the issue. The widget also tries to set a User-Agent, which browsers forbid, which suggests it was written for a server-side context and dropped into a page.

Nothing catches the failure, so the page threw three uncaught Cannot read properties of null (reading 'data') and rendered no listings.

The static placeholder underneath is what made this hard to notice. "No upcoming training events at the moment." is our own markup at the old index.astro:153, sitting there for the widget to replace. It never got replaced, so the page has been asserting there is nothing on rather than admitting it failed to ask. TeSS was serving ten Norwegian materials and ten past events throughout.

What changed

The three fetches moved into the build. An Accept: application/json header is all TeSS wants, and CORS does not apply outside a browser. Gone: the widget script, its stylesheet, and 123 lines of .tess-container .tess-widget overrides that existed only to restyle markup we now emit ourselves.

New files are src/lib/tess.ts (fetch, retry, date and place formatting) and src/components/tess-unavailable.astro.

The materials search box survives as a client-side filter over the fetched list, with aria-live on the empty state.

Handling the fact that TeSS falls over

Roughly one request in three returned 5xx while I was testing, and successful ones took 11 to 19 seconds. So each endpoint gets five attempts with a rising backoff.

More importantly, the page now distinguishes an empty listing from an unreachable one. Reproducing the original bug's habit of showing a plausible empty state over a dead feed would have defeated the point of the PR. A failed fetch renders "This listing could not be loaded from TeSS when the site was last built" plus a link to the matching TeSS page, and logs [tess] events failed after 5 attempts (HTTP 522) to stderr so it is visible in the build output.

This is not hypothetical. One of my verification builds hit exactly that: TeSS 522'd four times running on both events endpoints. That run is why the reachable/empty split exists.

Verification

pnpm build, test:slugs and test:pages green, 303 pages, astro check at 0 errors and 0 warnings.

Driven in a browser against the built output: 10 materials, 10 past events, 0 upcoming (TeSS genuinely returns [] for upcoming Norwegian events right now), no console or page errors where there were three. The filter was exercised for a match, a non-match, and a clear.

The outage path was tested by pointing the helper at an unresolvable host: the build still succeeds, all three sections render the unavailable state with their TeSS link, and the warnings appear on stderr.

Notes

Venue is deliberately left out of the one-line summary. TeSS stores a street address there and frequently a doubled one ('Moltke Moes vei, Moltke Moes vei'), so the line shows city and country and the linked event page carries the address.

The tradeoff is that listings refresh on deploy rather than on page load. For a training catalogue that seems right, and it is what makes the page immune to TeSS being down when someone visits.

Also drops an unused BASE constant that was already dead on main.

All three listings on /training were driven by the TeSS browser widget, and all
three had been failing silently. The widget issues its XHRs without an Accept
header, so TeSS answers with an HTML 403 that carries no CORS headers, and the
browser reports the result as a CORS violation. It also tries to set a
User-Agent, which browsers forbid. Nothing catches any of this, so the page threw
three uncaught TypeErrors and rendered no listings at all.

The static placeholder underneath made it worse. "No upcoming training events at
the moment." was our own markup waiting to be replaced, so the page claimed there
was nothing on instead of admitting it had failed to ask. TeSS was serving ten
Norwegian materials and ten past events the whole time.

The fetch now happens during the build, where an Accept header is enough to get
JSON back and CORS never comes into it. The third-party script and stylesheet are
gone, and so are the 123 lines of overrides that existed to restyle markup we now
emit ourselves.

TeSS is unreliable, around one request in three came back 5xx while I was
testing, so this retries and then degrades. It also separates an empty listing
from an unreachable one. Repeating the old bug's habit of showing a plausible
empty state over a dead feed would have defeated the point, so a failed fetch
says so and links out to TeSS.

Drops the unused BASE constant while the file is open.
Copilot AI lite review requested due to automatic review settings August 24, 2026 09:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes /training TeSS listings failing at runtime by moving TeSS data fetching to build time, replacing the browser widget integration with server-side fetch/retry logic and first-party-rendered markup (including an explicit “unavailable” state when TeSS can’t be reached).

Changes:

  • Fetch upcoming events, past events, and materials during the Astro build and render them as static HTML.
  • Add src/lib/tess.ts to handle TeSS API querying, retries/backoff, and event date/place formatting.
  • Add a reusable “TeSS unavailable” UI component and keep a client-side materials filter.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/pages/training/index.astro Replaces TeSS widget runtime integration with build-time fetched lists + client-side materials filter.
src/lib/tess.ts Adds TeSS API fetch/retry utilities and formatting helpers used by the training page.
src/components/tess-unavailable.astro Adds a dedicated unavailable-state component for unreachable TeSS listings.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/pages/training/index.astro
Comment thread src/lib/tess.ts
Comment thread src/pages/training/index.astro
Event and material URLs come from a feed we do not control, and a build-time
render writes them into static HTML, so a javascript: URL would ship and then sit
there until somebody rebuilt. eventLink and the new materialLink now parse the
URL and fall back to a TeSS address we construct ourselves whenever the scheme is
anything but http or https.

test-pages.mjs grew a matching check over the built output, because the helper is
only one of the routes a URL can take into an href. It allows http, https, mailto
and tel, which covers everything the site emits today, and fails the run on
anything else. Confirmed by injecting a javascript: href into dist and watching
the check catch it.

Raised in review on #332.
@yasinmiran
yasinmiran merged commit 9d4c4d6 into main Aug 24, 2026
1 check passed
@yasinmiran
yasinmiran deleted the fix/training-tess-build-time branch August 24, 2026 10:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants