Skip to content

Certifications outrank courses, and they should be filterable #130

Description

@davidtaing

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  • Where the credential catalogue is permanently housedWhere the credential catalogue is permanently housed #95. The four rows arrive through supabase/seed.sql today and this ticket does not change that.
  • Any new column. kind and platform already carry it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: uiReact routes, components, stylingenhancementNew feature or requesthitlNeeds a human (decision/design/review)size: MA weekend for someone new to this codebase

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions