Back to CV

Selected Project

Damus

Long-term product engineering and code maintenance work across iOS features, backend infrastructure, and app reliability.

Damus is a Twitter-like open-source Nostr client and one of the most prominent consumer apps in the Nostr ecosystem. In my part-time contract position, I helped that company build and maintain features across both the app itself and supporting backend systems.

My work spanned user-facing product features, StoreKit payment and subscription flows, push notification infrastructure, large app architecture refactors, performance improvements, and a large number of bug fixes and reliability improvements. Because the project is open source, a significant portion of this work is publicly visible through my contributions in relevant repositories.

Damus app card

Bridging Swift with LMDB-backed C code during a large architecture refactor

I helped the company integrate their custom-made embedded LMDB-backed database with the rest of the iOS app. This performance-optimized database made use of memory-mapping to FlatBuffer objects — which meant it did not need memory copying or a deserialization step for its structured data to be accessed, enabling much faster data access speeds.

However, integrating this with a Swift codebase brought several challenges — some of them discovered through deep debugging of crash reports during the migration phase. Here are a few:

  • The memory behind items fetched from the database was unowned — its lifetime was tied to its corresponding DB reader transaction object. This does not naturally work well with Swift's memory management, where the lifetime of variables is managed through automatic reference counting — which makes using normal Swift interfaces very error-prone.
  • Holding those reader transaction objects open for an extended time (e.g., by storing those variables in a SwiftUI view) could cause large storage usage growth as well as outdated query responses in other parts of the program, which was also error-prone.
  • To avoid deadlocks associated with nested queries (which are hard to avoid or check at compile-time), reader transactions could be inherited if one was already open on the same thread, via thread-state dictionaries. Later I found that reader transactions are dangerous to use in async functions, as Swift does not guarantee that a function will resume on the same thread after an async suspension point. This was a third factor that made using this database even more error-prone in Swift code.
  • There were several scattered portions of the codebase which were previously getting their data from the network through a different interface from the one provided by the database. All of those would need to be migrated while trying to keep regression risk low.

To address these challenges, I made use of new and advanced Swift 6 language features to perform the refactor in a cost-effective way, while reducing regression risks, and increasing overall interface safety:

  • The transaction object types were scoped to only the Swift database wrapper module, to minimize the risk of misuse of such objects and instead provide a safer interface to the layers above it.
  • Types related to those unowned database objects were migrated to use Swift's new ~Copyable protocol, and the functions in the Swift database wrapper module were refactored to only borrow those objects to their callers via Swift's new `borrowing` parameter ownership modifier. Together these ensured safety to the layers above it, by introducing compile-time checks that prevented those three issues related to transaction lifetime and async concerns mentioned above.
  • To minimize logical regression risk during such a large refactor, I built a universal interface that would work well when fetching data from either the network or the database. That universal interface was built using Swift's AsyncStream, which models Nostr relay interactions very naturally. This allowed the migration to be done in phases, and to minimize the amount of logical changes needed for each file.

Fixing a subtle mention-editing bug

In commit dcb9463, I fixed a text-editing issue where typing punctuation directly after an attributed user mention would incorrectly extend or corrupt the mention link. The solution required understanding how UITextView, UITextInput, attributed text ranges, and delegate callbacks interact during edge-case edits.

I handled the problematic edits manually in the SDK's textView(_:shouldChangeTextIn:replacementText:), removed link attributes when necessary, updated cursor placement explicitly, and added focused regression tests covering insertion, deletion, overlapping links, and edge-of-link cursor behavior.

Original root-cause technical report
Code commit

Implementing a new thread experience

In the beginning, the thread experience on the app was a bit confusing — it was not always clear to users how a thread was structured, which made it difficult to navigate or make sense of them. Therefore, I was asked to help redesign the experience.

A designer contributor provided a great initial UI design mockup using chat bubbles — which provided a great reference for how it should look and feel. However, the mockup did not answer an important design question that came from a technicality in the underlying Nostr protocol.

In the Nostr protocol, threads are structured like arbitrarily deep hierarchical trees. Due to interoperability reasons, this structure was out of our control (we could not simplify it). That deeply nested tree needed to properly fit into a small mobile screen and have the same linear look and feel as the designer's mockup. Therefore:

  • Reddit-style (or email-style) indentation blocks could not be used.
  • A completely linear chat history (similar to WhatsApp) would not solve the problem. That pattern might be sufficient in smaller chat groups, but they are insufficient in large public social networks due to the amount of parallel conversations that can happen in some cases. Some level of hierarchy and filtering needed to be present.

I solved this remaining design question by providing a hybrid approach, where deeply hierarchical thread trees could become linear just like WhatsApp, but with an option to easily switch focus to different portions of the conversation and momentarily hide irrelevant messages.

Threads can be viewed in their entirety at a glance, with chat bubbles.
It is easy to jump between replies.
When there are too many parallel conversations, the user can select and focus on a specific part of the thread, hiding all other irrelevant messages in an intuitive morphing transition.

This is a good example of why I believe having both software engineering knowledge and a sense of UX can be very useful. Being able to bridge the gap between the two disciplines can sometimes lead to new innovative solutions, and make collaboration between UX designers and developers smoother.

Damus is a good example of the kind of work I can do: I can contribute broadly across a stack, while also digging into the hard detailed technical parts underneath it. On the same project, I contributed across mobile UX, infrastructure, data storage, diagnostics, subscription flows, and protocol-adjacent engineering. The work involved real users (thousands of them), real production constraints, evolving protocols and interoperability with other systems, dealing with pre-existing legacy code, working with internal and external collaborators, and a codebase where reliability and iteration speed both mattered.