Files
meshcore-analyzer/test-channel-ux-round2.js
T
Kpa-clawbot 67da696a42 fix(channels): hide raw psk:* in header, label share button, red delete button (#1041)
## 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>
2026-05-04 20:56:01 -07:00

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);