mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-12 14:54:42 +00:00
## Summary Strips the Share Channel modal (shipped in #1090) down to its essentials. Removes redundant affordances that the QR already provides. ## What changed **Removed from the Share modal:** - The URL text printed inside the QR box (the QR encodes the URL) - The inline Copy Key button inside the QR box (overlapped the image) - The `meshcore://` URL input field below the QR - The Copy URL button next to the URL field **Result — the modal now contains exactly:** - Title `Share: <Channel Name>` - QR code (just the QR `<img>`, nothing else in that box) - Hex Key field with a single Copy button BELOW the QR - Privacy warning - ✕ close button (top right) ## Implementation - `public/channels.js` — drop the `meshcore://` URL field-group from share modal markup; `openShareModal()` no longer looks up `#chShareUrl` or builds a URL into a field; pass `{ qrOnly: true }` when calling `ChannelQR.generate` so the QR box renders ONLY the QR image. - `public/channel-qr.js` — `generate(name, secret, target, opts)` now accepts `opts.qrOnly` which short-circuits before appending the inline URL line + Copy Key button. Default behaviour (no opts) unchanged, so the Add-Channel "Generate & Show QR" flow is untouched. ## Tests (TDD: red → green) - New: `test-channel-issue-1101.js` (static grep) — asserts the URL field is gone from markup, `openShareModal` no longer references it, and `ChannelQR.generate` honours `qrOnly`. - Updated: `test-channel-issue-1087.js` and `test-channel-issue-1087-e2e.js` — those previously asserted the URL field's presence (which is exactly what #1101 removes); they now assert ONLY the hex key field exists, AND that `#chShareQr` contains exactly one `<img>` and no `.channel-qr-url` / `.channel-qr-copy` children. - Wired into `.github/workflows/deploy.yml` `node-test` job. Commit history shows red (test commit `c0c254a`) → green (fix commit `6315a19`) per AGENTS.md TDD requirement. E2E assertion added: test-channel-issue-1087-e2e.js:184 ## Acceptance criteria - [x] Share modal contains only: QR, "Copy Key" button, privacy warning - [x] No "Copy URL" affordance anywhere in the modal - [x] No duplicated hex key field below - [x] E2E test asserts the absence of the removed elements Fixes #1101 --------- Co-authored-by: meshcore-bot <bot@meshcore.local> Co-authored-by: clawbot <clawbot@users.noreply.github.com>
This commit is contained in:
@@ -90,6 +90,7 @@ jobs:
|
||||
node test-channel-qr-wiring.js
|
||||
node test-channel-modal-ux.js
|
||||
node test-channel-issue-1087.js
|
||||
node test-channel-issue-1101.js
|
||||
node test-pull-to-reconnect-1091.js
|
||||
node test-channel-fluid-layout.js
|
||||
|
||||
|
||||
@@ -75,9 +75,11 @@
|
||||
* every Generate click fall through to "[QR library not loaded]".
|
||||
* (Issue #1087 bug 1.)
|
||||
*/
|
||||
function generate(name, secretHex, target) {
|
||||
function generate(name, secretHex, target, opts) {
|
||||
if (!_hasDom() || !target) return;
|
||||
target.innerHTML = '';
|
||||
opts = opts || {};
|
||||
var qrOnly = !!opts.qrOnly;
|
||||
|
||||
const url = buildUrl(name, secretHex);
|
||||
|
||||
@@ -113,6 +115,12 @@
|
||||
qrBox.textContent = '[QR library not loaded]';
|
||||
}
|
||||
|
||||
// #1101: in qrOnly mode (Share modal), the host renders the hex
|
||||
// key field + Copy button BELOW the QR. Skip the inline URL line
|
||||
// and inline Copy Key button here so the QR box contains JUST the
|
||||
// QR image — no overlap, no redundant affordances.
|
||||
if (qrOnly) return;
|
||||
|
||||
const urlLine = document.createElement('div');
|
||||
urlLine.className = 'channel-qr-url';
|
||||
urlLine.style.cssText = 'font-family:monospace;font-size:11px;word-break:break-all;margin-top:6px;';
|
||||
|
||||
+9
-16
@@ -759,13 +759,6 @@
|
||||
<button type="button" class="ch-modal-btn-secondary" data-share-copy="key" aria-label="Copy hex key">📋 Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ch-share-field-group">
|
||||
<label class="ch-share-label" for="chShareUrl">meshcore:// URL</label>
|
||||
<div class="ch-share-row">
|
||||
<input type="text" id="chShareUrl" data-share-field="url" class="ch-modal-input ch-modal-input--mono" readonly aria-label="Channel meshcore URL">
|
||||
<button type="button" class="ch-modal-btn-secondary" data-share-copy="url" aria-label="Copy meshcore URL">📋 Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ch-modal-warn" role="note">
|
||||
⚠ Privacy: only share with trusted people. Anyone with this key can read all messages on this channel.
|
||||
</div>
|
||||
@@ -882,19 +875,19 @@
|
||||
if (title) title.textContent = 'Share: ' + safeName;
|
||||
var qrHolder = document.getElementById('chShareQr');
|
||||
var keyField = document.getElementById('chShareKey');
|
||||
var urlField = document.getElementById('chShareUrl');
|
||||
var fieldsWrap = shareModalEl.querySelectorAll('.ch-share-field-group');
|
||||
for (var i = 0; i < fieldsWrap.length; i++) fieldsWrap[i].hidden = false;
|
||||
var url = 'meshcore://channel/add?name=' + encodeURIComponent(safeName) +
|
||||
'&secret=' + keyHex;
|
||||
if (keyField) keyField.value = keyHex;
|
||||
if (urlField) urlField.value = url;
|
||||
if (qrHolder) {
|
||||
qrHolder.innerHTML = '';
|
||||
if (window.ChannelQR && typeof window.ChannelQR.generate === 'function') {
|
||||
// #1087 Bug 2: pass the user-facing displayName, NOT the
|
||||
// internal `psk:<hex8>` channelName lookup key.
|
||||
window.ChannelQR.generate(safeName, keyHex, qrHolder);
|
||||
// #1101: qrOnly=true — render JUST the QR image. The Share
|
||||
// modal has its own dedicated hex key field + Copy button
|
||||
// BELOW the QR; an inline URL line + Copy Key button inside
|
||||
// the QR box was redundant and visually overlapping.
|
||||
window.ChannelQR.generate(safeName, keyHex, qrHolder, { qrOnly: true });
|
||||
}
|
||||
}
|
||||
shareModalEl.classList.remove('hidden');
|
||||
@@ -946,10 +939,10 @@
|
||||
var copyBtn = e.target.closest && e.target.closest('[data-share-copy]');
|
||||
if (copyBtn) {
|
||||
e.preventDefault();
|
||||
var which = copyBtn.getAttribute('data-share-copy');
|
||||
var src = which === 'url'
|
||||
? document.getElementById('chShareUrl')
|
||||
: document.getElementById('chShareKey');
|
||||
// #1101: only the hex key is copyable from the share modal;
|
||||
// the URL field was removed, so the data-share-copy attribute
|
||||
// is informational only — the source is always #chShareKey.
|
||||
var src = document.getElementById('chShareKey');
|
||||
if (src) {
|
||||
try { src.select(); } catch (e2) {}
|
||||
var doneCopy = function () {
|
||||
|
||||
@@ -160,15 +160,36 @@ function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); }
|
||||
assert(/share/i.test(shareTitle),
|
||||
'Bug 4: share modal title must contain "Share", got: ' + shareTitle);
|
||||
|
||||
// Hex key + URL fields must be present and copyable.
|
||||
// Hex key field must be present and copyable. (#1101: URL field
|
||||
// removed — QR already encodes the URL, a separate Copy URL button
|
||||
// was redundant.)
|
||||
const hasFields = await page.evaluate(() => {
|
||||
const m = document.getElementById('chShareModal');
|
||||
if (!m) return false;
|
||||
const k = m.querySelector('#chShareKey, [data-share-field="key"]');
|
||||
const u = m.querySelector('#chShareUrl, [data-share-field="url"]');
|
||||
return !!(k && u);
|
||||
return !!k && !u;
|
||||
});
|
||||
assert(hasFields, 'Bug 4: share modal must expose hex key + URL fields');
|
||||
assert(hasFields, 'Bug 4 / #1101: share modal exposes ONLY the hex key field (no URL field)');
|
||||
|
||||
// #1101: the QR box must contain ONLY the QR <img> — no URL text
|
||||
// line, no inline Copy Key button overlapping the image.
|
||||
const qrBoxOnlyHasQr = await page.evaluate(() => {
|
||||
const qr = document.getElementById('chShareQr');
|
||||
if (!qr) return { ok: false, reason: 'no #chShareQr' };
|
||||
const imgs = qr.querySelectorAll('img');
|
||||
const urlLine = qr.querySelector('.channel-qr-url');
|
||||
const copyBtn = qr.querySelector('.channel-qr-copy, button');
|
||||
return {
|
||||
ok: imgs.length === 1 && !urlLine && !copyBtn,
|
||||
imgCount: imgs.length,
|
||||
hasUrlLine: !!urlLine,
|
||||
hasCopyBtn: !!copyBtn,
|
||||
};
|
||||
});
|
||||
assert(qrBoxOnlyHasQr.ok,
|
||||
'#1101: #chShareQr contains ONLY the QR image (got ' +
|
||||
JSON.stringify(qrBoxOnlyHasQr) + ')');
|
||||
});
|
||||
|
||||
console.log('\n=== Results: ' + passed + ' passed, ' + failed + ' failed ===');
|
||||
|
||||
@@ -120,8 +120,8 @@ assert(/id="chShareModalTitle"|class="ch-share-modal-title"|>Share[^<]*</.test(s
|
||||
'share modal has its own title element ("Share: <Channel Name>")');
|
||||
assert(/id="chShareKey"|data-share-field="key"/.test(shareModalBlock),
|
||||
'share modal exposes the hex key field with a copy affordance');
|
||||
assert(/id="chShareUrl"|data-share-field="url"/.test(shareModalBlock),
|
||||
'share modal exposes the meshcore:// URL field with a copy affordance');
|
||||
// #1101: meshcore:// URL field intentionally REMOVED — QR already
|
||||
// encodes the URL, separate field/button was redundant.
|
||||
assert(/trusted|privacy|do not share|only share/i.test(shareModalBlock),
|
||||
'share modal includes a privacy warning');
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* #1101 — Strip Share modal: remove redundant URL copy + duplicated key field.
|
||||
*
|
||||
* Acceptance criteria:
|
||||
* - Share modal contains only: QR (just the QR image, nothing else
|
||||
* in that box), Hex Key field with single Copy button BELOW the QR,
|
||||
* privacy warning, Close ✕ button.
|
||||
* - No "Copy URL" affordance ANYWHERE in the modal.
|
||||
* - No duplicated meshcore:// URL field below the QR.
|
||||
* - The QR box (#chShareQr) must contain ONLY the QR image — no URL
|
||||
* text, no Copy Key button overlapping it.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let passed = 0, failed = 0;
|
||||
function assert(cond, msg) {
|
||||
if (cond) { passed++; console.log(' ✓ ' + msg); }
|
||||
else { failed++; console.error(' ✗ ' + msg); }
|
||||
}
|
||||
|
||||
const channelsSrc = fs.readFileSync(path.join(__dirname, 'public', 'channels.js'), 'utf8');
|
||||
const qrSrc = fs.readFileSync(path.join(__dirname, 'public', 'channel-qr.js'), 'utf8');
|
||||
|
||||
console.log('\n=== #1101: Share modal markup ===');
|
||||
|
||||
// Locate the share modal markup block.
|
||||
const shareModalIdx = channelsSrc.indexOf('id="chShareModal"');
|
||||
assert(shareModalIdx > 0, 'share modal markup located');
|
||||
// Tighten block isolation: scan forward for the share modal's own
|
||||
// closing tag (the outer overlay div is indented 6 spaces, so its
|
||||
// matching close is the first "\n </div>" we hit after the
|
||||
// opener). Falls back to the old ch-main heuristic if that pattern
|
||||
// disappears for any reason.
|
||||
let shareEnd = channelsSrc.indexOf('\n </div>', shareModalIdx);
|
||||
if (shareEnd < 0) {
|
||||
shareEnd = channelsSrc.indexOf('<div class="ch-main"', shareModalIdx);
|
||||
}
|
||||
const shareModalBlock = channelsSrc.substring(shareModalIdx, shareEnd);
|
||||
assert(shareModalBlock.length > 0 && shareModalBlock.length < 4000,
|
||||
'share modal block isolated');
|
||||
|
||||
// Hex key field MUST still be present (single source of truth).
|
||||
assert(/id="chShareKey"/.test(shareModalBlock),
|
||||
'share modal still exposes the hex key field with a Copy button');
|
||||
|
||||
// meshcore:// URL field MUST be removed.
|
||||
assert(!/id="chShareUrl"/.test(shareModalBlock),
|
||||
'share modal does NOT render a #chShareUrl input field');
|
||||
assert(!/data-share-field="url"/.test(shareModalBlock),
|
||||
'share modal does NOT render any [data-share-field="url"] element');
|
||||
assert(!/data-share-copy="url"/.test(shareModalBlock),
|
||||
'share modal does NOT render any [data-share-copy="url"] button');
|
||||
assert(!/meshcore:\/\/ URL/.test(shareModalBlock),
|
||||
'share modal does NOT show a "meshcore:// URL" label');
|
||||
|
||||
// Privacy warning + close button still required.
|
||||
assert(/ch-modal-warn/.test(shareModalBlock),
|
||||
'share modal still includes the privacy warning');
|
||||
assert(/id="chShareModalClose"/.test(shareModalBlock),
|
||||
'share modal still has the ✕ close button');
|
||||
|
||||
console.log('\n=== #1101: openShareModal() body ===');
|
||||
|
||||
// openShareModal must no longer reference chShareUrl or build URL into a field.
|
||||
const openIdx = channelsSrc.indexOf('function openShareModal(');
|
||||
assert(openIdx > 0, 'openShareModal located');
|
||||
const openEnd = channelsSrc.indexOf('function ', openIdx + 30);
|
||||
const openBlock = channelsSrc.substring(openIdx, openEnd);
|
||||
assert(!/getElementById\('chShareUrl'\)/.test(openBlock),
|
||||
'openShareModal does NOT look up #chShareUrl');
|
||||
assert(!/urlField\.value\s*=/.test(openBlock),
|
||||
'openShareModal does NOT assign to urlField.value');
|
||||
|
||||
console.log('\n=== #1101: ChannelQR.generate() supports qrOnly ===');
|
||||
|
||||
// ChannelQR.generate must accept an opts.qrOnly flag so the Share
|
||||
// modal's QR box can render JUST the QR image — no URL line, no
|
||||
// inline Copy Key button. (The Share modal has its own dedicated
|
||||
// hex key field + Copy button BELOW the QR.)
|
||||
assert(/function generate\([^)]*opts[^)]*\)/.test(qrSrc),
|
||||
'ChannelQR.generate accepts an opts argument');
|
||||
assert(/qrOnly/.test(qrSrc),
|
||||
'ChannelQR.generate honours opts.qrOnly');
|
||||
|
||||
// Share modal call site must pass qrOnly:true.
|
||||
assert(/ChannelQR\.generate\([^)]*qrOnly[^)]*\)/.test(channelsSrc) ||
|
||||
/ChannelQR\.generate\([\s\S]{0,200}qrOnly\s*:\s*true/.test(channelsSrc),
|
||||
'openShareModal passes { qrOnly: true } to ChannelQR.generate');
|
||||
|
||||
console.log('\n=== Results: ' + passed + ' passed, ' + failed + ' failed ===');
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user