Solo Project · Unity & Unreal Engine Tooling · C# · C++

Design Notes

An editor tool I designed and built for scene-anchored design feedback — sticky notes placed directly in a level, with a Kanban board, and two-way sync to Trello and Jira that actually keeps both sides current. Built first for Unity, then rebuilt natively for Unreal Engine 5.7 as a C++ editor plugin.

Unity Editor Scripting IMGUI C# Unreal Engine 5.7 C++ Slate REST APIs
View on Unity Asset Store Read the Docs
What

Scene-anchored design notes with a List and Kanban board

Sync

Two-way Trello & Jira integrations

Engines

Unity (C#) and Unreal 5.7 (C++)

Role

Solo: design and implementation

About the Project

An editor tool for scene-anchored design feedback - bugs, art notes, TODOs pinned to the exact spot in a level they're about, browsable as a filterable list or a full drag-and-drop Kanban board, with optional two-way sync to Trello and Jira that goes both directions for real. Available for Unity, with a native C++ port for Unreal Engine 5.7.


Key Features

Challenges I Solved

A Main-Thread Freeze From an External Process

Typing in a note's text field was severely laggy for a reason that had nothing to do with typing - the Inspector's "last touched" Git info was spawning a fresh git log subprocess and synchronously blocking the main thread on every single repaint, and IMGUI repaints repeatedly while a text field has focus. I fixed it by caching the result per scene for 30 seconds, which eliminated the per-keystroke process spawn entirely.

Getting the Jira Mental Model Right

I initially mapped a note's Category to Jira's issue Type (Bug/Story/Task), copying the same shape as Trello's Category-to-List mapping without checking whether that was actually the right analog. It wasn't - real testing against a live Jira project showed Kanban columns never matched what a Jira user actually saw on their own board, because the thing that behaves like a column is Status, not Type. Correcting it meant integrating with Jira's separate Transitions API, since status changes aren't a normal field update there.

A Silent Data-Loss Bug in JSON Serialization

Pushing a note with no due date or assignee to Jira was failing with confusing validation errors. Unity's JsonUtility doesn't reliably omit null fields when serializing a class - a null string becomes an empty string rather than disappearing - so an unset field was being sent as an actual (invalid) value instead of being left out. The fix was building the request body manually rather than trusting JsonUtility with optional fields.

Reentrancy in Unity's IMGUI

Several confirmation dialogs were being shown synchronously from inside an open layout group mid-repaint, which is a real source of intermittent "Mismatched LayoutGroup" errors in Unity's Editor GUI system. I deferred those dialogs to run after the current layout pass completes instead, rather than mid-frame.


What I Learned

Code Deep Dive

I built Design Notes around one core idea: a note should be a real object in the scene, not an entry in a separate list disconnected from the level it's about. Everything else — the Kanban board, the integrations, the sync logic — is built on top of that one decision. Then I rebuilt it from scratch for Unreal, where the same idea had to be expressed through completely different engine systems.

1. Scene-Anchored Notes as Real GameObjects

A note is a MonoBehaviour on a real GameObject, not an entry in some external database. It's saved with the scene, visible to the whole team the moment they pull it, and shows up as a custom-rendered world-space sticky note via SceneView callbacks — billboarded toward the camera, colour-coded by Category and Priority, with a small minimap for navigating large scenes. I built a plain-label fallback style too, since not everyone wants the sticky-note skin.

Scene View Rendering

2. A Reflection-Based Extension Point for Integrations

Trello and Jira aren't hardcoded into the core tool — they implement a small IDesignNoteIntegration interface, discovered automatically by scanning every loaded assembly for implementors via AppDomain.CurrentDomain.GetAssemblies(). A third-party integration someone writes themselves needs zero registration anywhere in the core package - it just shows up. I cached the discovery after the first scan, since reflecting across every assembly is expensive enough that doing it on every Editor repaint would have been its own performance problem.

Integration Discovery

3. Two-Way Sync Without Hammering Either API

Editing a linked note's text, category, or due date pushes the change back to Trello or Jira automatically — but debounced roughly 1.5 seconds after you stop typing, so it's one request per pause in editing rather than one per keystroke. On the Jira side, a note's Category maps to the issue's Status (To Do / In Progress / Done) rather than its Type, since Status is what actually behaves like a Kanban column - and changing it needs a separate call to Jira's Transitions endpoint rather than a normal field update, since only certain transitions are valid from a given current status.

Debounced Auto-Sync

4. Finding and Fixing a Main-Thread Freeze

Adding two-way comment sync introduced a real regression I had to diagnose after the fact: checking every linked note for new comments made a blocking HTTP call per note, all inside the same EditorApplication.update tick the poll timer fires on - so the Editor visibly froze, scaling with however many notes were linked. I fixed it with a rotating cursor that checks only a small, fixed batch of notes per poll tick instead of all of them at once, picking up where it left off next time. Every note still gets checked eventually, just spread across several poll cycles instead of one synchronous burst.

Rotating Poll Batch

[ close ] ❮ ❯ Screenshot