feat: Sprint 17 -- workspace breadcrumbs, slash commands, send key setting

Track A: Workspace breadcrumb navigation
- Breadcrumb path bar with clickable segments when inside subdirectories
- Up button in panel header for parent directory navigation
- S.currentDir state tracking; file ops stay in current directory
- New file/folder creation respects current subdirectory

Track B: Slash commands foundation
- New commands.js module (7th JS module) with command registry and parser
- Built-in commands: /help, /clear, /model, /workspace, /new
- Autocomplete dropdown on / input with arrow/tab/enter/escape navigation
- Unrecognized commands pass through to agent normally

Track C: Send key setting (closes #26)
- send_key added to settings defaults in api/config.py
- Settings panel dropdown: Enter (default) vs Ctrl/Cmd+Enter
- Keydown handler rewritten for autocomplete + send key preference
- Setting loaded on boot, persisted to settings.json

5 new tests, 242 total (219 passing, 22 pre-existing failures, 0 regressions).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nathan Esquenazi
2026-04-02 17:31:31 -07:00
parent 856f5c21e1
commit 0f2bd537f1
13 changed files with 478 additions and 22 deletions

View File

@@ -59,8 +59,37 @@ $('modelSelect').onchange=async()=>{
await api('/api/session/update',{method:'POST',body:JSON.stringify({session_id:S.session.session_id,workspace:S.session.workspace,model:selectedModel})});
S.session.model=selectedModel;syncTopbar();
};
$('msg').addEventListener('input',autoResize);
$('msg').addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send();}});
$('msg').addEventListener('input',()=>{
autoResize();
const text=$('msg').value;
if(text.startsWith('/')&&text.indexOf('\n')===-1){
const prefix=text.slice(1);
const matches=getMatchingCommands(prefix);
if(matches.length)showCmdDropdown(matches); else hideCmdDropdown();
} else {
hideCmdDropdown();
}
});
$('msg').addEventListener('keydown',e=>{
// Autocomplete navigation when dropdown is open
const dd=$('cmdDropdown');
const dropdownOpen=dd&&dd.classList.contains('open');
if(dropdownOpen){
if(e.key==='ArrowUp'){e.preventDefault();navigateCmdDropdown(-1);return;}
if(e.key==='ArrowDown'){e.preventDefault();navigateCmdDropdown(1);return;}
if(e.key==='Tab'){e.preventDefault();selectCmdDropdownItem();return;}
if(e.key==='Escape'){e.preventDefault();hideCmdDropdown();return;}
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();selectCmdDropdownItem();return;}
}
// Send key: respect user preference
if(e.key==='Enter'){
if(window._sendKey==='ctrl+enter'){
if(e.ctrlKey||e.metaKey){e.preventDefault();send();}
} else {
if(!e.shiftKey){e.preventDefault();send();}
}
}
});
// B14: Cmd/Ctrl+K creates a new chat from anywhere
document.addEventListener('keydown',async e=>{
if((e.metaKey||e.ctrlKey)&&e.key==='k'){
@@ -151,6 +180,8 @@ document.querySelectorAll('.suggestion').forEach(btn=>{
})();
(async()=>{
// Load send key preference
try{const s=await api('/api/settings');window._sendKey=s.send_key||'enter';}catch(e){window._sendKey='enter';}
// Fetch available models from server and populate dropdown dynamically
await populateModelDropdown();
// Restore last-used model preference