Four years, regulated platforms, the features other people route around.
Across four years for a global healthcare company (via a consultancy) I delivered the complex frontend on regulated, AEM-based platforms: a patient-facing clinical trial finder; a regulated medical information portal handling legally mandated safety reporting; and a strangler-fig modernisation of a scientific content platform. Delivery-heavy contracting in established, compliance-bound codebases, where the hard part is the constraints, not the framework. Each is written up as its own case study below.
·
Three deep-dive case studies from this engagement
The platforms below get their own full write-up. What follows after them is more from the same four years: the search architecture, the accessibility work, and the analytics framework that don't fit neatly into any one of the three.
Clinical trial finder
A Solr-indexed trial registry with a dual pending/committed filter model serving four search entry points, a six-screen guided wizard, and a from-scratch screen-reader fix for an unopenable result-card menu.
HCP platform · safety reportingRegulated forms platform
A markup-driven form engine for legally mandated safety reports, plus a from-scratch accessible combobox and ARIA grid date picker where the component library provided nothing.
Content platform · strangler figDesign system migration
A 37-component Lit 3 library running alongside a legacy HTL/Handlebars/Vue 2/jQuery stack, bridged by typed CustomEvent contracts, plus a pipeline that compiles real AEM templates inside Storybook.
Search across three contexts that didn't want to agree
On the HCP medical-information portal I owned search and discovery: a Solr-backed single-page search with faceted filtering across publications, clinical trials, congress programmes and medical information. The catch was that three separate search contexts shared one navigation UI, each with its own result counts and parameters. I reconciled them through a redesigned Vuex state with deep-linkable parameters, so a filtered search is a shareable URL and the counts stay honest as a user moves between contexts.
count clinical trials
count congresses
count
A strict CSP that broke Vue's compiler
The adverse-event reporting system ran under an enterprise Content Security Policy that banned eval and
new Function, which is exactly how Vue's default build compiles templates at runtime.
The app simply wouldn't run. I refactored it to the runtime-only build with regex-based
label handling and webpack code-splitting, and migrated the whole system Vue 2 to Vue 3
in the same effort, with zero functionality lost. This is the kind of constraint that doesn't show up in
a job spec but decides whether the thing ships.
Why these are the interesting ones: none of them are "build a component". They're a search domain with three masters, a security policy at war with the framework, and an accessibility bar set by a regulator. The framework is the easy part.
An accessible mirror of a component I couldn't replace
The patient platform used a third-party Select2 control that announced blank or duplicated entries in NVDA — a real barrier on a patient-facing healthcare product, and one I couldn't fix by swapping the library out. So I built a parallel, ARIA-correct listbox that mirrored the Select2 state plus a live region to announce changes properly. The visible control stayed; the accessibility tree became correct underneath it.
Authenticated bookmarking on a legacy store
Patients needed to save clinical trials across sessions. I wired the platform's legacy Vuex store to AWS DynamoDB and Cognito, with event-driven state sync so a bookmark made on one page is consistent everywhere without a full reload. Dual-branded across the parent and a US partner brand, multi-country, so the same flow had to behave under several configurations.
oneDataLayer: analytics without per-component JavaScript
The global pharmaceutical client's global clinical-trials platform needed consistent analytics across page views, clicks, scroll depth, search and OneTrust consent. Rather than scatter tracking through every component, I built oneDataLayer: a single TypeScript class that AEM components opt into via one data attribute. No per-component JS, a flat client footprint at scale, and one place to reason about consent. It's my go-to architecture-decision story for a reason: it traded a thousand small integrations for one contract.
- Vue 2 / Vue 3
- Pinia / Vuex
- Vuetify 3
- Lit atoms
- TypeScript
- AEM
- Solr
- AWS DynamoDB
- AWS Cognito
- OneTrust
- WCAG 2.2 AA
- NVDA / VoiceOver
- axe-core
- GitLab CI
- reCAPTCHA v2
Accessibility you can defend to a regulator
I led WCAG remediation across the three platforms ahead of the June 2025 EU Accessibility Act deadline,
as the primary hands-on engineer working alongside an external accessibility auditor. That meant reusable modal
focus-traps, a WAI-ARIA combobox built from scratch with dual live regions and managed
aria-activedescendant, NVDA-specific fixes, and skip links, validated against real
screen readers, not just lint rules. It's one credential among several here, but it's the one with a
legal deadline attached.
Back to selected work · Read: the clinical trial finder · See: the enterprise SaaS codegen pipeline · Talk to me about a similar project