Wonders of Work Review hub

Open changes waiting for a look, and what shipped recently. Anyone can open a preview below and leave feedback on GitLab.

Open for review

🌱 Nothing is waiting for review right now. Want to help shape what comes next? Read CONTRIBUTING →

Recently shipped to production (main) wondersofwork.nl 🎉

  • MR !112 by Jurjen de Vries ·
    Fix
    reconnect to relays whose connection failed or dropped
    What this changes, and why

    If a relay's first connection attempt failed, or its connection dropped later, the site never tried that relay again during the visit, as long as another relay was up. relay.nostr.net fails its first handshake fairly often, and it is our most complete relay.

    The reconnect code in connect-relays.js never ran. onclose removed the relay from wsConnections and then checked wsConnections.has(url), which is always false. Now:

    • A failed or dropped connection is retried with the existing backoff (3 s, 6 s), up to 3 attempts. Relays that were removed from the list on purpose are not retried.
    • A stale onclose from an old socket no longer removes a newer, working connection.
    • The 60 s maintenance pass also retries relays that are down, once their attempt counter resets (every 5 min). Before, it only did this when no relay at all was working.

    When a relay connects late, it still receives the requests that are already running, through the late-relay watcher in nostr-queue.js.

    What this affects

    This touches every page that uses Nostr, but only the relay connection handling. Nothing else changes.

    How to try it

    On the preview, open /nl/verhalen with DevTools open.

    • Console: when a relay's first connection fails you should see Reconnecting to wss://… in 3 seconds..., followed by a new attempt.
    • To force it, in Network → WS, block relay.nostr.net for the first load (request blocking), then unblock it. The relay should come back within a few seconds.

    In a test with a simulated failing first handshake, production never reconnected. This branch reconnected on attempt 3 and relay.nostr.net still delivered 15 events for requests already in progress.

    Before you ask for review
    • [x] Branch named feat/…, fix/…, chore/…, docs/… or hotfix/…
    • [x] Commit messages follow Conventional Commits
    • [x] pnpm format has been run and the checks are green
    • [ ] Tried it on the preview link
    • [x] Rebased on the latest main
  • MR !111 by Jurjen de Vries ·
    Fix
    read Nostr content from relays that still hold it
    What this changes, and why

    Most stories, calendar events and profiles stopped showing. The site read from four relays, and on 2026-09-30 none of them still had the content: relay.nsec.app is unreachable, nos.lol and relay.primal.net keep our events for only about a day, and relay.damus.io accepts connections but mostly never answers. The content still exists on other relays.

    • New relay list: relay.nostr.net, relay.ditto.pub, nostr.oxtr.dev, nostr.mom, nostr.data.haus. Each was measured to hold our content and to answer.
    • purplepag.es is added for profiles only (kinds 0/3/10002). Other queries and posts skip it (window.relaysFor).
    • Subscriptions stay open 6 s after the first relay answers instead of 2.5 s, so slower relays are no longer cut off.
    • The relay hints in "copy ID" (naddr) now point to relays that actually hold the events.
    • Event detail showed "not found" at random: it stopped waiting while a relay was still connecting, and a duplicate load could leave the page hidden. RSVP links ("Meet <name> at") now pass the author, so events made in other Nostr calendar apps (no WoW tag) open too.
    What this affects

    These changes touch every Nostr read and publish (stories, events, profiles, reactions, deletions), so posting and deleting also go to the new relays. Not changed: the OpenBunker login relays. The OpenBunker API still returns relay.nsec.app, but that is a separate change. The "empty" or "loaded" state can now appear up to 6 s after the first relay answers, but only when a relay is slow.

    How to try it

    On the preview link:

    • /nl/verhalen and /nl/agenda should show all stories and events (in local tests: 22 stories and 11 events, where production showed 0–1).
    • /nl/wonderful-people should show all profiles with names and avatars (39 of 39, where production showed 1).
    • Open a profile with RSVPs and click the events under "Meet <name> at". Each should open every time.
    • In DevTools > Network > WS: purplepag.es should only get kinds:[0] REQs.
    • While logged in: post and delete a test story. Publishing and deleting have not been tested.
    Before you ask for review
    • [x] Branch named feat/…, fix/…, chore/…, docs/… or hotfix/…
    • [x] Commit messages follow Conventional Commits
    • [x] pnpm format has been run and the checks are green
    • [ ] Tried it on the preview link
    • [x] Rebased on the latest main
  • Fix
    default location room selector visibility
    What this changes, and why

    Fixed create an event default location room selector visibility

    What this affects

    src/pages/[...locale]/create-event.astro

    How to try it

    go to create event page and select the default location

  • Feature
    add sml-spaces variant to cardGrid to generate random S M L spaces
    What this changes, and why

    Add sml-spaces variants to CardGrid to generate 3 random spaces by serialy small, medium, large spaces

    What this affects

    src/components/blocks/CardGrid.astro

    How to try it

    go to homepage after Discover section you will find the section. you can regenerate or create from strapi using CardGrid page component and selecting sml-spaces from variants selector

  • MR !108 by Jurjen de Vries ·
    Chore
    rename default-* UI fallbacks to fallback-*
    What this changes, and why

    Renames the UI fallback assets from default-* to fallback-* so they are clearly not editorial media, and so the upcoming hardcoded/i18n lint can exclude fallback-* by convention.

    • public/images/default-avatar.svg -> fallback-avatar.svg
    • public/images/default-placeholder.svg -> fallback-placeholder.svg
    • public/images/loader.svg -> fallback-loader.svg
    • all references updated across .astro and public/scripts (24+ refs), zero old refs left.

    og-default.jpg is left as-is (Open Graph share image, different purpose).

    What this affects

    Image file names under public/images and every reference to them. No behaviour change — same images, new names. No editorial content.

    How to try it

    Open a preview: avatars, placeholders and the loader still render (they now load from /images/fallback-*.svg).

  • Fix
    hardcoded text replace with cms
    What this changes, and why

    Completes the hardcoded-copy → Strapi migration so every user-facing string is content-managed (editable and translatable in Strapi instead of baked into code). This covers the bulk of the hardcoded-copy backlog, including copy hidden in client scripts that the grep-based lint:hardcoded could not see.

    English is preserved verbatim in the EN locale; the frontend now reads whatever Strapi serves per locale (nl-NL is populated), with an English fallback kept as the safety net.

    What this affects
    • public/scripts/* — all client libs: raw literals → window.__texts / window.profileUi / window.eventUi lookups; two dead files removed (user-metadata.js, profile.js), superseded by check-user-metadata.js
    • src/components/* — form fields, editor, navbar, pagination, reactions, room/tag selectors, loading, and blocks (audio/card/categories/newsletter/offer/quote/richtext), plus ExportKeySection
    • src/pages/[...locale]/* — agenda details, create-event, edit-profile, login, and event/story/profile previews
    • src/layouts/Layout.astro — injects window.__texts for client scripts
    • src/utils/getGlobals.ts + get{Login,Profile,Event}UiConfig.ts — expose the per-feature config shapes

    No Strapi schema changes in this MR — schema and content already live in production.

    How to try it

    On the preview link, switch locale (nl / en) and confirm the strings follow:

    • Newsletter status messages, event/story preview copy, profile export-key labels
    • Form field labels/placeholders, editor toolbar options, loading and error states
    Before you ask for review
    • [x] Branch named fix/…
    • [x] Commit messages follow Conventional Commits
    • [x] pnpm format run, checks green
    • [x] Tried it on the preview link
    • [x] Rebased on the latest main
  • Fix
    replace newsletter custom icon with image media
    What this changes, and why

    update newsletter icon logic

    What this affects

    src/components/blocks/Newsletter.astro

    How to try it

    go to coworking page

  • MR !105 by M Maaz ·
    Fix
    harden markdown editor and add fenced code blocks
    What this changes, and why

    Hardening pass on the custom markdown editor used by the event and story description forms (MarkdownEditor2, a contenteditable WYSIWYG with a markdown bridge). Fixes from a security audit plus extended edge-case testing:

    • HTML injection and attribute breakout in the inline parser
    • Multi-line blockquotes kept only the first > line
    • Fenced code blocks were mangled on save/reload
    • Mixed bold+italic (**a*b*c**) lost the literal stars
    • Trailing-space growth on every save (soft breaks)
    • Duplicate .mde-editor p CSS rule
    • Autolinks (<https://...>) eaten as HTML tags
    • Link titles ([x](url "title")) broke the URL attribute
    • Nested lists and continuation lines were dropped
    • Missing constructs vs the renderer: h1/h5/h6, inline code, <hr>,
    • images, 1) lists, backslash escapes, task lists

    Adds fenced code blocks with sugar-high syntax highlighting (GitHub-dark palette). Interactive behavior hardened: Enter splits blocks into new paragraphs (Shift+Enter = soft break), the code button is selection-aware (only the selection becomes a block; a collapsed caret inserts a fresh one), empty paste no longer destroys the selection, Enter in an empty list item keeps the list, linking over a link is refused.

    Round-trips are byte-stable, escaping is minimal so nostr clients and marked never show stray backslashes, and the editor was fuzzed (unicode, deep nesting, huge inputs) without crashes, loss or XSS.

    What this affects
    • src/components/MarkdownEditor2.astro - the editor (used by both
    • create-event and create-story forms)

    • public/scripts/render-markdown.js - public renderer, imports the
    • sugar-high bundle for fenced code highlighting

    • public/scripts/sugar-high.bundle.mjs - new vendored bundle
    • src/styles/_base.scss - .mde-editor styles and --sh-* tokens
    • package.json / pnpm-lock.yaml - sugar-high@^2.0.0 dependency
    • docs/markdown-editor.md - new editor documentation

    Review carefully: inline HTML escaping and URL sanitization (same allowlist as the public renderer), block-start escaping, and the Range-based DOM surgery in the Enter / code-button paths.

    How to try it

    On the preview link, open "Create event" (or a story):

    • Type a paragraph, press Enter - a new paragraph starts; Shift+Enter
    • makes a soft line break. Save and reload: content is byte-identical.

    • Select text and press </> - only the selection becomes a code
    • block; surrounding text stays put.

    • On an empty line, </> starts a fresh code block. Enter adds lines.
    • Fenced code with a language (e.g. ```js) renders highlighted on the
    • public event page.

    • Paste an image (no text) - the selection is preserved.
    Before you ask for review
    • [x] Branch named feat/..., fix/..., chore/..., docs/... or hotfix/...
    • [x] Commit messages follow Conventional Commits
    • [x] pnpm format has been run and the checks are green
    • [ ] Tried it on the preview link
    • [x] Rebased on the latest main
  • MR !103 by Jurjen de Vries ·
    Fix
    purple form field borders instead of peach
    What this changes

    Form fields used the peach $secondary-lightest (#fcdccc) border, which reads as light orange next to the purple brand palette. All form field borders are now $primary-lighter (#c1b3d8, lila) — same lightness, so the outline stays soft.

    • src/styles/_base.scss:60 — shared input-base mixin, so .input, .input-sm, .selector, .selector-sm, .textarea, .textarea-sm, .input-icon-in and friends all follow. Covers the newsletter signup, contact form, offer/quote form and booking form.
    • src/components/DateTimePicker.astro, src/components/BookingForm.astro, src/components/blocks/Offer.astro — date/time dropdown panels (Offer also had a border-secondary-light/20 panel border and divider).
    • src/components/MarkdownEditor.astro, src/components/MarkdownEditor2.astro — editor frame, toolbar and floating toolbars.

    Left untouched on purpose: btn-border-secondary buttons, peach *backgrounds* (Specs, CapsuleGrid, StepsGrid, Booking, Spotlight), and the markdown prose hr/blockquote borders in _base.scss — those are content, not form chrome.

    How to try

    1. Open the preview deploy for this MR. 2. Home page → newsletter block ("Abonneer je op onze nieuwsbrief"): the e-mail field border is lila, not peach. 3. /nl/offerte (offer form): all text fields, the textareas, the date/time picker and its dropdown panel show the same lila border. 4. Booking block on the home page: the number-of-people and date/time fields plus the dropdown panel match. 5. Focus a field — the focus ring is still ring-primary-light, unchanged.

    What this affects

    CSS only, no markup or logic changes; 11 lines across 6 files. No content, Strapi or route changes. Both locales, all pages containing forms. Everything is a colour token swap between existing palette variables, so no new colours are introduced.

  • MR !104 by Jurjen de Vries ·
    CI
    add ESLint base (lint:code)
    What this changes, and why

    Adds an ESLint base as the foundation for real code-quality linting (and, next, the mature i18n no-literal-string rule that will replace the grep-based lint:hardcoded). ESLint understands the JS/TS/Astro AST, so it catches bugs and bad patterns a regex cannot.

    • eslint + typescript-eslint + eslint-plugin-astro, flat config (eslint.config.js), non-type-checked recommended sets (fast, no Strapi needed).
    • New pnpm lint script and lint:code CI job, allow_failure (orange warning) like lint:types/lint:hardcoded.
    • no-explicit-any turned off for now (Strapi/Nostr data is loosely typed; separate effort); unused-vars as warnings during rollout.
    • Currently ~123 findings (empty blocks, unused vars, unused expressions, useless escapes) — real smells, non-blocking.
    What this affects

    eslint.config.js (new), package.json, pnpm-lock.yaml, .gitlab-ci.yml (new lint:code job), CONTRIBUTING checks table. No application code.

    How to try it

    pnpm lint locally, or the lint:code job on this MR (orange warning, non-blocking).

See all merged changes on GitLab →