diff --git a/meshchatx.rsm b/meshchatx.rsm index 57c74590..f0b366a7 100644 Binary files a/meshchatx.rsm and b/meshchatx.rsm differ diff --git a/meshchatx/src/frontend/components/propagation-nodes/PropagationNodesPage.vue b/meshchatx/src/frontend/components/propagation-nodes/PropagationNodesPage.vue index e3b76d9a..0af04f19 100644 --- a/meshchatx/src/frontend/components/propagation-nodes/PropagationNodesPage.vue +++ b/meshchatx/src/frontend/components/propagation-nodes/PropagationNodesPage.vue @@ -923,6 +923,7 @@ export default { return; } this.manualHashDraft = parsed; + await this.usePropagationNode(parsed); }, async copyPreferredHash() { const hash = this.config.lxmf_preferred_propagation_node_destination_hash; diff --git a/meshchatx/src/frontend/components/settings/SettingsPage.vue b/meshchatx/src/frontend/components/settings/SettingsPage.vue index 7c430252..25f8789e 100644 --- a/meshchatx/src/frontend/components/settings/SettingsPage.vue +++ b/meshchatx/src/frontend/components/settings/SettingsPage.vue @@ -4337,6 +4337,7 @@ export default { } event.preventDefault(); this.config.lxmf_preferred_propagation_node_destination_hash = parsed; + this.savePreferredPropagationNodeHash(true); }, async pastePreferredPropagationNodeHash() { const result = await readTextFromClipboard(); diff --git a/meshchatx/src/frontend/js/Utils.js b/meshchatx/src/frontend/js/Utils.js index 11261d8c..4b64e26f 100644 --- a/meshchatx/src/frontend/js/Utils.js +++ b/meshchatx/src/frontend/js/Utils.js @@ -16,7 +16,12 @@ class Utils { if (raw == null) { return null; } - const compact = String(raw).replace(/[^0-9a-fA-F]/g, ""); + let text = String(raw).trim(); + if (!text) { + return null; + } + text = text.replace(/^(lxmf|lxma|lxm|rns):\/\//i, ""); + const compact = text.replace(/[^0-9a-fA-F]/g, ""); if (compact.length < 32) { return null; } diff --git a/meshchatx/src/frontend/locales/nl.json b/meshchatx/src/frontend/locales/nl.json index be4894da..86f74d14 100644 --- a/meshchatx/src/frontend/locales/nl.json +++ b/meshchatx/src/frontend/locales/nl.json @@ -2493,7 +2493,7 @@ "preferred_cleared": "Voorkeurs-propagatieknooppunt gewist", "invalid_hash": "Voer een bestemmings-hash van 32 tekens in", "manual_placeholder": "Plak bestemmings-hash, of lxmf://...", - "manual_hint": "Haakjes, spaties en lxmf://-links zijn in orde. Een hash uitzetten schakelt automatische selectie uit.", + "manual_hint": "Haakjes, spaties en lxmf://-links zijn in orde. Een hash instellen schakelt automatische selectie uit.", "set_preferred": "Instellen", "paste_hash": "Plakken", "clear_preferred": "Wissen", diff --git a/scripts/ci/verify-package-contents.sh b/scripts/ci/verify-package-contents.sh index ebadbde1..1d906801 100755 --- a/scripts/ci/verify-package-contents.sh +++ b/scripts/ci/verify-package-contents.sh @@ -53,7 +53,7 @@ record_hit() { # Do not use a bare /(^|/)android(/|$)/ pattern: LXST ships # LXST/Platforms/android for mobile hosts. That path must remain allowed in # APKs and must not false-fail Docker scans of site-packages. -COMMON_DENY_RE='(^|/)\.git(/|$)|(^|/)node_modules(/|$)|(^|/)\.pnpm-store(/|$)|(^|/)\.venv(/|$)|(^|/)vendor/offline(/|$)|(^|/)vendor/lxmfy/tests(/|$)|(^|/)vendor/lxmfy/docs(/|$)|(^|/)vendor/lxmfy/docker(/|$)|(^|/)vendor/rns_filesync/tests(/|$)|(^|/)vendor/rns_filesync/docs(/|$)|(^|/)vendor/rns_filesync/docker(/|$)|(^|/)vendor/rns_filesync/packaging(/|$)|(^|/)vendor/rns_filesync/sideband(/|$)|(^|/)vendor/rns_filesync/man(/|$)|(^|/)vendor/rns_filesync/scripts(/|$)|(^|/)\.github(/|$)|(^|/)docs/agents(/|$)|(^|/)screenshots(/|$)|(^|/)\.pytest_cache(/|$)|(^|/)mutants(/|$)|(^|/)coverage(/|$)' +COMMON_DENY_RE='(^|/)\.git(/|$)|(^|/)node_modules(/|$)|(^|/)\.pnpm-store(/|$)|(^|/)\.venv(/|$)|(^|/)vendor/offline(/|$)|(^|/)vendor/lxmfy/tests(/|$)|(^|/)vendor/lxmfy/docs(/|$)|(^|/)vendor/lxmfy/docker(/|$)|(^|/)vendor/rns_filesync/tests(/|$)|(^|/)vendor/rns_filesync/docs(/|$)|(^|/)vendor/rns_filesync/docker(/|$)|(^|/)vendor/rns_filesync/packaging(/|$)|(^|/)vendor/rns_filesync/sideband(/|$)|(^|/)vendor/rns_filesync/man(/|$)|(^|/)vendor/rns_filesync/scripts(/|$)|(^|/)\.github(/|$)|(^|/)\.agents(/|$)|(^|/)screenshots(/|$)|(^|/)\.pytest_cache(/|$)|(^|/)mutants(/|$)|(^|/)coverage(/|$)' BYTECODE_DENY_RE='(^|/)__pycache__(/|$)' diff --git a/scripts/sync-meshchatx-docs.js b/scripts/sync-meshchatx-docs.js index 8307caf6..56b8639c 100644 --- a/scripts/sync-meshchatx-docs.js +++ b/scripts/sync-meshchatx-docs.js @@ -1,7 +1,7 @@ /** * Copy docs/ tree into meshchatx/src/frontend/public/meshchatx-docs/ for in-app serving. * Source of truth: docs/ at repo root. - * Skips docs/agents/ (contributor/agent guidance, not end-user docs). + * Agent guidance lives under .agents/ at repo root, not under docs/. */ const fs = require("fs"); @@ -12,7 +12,7 @@ const srcDir = path.join(root, "docs"); const destDir = path.join(root, "meshchatx", "src", "frontend", "public", "meshchatx-docs"); const COPY_EXTENSIONS = new Set([".md", ".txt", ".json"]); -const SKIP_TOP_LEVEL_DIRS = new Set(["agents"]); +const SKIP_TOP_LEVEL_DIRS = new Set(); function walkSync(dir, callback, relBase = "") { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { diff --git a/tests/backend/module_ownership_contract_helpers.py b/tests/backend/module_ownership_contract_helpers.py index d51c86d2..8ee98878 100644 --- a/tests/backend/module_ownership_contract_helpers.py +++ b/tests/backend/module_ownership_contract_helpers.py @@ -2,7 +2,7 @@ """Helpers for the backend module ownership contract. -Parses the "Backend" table in docs/agents/module-ownership.md into a plain +Parses the "Backend" table in .agents/module-ownership.md into a plain row structure, and resolves the backtick-quoted spans in that table to on-disk paths under meshchatx/src/backend/ or meshchatx/src/frontend/components/. Prose cells without backtick spans diff --git a/tests/backend/test_module_ownership_contract.py b/tests/backend/test_module_ownership_contract.py index 989d94f3..fd943d3d 100644 --- a/tests/backend/test_module_ownership_contract.py +++ b/tests/backend/test_module_ownership_contract.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: 0BSD -"""Contract test: docs/agents/module-ownership.md Backend table vs files on disk. +"""Contract test: .agents/module-ownership.md Backend table vs files on disk. Parses the Backend table directly from the doc so the checked-in fixture drifts loudly (via a failing diff) whenever a row is added, removed, or @@ -28,7 +28,7 @@ from tests.backend.module_ownership_contract_helpers import ( ) _REPO_ROOT = Path(__file__).resolve().parents[2] -_DOC_PATH = _REPO_ROOT / "docs" / "agents" / "module-ownership.md" +_DOC_PATH = _REPO_ROOT / ".agents" / "module-ownership.md" _FIXTURE = ( Path(__file__).resolve().parent / "fixtures" / "backend_module_ownership.json" ) @@ -45,7 +45,7 @@ def test_backend_ownership_table_matches_fixture(): expected = load_ownership_fixture(_FIXTURE) assert parsed == expected, ( "Backend module ownership table drifted from the checked-in fixture. " - "If the docs/agents/module-ownership.md Backend table changed on purpose, run: " + "If the .agents/module-ownership.md Backend table changed on purpose, run: " "UPDATE_BACKEND_MODULE_OWNERSHIP=1 uv run pytest tests/backend/test_module_ownership_contract.py -k " "backend_ownership_table_matches_fixture" ) diff --git a/tests/frontend/LoadTimePerformance.test.js b/tests/frontend/LoadTimePerformance.test.js index 9578e8d9..e535fdaa 100644 --- a/tests/frontend/LoadTimePerformance.test.js +++ b/tests/frontend/LoadTimePerformance.test.js @@ -28,7 +28,9 @@ vi.mock("../../meshchatx/src/frontend/js/Utils", () => ({ formatTimeAgo: (d) => "1h ago", formatDestinationHash: (h) => (h && h.length >= 8 ? h.slice(0, 8) + "…" : h), parseDestinationHash: (h) => { - const compact = String(h || "").replace(/[^0-9a-fA-F]/g, ""); + let text = String(h || "").trim(); + text = text.replace(/^(lxmf|lxma|lxm|rns):\/\//i, ""); + const compact = text.replace(/[^0-9a-fA-F]/g, ""); return compact.length >= 32 ? compact.slice(0, 32).toLowerCase() : null; }, }, diff --git a/tests/frontend/PropagationNodesPage.test.js b/tests/frontend/PropagationNodesPage.test.js index 8ea0e88c..6563d0b7 100644 --- a/tests/frontend/PropagationNodesPage.test.js +++ b/tests/frontend/PropagationNodesPage.test.js @@ -24,6 +24,7 @@ describe("PropagationNodesPage", () => { afterEach(() => { vi.useRealTimers(); + vi.unstubAllGlobals(); }); it("finds local propagation node from list", () => { @@ -253,6 +254,24 @@ describe("PropagationNodesPage", () => { expect(ctx.manualHashDraft).toBe("a39610c89d18bb48c73e429582423c24"); }); + it("pastePreferredHash sets the preferred node from clipboard text", async () => { + const ctx = { + manualHashDraft: "", + usePropagationNode: vi.fn().mockResolvedValue(true), + $t: (k) => k, + }; + vi.stubGlobal("navigator", { + clipboard: { + readText: vi.fn().mockResolvedValue(""), + }, + }); + Object.defineProperty(window, "isSecureContext", { value: true, configurable: true }); + await PropagationNodesPage.methods.pastePreferredHash.call(ctx); + expect(ctx.manualHashDraft).toBe("a39610c89d18bb48c73e429582423c24"); + expect(ctx.usePropagationNode).toHaveBeenCalledWith("a39610c89d18bb48c73e429582423c24"); + vi.unstubAllGlobals(); + }); + it("clears preferred node and draft", async () => { const ctx = { manualHashDraft: "a39610c89d18bb48c73e429582423c24",