Capsule
Staff Software Engineer
I re-built Capsule CRM as a Single Page Application, contributing to the open source community along the way.
This happened in parallel with development on the existing product, and so, for a time there were two versions of Capsule running. The technical challenge was to ensure the customer couldn't tell. And to reduce the boot time of the SPA as much as possible as the user may transition between the two.
Read More-
The year of AI
Contributed to the frontend implementation of multiple new features leveraging LLMs: AI Summaries, AI Email assist, AI Meeting prep and AI pipeline generator.
Lower costs
Replaced an expensive backend service with client-side SVG generation. With a pseudo-random colour palette, giving colourful, stable avatars instantly with zero network cost.
Pins
Built a feature letting users surface key information by pinning activities across multiple entity types, with carefully wired bidirectional relationships to keep the data graph consistent & giving instant reactive UI updates
-
Calendar features
Refactored the calendar to support multi-day events, working out the layout logic to keep a spanning event in a consistent row across all its days, sized correctly across grid cells, and split cleanly where it runs into the next week.
Ember moves to Vite
Converted 13 internal Ember addons to the new v2 (Vite) format
-
Discussions
I built a new Discussion UX, with comments, @mentions, notification center and a new floating formatting toolbar utilising my past experience with ProseMirror. I also added bi-directional support to the infinite scroll library.
-
Plans & Payments
Led the implementation of the upgrade/downgrade flow and new upsell features. Accounting for legacy plan states, user quota violations, and ensuring all features, UI components, and state is correct after a plan change.
Embroider
Coordinated a large migration off Broccoli builds to Embroider (Webpack), overcoming challenges around dynamic assets and component lookups by implementing statically analyzable alternatives.
-
Internationalisation
Started work on internationalising the codebase (~8k strings). I came up with a solution for translating strings with components mid-sentence. Put error handling, tests, linting and logging in place to improve visibility of missing dynamic strings.
Workflow Automations
Delivered the Workflow Automations feature: trigger-based rules for sales pipelines, with nested triggers and actions. It supported reordering with a custom drag/drop solution using the native browser API. Introduced the app's first auto-save pattern with an optimistic UI.
-
ProseMirror
Adopted ProseMirror after evaluating alternatives, and led the creation of a rich text email editor. I designed the document schema, and implemented the state management, plugins, keymaps, input rules, etc. Then integrated it into the Ember ecosystem and shipped the subject, body, toolbar, hyperlink editor, attachments, templates, signatures and very thorough test suite.
The Write with AI feature was added a few years later. -
Multiple Sales Pipelines
I built the front end to allow users to migrate from a single Sales Pipeline to multiple Sales Pipelines. It guided them to fix pre-migration issues, showed a preview of the resulting configuration, and then ran the migration as an async job with polling and progress feedback.
Mixins
Migrated shared route logic from mixins to services, overcoming tight coupling and inheritance constraints to enable native-class compatibility in Ember Octane.
-
Glimmerisation
Migrated away from
Ember.Objectand observers to native classes with@trackedproperties, making behaviour explicit, maintainable, and predictable.File downloads
Implemented a feature to intercepts file-downloads - allowing trusted files through without friction, and routing higher-risk files through a confirmation step so users can make an informed choice before proceeding.
-
Flexible Pagination
Designed and implemented a reusable pagination abstraction, driven by Link headers, decoupling pagination logic from both the UI and Ember Data models, enabling use across arbitrary arrays.
Tooltips
Developed the @zestia/ember-async-tooltips Ember addon, implementing a technique to reduce perceived latency in lazy-loaded tooltips by accounting for data-fetch time within the show-animation delay. Focused on accessibility, keyboard/focus support, and sticky behavior for reliable contextual guidance.
-
List filtering
I led the rebuild of a legacy JSF list-filtering feature, delivering a modern UI that supports complex query composition with multiple conditions, grouped AND/OR logic, and operators across text, numeric, date, boolean, money, and custom-field data types. I also implemented filter serialisation/deserialisation for local storage and resilient restore logic to handle stale filters.
Tech health
Configured parallel test execution for faster runs, replaced PhantomJS with Headless Chrome, and migrated dependencies from Bower to npm.
-
Faux select box
I created a highly customisable select-box component with a deliberately small API surface. It follows WAI-ARIA practices, supports full keyboard and screen reader interaction, and was designed to be easily replaceable if a native browser solution emerged.
It's backed by a comprehensive test suite - because select boxes are deceptively complex. Keyboard interaction, focus management, ARIA state, async rendering edge cases, and cross-browser quirks all make this a high-risk UI primitive.
The select box can be a basic listbox, or a combobox, either with a trigger, an input, or both. Styleguide
I introduced a styleguide built as an Ember Engine - a self-contained, mountable mini-app that could be excluded from production builds. In 2026, similar isolation is achieved using modern Embroider techniques.
It wasn't just documentation - it was a sandbox encouraging developers to build components outside the main app. This small mindset shift fostered reusable abstractions, reinforced good architectural practices, and reduced accidental coupling.
-
Performance dashboard
Started capturing and sending performance metrics about route loading - First Paint to Full Render, and capturing slow components. Sending the data to a NewRelic dashboard.
Tech health
Upgraded Ember.js to version 2. The way user interactions changed in Ember, which involved a sweeping refactor from strings to closures. I also phased out CoffeeScript.
Integrations
Continued migrating the product to a SPA. Focusing on integrations with third parties like Zendesk, Sage, FreeAgent, Mailchimp, Xero, and Quickbooks etc.
-
Smooth rollout technique
Ported the most popular page to an Ember.js app. Users would enter the SPA when clicking to view a contact only. But would otherwise remain on the traditional MPA. Refreshing the page would load the old MPA - giving users an escape hatch if there was a problem with SPA.
Route gating
Improved how the Ember app was being rolled out by updating our Link component, such that it would route you within in the SPA or turf you out to the MPA version, based on a feature flag. Wrapped with tests so that unfinished pages didn't accidentally go into production.
Tackling performance issues
Due to component instantiation being expensive in Ember (we're talking version 1.13.2!), partials were sometimes used instead (For example when rendering long lists or tables). This was harder to maintain, but we knew they could be migrated over time as Ember performance improved.
For users who fell into the feature flag segment, the SPA app was prefetched on the MPA login screen. So upon signing in the app would already be loaded.