You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The four Pearson certifications are the credentials a visitor is actually scanning for, and today the directory draws them exactly like a Skilljar course — same line, same mark, same weight — and offers no way to filter on them. With 24 catalogue entries and most of them courses, the strongest signal in the column is the easiest one to miss.
The four, from Anthropic's Pearson VUE page, are already in the catalogue as kind = 'certification', platform = 'Pearson VUE' — see supabase/seed/credential-catalogue.json:
Claude Certified Associate — Foundations (CCAO-F)
Claude Certified Architect — Foundations (CCAR-F)
Claude Certified Architect — Professional (CCAR-P)
Claude Certified Developer — Foundations (CCDV-F)
So nothing here needs a migration or a new column. The axis already exists; it is just not being read.
Precedence keys off kind, never off the platform or the label
An exam is a different kind of claim from a course, and kind is the schema's name for that — 20260821… split source into kind and platform for exactly this reason (#103). Sorting on platform = 'Pearson VUE' or on a hardcoded list of four labels would work today and rot the moment a certification is examined somewhere else, or a fifth one ships. Write the rule as kind = 'certification' sorts first; that the four Pearson exams are the whole of that set right now is a fact about the catalogue, not something the code should know.
The ordering that exists today is the opposite one, deliberately
byCatalogueOrder in src/lib/practitioners.ts:102 puts courses before certifications, and its docstring says why: on the profile page the list is a track somebody works through, so the Academy courses lead into the exams. That reading is right for the profile page and wrong for the roster, where the visitor is comparing people rather than reading one person's path.
So this is not "fix the comparator" — it is deciding whether the two surfaces get two orders. Recommendation: they do. Keep byCatalogueOrder as-is for the profile page's earned/not-earned pair, and add a second comparator for the roster column that leads with certifications. Both live in src/lib/practitioners.ts beside each other with the difference stated once, so the next reader sees two orders on purpose rather than a bug in one of them.
What to do
Add the roster comparator to src/lib/practitioners.ts — certifications first, then the existing sortOrder/label tiebreak — with a unit test in practitioners.test.ts pinning that a course never precedes a certification.
Draw a certification differently in the roster row and on the profile page. It is a third visual distinction on a line that already carries two states (CredentialMark: verified tick vs earned dot), so it has to be a different channel from those, distinguishable without colour, and it must not read as a second Bluehex attestation. credentialSource already prints "Claude Certification" under the label; whether that line is the highlight or is replaced by something stronger is the design call.
Add a Certifications filter group to src/components/practitioner-directory.tsx, chips drawn from the catalogue the same way Services already is: one chip per certification, and only for certifications somebody in the roster actually holds — a chip that can only return nothing is worse than no chip, which is the rule offered and anyBadged already follow.
Feed it from listCredentialCatalogue in src/lib/directory.ts, which already exists and is already read by the profile page. src/app/page.tsx:84 fetches serviceCatalogue and passes it down because the directory is a client component; add the certification list to that same Promise.all and prop.
Match a chip on the catalogue entry's id, not its label. Services match by label because a custom service can arrive as free text; a credential cannot — it always references a catalogue row, so the id is available and is the correct key.
Decisions this ticket needs
Does the filter mean "holds it" or "holds it, verified"? "Verified only" is already its own chip and composes with everything else, so the recommendation is that a certification chip means holds it and the visitor stacks the two. Worth stating explicitly wherever it lands, because the opposite reading is the one people will assume.
How the highlight is drawn. See point 2 — the constraint is that it must not be mistakable for the Verified badge, which is the only thing on the page that means Bluehex checked something.
Done when
A profile holding a certification and several courses lists the certification first in the roster column, and the profile page's own ordering is unchanged.
A certification is visibly a different kind of thing from a course at a glance, in greyscale.
The Certifications chips filter the roster, show only certifications somebody holds, and clear with Clear all.
pnpm lint and pnpm test pass; pnpm test:e2e if the roster markup moved.
Not in scope
Capping how many credentials a row shows, and the show-more toggle — that is its own ticket, and it depends on this one landing first: a cap is only safe once the order guarantees the strongest credential is above the fold.
The four Pearson certifications are the credentials a visitor is actually scanning for, and today the directory draws them exactly like a Skilljar course — same line, same mark, same weight — and offers no way to filter on them. With 24 catalogue entries and most of them courses, the strongest signal in the column is the easiest one to miss.
The four, from Anthropic's Pearson VUE page, are already in the catalogue as
kind = 'certification',platform = 'Pearson VUE'— seesupabase/seed/credential-catalogue.json:So nothing here needs a migration or a new column. The axis already exists; it is just not being read.
Precedence keys off
kind, never off the platform or the labelAn exam is a different kind of claim from a course, and
kindis the schema's name for that —20260821…splitsourceintokindandplatformfor exactly this reason (#103). Sorting onplatform = 'Pearson VUE'or on a hardcoded list of four labels would work today and rot the moment a certification is examined somewhere else, or a fifth one ships. Write the rule askind = 'certification'sorts first; that the four Pearson exams are the whole of that set right now is a fact about the catalogue, not something the code should know.The ordering that exists today is the opposite one, deliberately
byCatalogueOrderinsrc/lib/practitioners.ts:102puts courses before certifications, and its docstring says why: on the profile page the list is a track somebody works through, so the Academy courses lead into the exams. That reading is right for the profile page and wrong for the roster, where the visitor is comparing people rather than reading one person's path.So this is not "fix the comparator" — it is deciding whether the two surfaces get two orders. Recommendation: they do. Keep
byCatalogueOrderas-is for the profile page's earned/not-earned pair, and add a second comparator for the roster column that leads with certifications. Both live insrc/lib/practitioners.tsbeside each other with the difference stated once, so the next reader sees two orders on purpose rather than a bug in one of them.What to do
src/lib/practitioners.ts— certifications first, then the existingsortOrder/label tiebreak — with a unit test inpractitioners.test.tspinning that a course never precedes a certification.CredentialMark: verified tick vs earned dot), so it has to be a different channel from those, distinguishable without colour, and it must not read as a second Bluehex attestation.credentialSourcealready prints "Claude Certification" under the label; whether that line is the highlight or is replaced by something stronger is the design call.src/components/practitioner-directory.tsx, chips drawn from the catalogue the same way Services already is: one chip per certification, and only for certifications somebody in the roster actually holds — a chip that can only return nothing is worse than no chip, which is the ruleofferedandanyBadgedalready follow.listCredentialCatalogueinsrc/lib/directory.ts, which already exists and is already read by the profile page.src/app/page.tsx:84fetchesserviceCatalogueand passes it down because the directory is a client component; add the certification list to that samePromise.alland prop.Decisions this ticket needs
Done when
pnpm lintandpnpm testpass;pnpm test:e2eif the roster markup moved.Not in scope
supabase/seed.sqltoday and this ticket does not change that.kindandplatformalready carry it.