diff hg-web/src/components/app.tsx @ 223:0e7b9464248d hg-web

[hg-web] Add browser route regression coverage
author MrJuneJune <me@mrjunejune.com>
date Sun, 02 Aug 2026 10:07:40 -0700
parents ce7f4400c2de
children 70de0c80d093
line wrap: on
line diff
--- a/hg-web/src/components/app.tsx	Sun Aug 02 09:07:36 2026 -0700
+++ b/hg-web/src/components/app.tsx	Sun Aug 02 10:07:40 2026 -0700
@@ -13,6 +13,7 @@
   graphTip?: string;
   dirPath?: string;
   changesetId?: string;
+  returnDepth?: number;
 }
 
 type ChangesetDetail = {
@@ -24,7 +25,10 @@
   tags: string[];
   user: string;
   parents: string[];
-  files: string[];
+  files: Array<{
+    file: string;
+    status: string;
+  }>;
   diff: Array<{
     blockno: number;
     lines: Array<{ t: string; n: number; l: string }>;
@@ -106,6 +110,13 @@
   }
 }
 
+function isRouteState(value: unknown): value is RouteState {
+  if (!value || typeof value !== 'object' || !('page' in value)) return false;
+  return ['landing', 'graph', 'directory', 'changeset'].includes(
+    String((value as { page: unknown }).page),
+  );
+}
+
 // Landing Page Component
 function LandingPage({
   onNavigateToGraph,
@@ -242,7 +253,7 @@
   return (
     <div>
       <div className="page-header">
-        <button className="back-button" onClick={onBack}>
+        <button className="back-button" onClick={onBack} aria-label="Back">
           &larr; Back
         </button>
         <span className="page-title">Commit Graph</span>
@@ -320,8 +331,8 @@
   return (
     <div>
       <div className="page-header">
-        <button className="back-button" onClick={onBack}>
-          &larr; Back to graph
+        <button className="back-button" onClick={onBack} aria-label="Back">
+          &larr; Back
         </button>
         <span className="page-title">Changeset</span>
       </div>
@@ -364,7 +375,14 @@
           {changeset.files.length > 0 && (
             <div className="changeset-files">
               <strong>Files</strong>
-              {changeset.files.map(file => <code key={file}>{file}</code>)}
+              {changeset.files.map(file => (
+                <code key={file.file}>
+                  <span className={`changeset-file-status status-${file.status}`}>
+                    {file.status}
+                  </span>
+                  {file.file}
+                </code>
+              ))}
             </div>
           )}
 
@@ -409,7 +427,7 @@
   return (
     <div>
       <div className="page-header">
-        <button className="back-button" onClick={onBack}>
+        <button className="back-button" onClick={onBack} aria-label="Back">
           &larr; Back
         </button>
         <span className="page-title">Repository Files</span>
@@ -430,8 +448,8 @@
 
   // Handle browser back/forward
   useEffect(() => {
-    const handlePopState = () => {
-      setRoute(parseRoute());
+    const handlePopState = (event: PopStateEvent) => {
+      setRoute(isRouteState(event.state) ? event.state : parseRoute());
     };
     window.addEventListener('popstate', handlePopState);
     return () => window.removeEventListener('popstate', handlePopState);
@@ -456,8 +474,19 @@
   }, [navigate]);
 
   const navigateToChangeset = useCallback((changesetId: string) => {
-    navigate({ page: 'changeset', changesetId });
-  }, [navigate]);
+    const returnDepth = route.page === 'changeset'
+      ? (route.returnDepth ?? 0) + 1
+      : 1;
+    navigate({ page: 'changeset', changesetId, returnDepth });
+  }, [navigate, route.page, route.returnDepth]);
+
+  const navigateBackFromChangeset = useCallback(() => {
+    if (route.returnDepth !== undefined && route.returnDepth > 0) {
+      window.history.go(-route.returnDepth);
+      return;
+    }
+    navigateToGraph();
+  }, [navigateToGraph, route.returnDepth]);
 
   const handleDirectoryPathChange = useCallback((path: string) => {
     // Update URL without full navigation
@@ -530,7 +559,7 @@
       {route.page === 'changeset' && route.changesetId && (
         <ChangesetPage
           changesetId={route.changesetId}
-          onBack={() => navigateToGraph()}
+          onBack={navigateBackFromChangeset}
           onOpenChangeset={navigateToChangeset}
         />
       )}