373b5c4187422a9584814dcfa324028fd2e50f14
chmalee
  Tue Aug 4 15:00:30 2026 -0700
Have the pre-finish hook return the rows it wrote, and fix the table display bugs that the real row data now lets us resolve, refs #37999

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git src/hg/hgHubConnect/hooks/pre-finish.c src/hg/hgHubConnect/hooks/pre-finish.c
index a414861b735..4443d1810c2 100644
--- src/hg/hgHubConnect/hooks/pre-finish.c
+++ src/hg/hgHubConnect/hooks/pre-finish.c
@@ -247,36 +247,59 @@
                         upgradeExistingHubToAssembly(row, userDataDir, encodedParentDir);
                         }
                     else
                         createNewTempHubForUpload(reqId, row, userDataDir);
                     }
                 else if (isTwoBit)
                     {
                     // user's hub.txt is authoritative; just flip rows to assemblyHub.
                     upgradeExistingHubToAssembly(row, userDataDir, encodedParentDir);
                     }
                 }
             // still under the hub lock: makeParentDirRows checks for a row and then
             // inserts it, so two uploads to one hub would otherwise both insert the
             // same directory row
             // first make the parentDir rows
-            makeParentDirRows(row->userName, sqlDateToUnixTime(row->lastModified), row->db, row->parentDir, userDataDir, row->hubType);
+            // the directory rows carry the upload's own timestamp. row->lastModified
+            // holds it as a GMT clock string, which sqlDateToUnixTime would read as
+            // local time, so pass the seconds directly
+            makeParentDirRows(row->userName, lastModified, row->db, row->parentDir, userDataDir, row->hubType);
             row->parentDir = encodedParentDir ? hubNameFromPath(encodedParentDir) : "";
             addHubSpaceRowForFile(row);
             unlockHubDir(hubLockFd);
             fprintf(stderr, "added hubSpace row for file '%s'\n", fileName);
             fflush(stderr);
+
+            // Send the client the hub as it now stands, so it can show the new file,
+            // its directories and the hub.txt. The upload has already succeeded at
+            // this point, so catch errors here rather than let them reject it: the
+            // worst case is a client that shows nothing new until the page is reloaded
+            struct errCatch *respCatch = errCatchNew(0);
+            if (errCatchStart(respCatch))
+                {
+                if (encodedParentDir)
+                    {
+                    char *hubName = hubRootFromParentDir(encodedParentDir);
+                    setUploadedFileList(response, userName, listFilesInHubDir(userName, hubName));
+                    freeMem(hubName);
+                    }
+                }
+            errCatchEnd(respCatch);
+            if (respCatch->gotError)
+                fprintf(stderr, "could not list hub for response: %s\n",
+                        respCatch->message->string);
+            errCatchFree(&respCatch);
             }
         }
     // pop the handlers before handling the error, the cleanup below can itself
     // errAbort, which would longjmp back into this same block
     errCatchEnd(errCatch);
     if (errCatch->gotError)
         {
         // App-level reject: exit 0 + RejectUpload=true is the tusd protocol for
         // forwarding HTTPResponse verbatim. Non-zero gets wrapped.
         rejectUpload(response, "%s", errCatch->message->string);
         // clear the partial upload so the user can try again. pre-create hands tusd a
         // ChangeFileInfo, so tusFile is the file in the user's directory, not a temp
         // copy, and tusInfo is tusd's .info alongside it. remove() rather than
         // mustRemove(): this is the error path, and an abort here exits before the
         // response is printed, leaving the client with a bare 500 instead of the