Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions plugins/web-ui/src/contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,14 +329,32 @@ function gridTpl(): TemplateResult {
Boolean(context.sessionCount))
);
};
const rank = (context: CoreContext) => {
if (context.kind === "personal") return 0;
return context.project ? 1 : 2;
const projects = contextsState.list.filter(matches);
const groupOf = (context: CoreContext) => {
if (context.kind === "personal") return "personal";
return context.project ? "web" : "slack";
};
const projects = contextsState.list.filter(matches).sort((a, b) => rank(a) - rank(b));
const groups = [
{ key: "personal", label: "Personal" },
{ key: "web", label: "Web" },
{ key: "slack", label: "Slack" },
]
.map((g) => ({ ...g, items: projects.filter((context) => groupOf(context) === g.key) }))
.filter((g) => g.items.length > 0);
const projectsFiltered = Boolean(q);
let projectList: TemplateResult | typeof nothing = nothing;
if (projects.length) projectList = html`<div class="grid project-grid">${projects.map(contextCard)}</div>`;
if (projects.length)
projectList = html`<div class="project-list">
${groups.map(
(g) =>
html`<section class="project-group">
<div class="project-group-head">
${g.label} <span class="project-group-count">· ${g.items.length}</span>
</div>
${g.items.map(contextRow)}
</section>`,
)}
</div>`;
else if (!contextsLoading) {
projectList = html`<div class="empty compact project-empty">
${projectsFiltered ? "No projects match your search." : "No projects yet."}
Expand Down Expand Up @@ -399,22 +417,18 @@ function gridTpl(): TemplateResult {
`;
}

function contextCard(c: CoreContext): TemplateResult {
function contextRow(c: CoreContext): TemplateResult {
const { title, sub, glyph } = contextMeta(c);
const count = c.sessionCount === 1 ? "1 conversation" : `${c.sessionCount} conversations`;
let access = "shared";
if (c.project && isProjectOwner(c)) access = "owned";
else if (c.kind === "personal") access = "private";
const meta = [c.project ? sub : "", count, c.lastActivityAt ? `active ${relTime(c.lastActivityAt)}` : ""]
.filter(Boolean)
.join(" · ");
return html`
<button class="card context-card" type="button" @click=${() => selectContext(c.scopeId)}>
<div class="card-head">
<span class="context-glyph">${icon(glyph, 17)}</span>
<h2 class="card-title">${title}</h2>
${c.isPrivate ? html`<span class="context-lock" title="Private channel">${icon(Lock, 13)}</span>` : nothing}
<span class="badge">${access}</span>
</div>
<div class="card-meta context-card-sub">${sub}</div>
<div class="card-meta">${count}${c.lastActivityAt ? ` · active ${relTime(c.lastActivityAt)}` : ""}</div>
<button class="context-row" type="button" title=${sub} @click=${() => selectContext(c.scopeId)}>
<span class="context-glyph">${icon(glyph, 15)}</span>
<span class="context-row-title">${title}</span>
${c.isPrivate ? html`<span class="context-lock" title="Private channel">${icon(Lock, 12)}</span>` : nothing}
<span class="context-row-meta">${meta}</span>
</button>
`;
}
Expand Down
73 changes: 52 additions & 21 deletions plugins/web-ui/src/shell.css
Original file line number Diff line number Diff line change
Expand Up @@ -4383,38 +4383,69 @@ a.chat-row-open {
background: var(--secondary);
font-size: 13px;
}
.project-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
.project-list {
width: min(960px, 100%);
margin: 0 auto;
}
.project-empty {
max-width: 960px;
margin: 12px auto 0;
border: 1px dashed var(--border);
box-sizing: border-box;
.project-group {
margin-top: 22px;
}
.project-other-label {
max-width: 960px;
margin: 28px auto 0;
.project-group-head {
padding: 0 6px 6px;
border-bottom: 1px solid var(--border);
color: var(--muted-foreground);
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.project-group-count {
font-weight: 500;
letter-spacing: normal;
}
.context-card {
.context-row {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
padding: 7px 6px;
border: 0;
border-bottom: 1px solid var(--border);
border-radius: 0;
background: transparent;
color: var(--foreground);
font: inherit;
text-align: left;
cursor: pointer;
transition:
background 0.12s ease,
border-color 0.12s ease;
white-space: nowrap;
}
.context-card:hover {
.context-row:hover {
background: color-mix(in srgb, var(--background) 88%, var(--secondary));
border-color: color-mix(in srgb, var(--foreground) 18%, var(--border));
}
.context-card .card-head {
align-items: center;
justify-content: flex-start;
.context-row-title {
overflow: hidden;
min-width: 0;
flex-shrink: 1;
text-overflow: ellipsis;
font-weight: 600;
}
.context-card .card-head .badge {
.context-row-meta {
overflow: hidden;
margin-left: auto;
flex-shrink: 0;
flex: none;
color: var(--muted-foreground);
font-size: 12.5px;
text-overflow: ellipsis;
}
.project-empty {
max-width: 960px;
margin: 12px auto 0;
border: 1px dashed var(--border);
box-sizing: border-box;
}
.project-other-label {
max-width: 960px;
margin: 28px auto 0;
}

.pane-subtitle {
Expand Down