v0.47.0: dialogs, session menu, /skills, mobile fixes, mobile QA suite
* fix: custom provider with slash model name no longer rerouted to OpenRouter (#255) When base_url is configured in config.yaml, resolve_model_provider() now trusts the configured provider/base_url entirely and skips the slash-based OpenRouter heuristic. Fixes google/gemma-4-26b-a4b with provider:custom being silently routed to OpenRouter, resulting in 401 errors. Fixes #230 * test: mobile layout regression suite — 14 tests for every QA run (#254) Adds tests/test_mobile_layout.py with 14 static regression tests that run on every QA pass to catch mobile layout breakage before it reaches prod. Covers: breakpoints at 900px/640px, right panel slide-over CSS, mobile overlay, bottom nav, files button, profile dropdown z-index, chip overflow, workspace close, 100dvh, 44px touch targets, 16px font-size on textarea. * feat: /skills slash command lists and filters available Hermes skills (#257) Adds /skills [query] command to commands.js. Fetches from /api/skills, groups by category (alphabetically sorted), displays as a formatted assistant message. Optional query filters by name, description, or category. i18n keys added for en, de, zh, zh-Hant. 1 regression test added. Fixes #248 * feat: shared app dialogs replace native confirm()/prompt() calls (#251) Adds showConfirmDialog() and showPromptDialog() helpers to ui.js, backed by a themed #appDialogOverlay. Replaces all 11 native browser confirm/prompt call sites across panels.js, sessions.js, ui.js, workspace.js. Supports: danger mode, keyboard focus trap (Tab/Escape/Enter), focus restore, ARIA roles, mobile-responsive stacked buttons at 640px. i18n for en/de/zh/zh-Hant. 5 new tests in test_sprint33.py verify markup, CSS, helpers, and absence of native dialog calls. Extracted from PR #242. * fix: Android Chrome mobile — workspace panel close + profile dropdown (#256) Fix #247: toggleMobileFiles() now shows/hides the mobile overlay when toggling the right workspace panel. New closeMobileFiles() helper closes the panel with correct overlay state tracking. Overlay onclick calls both closeMobileSidebar() and closeMobileFiles(). Mobile-only close button (x) added to workspace panel header. Fix #246: profile dropdown uses position:fixed;top:56px;right:8px at max-width:900px, escaping the overflow-x:auto stacking context that was clipping it on Android Chrome. Fix applied during review: closeMobileSidebar() now checks if the right panel is still open before hiding the overlay, preventing the overlay from disappearing when only the sidebar is closed. Fixes #247 Fixes #246 * feat: session ⋯ action dropdown replaces per-row buttons (#252) Replaces the 5 per-row hover action buttons (pin/move/archive/duplicate/trash) with a single ⋯ trigger that opens a positioned dropdown menu. Menu has full keyboard (Escape), click-outside, scroll, and resize-reposition handling. Position:fixed prevents sidebar clipping. 5 actions: Pin/Unpin, Move to project, Archive/Unarchive, Duplicate, Delete (danger style). Each with icon and descriptive subtitle. Updated test_sprint16.py: test_sessions_js_uses_action_menu_not_per_row_buttons asserts the new trigger and menu functions exist, old per-row classes are gone. Extracted from PR #242. * docs: v0.47.0 release notes, bump version, update test counts (645) --------- Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
@@ -12,6 +12,7 @@ const COMMANDS=[
|
||||
{name:'usage', desc:t('cmd_usage'), fn:cmdUsage},
|
||||
{name:'theme', desc:t('cmd_theme'), fn:cmdTheme, arg:'name'},
|
||||
{name:'personality', desc:t('cmd_personality'), fn:cmdPersonality, arg:'name'},
|
||||
{name:'skills', desc:t('cmd_skills'), fn:cmdSkills, arg:'query'},
|
||||
];
|
||||
|
||||
function parseCommand(text){
|
||||
@@ -140,6 +141,49 @@ async function cmdTheme(args){
|
||||
showToast(t('theme_set')+themeName);
|
||||
}
|
||||
|
||||
async function cmdSkills(args){
|
||||
try{
|
||||
const data = await api('/api/skills');
|
||||
let skills = data.skills || [];
|
||||
if(args){
|
||||
const q = args.toLowerCase();
|
||||
skills = skills.filter(s =>
|
||||
(s.name||'').toLowerCase().includes(q) ||
|
||||
(s.description||'').toLowerCase().includes(q) ||
|
||||
(s.category||'').toLowerCase().includes(q)
|
||||
);
|
||||
}
|
||||
if(!skills.length){
|
||||
const msg = {role:'assistant', content: args ? `No skills matching "${args}".` : 'No skills found.'};
|
||||
S.messages.push(msg); renderMessages(); return;
|
||||
}
|
||||
// Group by category
|
||||
const byCategory = {};
|
||||
skills.forEach(s => {
|
||||
const cat = s.category || 'General';
|
||||
if(!byCategory[cat]) byCategory[cat] = [];
|
||||
byCategory[cat].push(s);
|
||||
});
|
||||
const lines = [];
|
||||
for(const [cat, items] of Object.entries(byCategory).sort()){
|
||||
lines.push(`**${cat}**`);
|
||||
items.forEach(s => {
|
||||
const desc = s.description ? ` — ${s.description.slice(0,80)}${s.description.length>80?'...':''}` : '';
|
||||
lines.push(` \`${s.name}\`${desc}`);
|
||||
});
|
||||
lines.push('');
|
||||
}
|
||||
const header = args
|
||||
? `Skills matching "${args}" (${skills.length}):\n\n`
|
||||
: `Available skills (${skills.length}):\n\n`;
|
||||
S.messages.push({role:'assistant', content: header + lines.join('\n')});
|
||||
renderMessages();
|
||||
showToast(t('type_slash'));
|
||||
}catch(e){
|
||||
showToast('Failed to load skills: '+e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function cmdPersonality(args){
|
||||
if(!S.session){showToast(t('no_active_session'));return;}
|
||||
if(!args){
|
||||
|
||||
Reference in New Issue
Block a user