mirror of
https://github.com/ruvnet/RuView.git
synced 2026-10-18 21:00:22 -05:00
fix(ruview-live): pane hotkeys need the keyboard — ask for focus, say how
Button hotkeys reach a Pane only while it holds the keyboard; the pane opened without it, so 1/2/3/r/c went to the prompt. Open (and /ruview waterfall|radar) now request `focus: true`; closeOnEscape is dropped so Escape hands the keys back without closing; an unfocused pane shows "click the pane or press ctrl+x tab to use the keys". Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
1 parent
751b7ae17b
commit
78a1474057
4 files changed
+27
-5
No files matched your search
@@ -143,7 +143,10 @@ export function register(on, options = {}) {
|
||||
|
||||
async function open(initialMode) {
|
||||
if (MODES.includes(initialMode)) mode = initialMode;
|
||||
await host.open({ id: PANE_ID, title: 'RuView', closeOnEscape: true });
|
||||
// Ask for the keyboard so the view keys (1/2/3, r, c) work at once; the
|
||||
// surface grants it only over an empty prompt. No closeOnEscape: Escape
|
||||
// hands the keys back without closing the pane.
|
||||
await host.open({ id: PANE_ID, title: 'RuView', focus: true });
|
||||
stop();
|
||||
startPolling();
|
||||
}
|
||||
@@ -176,7 +179,10 @@ export function register(on, options = {}) {
|
||||
if (arg === 'off') { await close(); host.status(undefined); return { text: 'RuView pane closed.' }; }
|
||||
if (arg === 'refresh') { await refresh(); return { text: statusOf(model) }; }
|
||||
if (MODES.includes(arg)) {
|
||||
if (isOpen) setMode(arg); else await open(arg);
|
||||
if (isOpen) {
|
||||
setMode(arg);
|
||||
await host.open({ id: PANE_ID, title: 'RuView', focus: true }).catch(() => undefined);
|
||||
} else await open(arg);
|
||||
return { text: `RuView pane: ${arg} view.` };
|
||||
}
|
||||
if (isOpen) { await close(); return { text: 'RuView pane closed.' }; }
|
||||
@@ -198,6 +204,7 @@ export function register(on, options = {}) {
|
||||
const now = await $.clock.now();
|
||||
return viewOf(ui, shownModel(), {
|
||||
...drawOpts(now), refreshMs: settings.refreshMs, liveRefreshMs: settings.liveRefreshMs, busy, now,
|
||||
focused: e.props?.isFocused !== false,
|
||||
udpPort: settings.udpPort, radarConfigured: Boolean(settings.radarHost),
|
||||
onRefresh: () => { void refresh(); },
|
||||
onClose: () => { void close(); },
|
||||
|
||||
@@ -84,9 +84,13 @@ export function viewOf(ui, model, opts) {
|
||||
]);
|
||||
const rule = pics.shimmer ? ui.Raster(pics.shimmer.grid.toRaster('shimmer')) : null;
|
||||
|
||||
const tabs = opts.onMode ? row(TABS.map(([m, key, label]) => Button({
|
||||
key: `tab-${m}`, hotkey: key, label: `${m === mode ? '▸ ' : ''}${label} (${key})`, onPress: () => opts.onMode(m),
|
||||
})), { gap: 2 }) : null;
|
||||
// Hotkeys reach a Pane only while it holds the keyboard; say how to give it.
|
||||
const tabs = opts.onMode ? row([
|
||||
...TABS.map(([m, key, label]) => Button({
|
||||
key: `tab-${m}`, hotkey: key, label: `${m === mode ? '▸ ' : ''}${label} (${key})`, onPress: () => opts.onMode(m),
|
||||
})),
|
||||
opts.focused === false ? t('click the pane or press ctrl+x tab to use the keys', { dimColor: true, italic: true }) : null,
|
||||
], { gap: 2 }) : null;
|
||||
|
||||
const alerts = (model?.alerts || []).map((a) => Text({ color: a.level === 'bad' ? 'red' : 'yellow', wrap: 'wrap', children: `! ${a.text}` }));
|
||||
|
||||
|
||||
@@ -26,8 +26,10 @@ describe('register', () => {
|
||||
runs.push(e.argv)
|
||||
return { value: { exitCode: 0, stdout: JSON.stringify(CAPTURE), stderr: '' } }
|
||||
})
|
||||
const focusAsked: (true | undefined)[] = []
|
||||
on('ui.open', ($, e) => {
|
||||
opened.push(e.id)
|
||||
focusAsked.push(e.focus)
|
||||
return { value: undefined }
|
||||
})
|
||||
on('ui.close', ($, e) => {
|
||||
@@ -50,6 +52,7 @@ describe('register', () => {
|
||||
|
||||
expect(text).toContain('RuView pane open')
|
||||
expect(opened).toEqual(['ruview-live'])
|
||||
expect(focusAsked).toEqual([true]) // hotkeys reach a Pane only while it holds the keyboard
|
||||
expect(runs.length).toBeGreaterThanOrEqual(1)
|
||||
const argv = runs[0] ?? []
|
||||
expect(argv[0]).toBe('node')
|
||||
@@ -109,6 +112,7 @@ describe('register', () => {
|
||||
const again = await $.ui.mount({ plugin: 'ruview-live', surface: 'terminal', component: 'Pane', requestId: 'ruview-live', props: PANE })
|
||||
expect(await again.find({ type: 'Text', text: /NODES/ })).toBeDefined()
|
||||
expect(await again.find({ type: 'Text', text: /waiting for the first capture/ })).toBeUndefined()
|
||||
expect(await again.find({ type: 'Text', text: /ctrl\+x tab/ })).toBeDefined() // unfocused: says how to focus
|
||||
const polled = runs.length
|
||||
// Step time as a live session does (the animation timer runs every 80 ms).
|
||||
for (let s = 0; s < 16; s++) {
|
||||
|
||||
@@ -160,3 +160,10 @@ test('spectrum requests, merging across captures, and live commands', () => {
|
||||
assert.deepEqual(merged[0].frames, [[2], [3]], 'frames carry across captures, bounded');
|
||||
assert.deepEqual(spectrumWith(merged, [{ ...a, bins: 8, frames: [[9]] }])[0].frames, [[9]], 'a new bin count starts over');
|
||||
});
|
||||
|
||||
test('an unfocused pane says how to give it the keys; a focused one does not', () => {
|
||||
const base = { mode: 'overview', refreshMs: 15000, columns: 120, rows: 34, onMode() {}, onRefresh() {}, onClose() {} };
|
||||
const model = modelOf(spectrumCapture(false), null, 0);
|
||||
assert.match(JSON.stringify(viewOf(UI, model, { ...base, focused: false })), /ctrl\+x tab to use the keys/);
|
||||
assert.doesNotMatch(JSON.stringify(viewOf(UI, model, { ...base, focused: true })), /ctrl\+x tab/);
|
||||
});
|
||||
Reference in new issue
Block a user