merge: resolve v1.23.0 upstream conflicts, preserve custom git provider config

Resolved conflicts in 8 files by taking upstream refactored code:
- desktop.ts: re-export DesktopSettings from registry
- openchamberConfig.ts: simplified project setup client
- persistence.ts: registry-derived settings, add git provider hydration
- search.ts: upstream search entries + git provider entries
- useConfigStore.ts: loadDesktopSettings() path
- settings-helpers.js: add gitProviderId/gitModelId/gitProviders sanitization
- DOCUMENTATION.md: upstream walkthrough docs
- vite.config.ts: upstream SW glob patterns

Custom fork additions preserved:
- gitProviderId, gitModelId, gitProviders fields in settings registry
- Git provider domain store hydration in persistence.ts
- Git provider search entries in search.ts
- Git provider sanitization in settings-helpers.js
This commit is contained in:
2026-09-10 10:11:50 +00:00
559 changed files with 36139 additions and 9087 deletions
+116
View File
@@ -0,0 +1,116 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { runInNewContext } from 'node:vm';
const requireUI = createRequire(new URL('../packages/ui/package.json', import.meta.url));
const packageDirectory = dirname(requireUI.resolve('@legendapp/list/react'));
const bundles = ['react.js', 'react.mjs', 'react-native.web.js', 'react-native.web.mjs'];
// Exercise the installed dependency's actual private ScrollAdjust controller.
// Only its external hooks, DOM geometry and frame scheduler are supplied here.
// Keeping the controller in the package (rather than copying it into a test)
// makes a missing patch or a changed upstream implementation fail this check.
function controller(bundle, { horizontal = false, baseline = '' } = {}) {
const source = readFileSync(join(packageDirectory, bundle), 'utf8');
const start = source.indexOf('function ScrollAdjust() {');
const end = source.indexOf('var SnapWrapper', start);
assert.ok(start >= 0 && end > start, "Find the pinned package version's ScrollAdjust implementation");
const hooks = { useRef: (value) => ({ current: value }), useCallback: (callback) => callback };
const signals = new Map([['scrollAdjust', 0], ['scrollAdjustUserOffset', 0]]);
const listeners = new Map();
const frames = new Map();
let frameId = 0;
const paddingKey = horizontal ? 'paddingRight' : 'paddingBottom';
let padding = baseline;
const style = {};
Object.defineProperty(style, paddingKey, {
get: () => padding,
set(value) {
// Observed in Chromium: assigning 2123.1875px reads back as
// 2123.19px. This normalization is what a plain JS style mock misses.
padding = value.endsWith('px') ? `${Number(Number.parseFloat(value).toPrecision(6))}px` : value;
},
});
const contentNode = {
style,
get scrollHeight() { return 300 + (Number.parseFloat(padding) || 0); },
get scrollWidth() { return 300 + (Number.parseFloat(padding) || 0); },
get offsetHeight() { return this.scrollHeight; },
};
const scrollElement = {
scrollTop: 0, scrollLeft: 0, clientHeight: 300, clientWidth: 300,
scrollBy({ left, top }) { this.scrollLeft += left; this.scrollTop += top; },
};
const ctx = { state: { props: { horizontal }, scroll: 0, adjustingFromInitialMount: false } };
const ScrollAdjust = runInNewContext(`(${source.slice(start, end).trim()})`, {
React3: hooks,
React3__namespace: hooks,
useStateContext: () => ctx,
peek$: (_ctx, key) => signals.get(key),
useValueListener$: (key, callback) => listeners.set(key, callback),
getScrollAdjustTarget: () => ({ contentNode, scrollElement }),
getScrollAdjustAxis: () => ({
x: horizontal ? 1 : 0, y: horizontal ? 0 : 1,
contentSizeKey: horizontal ? 'scrollWidth' : 'scrollHeight',
viewportSizeKey: horizontal ? 'clientWidth' : 'clientHeight',
paddingEndProp: paddingKey,
}),
scrollAdjustBy: (element, left, top) => element.scrollBy({ left, top }),
window: { getComputedStyle: () => style },
requestAnimationFrame: (callback) => { frames.set(++frameId, callback); return frameId; },
cancelAnimationFrame: (id) => frames.delete(id),
});
ScrollAdjust();
return {
get padding() { return padding; },
set padding(value) { style[paddingKey] = value; },
adjust(offset) {
signals.set('scrollAdjustUserOffset', offset);
listeners.get('scrollAdjustUserOffset')();
},
finishFrame() {
const callbacks = [...frames.values()];
frames.clear();
for (const callback of callbacks) callback();
},
};
}
for (const bundle of bundles) {
describe(`LegendList temporary padding: ${bundle}`, () => {
it('removes browser-rounded fractional padding on the next frame', () => {
const view = controller(bundle);
view.adjust(1061.59375);
assert.equal(view.padding, '2123.19px');
view.finishFrame();
assert.equal(view.padding, '');
});
it('preserves the original baseline across overlapping adjustments', () => {
const view = controller(bundle, { baseline: '12px' });
view.adjust(1061.59375);
view.adjust(5000.25);
view.finishFrame();
assert.equal(view.padding, '12px');
});
it('cleans up the horizontal web entry point too', () => {
const view = controller(bundle, { horizontal: true });
view.adjust(1061.59375);
view.finishFrame();
assert.equal(view.padding, '');
});
it('does not clear padding changed by another owner', () => {
const view = controller(bundle);
view.adjust(1061.59375);
view.padding = '28px';
view.finishFrame();
assert.equal(view.padding, '28px');
});
});
}
+28 -1
View File
@@ -91,6 +91,10 @@
overflow: visible;
display: block;
}
.v-activity-static, .v-activity-collapse { display: block; }
.v-activity-static .cell, .v-activity-collapse .cell { width: 680px; height: auto; overflow: hidden; }
.activity-row { height: 18px; display: flex; gap: 12px; }
</style>
</head>
<body>
@@ -108,16 +112,39 @@
grid.className = 'grid v-' + variant;
const usesWrapper = variant.endsWith('-wrapper');
const usesButton = variant === 'ctx-button' || variant === 'ctx-sibling-translatez';
const usesActivity = variant === 'activity-static' || variant === 'activity-collapse';
for (let index = 0; index < count; index += 1) {
const cell = document.createElement('div');
cell.className = 'cell';
const spinner = SPINNER.replace('<svg', '<svg class="spin"');
if (usesWrapper) cell.innerHTML = '<span class="wrap">' + SPINNER + '</span>';
if (usesActivity) {
cell.innerHTML = Array.from({ length: 80 }, (_, row) =>
'<div class="activity-row"><span>Read file</span><code>src/module-' + row + '.ts</code><span>Completed</span></div>'
).join('');
}
else if (usesWrapper) cell.innerHTML = '<span class="wrap">' + SPINNER + '</span>';
else if (usesButton) cell.innerHTML = '<button>' + spinner + '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4" fill="#456"/></svg></button>';
else cell.innerHTML = SPINNER;
grid.appendChild(cell);
}
// Two content regions per turn at most: prior messages and non-text parts
// of the final message. Exercise the same 180ms height transition, with no
// continuous animation between disclosure changes. The static variant has
// identical content for the baseline.
if (variant === 'activity-collapse') {
let expanded = true;
setInterval(() => {
for (const cell of grid.children) {
const height = cell.scrollHeight;
cell.animate({ height: expanded ? [height + 'px', '0px'] : ['0px', height + 'px'] }, {
duration: 180, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'forwards',
});
}
expanded = !expanded;
}, 2000);
}
if (filler > 0) {
const container = document.createElement('div');
container.style.cssText = 'position:absolute;visibility:hidden;pointer-events:none;';