From 3a8ada870632610a4931f54d67cd85bbef7f19db Mon Sep 17 00:00:00 2001 From: Mando Escamilla Date: Fri, 24 Jul 2026 12:19:29 -0500 Subject: [PATCH] Sessions: restore the project filter chip Show the active project as a removable pill in the Sessions toolbar when arriving via a Projects/Health link, and include it in the toolbar's reset-filters logic so it can be cleared. --- docs/sessions.md | 3 ++- web/src/routes/Sessions.tsx | 35 ++++++++++++++++++++++++++++++----- web/src/styles.css | 8 ++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/docs/sessions.md b/docs/sessions.md index 205e7693..766d84d5 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -21,7 +21,8 @@ and task counts. The toolbar across the top narrows the list: The reset button next to the filters clears them back to the last 30 days and all sources. If you open Sessions by clicking a project or [source](/terminology#source) -on another view, the list arrives already narrowed to it. +on another view, the list arrives already narrowed to it, and the project appears +as a pill you can remove. ::: tip Press `/` or Cmd+K (Ctrl+K on Windows) to jump to the search box. With a session diff --git a/web/src/routes/Sessions.tsx b/web/src/routes/Sessions.tsx index a63a87a3..041fa79b 100644 --- a/web/src/routes/Sessions.tsx +++ b/web/src/routes/Sessions.tsx @@ -1,6 +1,6 @@ import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Link, Navigate, Outlet, useNavigate, useParams, useSearch } from "@tanstack/react-router"; -import { Calendar, EyeOff, FilterX, Layers, Search, Tag, X } from "lucide-react"; +import { Calendar, EyeOff, FilterX, Folder, Layers, Search, Tag, X } from "lucide-react"; import { type MouseEvent, type ReactNode, useEffect, useMemo, useRef, useState } from "react"; import { compactProject, dayStamp, fmt, pluralize } from "../lib/format"; import { TasksIcon, TokensIcon } from "../lib/icons"; @@ -632,13 +632,14 @@ export function Sessions() { ); const navigate = useNavigate(); - const { since, until, committedQ, source, labelIds, labelMode } = useSearch({ + const { since, until, committedQ, source, project, labelIds, labelMode } = useSearch({ strict: false, select: (s) => ({ since: s.since ?? DEFAULT_SINCE, until: s.until ?? DEFAULT_UNTIL, committedQ: s.q ?? "", source: s.source, + project: s.project, labelIds: labelIdsFromSearch(s as Record), labelMode: s.labelMode === "all" ? "all" : "any", }), @@ -674,11 +675,20 @@ export function Sessions() { const sourcesSummary = source ? sourceLabel(source) : "All sources"; // Reset mirrors the shared FilterBar's reset (source + date range), plus the toolbar's own search - // box and label selection — enabled only when one of those is off its default. - const hasActiveFilters = Boolean(source) || !dateIsDefault || query.trim() !== "" || labelIds.length > 0; + // box, label selection, and a `project` filter arrived at via a link from Projects/Health (#273) — + // enabled only when one of those is off its default. + const hasActiveFilters = Boolean(source) || Boolean(project) || !dateIsDefault || query.trim() !== "" || labelIds.length > 0; const resetFilters = () => { setQuery(""); - setRange({ since: undefined, until: undefined, source: undefined, q: undefined, label: undefined, labelMode: undefined }); + setRange({ + since: undefined, + until: undefined, + source: undefined, + project: undefined, + q: undefined, + label: undefined, + labelMode: undefined, + }); }; const filteredLabels = (labelCatalog.data ?? []).filter((l) => l.name.toLowerCase().includes(labelSearch.toLowerCase())); @@ -720,6 +730,21 @@ export function Sessions() {
+ {project && ( + + + {compactProject(project)} + + + )} + } label="Labels" diff --git a/web/src/styles.css b/web/src/styles.css index 8f4fd465..9ba290ee 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -199,6 +199,14 @@ main:has(.inbox-page) { padding: 0; } .inbox-search-input { box-sizing: border-box; width: 100%; height: 32px; background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: 0 12px 0 32px; color: var(--text); font: 13px "Aleo", Georgia, serif; } .inbox-search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } +/* A static pill for the `project` filter (#273): arrives via a link from Projects/Health, so unlike + the dropdown pills beside it there's no menu to open — just a name and a clear button. Matches + .filter-dropdown-btn's active look so the two read as one pill row. */ +.inbox-project-chip { display: inline-flex; align-items: center; gap: 7px; max-width: 220px; height: 32px; padding: 0 6px 0 12px; border: 1px solid var(--accent); border-radius: 999px; background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--heading); font: 600 12px var(--font-ui); white-space: nowrap; } +.inbox-project-chip-clear { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex: 0 0 auto; border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer; } +.inbox-project-chip-clear:hover { background: color-mix(in srgb, var(--accent) 30%, transparent); } +.inbox-project-chip-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } + .filter-dropdown { position: relative; flex: 0 0 auto; } .filter-dropdown-btn { display: inline-flex; align-items: center; gap: 7px; height: 32px; padding: 0 12px; border: 1px solid var(--line); border-radius: 999px; background: var(--bg); color: var(--text); font: 600 12px var(--font-ui); cursor: pointer; white-space: nowrap; } .filter-dropdown-btn:hover { border-color: var(--accent); }