Skip to content

release: promote development to main (academy tutorials + ConNext pivot + partners on app/solution pages) - #118

Open
WilcoLouwerse wants to merge 426 commits into
mainfrom
development
Open

WilcoLouwerse wants to merge 426 commits into
mainfrom
development

Conversation

@WilcoLouwerse

Copy link
Copy Markdown
Contributor

Summary

Promotes ~123 commits from development to main. Highlights:

  • Academy — new tutorials: spec-driven development (EN + NL), local Nextcloud, OpenSpec introductie, Hydra leerlijn, Claude Skills leerlijn, DeskDesk T5 (Integrate). Series-focus filter polish + locale-aware /academy links.
  • Blog — the-platform-moment rewrite (addressable-market segmentation) + DownloadPanel CTA polish.
  • Features — homepage hero pivot to ConNext, partners on every app & solution page, OpenRegister Showcase → MockScene, footer "Way of Work" entry, AI-crawler-friendly (llms.txt, robots.txt).
  • Solutions — added anonimiseren and openwoo; removed legacy-erp, software-catalog, woo.
  • Site — hide auto-generated "Footnotes" label, custom theme Error page, OG image refresh.
  • Copy — quality policy refreshed to January 2026; OpenTalk references removed from ConNext.
  • Chore — preset 3.20.0 bump, package-lock refresh, app download stats refresh.

174 files changed · +20,243 / −1,936

Test plan

  • Build passes on main after merge
  • Homepage hero renders with ConNext pivot
  • Academy index shows new tutorials in both EN and NL
  • App and solution pages render the partners block
  • /solutions/anonimiseren and /solutions/openwoo load; removed solution slugs 404 cleanly
  • static/llms.txt and static/robots.txt served at root

🤖 Generated with Claude Code

@MWest2020

Copy link
Copy Markdown
Contributor

Code Review — PR #118: release: promote development to main

Overview

Release-merge van development → main: 174 bestanden, +20.243 / −1.936. Bundelt ~123 commits — academy-uitbreiding (Hydra, Claude Skills, OpenSpec, DeskDesk T5/T6 series + i18n/nl mirrors), homepage-pivot naar ConNext, partners op alle app/solution pagina's, AI-crawler baseline (llms.txt/robots.txt/sitemap), solution-herstructurering, @conduction/docusaurus-preset major bump 3.3.0 → 3.20.0, Node 18 → 20, en een nieuwe PR-validation workflow. CI is groen (CodeQL pass, deploy / Build and validate pass).

Sterke punten

  • PR-gated validatie — .github/workflows/validate-ai-baseline.yml draait de volledige build + AI-baseline check op iedere PR. Dat sluit precies het gat dat de inline comment beschrijft (centrale .github-workflow draait alleen op push). Belt-and-braces extra step is een nette redundantie.
  • Auditable comments — vrijwel iedere niet-triviale config-keuze (sitemap ignorePatterns, legalLinks, redirects, npmrc bypass, locale-gap voor nl) heeft een uitlegblok met waarom. Past goed bij de "boring, auditable" voorkeur.
  • Client redirects voor SEO-equity — /over-ons, /openconnector*, /solutions/woo, /iso netjes afgevangen via @docusaurus/plugin-client-redirects. De keuze om de overige verwijderde URLs (componenten-catalog, legacy training) bewust te laten 404'en is gedocumenteerd.
  • Node 18 → 20 is consistent doorgevoerd: .nvmrc, package.json engines, en workflow node-version: '20'.

Aandachtspunten

1. min-release-age cooldown is uitgeschakeld (supply-chain)

.npmrc zet min-release-age=1 uit om @conduction/docusaurus-preset@3.20.0 te kunnen installeren vóór de 24h-cooldown verstreken was:

# TEMPORARILY DISABLED 2026-05-22 ... Re-enable tomorrow.
# Tracking issue: ConductionNL/conduction-website#115 (re-enable tomorrow).
# min-release-age=1
  • Vandaag is 2026-05-26 — "morgen" is 4 dagen geleden. De cooldown hoort allang weer aan te staan, óf de PR sluit met een commit die min-release-age=1 re-activeert. Niet mergen naar main met cooldown uit.
  • Bonus: de baseline stond op 1 dag, terwijl een 7-daags anti-yank-window aan te raden is. Overweeg meteen door te trekken naar min-release-age=7 bij re-enable.
  • De preset-versiesprong is groot (3.3.0 → 3.20.0, 17 minor releases). Aanbeveling: re-enable cooldown, draai daarna npm audit signatures en commit het resultaat van een verse npm ci --ignore-scripts als sanity check.

2. llms.txt is al stale bij landing

static/llms.txt beschrijft Solutions als:

"government use cases (WOO compliance, software catalog, zaakafhandeling, archief, legacy ERP migration, MKB workspace)"

