Conversation
…nuqs - Create LaminarAgentStore with viewMode, prefillInput, chatMessages, chatStatus state - Create useSpanId() nuqs hook for URL-driven span selection - Remove selectedSpan usage from main trace view, derive from spans + spanId URL param - Update condensed-timeline to use useSpanId() hook - Update mini-tree to use useSpanId() instead of manual URL manipulation - Update shared trace view to use useSpanId() hook - Keep selectedSpan/setSelectedSpan in base store for debugger session backward compat - Add getSpanById() helper to base store Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-fetching
- Create useEventId() nuqs hook for URL-driven event selection
- Update signals page to use useEventId() instead of selectedEvent from store
- Update events table to set eventId via nuqs on row click
- Make EventDetailPanel accept eventId instead of event prop, fetch its own data
- Add GET /api/projects/{projectId}/signals/{signalId}/events/{eventId} endpoint
- Add loading skeleton and error states to EventDetailPanel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…o-scroll - Replace state.selectedSpan from Zustand store with useSpanId() hook in span-card.tsx, list-item.tsx, tree/index.tsx, and list/index.tsx - The nuqs migration stopped calling setSelectedSpan(), leaving it always undefined, which broke highlight and auto-scroll - isSelected now compares against the spanId URL param - selectedSpanIndex now derives from the spanId URL param Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create collapsed FAB button (bottom-right) with Laminar icon - Create floating panel (400px wide, 70vh tall, fixed right side) - Create side-by-side wrapper using ResizablePanelGroup - Create shared AgentPanel with header, mode-switch buttons, tooltips - Create root LaminarAgent component orchestrating view modes - Mount collapsed button, floating panel, and side-by-side wrapper in project layout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use transition-all on collapsed FAB to animate both shadow and transform - Add bg-background to floating panel outer div for sub-pixel gap prevention - Add withHandle prop to ResizableHandle for visible drag affordance - Align floating panel to bottom-6 right-6 matching FAB position - Bump floating panel z-index to z-[60] to stay above sheets/dialogs - Confirm icon logic correctness (INFO item, no code change needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Create global agent API route at /api/projects/[projectId]/agent - Add two tools: compactTraceContext (trace analysis) and executeSql (SQL queries) - Create system prompt with table schemas, enum values, and tool usage guidelines - Implement full chat UI in agent-panel.tsx using useChat + DefaultChatTransport - Add tool call rendering cards (CompactTraceCard, SqlToolCard with expandable SQL) - Implement inline span buttons in markdown responses for trace navigation - Support prefill input from Zustand store for suggestions integration - Reuse existing Conversation, Response, and CodeHighlighter components Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show streaming indicator for both submitted and streaming status - Disable send button during submitted status to prevent double-send - Clear prefill ref after consumption so same text can prefill again - Add cursor-pointer, hover, and focus-visible styles to inline span buttons - Disable New Chat button during streaming/submitted - Add gradient overlay at top of messages area matching chat.tsx - Add minimal-scrollbar class to scroll container - Use variant="default" instead of variant="ghost" + bg-primary on send button Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All items resolved in 01797e6 (code fixes) or marked as intentional (empty state icon, CompactTraceCard asymmetry). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ase 4) - Create suggestions.ts with route-pattern-to-suggestion mapping - Update collapsed-button.tsx with animated suggestion cycling (5s interval) - Clicking a suggestion prefills the agent prompt and opens floating mode - Create signals-pill.tsx that queries signal_events for the current trace - Signals pill shows dropdown with submenu: explain via agent or open in signals - Replace "Chat with trace" pill in header with SignalsPill component - Remove chatOpen/setChatOpen props from Header and trace-view/index.tsx - Remove Chat component import and usage from trace-view/index.tsx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ments - Replace server-only executeQuery with client-side fetch to /api/projects/.../sql route - Add UUID validation for traceId to prevent SQL injection - Combine multiple useLaminarAgentStore selectors with shallow equality (signals-pill, collapsed-button) - Fix suggestion exit animation to slide left (x: -10) for natural flow - Pause suggestion cycling on hover with mouseenter/mouseleave handlers - Add "Signals" text label to loading state spinner Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All Phase 4 QA/Designer issues resolved in fc59004. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…an navigation - Add traceIdContext to agent store for cross-page span navigation - Agent span buttons navigate to trace page when not already on it - Sync traceIdContext when navigating to trace pages - Add auto-scroll to focused row in InfiniteDataTable for eventId deep links - Reset scroll tracking when focusedRowId changes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…llow - Combine six individual useLaminarAgentStore calls into one with shallow equality - Follows CLAUDE.md best practice to avoid unnecessary rerenders Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show EventDetailPanel when eventId is present regardless of traceId in signal/index.tsx (deep link from "Open in Signals" now works) - Merge two useEffects for auto-scroll in infinite-datatable so reset and scroll happen in a single effect (fixes subsequent scroll failures) - Disable span buttons visually with tooltip when no trace context is available in agent-panel.tsx - Replace fragile pushState + PopStateEvent with router.replace for nuqs-compatible span navigation on trace pages - Clear traceIdContext when navigating away from trace pages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All 5 open items resolved in b7f6f24: - EventDetailPanel deep link condition - Auto-scroll useEffect merge - Span button disabled state with tooltip - router.replace instead of pushState - traceIdContext cleanup on navigation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Only include eventId param so EventDetailPanel renders without TraceView covering it - Users can still open the trace via the "View trace" button inside EventDetailPanel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…yboard shortcuts - Tune system prompt for more reliable tool usage: explicit rules for when to use compactTraceContext vs executeSql, signal event handling - Add isAiProviderConfigured() check; hide FAB when no AI provider set - Add keyboard shortcuts: Cmd+Shift+L to toggle, Escape to collapse - Make floating panel responsive with max-w/max-h constraints - Delete old chat.tsx (all functionality migrated to global agent) - Remove dead API routes: trace-scoped agent/messages, new-chat, route - Remove dead server actions: trace/agent/stream, messages, prompt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ortal - Use a single AgentPanel instance mounted in LaminarAgent - Portal AgentPanel into SideBySideWrapper container for side-by-side mode - SideBySideWrapper provides a container ref via Zustand store - Delete floating-panel.tsx (inlined into index.tsx) - Messages now persist when switching between floating and side-by-side Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sync useChat messages to/from store on mount/unmount to preserve chat across mode switches (portal remount) - Fix Cmd+Shift+L shortcut by using e.key.toLowerCase() instead of exact match (Shift produces uppercase "L") - Lower floating panel z-index from z-[60] to z-40 so dialogs (z-50) render above it - Skip Escape-to-collapse when focus is in textarea/input/contenteditable - Combine 4 separate useLaminarAgentStore calls into single selector with shallow equality - Always render ResizablePanelGroup in SideBySideWrapper and control agent panel via resize(0)/resize(35) to prevent children remount - Add aiEnabled field to store (set from server layout via initializer component) and conditionally hide "Explain signal" option in SignalsPill when AI is not configured - Add min-h-[300px] to floating panel for short viewports Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tions matching
- Fix side-by-side panel not expanding: call panel.expand() before
panel.resize(35) and panel.collapse() instead of panel.resize(0),
since resize() fails silently on a collapsed panel with defaultSize={0}
- Fix trace-specific suggestions never matching: update getSuggestionsForRoute
to accept search params and match trace detail when traceId param exists,
since actual trace URLs use ?traceId= not /traces/{traceId}
- Fix floating panel click-through: add pointer-events-auto and
stopPropagation on the floating panel container
- Fix suggestion text clipping: add max-w-[200px] truncate on suggestion text
- Add aria-label attributes to mode-switch and close buttons in agent panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All items resolved in dbef233 or marked out of scope. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ble-panels v4 In react-resizable-panels v4, bare numbers passed to panel.resize() are interpreted as pixels, not percentages. This caused the side-by-side panel to expand to only ~35px instead of 35% width. Fix: use "35%" string for resize() and percentage strings for defaultSize/minSize props. Also remove collapsible/collapsedSize props which are unnecessary when controlling size programmatically. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…de-switch message persistence - Extract traceId from search params via useSearchParams() as fallback when URL uses ?traceId= query param instead of path segment - Update isOnTracePage to match /project/*/traces with traceId in search params - Raise floating panel and FAB from z-40/z-50 to z-[55] so they appear above trace detail panel (z-50) - Raise dialog overlay and content from z-50 to z-[60] so dialogs still appear above the floating agent panel - Fix conversation history loss during mode switch by syncing messages to store on every update (not just unmount) and reading initial messages from a ref snapshot to avoid React batching timing issues Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…and floating panel position - Add `relative` and `overflow-hidden` to left panel content div so absolutely-positioned children (trace detail, event detail) are contained within the left panel bounds in side-by-side mode - Change floating panel from `bottom-6` to `top-6` so it anchors to the top-right of the viewport instead of bottom-right - In non-side-by-side mode, the left panel is 100% width so behavior is unchanged; in side-by-side mode, absolute elements now correctly position relative to the left panel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oper clipping The ResizablePanel's internal flex-grow div was expanding beyond the panel bounds, causing content to overflow. Adding overflow-hidden to the panel ensures children are visually clipped at the panel boundary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change FAB button icon from Laminar logo to Sparkles (lucide-react) - Floating window now extends full viewport height (top to bottom with gap) - Add left-edge draggable resize handle on floating window (min 320px, max 700px) - Update view mode icons: Columns2 for floating header, Layers for side-by-side - Make open-chat suggestions URL-dependent using shared route-to-suggestion map - Each route now has >3 suggestions; "Summarize trace" only appears on trace pages - Fix layout height: use flex-1 min-h-0 on ResizablePanelGroup for proper flex fill - Remove withHandle from side-by-side ResizableHandle (cleaner appearance) - Fix unnecessary SQL on trace summarization via explicit prompt instructions - Move SQL copy button to card header, show only when expanded - Fix span pill hover: use ring-2 instead of bg change to avoid size growth - Update signals prompt to include payload request - Change CompactTraceCard icon from Search to ListTree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…vals page - Add `flex flex-col` to left panel content wrapper in side-by-side-wrapper.tsx so flex children (Tabs, evaluations content) properly fill available height instead of overflowing the viewport. This single fix resolves both the trace table extending below the viewport and the evals page not filling height. - Change floating panel gap from top-3/bottom-3 to top-4/bottom-4 for better consistency with FAB's 24px offset positioning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Switch Layers to Layers2 icon for floating mode toggle - Thin resize handle (w-0.5) and raise z-index (z-50) for better UX - Move scroll-to-bottom button to left-3 to avoid agent panel overlap - Add .agents/, PLAN.md, SPEC.md, TESTING_PLAN.md, TODO.md to .gitignore Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eanup - Consolidate CollapsedButton with keyed inner component for clean state reset - Fix collapsed width not animating back (width: undefined → width: 40) - Add AnimatePresence slide-in/out animation for floating panel - Remove all keyboard shortcuts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…n stable Extract SuggestionSpan as keyed child so route changes only remount the suggestion text, not the sparkle button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tand fix - Move New Chat button to header (reload, change view, close icons) - Split tool-call-cards into one component per file - Fix deprecated Zustand useStore signature with useShallow - Remove resolved TODO/QUESTION comments Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: global store * feat: add global store
…vents/URL params
- Signal events table: replace CustomEvent("open-trace") with direct store access
via laminarAgentStore refs; clicking trace ID now opens trace panel, activates
side-by-side mode, and prefills agent with signal analysis prompt
- Agent panel: replace URL-based span navigation (router.replace) with
traceViewStore.selectSpanById() via refs, fixing span clicks in embedded
trace views (signals page)
- Remove open-trace event listener from events-table/index.tsx
- Update canNavigateToSpan to use refs.traceView instead of URL-based detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…-based selectedSpan - Remove useSpanId() nuqs hook usage from all trace view components - Restore selectedSpan and setSelectedSpan from Zustand store as source of truth - Remove @deprecated JSDoc comments from store fields - Update handleSpanSelect to use store + URL params for bookmarkability - Revert tree, list, condensed-timeline, and shared trace view components - Keep URL param updates for bookmarkability while store drives selection - Fixes embedded trace view (signals page) where URL changes caused navigation Files changed: - store/base.ts: remove @deprecated from selectedSpan/setSelectedSpan - index.tsx: use store selectedSpan, update URL via router.replace - tree/span-card.tsx: read selectedSpan from store - tree/index.tsx: read selectedSpan from store for scroll - list/list-item.tsx: read selectedSpan from store - list/index.tsx: read selectedSpan from store for scroll - list/mini-tree.tsx: use selectSpanById + router.push - condensed-timeline/index.tsx: use store selectedSpan/setSelectedSpan - shared/traces/trace-view.tsx: use store selectedSpan/setSelectedSpan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…s query signal_events table does not have a project_id column, causing 500 errors when fetching event details. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix prepared fixes for all 3 issues found in the latest run.
- ✅ Fixed: SQL injection via string interpolation of URL params
- I added UUID validation for
signalIdandeventIdin the route and return HTTP 400 for invalid identifiers before building the SQL query.
- I added UUID validation for
- ✅ Fixed: Trace ID click unconditionally opens agent, breaks non-AI flow
- I restored trace URL synchronization on trace-ID click and gated Laminar Agent opening/prefill logic behind the
LAMINAR_AGENTfeature flag.
- I restored trace URL synchronization on trace-ID click and gated Laminar Agent opening/prefill logic behind the
- ✅ Fixed: Event panel renders behind trace view simultaneously
- I restored the
!traceIdcondition for rendering the event drawer so event and trace panels are mutually exclusive.
- I restored the
Or push these changes by commenting:
@cursor push 0be0ab07fe
Preview (0be0ab07fe)
diff --git a/frontend/app/api/projects/[projectId]/signals/[id]/events/[eventId]/route.ts b/frontend/app/api/projects/[projectId]/signals/[id]/events/[eventId]/route.ts
--- a/frontend/app/api/projects/[projectId]/signals/[id]/events/[eventId]/route.ts
+++ b/frontend/app/api/projects/[projectId]/signals/[id]/events/[eventId]/route.ts
@@ -1,14 +1,25 @@
import { type NextRequest } from "next/server";
+import { z } from "zod/v4";
import { executeQuery } from "@/lib/actions/sql";
import { type EventRow } from "@/lib/events/types";
+const RouteParamsSchema = z.object({
+ signalId: z.string().uuid(),
+ eventId: z.string().uuid(),
+});
+
export async function GET(
_req: NextRequest,
props: { params: Promise<{ projectId: string; id: string; eventId: string }> }
): Promise<Response> {
const { projectId, id: signalId, eventId } = await props.params;
+ const parsedParams = RouteParamsSchema.safeParse({ signalId, eventId });
+ if (!parsedParams.success) {
+ return Response.json({ error: "Invalid signal or event identifier." }, { status: 400 });
+ }
+
try {
const query = `
SELECT id, signal_id as signalId, trace_id as traceId, payload, timestamp
diff --git a/frontend/components/signal/events-table/columns.tsx b/frontend/components/signal/events-table/columns.tsx
--- a/frontend/components/signal/events-table/columns.tsx
+++ b/frontend/components/signal/events-table/columns.tsx
@@ -140,52 +140,62 @@
},
];
-const staticColumnsAfterPayload: ColumnDef<EventRow>[] = [
- {
- accessorKey: "id",
- cell: (row) => <Mono>{String(row.getValue())}</Mono>,
- header: "ID",
- size: 100,
- id: "id",
- },
- {
- accessorKey: "traceId",
- header: "Trace ID",
- cell: (row) => {
- const traceId = String(row.getValue());
- const event = row.row.original;
- return (
- <div className="flex items-center gap-1 min-w-0">
- <button
- className="font-mono text-xs min-w-0 truncate"
- onClick={(e: MouseEvent) => {
- e.stopPropagation();
- // Open trace panel via signal store
- const agentState = laminarAgentStore.getState();
- const signalStore = agentState.refs.signal;
- if (signalStore) {
- signalStore.getState().setTraceId(traceId);
- }
- // Open Laminar Agent in side-by-side view with prefilled prompt
- agentState.setViewMode("floating");
- agentState.setPrefillInput(
- `Show me the payload of this signal event ${event.id} formatted in a table, explain why it was detected on this trace ${traceId}, and detail which spans are relevant and why`
- );
- }}
- >
- {traceId}
- </button>
- <CopyTooltip value={traceId} className="">
- <Copy className="size-3" />
- </CopyTooltip>
- </div>
- );
+type BuildEventsColumnsOptions = {
+ onTraceIdClick?: (traceId: string) => void;
+ aiEnabled?: boolean;
+};
+
+function getStaticColumnsAfterPayload({
+ onTraceIdClick,
+ aiEnabled = false,
+}: BuildEventsColumnsOptions): ColumnDef<EventRow>[] {
+ return [
+ {
+ accessorKey: "id",
+ cell: (row) => <Mono>{String(row.getValue())}</Mono>,
+ header: "ID",
+ size: 100,
+ id: "id",
},
- size: 180,
- id: "traceId",
- },
-];
+ {
+ accessorKey: "traceId",
+ header: "Trace ID",
+ cell: (row) => {
+ const traceId = String(row.getValue());
+ const event = row.row.original;
+ return (
+ <div className="flex items-center gap-1 min-w-0">
+ <button
+ className="font-mono text-xs min-w-0 truncate"
+ onClick={(e: MouseEvent) => {
+ e.stopPropagation();
+ onTraceIdClick?.(traceId);
+ if (!aiEnabled) {
+ return;
+ }
+
+ const agentState = laminarAgentStore.getState();
+ agentState.setViewMode("floating");
+ agentState.setPrefillInput(
+ `Show me the payload of this signal event ${event.id} formatted in a table, explain why it was detected on this trace ${traceId}, and detail which spans are relevant and why`
+ );
+ }}
+ >
+ {traceId}
+ </button>
+ <CopyTooltip value={traceId} className="">
+ <Copy className="size-3" />
+ </CopyTooltip>
+ </div>
+ );
+ },
+ size: 180,
+ id: "traceId",
+ },
+ ];
+}
+
const staticFilters: ColumnFilter[] = [
{
name: "ID",
@@ -204,7 +214,10 @@
},
];
-export function buildEventsColumns(schemaFields: SchemaField[]): {
+export function buildEventsColumns(
+ schemaFields: SchemaField[],
+ options: BuildEventsColumnsOptions = {}
+): {
columns: ColumnDef<EventRow>[];
columnOrder: string[];
filters: ColumnFilter[];
@@ -213,7 +226,7 @@
const payloadColumns = validFields.map(createPayloadColumnDef);
const payloadFilters = validFields.map(createPayloadFilter);
- const columns = [...staticColumnsBeforePayload, ...payloadColumns, ...staticColumnsAfterPayload];
+ const columns = [...staticColumnsBeforePayload, ...payloadColumns, ...getStaticColumnsAfterPayload(options)];
const columnOrder = ["timestamp", "traceId", ...validFields.map((f) => `payload:${f.name}`), "id"];
diff --git a/frontend/components/signal/events-table/index.tsx b/frontend/components/signal/events-table/index.tsx
--- a/frontend/components/signal/events-table/index.tsx
+++ b/frontend/components/signal/events-table/index.tsx
@@ -20,8 +20,10 @@
import ColumnsMenu from "@/components/ui/infinite-datatable/ui/columns-menu";
import DataTableFilter, { DataTableFilterList } from "@/components/ui/infinite-datatable/ui/datatable-filter";
import { TableCell, TableRow } from "@/components/ui/table.tsx";
+import { useFeatureFlags } from "@/contexts/feature-flags-context";
import { UNCLUSTERED_ID } from "@/lib/actions/clusters";
import { type EventRow } from "@/lib/events/types";
+import { Feature } from "@/lib/features/features";
import { useToast } from "@/lib/hooks/use-toast";
import { buildEventsColumns } from "./columns";
@@ -61,8 +63,11 @@
const [clusterId] = useClusterId();
const [eventId, setEventId] = useEventId();
const signal = useSignalStoreContext((state) => state.signal);
+ const setTraceId = useSignalStoreContext((state) => state.setTraceId);
const selectedClusterIds = useSignalStoreContext((state) => getFilterClusterIds(state, clusterId), shallow);
const isUnclusteredFilter = clusterId === UNCLUSTERED_ID;
+ const featureFlags = useFeatureFlags();
+ const aiEnabled = featureFlags[Feature.LAMINAR_AGENT];
const searchParams = useSearchParams();
const pathName = usePathname();
const router = useRouter();
@@ -73,8 +78,21 @@
const filterRaw = searchParams.getAll("filter");
const filter = useMemo(() => filterRaw, [JSON.stringify(filterRaw)]);
- const { columns, filters } = useMemo(() => buildEventsColumns(signal.schemaFields), [signal.schemaFields]);
+ const handleTraceIdClick = useCallback(
+ (traceId: string) => {
+ setTraceId(traceId);
+ const params = new URLSearchParams(searchParams.toString());
+ params.set("traceId", traceId);
+ router.push(`${pathName}?${params.toString()}`);
+ },
+ [pathName, router, searchParams, setTraceId]
+ );
+ const { columns, filters } = useMemo(
+ () => buildEventsColumns(signal.schemaFields, { onTraceIdClick: handleTraceIdClick, aiEnabled }),
+ [signal.schemaFields, handleTraceIdClick, aiEnabled]
+ );
+
const fetchEvents = useCallback(
async (pageNumber: number) => {
try {
diff --git a/frontend/components/signal/index.tsx b/frontend/components/signal/index.tsx
--- a/frontend/components/signal/index.tsx
+++ b/frontend/components/signal/index.tsx
@@ -133,7 +133,7 @@
)}
<AnimatePresence>
- {eventId && (
+ {eventId && !traceId && (
<motion.div
key="event-drawer"
initial={{ x: 400 }}This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
| WHERE signal_id = '${signalId}' | ||
| AND id = '${eventId}' | ||
| LIMIT 1 | ||
| `; |
There was a problem hiding this comment.
SQL injection via string interpolation of URL params
High Severity
The signalId and eventId URL path parameters are directly interpolated into a raw SQL query string without any sanitization or parameterization. While these values come from URL path segments, a crafted request could include SQL injection payloads (e.g., values containing single quotes) that modify the query logic. The existing executeQuery function sends this string directly to the backend ClickHouse endpoint.
| agentState.setViewMode("floating"); | ||
| agentState.setPrefillInput( | ||
| `Show me the payload of this signal event ${event.id} formatted in a table, explain why it was detected on this trace ${traceId}, and detail which spans are relevant and why` | ||
| ); |
There was a problem hiding this comment.
Trace ID click unconditionally opens agent, breaks non-AI flow
Medium Severity
Clicking a trace ID in the events table unconditionally calls agentState.setViewMode("floating") and setPrefillInput(...), regardless of whether the Laminar Agent feature is enabled. When the feature flag is off, the LaminarAgent component isn't rendered, so the user sees no visible response to the agent-related actions. Additionally, the old code updated the URL with the traceId param via router.push(...), but this URL synchronization was removed, so the trace view state is no longer bookmarkable or back-button compatible.
|
|
||
| <AnimatePresence> | ||
| {selectedEvent && !traceId && ( | ||
| {eventId && ( |
There was a problem hiding this comment.
Event panel renders behind trace view simultaneously
Low Severity
The !traceId guard was removed from the event detail panel visibility condition. Previously {selectedEvent && !traceId && ( ensured the event panel hid when a trace view was open. Now {eventId && ( allows both panels to render simultaneously — the event panel at z-40 rendered behind the trace view at z-50. This wastes resources and can cause visual glitches during animations.



Note
Medium Risk
Adds a new AI agent API that can execute arbitrary SELECT SQL and fetch trace context, and rewires core trace/signal UX to integrate it; this touches data-access paths and navigation behavior. Also removes the existing per-trace chat persistence endpoints, so regressions in trace-assistant workflows are possible.
Overview
Introduces a new Laminar Agent experience: a project-wide chat panel (collapsed button, floating drawer, or side-by-side resizable panel) that persists messages across remounts and can deep-link to spans via clickable
<span .../>references.Adds
POST /api/projects/[projectId]/agentwhich streams model responses using a newgetGlobalAgentSystemPromptand exposes two tools:compactTraceContext(trace structure viagetTraceStructureAsString) andexecuteSql(runs SELECT queries with 100-row truncation). The previous trace-scoped chat API routes and server-side message storage/streaming logic are removed.Signals/trace UI is updated to integrate the agent: signal event drawers now load a single event via a new
GET /signals/[id]/events/[eventId], event selection is driven by theeventIdquery param (with table auto-scroll to the focused row), and trace view gets a new signals dropdown that can prefill the agent to explain a specific signal on a trace.Written by Cursor Bugbot for commit b3db65d. This will update automatically on new commits. Configure here.