Scarf Fire

This commit is contained in:
Connor Yoh 2025-08-28 11:46:27 +01:00
parent e59e73ceb0
commit cd6652a48d
6 changed files with 58 additions and 61 deletions

View File

@ -7,7 +7,6 @@ import { ToolWorkflowProvider } from "./contexts/ToolWorkflowContext";
import { SidebarProvider } from "./contexts/SidebarContext"; import { SidebarProvider } from "./contexts/SidebarContext";
import ErrorBoundary from "./components/shared/ErrorBoundary"; import ErrorBoundary from "./components/shared/ErrorBoundary";
import HomePage from "./pages/HomePage"; import HomePage from "./pages/HomePage";
import { ScarfPixel } from "./components/ScarfPixel";
// Import global styles // Import global styles
import "./styles/tailwind.css"; import "./styles/tailwind.css";
@ -37,7 +36,6 @@ export default function App() {
<ErrorBoundary> <ErrorBoundary>
<FileContextProvider enableUrlSync={true} enablePersistence={true}> <FileContextProvider enableUrlSync={true} enablePersistence={true}>
<NavigationProvider> <NavigationProvider>
<ScarfPixel />
<FilesModalProvider> <FilesModalProvider>
<ToolWorkflowProvider> <ToolWorkflowProvider>
<SidebarProvider> <SidebarProvider>

View File

@ -1,30 +0,0 @@
import { useEffect, useRef } from "react";
import { useNavigationState } from "../contexts/NavigationContext";
export function ScarfPixel() {
const { workbench, selectedTool } = useNavigationState();
const lastUrlSent = useRef<string | null>(null); // helps with React 18 StrictMode in dev
useEffect(() => {
// Get current pathname from browser location
const pathname = window.location.pathname;
const url = 'https://static.scarf.sh/a.png?x-pxid=3c1d68de-8945-4e9f-873f-65320b6fabf7'
+ '&path=' + encodeURIComponent(pathname)
+ '&t=' + Date.now(); // cache-buster
console.log("ScarfPixel: Navigation change", { workbench, selectedTool, pathname });
if (lastUrlSent.current !== url) {
lastUrlSent.current = url;
const img = new Image();
img.referrerPolicy = "no-referrer-when-downgrade"; // optional
img.src = url;
console.log("ScarfPixel: Fire to... " + pathname, url);
}
}, [workbench, selectedTool]); // Fire when navigation state changes
return null; // Nothing visible in UI
}

View File

@ -225,10 +225,7 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
// URL sync for proper tool navigation // URL sync for proper tool navigation
useNavigationUrlSync( useNavigationUrlSync(
navigationState.workbench,
navigationState.selectedTool, navigationState.selectedTool,
actions.setWorkbench,
actions.setSelectedTool,
handleToolSelect, handleToolSelect,
() => actions.setSelectedTool(null), () => actions.setSelectedTool(null),
toolRegistry, toolRegistry,

View File

@ -3,7 +3,7 @@
*/ */
import { useEffect, useCallback } from 'react'; import { useEffect, useCallback } from 'react';
import { WorkbenchType, ToolId, ToolRoute } from '../types/navigation'; import { WorkbenchType, ToolId } from '../types/navigation';
import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting'; import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting';
import { ToolRegistry } from '../data/toolsTaxonomy'; import { ToolRegistry } from '../data/toolsTaxonomy';
@ -11,10 +11,7 @@ import { ToolRegistry } from '../data/toolsTaxonomy';
* Hook to sync workbench and tool with URL using registry * Hook to sync workbench and tool with URL using registry
*/ */
export function useNavigationUrlSync( export function useNavigationUrlSync(
workbench: WorkbenchType,
selectedTool: ToolId | null, selectedTool: ToolId | null,
setWorkbench: (workbench: WorkbenchType) => void,
setSelectedTool: (toolId: ToolId | null) => void,
handleToolSelect: (toolId: string) => void, handleToolSelect: (toolId: string) => void,
clearToolSelection: () => void, clearToolSelection: () => void,
registry: ToolRegistry, registry: ToolRegistry,
@ -28,7 +25,9 @@ export function useNavigationUrlSync(
if (route.toolId !== selectedTool) { if (route.toolId !== selectedTool) {
if (route.toolId) { if (route.toolId) {
handleToolSelect(route.toolId); handleToolSelect(route.toolId);
} else { } else if (selectedTool !== null) {
// Only clear selection if we actually had a tool selected
// Don't clear on initial load when selectedTool starts as null
clearToolSelection(); clearToolSelection();
} }
} }
@ -41,8 +40,10 @@ export function useNavigationUrlSync(
if (selectedTool) { if (selectedTool) {
updateToolRoute(selectedTool, registry); updateToolRoute(selectedTool, registry);
} else { } else {
// Clear URL when no tool is selected // Only clear URL if we're not on the home page already
clearToolRoute(); if (window.location.pathname !== '/') {
clearToolRoute();
}
} }
}, [selectedTool, registry, enableSync]); }, [selectedTool, registry, enableSync]);

View File

@ -0,0 +1,28 @@
let lastFiredPathname: string | null = null;
let lastFiredTime = 0;
/**
* Fire scarf pixel for analytics tracking
* Only fires if pathname is different from last call or enough time has passed
*/
export function firePixel(pathname: string): void {
const now = Date.now();
// Only fire if pathname changed or it's been at least 1 second since last fire
if (pathname === lastFiredPathname && now - lastFiredTime < 250) {
return;
}
lastFiredPathname = pathname;
lastFiredTime = now;
const url = 'https://static.scarf.sh/a.png?x-pxid=3c1d68de-8945-4e9f-873f-65320b6fabf7'
+ '&path=' + encodeURIComponent(pathname)
const img = new Image();
img.referrerPolicy = "no-referrer-when-downgrade";
img.src = url;
console.log("ScarfPixel: Fire to... " + pathname);
}

View File

@ -8,6 +8,7 @@ import {
getDefaultWorkbench getDefaultWorkbench
} from '../types/navigation'; } from '../types/navigation';
import { ToolRegistry, getToolWorkbench, getToolUrlPath, isValidToolId } from '../data/toolsTaxonomy'; import { ToolRegistry, getToolWorkbench, getToolUrlPath, isValidToolId } from '../data/toolsTaxonomy';
import { firePixel } from './scarfTracking';
/** /**
* Parse the current URL to extract tool routing information * Parse the current URL to extract tool routing information
@ -44,33 +45,38 @@ export function parseToolRoute(registry: ToolRegistry): ToolRoute {
}; };
} }
/**
* Update URL and fire analytics pixel
*/
function updateUrl(newPath: string, searchParams: URLSearchParams): void {
const currentPath = window.location.pathname;
const queryString = searchParams.toString();
const fullUrl = newPath + (queryString ? `?${queryString}` : '');
// Only update URL and fire pixel if something actually changed
if (currentPath !== newPath || window.location.search !== (queryString ? `?${queryString}` : '')) {
window.history.replaceState(null, '', fullUrl);
firePixel(newPath);
}
}
/** /**
* Update the URL to reflect the current tool selection * Update the URL to reflect the current tool selection
*/ */
export function updateToolRoute(toolId: ToolId, registry: ToolRegistry): void { export function updateToolRoute(toolId: ToolId, registry: ToolRegistry): void {
const currentPath = window.location.pathname;
const searchParams = new URLSearchParams(window.location.search);
const tool = registry[toolId]; const tool = registry[toolId];
if (!tool) { if (!tool) {
console.warn(`Tool ${toolId} not found in registry`); console.warn(`Tool ${toolId} not found in registry`);
return; return;
} }
const newPath = getToolUrlPath(toolId, tool); const newPath = getToolUrlPath(toolId, tool);
const searchParams = new URLSearchParams(window.location.search);
// Remove tool query parameter since we're using path-based routing // Remove tool query parameter since we're using path-based routing
searchParams.delete('tool'); searchParams.delete('tool');
// Construct final URL updateUrl(newPath, searchParams);
const queryString = searchParams.toString();
const fullUrl = newPath + (queryString ? `?${queryString}` : '');
// Update URL without triggering page reload
if (currentPath !== newPath || window.location.search !== (queryString ? `?${queryString}` : '')) {
window.history.replaceState(null, '', fullUrl);
}
} }
/** /**
@ -80,10 +86,7 @@ export function clearToolRoute(): void {
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
searchParams.delete('tool'); searchParams.delete('tool');
const queryString = searchParams.toString(); updateUrl('/', searchParams);
const url = '/' + (queryString ? `?${queryString}` : '');
window.history.replaceState(null, '', url);
} }
/** /**