Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4619683
feat: add trace timelines endpoint for session view revamp
kolbeyang Mar 31, 2026
c309588
feat: add static UI components for revamped session view
kolbeyang Mar 31, 2026
39bb626
feat: wire up virtualized session list with data integration
kolbeyang Mar 31, 2026
70b7358
feat: polish session view with loading states, edge cases, and visual…
kolbeyang Mar 31, 2026
14b48dd
fix: resolve QA issues in session view (stale state, virtualizer keys…
kolbeyang Mar 31, 2026
c42b0f7
fix: correct duration display and make trace card columns scrollable
kolbeyang Mar 31, 2026
2c46185
fix: gradient overlays, longer placeholder text, estimateSize, and ex…
kolbeyang Mar 31, 2026
16a5dc7
feat: combine sessions+timelines endpoint, extract Zustand store, fix…
kolbeyang Mar 31, 2026
bee05af
fix: make gradient fade sticky in session trace card
kolbeyang Mar 31, 2026
8f1f410
feat: add exit animations to session expand/collapse
kolbeyang Mar 31, 2026
27a791e
fix: address 5 bugbot issues in sessions table
kolbeyang Mar 31, 2026
b299c91
fix: session toggle race, refresh stale data, timeline query limit
kolbeyang Mar 31, 2026
c7fc340
fix: guard against stale trace responses after state reset
kolbeyang Mar 31, 2026
9795604
refactor: use AbortController for session trace fetch cancellation
kolbeyang Mar 31, 2026
b958eba
fix: remove dead abort actions, add fetch version guard for stale tim…
kolbeyang Mar 31, 2026
e873156
feat: clickable timeline bars, fix gradient scroll, animate height
kolbeyang Apr 1, 2026
1f49293
feat: clickable timeline bars, copy-to-clipboard IDs, animation cleanup
kolbeyang Apr 1, 2026
b9d7db9
fix: replace toggle with explicit expand/collapse to prevent double-c…
kolbeyang Apr 1, 2026
9827c11
fix: replace hardcoded hex color with theme token in totals pill
kolbeyang Apr 1, 2026
af29cd1
feat: fetch and render main agent input/output in session trace cards
kolbeyang Apr 1, 2026
c900b9d
fix: extract last user message (not first) for trace input
kolbeyang Apr 1, 2026
f2c5f77
feat: add output preview, add timerange
olzhik11 Apr 1, 2026
342e9f0
feat: refactor TraceIOContent column, fix chevron toggle, sort traces…
kolbeyang Apr 1, 2026
847eb26
feat: add getMainAgentIOBatch action and POST /traces/io batch API route
kolbeyang Apr 2, 2026
310ab4b
feat: add mergeTraceIO, setLoadingSessionIO, and loadingSessionIO to …
kolbeyang Apr 2, 2026
10be876
feat: fire batch IO fetch in handleToggleSession after traces load
kolbeyang Apr 2, 2026
2957b49
refactor: make SessionTraceCard display-only, remove per-card useEffe…
kolbeyang Apr 2, 2026
ffdf3fb
fix: use permissive hex UUID regex instead of strict z.string().uuid(…
kolbeyang Apr 2, 2026
c73d4da
feat: trim sessions filters, add autocomplete entry, fix empty filter…
kolbeyang Apr 2, 2026
4c869a2
refactor: export column width constants from session-table-header for…
kolbeyang Apr 2, 2026
823abc4
Merge branch 'dev' into feat/revamp-session-view
olzhik11 Apr 6, 2026
fabffc2
feat: hash system prompt, regex user prompt
olzhik11 Apr 6, 2026
9057422
Merge branch 'feat/revamp-session-view-output-preview' into feat/reva…
olzhik11 Apr 6, 2026
f597454
feat: rework prompt, increase batch, add tooltip, fix borders
olzhik11 Apr 7, 2026
639c79e
feat: sticky row, optional resource, small refactor
olzhik11 Apr 7, 2026
ee93afd
feat: update ui, wip
olzhik11 Apr 7, 2026
49244b3
feat: ui
olzhik11 Apr 8, 2026
de33377
Merge branch 'dev' into feat/revamp-session-view
olzhik11 Apr 8, 2026
d09c714
feat: backend hashing, sorting, session link
olzhik11 Apr 9, 2026
cc4dd6e
feat: address comments
olzhik11 Apr 9, 2026
39f04e3
feat: add ui improvements
olzhik11 Apr 9, 2026
808fa9c
feat: fix comments
olzhik11 Apr 9, 2026
39ca19c
feat: use batched hook to prevent overfetches
olzhik11 Apr 9, 2026
c83ac98
feat: fix comments
olzhik11 Apr 9, 2026
d03426f
feat: code org
olzhik11 Apr 9, 2026
549e4cb
feat: fix comments
olzhik11 Apr 9, 2026
e5ee4ea
feat: add proper tracing, fix comments
olzhik11 Apr 9, 2026
16af4cf
feat: fix comment
olzhik11 Apr 9, 2026
e5f38cc
feat: remove library, fix comment
olzhik11 Apr 10, 2026
2e49eef
feat: restore lock file
olzhik11 Apr 10, 2026
bd0713c
fix: prompt
olzhik11 Apr 10, 2026
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
target/
.claude/
.agent-browser/
.agent-team/TODO.md
.agent-team/
3 changes: 2 additions & 1 deletion app-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1616,7 +1616,8 @@ fn main() -> anyhow::Result<()> {
.service(routes::spans::search_spans)
.service(routes::rollouts::run)
.service(routes::rollouts::update_status)
.service(routes::signals::submit_signal_job),
.service(routes::signals::submit_signal_job)
.service(routes::spans::get_skeleton_hashes),
)
.service(routes::probes::check_health)
.service(routes::probes::check_ready)
Expand Down
24 changes: 24 additions & 0 deletions app-server/src/routes/spans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::{
quickwit::client::QuickwitClient,
routes::ResponseResult,
search::snippets::SearchSpanHit,
signals::utils::structural_skeleton_hash,
traces::{OBSERVATIONS_EXCHANGE, OBSERVATIONS_ROUTING_KEY, spans::SpanAttributes},
};

Expand Down Expand Up @@ -149,3 +150,26 @@ pub async fn search_spans(

Ok(HttpResponse::Ok().json(results))
}

#[derive(Deserialize)]
pub struct SkeletonHashRequest {
pub texts: Vec<String>,
}

#[post("skeleton-hashes")]
pub async fn get_skeleton_hashes(
_project_id: web::Path<Uuid>,
request: web::Json<SkeletonHashRequest>,
) -> ResponseResult {
let texts = &request.texts;

if texts.is_empty() || texts.len() > 200 {
return Ok(HttpResponse::BadRequest().json(serde_json::json!({
"error": "texts must contain between 1 and 200 items"
})));
}

let hashes: Vec<String> = texts.iter().map(|t| structural_skeleton_hash(t)).collect();

Ok(HttpResponse::Ok().json(hashes))
}
18 changes: 18 additions & 0 deletions frontend/app/api/projects/[projectId]/traces/io/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { type NextRequest } from "next/server";
import { prettifyError, ZodError } from "zod/v4";

import { getMainAgentIOBatch } from "@/lib/actions/sessions/trace-io";

export async function POST(req: NextRequest, props: { params: Promise<{ projectId: string }> }): Promise<Response> {
const { projectId } = await props.params;
try {
const body = await req.json();
const result = await getMainAgentIOBatch({ ...body, projectId });
return Response.json(result);
} catch (error) {
if (error instanceof ZodError) {
return Response.json({ error: prettifyError(error) }, { status: 400 });
}
return Response.json({ error: error instanceof Error ? error.message : "Internal server error" }, { status: 500 });
}
}
6 changes: 3 additions & 3 deletions frontend/components/common/advanced-search/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {

interface AdvancedSearchInnerProps {
filters: ColumnFilter[];
resource: "traces" | "spans";
resource?: "traces" | "spans";
placeholder?: string;
className?: string;
disabled?: boolean;
Expand Down Expand Up @@ -105,7 +105,7 @@ const AdvancedSearchInner = ({
}, [urlTags, setTags, updateLastSubmitted, mode]);

useSWR<{ suggestions: AutocompleteSuggestion[] }>(
suggestions ? null : `/api/projects/${projectId}/${resource}/autocomplete`,
suggestions || !resource ? null : `/api/projects/${projectId}/${resource}/autocomplete`,
swrFetcher,
{
onSuccess: (data) => {
Expand Down Expand Up @@ -146,7 +146,7 @@ AdvancedSearchInner.displayName = "AdvancedSearchInner";

interface AdvancedSearchProps {
filters: ColumnFilter[];
resource: "traces" | "spans";
resource?: "traces" | "spans";
placeholder?: string;
className?: string;
disabled?: boolean;
Expand Down
4 changes: 3 additions & 1 deletion frontend/components/common/advanced-search/store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,9 @@ function createCoreSlice(

submit: (router, pathname, searchParams) => {
const { tags, inputValue, onSubmit, mode } = get();
const filterObjects = tags.map(createFilterFromTag);
// Skip incomplete tags (empty value) so we don't submit invalid filters
const completeTags = tags.filter((t) => (Array.isArray(t.value) ? t.value.length > 0 : t.value !== ""));
const filterObjects = completeTags.map(createFilterFromTag);
const searchValue = inputValue.trim();

set({ isOpen: false, activeIndex: -1, activeRecentIndex: -1 });
Expand Down
169 changes: 0 additions & 169 deletions frontend/components/traces/sessions-table/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,11 @@
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { type ColumnDef } from "@tanstack/react-table";
import { ChevronRightIcon } from "lucide-react";

import ClientTimestampFormatter from "@/components/client-timestamp-formatter";
import TagsCell from "@/components/tags/tags-cell";
import { type ColumnFilter } from "@/components/ui/infinite-datatable/ui/datatable-filter/utils";
import Mono from "@/components/ui/mono";
import { type SessionRow } from "@/lib/traces/types";
import { getDurationString } from "@/lib/utils";

const format = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
maximumFractionDigits: 5,
minimumFractionDigits: 1,
});

export const filters: ColumnFilter[] = [
{
key: "session_id",
name: "Session ID",
dataType: "string",
},
{
key: "user_id",
name: "User ID",
dataType: "string",
},
{
key: "trace_count",
name: "Trace Count",
Expand All @@ -42,157 +21,9 @@ export const filters: ColumnFilter[] = [
name: "Total Tokens",
dataType: "number",
},
{
key: "input_tokens",
name: "Input Tokens",
dataType: "number",
},
{
key: "output_tokens",
name: "Output Tokens",
dataType: "number",
},
{
key: "total_cost",
name: "Total Cost",
dataType: "number",
},
{
key: "input_cost",
name: "Input Cost",
dataType: "number",
},
{
key: "output_cost",
name: "Output Cost",
dataType: "number",
},
{
key: "tags",
name: "Span tags",
dataType: "string",
},
];

export const columns: ColumnDef<SessionRow, any>[] = [
{
header: "Type",
cell: ({ row }) =>
row.original?.subRows ? (
<div className="flex items-center gap-2">
<span className="">Session</span>
{row.getIsExpanded() ? (
<ChevronDownIcon className="w-4 text-secondary-foreground" />
) : (
<ChevronRightIcon className="w-4 text-secondary-foreground" />
)}
</div>
) : (
<div>
<span className="text-gray-500">Trace</span>
</div>
),
id: "type",
size: 120,
},
{
accessorFn: (row) => row.id || row.sessionId,
header: "ID",
id: "id",
cell: (row) => <Mono className="text-xs">{row.getValue()}</Mono>,
},
{
accessorFn: (row) => row.startTime,
header: "Start time",
cell: (row) => <ClientTimestampFormatter timestamp={String(row.getValue())} />,
id: "start_time",
size: 150,
},
{
accessorFn: (row) => {
if (!row?.subRows) {
return getDurationString(row.startTime, row.endTime);
}

return row.duration.toFixed(2) + "s";
},
header: "Duration",
id: "duration",
size: 100,
},
{
accessorFn: (row) => format.format(row.inputCost),
header: "Input cost",
id: "input_cost",
size: 120,
},
{
accessorFn: (row) => format.format(row.outputCost),
header: "Output cost",
id: "output_cost",
size: 120,
},
{
accessorFn: (row) => format.format(row.totalCost),
header: "Total cost",
id: "total_cost",
size: 120,
},
{
accessorFn: (row) => row.inputTokens,
header: "Input tokens",
id: "input_tokens",
size: 120,
},
{
accessorFn: (row) => row.outputTokens,
header: "Output tokens",
id: "output_tokens",
size: 120,
},
{
accessorFn: (row) => row.totalTokens,
header: "Total tokens",
id: "total_tokens",
size: 120,
},
{
accessorFn: (row) => (row?.subRows ? row.traceCount || 0 : "-"),
header: "Trace Count",
id: "trace_count",
size: 120,
},
{
accessorFn: (row) => (row?.subRows ? "-" : row.userId),
header: "User ID",
id: "user_id",
cell: (row) => <Mono className="text-xs">{row.getValue() || "-"}</Mono>,
},
{
accessorFn: (row) => ("spanTags" in row ? row.spanTags : "-"),
cell: (row) => {
const tags = row.getValue() as string[];
if (Array.isArray(tags) && tags?.length > 0) return <TagsCell tags={tags} />;
return "-";
},
header: "Span tags",
accessorKey: "spanTags",
id: "span_tags",
},
];

export const defaultSessionsColumnOrder = [
"type",
"id",
"start_time",
"duration",
"input_cost",
"output_cost",
"total_cost",
"input_tokens",
"output_tokens",
"total_tokens",
"trace_count",
"user_id",
"span_tags",
];
Loading
Loading