mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-06-05 04:21:20 +00:00
67da696a42
## Channel UX round 2 (follow-up to #1040) Three UX issues reported after #1040 landed: ### 1. Header shows raw `psk:372a9c93` for PSK channels The selected-channel title rendered `ch.name` directly, which for user-added PSK channels is the synthetic `psk:<hex8>` string. Users see opaque key fragments where they expected the friendly name they typed. **Fix:** new `channelDisplayName(ch)` helper. Returns `ch.userLabel` when set, falls back to `"Private Channel"` for any `psk:*` name, then to the original name, then to `Channel <hash>`. Used in both `selectChannel` (header) and `renderChannelRow` (sidebar). ### 2. Share button `⤴` is unrecognizable Up-arrow glyph carried no meaning — users didn't know it opened the QR/key reshare modal. **Fix:** swap `⤴` for `📤 Share` text label. Same hook, same handler. ### 3. ✕ delete button is a subtle span, not a destructive button Looked like decorative text, not a real action. **Fix:** `.ch-remove-btn` gets `background: var(--statusRed, #b54a4a)`, `color: white`, `border-radius: 4px`, `padding: 4px 8px`, `font-weight: bold`. Now reads as a destructive action. ### TDD - Red commit `2d05bbf`: 9 failing assertions (helper missing, ⤴ still present, CSS rules absent), test compiles + runs to assertion failure. - Green commit `938f3fc`: all 12 assertions pass. Existing `test-channel-ux-followup.js` still 28/28. ### Files - `public/channels.js` — `channelDisplayName` helper, header + row rendering, share button label - `public/style.css` — `.ch-remove-btn` destructive styling - `test-channel-ux-round2.js` — new test (helper behavior + source/CSS assertions) --------- Co-authored-by: openclaw-bot <bot@openclaw.dev> Co-authored-by: corescope-bot <bot@corescope.local>
88 lines
4.1 KiB
JavaScript
88 lines
4.1 KiB
JavaScript
/**
|
|
* Follow-up UX round 2 to channels (post #1040):
|
|
*
|
|
* 1. Channel header (selected-channel title) must NOT display the raw
|
|
* "psk:<hex8>" key prefix. Use the user-supplied label when present,
|
|
* otherwise fall back to "Private Channel".
|
|
* 2. Sidebar share button uses a recognizable label ("📤 Share" or
|
|
* similar), not the bare ⤴ glyph.
|
|
* 3. ✕ remove button has a red background, white text, proper button
|
|
* styling — looks like a destructive action.
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const chSrc = fs.readFileSync(path.join(__dirname, 'public/channels.js'), 'utf8');
|
|
const cssSrc = fs.readFileSync(path.join(__dirname, 'public/style.css'), 'utf8');
|
|
|
|
let passed = 0, failed = 0;
|
|
function assert(cond, msg) {
|
|
if (cond) { passed++; console.log(' ✓ ' + msg); }
|
|
else { failed++; console.error(' ✗ ' + msg); }
|
|
}
|
|
|
|
console.log('\n=== Fix 1: header display name for PSK channels ===');
|
|
// Behavior test: extract channelDisplayName helper and exercise it.
|
|
const vm = require('vm');
|
|
function extractFn(src, header) {
|
|
const start = src.indexOf(header);
|
|
if (start < 0) return null;
|
|
let depth = 0, i = src.indexOf('{', start);
|
|
if (i < 0) return null;
|
|
for (let j = i; j < src.length; j++) {
|
|
const c = src[j];
|
|
if (c === '{') depth++;
|
|
else if (c === '}') { depth--; if (depth === 0) return src.substring(start, j + 1); }
|
|
}
|
|
return null;
|
|
}
|
|
const helperSrc = extractFn(chSrc, 'function channelDisplayName(ch');
|
|
assert(helperSrc, 'channelDisplayName helper exists');
|
|
if (helperSrc) {
|
|
const sandbox = { formatHashHex: h => h, PRIVATE_CHANNEL_LABEL: 'Private Channel' };
|
|
vm.createContext(sandbox);
|
|
vm.runInContext('const PRIVATE_CHANNEL_LABEL = "Private Channel";\n' + helperSrc, sandbox);
|
|
assert(sandbox.channelDisplayName({ name: 'psk:372a9c93', userLabel: 'My Crew' }) === 'My Crew',
|
|
'psk:* with userLabel returns the userLabel');
|
|
assert(sandbox.channelDisplayName({ name: 'psk:372a9c93' }) === 'Private Channel',
|
|
'psk:* without label returns "Private Channel"');
|
|
assert(sandbox.channelDisplayName({ name: '#meshcore' }) === '#meshcore',
|
|
'non-PSK names pass through unchanged');
|
|
assert(sandbox.channelDisplayName({ hash: 'abc', name: '' }) === 'Channel abc',
|
|
'falls back to "Channel <hash>" when name missing');
|
|
assert(sandbox.channelDisplayName({ hash: 'abc', name: '' }, 'Unknown') === 'Unknown',
|
|
'caller-supplied fallback overrides "Channel <hash>" default');
|
|
assert(sandbox.channelDisplayName({ name: 'psk:abc' }, 'Unknown') === 'Private Channel',
|
|
'fallback does NOT override the psk:* → "Private Channel" rule');
|
|
}
|
|
// Source-level: header rendering must call channelDisplayName, not raw ch.name.
|
|
assert(/channelDisplayName\(ch\)/.test(chSrc),
|
|
'selectChannel header rendering uses channelDisplayName(ch)');
|
|
|
|
console.log('\n=== Fix 2: share button has recognizable label ===');
|
|
assert(!/'⤴'/.test(chSrc) && !/"⤴"/.test(chSrc),
|
|
'bare ⤴ glyph no longer used as the share button content');
|
|
// Tighten: assert the literal '📤 Share' string is the glyph argument
|
|
// passed into the iconBtn(...) call for ch-share-btn — this catches the
|
|
// case where someone removes the icon from the button content but leaves
|
|
// "Share" in an aria-label or title.
|
|
assert(/iconBtn\(\s*'ch-share-btn'[^)]*'📤 Share'/.test(chSrc),
|
|
"iconBtn('ch-share-btn', ...) is called with '📤 Share' as the glyph");
|
|
|
|
console.log('\n=== Fix 3: ✕ delete button is a visibly red destructive button ===');
|
|
const removeRule = (cssSrc.match(/\.ch-remove-btn\s*\{[^}]*\}/) || [''])[0];
|
|
assert(/background:\s*var\(--statusRed/.test(removeRule) || /background:\s*#b54a4a/.test(removeRule),
|
|
'.ch-remove-btn has red background (var(--statusRed,...) or #b54a4a)');
|
|
assert(/color:\s*white/.test(removeRule) || /color:\s*#fff/.test(removeRule),
|
|
'.ch-remove-btn has white text');
|
|
assert(/border-radius:/.test(removeRule),
|
|
'.ch-remove-btn has border-radius (button shape)');
|
|
assert(/font-weight:\s*bold|font-weight:\s*700/.test(removeRule),
|
|
'.ch-remove-btn has bold font-weight');
|
|
|
|
console.log('\n=== Results ===');
|
|
console.log('Passed: ' + passed + ', Failed: ' + failed);
|
|
process.exit(failed > 0 ? 1 : 0);
|