From 9c99b33d6c01f98f08e05df22681621792489c0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 15:00:17 +0000 Subject: [PATCH 1/9] Initial plan From 7da44d530ee651b461ca0c95692ecd9e194934e0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 15:14:33 +0000 Subject: [PATCH 2/9] perf: optimize morph hot path - text-only fast path, skip updateAttrs when attrs match, simplify noMorph Agent-Logs-Url: https://github.com/dadhi/dmax/sessions/90d6b16d-998a-4c9f-a856-a241dade6193 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- dmax.js | 73 +++++++++++++++---------------------- tests/dmax.size.limits.json | 4 +- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/dmax.js b/dmax.js index 798f1f9..2891ef3 100644 --- a/dmax.js +++ b/dmax.js @@ -567,7 +567,7 @@ } const noScan = (el) => el && el.hasAttribute && (el.hasAttribute(DM_NO) || el.hasAttribute(DM_NO_SCAN)) - const noMorph = (el) => el && el.hasAttribute && (el.hasAttribute(DM_NO) || el.hasAttribute(DM_NO_MORPH)) + const noMorph = (el) => el.hasAttribute(DM_NO) || el.hasAttribute(DM_NO_MORPH) const warn = (...a) => console.warn('[dmax]', ...a), logErr = (...a) => console.error('[dmax]', ...a) const wireItClone = (node) => { const stack = [node] @@ -1400,19 +1400,15 @@ const getPatchTars = (selector, simpleId = selector && getSimpleIdSelector(selector), el = simpleId && document.getElementById(simpleId)) => !selector ? NIL : simpleId ? el ? [el] : NIL : document.querySelectorAll(selector) const sameAttrs = (from, to) => { - const fromAttrs = from.attributes, toAttrs = to.attributes, len = toAttrs.length - if (fromAttrs.length !== len) return false - for (let i = 0; i < len; i++) { - const fromAttr = fromAttrs[i], toAttr = toAttrs[i] - if (fromAttr.name !== toAttr.name || fromAttr.value !== toAttr.value) return false - } + const fa = from.attributes, ta = to.attributes, len = ta.length + if (fa.length !== len) return false + for (let i = 0; i < len; i++) if (fa[i].name !== ta[i].name || fa[i].value !== ta[i].value) return false return true } - // Sync attributes from to onto from. + // Sync attributes from to onto from. Called only when attrs differ. const updateAttrs = (from, to) => { const toAttrs = to.attributes, fromAttrs = from.attributes, tl = toAttrs.length, fl = fromAttrs.length - if (!fl && !tl) return if (fl === tl) { let same = true, re = false for (let i = 0; i < tl; i++) { @@ -1430,14 +1426,8 @@ if (same) return } } - if (!tl) { - for (let i = fl - 1; i >= 0; i--) from.removeAttribute(fromAttrs[i].name) - return - } - for (let i = 0; i < tl; i++) { - const ta = toAttrs[i], fa = fromAttrs.getNamedItem(ta.name) - if (!fa || fa.value !== ta.value) from.setAttribute(ta.name, ta.value) - } + if (!tl) { for (let i = fl - 1; i >= 0; i--) from.removeAttribute(fromAttrs[i].name); return } + for (let i = 0; i < tl; i++) { const ta = toAttrs[i]; if (fromAttrs.getNamedItem(ta.name)?.value !== ta.value) from.setAttribute(ta.name, ta.value) } for (let i = fl - 1; i >= 0; i--) if (!to.hasAttribute(fromAttrs[i].name)) from.removeAttribute(fromAttrs[i].name) } @@ -1495,7 +1485,7 @@ } let _morphActiveEl = null - const doneMorph = (root) => root && (_morphActiveEl = null) + const _endMorph = (root) => root && (_morphActiveEl = null) // Update from in place without disturbing matched-node listeners or cleanup state. // Preserve caret, selection, and scroll across streamed updates. const morph = (from, to) => { @@ -1503,36 +1493,33 @@ if (root) _morphActiveEl = document.activeElement if (from.nodeType === 3 && to.nodeType === 3) { if (from.nodeValue !== to.nodeValue) from.nodeValue = to.nodeValue - return doneMorph(root) + return _endMorph(root) } - if (from.nodeType !== ELEMENT_NODE || to.nodeType !== ELEMENT_NODE || noMorph(from) || noMorph(to)) return doneMorph(root) + if (from.nodeType !== ELEMENT_NODE || to.nodeType !== ELEMENT_NODE || noMorph(from) || noMorph(to)) return _endMorph(root) if (from.tagName !== to.tagName) { if (from.parentNode) from.parentNode.replaceChild(to.cloneNode(true), from) - return doneMorph(root) + return _endMorph(root) } const fromFirst = from.firstChild, toFirst = to.firstChild, textOnly = fromFirst && toFirst && !fromFirst.nextSibling && !toFirst.nextSibling && fromFirst.nodeType === TEXT_NODE && toFirst.nodeType === TEXT_NODE - if (sameAttrs(from, to) && (!fromFirst && !toFirst || textOnly && fromFirst.nodeValue === toFirst.nodeValue)) return doneMorph(root) - const tag = from.tagName, isFocused = from === _morphActiveEl + const attrsMatch = sameAttrs(from, to) + if (attrsMatch && (!fromFirst && !toFirst || textOnly && fromFirst.nodeValue === toFirst.nodeValue)) return _endMorph(root) + if (textOnly && attrsMatch && from !== _morphActiveEl) { fromFirst.nodeValue = toFirst.nodeValue; return _endMorph(root) } + const isFocused = from === _morphActiveEl let selStart = -1, selEnd = -1, selDir = 'none', selVal = null, selIdx = -1 - if (isFocused && (tag === 'INPUT' || tag === 'TEXTAREA')) { - try { selStart = from.selectionStart; selEnd = from.selectionEnd; selDir = from.selectionDirection || 'none' } catch (_) {} - } else if (isFocused && tag === 'SELECT') selVal = from.value, selIdx = from.selectedIndex - const scrollTop = from.scrollTop, scrollLeft = from.scrollLeft, keepScroll = scrollTop || scrollLeft - updateAttrs(from, to) - if (textOnly) { - if (fromFirst.nodeValue !== toFirst.nodeValue) fromFirst.nodeValue = toFirst.nodeValue - } else if (fromFirst || toFirst) morphChildren(from, to) - if (keepScroll) { - if (from.scrollTop !== scrollTop) from.scrollTop = scrollTop - if (from.scrollLeft !== scrollLeft) from.scrollLeft = scrollLeft - } - if (isFocused && selStart >= 0) { - try { from.setSelectionRange(selStart, selEnd, selDir) } catch (_) {} - } else if (isFocused && tag === 'SELECT') { - from.value = selVal - if (from.value !== selVal && selIdx >= 0 && selIdx < from.options.length) from.selectedIndex = selIdx - } - doneMorph(root) + if (isFocused) { + const tag = from.tagName + if (tag === 'INPUT' || tag === 'TEXTAREA') { + try { selStart = from.selectionStart; selEnd = from.selectionEnd; selDir = from.selectionDirection || 'none' } catch (_) {} + } else if (tag === 'SELECT') selVal = from.value, selIdx = from.selectedIndex + } + const scrollTop = from.scrollTop, scrollLeft = from.scrollLeft + if (!attrsMatch) updateAttrs(from, to) + if (textOnly) { if (fromFirst.nodeValue !== toFirst.nodeValue) fromFirst.nodeValue = toFirst.nodeValue } + else if (fromFirst || toFirst) morphChildren(from, to) + if (scrollTop || scrollLeft) { if (from.scrollTop !== scrollTop) from.scrollTop = scrollTop; if (from.scrollLeft !== scrollLeft) from.scrollLeft = scrollLeft } + if (isFocused && selStart >= 0) { try { from.setSelectionRange(selStart, selEnd, selDir) } catch (_) {} } + else if (isFocused && selVal !== null) { from.value = selVal; if (from.value !== selVal && selIdx >= 0 && selIdx < from.options.length) from.selectedIndex = selIdx } + _endMorph(root) } const JSON_MERGE_DELETE = Symbol('json_merge_delete') @@ -1655,7 +1642,7 @@ st[0] = 'message', st[1] = null, st[2] = false } const consumeSseLine = (raw, st, applied, dKey) => { - const line = raw[raw.length - 1] === '\r' ? raw.slice(0, -1) : raw + const line = raw.length && raw[raw.length - 1] === '\r' ? raw.slice(0, -1) : raw if (!line) return flushSse(applied, st, dKey) if (line[0] === SSE_COMMENT) return const ci = line.indexOf(':'), field = ci < 0 ? line : line.slice(0, ci) diff --git a/tests/dmax.size.limits.json b/tests/dmax.size.limits.json index f6d6f98..8ab6db2 100644 --- a/tests/dmax.size.limits.json +++ b/tests/dmax.size.limits.json @@ -1,4 +1,4 @@ { - "lines": 1755, - "bytes": 89998 + "lines": 1742, + "bytes": 89941 } From 9f2fffe79d2d548ce625a9ffbfd1a4c5054a7f70 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 15:16:49 +0000 Subject: [PATCH 3/9] perf: remove redundant empty-string guard in consumeSseLine Agent-Logs-Url: https://github.com/dadhi/dmax/sessions/90d6b16d-998a-4c9f-a856-a241dade6193 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- dmax.js | 2 +- tests/dmax.size.limits.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dmax.js b/dmax.js index 2891ef3..1e01b45 100644 --- a/dmax.js +++ b/dmax.js @@ -1642,7 +1642,7 @@ st[0] = 'message', st[1] = null, st[2] = false } const consumeSseLine = (raw, st, applied, dKey) => { - const line = raw.length && raw[raw.length - 1] === '\r' ? raw.slice(0, -1) : raw + const line = raw[raw.length - 1] === '\r' ? raw.slice(0, -1) : raw if (!line) return flushSse(applied, st, dKey) if (line[0] === SSE_COMMENT) return const ci = line.indexOf(':'), field = ci < 0 ? line : line.slice(0, ci) diff --git a/tests/dmax.size.limits.json b/tests/dmax.size.limits.json index 8ab6db2..0cf125b 100644 --- a/tests/dmax.size.limits.json +++ b/tests/dmax.size.limits.json @@ -1,4 +1,4 @@ { "lines": 1742, - "bytes": 89941 + "bytes": 89927 } From 37d4609309b6624be929812d6d8e8b4b23648df6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 15:56:00 +0000 Subject: [PATCH 4/9] =?UTF-8?q?perf:=20optimize=20dmEx/dmIt/dmAct=20hot=20?= =?UTF-8?q?paths=20=E2=80=94=20precompute=20targets,=20skip=20empty=20loop?= =?UTF-8?q?s,=20single-item=20fast=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/dadhi/dmax/sessions/7dd03f41-caf2-4108-bbe7-6c17dbdbc944 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- dmax.js | 85 +++++++++++-------------------------- tests/dmax.size.limits.json | 4 +- 2 files changed, 26 insertions(+), 63 deletions(-) diff --git a/dmax.js b/dmax.js index 1e01b45..9c29850 100644 --- a/dmax.js +++ b/dmax.js @@ -595,25 +595,21 @@ if (!expected(Array.isArray(val), 'dmIt expected array value from:', tr, 'on:', el)) return const newLen = val.length, oldLen = itState.count || 0 if (newLen < oldLen) { - for (let i = 0; i < oldLen - newLen; i++) { - const node = itState.nodes.pop() - if (node && node.parentNode) node.parentNode.removeChild(node) - } + for (let i = 0; i < oldLen - newLen; i++) { const node = itState.nodes.pop(); if (node && node.parentNode) node.parentNode.removeChild(node) } itState.count = newLen } if (newLen > oldLen) { - const frag = document.createDocumentFragment() - for (let idx = oldLen; idx < newLen; idx++) { - try { - const node = tplFirst.cloneNode(true) - const idxText = '' + idx - rewriteItBindings(node, itemRefBase + '.' + idxText, itemExprBase + '[' + idxText + ']', idxText) - frag.appendChild(node) - itState.nodes.push(node) - } catch { } + const count = newLen - oldLen + if (count === 1) { + const node = tplFirst.cloneNode(true), idxText = '' + oldLen + rewriteItBindings(node, itemRefBase + '.' + idxText, itemExprBase + '[' + idxText + ']', idxText) + el.appendChild(node), itState.nodes.push(node) + } else { + const frag = document.createDocumentFragment() + for (let idx = oldLen; idx < newLen; idx++) { try { const node = tplFirst.cloneNode(true), idxText = '' + idx; rewriteItBindings(node, itemRefBase + '.' + idxText, itemExprBase + '[' + idxText + ']', idxText); frag.appendChild(node); itState.nodes.push(node) } catch { } } + el.appendChild(frag) } - el.appendChild(frag) - for (let i = itState.nodes.length - (newLen - oldLen); i < itState.nodes.length; ++i) wireItClone(itState.nodes[i]) + for (let i = itState.nodes.length - count; i < itState.nodes.length; ++i) wireItClone(itState.nodes[i]) itState.count = newLen } } @@ -976,19 +972,11 @@ } if (tars.length) { const rawFn = fn + for (const tar of tars) { tar._m = getWriteMode(tar.mods); tar._j = !!(tar.mods && tar.mods.some((m) => m.root === M_JSOS)); if (!tar.isSi) tar._el = tar.isSp ? tar.root === SP_WIN ? window : tar.root === SP_DOC ? document : tar.root === SP_HISTORY ? window.history : null : tar.root ? getElById(tar.root, dKey) : null } fn = (dm, el, trig, trigVal, detail) => { const exprVal = rawFn(dm, el, trig, trigVal, detail) - let failedTa = null - try { - for (const tar of tars) { - failedTa = tar - const mode = getWriteMode(tar.mods) - const outVal = tar.mods && tar.mods.some((m) => m.root === M_JSOS) ? dmJsos(exprVal) : exprVal - const nextVal = tar.isSi ? combineActResult(getSiVal(tar), outVal, mode) : combineActResult(getElPrVal(tar.isSp ? tar.root === SP_WIN ? window : tar.root === SP_DOC ? document : tar.root === SP_HISTORY ? window.history : null : tar.root ? getElById(tar.root, dKey) : el, tar.path), outVal, mode) - if (tar.isSi) setSiAndNotifySubsNDeep(dKey, tar, nextVal) - else setPr(el, dKey, tar, nextVal) - } - } catch (e) { logErr('Error: setting target', failedTa, 'in', dKey, 'ended with ex:', e) } + try { for (const tar of tars) { const outVal = tar._j ? dmJsos(exprVal) : exprVal; const nextVal = tar.isSi ? combineActResult(getSiVal(tar), outVal, tar._m) : combineActResult(getElPrVal(tar._el || el, tar.path), outVal, tar._m); if (tar.isSi) setSiAndNotifySubsNDeep(dKey, tar, nextVal); else setPr(el, dKey, tar, nextVal) } } + catch (e) { logErr('Error: setting target in', dKey, 'ended with ex:', e) } } } if (!trigs.length) { if (hasExpr) fn(DM, el, null, null, null); return } let ran = false @@ -1240,6 +1228,7 @@ else if (p && p.isSi) actHdrMods.push([camelToKebab(p.path ? p.path.at(-1) : p.root), null, p]) } const ss = (k, v) => actStats && setSiAndNotifySubsNDeep(dKey, actStats[k], v) + const hasAdds = adds.length > 0, hasRouteMods = actRouteMods.length > 0 const doRequest = async () => { const url = urlFn ? urlFn(DM, el, null, null, null) : '' if (!url) return logErr('Error: dmAct: URL is empty in:', dKey) @@ -1247,53 +1236,27 @@ try { const queryParams = noProto(), bodyFields = noProto(), addDst = isGetOrDelete ? queryParams : bodyFields if (sendAll) for (const [siName, siVal] of _dm.entries()) bodyFields[siName] = siVal - for (const add of adds) { + if (hasAdds) for (const add of adds) { const val = add.isEv ? getElPrVal(add.taEl || el, add.path) : getSiValOrIt(add) if (add.spread) { if (val && typeof val === 'object') for (const k in val) if (hasOwn(val, k)) addDst[k] = val[k] else addDst.value = val } else addDst[add.key] = val } - for (const [isBody, key, path, ref] of actRouteMods) (isBody ? bodyFields : queryParams)[key] = ref ? getSiValOrIt(ref) : _dm.get(path) + if (hasRouteMods) for (const [isBody, key, path, ref] of actRouteMods) (isBody ? bodyFields : queryParams)[key] = ref ? getSiValOrIt(ref) : _dm.get(path) let finalUrl = url, hasQ = finalUrl.includes('?') for (const k in queryParams) finalUrl += (hasQ ? '&' : '?') + encodeURIComponent(k) + '=' + encodeURIComponent('' + (queryParams[k] ?? '')), hasQ = true let hs = ACT_HS_EMPTY, sharedHs = 1 - if (hdrsPath) { - const hdrObj = resolveMPathVal(hdrsPath) - if (isPlainObj(hdrObj)) { - hs = noProto() - sharedHs = 0 - for (const hk in hdrObj) if (hasOwn(hdrObj, hk)) hs[hsNoKebab ? hk : camelToKebab(hk)] = '' + hdrObj[hk] - } - } - if (baseHs !== ACT_HS_EMPTY) { - if (hs === ACT_HS_EMPTY) hs = baseHs - else for (const hk in baseHs) if (hasOwn(baseHs, hk)) hs[hk] = baseHs[hk] - } - if (authPath != null) { - const authVal = resolveMPathVal(authPath) - if (authVal != null) { - if (sharedHs) hs = cloneOwnProps(hs), sharedHs = 0 - hs[H_AUTHORIZATION] = '' + authVal - } - } - for (const [kebabKey, path, ref] of actHdrMods) { - if (sharedHs) hs = cloneOwnProps(hs), sharedHs = 0 - const v = ref ? getSiValOrIt(ref) : _dm.get(path) - hs[kebabKey] = v != null ? '' + v : '' - } - let bodyCount = 0, firstBodyKey = null + if (hdrsPath) { const hdrObj = resolveMPathVal(hdrsPath); if (isPlainObj(hdrObj)) { hs = noProto(); sharedHs = 0; for (const hk in hdrObj) if (hasOwn(hdrObj, hk)) hs[hsNoKebab ? hk : camelToKebab(hk)] = '' + hdrObj[hk] } } + if (baseHs !== ACT_HS_EMPTY) { if (hs === ACT_HS_EMPTY) hs = baseHs; else for (const hk in baseHs) if (hasOwn(baseHs, hk)) hs[hk] = baseHs[hk] } + if (authPath != null) { const authVal = resolveMPathVal(authPath); if (authVal != null) { if (sharedHs) hs = cloneOwnProps(hs), sharedHs = 0; hs[H_AUTHORIZATION] = '' + authVal } } + for (const [kebabKey, path, ref] of actHdrMods) { if (sharedHs) hs = cloneOwnProps(hs), sharedHs = 0; const v = ref ? getSiValOrIt(ref) : _dm.get(path); hs[kebabKey] = v != null ? '' + v : '' } + let bodyCount = 0, firstBodyKey = null, body = null for (const bk in bodyFields) if (hasOwn(bodyFields, bk)) { if (!bodyCount) firstBodyKey = bk; bodyCount++ } - let body = null if (bodyCount) { const raw = bodyCount === 1 ? bodyFields[firstBodyKey] : bodyFields - if (isForm && (isPlainObj(raw) || Array.isArray(raw))) { - const params = new URLSearchParams() - if (Array.isArray(raw)) for (let i = 0; i < raw.length; i++) params.append('' + i, '' + (raw[i] ?? '')) - else for (const k in raw) if (hasOwn(raw, k)) params.append(k, '' + (raw[k] ?? '')) - body = params.toString() - } else if (isJson || isPlainObj(raw) || Array.isArray(raw)) body = JSON.stringify(raw) - else body = '' + raw + if (isForm && (isPlainObj(raw) || Array.isArray(raw))) { const params = new URLSearchParams(); if (Array.isArray(raw)) for (let i = 0; i < raw.length; i++) params.append('' + i, '' + (raw[i] ?? '')); else for (const k in raw) if (hasOwn(raw, k)) params.append(k, '' + (raw[k] ?? '')); body = params.toString() } + else body = isJson || isPlainObj(raw) || Array.isArray(raw) ? JSON.stringify(raw) : '' + raw } const ac = typeof AbortController !== 'undefined' ? new AbortController() : null activeAbort = ac ? () => ac.abort() : null diff --git a/tests/dmax.size.limits.json b/tests/dmax.size.limits.json index 0cf125b..2eefaa4 100644 --- a/tests/dmax.size.limits.json +++ b/tests/dmax.size.limits.json @@ -1,4 +1,4 @@ { - "lines": 1742, - "bytes": 89927 + "lines": 1705, + "bytes": 89850 } From 05096125b0a0d840bef6170af6bf57b9fbf8c11d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 24 May 2026 16:26:40 +0000 Subject: [PATCH 5/9] =?UTF-8?q?test:=20cover=20optimization=20fast=20paths?= =?UTF-8?q?=20=E2=80=94=20morph=20text-only,=20dmEx=20jsos/window=20target?= =?UTF-8?q?s,=20dmIt=20single/multi=20growth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Logs-Url: https://github.com/dadhi/dmax/sessions/2ed391cb-0117-478d-9c1e-ad79385bc2b0 Co-authored-by: dadhi <39516+dadhi@users.noreply.github.com> --- asserts.js | 90 +++++++++++++++++++++++++++++++++++++ tests/fuzz.deterministic.js | 9 ++++ 2 files changed, 99 insertions(+) diff --git a/asserts.js b/asserts.js index 0da7d35..c9b9eee 100644 --- a/asserts.js +++ b/asserts.js @@ -735,6 +735,22 @@ return { inc, dec: DM['n'] }; } __assert(__tSubTarIncDec, [], { inc: 3, dec: 2 }, 'dmEx target ^inc/^dec updates current numeric target by one'); + function __tSubTarJsos() { + __reset(); + _dm.set('src', { a: 1, b: [2, 3] }); + const el = document.createElement('div'); + dmEx(el, 'data-m-ex:out^jsos@src', 'val'); + return DM['out']; + } + __assert(__tSubTarJsos, [], '{\n "a": 1,\n "b": [\n 2,\n 3\n ]\n}', 'dmEx target ^jsos precomputed flag applies JSON stringify to output'); + function __tSubTarWindowProp() { + __reset(); + _dm.set('title', 'dmax-test'); + const el = document.createElement('div'); + dmEx(el, 'data-m-ex:_window.document.title@title', 'val'); + return document.title; + } + __assert(__tSubTarWindowProp, [], 'dmax-test', 'dmEx target _window property written via precomputed _el'); function __tSubSignalSiModPathAndExpr() { __reset(); _dm.set('foo', { bar: 7 }); @@ -1369,6 +1385,42 @@ } finally { tplEl.remove(); el.remove() } } __assert(__tDumpExplicitTemplate, [], 2, 'dmIt +#tplId explicit template reference appends 2 clones') + function __tDumpSingleItemGrowth() { + __reset() + const el = document.createElement('ul') + const tpl = document.createElement('template') + tpl.innerHTML = '
?>Yiqs9ST9@HZBbBoJqDXq2dbyX
zV82E|>4xC~o5X%Y{?fFsS3r69sm8lNiVH);ZC*s`t8?WQ7rsva%QY&%-wH}w&vyS*
zY}+_cM5emT-{!y-W+O26r+F9Ywz%s}T6cOw>lqnZd2XyNHMJ?2Ak`P&c+U2(ddm&4
za@}}>S~f7zz&${vzZ%A4; 0DN-->?5
z77^dL2On~V-8*tmT7N*B&V5skthHE-28eTTED;JQo4N>aW6Lg)%Y;W~)Zz41EVsnB
z d(n*k>`pZ|q*9cB;
zeSqS!?YiR6jyU$r0^x(mP_o%R0C5}uKS`Ef|Ko{P&(KhU?G0gRl@J8+Ghz?AqR(2#
z@`kv49d1k}?Vaa!a4Z%vg84Vcs4q$t39>vOr-w|E(nnXC%xVmbY#pIFmYZ!5i81vZ
zAZICb)R8ydCkmoCmKo|~a6`-}J%>DT7bz@S!YMV{Vn*Js%g|XFg4kTjL)JZD1H6Ek
z+^Ukubk(3UagDL&go;X0#r-hI6KNnJL*;oztP>A-Z}O||Zj}fGCdwPaP}aP6Y=^7>
zn$DsvqKyN`$J)vqUQ@e?iz-=a%u&^}QVluF4pJ#RDx@|1SvNj-hBHM$J(T*K3|$CD
zNy`PQIs1o03|kjYWsT`g3S4|xjwZuXHFN%AB_vb_t^+MYMNH2-yGsAm;20gXE`IB+
zXQHEOy3w@1q6Mu=VQ9(#3!>Fh3%b;xLhZMdQn@@@xvX6D>gsE)OyWwIT~7O8o$Yev
z?qA$Fl}anb6$%Bwtt^!C=huU}91FrUAKAi~(>y2ekt{(2$CJwOaa>XyOHjPjQR9qLPK9J60f
z@FaCwOtSg@siTC0vP7A69F0geJ@Um1$rrfd!qvstV&S4T+(ecO jWhVsfG$?6HEW0i}efuYu6P
zBJJ?QcUDrlR>G3$i5Z?r%+79dszr_5A}J;;Bv??a{+NWxzgLex;?qxnSGbOgRx)Fi
z2u4S^ja+8Mt(Gd|g}d#G3-D+oW>73BS60*3H_d-jR1OyqLXE)
TD}lDM>9qod@YMG
zn71vFZsW0To0<*kHdHWBgOa`kDqbNxViM~2f-mTY=}0yM9u9jtk+zqtgikQfCXDQb
zxm_s!sx{OZ8wcGs{n{CE{fpmExy9;HX7t@FzjQ2mem5U+eagD1+=^gZRHM6KLPT50
z`B#3nfElpIz0de
R8)?eVU;HZN?cmbs!lfs5=ObJYV-D-h~K}M~JOD4zjF!`z*Z@QcXTi
zIXA1YdQ&0lG*>Vu<>{M@2u=UHxP!Cw&4W8!{c%Vppc
zJJaasVVyO!>O`w9m{)y6+ZA(XM3}&ts?T+tnd3kG?GwpYh2sEWht-ZEL>;Iu$~6Gu
zy(*>~y3W+;hz=TcE3eoSIRP8+yd=5*3MHpa28!+NJGHfCg@HJ?+t$?Rtb2tuy|t}G
zThfxr^>;x0XF%)J(;qNhD3hN&@Qg6k*zAIXLk`xLS?3k40Y?);{CXOgwD0Ot*0lRa
z8Vk>HNXNE}q+`bKvsm&I`6d>C!+E1#0cH|AX&M|9LiDt|PS+kl$h)Djp2!C^BUFV-
zg$i-NVYm(stnwsf2?+gSR+~L9?TT;(FlE4!@8_a29RWuB0#HO?jqNtXXUtdX