Tana: turn meeting decisions into actions you can approve and track
Tana is an AI meeting platform for teams, connecting calls, transcripts, documents and tasks. Discussions can become proposed actions that you approve, send to connected work tools and follow through to their results.
Break down the problem and try your own design and implementation first. Then explore the reasoning to check states, edge cases, trade-offs, and ways to test your approach. The guide offers a reasoning path, not a complete implementation.
How should a three-way merge preserve both a teammate’s and AI’s changes?
Scenario
While AI proposes changes from an old document, a teammate may edit its current version. Design a pure in-memory three-way merge analyzer using base, current and proposed. Preserve independent changes and identify fields requiring a human decision. It creates a merge suggestion only: it does not save documents or call external tools. This is a teaching protocol, not Tana's internal implementation.
Inputs and decisions
- base, current and proposed are JS Maps, each with at most 100,000 entries. Keys must be nonempty strings. Values may only be strings, safe integers, booleans or null. Reject NaN, Infinity, objects, arrays, undefined and all other values. All three Maps and values are read-only.
- An absent key differs from a present key whose value is null. Compare existence first; if both exist, use strict equality for values. Numeric 0 and -0 are equal; false and 0 differ. There is no nested-object or array merge, field rename or inline text merge.
- For each key in the three inputs: if current and proposed agree, keep that state. Otherwise, if current equals base, adopt proposed. Otherwise, if proposed equals base, keep current. If none applies, report a conflict. Deletion participates as the absent state.
- Visit keys in base order, then new keys in current order, then new keys in proposed order, with each key occurring once. With no conflicts, output mergedEntries and changes relative to current. Changes are set(key,value) or delete(key), in the same stable order. Do not include finally absent keys in mergedEntries.
- If any conflict exists, output only all conflicts, including each version's existence and value; output neither mergedEntries nor changes. For invalid input, return a version/field-located error and no conflict list. Validate all three Maps before merge analysis. Empty Maps are valid.
- Let n be the total number of entries and L the total character length of input keys and string values. Every valid key, including
__proto__, is an ordinary ID. Use entry arrays or a safe container for output.
Small example
base, in order: title=Old title, owner=Lin, priority=2, note=null. current, in order: title=Old title, owner=Mei, priority=2, note=null, tag=web. proposed, in order: title=New title, owner=Lin, priority=1; note and tag are absent.
Give mergedEntries and changes. Explain why the teammate's new tag survives, note is deleted, and AI's old owner value does not overwrite the teammate's change.
Check independent variants: (a) proposed.owner becomes Zhou; (b) current.note becomes “Call back” while proposed still deletes note; (c) current.title is also New title; (d) any value is NaN. Distinguish merges, conflicts and invalid input.
Deliverables
Describe state representation, complete validation, stable key order, field decisions and all-or-nothing output. State at least four invariants and analyze time and space using n and L. You may discuss JS Map or optional values in Rust; provide no complete code or line-by-line pseudocode.
Explore the reasoning
- Represent absence separately. Use Map.has before reading a value. Neither null nor undefined should stand in for an absent key. Validate every key and value in all inputs; invalid input takes precedence over conflict analysis.
- Build a stable key union. Scan base, current and proposed in order, recording seen keys in a set and retaining first occurrence. Do not place user keys into ordinary object properties, so
__proto__has no special path. - Separate agreement, one-sided change and conflict. If current and proposed agree, there is no disagreement. Adopt AI's version when only AI changed it; keep the teammate's version when only the teammate changed it. Different changes by both sides need a decision. Treating absence as a state applies the same rules to additions, deletions and null.
- Compute changes against current; do not return a partial merge on conflict. The main result is title=New title, owner=Mei, priority=1, tag=web. Changes, in order, are set title, set priority, delete note. tag is absent in base and proposed, so it is the teammate's addition. Variant a conflicts on owner; b on note; c still merges but changes no longer includes title; d is an input error with neither conflicts nor partial results.
- Check invariants and cost. If current=proposed, changes is empty; if proposed=base, retain current; if current=base, adopt proposed. Any conflict suppresses partial merged output. Inputs remain unchanged and order remains stable. With average Map/set behavior, there are O(n) structural operations and only a constant number of state/value comparisons per field. Counting character work in L gives expected O(n+L) time and O(n+L) space. A conflict list does not authorize overwriting later concurrent changes; actual saving needs a separate version check.