595531f894a27e59e18b32436f6cb932fa4374a0
lrnassar
  Tue Sep 8 13:03:44 2026 -0700
BLAT form character counter: per-type limits passed through from the C constants. refs #38293

The counter showed the 75,000 DNA limit for every query type; protein and translated queries
are capped at 10,000, so an oversized protein paste looked fine until the server rejected it.
The per-sequence limits are now named constants in hgBlat.c, emitted into hgBlatFormData and
read by the counter, which keys on the Query type dropdown and recounts when it changes -
so the numbers cannot drift apart again. BLAT's guess counts against the DNA limit.

diff --git src/hg/hgBlat/hgBlat.c src/hg/hgBlat/hgBlat.c
index 6188f8fe540..b0e25eb588a 100644
--- src/hg/hgBlat/hgBlat.c
+++ src/hg/hgBlat/hgBlat.c
@@ -52,30 +52,36 @@
 struct hash *oldVars = NULL;
 struct perfTimer *hgBlatTiming = NULL;	/* Non-NULL when &measureTiming is set; times the request
 					 * and is emitted as hgBlatData.timing for the JS dialog. */
 boolean orgChange = FALSE;
 boolean dbChange = FALSE;
 boolean allGenomes = FALSE;
 boolean allResults = FALSE;
 boolean autoRearr = FALSE;
 static long enteredMainTime = 0;
 
 
 boolean autoBigPsl = FALSE;  // DEFAULT VALUE change to TRUE in future
 
 /* for earlyBotCheck() function at the beginning of main() */
 #define delayFraction   0.5    /* standard penalty is 1.0 for most CGIs */
+
+/* Per-sequence query size limits, enforced below and shown by the new form's character counter
+ * (emitted into hgBlatFormData so the C and JS numbers cannot drift apart).  The total limit for
+ * a multi-sequence submission is 2.5x the per-sequence limit. */
+#define maxSingleSizeDna 75000
+#define maxSingleSizeTx  10000    /* protein and translated queries */
                                 /* this one is 0.5 */
 static boolean issueBotWarning = FALSE;
 
 struct gfResult
 /* Detailed gfServer results, this is a span of several nearby tiles, minimum 2 for dna. */
     {
     struct gfResult *next;
     /* have to multiply translated coordinates by 3 */
     int qStart;    /* Query Start Coordinate */  
     int qEnd;      /* Query End Coordinate */
     char *chrom;   /* Target Chrom Name */
     int tStart;    /* Target Start Coordinate */  
     int tEnd;      /* Target End Coordinate */
     int numHits;   /* number of tile hits, minimum  2 for dna */ 
     char tStrand;  /* + or - Target Strand used with prot, rnax, dnax */ 
@@ -2244,31 +2250,31 @@
 if (seqList != NULL && seqList->name[0] == 0)
     {
     freeMem(seqList->name);
     seqList->name = cloneString("YourSeq");
     }
 trimUniq(seqList);
 
 
 /* If feeling lucky only do the first one. */
 if(feelingLucky && seqList != NULL)
     {
     seqList->next = NULL;
     }
 
 /* Figure out size allowed. */
-maxSingleSize = (isTx ? 10000 : 75000);
+maxSingleSize = (isTx ? maxSingleSizeTx : maxSingleSizeDna);
 maxTotalSize = maxSingleSize * 2.5;
 #ifdef LOWELAB
 maxSeqCount = 200;
 #else
 maxSeqCount = 25;
 #endif
 char *optionMaxSeqCount = cfgOptionDefault("hgBlat.maxSequenceCount", NULL);
 
 if (isNotEmpty(optionMaxSeqCount))
    maxSeqCount = sqlSigned(optionMaxSeqCount);
 
 /* Create temporary file to store sequence. */
 trashDirFile(&faTn, "hgSs", "hgSs", ".fa");
 faWriteAll(faTn.forCgi, seqList);
 
@@ -2802,30 +2808,33 @@
 jsonWriteBoolean(jw, "autoRearr", autoRearr);
 jsonWriteBoolean(jw, "allGenomes", allGenomes);
 /* "Keep results" only means something on a machine configured to clear earlier BLAT result tracks.
  * With blatOldTracks at its "keep" default, or at "hide", there is nothing to opt out of, so the
  * checkbox is not shown at all.  hgc.c (buildBigPsl) is what acts on blatKeepResults. */
 jsonWriteBoolean(jw, "showKeepResults",
                  sameString(cfgOptionDefault("blatOldTracks", "keep"), "delete"));
 jsonWriteBoolean(jw, "keepResults", cartUsualBoolean(cart, "blatKeepResults", FALSE));
 /* "Keep only last search" checkbox (RM #38086): the inverse framing - results accumulate by
  * default (the current public behavior) and checking the box opts into removing earlier BLAT
  * result tracks on each new search.  Gated by its own hg.conf setting, independent of
  * blatOldTracks above; hgc.c (buildBigPsl) is what acts on blatOnlyLatest. */
 jsonWriteBoolean(jw, "showOnlyLatest",
     sameString(cfgOptionDefault("blatOnlyLatestCheckbox", "off"), "on"));
 jsonWriteBoolean(jw, "onlyLatest", cartUsualBoolean(cart, "blatOnlyLatest", FALSE));
+/* The enforced per-sequence limits, so the character counter shows the right cap per query type. */
+jsonWriteNumber(jw, "maxSingleDna", maxSingleSizeDna);
+jsonWriteNumber(jw, "maxSingleTx", maxSingleSizeTx);
 /* The example is fetched on demand rather than inlined: it is a real 2.5 kb sequence, which would
  * otherwise be embedded in every page load of the form just to serve the few users who click
  * "Load example".  The sequence is a window over two PTP4A3 exons that is also carried by an alt
  * haplotype and a fix patch of chr8, so the results table shows the alt/fix rows and their
  * explanatory icons rather than a single boring hit. */
 jsonWriteString(jw, "exampleUrl", "../goldenPath/help/blatExample.fa");
 jsonWriteString(jw, "exampleLabel", "Load example");
 jsonWriteString(jw, "exampleTitle", "Fill the box with an example query: "
     "2.5 kb of the human PTP4A3 gene.");
 /* Same "similar tools" links the classic page offered, so the sidebar isn't a set of dead links.
  * These carry $DB$ rather than a baked-in db: picking a genome no longer reloads the page, so
  * blatFormSetDb() re-expands them against the newly chosen assembly.
  *
  * Deliberately NOT gated on hgPcrOk() the way the classic form was.  That test can only be made for
  * the assembly the page happened to load with, so on a page where the genome can be changed without