novasia-estate/property-review-merge/1.0.1

Property.ReviewMerge

Three-way merge for independently edited offline property inspection notes. No timestamps, last-writer-wins rule or automatic persistence are used.

Input

type alias Row =
{ id : String
, fields : List Field
}

A stable record ID with an explicitly authored list of fields.

type alias Field =
{ key : String
, value : String
}

One exact, case-sensitive field key and its unnormalized text value.

type alias Issue =
{ row : Int
, code : String
}

Fixed validation code and 1-based row number; 0 means a snapshot-level issue.

Validated immutable snapshot. Construct it with validate.

validate : List Row -> Result (List Issue) Snapshot

Validate at most 200 rows, 30 fields/row, 64-character keys and 2,000-character values. IDs are 1–64 ASCII letters/digits/dot/underscore/dash. Empty snapshots are valid. Duplicate record IDs or field keys are rejected before constructing dictionaries.

toRows : Snapshot -> List Row

Deterministic record/key order; values and Unicode remain unchanged.

Merge and deliberate resolution

type Plan

Opaque merge plan. Export is possible only through finalize.

= FieldConflict
{ recordId : String
, field : String
, base : Maybe String
, left : Maybe String
, right : Maybe String
}
| RecordConflict
{ recordId : String
, base : Maybe Row
, left : Maybe Row
, right : Maybe Row
}

A field conflict keeps absent values distinct from empty strings. A record conflict represents a delete/edit conflict or different concurrent additions.

type Choice
= KeepBase
| KeepLeft
| KeepRight

Explicitly retain one of the three source values, including source deletion.

Compare base with two independently edited snapshots. Identical edits and changes made by only one side merge automatically. Different edits to one field, delete/edit pairs and different concurrent additions require choices.

All unresolved source conflicts, in deterministic order.

conflictKey : Conflict -> String

Collision-free length-prefixed key for associating a choice with a conflict. Keys include record IDs/field names and may be private; do not use them as telemetry.

finalize : List ( String, Choice ) -> Plan -> Result (List String) (List Row)

Resolve every conflict exactly once. Unknown, duplicate and missing keys fail the whole finalization. Source snapshots and the original plan remain immutable. No partial result is returned while any conflict remains unresolved.