perf(moderation): O(1) hidden count, index hidden folder prefixes (P2)

ober

adf7e709dec967169ffb5faa719b4e832ceee862

diff --git a/src/imagesite/moderation.ss b/src/imagesite/moderation.ss
index ed4b399..def64ba 100644
--- a/src/imagesite/moderation.ss
+++ b/src/imagesite/moderation.ss
@@ -57,19 +57,37 @@
   (sort (map car (hash->list hidden)) string<?))
 
 (def (hidden-media-count hidden)
-  (length (hidden-media-paths hidden)))
+  (hash-length hidden))
+
+;; Cache of (hidden-table . folder-entry-list) keyed by table identity, so
+;; hidden-path? scans only the folder entries (not every hidden media path) and
+;; rebuilds that short list once per hidden table rather than once per media
+;; checked. The cached table is read in one snapshot to stay consistent under
+;; concurrent requests; a miss simply recomputes.
+(def *hidden-folder-cache* (cons #f '()))
+
+(def (hidden-folder-prefixes hidden)
+  (let ((cached *hidden-folder-cache*))
+    (if (eq? (car cached) hidden)
+      (cdr cached)
+      (let ((folders (let loop ((entries (hash->list hidden)) (out '()))
+                       (cond
+                         ((null? entries) (reverse out))
+                         ((hidden-folder-entry? (car (car entries)))
+                          (loop (cdr entries) (cons (car (car entries)) out)))
+                         (else (loop (cdr entries) out))))))
+        (set! *hidden-folder-cache* (cons hidden folders))
+        folders))))
 
 (def (hidden-path? hidden rel)
   (and hidden
        (safe-relative-path? rel)
        (or (hash-key? hidden rel)
-           (let loop ((entries (hash->list hidden)))
+           (let loop ((folders (hidden-folder-prefixes hidden)))
              (cond
-               ((null? entries) #f)
-               ((let ((entry (car (car entries))))
-                  (and (hidden-folder-entry? entry)
-                       (string-prefix? entry rel))) #t)
-               (else (loop (cdr entries))))))))
+               ((null? folders) #f)
+               ((string-prefix? (car folders) rel) #t)
+               (else (loop (cdr folders))))))))
 
 (def (hidden-media? hidden rel)
   (hidden-path? hidden rel))