refactor(notes): decompose Notes into feature-owned modules (#6713)

* refactor(notes): decompose Notes into feature-owned modules

* fix(notes): avoid HTML interpolation in reminder picker

* docs(notes): drop checkpoint archives, session logs and publication drafts

The N0-N6 tarballs and raw pytest/node logs are not read by any script or
test, and the logs carry absolute home-directory paths. Once squashed into
dev they would stay in history for good. notes-pr.md and notes-issue.md
were the drafts used to open #6712 and #6713, not documentation.

notes-validation.md keeps the counts, the checkpoint hashes and
implementation.sha256 as the record; local interpreter paths and the local
Docker image name are replaced with portable placeholders.

* fix(notes): keep the state store raw and stop masonry re-observing after close

- state: writes built from views (filter, spread, splice) stored nested
  read-only views in the store, so structuredClone on a stored note threw
  DataCloneError. Store writes now unwrap nested views.
- layout: a save that resolves after closePanel re-renders the leaving pane
  and re-created the masonry ResizeObserver that teardown had disposed.
  Skip creating it once Notes is closed.
- index.js: drop 30 imported or declared names that were never used and
  group the imports at the top. Import order is unchanged.
- Revert unrelated punctuation edits in .gitattributes and keep the header
  comment and docstring first in compare/vote.js and
  test_notes_dom_xss_helpers.py.

Both fixes have a node test that fails on the previous head.

* test(notes): make script rejection assertion case-insensitive

Address CodeQL js/bad-tag-filter alert #1150 in test-only assertion and refresh Notes evidence manifest checksum.

---------

Co-authored-by: Léo <leograndcontact@gmail.com>
This commit is contained in:
Alexandre Teixeira
2026-10-15 09:29:41 +01:00
committed by GitHub
co-authored by Léo
parent 9f8eae72fd
commit f1a47f3812
121 changed files with 15318 additions and 5544 deletions
@@ -0,0 +1,111 @@
/** Mobile checklist grip dragging and document listener ownership. */
export function createChecklistReorder({
lifecycle
}) {
let _clDrag = null;
let _clDocBound = false;
let _clOwner = null;
function _wireChecklistTouchReorder(form) {
const container = form.querySelector('.note-checklist-inputs');
if (!container) return;
container.querySelectorAll('.note-cl-grip').forEach(grip => {
if (grip.dataset.touchBound === '1') return;
grip.dataset.touchBound = '1';
grip.addEventListener('touchstart', (e) => {
if (e.touches.length !== 1) return;
e.preventDefault();
const row = grip.closest('.note-cl-row');
if (!row) return;
_beginChecklistGrab(row, container, e.touches[0]);
}, { passive: false });
});
if (!_clDocBound || _clOwner !== form) {
if (_clOwner) lifecycle.release(_clOwner);
_clOwner = form;
lifecycle.listen(form, document, 'touchmove', _onClTouchMove, { passive: false });
lifecycle.listen(form, document, 'touchend', _onClTouchEnd, { passive: true });
lifecycle.listen(form, document, 'touchcancel', _onClTouchEnd, { passive: true });
_clDocBound = true;
}
}
function _beginChecklistGrab(row, container, touch) {
if (_clDrag) return;
const rect = row.getBoundingClientRect();
const placeholder = document.createElement('div');
placeholder.className = 'note-cl-row-placeholder';
placeholder.style.height = rect.height + 'px';
container.insertBefore(placeholder, row);
row.classList.add('note-cl-row-dragging');
row.style.position = 'fixed';
row.style.left = rect.left + 'px';
row.style.top = rect.top + 'px';
row.style.width = rect.width + 'px';
row.style.zIndex = '10002';
row.style.pointerEvents = 'none';
_clDrag = {
row, placeholder, container,
grabOffsetX: touch.clientX - rect.left,
grabOffsetY: touch.clientY - rect.top,
};
try { navigator.vibrate?.(8); } catch {}
}
function _onClTouchMove(e) {
if (!_clDrag) return;
if (e.touches.length !== 1) return;
e.preventDefault();
const t = e.touches[0];
const { row, placeholder, container } = _clDrag;
row.style.left = (t.clientX - _clDrag.grabOffsetX) + 'px';
row.style.top = (t.clientY - _clDrag.grabOffsetY) + 'px';
const under = document.elementFromPoint(t.clientX, t.clientY);
const target = under && under.closest
? under.closest('.note-cl-row:not(.note-cl-row-dragging)')
: null;
if (!target || target === row) return;
if (target.parentNode !== container) return;
const tRect = target.getBoundingClientRect();
const targetMidY = tRect.top + tRect.height / 2;
if (t.clientY < targetMidY) {
if (placeholder.nextElementSibling !== target) {
container.insertBefore(placeholder, target);
}
} else {
if (target.nextElementSibling !== placeholder) {
container.insertBefore(placeholder, target.nextElementSibling);
}
}
}
function _onClTouchEnd() {
if (!_clDrag) return;
const { row, placeholder } = _clDrag;
_clDrag = null;
const phRect = placeholder.getBoundingClientRect();
row.style.transition = 'left 0.18s ease, top 0.18s ease';
row.style.left = phRect.left + 'px';
row.style.top = phRect.top + 'px';
setTimeout(() => {
placeholder.parentNode.insertBefore(row, placeholder);
placeholder.remove();
row.classList.remove('note-cl-row-dragging');
row.style.cssText = '';
// Order is persisted as part of the form's normal save (rows are
// re-serialized in DOM order via _collectItems).
}, 200);
}
function dispose() {
if (_clDrag) _onClTouchEnd();
_clDocBound = false;
_clOwner = null;
}
return { dispose, _wireChecklistTouchReorder, _beginChecklistGrab, _onClTouchMove, _onClTouchEnd };
}