fix: project picker clipped, full-screen width bug, New Project shortcut
Five fixes to the Sprint 15 Move to Project picker: 1. CRITICAL: Picker was invisible (overflow:hidden clipping) Appended to document.body + positioned with fixed/getBoundingClientRect instead of inside .session-item (overflow:hidden). Flips above button when near bottom of viewport. 2. CRITICAL: Picker stretched full screen width position:fixed removed the containing block width constraint. Added max-width:220px; width:max-content to .project-picker. 3. UX: No way to create a project from the picker Added '+ New project': creates project and moves session in one click. 4. UX: Feature was undiscoverable Folder button shows persistently (blue, 60% opacity) when session has a project. 5. Minor: Event listener leak removeEventListener was missing from picker item onclick handlers. Tests: 237 passed (7 pre-existing failures from unrelated logger bug).
This commit is contained in:
@@ -287,7 +287,8 @@ function renderSessionListFromCache(){
|
||||
trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);};
|
||||
// Project move button (folder icon)
|
||||
const move=document.createElement('button');
|
||||
move.className='session-action-btn'+(s.project_id?' has-project':'');move.innerHTML='📂';move.title='Move to project';
|
||||
move.className='session-action-btn'+(s.project_id?' has-project':'');
|
||||
move.innerHTML='📂';move.title='Move to project';
|
||||
move.onclick=async(e)=>{e.stopPropagation();e.preventDefault();_showProjectPicker(s,move);};
|
||||
// Project dot indicator
|
||||
if(s.project_id){
|
||||
@@ -360,15 +361,13 @@ function _showProjectPicker(session, anchorEl){
|
||||
document.querySelectorAll('.project-picker').forEach(p=>p.remove());
|
||||
const picker=document.createElement('div');
|
||||
picker.className='project-picker';
|
||||
// Close on outside click
|
||||
const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}};
|
||||
// "No project" option
|
||||
const none=document.createElement('div');
|
||||
none.className='project-picker-item'+(!session.project_id?' active':'');
|
||||
none.textContent='No project';
|
||||
none.onclick=async()=>{
|
||||
document.removeEventListener('click',close);
|
||||
picker.remove();
|
||||
document.removeEventListener('click',close);
|
||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:null})});
|
||||
session.project_id=null;
|
||||
renderSessionListFromCache();
|
||||
@@ -389,8 +388,8 @@ function _showProjectPicker(session, anchorEl){
|
||||
name.textContent=p.name;
|
||||
item.appendChild(name);
|
||||
item.onclick=async()=>{
|
||||
document.removeEventListener('click',close);
|
||||
picker.remove();
|
||||
document.removeEventListener('click',close);
|
||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:p.project_id})});
|
||||
session.project_id=p.project_id;
|
||||
renderSessionListFromCache();
|
||||
@@ -398,19 +397,21 @@ function _showProjectPicker(session, anchorEl){
|
||||
};
|
||||
picker.appendChild(item);
|
||||
}
|
||||
// "+ New project" item
|
||||
// "+ New project" shortcut at the bottom
|
||||
const createItem=document.createElement('div');
|
||||
createItem.className='project-picker-item project-picker-create';
|
||||
createItem.textContent='+ New project';
|
||||
createItem.onclick=async()=>{
|
||||
picker.remove();
|
||||
document.removeEventListener('click',close);
|
||||
// Prompt for name inline
|
||||
const name=prompt('Project name:');
|
||||
if(!name||!name.trim()) return;
|
||||
const color=PROJECT_COLORS[_allProjects.length%PROJECT_COLORS.length];
|
||||
const res=await api('/api/projects/create',{method:'POST',body:JSON.stringify({name:name.trim(),color})});
|
||||
if(res.project){
|
||||
_allProjects.push(res.project);
|
||||
// Now move session into it
|
||||
await api('/api/session/move',{method:'POST',body:JSON.stringify({session_id:session.session_id,project_id:res.project.project_id})});
|
||||
session.project_id=res.project.project_id;
|
||||
await renderSessionList();
|
||||
@@ -418,11 +419,13 @@ function _showProjectPicker(session, anchorEl){
|
||||
}
|
||||
};
|
||||
picker.appendChild(createItem);
|
||||
// Position picker on document.body to avoid overflow:hidden clipping
|
||||
// Append to body and position using getBoundingClientRect so it isn't clipped
|
||||
// by overflow:hidden on .session-item ancestors
|
||||
document.body.appendChild(picker);
|
||||
const rect=anchorEl.getBoundingClientRect();
|
||||
picker.style.position='fixed';
|
||||
picker.style.zIndex='999';
|
||||
// Prefer opening below; flip above if too close to bottom of viewport
|
||||
const spaceBelow=window.innerHeight-rect.bottom;
|
||||
if(spaceBelow<160&&rect.top>160){
|
||||
picker.style.bottom=(window.innerHeight-rect.top+4)+'px';
|
||||
@@ -431,10 +434,13 @@ function _showProjectPicker(session, anchorEl){
|
||||
picker.style.top=(rect.bottom+4)+'px';
|
||||
picker.style.bottom='auto';
|
||||
}
|
||||
const pickerW=160;
|
||||
// Align right edge of picker with right edge of button; keep within viewport
|
||||
const pickerW=Math.min(220,Math.max(160,picker.scrollWidth||160));
|
||||
let left=rect.right-pickerW;
|
||||
if(left<8) left=8;
|
||||
picker.style.left=left+'px';
|
||||
// Close on outside click
|
||||
const close=(e)=>{if(!picker.contains(e.target)&&e.target!==anchorEl){picker.remove();document.removeEventListener('click',close);}};
|
||||
setTimeout(()=>document.addEventListener('click',close),0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user