diff --git a/plugins/web-ui/src/contexts.ts b/plugins/web-ui/src/contexts.ts index ec42aeff4..e99b52d04 100644 --- a/plugins/web-ui/src/contexts.ts +++ b/plugins/web-ui/src/contexts.ts @@ -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`
${projects.map(contextCard)}
`; + if (projects.length) + projectList = html`
+ ${groups.map( + (g) => + html`
+
+ ${g.label} · ${g.items.length} +
+ ${g.items.map(contextRow)} +
`, + )} +
`; else if (!contextsLoading) { projectList = html`
${projectsFiltered ? "No projects match your search." : "No projects yet."} @@ -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` - `; } diff --git a/plugins/web-ui/src/shell.css b/plugins/web-ui/src/shell.css index e4f6fd67f..790f2468a 100644 --- a/plugins/web-ui/src/shell.css +++ b/plugins/web-ui/src/shell.css @@ -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 {