Merge pull request #20 from nesquena/sprint-16-sidebar-polish
Sprint 16: Session sidebar visual polish — overlay actions, SVG icons, project borders
This commit is contained in:
18
BUGS.md
Normal file
18
BUGS.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Bugs Backlog
|
||||||
|
|
||||||
|
This file tracks UI bugs and polish items to address in a future sprint.
|
||||||
|
|
||||||
|
## ~~Conversation list title truncation / hover actions~~ — Fixed (Sprint 16)
|
||||||
|
|
||||||
|
- **Was:** Action icons reserved ~30px of space even when invisible, truncating titles.
|
||||||
|
- **Fix:** Wrapped all action buttons in a `.session-actions` overlay container with `position:absolute`. Titles now use full available width. Actions appear on hover with a gradient fade from the right edge.
|
||||||
|
|
||||||
|
## ~~Folder/project assignment interaction feels sticky~~ — Fixed (Sprint 16)
|
||||||
|
|
||||||
|
- **Was:** Folder icon stayed permanently visible (blue, 60% opacity) when a session belonged to a project.
|
||||||
|
- **Fix:** Replaced `.has-project` persistent button with a colored left border matching the project color. The folder button now only appears in the hover overlay like all other actions.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Both issues resolved in Sprint 16 (Session Sidebar Visual Polish).
|
||||||
|
- Icons replaced from inconsistent emoji HTML entities to monochrome SVG line icons.
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
> Goal: Full 1:1 parity with the Hermes CLI experience via a clean dark web UI.
|
> Goal: Full 1:1 parity with the Hermes CLI experience via a clean dark web UI.
|
||||||
> Everything you can do from the CLI terminal, you can do from this UI.
|
> Everything you can do from the CLI terminal, you can do from this UI.
|
||||||
>
|
>
|
||||||
> Last updated: Sprint 15 (April 1, 2026)
|
> Last updated: Sprint 15 / v0.17.1 (April 2, 2026)
|
||||||
> Tests: 237 passing
|
> Tests: 237 passing
|
||||||
> Source: <repo>/
|
> Source: <repo>/
|
||||||
|
|
||||||
|
|||||||
87
SPRINTS.md
87
SPRINTS.md
@@ -217,54 +217,55 @@ organizational gap vs. Claude's project folders.
|
|||||||
**Hermes CLI parity impact:** Low (CLI has no session organization)
|
**Hermes CLI parity impact:** Low (CLI has no session organization)
|
||||||
**Claude parity impact:** Very High (projects are a core Claude concept)
|
**Claude parity impact:** Very High (projects are a core Claude concept)
|
||||||
|
|
||||||
### Candidates for next sprints
|
### Candidates for later sprints
|
||||||
- Workspace reorder (drag-and-drop)
|
- Artifacts + code execution (HTML/SVG preview, inline Python execution)
|
||||||
- View skill linked files
|
|
||||||
- Voice input via Whisper
|
- Voice input via Whisper
|
||||||
- Subagent delegation cards (enhanced tool card rendering)
|
- Subagent delegation cards (enhanced tool card rendering)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sprint 16 -- Artifacts + Code Execution
|
## Sprint 16 -- Session Sidebar Visual Polish (COMPLETED)
|
||||||
|
|
||||||
**Theme:** See outputs, not just text.
|
**Theme:** Make the session list feel high-quality and delightful.
|
||||||
|
|
||||||
**Why now:** Claude's most distinctive feature is the artifact panel --
|
**Why now:** The session sidebar had two visible UX bugs: titles truncated
|
||||||
code runs inline, HTML renders in a sandboxed iframe, SVGs show as images.
|
unnecessarily because action icons reserved space even when hidden, and
|
||||||
This is the largest single capability gap between what we have and what Claude
|
the project folder icon felt "sticky" and awkward. Emoji icons rendered
|
||||||
feels like. It also directly enables the Hermes "code execution cell" feature
|
inconsistently across platforms. These were the most common visual complaints.
|
||||||
(Jupyter-style in-browser execution).
|
|
||||||
|
|
||||||
### Track A: Bugs
|
### Track A: Bugs (from BUGS.md)
|
||||||
- Prism.js autoloader makes one CDN request per language encountered. On a
|
- **Session title truncation.** Action icons (pin, move, archive, dup, trash)
|
||||||
code-heavy session this causes noticeable latency. Bundle the top 10 languages
|
were always in the DOM with `flex-shrink:0`, reserving ~30px even when
|
||||||
(Python, JS, bash, JSON, SQL, YAML, TypeScript, CSS, HTML, Rust) locally.
|
invisible. Fix: wrapped all actions in a `.session-actions` overlay
|
||||||
- Code blocks in long responses sometimes re-highlight on every renderMessages()
|
container with `position:absolute`. Titles now use full available width.
|
||||||
call. Debounce highlightCode() with requestAnimationFrame.
|
Actions appear on hover with a gradient fade from the right edge.
|
||||||
|
- **Folder button feels sticky.** Replaced `.has-project` persistent blue
|
||||||
|
button with a colored left border matching the project color. The folder
|
||||||
|
button now only appears in the hover overlay like all other actions.
|
||||||
|
|
||||||
### Track B: Features
|
### Track B: Features
|
||||||
- **Artifact panel:** When Hermes produces a code block tagged as `html`, `svg`,
|
- **SVG action icons.** Replaced all emoji HTML entities (★, 📂, 📦, ⊕, 🗑)
|
||||||
or `react`, a "Preview" button appears on that code block. Clicking it opens
|
with monochrome SVG line icons that inherit `currentColor`. Consistent
|
||||||
a sandboxed `<iframe>` in the right panel showing the rendered output. The
|
rendering across macOS, Linux, and Windows. Icons: pin (star), folder,
|
||||||
preview updates live if Hermes edits the artifact in a follow-up.
|
archive (box), duplicate (overlapping squares), trash (bin with lines).
|
||||||
- **Code execution cell:** A "Run" button on Python code blocks. Sends the code
|
- **Pin indicator.** Small gold filled-star icon rendered inline before the
|
||||||
to a new server endpoint (`POST /api/execute`) which runs it in a subprocess
|
title only when the session is actually pinned. Unpinned sessions get
|
||||||
with a 30-second timeout and streams stdout/stderr back as SSE. Output appears
|
full title width with zero space reservation.
|
||||||
below the code block inline. This is the Jupyter cell experience without
|
- **Project border indicator.** Sessions assigned to a project show a
|
||||||
needing a kernel.
|
colored left border matching the project color, replacing the old
|
||||||
- **Mermaid diagram rendering:** Mermaid.js CDN (deferred). Code blocks tagged
|
always-visible blue folder button.
|
||||||
as `mermaid` render as flow/sequence/gantt diagrams inline.
|
- **Hover overlay polish.** Actions container uses a gradient background
|
||||||
|
that fades from transparent to the sidebar color, creating a smooth
|
||||||
|
emergence effect. Overlay hides automatically during inline rename.
|
||||||
|
|
||||||
### Track C: Architecture
|
### Deferred to Sprint 17
|
||||||
- Sandbox safety: `/api/execute` runs in a restricted subprocess (no network,
|
- Slash commands (basic set with `commands.js` module)
|
||||||
limited filesystem via a temp directory). Returns exit code, stdout, stderr,
|
- Thinking/reasoning display for extended-thinking models
|
||||||
and execution time.
|
- Slash command autocomplete popup
|
||||||
- Artifact state: artifacts are tracked in `S.artifacts = {}` (code block hash
|
|
||||||
-> rendered content). Persisted in session JSON as `artifacts` array.
|
|
||||||
|
|
||||||
**Tests:** ~18 new. Total: ~259.
|
**Tests:** 0 new (pure CSS/DOM changes). Total: 237.
|
||||||
**Hermes CLI parity impact:** High (code execution closes the Jupyter gap)
|
**Hermes CLI parity impact:** Low
|
||||||
**Claude parity impact:** Very High (artifacts are Claude's signature feature)
|
**Claude parity impact:** Medium (sidebar polish matches Claude's quality bar)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -403,7 +404,7 @@ address.
|
|||||||
| Settings persistence | Done (Sprint 12) |
|
| Settings persistence | Done (Sprint 12) |
|
||||||
| Subagent visibility | Sprint 18 |
|
| Subagent visibility | Sprint 18 |
|
||||||
| Background task monitor | Sprint 18 |
|
| Background task monitor | Sprint 18 |
|
||||||
| Code execution (Jupyter) | Sprint 16 |
|
| Code execution (Jupyter) | Sprint 17+ |
|
||||||
| Cron completion alerts | Done (Sprint 13) |
|
| Cron completion alerts | Done (Sprint 13) |
|
||||||
| Virtual scroll (perf) | Deferred |
|
| Virtual scroll (perf) | Deferred |
|
||||||
|
|
||||||
@@ -419,12 +420,12 @@ address.
|
|||||||
| Tool use visibility | Done (v0.11) |
|
| Tool use visibility | Done (v0.11) |
|
||||||
| Edit/regenerate messages | Done (v0.10) |
|
| Edit/regenerate messages | Done (v0.10) |
|
||||||
| Session management | Done (v0.6) |
|
| Session management | Done (v0.6) |
|
||||||
| Artifacts (HTML/SVG preview) | Sprint 16 |
|
| Artifacts (HTML/SVG preview) | Sprint 17+ |
|
||||||
| Code execution inline | Sprint 16 |
|
| Code execution inline | Sprint 17+ |
|
||||||
| Mermaid diagrams | Done (Sprint 14) |
|
| Mermaid diagrams | Done (Sprint 14) |
|
||||||
| Projects / folders | Done (Sprint 15) |
|
| Projects / folders | Done (Sprint 15) |
|
||||||
| Pinned/starred sessions | Done (Sprint 12) |
|
| Pinned/starred sessions | Done (Sprint 12) |
|
||||||
| Reasoning display | Sprint 18 |
|
| Reasoning display | Sprint 16 |
|
||||||
| Voice input | Sprint 17 |
|
| Voice input | Sprint 17 |
|
||||||
| TTS playback | Sprint 17 |
|
| TTS playback | Sprint 17 |
|
||||||
| Notifications | Done (Sprint 13) |
|
| Notifications | Done (Sprint 13) |
|
||||||
@@ -448,6 +449,6 @@ address.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Last updated: April 1, 2026*
|
*Last updated: April 2, 2026*
|
||||||
*Current version: v0.17 | 237 tests*
|
*Current version: v0.18 | 237 tests*
|
||||||
*Next sprint: Sprint 16 (Artifacts + Code Execution)*
|
*Next sprint: Sprint 17 (Slash Commands + Thinking Display)*
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
// ── Session action icons (SVG, monochrome, inherit currentColor) ──
|
||||||
|
const ICONS={
|
||||||
|
pin:'<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" stroke="none"><polygon points="8,1.5 9.8,5.8 14.5,6.2 11,9.4 12,14 8,11.5 4,14 5,9.4 1.5,6.2 6.2,5.8"/></svg>',
|
||||||
|
unpin:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><polygon points="8,2 9.8,6.2 14.2,6.2 10.7,9.2 12,13.8 8,11 4,13.8 5.3,9.2 1.8,6.2 6.2,6.2"/></svg>',
|
||||||
|
folder:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M2 4.5h4l1.5 1.5H14v7H2z"/></svg>',
|
||||||
|
archive:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><rect x="1.5" y="2" width="13" height="3" rx="1"/><path d="M2.5 5v8h11V5"/><line x1="6" y1="8.5" x2="10" y2="8.5"/></svg>',
|
||||||
|
unarchive:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><rect x="1.5" y="2" width="13" height="3" rx="1"/><path d="M2.5 5v8h11V5"/><polyline points="6.5,7 8,5.5 9.5,7"/></svg>',
|
||||||
|
dup:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><rect x="4.5" y="4.5" width="8.5" height="8.5" rx="1.5"/><path d="M3 11.5V3h8.5"/></svg>',
|
||||||
|
trash:'<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.3"><path d="M3.5 4.5h9M6.5 4.5V3h3v1.5M4.5 4.5v8.5h7v-8.5"/><line x1="7" y1="7" x2="7" y2="11"/><line x1="9" y1="7" x2="9" y2="11"/></svg>',
|
||||||
|
};
|
||||||
|
|
||||||
async function newSession(flash){
|
async function newSession(flash){
|
||||||
MSG_QUEUE.length=0;updateQueueBadge();
|
MSG_QUEUE.length=0;updateQueueBadge();
|
||||||
S.toolCalls=[];
|
S.toolCalls=[];
|
||||||
@@ -242,11 +253,35 @@ function renderSessionListFromCache(){
|
|||||||
setTimeout(()=>{inp.focus();inp.select();},10);
|
setTimeout(()=>{inp.focus();inp.select();},10);
|
||||||
};
|
};
|
||||||
|
|
||||||
const pin=document.createElement('span');
|
// Pin indicator (inline, only when pinned — no space reserved otherwise)
|
||||||
pin.className='session-pin'+(s.pinned?' pinned':'');
|
if(s.pinned){
|
||||||
pin.innerHTML=s.pinned?'★':'☆';
|
const pinInd=document.createElement('span');
|
||||||
pin.title=s.pinned?'Unpin':'Pin to top';
|
pinInd.className='session-pin-indicator';
|
||||||
pin.onclick=async(e)=>{
|
pinInd.innerHTML=ICONS.pin;
|
||||||
|
el.appendChild(pinInd);
|
||||||
|
}
|
||||||
|
// Project indicator: colored left border
|
||||||
|
if(s.project_id){
|
||||||
|
const proj=_allProjects.find(p=>p.project_id===s.project_id);
|
||||||
|
if(proj){
|
||||||
|
el.style.borderLeftColor=proj.color||'var(--blue)';
|
||||||
|
const dot=document.createElement('span');
|
||||||
|
dot.className='session-project-dot';
|
||||||
|
dot.style.background=proj.color||'var(--blue)';
|
||||||
|
dot.title=proj.name;
|
||||||
|
title.appendChild(dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
el.appendChild(title);
|
||||||
|
// Action buttons overlay (appears on hover with gradient fade)
|
||||||
|
const actions=document.createElement('div');
|
||||||
|
actions.className='session-actions';
|
||||||
|
// Pin toggle
|
||||||
|
const pinBtn=document.createElement('button');
|
||||||
|
pinBtn.className='act-pin'+(s.pinned?' pinned':'');
|
||||||
|
pinBtn.innerHTML=s.pinned?ICONS.pin:ICONS.unpin;
|
||||||
|
pinBtn.title=s.pinned?'Unpin':'Pin to top';
|
||||||
|
pinBtn.onclick=async(e)=>{
|
||||||
e.stopPropagation();e.preventDefault();
|
e.stopPropagation();e.preventDefault();
|
||||||
const newPinned=!s.pinned;
|
const newPinned=!s.pinned;
|
||||||
try{
|
try{
|
||||||
@@ -256,8 +291,15 @@ function renderSessionListFromCache(){
|
|||||||
renderSessionList();
|
renderSessionList();
|
||||||
}catch(err){showToast('Pin failed: '+err.message);}
|
}catch(err){showToast('Pin failed: '+err.message);}
|
||||||
};
|
};
|
||||||
|
actions.appendChild(pinBtn);
|
||||||
|
// Move to project
|
||||||
|
const move=document.createElement('button');
|
||||||
|
move.className='act-move';move.innerHTML=ICONS.folder;move.title='Move to project';
|
||||||
|
move.onclick=async(e)=>{e.stopPropagation();e.preventDefault();_showProjectPicker(s,move);};
|
||||||
|
actions.appendChild(move);
|
||||||
|
// Archive
|
||||||
const archive=document.createElement('button');
|
const archive=document.createElement('button');
|
||||||
archive.className='session-action-btn';archive.innerHTML=s.archived?'✉':'📦';
|
archive.className='act-archive';archive.innerHTML=s.archived?ICONS.unarchive:ICONS.archive;
|
||||||
archive.title=s.archived?'Unarchive':'Archive';
|
archive.title=s.archived?'Unarchive':'Archive';
|
||||||
archive.onclick=async(e)=>{
|
archive.onclick=async(e)=>{
|
||||||
e.stopPropagation();e.preventDefault();
|
e.stopPropagation();e.preventDefault();
|
||||||
@@ -269,8 +311,10 @@ function renderSessionListFromCache(){
|
|||||||
showToast(s.archived?'Session archived':'Session restored');
|
showToast(s.archived?'Session archived':'Session restored');
|
||||||
}catch(err){showToast('Archive failed: '+err.message);}
|
}catch(err){showToast('Archive failed: '+err.message);}
|
||||||
};
|
};
|
||||||
|
actions.appendChild(archive);
|
||||||
|
// Duplicate
|
||||||
const dup=document.createElement('button');
|
const dup=document.createElement('button');
|
||||||
dup.className='session-dup';dup.innerHTML='⧉';dup.title='Duplicate';
|
dup.className='act-dup';dup.innerHTML=ICONS.dup;dup.title='Duplicate';
|
||||||
dup.onclick=async(e)=>{
|
dup.onclick=async(e)=>{
|
||||||
e.stopPropagation();e.preventDefault();
|
e.stopPropagation();e.preventDefault();
|
||||||
try{
|
try{
|
||||||
@@ -282,26 +326,13 @@ function renderSessionListFromCache(){
|
|||||||
}
|
}
|
||||||
}catch(err){showToast('Duplicate failed: '+err.message);}
|
}catch(err){showToast('Duplicate failed: '+err.message);}
|
||||||
};
|
};
|
||||||
|
actions.appendChild(dup);
|
||||||
|
// Trash
|
||||||
const trash=document.createElement('button');
|
const trash=document.createElement('button');
|
||||||
trash.className='session-trash';trash.innerHTML='🗑';trash.title='Delete';
|
trash.className='act-trash';trash.innerHTML=ICONS.trash;trash.title='Delete';
|
||||||
trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);};
|
trash.onclick=async(e)=>{e.stopPropagation();e.preventDefault();await deleteSession(s.session_id);};
|
||||||
// Project move button (folder icon)
|
actions.appendChild(trash);
|
||||||
const move=document.createElement('button');
|
el.appendChild(actions);
|
||||||
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){
|
|
||||||
const proj=_allProjects.find(p=>p.project_id===s.project_id);
|
|
||||||
if(proj){
|
|
||||||
const dot=document.createElement('span');
|
|
||||||
dot.className='session-project-dot';
|
|
||||||
dot.style.background=proj.color||'var(--blue)';
|
|
||||||
dot.title=proj.name;
|
|
||||||
title.appendChild(dot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el.appendChild(pin);el.appendChild(title);el.appendChild(move);el.appendChild(archive);el.appendChild(dup);el.appendChild(trash);
|
|
||||||
|
|
||||||
// Use a click timer to distinguish single-click (navigate) from double-click (rename).
|
// Use a click timer to distinguish single-click (navigate) from double-click (rename).
|
||||||
// This prevents loadSession from firing on the first click of a double-click,
|
// This prevents loadSession from firing on the first click of a double-click,
|
||||||
@@ -309,7 +340,7 @@ function renderSessionListFromCache(){
|
|||||||
let _clickTimer=null;
|
let _clickTimer=null;
|
||||||
el.onclick=async(e)=>{
|
el.onclick=async(e)=>{
|
||||||
if(_renamingSid) return; // ignore while any rename is active
|
if(_renamingSid) return; // ignore while any rename is active
|
||||||
if([trash,dup,archive,move].some(b=>e.target===b||b.contains(e.target))) return;
|
if(actions.contains(e.target)) return;
|
||||||
clearTimeout(_clickTimer);
|
clearTimeout(_clickTimer);
|
||||||
_clickTimer=setTimeout(async()=>{
|
_clickTimer=setTimeout(async()=>{
|
||||||
_clickTimer=null;
|
_clickTimer=null;
|
||||||
|
|||||||
@@ -20,13 +20,21 @@
|
|||||||
.session-search input::placeholder{color:var(--muted);opacity:.7;}
|
.session-search input::placeholder{color:var(--muted);opacity:.7;}
|
||||||
/* Inline session title edit */
|
/* Inline session title edit */
|
||||||
.session-title-input{flex:1;background:rgba(20,32,60,.9);border:1px solid rgba(124,185,255,.6);border-radius:6px;color:var(--text);padding:3px 8px;font-size:13px;outline:none;min-width:0;box-shadow:0 0 0 2px rgba(124,185,255,.15);font-family:inherit;}
|
.session-title-input{flex:1;background:rgba(20,32,60,.9);border:1px solid rgba(124,185,255,.6);border-radius:6px;color:var(--text);padding:3px 8px;font-size:13px;outline:none;min-width:0;box-shadow:0 0 0 2px rgba(124,185,255,.15);font-family:inherit;}
|
||||||
.session-item{padding:8px 10px 8px 8px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background .15s,color .15s,border-color .15s;display:flex;align-items:center;gap:6px;min-width:0;border-left:2px solid transparent;}
|
.session-item{padding:8px 10px 8px 8px;border-radius:8px;cursor:pointer;font-size:13px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:background .15s,color .15s,border-color .15s;display:flex;align-items:center;gap:6px;min-width:0;border-left:2px solid transparent;position:relative;}
|
||||||
.session-item:hover{background:rgba(255,255,255,0.06);color:var(--text);}
|
.session-item:hover{background:rgba(255,255,255,0.06);color:var(--text);}
|
||||||
.session-item.active{background:rgba(124,185,255,0.1);color:var(--blue);border-left:2px solid var(--blue);padding-left:8px;}
|
.session-item.active{background:rgba(124,185,255,0.1);color:var(--blue);border-left:2px solid var(--blue);padding-left:8px;}
|
||||||
.session-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
.session-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
|
||||||
.session-trash{flex-shrink:0;opacity:0;font-size:13px;color:var(--muted);background:none;border:none;cursor:pointer;padding:0 2px;line-height:1;transition:opacity .15s,color .15s;}
|
/* ── Session action button overlay ── */
|
||||||
.session-item:hover .session-trash{opacity:1;}
|
.session-actions{position:absolute;right:0;top:0;bottom:0;display:flex;align-items:center;gap:2px;padding:0 6px 0 16px;background:linear-gradient(to right,transparent,var(--sidebar) 12px);opacity:0;pointer-events:none;transition:opacity .15s ease;border-radius:0 8px 8px 0;}
|
||||||
.session-trash:hover{color:var(--accent)!important;}
|
.session-item:hover .session-actions{opacity:1;pointer-events:auto;}
|
||||||
|
.session-item.active .session-actions{background:linear-gradient(to right,transparent,rgba(16,33,62,.95) 12px);}
|
||||||
|
.session-actions button{background:none;border:none;color:var(--muted);cursor:pointer;padding:2px 3px;line-height:1;transition:color .12s;display:flex;align-items:center;}
|
||||||
|
.session-actions button:hover{color:var(--text);}
|
||||||
|
.session-actions .act-trash:hover{color:var(--accent);}
|
||||||
|
.session-actions .act-pin.pinned{color:#f5c542;}
|
||||||
|
.session-actions .act-pin.pinned:hover{color:#d4a017;}
|
||||||
|
/* Hide overlay during inline rename */
|
||||||
|
.session-item:has(.session-title-input) .session-actions{display:none;}
|
||||||
@keyframes newflash{0%{background:rgba(124,185,255,0.22);color:var(--blue);}100%{background:transparent;color:var(--muted);}}
|
@keyframes newflash{0%{background:rgba(124,185,255,0.22);color:var(--blue);}100%{background:transparent;color:var(--muted);}}
|
||||||
.session-item.new-flash{animation:newflash 1.4s ease-out forwards;}
|
.session-item.new-flash{animation:newflash 1.4s ease-out forwards;}
|
||||||
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(20,30,50,.95);backdrop-filter:blur(12px);border:1px solid rgba(124,185,255,0.25);color:var(--text);font-size:13px;padding:10px 20px;border-radius:12px;pointer-events:none;opacity:0;transition:opacity .2s,transform .2s;z-index:100;box-shadow:0 4px 20px rgba(0,0,0,.3);letter-spacing:.01em;}
|
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:rgba(20,30,50,.95);backdrop-filter:blur(12px);border:1px solid rgba(124,185,255,0.25);color:var(--text);font-size:13px;padding:10px 20px;border-radius:12px;pointer-events:none;opacity:0;transition:opacity .2s,transform .2s;z-index:100;box-shadow:0 4px 20px rgba(0,0,0,.3);letter-spacing:.01em;}
|
||||||
@@ -495,17 +503,9 @@ body.resizing{user-select:none;cursor:col-resize;}
|
|||||||
.gear-btn{font-size:13px;cursor:pointer;transition:color .15s,background .15s;}
|
.gear-btn{font-size:13px;cursor:pointer;transition:color .15s,background .15s;}
|
||||||
.gear-btn:hover{color:var(--text);background:rgba(255,255,255,.08);}
|
.gear-btn:hover{color:var(--text);background:rgba(255,255,255,.08);}
|
||||||
|
|
||||||
/* ── Session pin star ── */
|
/* ── Session pin indicator (inline, only when pinned) ── */
|
||||||
.session-pin{font-size:12px;cursor:pointer;opacity:0;transition:opacity .15s;padding:2px 4px;flex-shrink:0;}
|
.session-pin-indicator{flex-shrink:0;color:#f5c542;line-height:1;display:flex;align-items:center;}
|
||||||
.session-item:hover .session-pin,.session-pin.pinned{opacity:1;}
|
.session-pin-indicator svg{width:10px;height:10px;}
|
||||||
.session-pin.pinned{color:#f5c542;}
|
|
||||||
|
|
||||||
/* ── Session duplicate button ── */
|
|
||||||
.session-dup,.session-action-btn{background:none;border:none;color:var(--muted);font-size:11px;cursor:pointer;opacity:0;transition:opacity .15s;padding:2px 4px;flex-shrink:0;}
|
|
||||||
.session-item:hover .session-dup,.session-item:hover .session-action-btn{opacity:1;}
|
|
||||||
.session-dup:hover,.session-action-btn:hover{color:var(--text);}
|
|
||||||
.session-action-btn.has-project{opacity:.6;color:var(--blue);}
|
|
||||||
.session-item:hover .session-action-btn.has-project{opacity:1;}
|
|
||||||
|
|
||||||
/* ── Cron alert badge ── */
|
/* ── Cron alert badge ── */
|
||||||
.cron-badge{position:absolute;top:2px;right:2px;background:#e53e3e;color:#fff;font-size:9px;font-weight:700;min-width:14px;height:14px;line-height:14px;text-align:center;border-radius:7px;padding:0 3px;}
|
.cron-badge{position:absolute;top:2px;right:2px;background:#e53e3e;color:#fff;font-size:9px;font-weight:700;min-width:14px;height:14px;line-height:14px;text-align:center;border-radius:7px;padding:0 3px;}
|
||||||
|
|||||||
Reference in New Issue
Block a user