Code Is Not Figma

Real-time, character-level sync is correct for a vector canvas. It is wrong for a compiler's input.

"Figma for code" is an idea that sounds good at a demo and falls apart the first time two people press a key at the same moment inside the same function.

The Concern

The pitch goes: Figma revolutionized design by making the canvas multiplayer. Apply the same pattern to code. Multiple engineers typing into the same file at the same time, cursors visible, edits merging live. Dreams of a new collaborative era.

The problem is that design files and code files are not the same kind of object.

A Figma document is a scene graph of vector primitives. You can interleave any two users' edits to that scene graph and the result is still a valid scene — maybe visually cluttered, maybe aesthetically bad, but structurally coherent. A rectangle with two overlapping fills still renders. A text box with two interleaved character inserts still renders. The invariant is "the scene graph is well-formed," and CRDT algorithms preserve that invariant cheaply.

Code has a different invariant: it has to compile. And compilation is not a relaxation of "looks okay"; it is a hard predicate. A Rust file that is one brace out of balance is not a degraded version of a correct Rust file. It is not a file the compiler will accept at all. Your IDE's analyzer stops working. Your test runner refuses to start. Your teammate's language server spams error squiggles. The document is not rendering; it is broken.

This is why every attempt at Figma-style real-time code editing either (a) locks the file while someone is editing, which defeats the point, (b) accepts that the intermediate states will be broken and tells users not to worry about it, which is exactly where silent bugs live, or (c) tries to be clever about when to sync and when to buffer, which is Aura's approach — but we would not call what we do "Figma for code."

How Aura Handles It

Aura's insight is that code has a natural unit of atomicity, and it is the function, not the character.

A function in any mainstream language has a well-defined begin and end. It has a signature. It has a body. It can be parsed, type-checked, and reasoned about as a single object. Inside a function, a programmer is in a flow state, iterating rapidly, writing code that is not yet valid and that is not yet meant to be seen by others. At the boundary — when the function compiles, when the tests pass, when the programmer stops typing — the function becomes a coherent artifact that can be shared.

Aura syncs at the function boundary, not the character boundary. Specifically:

  • Aura's live sync watches your working tree and, on a debounce of roughly five seconds after the last keystroke, parses the file with tree-sitter.
  • If parsing succeeds and the function has changed, Aura emits a function-level update to the Mothership containing the full new function body, its AST hash, and its semantic identity.
  • Peers receive the update and apply it atomically. They see your function appear, not your keystrokes.
  • If parsing fails (you are mid-edit, the file is syntactically invalid), Aura does not sync. Your broken state stays local. Peers see the previous valid version.

The granularity matters. Five seconds is long enough that a developer's intermediate states stay private. It is short enough that a teammate does not wait minutes to see a change they need. And critically, the unit of sync is the function — not the line, not the character — so the thing your teammate receives is always a coherent piece of code, not a syntactically broken intermediate.

A concrete comparison

Imagine you and a teammate are both editing this Rust function:

fn compute_tax(amount: Decimal, rate: Decimal) -> Decimal {
    amount * rate
}

You change it to include rounding. Your teammate, simultaneously, changes it to accept a region parameter.

Character-level CRDT sync, applied naively, would try to interleave your keystrokes and theirs. Likely intermediate states on both sides:

fn compute_tax(amount: Decimal, rate: Decimalregion: Region,) -> Decimal {
    amount * rate.round_dp
}

That is not valid Rust. It does not parse. Your LSP is broken. Your cargo check fails. If you were not paying close attention, you might think your own edit is broken and start debugging your own code. This is a silent-conflict-shaped bug with extra steps.

Function-level sync, which is what Aura does, treats each of your edits as a candidate new version of compute_tax. Both are syntactically complete Rust functions when you each stop typing. Aura detects that both sides changed the same function and raises a conflict. A human (or the agent with permission) picks which version wins, or authors a merge that incorporates both concerns. The intermediate state is never broadcast, because the intermediate state is nobody else's business.

Why five seconds

The debounce window is a tuning parameter, not a theorem. We landed on five seconds after internal testing across Rust, TypeScript, and Python codebases. Too short and you generate parse-failure churn during normal typing. Too long and teammates feel the lag. Five seconds is long enough for a programmer to pause, short enough to feel live. It is configurable per-project via aura config set live.debounce_ms 5000.

At the Mothership, function updates are compacted: if you saved ten versions of the same function in the last minute, peers who were offline receive only the final one. History is preserved locally for rewind but not shipped over the wire.

The Figma Analogy, Carefully

Figma's UI — presence indicators, cursor positions, who-is-editing-what — is legitimately useful for code too. Aura borrows that vibe. In Sentinel you can see which AI agents and humans are currently touching which functions. You can claim a zone and signal to teammates that a file is yours for the next hour. That ambient-awareness layer of Figma translates directly.

What does not translate is Figma's mechanism. Figma uses a CRDT over the scene graph. Aura uses function-level atomic updates over an AST. Different problems, different answers.

If you hear someone describe Aura as "Figma for code," gently correct them. Aura is "multiplayer presence with function-level atomic sync." It is longer and less fun but it is what the tool actually does.

What Aura Does Not Solve

Function-level sync has its own limits, and we want to name them.

Very large functions. If a single function is 500 lines and two people edit different halves, Aura still treats the whole function as the unit of conflict. You will get a same-function conflict even though the textual overlap is zero. Our recommendation is the same as any senior engineer's: break up 500-line functions. But we acknowledge that this is an Aura limitation on codebases that do not follow that advice.

Languages with weak function boundaries. Some languages — heavy Lisp-like macros, shell scripts, embedded DSLs inside string literals — do not have a clean "function" unit that tree-sitter can identify reliably. Aura falls back to file-level sync in those cases, which is less precise. See scale limits and research.

Truly real-time pair programming. If you and a colleague want to see each other's keystrokes as they happen, Aura is not that tool. Use Live Share, CodeTogether, or tmate for that workflow. Aura is for asynchronous and near-synchronous collaboration, not for two people sharing a cursor.

Cosmetic changes below the function level. If you reformat a function (whitespace only), Aura detects that the AST is unchanged and suppresses the sync. This is correct for most cases. It is occasionally surprising if your team cares about formatting as a first-class concern — a pre-commit formatter is still the right solution.

The Honest Tradeoff

The five-second debounce means Aura is "near real-time," not real-time. A teammate watching your function update will see it roughly five seconds after you stop typing, not as you type. We chose that latency explicitly, because the alternative is broadcasting broken code, and broken code is worse than a small delay. Users coming from Figma expect zero latency and sometimes flinch at five seconds. Once they understand why, they stop flinching.

The other tradeoff: Aura requires a working tree-sitter grammar for your language. Not every language has equally good grammar coverage. Rust, TypeScript, Python, Go, Java, and JavaScript are well supported. C++ with heavy template metaprogramming, older Fortran, and hand-rolled DSLs are not. In those cases, Aura either falls back gracefully (file-level) or declines to manage the file and lets Git do its job.

See Also