fix exponential recursion in detail screen config

by memoizing `lcsMerge`. Also:

- simplify it to return the merged result rather than an internal data
  structure
- do not keep track of the whole longest submatch, just its length, for
  simplicity

fixes http://manage.dimagi.com/default.asp?125416
The code below does not look like it is a direct implementation of the pseudocode on that wikipedia page. One difference: the implementation below is recursive, but the one on wikipedia is not. Also, the pseudocode has a conditional with the expression `i == 1 or j == 1` while this one has `i === 0 && j === 0` (this one uses AND instead of OR), and I'm not sure if that makes it substantively different or not. They may be effectively using the same algorithm, but I'm not sure I would call this a direct implementation of that.

Here is what I would call a closer "direct" implementation:

```js
function LCSubstr(s, t, isEqual) {
    isEqual = isEqual || function (a, b) { return a === b; };
    var i, j, key,
        z = 0, L = {}, ret = null,
        s_length = s.length,
        t_length = t.length;
    for (i = 0; i < s_length; i++) {
        for (j = 0; j < t_length; j++) {
            if (isEqual(s[i], t[j])) {
                key = i + " " + j;
                if (i === 0 || j === 0) {
                    L[key] = 1;
                } else {
                    L[key] = (L[(i - 1) + " " + (j - 1)] || 0) + 1;
                }
                if (L[key] > z) {
                    z = L[key];
                    ret = i;
                }
            }
        }
    }
    return ret === null ? "" : s.substring(ret - z + 1, ret + 1);
}
```

Differences:
- Only returns first longest common substring. There may be other common substrings of the same length; it's trivial to change this to return all of them if desired.
- Customizable equality function.