fix: make email whitelist comparison case-insensitive#1579
Open
xingyaoww wants to merge 31 commits intolmnr-ai:devfrom
Open
fix: make email whitelist comparison case-insensitive#1579xingyaoww wants to merge 31 commits intolmnr-ai:devfrom
xingyaoww wants to merge 31 commits intolmnr-ai:devfrom
Conversation
AI SDK fixes, fixes to shared,upgrade dependencies
UI updates, screenshots player, minimap
Realtime updates, move traces and spans to query engine, frontend fixes
fix migration, pass env vars (lmnr-ai#907)
Fix CH migrations, fix console logs, optimize tag writing
Infinite datatable fixes, new UI, versioned datapoints, performance improvements
fix eval page pagination, fix tool definitions, new dashboard experience
Quick search, fix rt, fix evals, better onboarding,robust rabbit, faster traces, fix metadata query
Bump some more deps, fix docker compose with wait for quickwit
improve search, SQL editor; rollouts; improve trace metadata; labelling queue API (dev)
updated semantic conventions, tracing agent, signals, landing, blog posts, clustering, batch workers
bump protobuf, zod, streamdown (lmnr-ai#1184)
Fix full text search, logs ingestion, better batching, fix dashboard, update llm prices
Update README, improve UI, improve evals, custom SQL columns in evals, shared evals, fix browser session needle, Laminar MCP server, improve debugger, parse OpenAI format and anthropic format messages
feat: add Okta as a NextAuth authentication provider (lmnr-ai#1323)
* bump rustls and aws-lc-rs deps * fix further deps * unpin rustls patch version
UI improvements, updated model costs, fix MCP, improved search in full build
redesign signals list page
hotfix helm migration (lmnr-ai#1435)
…lmnr-ai#1476) This commit will break cargo build, but it is ok, this is needed to unblock (and will be immediately followed by) lmnr-ai#1475.
* properly remove images (lmnr-ai#1487) * back * front * feat: add Keycloak as an OAuth authentication provider (LAM-1382) (lmnr-ai#1488) Add Keycloak support following the same pattern as Okta and other existing OAuth providers. Enabled via AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, and AUTH_KEYCLOAK_ISSUER environment variables. Co-authored-by: Robert Kim <skull8888888@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: laminar-coding-agent[bot] <262413387+laminar-coding-agent[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 1/2: use project+signal lock key; keep old key for the time of migration (#1485)
* 1/2: use project+signal lock key; keep old key for the time of migration
* release project lock at the very end for the time being
* chore: update comment
* fix log line as well
* 2/2 fix clustering lock. Now drop the old project-wide lock (#1492)
* add idempotency for signal batch handling (#1496)
* feat: add new spans table (#1495)
* feat: add new spans table
- migration (self-hosted users) move data immediately
- production writes to the new table via env var control
- the new table is to be backfilled in reverse order
- failures to write so new table are logged so we can manually backfill
* fix migration substring
* remove bf indexes
* remove preview columns from projection
* gemini models playground (#1502)
* feat(trace-view): signal events panel + dual layout strategy (#1486)
* feat(trace-view): refactor panel state into Zustand store (Phase 1)
- Add tracesAgentOpen, signalsPanelOpen, traceSignals, activeSignalTabId,
agentInitialMessages, agentPrefillInput state fields to BaseTraceViewState
- Add corresponding actions: setTracesAgentOpen, setSignalsPanelOpen,
setTraceSignals, setIsTraceSignalsLoading, setActiveSignalTabId,
openSignalInChat, clearAgentInjection
- Remove chatOpen local state from trace-view/index.tsx, replace with
tracesAgentOpen from store
- Update Header to read tracesAgentOpen from store instead of props
- Remove chatOpen/setChatOpen from Header props interface
- Change selectedSpan default behavior: no longer auto-selects first span,
only selects when spanId is present in URL or props
- Export TraceSignal type from store index
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): refactor to multi-panel layout with resizable panels
- Replace two-column layout with multi-panel system: Trace | Span | Signal Events | Traces Agent
- Add PanelWrapper component with left-edge drag handle resize (mousedown/mousemove pattern)
- Each panel defaults to 480px width, minimum 200px, independently resizable
- Span panel only visible when a span is selected, with X close button in SpanControls header
- Signal Events panel placeholder appears when Signals toggle is clicked
- Traces Agent panel placeholder appears when Traces Agent toggle is clicked
- Add "Signals (N)" button with Radio icon to trace header, next to Traces Agent button
- Rename "Chat with trace" to "Traces Agent" in header
- Simplify header: always show search and timeline controls (no longer hidden when chat is open)
- Chat no longer takes over the trace tree area; it will get its own dedicated panel in Phase 4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-events): implement Signal Events panel in trace view (Phase 3)
- Create API route at traces/[traceId]/signals to fetch signals + events for a trace
- Query signal_events by trace_id, join with signals table for metadata
- Create SignalEventsPanel with tabbed interface per associated signal
- Create SignalTabBar with session-player-style tab buttons (border-b-2 active state)
- Add truncated tab names with max-w-[120px] and createPortal hover tooltip
- Create SignalTab with PayloadValue rendering matching event-detail-panel pattern
- Add "Open in AI Chat" button triggering openSignalInChat store action
- Add "Open in Signals" button linking to signal page
- Wire SignalEventsPanel into trace-view replacing placeholder from Phase 2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal): refactor signal page to remove event detail drawer and simplify trace flow
- Remove AnimatePresence/EventDetailPanel drawer from signal/index.tsx
- Remove ArrowUpRight button from trace ID column in events-table/columns.tsx
- Set CopyTooltip delayDuration to 300ms for trace ID tooltip ("Click to copy")
- Make event row click open trace panel via traceId URL param (nuqs-compatible)
- Remove window.addEventListener("open-trace") custom event pattern
- Highlight first matching event row when trace panel is open
- Add delayDuration prop to CopyTooltip component for configurability
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(traces-agent): wire Chat component into Traces Agent panel with injection support
- Replace placeholder in trace-view with actual Chat component
- Add ref-guarded one-time message injection from store (agentInitialMessages)
- Support prefill input from store (agentPrefillInput) for "Open in AI Chat" flow
- Read injection state from useTraceViewBaseStore, clear after applying
- Pass selectSpanById and fetchSpans as callbacks for span navigation
- Panel header "Traces Agent" + X close provided by PanelWrapper
- Traces Agent not shown on shared traces page (separate component)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-events-panel): harden signal panel rendering to prevent crashes
- Add shallow equality to Zustand store selectors in trace-view index,
header, and signal-events-panel to prevent unnecessary re-renders
that could cascade into rendering errors
- Add equalityFn parameter support to useTraceViewStore hook
- Use individual selectors in SignalEventsPanel for stable references
- Add defensive Array.isArray check on API response data
- Add defensive fallback for events (events ?? []) in SignalTab
- Add null-safety for prompt field (prompt ?? "")
- Guard createPortal with document.body null check in SignalTabBar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update TODO with fix status for signal panel crash
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): phase 6 polish - add span panel close button, delete unused event-detail-panel
- Add X close button to span panel via PanelWrapper (floating when no title header)
- Delete event-detail-panel.tsx (no longer imported after signal page refactor)
- Verify shared trace view has no Signals/Traces Agent buttons (correct)
- Verify full-screen trace page works with multi-panel layout (correct)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(button): prevent React.Children.only crash when asChild is true
When Button uses asChild (Radix Slot), the JSX template was rendering
both {IconComponent && ...} and {children} as siblings. In React 19,
React.Children.count now counts null entries, so [null, <Link>] has
count=2. This triggers Radix Slot's guard: React.Children.only(null)
which throws "expected to receive a single React element child".
Fix: when asChild is true, pass only {children} to Slot, skipping
the IconComponent injection entirely. This ensures Slot always
receives exactly one child element.
Fixes the "Signals (N)" button crash in the trace view header.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update TODO with signal panel crash fix status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* wip: signal events in trace panel component
* wip: semi-working resize behavior
* feat(trace-view): signal events as horizontal panel + dual layout strategy
- Extract signal events panel from vertical ResizablePanelGroup into its
own independent horizontal panel (between span and chat panels)
- Add proper header to signal events panel matching chat panel style
- Replace custom tooltip with standard Tooltip component in signal tab bar
- Add traceId to signal tab links ("Open in Signals" includes ?traceId=)
Introduce dual layout system for TraceView:
- FixedWidthLayout: additive pixel-width panels with custom drag resize
(used by modal views: traces table, evaluations, signals)
- FillWidthLayout: zero-sum ResizablePanelGroup with pixel min widths
(used by full-screen trace view)
Add isFillWidth and isAlwaysSelectSpan props to TraceView:
- isFillWidth switches between layout strategies
- isAlwaysSelectSpan auto-selects first span and hides span close button
- Full-screen trace view uses both props
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): move signal events panel into header as conditional card
- Import and render SignalEventsPanel in header/index.tsx between button row and TraceViewSearch
- Wrap in conditional card with rounded-md border bg-card max-h-[200px]
- Pass traceId prop through Header -> TracePanel chain
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-panel): replace custom SignalTabBar with standard Tabs component
- Use Tabs/TabsList/TabsTrigger/TabsContent from components/ui/tabs
- Apply horizontal scroll with overflow-x-auto no-scrollbar on TabsList
- Add min-w-[120px] max-w-[120px] truncate with tooltip on each trigger
- TabsContent gets overflow-y-auto for vertical scroll within card
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): remove signal panel from layout panels and store
- Remove signalPanel and showSignal from TraceViewPanels interface
- Remove signal panel sections from FillWidthLayout and FixedWidthLayout
- Remove signalPanelWidth from store state, persistence, and resize logic
- Signal panel now lives entirely in the header card
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-panel): cleanup - remove dummy tabs, header, and signal-tab-bar
- Remove dummy tabs from SignalEventsPanel
- Remove standalone header with close button (toggle in trace header controls visibility)
- Delete signal-tab-bar.tsx (replaced by standard Tabs component)
- Remove unused onClose prop, X and Button imports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): wrap ref assignment in useEffect to fix React compiler lint error
- Move resizePanelRef.current assignment into useEffect to avoid
writing to refs during render, which violates React compiler rules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): eagerly fetch signal count so header shows correct count
- Move signal fetch logic to Header component so it runs when the trace
loads, not only when the signals panel is opened
- SignalEventsPanel still guards against duplicate fetches via its
traceSignals.length > 0 check
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-tab): icon-only action buttons + simplified event count
- Replace full-text action buttons with icon-only ghost buttons (sparkles, external-link)
- Put action buttons on same line as event count with justify-between
- Add Tooltip wrappers for discoverability
- Simplify event count to just "N events", remove timestamp
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-panel): use TabsList as card header directly
- Move border/bg-card/rounded-md styling into the Tabs component itself
- Remove redundant card wrapper div from header/index.tsx
- TabsList now serves as the card header row with rounded top corners
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-card): add bottom-edge drag resize handle
- Add ResizableSignalCard wrapper with mouse-drag resize on bottom edge
- Default height 180px, min 80px, max 500px
- Visual handle indicator (small rounded bar) at bottom
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): prevent chat panel from dominating layout height
- Replace 'grow' with 'h-full' on chat container so it fills its
allocated space without pushing other panels
- Remove h-full from empty state to prevent stretching
- Remove flex-1 from chat panel wrapper in trace-view-content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* wip
* feat(signals): auto-enable signals panel when opening trace from signals page
- Add initialSignalsPanelOpen prop to TraceViewProps and TraceViewContentProps
- Set signalsPanelOpen=true on mount when prop is provided
- Pass initialSignalsPanelOpen from signals page TraceViewSidePanel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(signal-tab): render span references as clickable badges in signal event payloads
- Extract span-reference rendering from chat.tsx into shared span-reference.tsx utility
- Refactor chat.tsx to use shared renderSpanReferences function
- Update PayloadValue in signal-tab.tsx to render span references as clickable badges
- Clicking a span reference in signal events selects the span in trace view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: untrack TODO.md from version control
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): increase default signal card height to 300px
- Change DEFAULT_SIGNAL_CARD_HEIGHT from 180 to 300 for better content visibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): fix scroll by making TabsContent a flex container
- Change TabsContent to flex flex-col with overflow-hidden so inner
SignalTab's flex-1 and overflow-y-auto work correctly
- The scroll now happens inside SignalTab's content area as intended
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): left-align tab text when truncated
- Add text-left justify-start to TabsTrigger so truncated signal names
show the beginning of the name, not the middle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): move action buttons inside scroll container
- Action buttons row now scrolls with the event payload content
- Removed border-b from the actions row per designer feedback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): add "Signal events" title header above tabs
- Add small header text above the TabsList for context
- Adjust TabsList rounding to rounded-none since header is now above it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-events-panel): fix active tab style by isolating tooltip data-state
TooltipTrigger asChild was merging its data-state="closed" directly onto
TabsTrigger, overriding Radix Tabs' data-state="active" attribute. This
prevented the data-[state=active]:bg-background style from ever applying.
Fix: wrap TabsTrigger in a <span> as the asChild target for TooltipTrigger,
so tooltip's data-state lives on the span while TabsTrigger keeps its own.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): ensure tab name truncation with ellipsis works
- Wrap signal tab name text in a block span with truncate class
- Add overflow-hidden to TabsTrigger so flex layout doesn't prevent clipping
- Text now properly shows "..." when signal name exceeds 120px width
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(signal-panel): single bordered container layout
- Move rounded-md border bg-card from inner Tabs to ResizableSignalCard
- Move "Signal events" label into ResizableSignalCard above the tabs
- Remove border-b from TabsList — tabs sit inside padded container
- Resize handle is now inside the bordered container
- Result: one container owns border + padding, no nested borders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): add X close button to signal events card
- Add X button right-aligned on the same line as "Signal events" label
- Uses ghost variant Button with lucide X icon (h-6 w-6)
- Calls setSignalsPanelOpen(false) to close the panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): fix scroll on signal card content
- Make TabsContent use overflow-y-auto directly instead of nested flex
- Remove h-full overflow-hidden wrapper div from SignalTab
- Scroll now works because TabsContent gets a concrete height from
flex-1 min-h-0 and scrolls its own content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-panel): tabs respect container padding
- Add px-2 to SignalEventsPanel wrapper in ResizableSignalCard
- Change TabsList from rounded-none to rounded-md for padded context
- Tabs now sit inside the card's padded area consistently
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(span-reference): prevent second span click from breaking
- Remove onSearchSpans call from SpanBadge click handler
- The search was replacing the spans array with filtered results,
making subsequent span references unfindable
- Span reference clicks now only select the span without filtering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): don't stretch to fill entire panel height
- Change h-full to max-h-full on chat root container
- Chat now sizes to content but won't exceed parent bounds
- Fixes full-screen trace view where chat filled entire height
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-events-panel): remove w-full from TabsList so tabs hug content
- TabsList base already has w-fit, w-full was overriding it
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-tab): remove redundant inner px-2 padding
- Parent ResizableSignalCard already applies px-2, so inner px-2 caused
double horizontal padding on event payload content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal): default signal tab to current signal when opening trace panel
- Pass initialSignalId from signal page through TraceViewSidePanel →
TraceView → TraceViewContent → TracePanel → Header
- In Header's signal fetch effect, prefer initialSignalId if it exists
in the fetched signals, otherwise fall back to first signal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-card): add flex-col to wrapper div so flex-1 chain works for scroll
The wrapper div around SignalEventsPanel was not a flex container,
so the Tabs flex-1 had no effect and overflow-y-auto never triggered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-card): tabs max-w-full for scroll, styled-scrollbar, header padding
- Add max-w-full and styled-scrollbar to TabsList for horizontal scroll
- Add styled-scrollbar to TabsContent for vertical content scroll
- Adjust header row padding: pl-0.5, remove pb
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signal-card): force horizontal-only scroll on TabsList
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(span-reference): detect markdown-style span links in signal payloads
- Add MD_SPAN_LINK_REGEX to match [Label](url?spanId=UUID) patterns
- Add MarkdownSpanBadge component that calls onSelectSpan directly
(no resolveSpanId needed since URL contains the real span UUID)
- Refactor renderSpanReferences to collect both XML and markdown
matches, sort by position, and render with overlap protection
- Update hasSpanReferences to check both formats
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: default signal tab, span badge color, tabs flex layout
- Fix default signal tab: add separate useEffect to apply initialSignalId
even when signals were already fetched by the panel (not just header)
- Span badges: add text-primary-foreground for readable text on bg-primary
- Tabs: w-full container, flex-1 on triggers, min-w-[120px] (no max-w)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: move initialSignalId into Zustand store to fix default tab selection
The previous approach threaded initialSignalId as a prop through 5
components and used useEffects to apply it — but SignalEventsPanel's
fetch raced with Header's fetch, and whichever completed first would
set the tab to mapped[0] before the other could apply initialSignalId.
Now initialSignalId is set in the store at creation time. Both fetch
locations (Header and SignalEventsPanel) check the store value when
selecting the default tab. Whichever wins the race gets it right.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): change root from overflow-auto to overflow-hidden
The root container had overflow-auto, which let it grow beyond the
viewport instead of constraining children. In full-screen trace view,
this caused the input to float up instead of pinning to the bottom.
Conversation already has its own overflow-y-auto for message scrolling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): update example questions and placeholder text
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: DEFAULT_PANEL_WIDTH matches MIN_PANEL_WIDTH, split chat useEffects
- Change DEFAULT_PANEL_WIDTH from 365 to 375 to match MIN_PANEL_WIDTH,
fixing invalid initial state for resizable panels.
- Split chat's single useEffect into two: one for loading existing
messages (depends on trace.id), one for one-time injection (depends
on agentInitialMessages). Previously clearAgentInjection() nulled
agentInitialMessages which was a dep of the same effect, triggering
a re-fetch that overwrote the injected messages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: prevent full-screen trace view from scrolling page
- trace.tsx: add overflow-hidden to the flex-1 wrapper
- header/index.tsx: add flex-shrink-0 so the header doesn't compress
- trace-panel.tsx: add flex-1 min-h-0 to ResizablePanelGroup so it
fills remaining space without overflowing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove unused hasSpanReferences export
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: include cost heatmap state from upstream merge
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): replace effect-based injection with pendingChatInjection state
The old approach used two separate useEffects + a hasInjectedRef guard to
bridge signal data from the store into the Chat component. This broke on
repeated "Open in AI Chat" clicks (ref never reset) and had a race where
the message fetch could overwrite injected messages.
New approach: a single `pendingChatInjection` field in the store holds
the signal definition + event payload. One effect in Chat consumes it
(via consumePendingChatInjection which atomically reads and nulls it).
When pending, the fetch is skipped entirely — no race possible. Repeated
clicks just overwrite the pending state; the effect picks up whatever's
latest.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): prevent fetch from overwriting injected messages
Consuming pendingChatInjection nulls the store value, which caused a
re-render that re-triggered the single effect — falling through to the
fetch path and overwriting the injection.
Split back into two effects with a justInjectedRef guard:
- Effect 1 (injection): fires on pendingChatInjection changes, consumes
and sets justInjectedRef
- Effect 2 (fetch): fires on trace.id, skips when justInjectedRef is set
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): append injected messages instead of replacing
Use setMessages(prev => [...prev, ...new]) so existing chat history
is preserved when injecting signal context via "Open in AI Chat".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): move new chat button to header as refresh icon
Replace the inline "New Chat" outline button with a ghost icon button
(RotateCcw) in the header, positioned just left of the close button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): render span links as badges in injected signal payloads
Signal event payloads contain markdown-style span links like
[SpanName](url?spanId=UUID). When injected into chat, the markdown
renderer converts these to <a> tags before the custom `code` component
override can intercept them.
Add an `a` component override that catches links with spanId params
and renders them as clickable span badges, matching the behavior in
normal AI-generated responses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Revert "fix(chat): render span links as badges in injected signal payloads"
This reverts commit 07d798cd2ff8b432fae95d0315dad5b5ef28d22d.
* fix(chat): format injected signal payload as markdown headers
Parse the JSON payload one layer deep: keys become ### headers,
values become body text. This avoids raw JSON in the chat and
prevents the markdown renderer from converting span reference
URLs into <a> tags (since the values are no longer inside
markdown link syntax from the raw JSON).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(chat): simplify injected signal definition to name + prompt
Format as "### SignalName\nprompt" instead of dumping the full signal
name, prompt, and JSON schema. The schema isn't useful context for
the chat.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(signals): remove duplicate signal fetch from SignalEventsPanel
Header always mounts and fetches signals into the store. SignalEventsPanel
now just reads from the store instead of duplicating the same fetch logic,
eliminating the race condition between the two components.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): use percentage sizing in FillWidthLayout, fix empty span panel
FillWidthLayout was passing pixel constants (500, 375) as defaultSize/
minSize to react-resizable-panels which defaults to percentage units.
Replace with proper percentage values.
Also fix permanent skeleton loaders when isAlwaysSelectSpan is true but
no spans exist — show "No span selected" message instead, and don't
show the span panel at all when spans array is empty.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): wire initialSignalsPanelOpen through store, clear stale spanId
- Pass initialSignalsPanelOpen from TraceView props through store constructor
- Clear spanId when navigating to a new trace in side panel
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): auto-select first span when opening trace side panel
Always select the first span after fetchSpans when no spanId is specified,
so the span panel is open by default for both side panel and full-page views.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): chat input flex-row layout, signal tabs full-width
- Change chat input from absolute positioning to flex-row so input
doesn't go behind the send button
- Apply flex-1 and w-full to signal tabs to divide width evenly,
remove horizontal scroll
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(signals): remove event count, use outline buttons with text labels
- Remove event count badge from signal panel header
- Left-align action buttons since event count is removed
- Change icon-only ghost buttons to outline buttons with full text labels,
matching the Media button pattern (variant="outline", h-6, bg-transparent)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): hide signals toggle when no signal events
Only show the signals toggle button in the trace header when
traceSignals.length > 0.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: resize behavior with max width
* fix: eval trace sidepanel
* fix: minor style
* refactor: rename
* feat(trace-view): span panel state, animations, and polish
- Add spanPanelOpen boolean to store (persisted) to decouple panel
visibility from selectedSpan data loading
- Add SpanViewSkeleton component matching actual SpanControls layout
- Add framer-motion AnimatePresence curtain-reveal for span/chat panels
- Add spring entrance animation on TraceViewSidePanel
- Key store provider by traceId so animation shell persists across traces
- Respect spanPanelOpen preference when auto-selecting spans
- Open in Signals link opens in new tab
- Toolbar shrink behavior: overflow-hidden + truncate on Content/Media buttons
- Guaranteed gap between left and right toolbar groups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): resize animation, eval side panel, scroll behavior
- Disable framer-motion transition during drag via isResizing flag
- TraceViewSidePanel accepts children prop for custom headers
- Eval view uses TraceViewSidePanel with comparison selector as children
- Key store provider by traceId in eval to prevent stale trace data
- Scroll to selected span uses align:"auto" instead of "start"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): hide chat/timeline buttons during loading, remove auto-open chat
- Remove auto-open chat for >1000 token traces
- Hide Chat with trace and timeline buttons until spans load
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(trace-view): selectSpanById now uses overridden setSelectedSpan
- Ensures spanPanelOpen and fitPanelsToMaxWidth are triggered when
selecting spans via chat references or signal tab badges
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(trace-view): auto-open chat panel based on token count
- Add initialChatOpen option to TraceViewStoreProvider and base store
- Thread showChatInitial prop through TraceView and TraceViewSidePanel
- Compute showChatInitial as totalTokens > 1000 on traces table row click
- Store showChatInitial in TracesStore alongside traceId
- Signal page always passes showChatInitial={true}
- Evaluation page unchanged (defaults to false)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* open chat by default
* fix: dependency array
* fix: bugbot comments
* fix: reduce signal events API from 3 queries to 2
Fetch all signal events for a trace in a single ClickHouse query
instead of first querying for distinct signal IDs then re-querying
for the full event rows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: signals panel blue
* fix: add shallow equality to useTraceViewBaseStore to prevent excessive re-renders
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: chat URL param and auto-open signals panel
Read ?chat=true URL param on dedicated trace page to open chat on load.
Auto-open signals panel when trace has signals.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace refs with empty-dep useEffects, revert pnpm-lock.yaml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: remove initialSignalsPanelOpen prop
Signals panel now auto-opens when signals are fetched, making this
prop redundant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: read spanPanelOpen from store at call time to avoid stale closure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: openSignalInChat uses setTracesAgentOpen to trigger panel fitting
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: Robert Kim <skull8888888@gmail.com>
* added preview to gemini models (#1504)
* low hanging agent improvements (#1505)
* low hanging agent improvements
* wording
* catch errors
* fix(LAM-1388): replace dropped input_lower/output_lower columns with lower() expressions (#1512)
The input_lower and output_lower materialized columns were dropped from
production ClickHouse, causing "Unknown expression or function identifier"
errors in the sessions view. Replace references with inline lower(input)
and lower(output) calls, and remove the columns from the ClickHouseSpan
TypeScript interface.
Co-authored-by: Robert Kim <skull8888888@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* disable writing to spans v2 (#1514)
The table was migrated back to spans and is to be dropped
* feat(LAM-1389): Slack Reports & Email Alerts – unified notification channels (#1517)
* feat(LAM-1389): add Slack support to Reports and Email support to Alerts
Unify notification channels across Reports and Alerts so both support
Slack and Email delivery.
Backend:
- Add get_slack_report_targets() DB query and push Slack notifications
from the reports generator alongside existing email ones.
- Add get_email_targets_for_event() DB query and push email
notifications from the alerts postprocessor alongside existing Slack.
- Include an HTML email template for alert notifications.
Frontend:
- Alerts: add Email toggle (subscribe with user email) to the manage-
alert sheet alongside the existing Slack channel selector.
- Reports: add Slack channel selector per report alongside the existing
email subscribe toggle; show SlackConnectionCard on the reports page.
- New API route for setting/removing Slack targets on reports.
- Fix Drizzle schema: mark alert_targets.integrationId nullable to
match the actual DB column.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify target handling in generator/postprocess, fix review bugs
- Replace separate get_email_report_targets + get_slack_report_targets
with single get_report_targets query returning all target types
- Replace separate get_slack_targets_for_event + get_email_targets_for_event
with single get_targets_for_event query returning all target types
- Refactor generator.rs and postprocess.rs to use single DB call with
unified loop that only differs in payload construction per target type
- Fix: email payload size error in generator.rs now uses continue instead
of return Err, preventing one oversized email from blocking Slack delivery
- Fix: Slack report notifications no longer render broken "View Trace"
button when project_id/trace_id are nil (reports have no trace)
- Fix: editing alert without Slack connected preserves existing Slack
targets instead of silently deleting them
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: track processed targets separately from skipped for retry detection
Skipped targets (missing email, missing channel_id, unknown type) were
not incrementing push_failures, so push_failures == total_targets could
be false even when every processable target failed. Now we track a
separate `processed` counter that only increments for targets we
actually attempt to push.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Rakhman Asmatullayev <rakhmandva@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Revert "feat(LAM-1389): Slack Reports & Email Alerts – unified notification channels (#1517)" (#1521)
This reverts commit 46669fe0f1e2f3e3866e5417ae4fffa69eec6a1b.
* Feat/snippets (#1519)
* feat: snippets UI WIP
* feat: snippets testing WIP
* feat: remove mock data, add span-card list item previews
* search snippets - backend
* fix
* chore
* chore
* do not use snippet_count field
* fix: address cursor review comments on snippets PR
- Fix stale fetchSpans on initial URL search: defer search-with-query
until trace (and its time range) is available, preventing Quickwit's
7-day default from hiding older traces.
- Fix highlight offset encoding: emit UTF-16 code-unit offsets instead
of Unicode codepoint counts so JS String.prototype.slice() highlights
the correct substring for text containing emoji/supplementary chars.
- Add tests for UTF-16 encoding correctness (emoji before match, emoji
in match, multiple emoji, emoji with ellipses, BMP CJK characters).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: remove snippet count, fix fetching
* feat: cleanup
* highlight search in mem
* add tracing
* feat: count snippets in traces
* back to spans table
* move logic to search module
* chore: stricter typing
* add project_id to tracing
* chore
* feat: fix fetching
---------
Co-authored-by: Olzhas Nurpeisov <olzhik1123@gmail.com>
Co-authored-by: lmnr-bot <bot@lmnr.ai>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix spans for signals (#1518)
* fix spans
* comments
* to low
* fix status agg
* fix(LAM-1393): remove span inputs/outputs from spans table query (#1525)
Stop querying span input and output columns in the spans table (all
spans across all traces view) to reduce payload size and query cost.
- Remove `substring(input, 1, 200) as inputPreview` and
`substring(output, 1, 200) as outputPreview` from default
spansSelectColumns
- Remove Input and Output column definitions and column order entries
from the spans-table component
- Remove inputPreview/outputPreview from SpanRow type, make them
optional on Span type
Co-authored-by: Dinmukhamed Mailibay <dinmukhamed.mailibay@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fix filters in signal events (#1526)
* fix: send filters as is
* fix: restore payload. prefix on filter keys to prevent collision with static filters
Payload filter keys without the prefix can collide with static processor
keys (id, trace_id, run_id), causing the wrong processor to be used.
Restore the payload. prefix and strip it in the defaultProcessor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: sanitize paramKey in buildColumnFilters to avoid dots in ClickHouse params
Filter columns with dot notation (e.g. payload.fieldName) produce
invalid ClickHouse named parameter names. Sanitize by replacing
non-alphanumeric characters with underscores, matching the pattern
already used in evaluation/query-builder.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Olzhas Nurpeisov <olzhik11@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: span preview generation with provider matching and LLM fallback (LAM-1384) (#1497)
* feat: span preview generation with provider matching and LLM fallback (LAM-1384)
Server-side span preview generation that replaces client-side output parsing.
The new flow fetches span data from ClickHouse, matches against known provider
schemas (OpenAI, Anthropic, Gemini, LangChain), falls back to Gemini flash
for unknown structures, and returns rendered preview strings to the client.
Key changes:
- New server action `getSpanPreviews` orchestrating the full preview flow
- Fingerprint-based caching with Postgres persistence (queries commented out
pending migration)
- Provider schema matching in `lib/spans/provider-keys.ts`
- LLM-based key generation in `lib/actions/spans/prompts.ts`
- Repurposed `useBatchedSpanOutputs` hook to fetch preview strings
- Disabled MustacheTemplateSheet UI (kept component file)
- Removed settings button from span cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle fingerprint collisions in span preview generation
When multiple spans share the same fingerprint (same name + schema shape),
the fingerprintToSpan Map would only keep the last span, causing all other
spans with that fingerprint to silently lose their preview.
Changed fingerprintToSpan from Map<string, ParsedSpan> to
Map<string, ParsedSpan[]> so all spans in a fingerprint group get their
preview rendered against their own parsedData. Also deduplicate structures
sent to the LLM to avoid redundant processing of identical schemas.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use output data for all span types, remove input/output side distinction
Tool, executor, and evaluator spans now query output data (same as LLM
spans) instead of input data. This simplifies the data fetching to a
single ClickHouse query and removes the "side" concept from ParsedSpan,
SpanStructure, and the LLM prompt.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: prompt, ui, generation logic, refactor, shared trace
* chore: remove unused capPayloadSize function
The function was no longer imported after the refactor to use
truncateFieldValues directly in deduplicateByFingerprint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: restore null guard for listSpan in virtualized list
Prevents a TypeError crash when listSpans[virtualRow.index] is undefined
during virtualization race conditions (e.g., list shrinking between renders).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct misleading comment about pattern ordering in allPatterns
The comment said "Mixed content first" but OpenAI patterns are actually
first. Updated to accurately describe the ordering: OpenAI first, then
mixed content before Anthropic/LangChain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: prevent empty output from expanding spans and gate provider matching by span type
1. Empty string output (`""`) from fillMissing no longer causes
defaultExpanded to be true for non-preview span types. The check now
requires output to be both non-nil and non-empty.
2. applyCachedKeys now receives spanTypes and only calls matchProviderKey
for PROVIDER_SPAN_TYPES (LLM/CACHED), consistent with
applyProviderMatching. Prevents TOOL/EXECUTOR/EVALUATOR spans whose
data coincidentally matches a provider schema from getting
provider-transformed data applied to cached keys.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove unnecessary exports from fetchSpanData and extractFirstPrimitiveValue
Both functions are only used within the same file and don't need to be
part of the module's public API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: eliminate redundant matchProviderKey call in applyProviderMatching
Changed tryProviderMatch to return both the rendered string and the key,
so applyProviderMatching no longer needs to call matchProviderKey a
second time just to retrieve the key for saving.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: handle missing span_rendering_keys table in applyCachedKeys
The span_rendering_keys table is defined in the Drizzle schema but has
no migration yet, so it doesn't exist in the database. applyCachedKeys
now catches DB errors and treats all spans as uncached (cache miss),
preventing runtime crashes. saveRenderingKeys already had error handling.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: fall back to JSON preview when LLM returns fewer results than expected
When generatePreviewKeys returns fewer results than deduplicated
fingerprints, spans for unprocessed fingerprints were silently left
without a preview and ended up as empty strings via fillMissing. Now
applies JSON.stringify fallback for any spans not resolved after the
LLM results loop, consistent with the error-case fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove dead Mustache code from markdown.tsx
The `defaultValue` prop was only used by the deleted MustacheTemplateSheet
component. Remove the Mustache import, tryDeepParse, preprocessDataForMustache,
and the defaultValue template-rendering branch since they are unreachable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Only cache LLM-generated keys that pass validation
Previously, any non-null key returned by the LLM was persisted to the
DB cache regardless of whether validateMustacheKey succeeded. A
hallucinated key referencing nonexistent paths would render empty on
the first request (falling back to JSON), then get cached and produce
permanently empty previews on all subsequent requests.
Now the key is only added to keysToSave when validation succeeds for
at least one span sharing that fingerprint.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: prompt improvements
* feat: fix scroll
* feat: fix build
* feat: improve prompt, add loading
* feat: add migraiton
* feat: improve guessing, prompting, timeout 5 secs
* Replace deepParseAllValues with existing deepParseJson utility
deepParseAllValues was functionally equivalent to the existing
deepParseJson in lib/actions/common/utils.ts. Reuse the shared
utility to avoid duplication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add migration
* Keep snippet spans expanded regardless of output value
Snippet spans render inputSnippet/outputSnippet which don't depend on
the preview output. When output resolved to "" or null, the card would
collapse and hide the snippet content. Bypass the output check for
spans that have snippets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Validate cached keys on render, fall back to JSON on failure
applyCachedKeys used renderMustachePreview which returns empty strings
without fallback, while the fresh generation path uses
validateMustacheKey which rejects empty/[object Object] results. Use
validateMustacheKey in the cached path too so spans whose data happens
to render empty with a cached key get a JSON fallback instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: return tool outputs as is, address comments
* feat: update migrations
---------
Co-authored-by: Olzhas Nurpeisov <olzhik1123@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: lmnr-bot <bot@lmnr.ai>
* feat: fix reader mode (#1532)
* feat: show costs (#1535)
* fix(LAM-1361): Read SDK spans from spans table instead of the older tags table (#1510)
* fix(LAM-1361): insert SDK tags into ClickHouse tags table so they appear in the UI
Tags sent via the SDK (source=CODE) were only written to the `spans.tags_array`
column during ingestion but never inserted into the dedicated `tags` ClickHouse
table. The UI reads from the `tags` table, so SDK-sent tags were invisible.
This adds a CHTag struct and inserts CODE-source tags into the `tags` table
during span processing. Also fixes TagsList to display tag names directly
instead of only showing names that match a tagClass entry.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: read tags from spans.tags_array instead of deprecated tags table
Reverts backend changes (CHTag, Table::Tags, processor insertion) since the
tags table is deprecated. Instead, updates the frontend to:
- Query tags_array column from the spans table via arrayJoin
- Write/remove tags only via spans.tags_array (ALTER TABLE UPDATE)
- Simplify delete route to remove tag by name from tags_array
- Remove unused imports (dateToNanoseconds, generateUuid, TagSourceMap)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: encode tag name in URL path segment for delete requests
Tag id is now the tag name (not a UUID), so it must be URL-encoded
when used as a path segment to handle names with special characters
like /, ?, #, or spaces.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove redundant decodeURIComponent on auto-decoded route param
Next.js App Router auto-decodes dynamic route params, so calling
decodeURIComponent again would throw URIError on tag names containing
% followed by non-hex characters (e.g. "50%off").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add DISTINCT to tags query to prevent duplicates
The spans table uses MergeTree (not ReplacingMergeTree), so duplicate
rows for the same span_id are possible. Without DISTINCT, arrayJoin
would expand each row's tags_array independently, producing duplicate
tag entries and React key collisions in the UI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: await ClickHouse ALTER TABLE commands instead of fire-and-forget
With mutations_sync=0, the ALTER TABLE command returns immediately
while the actual mutation runs in the background, so awaiting it
does not block. This properly propagates connection/syntax errors
instead of silently swallowing them via .catch(console.error).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Dinmukhamed Mailibay <dinmukhamed.mailibay@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* overwrite eval values on every call to insert datapoints (#1536)
* overwrite eval values on every call to insert datapoints
* filter out null scores
* bump yaml and picomatch deps in frontend (#1528)
* bump yaml and picomatch deps in frontend
* bump brace-expansion in dev deps
* fix(evals): prevent score columns from being purged on page refresh (LAM-1380) (#1484)
The column-order sync effect ran before rebuildColumns had populated
columnDefs with score columns, so it treated them as stale and removed
them from the persisted order. Guard the effect with a columnsReady
check that waits until columnDefs includes the expected score columns.
Co-authored-by: kolbeyang@gmail.com <kolbeyang@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(LAM-1394): prevent alias shadowing when aggregating and filtering same column (#1530)
When using the same column for both aggregation and filtering (e.g.,
"Average total_tokens" with filter "total_tokens > 0"), ClickHouse
would fail with ILLEGAL_AGGREGATION because the generated alias
shadowed the column name in WHERE. Changed default alias from bare
column name to fn_column pattern (e.g., avg_total_tokens) and updated
frontend metric constants to provide matching explicit aliases.
Co-authored-by: Rakhman Asmatullayev <rakhmandva@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add trace to span search (#1543)
* feat: deep parse output, don't persist on db (#1539)
* feat(LAM-1398): preprocess data before Quickwit indexing (#1529)
* feat(LAM-1398): preprocess data before Quickwit indexing
Add text preprocessing in the QuickwitIndexerHandler to normalize
data before ingestion, fixing tokenization issues where literal
escape sequences like "\n" caused Quickwit's tokenizer to produce
incorrect tokens (e.g. "nlet" instead of "let").
The preprocessing pipeline (applied in order):
1. Unescape literal \n, \t, \r sequences to actual characters
2. Replace all whitespace-class characters with a single space
3. Strip ANSI escape codes
4. NFC Unicode normalization
5. Collapse runs of spaces and trim
Punctuation, casing, and multilingual content are preserved since
Quickwit's default tokenizer already handles lowercasing and
punctuation splitting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* remove unnecessary trimming
* fix: strip ANSI codes before whitespace normalization
Swap the order of steps 2 and 3 in the preprocessing pipeline so
ANSI escape codes are stripped before whitespace collapsing. This
prevents double spaces when an ANSI code sits between two spaces
(e.g. "text \x1b[31m more" now correctly produces "text more").
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Rakhman Asmatullayev <rakhmandva@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* LAM-1280: Add scoped error boundaries, improve error handling (#1393)
* LAM-1280: Add scoped error boundaries and handleRoute utility
Add workspace and project-scoped error boundaries to prevent all errors
from crashing to a single full-page error screen. Update the root
error.tsx to use window.location instead of useRouter for more reliable
navigation in error states.
Create a handleRoute wrapper utility that eliminates repetitive
try/catch boilerplate across ~105 API route files, providing consistent
error handling with ZodError -> 400 and other errors -> 500.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix DuplicateModelCostError returning 500 instead of 409
Add HttpError base class to route-handler so that action-layer errors
can carry an HTTP status code through the generic handleRoute wrapper.
DuplicateModelCostError now extends HttpError(409), restoring the 409
Conflict response that the frontend relies on to display duplicate-entry
messages.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use HttpError with correct status codes across all route handlers
Replace throw new Error(...) with throw new HttpError(..., status) in
all route handlers so that handleRoute returns the appropriate HTTP
status instead of 500 for every error:
- 401 for missing session / unauthenticated requests
- 403 for authorization failures
- 400 for input validation / bad request body
- 404 for not-found resources
This fixes auth failures, validation errors, and not-found cases that
were all incorrectly returned as 500 Internal Server Error after the
handleRoute refactor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Revert route handler refactoring, keep error boundaries only
Per reviewer feedback, revert all route files to their original form
(without handleRoute wrapper). The PR now only contains:
- Scoped error boundaries for project and workspace layouts
- Updated root error boundary
- The handleRoute utility (kept for potential future use)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove unused route-handler.ts
This file was left behind after the route handler refactoring was
reverted. It has no imports anywhere in the codebase.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix root error boundary Back button to use history navigation
The Back button was changed to navigate to /projects, making it
identical to the logo link. Restore go-back behavior using
window.history.back() to match the ArrowLeft icon and "Back" label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Extract shared scoped error boundary into reusable component
ProjectError and WorkspaceError were identical. Extract the shared UI
into components/error/scoped-error-page.tsx and re-export it from both
error.tsx files.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Consolidate error boundaries into single shared ErrorPage component
Extract common error page UI into ErrorPage component with backAction
and backLabel props, eliminating ~90% code duplication between root and
scoped error boundaries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: handle errors improve error ui
* Remove .catch() on DB queries that mask errors as empty results
The .catch(() => []) on the workspace list query in projects/page.tsx
and .catch(() => [{ count: 0 }]) on the membership count query in
onboarding/page.tsx treated transient DB errors as "no workspaces,"
silently redirecting existing users to onboarding. Let these errors
propagate to the error boundary instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: handle error in timestamp, ui
* feat: add guid type
* feat: propagate error
* docs: add error handling best practices to CLAUDE.md
Document the three error handling patterns used in the frontend:
- Client-side fetch: try/catch with res.ok check and toast notifications
- API route handlers: try/catch with ZodError distinction and proper HTTP status codes
- Server components: let errors propagate to error boundaries, only use notFound() for missing resources
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: propagate on failed api keys
* fix: let db errors propagate in shared pages instead of masking as 404
Remove blanket try/catch blocks around getCachedSharedEvaluation and
getCachedSharedTrace in the page components. These were converting all
errors (including DB connectivity failures, timeouts) into notFound()
responses, masking real server errors. The functions already return
undefined for missing resources, which the null checks handle correctly.
Actual errors now propagate to the root error boundary.
The try/catch in generateMetadata is kept since metadata generation
should gracefully fall back rather than crash the page.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Olzhas Nurpeisov <olzhik1123@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: lmnr-bot <bot@lmnr.ai>
Co-authored-by: cursor[bot] <cursor[bot]@users.noreply.github.com>
* fix: remove hardcoded height, fix poisoned state, form, add preset key (#1544)
* fix: remove hardcoded height, fix poisoned state, form, add preset key
* feat: revert lazy loading, isolation
* slack reports and email alerts (#1522)
* feat(LAM-1389): add Slack support to Reports and Email support to Alerts
Unify notification channels across Reports and Alerts so both support
Slack and Email delivery.
Backend:
- Add get_slack_report_targets() DB query and push Slack notifications
from the reports generator alongside existing email ones.
- Add get_email_targets_for_event() DB query and push email
notifications from the alerts postprocessor alongside existing Slack.
- Include an HTML email template for alert notifications.
Frontend:
- Alerts: add Email toggle (subscribe with user email) to the manage-
alert sheet alongside the existing Slack channel selector.
- Reports: add Slack channel selector per report alongside the existing
email subscribe toggle; show SlackConnectionCard on the reports page.
- New API route for setting/removing Slack targets on reports.
- Fix Drizzle schema: mark alert_targets.integrationId nullable to
match the actual DB column.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: unify target handling in generator/postprocess, fix review bugs
- Replace separate get_email_report_targets + get_slack_report_targets
with single get_report_targets query returning all target types
- Replace separate get_slack_targets_for_event + get_email_targets_for_event
with single get_targets_for_event query returning all target types
- Refactor generator.rs and postprocess.rs to use single DB call with
unified loop that only differs in payload construction per target type
- Fix: email payload size error in generator.rs now uses continue instead
of return Err, preventing one oversized email from blocking Slack delivery
- Fix: Slack report notifications no longer render broken "View Trace"
button when project_id/trace_id are nil (reports have no trace)
- Fix: editing alert without Slack connected preserves existing Slack
targets instead of silently deleting them
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: track processed targets separately from skipped for retry detection
Skipped targets (missing email, missing channel_id, unknown type) were
not incrementing push_failures, so push_failures == total_targets could
be false even when every processable target failed. Now we track a
separate `processed` counter that only increments for targets we
actually attempt to push.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address cursor bot review comments on PR #1522
- Fix oversized MQ payload retry loop: when all targets exceed the MQ
size limit, return HandlerError::permanent instead of transient since
payload size is deterministic and retrying won't help
- Deduplicate html_escape: make the function in email_template.rs public
and reuse it from postprocess.rs instead of maintaining a duplicate
The "editing alert deletes other users' email targets" comment is a
false positive — the UI only manages the current user's own email toggle
and there is no multi-user email subscription feature.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* cleanup backend
* refactor: unify reports UI with alerts pattern, fix setSlackTarget transaction
- Rewrite reports page to use the same table layout as alerts with
columns [Report, Schedule, Send to, Edit] using SettingsTable
- Add ManageReportSheet side panel for editing report notification
targets (Slack channel + email toggle), matching the alerts pattern
- Generalize TargetChips to accept any target with id/type/channelId/
channelName/email fields, shared by both alerts and reports
- Remove old card-style reports-list.tsx (replaced by table + sheet)
- Wrap setSlackTarget delete+insert in db.transaction() to prevent
data loss if insert fails after delete succeeds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Filter report target chips to only show current user's email
The reports table was showing all email targets including other users'
emails. Now filters to only display the current user's own email
subscription and all Slack targets in the "Send to" column.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Filter other members' emails server-side in reports and alerts APIs
Move email target filtering from frontend-only to the API layer.
Both getReports() and getAlerts() now accept the authenticated user's
email and exclude other members' email targets from the response.
Also add matching frontend filter on the alerts page (previously
only reports had it), addressing the cursor bot comment about alerts
exposing other users' email addresses.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* better slack formatting
* Preserve other users' email targets on alert update; fix UTF-8 slicing panic
- updateAlert now fetches existing email targets belonging to other users
before the delete-and-reinsert, and re-inserts them alongside the
current user's targets. This prevents silently dropping other members'
email subscriptions when one user edits an alert.
- split_mrkdwn_chunks now uses floor_char_boundary() to snap byte offsets
to valid UTF-8 character boundaries, preventing panics on multi-byte
characters (e.g. emoji in project names or non-Latin text).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Use laminar.sh for all trace links; strip emails when user unknown
- Change report trace links in generator.rs and email_template.rs from
lmnr.ai to laminar.sh, matching the domain used in alert notifications.
- Fix email target filter so that when userEmail is falsy (no session),
ALL email targets are stripped rather than passing through unfiltered.
Previously the condition short-circuited on falsy userEmail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix stale cache on partial save, email validation, and Slack text limits
- Refresh SWR cache when a partial report save fails (e.g. email
succeeds but Slack fails), so the UI reflects actual DB state.
- Validate alert email targets with z.email() instead of z.string(),
matching the stricter validation already used in report schemas.
- Apply split_mrkdwn_chunks to event identification Slack blocks so
large extracted_information values are chunked within the 3000-char
section text limit, matching format_report_blocks behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore
* fix alerts settings not loading on the first click
* fix ui
* move report formatting to slack, generalize payload
* Remove chunk splits
* chore
* validate email belongs to user upon alert/report setup
* Fix SWR gating, email preservation, auth checks, and email validation
- Gate signals SWR fetch on sheet `open` prop to avoid unnecessary
requests when the manage-alert sheet is not visible.
- Always fetch existing email targets in updateAlert, even when
userEmail is falsy, to prevent silent deletion of all email
subscriptions. When userEmail is unknown, preserve ALL email targets.
- Add getServerSession auth checks to the slack-target route (POST
and DELETE), matching the sibling reports route pattern.
- Add email target validation to the PATCH alert route to prevent
users from setting email targets for other users, matching the
existing guard in the POST route.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix span links in slack msgs
* Use LazyLock for regex in md_links_to_slack to avoid recompilation
The markdown-to-Slack link regex was being compiled on every call.
Now uses std::sync::LazyLock to compile it once and reuse.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* use lmnr.ai link in emails
* feat: revert stats query, fix sheet
* feat: replace with guid
* chore column width
* feat: allow remove targets, add combobox
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Olzhas Nurpeisov <olzhik1123@gmail.com>
* search (#1537)
* regex
* fixes
* tmp
* add tools to spans
* parsing
* simplification
* fixes
* simplify
* improved search
* minor patches
* feat(LAM-1401): Add per-signal sampling for trace processing (#1541)
* signals sampling per user
* fix: align migration journal tag with SQL filename
The tag was `0078_add_signals_sample_rate` but the file is
`0078_add_signals_sampling_percent.sql`. Drizzle resolves migrations
by tag, so this mismatch would cause the migration runner to fail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unnecessary mut from sampling_factors
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore migration
* ui chore
* fix: exclude empty user_id from sampling factor computation
Traces without a user_id are stored as '' in ClickHouse. Including
them inflates total_users and total_traces while those traces can't
be matched in should_sample_trace (where user_id is None). F…
GitHub OAuth may return emails with different casing than what is configured in allowed-emails.json (e.g. 'User@gmail.com' vs 'user@gmail.com'). The previous list.includes() check is case-sensitive, causing legitimate users to be denied access. Use case-insensitive comparison instead.
Contributor
Greptile SummaryThis PR fixes a case-sensitivity bug in the GitHub OAuth email whitelist check in
Confidence Score: 5/5
Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User initiates sign-in] --> B{provider === 'github'?}
B -- No --> C[Return true - allow sign-in]
B -- Yes --> D{user.email present AND list exists?}
D -- No --> C
D -- Yes --> E["emailLower = user.email.toLowerCase()"]
E --> F["list.some(e => e.toLowerCase() === emailLower)"]
F -- Match found --> G[Return true - allow sign-in]
F -- No match --> H[Return false - deny sign-in]
Reviews (1): Last reviewed commit: "fix: make email whitelist comparison cas..." | Re-trigger Greptile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
GitHub OAuth may return emails with different casing than what is configured in allowed-emails.json (e.g. 'User@gmail.com' vs 'user@gmail.com'). The previous list.includes() check is case-sensitive, causing legitimate users to be denied access.
Use case-insensitive comparison instead.
Note
Low Risk
Low risk, small change limited to the GitHub sign-in whitelist comparison logic. Main risk is unintentionally allowing emails that differ only by case from the configured list (typically desired).
Overview
Updates the NextAuth
signIncallback to perform a case-insensitive comparison when checking GitHub-authenticated users againstallowed-emails.json, replacinglist.includes(user.email)with a lowercasedsome()match.Written by Cursor Bugbot for commit 24b6226. This will update automatically on new commits. Configure here.