[P0] Shareable links with filter state #103
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent Epic
#100
Problem
Incident response requires sharing "look at this" links in Slack/Teams. Currently, every visit starts from scratch — there are no saved views, no shareable URLs with filter state encoded, and no way to bookmark a specific investigation context. Without this, Cameleer can't be part of a team's incident workflow.
Current State
/exchanges,/dashboard/sample-app)history.state— not shareableProposed Solution
1. URL-Encoded Filter State
All filter state should be reflected in the URL query string so links are shareable:
URL parameters to encode:
statusok,errorrange1h,3h,6h,today,24h,7dfrom/toappbackend-approuteroute3exchange73A3A...B3tabinfo,headers,error,timelinesearchorderId=1232. Copy Link Button
3. Saved Views (stretch goal)
Implementation Notes
useSearchParamsto sync filter state with URLhistory.statefor exchange selection with URL path parameters (already partially done)Acceptance Criteria
Competitive Reference
Design Specification
URL Schema
Path = navigation scope, query params = view state. Params are optional (absent = default).
range1h1h,3h,6h,today,24h,7d,customfrom/torange=customstatusok,error,warning,runningsearchsortstartTimedirasc/descdesctabinfolive0/11Examples:
Architecture: URL as Source of Truth
UrlFilterSyncProviderwraps the existingGlobalFilterProviderfrom the design system. InnerUrlFilterSynccomponent syncs bidirectionally: URL→state on mount/popstate, state→URL on filter changes (debounced, replaceState). No design system changes needed.New files:
ui/src/hooks/url-filter-types.ts— Types, parse/serialize, mapping tablesui/src/components/UrlFilterSyncProvider.tsx— Wrapper providerui/src/hooks/useSavedViews.ts— Saved views (stretch)Modified files:
LayoutShell.tsx— Swap provider, add Copy Link buttonDashboard.tsx— Renametexttosearch, addsort/dirURL syncExchangesPage.tsx— AddtabURL param for detail panelHistory Behavior
Filter changes →
replaceState(Back undoes navigation, not each filter tweak). Navigation changes (sidebar click, exchange select, tab switch) →pushState.Global params (
range,from,to,live) preserved across sidebar navigation. Page-specific params (status,search,sort,dir,tab) reset.Copy Link Button
Breadcrumb area, right-aligned.
navigator.clipboard.writeText(window.location.href)with toast confirmation. Keyboard shortcut: Ctrl+Shift+C.Deep Link Restoration
Full sequence: URL → React Router match → auth check (redirect to login if needed, storing URL in sessionStorage) → UrlFilterSync reads params → filters applied → data fetched → view rendered. Invalid params silently dropped (use defaults).
Migration
Existing URLs without params continue to work.
?text=renamed to?search=(both accepted during transition). Exchange selection migrates fromhistory.stateto URL path params (already partially done viaurlDerivedExchange).Rollout (9 independent steps)
url-filter-types.ts(pure functions)UrlFilterSyncProvidertext→searchin Dashboardsort/dirURL paramstabURL param to DetailPanelConflict Analysis: URL Filter State vs Current Navigation Infrastructure
After a thorough review of the current sidebar,
useScope,LayoutShell, andExchangesPagecode against this proposed design, there are 5 real conflicts that need to be addressed before implementation. The sidebar component itself is safe (it only readslocation.pathnameandlocation.state.sidebarReveal), but the navigation layer connecting sidebar clicks to page URLs is where all conflicts live.Conflict 1: All Navigation Methods Strip Query Params (HIGH)
Every navigation function in
useScope.tsbuilds bare URLs with no query string:Same in
LayoutShell.tsx— sidebar click handler:Impact: Every sidebar click, tab switch, and app/route navigation wipes all URL-encoded filter state (
range,status,search,live, etc.). The spec says "global params preserved across sidebar navigation" — this requires every navigation call to carry forward global query params.Conflict 2:
replaceStateWill Clobberlocation.state(HIGH)The spec says filter changes use
replaceState. Butlocation.statecarries critical data that must survive:sidebarReveal— sidebar uses this to highlight the active tree node after LayoutShell translates/apps/X→/exchanges/XselectedExchange—ExchangesPageuses this to restore exchange selection on Back/Forward in split viewIf
UrlFilterSyncdoesnavigate(url, { replace: true })without forwarding existing state, changing a time range filter will break sidebar highlighting and lose the selected exchange.Conflict 3: Auto-Refresh Time Sliding vs URL (MEDIUM)
GlobalFilterProviderauto-slides the time window every 10 seconds whenautoRefreshis on with a preset. If time range moves to URL asfrom/totimestamps, this creates areplaceStateevery 10 seconds that must preserve all other query params andlocation.statewithout URL bar flicker.Recommendation: URL should only store the preset name (
?range=1h), not computedfrom/to, unlessrange=custom. The provider computes the actual window from the preset. Auto-refresh with custom ranges should not update the URL.Conflict 4:
handlePaletteSubmitUses?text=(LOW)LayoutShell.tsxline 269:This constructs a fresh URL, stripping any existing filter query params. Needs to use
?search=and merge with existing global params.Conflict 5: Dashboard Reads
?text=Directly (LOW)Dashboard uses
searchParams.get('text')for its filter. Needs migration tosearchwith backward compat during transition.Design Gaps in the Current Spec
No mention of
sidebarRevealstate preservation. The spec discussespushStatevsreplaceStatebut doesn't acknowledge the existinglocation.statepayloads (sidebarReveal,selectedExchange).No mention of
useScopemodifications. The spec listsLayoutShell.tsxas modified but doesn't mentionuseScope.ts— yetuseScopeis the primary navigation mechanism and it MUST preserve global query params.The "9 independent steps" rollout isn't independent. Step 3 (swap provider in LayoutShell) breaks things unless navigation methods are updated first. The rollout ordering needs revision.
Conclusion
This issue requires a broader navigation layer redesign rather than just wrapping the existing
GlobalFilterProvider. The core navigation functions (useScope,handleSidebarNavigate,handlePaletteSubmit,handlePaletteSelect) all need to become filter-aware, andlocation.statemust be preserved across allreplaceStateoperations. The original spec underestimated the coupling between filter state, navigation, and the sidebar/split-view infrastructure.The design spec should be revised to address these conflicts before implementation begins.