…maar in dezelfde PR worden solutions/woo, solutions/legacy-erp en solutions/software-catalog verwijderd, en solutions/anonimiseren + solutions/openwoo toegevoegd. Sync de tekst (en de losse https://www.conduction.nl/iso link → die redirect nu naar /quality, dus de redirect vangt het op, maar canonical zou kloppender zijn). Een AI-crawler die llms.txt ophaalt krijgt nu een onjuiste eerste indruk van wat het bedrijf aanbiedt.

3. Redirect-dekking ontbreekt voor nl-locale

De comment in docusaurus.config.js benoemt het correct: client-redirects worden voor src/pages/*.mdx alleen voor de EN-route geëmit. Concreet: als ergens /nl/solutions/woo of /nl/iso is geïndexeerd of gedeeld (presentaties, partner-pages), gaan die naar een 404. Geen blocker — wel het overwegen waard om óf:

  • óf de NL-equivalent expliciet toe te voegen ({from: '/nl/iso', to: '/nl/quality'}),
  • óf een korte regel toe te voegen aan static/robots.txt/llms.txt over welke locale-paden gegarandeerd zijn.

4. start-script breaking change

package.json: start is docusaurus start --locale en geworden. Wie npm start gewend was om "default locale" te krijgen, krijgt nu altijd EN. start:nl is nieuw. Niet ernstig (intern team), maar wel vermelden in CHANGELOG en/of release notes — anders verliest iemand vijf minuten met "huh, NL render verschilt".

5. CI-workflow Node-versie hardcoded los van .nvmrc

.github/workflows/validate-ai-baseline.yml zet node-version: '20' letterlijk, terwijl .nvmrc ook 20 zegt. Bij een volgende bump (20 → 22) is dat twee plekken bijwerken, dus drift-risico. Triviale fix:

- name: Setup Node.js
  uses: actions/setup-node@v4
  with:
    node-version-file: '.nvmrc'

6. Voor release: dev → main PRs: graag ook GitHub release tag

Met 123 commits in één klap is git log op main straks lastig leesbaar. Overweeg een gh release create met dezelfde body als deze PR ná de merge — geeft auditors een single-page changelog.

Risico's

Risico Kans Impact Mitigatie
Supply-chain via uitgeschakelde cooldown laag hoog Re-enable vóór merge naar main; verifieer met npm audit signatures
Stale llms.txt schaadt AI-citaten hoog laag Sync solution-lijst in dezelfde commit
/nl/* deep-links 404 na rename laag laag NL-redirects toevoegen of bewust accepteren
Preset 3.3 → 3.20 verbergt regressie laag midden CI dekt het al via build + validator (groen)

Verdict

Approve onder voorwaarde: re-enable min-release-age (issue #115) en sync static/llms.txt vóór merge naar main. De overige punten zijn nits / opvolg-werk. CI is groen en de auditability is sterk — dit is het soort release-PR dat goed te reviewen blijft ook door iemand die de afgelopen weken niet meekeek.

🤖 Generated with Claude Code

WilcoLouwerse and others added 25 commits June 8, 2026 13:23
…MCP schema

- objects-tool Notes column: `registerId` / `schemaId` → `register` / `schema`
  (integer IDs) to match McpToolsService::getObjectsTool() inputSchema.
- Resource URI template: `openregister://objects/{registerId}/{schemaId}` →
  `{register}/{schema}` and `{registerId}/{schemaId}/{uuid}` → `{register}/{schema}/{id}`
  to match McpResourcesService::listTemplates() output.
- Discovery JSON description: drop `audit` from the parenthetical capability
  list — `audit` isn't a tool, served as object history via the same `objects`
  family (as the tutorial itself correctly explains later).

Mirrors EN edits in the NL i18n file.

Resolves all 3 review threads on PR #12.
…ed download total

- Replace hand-drawn placeholder app icons with the canonical <AppGlyph>
  from the preset across the 17 /apps product heroes (auto from appId),
  the ConNext platform diagram, and the apps-catalogue grid. openanonymiser
  and app-versions keep inline icons (no canonical glyph yet).
- Drop the explicit cobalt iconColor on product heroes so DetailHero renders
  the orange icon hex on its cobalt background.
- Docs links now point at per-app subdomains (https://<slug>.conduction.nl)
  instead of docs.conduction.nl/<slug>.
- Homepage download stat reads the combined GitHub+Codeberg total from the
  local app-downloads.json (TOTAL_DOWNLOADS), and per-app cards use
  downloads_total. Collector merges both forges. Fixes the "0 downloads"
  figure left after the GitHub→Codeberg migration.
…real endpoint, basic-auth, nextcloud.local' (#12) from tutorial/mcp-server-deepdive into development

Reviewed-on: https://codeberg.org/Conduction/conduction-website/pulls/12
…trofit playbook' (#15) from feature/tutorial-openspec-retrofit into development

Reviewed-on: https://codeberg.org/Conduction/conduction-website/pulls/15
OpenSpec series:
- add Part 0 (why spec-first), Part 3 (ADRs), Part 4 (scenario to Playwright)
- revise Parts 1-2 (starting point, cross-links, voice)

Hydra series:
- correct gate count to 22 and builder model to Sonnet (config-authoritative)
- add OpenSpec->Hydra contract, supervisor/orchestrator split, hydra.json v2 schema
- fix Applier description (read-only go/no-go, no fixes)
- new Part 7 (CI and deployment)

Site-wide:
- rename all Dutch tutorial slugs to English (en + nl) with redirects
- strip em-dashes from new content per tutorial-write voice rules
# Conflicts:
#	academy/2026-05-12-openspec-tutorial-2-eerste-change/index.mdx
The fleet-rename sed pass had rewritten the redirect 'from' values to the
new English slugs, turning each into a self-redirect that collided with the
real page at build time. from = old Dutch slug, to = new English slug.
…OpenConnector)

- Part 3: scope a catalog, the publicatiedatum publish rule, public search,
  and the DCAT-AP-NL harvest feed (with live screenshots)
- Part 4: source, mapping, and synchronisation in OpenConnector that turn an
  external API into Woo publications (with live screenshots)
- Part 2: link its next-steps to the new Part 3
…catalog + OpenConnector)' (#17) from academy/woo-publish-harvest-connect into documentation
The docs build prebuild crashed when an upstream stats API (GitHub/Codeberg/
Nextcloud store) returned 4xx/5xx, blocking every Cloudflare Pages deploy from
the documentation branch since 2026-06-11. A download-stats fetch must never
break the build: catch network errors, keep the committed data/app-downloads.json
stub, and continue.
…ts fetch fails' (#18) from fix/docs-build-app-downloads-resilient into documentation
…T register

- Part 1: import Woo and DCAT registers via the OpenRegister UI
- Part 2: upload files via the Files panel; API moved to an appendix
- Part 3: publish and harvest a DCAT catalog (catalog scope, publicatiedatum
  publish rule, public search, DCAT-AP-NL feed) via the UI
- Part 4: source, mapping, synchronisation via OpenConnector UI; API appendix
- Fresh vanilla-Nextcloud screenshots for Parts 1-3 (theming excluded)
- Registers hosted on openregister.conduction.nl/oas/
…Parts 1-4) + DCAT' (#19) from academy/woo-series-ui-first into documentation
…art 4 resultsPosition

- static/oas/woo_register.json + dcat_register.json: the two register configs
  the WOO tutorial imports (Part 1). Previously the tutorial pointed at
  openregister.conduction.nl/oas/ which 404'd and the files never existed.
- Part 1: repoint import URLs to conduction.nl/oas/ (deployable here).
- Part 4: resultsPosition 'body' -> '_root' (the pet-store endpoint returns a
  root array; 'body' dot-paths into it and creates zero objects).
- bump @conduction/docusaurus-preset ^3.20 -> ^3.22 (adds the AppGlyph the
  landing pages need). NB: /apps + /connext still use components ahead of the
  latest published preset, so a full site build is still blocked until that
  preset publishes.
Two fixes that were blocking the docs deploy and the tutorial UX:

1. AppGlyph build break: connext.mdx and apps-catalog.js imported AppGlyph
   from @conduction/docusaurus-preset, but that component is not in any
   published preset release (3.22.0 / 3.22.1-beta.1 both omit it), so the
   import resolved to undefined and crashed the static render of /connext
   — failing the whole build. Add a local AppGlyph (token-painted app
   monogram) and point both importers at it. Drop once the preset ships
   AppGlyph in a stable release.

2. Tutorial series navigation: the academy uses a custom BlogPostPage that
   bypasses BlogPostItem, so the preset's BlogPostItem/Footer SeriesNav
   swizzle doesn't reach it. Render <BlogSeriesNav/> (from the preset)
   directly after the post body, so every multi-part tutorial (woo, hydra,
   build-an-app, openspec, workstation, claude-skills) gets a 'Part X of N'
   strip with prev/next. Requires @conduction/docusaurus-preset with
   SeriesNav/BlogSeriesNav (^3.22.0 picks it up once published).
…enshots

Part 3 (publish-and-harvest) showed the public-search response and the
DCAT-AP-NL feed as screenshots of the browser's raw JSON view — an
unformatted wall of text. Replace both with Docusaurus (Prism) `json`
code blocks: selectable, syntax-highlighted, and diffable.

Also corrects a content mismatch: the public-search screenshot showed
both datasets (total: 2) under a caption claiming only the published one;
the code block now shows the published-only response (total: 1), matching
the narrative. The DCAT block mirrors OpenCatalogi's actual
DcatMappingService serialisation (dcat:Dataset / dcat:Distribution with
dct:* + EU authority URIs for theme and file-type).
Convert the OpenConnector tutorial so each action step (register source,
write mapping, create synchronisation, run) offers three synced tabs —
UI, API (curl), and PHP — via Docusaurus <Tabs groupId="woo-how">, so a
reader picks an interaction style once and it persists across every step.

- The old 'Appendix: via the API' is folded into per-step API tabs.
- PHP tabs use the apps' real OCA\ services, verified against source:
  OpenRegister ObjectService::saveObject(object:,register:'openconnector',
  schema:'source'|'mapping'|'synchronization'), OpenConnector
  CallService::call(), MappingService::executeMapping(), and
  SynchronizationService::synchronize().
- Fixes a real bug: the synchronisation's results position for the Pet
  Store's bare array is '_root', not 'body' (the UI text said 'body').
- Step 5 (schedule) stays UI-only with a note: scheduling is a separate
  background job, so it has no one-line API/PHP equivalent.
…rg URL 404)

The Dutch woo-register tutorial still linked the woo-website Codeberg raw
URL, which now 404s. Match the English version: use the live, hosted
config at https://conduction.nl/oas/woo_register.json.
! almere voor Acato > Den Haag
remko48 and others added 19 commits September 21, 2026 22:37
…and-input

chore(config): roster paths, sitemap scope and a dev watch fix
…ays (#224)

The app is installed on the portal and the site sends it nothing,
because the plugin is only wired when PORTALIQ_TRAFFIC_ORIGIN is set
and production never set it. So there is no measurement, and nothing
on either side says so.

This adds the variables to the build and a guard that fails the deploy
when the two disagree: set the origin and the built pages must carry
the traffic client, leave it unset and they must not. A variable that
is set but does nothing is worse than one that is unset, because it
reads as measurement being on. The guard matches the client route,
which nothing else requests, rather than the portal host, which is
also the enquiry endpoint and is in the bundle either way.

The variable is NOT set in this change, deliberately. The client's own
settings call, `/api/content/site?portal=<slug>`, is public on that
instance and answers 404 not_found for every slug tried, including
none: conduction, conduction-nl, website, www.conduction.nl, main,
public, site, connext, portaliq. Other portaliq routes answer 401, so
the route is reachable and the portal is what is missing. Turning the
site side on now would put a 14KB script on every page whose settings
call 404s and which measures nothing.

Setting the variable is then the whole remaining change.
…225)

* feat(a11y): measure the states a page is in after someone clicks, and whether focus is visible

Three of the four gaps I named when asked whether every page had been
thoroughly tested. It had not: every result so far described a page as it
loads, in Chromium, with no opinion about keyboard users.

REVEAL_ALL puts a page into its opened state before measuring: every
<details> opened, every aria-expanded=false clicked, every unselected tab
and Showcase-style item activated. This is not hypothetical. The
colour-role lint flagged badge markup on /quality/ that renders ZERO times
as the page loads, because it lives in a Showcase panel that is not
defaultOpen, and the runtime sweep had no way to see it. Findings now carry
the state they were found in, so "only after opening something" stays
distinguishable from "on the page as it loads".

FOCUS_CHECK asks two things of each control: does anything change when it
takes focus, and if the change is an outline, does it clear 3:1. This was in
the September plan and never shipped. On two pages alone it finds focus
rings at 1.00:1, which is an indicator the same colour as the thing behind
it.

The focus check compares what is PAINTED, not the property list. Chromium's
UA sheet moves outline-offset from 0px to 1px on a:focus even when the
author has set outline:none, so a raw property dump saw a change where a
reader sees nothing and the stripped-focus fixture went unreported. An
outline with style none or zero width now reads as nothing.

WebKit is Safari's engine and therefore most phones, and the suite ran zero
percent of it: the mobile project was pinned to chromium on the grounds that
layout is engine-agnostic. Largely, not entirely. A mobile-safari project
runs the mobile and a11y specs on it.

It ships warning-first in CI and I want to be plain about why: WebKit needs
system libraries this machine cannot install without sudo, so CI is the
first place it will ever execute and nobody knows what Safari says about
these pages. Blocking the deploy on an unknown would stop releases for
reasons unrelated to the change being deployed. The step prints what it
found and the comment says to promote it once it is understood. A warning
nobody promotes is the same as no check at all.

a11y-interactive.spec.js is the control for both. The reveal test uses a
fixture whose defects are all behind a disclosure and asserts the checker
finds nothing before opening and both defects after. The focus test asserts
that outline:none with nothing put back, and an indicator under 3:1, are
each reported, and that a correct indicator stays quiet.

* test(a11y): the focus check scores the whole indicator, not just the outline

The preset's focus treatment is two rings now: a brand outline plus a halo of
the opposite luminance, because no single colour clears 3:1 on every surface
this site puts a control on. Judging the outline alone would report that
treatment as broken, which is the checker being wrong about a page rather
than the page being wrong.

It reads the box-shadow rings as well and scores the best of them, since the
criterion is met when any part of the indicator is visible.

The control gained a case for it: a link on an orange button whose outline is
1.00:1 and whose halo is 5.93:1 must stay quiet. Without this change that
case fails, which is what makes it a control rather than a comment.
…ite (#226)

3.49.1 carries the two-ring focus indicator. A focus-visibility check added to
this suite measured 1,399 failures on 316 pages, every one an indicator under
the 3:1 the criterion asks for, and the site has been pinned one patch short
of the fix since it was released.

No single ring colour clears 3:1 on every surface here: KNVB orange is 2.68:1
on a cobalt-50 panel and 1.00:1 on an orange button, coral-600 is 1.94:1 on a
cobalt hero, white fails on light grounds and cobalt-900 on dark ones. The
preset draws two rings now, the brand outline plus a halo of the opposite
luminance, and the worst case across those surfaces is 5.93:1.

3.49.0 to 3.49.1 is only that change. The FullStack component the site already
uses is unaffected.

Verified: build green, colour-role lint reports 0 blocking, the halo is in the
built CSS with both theme values, and focus measures clean across 20 page and
theme combinations. Full e2e is 272 of 274 at --workers=2; the two failures are
"connext lists the renamed apps" and "deadline defender", both of which pass in
isolation (2 of 2, twice). The cn-* custom elements upgrade asynchronously and
lose that race when this machine is loaded.
)

The deploy workflow builds with PORTALIQ_TRAFFIC_ORIGIN set, a guard
fails the deploy if the traffic client is missing, and then the e2e
suite runs against that same build. So every test that loaded a page
posted a real page_view to the production collector, on every deploy,
across three browser projects.

Measured hours after measurement was switched on: 46 of the 58 events
recorded against the ConductionNl portal came from localhost:4173,
which is this suite's own preview server. The first day of the site's
analytics was three quarters test traffic.

e2e/base.js aborts the collector beacon and every spec imports its
test from there. Aborting beats pointing the build at a dead origin:
the page under test stays byte-for-byte what deploys, including the
script tag the guard checks for, and only the beacon is dropped. The
settings GET is left alone so the client still runs its real code path
and would still fail the suite if it threw.

Proven against the live instance, both directions: a full 274-test run
leaves the event count at 92, and the same spec with the import put
back takes it 62 -> 64.

Two of my own game tests came with it, both brittle for the same
reason — they encoded copy that the 3.48.0 rework changed:

- deadline defender mapped each case's wording to the step it wanted.
  That map broke twice, most recently because a fourth lane arrived:
  a case that takes no next step at all is now one of the answers. It
  asserts the board is dealt and that answering replaces the case;
  which lane a case belongs in is the engine's own tests' business.
- blueprint rush now takes the next flow when the clock beats it,
  rather than depending on winning the one it was dealt.

And one stale test unrelated to any of it: product-names looked for
the renamed apps inside `pd-item[name]`, the platform diagram's custom
elements. That diagram lists the Nextcloud apps Connext runs on now,
and the elements are replaced on hydration, so the selector matched
nothing. It asserts the page names them, which is what the rename
sweep is actually for.

Verified: both locales build, 274 of 274 pass on the gating projects.
The 80 mobile-safari failures here are the missing WebKit system
libraries this config already documents; CI installs them.
…ger never meets (#228)

Closing out the last open items from the mobile and dark-mode review. Three of
the four turned out to be the instrument over-reporting rather than site debt,
which is worth saying plainly.

WebKit is in the gating step now. Its first real run was 79 of 81, and the two
were one flake that passed on retry. The single genuine engine difference was
a race the drawer test was losing: the link resolves while the drawer is still
re-rendering, so the node about to be clicked is replaced under it. Chromium
won that race and Safari did not, which is the kind of thing running one engine
hides. The test waits for aria-expanded to flip before reaching in, which is
the component saying it has settled rather than a sleep. Both engines install
in one step now that both gate the deploy; a failed install should look like
the infrastructure problem it is, not like a silent gap.

target-size was measuring the wrong box. A labelled control's target is the
control plus its label, because clicking the label activates it. The filter
checkboxes are 16x16 and each has a label[for] of 305x44, so judging the input
alone reported 42 failures a finger never experiences. Across eight pages at
390px that is 42 down to 4, and the 4 left are text links whose height is
their line-height, which is the grey area the criterion itself carves out.

tiny-text said "under 12px" for everything, and 459 of 520 findings on a
mobile sample were the brand's 11px uppercase eyebrow. That buried the 61 that
are genuinely smaller. Severity is split by size now: below 11px is moderate,
11px is info and named as the convention. The sub-11px text lives in preset
game HUDs and card metadata, and raising it is a design decision across every
Conduction site rather than a bug fix, so it is reported and not changed.

The colour-role lint went from 163 advisory findings to 44. 126 were brand
accent colours used as ink, which is their documented job: an accent that
followed the theme would stop being the brand. They are excused by value, in a
list, with the reasoning, and the runtime sweep reports zero contrast failures
for any of them. Two whole-file fixed shells, the cookie terminal and the error
page, take a file-level opt-out, which only counts in the header so it cannot
be dropped in halfway down to silence something inconvenient.

Every change here has a control. The labelled-target case fails without the
fix, and --strict now prints every finding instead of the first forty, because
capping the output of the show-me-everything mode defeats the point of it.

Verified: 273 of 274 at --workers=2. The one failure is planninq's hydration
test, which passes 2 of 2 in isolation and took 31.9s under contention.
development is red and this is why. #227 correctly stopped the suite posting
page views to the live collector, but it did so with route.abort(), and an
aborted request makes the browser log "Failed to load resource:
net::ERR_FAILED". space-invaders.spec.js asserts that playing produces no
console errors, so it fails all three attempts and reads as a product fault
rather than as the suite tripping over its own stub. #227's own run went red
the same way.

Fulfilling with 204 keeps the intent exactly: the beacon still never reaches
the collector. It also keeps the client on its success path, which is what
#227 wanted from leaving the settings GET alone, so a client that throws on a
real response would still fail this suite.

Proven rather than reasoned: against a page that posts to the collector route,
abort() produces one console error with exactly the string from the CI log and
fulfill 204 produces none.

I could not reproduce the failure locally. The beacon only fires when
PORTALIQ_TRAFFIC_ORIGIN is configured, which CI has and a local build does not,
so the route never matches here and both versions pass. CI is the verification
for the end-to-end case; the mechanism above is what I could establish
directly.

274 of 274 locally.
…hiding

The a11y gate carried two colours it was told to ignore: Common Ground
yellow cited as text at 1.93:1, and KNVB orange at 2.68:1 on a cobalt-50
ground. The comment said the fix was a brand decision, not an
accessibility one, so the sweep should not make it.

The decision is made, in the design system, from the kit's own rules.
Preset 3.50.2 brings it: both citation classes now read a theme-aware
token, six components that overrode the class follow it, and the cobalt
hero switches its title citations the way its tagline already did.

So the allowlist has nothing left to allow, and it is gone. That is the
point of this change rather than a side effect: with it removed, the
gate fails if either raw colour reaches text again.

Removing it turned up two live defects on the sampled pages, both a
hand-rolled version of something the kit already publishes.

  /support/ painted the eyebrow LABEL in KNVB, 2.68:1. The kit's own
  Eyebrow primitive colours the label with text-muted and lets the hex
  bullet carry the orange. Now it matches. The hex stays KNVB.

  /apps/integriq/ painted a tag in KNVB on the coral-50 tint, 2.76:1.
  tokens.css already publishes the pair: coral-700 is documented as
  "warning ink, 7.89:1 on coral-50". Now it uses it. Both locales.

Note that text-accent alone would NOT have fixed the eyebrow: coral-600
is 4.67:1 on white, which the kit says, but only 4.16:1 on the cobalt-50
panel. A colour is safe against a surface, never on its own.

Verified: 274 of 274 Playwright tests pass on chromium and mobile with
the allowlist removed. WebKit could not launch on this machine, so
mobile-safari runs in CI.

Inherited, reported, not fixed here. The full-site sweep found three
more contrast defects, none on a line this change touches:

  8 academy blog posts, figcaption on --ifm-color-emphasis-600, 2.83:1.
    text-muted reads 5.01:1 on that panel.
  /apps/decidiq/, the WITHDRAWN badge on cobalt-300, 2.69:1.
    cobalt-400 reads 4.83:1 on that ground.
  /demo/, a comment in the dark code block, 2.84:1.

Each is one line. They belong to a debt sweep, not to this branch.
feat(a11y): retire the brand-citation allowlist, and fix what it was hiding
All three were reported but not fixed in #230, as inherited debt. This
closes them. Preset 3.50.3 brings the focus-ring half of the same sweep.

Academy figcaptions, 12 declarations across 10 posts. They used Infima's
--ifm-color-emphasis-600, which is 2.83:1 on the chart panel. They take
text-muted now, 5.01:1 there.

The three SVG arrow marks in la-frankendesk keep the old grey on
purpose. That post's own caption reads "Blue marks a component that came
from an existing project", so pointing its arrows at the muted blue ink
would make the figure contradict its legend. They need a neutral darker
grey, which the kit does not publish. Left reported.

decidiq, the WITHDRAWN badge. cobalt-300 was 2.69:1 on the light panel.
It takes text-muted, which is 4.83:1 in light and 8.77:1 in dark.

Worth recording why it is the THEME-AWARE token and not cobalt-400. I
shipped cobalt-400 first, measured only against the light ground, and
verification caught it at 2.89:1 in dark: that panel's surface flips and
a fixed ink over a flipping surface is exactly the fault the colour-role
lint exists to catch. The lint did not catch this one because the value
is an inline style in MDX, which it does not parse.

/demo/ code comments. Prism's palenight writes 2.84:1 comments, and this
needed two attempts as well. A CSS rule in site.css does nothing here:
prism-react-renderer emits every token colour as an INLINE style. The
config now passes a palenight variant with only the comment token
changed, to #b6c2dd, 7.59:1 on that block and still dimmer than the code.

Verified, both themes, directly on each fixed page: 9 pages clear of
contrast-severe, and the focus check clear on the partner tiles and the
filter chips with its sample limit raised to 400, which covered every
control (71/71, 179/179, 68/68). 274 of 274 Playwright tests pass.

Left reported: brand.css sets --ifm-pre-background to cobalt-900, and
palenight paints over it, so no site renders the kit's intended
code-block colour. Retheming every code block is a design decision and
belongs in the preset, not in this fix.
design-system #90. HiddenGame's opener button hardcoded a cobalt focus
ring, which on a cobalt section is 1.00:1, so the best the indicator
managed was the halo at 1.97:1. Measured on /apps/integriq/ in light.

It was found by raising the sweep's focus sample limit from 40 to 400.
/academy/ has 179 controls, so the default had been visiting under a
quarter of them and walking past this one.

Verified on the built site, both themes, every control sampled:
/arcade/, /nl/arcade/, /apps/integriq/, /apps/openregister/, /academy/
and / are clear of focus-contrast. 274 of 274 Playwright tests pass.
…ects

fix(a11y): clear the last three contrast defects the sweep found
…neutral

Preset 3.51.1 brings the brand syntax theme (design-system #91), the
footer citation fix, and visible focus rings on the mini-game buttons
(#92). This side removes the override that would have blocked the first
of those, and fixes the one thing left in the academy post.

Code blocks. brand.css has always declared cobalt-900 with cobalt-100
code, and no site has ever rendered it: Docusaurus fell back to
palenight, which paints its own #292d3e and writes token colours as
inline styles. The palenight override added here yesterday raised one
token and is now redundant, so it is gone. Verified on the built site:
the block is background-color:#0A172F with color:#DCE3F0.

The footer citation. Nextcloud's own darker blue was 4.28:1 on the
cobalt-900 footer, just under AA, on every page of the site. It is the
cyan now, 7.33:1. That single fix removed 88 of the 94 contrast findings
across the pages that carry a code block.

The la-frankendesk arrows. They were the one thing left from the sweep,
reported in #232 as needing a colour the kit does not publish. It does:
--c-gray-500. I was wrong about that. The arrows had to stay NEUTRAL
because that chart's own caption reads "Blue marks a component that came
from an existing project", so a muted-blue arrow would have made the
figure contradict its legend. Measured against the real panel in both
themes: 4.47:1 light, 3.46:1 dark, both past the 3:1 SC 1.4.11 asks of
meaningful non-text. A mid-luminance neutral is what survives a ground
that flips.

Verified on the built site, both themes:
  22 pages with a code block: 0 blocking findings, and 0 findings whose
    colour is any of the nine syntax tokens
  arrows rgb(107,114,128), footer citation rgb(28,175,255)
  focus clear on /apps/integriq/, /academy/, /apps/dossiq/ and /arcade/
    with every control sampled (88/88, 179/179, 71/71, 59/59)
  274 of 274 Playwright tests pass

One note for the next person who touches this: a preset static-asset
change does not reach the build until node_modules/.cache is cleared.
rm -rf build .docusaurus is not enough, and the stale copy made the
footer fix look like a no-op through two clean rebuilds.
feat(code): code blocks render the kit, and the diagram arrows get a neutral
…eplaces the Codeberg SSH + tea step

The 2026-05-29 move to Codeberg was reversed (directive 2026-07-17, executed
2026-07-23). Part 2 Step 7 still taught a passphrase SSH key, keychain and tea
for a host ConductionNL no longer uses; it now sets up gh auth login over HTTPS
with gh as git's credential helper. The Codeberg guide stays linked for
Forgejo-only cases, with a fix for the leftover keychain prompt. SSH clone URLs
in parts 3, 5 and 6 become HTTPS. EN + NL.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ithub-first

docs(academy): workstation tutorial is GitHub-first — gh over HTTPS replaces the Codeberg SSH + tea step
…mages, forge and example repos

The 2026-05-29 move to Codeberg was reversed (directive 2026-07-17, executed
2026-07-23), but several tutorials still described Codeberg as the host.
Hydra part 7 now says what GitHub Actions does (the image build, to GHCR) and
does not do (stage dispatch moved to OpenRegister flows in hydra#452), with
HYDRA_FORGE as github for Conduction, forgejo only for customer instances and
no default. Hydra part 6 drops the "Codeberg numbering" note and points its
hydra.json example at ConductionNL/decidiq; build-an-app 4 and 8 use GitHub
for petstore, the release workflow and a single docs-workflow path with the
real inputs; openspec 0 and 3 point at GitHub. Big Tech part 1 keeps its
story with an update note. EN + NL.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… and name the right workflows for hydra#452

The summary's "unattended: flows" made YAML read a new mapping, so the
EN build failed on the front matter. The dispatch sentence credited
hydra#452 with removing hydra-build.yml and hydra-review.yml. Those went
in May; #452 removed hydra-stage.yml, which #448 had added the day before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ub-first

docs(academy): the remaining tutorials are GitHub-first — Hydra CI, images, forge and example repos
WilcoLouwerse and others added 9 commits September 29, 2026 11:01
… legacy

The collector still described the 2026-05 move to Codeberg: Codeberg as
the live source and GitHub as legacy, and each app's repo path preferred
the Codeberg one. The move was reversed in 2026-07, so GitHub is where
releases and new downloads happen. Swap the roles in the docstring, the
section headers, the progress output and the notes field, and prefer the
GitHub repo path. Codeberg release counts are still added to the total,
so the StatsStrip number does not drop.

The committed data/app-downloads.json fallback is updated to match.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Part 5 taught a local run through hydra-supervisor.sh, secrets/
credentials.json and logs/supervisor.log. All three are gone: the
supervisor and orchestrate.sh were replaced by the hydra-dispatch and
hydra-sequencer flows, and the local scripts read GH_TOKEN and
CLAUDE_CODE_OAUTH_TOKEN from the environment only.

Rewrite the recipe around what exists: environment credentials, a
secrets/.env with the required HYDRA_FORGE=github, one stage with
dev-run.sh, the whole chain with smoke-test.sh, and a real run started
with a label. State that a local run writes no run record, and explain
why HYDRA_LABEL_PREFIX no longer matters (the flows act on bare labels
only). The quiz follows the new content. English and Dutch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…edentials

Part 2 said the supervisor daemon queued each next stage and explained a
supervisor/orchestrator split that no longer exists. It now names the
hydra-dispatch and hydra-sequencer flows and says the shell scripts were
retired.

Part 7 described secrets/credentials.json with per-persona git tokens and
Claude token rotation. The flows reach the forge through one brokered
source that holds only a credential id, and every run has an owner so a
personal subscription only serves its owner. The credential section,
the comparison table, Model 1, the troubleshooting items and quiz
question 2 follow that.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ads-and-hydra-tutorials

Count GitHub downloads as live; bring Hydra tutorials 2, 5 and 7 up to the flows
The parts on concept, pipelines and gates still described the retired
shell pipeline:
- a ready-to-build trigger
- automatic hand-off from build to review to applier
- an applier skip rule
- a post-review quality recheck
- reviews/<round>.json
- *:running labels
- yolo auto-merge
- "22 gates"

Checked against ConductionNL/hydra flows/ (identical on main and
development):
- Part 1: build:queued plus the change named in the issue body is the
  trigger. It gives the end-to-end path (build, gates, one fix pass, a
  PR on pass and fail, review only on code-review:queued, a human
  merges) and no longer promises lead times or model env vars.
- Part 2: the build lane with its four outcomes, the review lane routed
  on hydra-verdict.json, and nothing queuing code review after a build.
  Also one unit of work per tick, the slot pool and lock, the labels no
  flow acts on, and a blocked review re-running every tick.
- Part 3: about 96 declared gates and the COVERAGE line. The runner is
  a delegator to conduction/hydra-gates. The only re-gate is after the
  build's fix pass, and the false-positive example is real gate history.

English and Dutch; the Dutch pages had drifted further and now say the
same as the English ones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ill tree

- Part 4: skill, gate and persona counts follow the repo, or are
  phrased so they do not go stale (about 96 gates, 9 personas). It says
  what the factory actually runs. The image table now says: builder
  copies .claude/skills/ only; reviewer and security carry hydra-gates
  plus 16 hydra-gate-* skills. Wrong skill descriptions are rewritten.
- Part 5: a real run starts with build:queued and the change named in
  the issue body. Code review is queued by hand after build:pass. The
  label sequence has no :running, done or merge. Image defaults are
  localhost/hydra:test. The base image is COPY --from, not FROM.
- Part 6: the recovery ladder uses the re-queue levers that work, and
  says a blocked review stage must lose its :queued label first. It
  states that retry:queued and rebuild:queued are not handled. The
  supervisor-log commands and the double-prefix example are gone. The
  hydra.json example shows what the record flow writes.
- Part 7: hydra-sequencer polls; hydra-dispatch does not. There are no
  dependency or sibling checks. The owner comes from the hermiq workload
  step; the owner:<uid> marker is dropped because nothing in hydra
  parses it. The quiz drops "22 gates".

English and Dutch (part 7 has no Dutch page).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-flows

Hydra tutorial series: describe the pipeline the flows actually run
fix(buildiq): product page shows v0.7, matching buildiq 0.7.15

This branch has not been deployed

No deployments
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.

6 participants