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
3 changes: 2 additions & 1 deletion docs/sessions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 30 additions & 5 deletions web/src/routes/Sessions.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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<string, unknown>),
labelMode: s.labelMode === "all" ? "all" : "any",
}),
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -720,6 +730,21 @@ export function Sessions() {
</span>

<div className="inbox-toolbar-filters">
{project && (
<span className="inbox-project-chip" title={`Filtered to ${project}`}>
<Folder size={14} strokeWidth={2} aria-hidden />
<span className="truncate">{compactProject(project)}</span>
<button
type="button"
className="inbox-project-chip-clear"
aria-label={`Clear project filter (${compactProject(project)})`}
onClick={() => setRange({ project: undefined })}
>
<X size={12} strokeWidth={2.5} aria-hidden />
</button>
</span>
)}

<FilterDropdown
icon={<Tag size={14} strokeWidth={2} aria-hidden />}
label="Labels"
Expand Down
8 changes: 8 additions & 0 deletions web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down