← back to kburke8__poe-watcher

Function bodies 255 total

All specs Real LLM only Function bodies
ViewLoader function · typescript · L26-L32 (7 LOC)
src/App.tsx
function ViewLoader() {
  return (
    <div className="h-full flex items-center justify-center">
      <div className="text-[--color-text-muted] text-sm">Loading...</div>
    </div>
  );
}
formatMinutesToTime function · typescript · L50-L61 (12 LOC)
src/components/Comparison/ComparisonCharts.tsx
function formatMinutesToTime(minutes: number): string {
  const totalSeconds = Math.round(Math.abs(minutes) * 60);
  const hrs = Math.floor(totalSeconds / 3600);
  const mins = Math.floor((totalSeconds % 3600) / 60);
  const secs = totalSeconds % 60;

  const sign = minutes < 0 ? '-' : '';
  if (hrs > 0) {
    return `${sign}${hrs}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
  }
  return `${sign}${mins}:${secs.toString().padStart(2, '0')}`;
}
formatDeltaMs function · typescript · L63-L70 (8 LOC)
src/components/Comparison/ComparisonCharts.tsx
function formatDeltaMs(ms: number): string {
  const sign = ms >= 0 ? '+' : '-';
  const absMs = Math.abs(ms);
  const totalSeconds = Math.floor(absMs / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  return `${sign}${minutes}:${seconds.toString().padStart(2, '0')}`;
}
formatTime function · typescript · L668-L678 (11 LOC)
src/components/Comparison/ComparisonView.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
formatDelta function · typescript · L680-L688 (9 LOC)
src/components/Comparison/ComparisonView.tsx
function formatDelta(ms: number): string {
  const sign = ms >= 0 ? '+' : '-';
  const absMs = Math.abs(ms);
  const totalSeconds = Math.floor(absMs / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;

  return `${sign}${minutes}:${seconds.toString().padStart(2, '0')}`;
}
AddMemberForm function · typescript · L10-L61 (52 LOC)
src/components/Group/AddMemberForm.tsx
export function AddMemberForm({ memberCount }: AddMemberFormProps) {
  const [accountName, setAccountName] = useState('');
  const [error, setError] = useState<string | null>(null);
  const [isAdding, setIsAdding] = useState(false);
  const { addMember } = useGroupStore();

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    const trimmed = accountName.trim();
    if (!trimmed) return;

    if (memberCount >= 5) {
      setError('Maximum of 5 group members allowed');
      return;
    }

    setIsAdding(true);
    setError(null);
    try {
      await addMember({ accountName: trimmed });
      setAccountName('');
    } catch (err) {
      setError(String(err));
    } finally {
      setIsAdding(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="flex gap-2 items-end">
      <div className="flex-1">
        <input
          type="text"
          value={accountName}
          onChange={(e) => setAccountName(e.target.value)}
         
GroupMemberCard function · typescript · L13-L154 (142 LOC)
src/components/Group/GroupMemberCard.tsx
export function GroupMemberCard({ member }: GroupMemberCardProps) {
  const { updateMember, removeMember, setMemberActive } = useGroupStore();
  const detectionStatus = useGroupStore((s) => s.detectionStatus[member.id]);
  const [isEditing, setIsEditing] = useState(false);
  const [editCharName, setEditCharName] = useState(member.characterName || '');
  const [isResolving, setIsResolving] = useState(false);
  const [resolvedChars, setResolvedChars] = useState<PoeCharacter[] | null>(null);

  const handleResolve = async () => {
    setIsResolving(true);
    try {
      const characters = await invoke<PoeCharacter[]>('resolve_group_member_characters', {
        accountName: member.accountName,
      });
      setResolvedChars(characters);
    } catch (err) {
      console.error('Failed to resolve characters:', err);
    } finally {
      setIsResolving(false);
    }
  };

  const handleSelectCharacter = async (charName: string) => {
    await updateMember(member.id, charName, member.disp
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
formatTime function · typescript · L9-L18 (10 LOC)
src/components/Group/GroupRunHistory.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  if (hours > 0) {
    return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
  }
  return `${minutes}:${String(seconds).padStart(2, '0')}`;
}
GroupView function · typescript · L9-L62 (54 LOC)
src/components/Group/GroupView.tsx
export function GroupView() {
  const { members, loadMembers } = useGroupStore();

  useEffect(() => {
    loadMembers();
  }, [loadMembers]);

  return (
    <div className="h-full flex flex-col p-6">
      <div className="mb-4">
        <h1 className="text-2xl font-bold text-[--color-text] flex items-center gap-2">
          <Users className="w-6 h-6" />
          Group Mode
          <HelpTip>
            Track up to 5 party members during group speedruns. Each member's progress is tracked independently. Enable Group Mode in Settings first, then add members here.
          </HelpTip>
        </h1>
        <p className="text-[--color-text-muted] mt-1">
          Manage party members and view group run history
        </p>
      </div>

      <div className="flex-1 overflow-auto space-y-6">
        {/* Group Members Section */}
        <section>
          <div className="flex items-center justify-between mb-3">
            <h2 className="text-lg font-semibold text-[--color-text]">
   
formatTime function · typescript · L259-L269 (11 LOC)
src/components/History/AnalyticsTab.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
HistoryView function · typescript · L14-L109 (96 LOC)
src/components/History/HistoryView.tsx
export function HistoryView() {
  const [activeTab, setActiveTab] = useState<TabType>('runs');
  const [showAddReferenceModal, setShowAddReferenceModal] = useState(false);
  const { filters, setFilters, clearFilters, loadFilteredRuns, loadRunStats, loadSplitStats } =
    useRunStore();

  // Load data when filters change
  useEffect(() => {
    loadFilteredRuns();
    loadRunStats();
    loadSplitStats();
  }, [filters, loadFilteredRuns, loadRunStats, loadSplitStats]);

  const handleFiltersChange = (newFilters: Partial<RunFilters>) => {
    setFilters(newFilters);
  };

  const handleClearFilters = () => {
    clearFilters();
  };

  return (
    <div className="h-full flex flex-col p-6">
      <div className="mb-4">
        <h1 className="text-2xl font-bold text-[--color-text] flex items-center gap-2">
          Run History
          <HelpTip>
            Browse all completed and abandoned runs with detailed split times, statistics, and trends over time. Click a run to view its split
formatTime function · typescript · L283-L293 (11 LOC)
src/components/History/RunsTab.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
formatPbTime function · typescript · L21-L31 (11 LOC)
src/components/Overlay/OverlayBreakpoints.tsx
function formatPbTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
formatDelta function · typescript · L33-L45 (13 LOC)
src/components/Overlay/OverlayBreakpoints.tsx
function formatDelta(ms: number): string {
  const absMs = Math.abs(ms);
  const totalSeconds = Math.floor(absMs / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  // Positive timeUntilPb means ahead (still have time), negative means behind
  const sign = ms >= 0 ? '-' : '+';

  if (minutes > 0) {
    return `${sign}${minutes}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${sign}0:${seconds.toString().padStart(2, '0')}`;
}
OverlayBreakpoints function · typescript · L47-L118 (72 LOC)
src/components/Overlay/OverlayBreakpoints.tsx
export function OverlayBreakpoints({ breakpoints, maxCount = 3, fontSize = 'medium', startTime, elapsedMs, isRunning }: OverlayBreakpointsProps) {
  const visibleBreakpoints = breakpoints.slice(0, maxCount);
  const itemClass = fontSize === 'small' ? 'text-[10px]' : fontSize === 'large' ? 'text-sm' : 'text-xs';

  // Tick every second when running so countdowns update live
  const [tick, setTick] = useState(0);
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  useEffect(() => {
    if (isRunning && startTime != null) {
      intervalRef.current = setInterval(() => setTick((t) => t + 1), 1000);
    }
    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, [isRunning, startTime]);

  // Suppress unused warning - tick drives re-renders
  void tick;

  const currentElapsed = isRunning && startTime ? Date.now() - startTime : elapsedMs;

  return (
    <div className="pt-1" style={{ borderTop: '1px solid rgba(58, 58, 62, 0
Open data scored by Repobility · https://repobility.com
formatDelta function · typescript · L13-L22 (10 LOC)
src/components/Overlay/OverlaySplit.tsx
function formatDelta(ms: number): string {
  const absMs = Math.abs(ms);
  const totalSeconds = Math.floor(absMs / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  const centiseconds = Math.floor((absMs % 1000) / 10);
  const sign = ms >= 0 ? '+' : '-';

  return `${sign}${minutes}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
}
formatSplitTime function · typescript · L24-L34 (11 LOC)
src/components/Overlay/OverlaySplit.tsx
function formatSplitTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
formatSegmentTime function · typescript · L36-L45 (10 LOC)
src/components/Overlay/OverlaySplit.tsx
function formatSegmentTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;

  if (minutes > 0) {
    return `${minutes}:${seconds.toString().padStart(2, '0')}`;
  }
  return `${seconds}s`;
}
OverlaySplit function · typescript · L47-L98 (52 LOC)
src/components/Overlay/OverlaySplit.tsx
export function OverlaySplit({ name, deltaMs, isBestSegment, splitTimeMs, segmentTimeMs, pbSegmentTimeMs, goldSegmentTimeMs, fontSize = 'medium', scale = 'medium' }: OverlaySplitProps) {
  let deltaColor = '#9a8e82'; // neutral
  if (isBestSegment) {
    deltaColor = '#fbbf24'; // gold
  } else if (deltaMs !== null) {
    deltaColor = deltaMs < 0 ? '#22c55e' : '#d4a574'; // ahead / behind (amber)
  }

  const sizeClass = fontSize === 'small' ? 'text-xs' : fontSize === 'large' ? 'text-base' : 'text-sm';
  const detailSizeClass = fontSize === 'small' ? 'text-[9px]' : fontSize === 'large' ? 'text-sm' : 'text-xs';
  const ptClass = scale === 'small' ? 'pt-1' : 'pt-2';

  // Build segment comparison text
  const hasComparison = segmentTimeMs !== undefined && (pbSegmentTimeMs || goldSegmentTimeMs);

  return (
    <div className={`${ptClass}`} style={{ borderTop: '1px solid rgba(58, 58, 62, 0.5)' }}>
      <div className={`flex items-center justify-between ${sizeClass}`}>
        <span class
formatTime function · typescript · L10-L21 (12 LOC)
src/components/Overlay/OverlayTimer.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  const centiseconds = Math.floor((ms % 1000) / 10);

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
}
OverlayTimer function · typescript · L23-L59 (37 LOC)
src/components/Overlay/OverlayTimer.tsx
export function OverlayTimer({ startTime, elapsedMs, isRunning, fontSize = 'medium' }: OverlayTimerProps) {
  const [displayMs, setDisplayMs] = useState(elapsedMs);
  const animationRef = useRef<number | null>(null);

  // Run our own animation loop to compute elapsed time locally
  useEffect(() => {
    if (isRunning && startTime) {
      const updateTimer = () => {
        setDisplayMs(Date.now() - startTime);
        animationRef.current = requestAnimationFrame(updateTimer);
      };
      animationRef.current = requestAnimationFrame(updateTimer);
    } else {
      // When not running, use the last known elapsedMs from the main window
      setDisplayMs(elapsedMs);
    }

    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [isRunning, startTime, elapsedMs]);

  const timerSizeClass = fontSize === 'small' ? 'text-xl' : fontSize === 'large' ? 'text-4xl' : 'text-3xl';

  return (
    <div className="text-center">
OverlayZone function · typescript · L9-L25 (17 LOC)
src/components/Overlay/OverlayZone.tsx
export function OverlayZone({ zoneName, fontSize = 'medium', isAhead, hotkeyHint, showHotkeyHint }: OverlayZoneProps) {
  const sizeClass = fontSize === 'small' ? 'text-xs' : fontSize === 'large' ? 'text-base' : 'text-sm';
  // Green when ahead of PB, amber when behind or no data; muted when empty
  const color = !zoneName ? '#4a4440' : isAhead === undefined ? '#9a8e82' : isAhead ? '#22c55e' : '#d4a574';

  const placeholder = showHotkeyHint
    ? `${hotkeyHint || 'Ctrl+Space'} to start`
    : 'Waiting for zone...';

  return (
    <div className="text-center">
      <div className={`${sizeClass} truncate`} style={{ color }} title={zoneName || undefined}>
        {zoneName || placeholder}
      </div>
    </div>
  );
}
PracticeHistory function · typescript · L5-L128 (124 LOC)
src/components/Practice/PracticeHistory.tsx
export function PracticeHistory() {
  const { attempts, bestTimeMs, clearAttempts, mode, selectedZones } = usePracticeStore();

  // Calculate stats
  const avgTimeMs = attempts.length > 0
    ? attempts.reduce((sum, a) => sum + a.timeMs, 0) / attempts.length
    : null;
  const totalDeaths = attempts.reduce((sum, a) => sum + a.deathCount, 0);

  return (
    <div className="card-inset rounded-lg h-full flex flex-col">
      <div className="p-4 section-header rounded-t-lg flex items-center justify-between">
        <div>
          <h2 className="text-lg font-semibold text-[--color-text]">Attempts</h2>
          <p className="text-xs text-[--color-text-muted] mt-1">
            {attempts.length} attempt{attempts.length !== 1 ? 's' : ''}
          </p>
        </div>
        {attempts.length > 0 && (
          <button
            onClick={clearAttempts}
            className="p-1.5 rounded-md text-[--color-text-muted] hover:text-red-400 transition-colors"
            title="Clear all att
All rows scored by the Repobility analyzer (https://repobility.com)
formatTime function · typescript · L130-L141 (12 LOC)
src/components/Practice/PracticeHistory.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  const centiseconds = Math.floor((ms % 1000) / 10);

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
}
PracticeTimer function · typescript · L7-L173 (167 LOC)
src/components/Practice/PracticeTimer.tsx
export function PracticeTimer() {
  const {
    mode, selectedZones, timer, attempts, bestTimeMs,
    stopPractice, resetPractice, updateElapsed,
  } = usePracticeStore();
  const currentZone = useRunStore((s) => s.timer.currentZone);
  const animationRef = useRef<number | null>(null);

  // Update timer every frame
  useEffect(() => {
    const tick = () => {
      if (timer.isRunning && timer.startTime) {
        updateElapsed(Date.now() - timer.startTime);
      }
      animationRef.current = requestAnimationFrame(tick);
    };

    if (timer.isRunning) {
      animationRef.current = requestAnimationFrame(tick);
    }

    return () => {
      if (animationRef.current) {
        cancelAnimationFrame(animationRef.current);
      }
    };
  }, [timer.isRunning, timer.startTime, updateElapsed]);

  const handleBack = () => {
    stopPractice();
    resetPractice();
  };

  return (
    <div className="flex flex-col gap-4 h-full">
      {/* Header with back button */}
      <div classNa
PracticeInfo function · typescript · L175-L210 (36 LOC)
src/components/Practice/PracticeTimer.tsx
function PracticeInfo() {
  const mode = usePracticeStore((s) => s.mode);
  const selectedZones = usePracticeStore((s) => s.selectedZones);
  const getExitZone = usePracticeStore((s) => s.getExitZone);
  const getRouteExitZone = usePracticeStore((s) => s.getRouteExitZone);

  const isSingleZone = mode === 'single_zone';
  const exitZone = isSingleZone ? getExitZone() : getRouteExitZone();
  const startZone = selectedZones[0];

  return (
    <div className="card-inset rounded-lg p-3">
      <div className="flex items-center gap-2 text-sm">
        <span className="text-[--color-text-muted]">
          {isSingleZone ? 'Practicing:' : 'Starts on:'}
        </span>
        <span className="text-[--color-poe-gold] font-medium">{startZone.name}</span>
        <span className="text-[--color-text-muted] text-xs">(A{startZone.act})</span>
      </div>
      {exitZone ? (
        <div className="flex items-center gap-2 text-sm mt-1.5">
          <span className="text-[--color-text-muted]">Compl
formatTime function · typescript · L212-L223 (12 LOC)
src/components/Practice/PracticeTimer.tsx
function formatTime(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  const centiseconds = Math.floor((ms % 1000) / 10);

  if (hours > 0) {
    return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
  }
  return `${minutes}:${seconds.toString().padStart(2, '0')}.${centiseconds.toString().padStart(2, '0')}`;
}
PracticeView function · typescript · L8-L48 (41 LOC)
src/components/Practice/PracticeView.tsx
export function PracticeView() {
  const { selectedZones, isActive, loadFromStorage } = usePracticeStore();

  useEffect(() => {
    loadFromStorage();
  }, [loadFromStorage]);

  return (
    <div className="h-full flex flex-col p-6">
      <div className="mb-6">
        <h1 className="text-2xl font-bold text-[--color-text] flex items-center gap-2" style={{ textShadow: '0 0 30px rgba(175, 96, 37, 0.2)' }}>
          Practice Mode
          <HelpTip>
            Practice zone layouts on a high-level, fast character. Run the same zones repeatedly to learn tileset layouts and optimize pathing. Your times are saved so you can track improvement across many attempts.
          </HelpTip>
        </h1>
        <p className="text-[--color-text-muted] mt-1 text-sm">
          Practice individual zones or run through a sequence of zones.
        </p>
      </div>

      <div className="flex-1 flex gap-6 min-h-0">
        {/* Left side - Zone selector and controls */}
        <div className="fle
getSelectableZones function · typescript · L9-L22 (14 LOC)
src/components/Practice/ZoneSelector.tsx
function getSelectableZones(): PracticeZone[] {
  const zones: PracticeZone[] = [];
  for (const bp of defaultBreakpoints) {
    // Only include zone-trigger breakpoints (not level milestones or kitava triggers)
    if (bp.trigger.type === 'zone' && bp.trigger.zoneName && bp.trigger.act) {
      zones.push({
        name: bp.name,
        zoneName: bp.trigger.zoneName,
        act: bp.trigger.act,
      });
    }
  }
  return zones;
}
ZoneSelector function · typescript · L27-L223 (197 LOC)
src/components/Practice/ZoneSelector.tsx
export function ZoneSelector() {
  const { mode, setMode, selectedZones, addZone, removeZone, moveZone, clearZones, startPractice } = usePracticeStore();
  const [search, setSearch] = useState('');
  const [actFilter, setActFilter] = useState<number | null>(null);

  const filteredZones = useMemo(() => {
    let zones = allZones;
    if (actFilter !== null) {
      zones = zones.filter(z => z.act === actFilter);
    }
    if (search) {
      const lower = search.toLowerCase();
      zones = zones.filter(z => z.name.toLowerCase().includes(lower) || z.zoneName.toLowerCase().includes(lower));
    }
    return zones;
  }, [search, actFilter]);

  const selectedZoneNames = new Set(selectedZones.map(z => `${z.zoneName}-${z.act}`));

  return (
    <div className="flex flex-col gap-4 h-full min-h-0">
      {/* Mode toggle */}
      <div className="flex gap-2">
        <button
          onClick={() => setMode('single_zone')}
          className={`flex-1 flex items-center justify-center gap-2 p
getStepSummary function · typescript · L12-L30 (19 LOC)
src/components/Settings/BreakpointWizard.tsx
function getStepSummary(step: number, config: WizardConfig): string {
  switch (step) {
    case 1:
      return config.endAct === 0 ? 'Dev Test' : `Act ${config.endAct} ${config.runType === 'any_percent' ? 'Any%' : '100%'}`;
    case 2:
      return ({
        every_zone: 'Every Zone',
        key_zones: 'Key Zones',
        bosses_only: 'Bosses Only',
        acts_only: 'Acts Only',
      } as Record<string, string>)[config.verbosity] ?? config.verbosity;
    case 3: {
      const freq = config.snapshotFrequency || 'acts_only';
      return freq === 'bosses_only' ? 'Bosses + Acts' : 'Acts Only';
    }
    default:
      return '';
  }
}
Same scanner, your repo: https://repobility.com — Repobility
BreakpointWizard function · typescript · L32-L181 (150 LOC)
src/components/Settings/BreakpointWizard.tsx
export function BreakpointWizard() {
  const { wizardConfig, setWizardConfig } = useSettingsStore();

  // Local wizard state (committed on Apply)
  // Migrate older configs that may lack snapshotFrequency
  const [config, setConfig] = useState<WizardConfig>(() => {
    const saved = wizardConfig ?? DEFAULT_WIZARD_CONFIG;
    if (!saved.snapshotFrequency) {
      return { ...saved, snapshotFrequency: 'acts_only' as const };
    }
    return saved;
  });
  const [step, setStep] = useState<Step>(1);

  // Preview: generate breakpoints from current local config
  const preview = useMemo(() => generateBreakpoints(config), [config]);
  const enabledCount = useMemo(() => preview.filter(bp => bp.isEnabled).length, [preview]);
  const snapshotCount = useMemo(() => preview.filter(bp => bp.isEnabled && bp.captureSnapshot).length, [preview]);
  const grouped = useMemo(() => groupByAct(preview), [preview]);

  const handleApply = () => {
    setWizardConfig(config);
    try {
      localStorage.se
RouteCustomizations function · typescript · L353-L498 (146 LOC)
src/components/Settings/BreakpointWizard.tsx
export function RouteCustomizations() {
  const { wizardConfig, setWizardConfig } = useSettingsStore();

  if (!wizardConfig) {
    return (
      <p className="text-sm text-[--color-text-muted] p-4">
        Configure the wizard above first to enable route customizations.
      </p>
    );
  }

  const setRoute = <K extends keyof WizardConfig['routes']>(key: K, value: WizardConfig['routes'][K]) => {
    const updated = { ...wizardConfig, routes: { ...wizardConfig.routes, [key]: value } };
    setWizardConfig(updated);
    try {
      localStorage.setItem('poe-watcher-wizard-config', JSON.stringify(updated));
    } catch (e) {
      console.error('Failed to save wizard config:', e);
    }
  };

  const showAct6Plus = wizardConfig.endAct === 10;

  return (
    <div className="space-y-3">
      {/* Act 1 */}
      <RouteSection title="Act 1">
        <RadioOption
          label="Standard"
          desc="Skip or delay Dweller of the Deep"
          selected={wizardConfig.routes.act1 ==
RouteSection function · typescript · L502-L521 (20 LOC)
src/components/Settings/BreakpointWizard.tsx
function RouteSection({ title, children }: { title: string; children: React.ReactNode }) {
  const [open, setOpen] = useState(true);
  return (
    <div className="border border-[--color-border] rounded-lg overflow-hidden">
      <button
        onClick={() => setOpen(!open)}
        className="w-full flex items-center justify-between p-2.5 text-sm font-medium text-[--color-text] hover:bg-[--color-surface-elevated]/50 active:scale-[0.99] transition-all"
      >
        <span>{title}</span>
        <svg
          className={`w-4 h-4 text-[--color-text-muted] transition-transform ${open ? 'rotate-180' : ''}`}
          fill="none" stroke="currentColor" viewBox="0 0 24 24"
        >
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
        </svg>
      </button>
      {open && <div className="px-3 pb-3 space-y-1.5">{children}</div>}
    </div>
  );
}
mapKeyToTauri function · typescript · L11-L42 (32 LOC)
src/components/Settings/HotkeyInput.tsx
function mapKeyToTauri(key: string): string | null {
  // Ignore standalone modifier keys
  if (['Control', 'Shift', 'Alt', 'Meta'].includes(key)) return null;

  const keyMap: Record<string, string> = {
    ' ': 'Space',
    'ArrowUp': 'Up',
    'ArrowDown': 'Down',
    'ArrowLeft': 'Left',
    'ArrowRight': 'Right',
    'Enter': 'Enter',
    'Escape': 'Escape',
    'Tab': 'Tab',
    'Backspace': 'Backspace',
    'Delete': 'Delete',
    'Home': 'Home',
    'End': 'End',
    'PageUp': 'PageUp',
    'PageDown': 'PageDown',
    'Insert': 'Insert',
  };

  if (keyMap[key]) return keyMap[key];

  // Function keys
  if (/^F\d{1,2}$/.test(key)) return key;

  // Single character keys - uppercase
  if (key.length === 1) return key.toUpperCase();

  return key;
}
HotkeyInput function · typescript · L44-L115 (72 LOC)
src/components/Settings/HotkeyInput.tsx
export function HotkeyInput({ value, onChange, error }: HotkeyInputProps) {
  const [capturing, setCapturing] = useState(false);
  const buttonRef = useRef<HTMLButtonElement>(null);

  const handleKeyDown = useCallback((e: KeyboardEvent) => {
    e.preventDefault();
    e.stopPropagation();

    // Escape cancels capture
    if (e.key === 'Escape') {
      setCapturing(false);
      return;
    }

    const tauriKey = mapKeyToTauri(e.key);
    if (!tauriKey) return; // Ignore lone modifier presses

    // Require at least one modifier
    if (!e.ctrlKey && !e.shiftKey && !e.altKey) return;

    // Build shortcut string in Tauri format
    const parts: string[] = [];
    if (e.ctrlKey) parts.push('Ctrl');
    if (e.shiftKey) parts.push('Shift');
    if (e.altKey) parts.push('Alt');
    parts.push(tauriKey);

    const shortcut = parts.join('+');
    onChange(shortcut);
    setCapturing(false);
  }, [onChange]);

  useEffect(() => {
    if (capturing) {
      // Suspend OS-level global s
getTypeIcon function · typescript · L35-L47 (13 LOC)
src/components/Settings/SettingsView.tsx
function getTypeIcon(type: BreakpointType | string): ReactNode {
  const cls = 'w-4 h-4';
  const sw = 1.75;
  switch (type) {
    case 'zone': return <MapPin className={cls} strokeWidth={sw} />;
    case 'level': return <ArrowUp className={cls} strokeWidth={sw} />;
    case 'boss': return <Skull className={cls} strokeWidth={sw} />;
    case 'act': return <Landmark className={cls} strokeWidth={sw} />;
    case 'lab': return <Trophy className={cls} strokeWidth={sw} />;
    case 'custom': return <Star className={cls} strokeWidth={sw} />;
    default: return <span>•</span>;
  }
}
GroupModeSection function · typescript · L431-L454 (24 LOC)
src/components/Settings/SettingsView.tsx
function GroupModeSection() {
  const groupModeEnabled = useSettingsStore((s) => s.groupModeEnabled);
  const setGroupModeEnabled = useSettingsStore((s) => s.setGroupModeEnabled);

  return (
    <section>
      <h2 className="text-lg font-semibold text-[--color-text] mb-4 flex items-center gap-2 flex-wrap">
        Group Mode
        <HelpTip>
          Group Mode tracks up to 5 party members during group speedruns. Each member's progress is tracked independently. Enable this before starting a group run, then configure members in the Group tab.
        </HelpTip>
      </h2>
      <div className="card-inset rounded-lg p-4">
        <div className="flex items-center justify-between">
          <div>
            <div className="text-[--color-text]">Enable Group Mode</div>
            <div className="text-xs text-[--color-text-muted]">Track up to 5 party members during group speedruns. Configure members in the Group tab.</div>
          </div>
          <Toggle checked={groupModeEnabled}
GeneralTab function · typescript · L481-L668 (188 LOC)
src/components/Settings/SettingsView.tsx
function GeneralTab({
  poeLogPath,
  accountName,
  testCharacterName,
  checkUpdates,
  setLogPath,
  setAccountName,
  setTestCharacterName,
  setCheckUpdates,
  handleBrowseLogPath,
  handleDetectLogPath,
  handleSaveSettings,
  saveStatus,
  checking,
  available,
  version,
  updateError,
  checkForUpdate,
  downloadAndInstall,
  downloading,
  progress,
}: GeneralTabProps) {
  return (
    <div className="space-y-8">
      {/* PoE Configuration */}
      <section>
        <h2 className="text-lg font-semibold text-[--color-text] mb-4 flex items-center gap-2 flex-wrap">
          Path of Exile
          <HelpTip>
            PoE Watcher monitors your Client.txt log file to detect zone changes, level ups, and other game events. Your account name is used to fetch character data (equipment, passives, skills) from the public PoE API. Your profile must be set to public at pathofexile.com for snapshots to work.
          </HelpTip>
        </h2>
        <div className="card-inset rounde
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
ShortcutsTab function · typescript · L1161-L1222 (62 LOC)
src/components/Settings/SettingsView.tsx
function ShortcutsTab({
  editingHotkeys,
  hotkeyErrors,
  hotkeyApplyStatus,
  hasHotkeyChanges,
  hasHotkeyErrors,
  handleHotkeyChange,
  handleApplyHotkeys,
  handleResetHotkeys,
}: ShortcutsTabProps) {
  return (
    <div className="card-inset rounded-lg p-4 space-y-3">
      <div className="flex items-start gap-2 mb-3">
        <p className="text-sm text-[--color-text-muted]">
          Customize global hotkeys. Click a shortcut to rebind it, then press your desired key combination (must include Ctrl, Shift, or Alt). Press Escape to cancel.
        </p>
        <HelpTip>
          These global hotkeys work even when PoE Watcher is not focused — they're registered system-wide. Click a shortcut field and press your desired key combination. Each shortcut must include at least one modifier key (Ctrl, Shift, or Alt). Press Escape while recording to cancel.
        </HelpTip>
      </div>
      {HOTKEY_ACTIONS.map(({ key, label }) => (
        <div key={key} className="flex items-cent
Button function · typescript · L51-L90 (40 LOC)
src/components/Shared/Button.tsx
export function Button({
  variant = 'secondary',
  size = 'md',
  icon: Icon,
  loading = false,
  children,
  disabled,
  className = '',
  style,
  ...props
}: ButtonProps) {
  const vs = variantStyles[variant];
  return (
    <button
      disabled={disabled || loading}
      className={`
        inline-flex items-center justify-center gap-2 rounded-lg border
        transition-all duration-100 active:scale-95 active:brightness-90
        focus-visible:ring-2 focus-visible:ring-[--color-poe-gold] focus-visible:ring-offset-1 focus-visible:ring-offset-[--color-poe-darker]
        disabled:opacity-50 disabled:cursor-not-allowed disabled:active:scale-100
        hover:brightness-110
        ${vs.className}
        ${sizeClasses[size]}
        ${className}
      `}
      style={{ ...vs.style, ...style }}
      {...props}
    >
      {loading ? (
        <svg className="w-4 h-4 spinner" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5}>
          <circle cx="12" cy=
CustomSelect function · typescript · L18-L171 (154 LOC)
src/components/Shared/CustomSelect.tsx
export function CustomSelect({
  value,
  onChange,
  options,
  placeholder = 'Select...',
  disabled = false,
  className = '',
  maxHeight = 280,
}: CustomSelectProps) {
  const [isOpen, setIsOpen] = useState(false);
  const [highlightedIndex, setHighlightedIndex] = useState(-1);
  const containerRef = useRef<HTMLDivElement>(null);
  const listRef = useRef<HTMLDivElement>(null);

  const selectedOption = options.find((o) => o.value === value);
  const displayText = selectedOption?.label || placeholder;

  // Close on click outside
  useEffect(() => {
    if (!isOpen) return;
    const handleClickOutside = (e: MouseEvent) => {
      if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
        setIsOpen(false);
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [isOpen]);

  // Scroll highlighted item into view
  useEffect(() => {
    if (!isOpen 
EmptyState function · typescript · L11-L22 (12 LOC)
src/components/Shared/EmptyState.tsx
export function EmptyState({ icon: Icon, title, description, action }: EmptyStateProps) {
  return (
    <div className="flex flex-col items-center justify-center py-12 px-6 text-center">
      <Icon className="w-12 h-12 text-[--color-text-muted] mb-4" strokeWidth={1.25} />
      <h3 className="text-lg font-medium text-[--color-text] mb-1">{title}</h3>
      {description && (
        <p className="text-sm text-[--color-text-muted] max-w-xs">{description}</p>
      )}
      {action && <div className="mt-4">{action}</div>}
    </div>
  );
}
HelpTip function · typescript · L8-L35 (28 LOC)
src/components/Shared/HelpTip.tsx
export function HelpTip({ children }: HelpTipProps) {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button
        onClick={(e) => {
          e.stopPropagation();
          setOpen(!open);
        }}
        className={`inline-flex items-center justify-center rounded-full transition-colors ${
          open
            ? 'text-[--color-poe-gold]'
            : 'text-[--color-text-muted] hover:text-[--color-text]'
        }`}
        title="Help"
        type="button"
      >
        <HelpCircle className="w-4 h-4" />
      </button>
      {open && (
        <div className="w-full text-xs text-[--color-text-muted] mt-2 p-3 rounded-lg bg-[--color-surface-elevated] border border-[--color-border] leading-relaxed">
          {children}
        </div>
      )}
    </>
  );
}
LoadingSpinner function · typescript · L12-L25 (14 LOC)
src/components/Shared/LoadingSpinner.tsx
export function LoadingSpinner({ size = 'md', className = '' }: LoadingSpinnerProps) {
  return (
    <svg
      className={`spinner text-[--color-poe-gold] ${sizeClasses[size]} ${className}`}
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2.5}
    >
      <circle cx="12" cy="12" r="10" strokeOpacity={0.25} />
      <path d="M12 2a10 10 0 0 1 10 10" strokeLinecap="round" />
    </svg>
  );
}
RunFilter function · typescript · L29-L196 (168 LOC)
src/components/Shared/RunFilter.tsx
export function RunFilter({
  filters,
  onFiltersChange,
  onClear,
  showPresetFilter = true,
  showReferenceToggle = false,
}: RunFilterProps) {
  const [availableLeagues, setAvailableLeagues] = useState<string[]>([]);
  const [availableCategories, setAvailableCategories] = useState<string[]>([]);
  const [availablePresets, setAvailablePresets] = useState<string[]>([]);

  // Load distinct values from existing runs
  useEffect(() => {
    const loadDistinctValues = async () => {
      try {
        const runs = await invoke<Run[]>('get_runs');

        // Extract unique leagues
        const leagues = [...new Set(runs.map((r) => r.league).filter(Boolean))] as string[];
        setAvailableLeagues(leagues.sort());

        // Extract unique categories
        const categories = [...new Set(runs.map((r) => r.category))] as string[];
        setAvailableCategories(categories.sort());

        // Extract unique presets
        const presets = [...new Set(
          runs.map((r) => r.bre
SplitTimeEditor function · typescript · L20-L158 (139 LOC)
src/components/Shared/SplitTimeEditor.tsx
export function SplitTimeEditor({
  breakpoints,
  splitTimes,
  setSplitTimes,
  bossTimes,
  setBossTimes,
  townTimes,
  setTownTimes,
  enabledSplits,
  onToggleSplit,
  onToggleAll,
  allSelected,
}: SplitTimeEditorProps) {
  const timeInputRefs = useRef<Record<string, HTMLInputElement | null>>({});

  const showCheckboxes = !!enabledSplits;

  const isEnabled = (name: string) => !showCheckboxes || enabledSplits!.has(name);

  const getActiveBreakpoints = () =>
    showCheckboxes ? breakpoints.filter(bp => enabledSplits!.has(bp.name)) : breakpoints;

  const focusNextTimeInput = (currentBpName: string) => {
    const activeList = getActiveBreakpoints();
    const currentIndex = activeList.findIndex(bp => bp.name === currentBpName);
    if (currentIndex >= 0 && currentIndex < activeList.length - 1) {
      const nextBpName = activeList[currentIndex + 1].name;
      timeInputRefs.current[nextBpName]?.focus();
    }
  };

  const makeKeyHandler = (
    times: Record<string, string>,
Open data scored by Repobility · https://repobility.com
msToDigits function · typescript · L162-L169 (8 LOC)
src/components/Shared/SplitTimeEditor.tsx
export function msToDigits(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const hours = Math.floor(totalSeconds / 3600);
  const minutes = Math.floor((totalSeconds % 3600) / 60);
  const seconds = totalSeconds % 60;
  const raw = `${hours.toString().padStart(2, '0')}${minutes.toString().padStart(2, '0')}${seconds.toString().padStart(2, '0')}`;
  return raw.replace(/^0+/, '') || '0';
}
msToShortDigits function · typescript · L171-L177 (7 LOC)
src/components/Shared/SplitTimeEditor.tsx
export function msToShortDigits(ms: number): string {
  const totalSeconds = Math.floor(ms / 1000);
  const minutes = Math.floor(totalSeconds / 60);
  const seconds = totalSeconds % 60;
  const raw = `${minutes.toString().padStart(2, '0')}${seconds.toString().padStart(2, '0')}`;
  return raw.replace(/^0+/, '') || '0';
}
parseDigitsToMs function · typescript · L179-L186 (8 LOC)
src/components/Shared/SplitTimeEditor.tsx
export function parseDigitsToMs(digits: string): number {
  if (!digits) return 0;
  const padded = digits.padStart(6, '0');
  const hours = parseInt(padded.slice(0, 2));
  const minutes = parseInt(padded.slice(2, 4));
  const seconds = parseInt(padded.slice(4, 6));
  return (hours * 3600 + minutes * 60 + seconds) * 1000;
}
page 1 / 6next ›