web: leave the 204 PUT response bodyless
A route block returning "" gets it written into the body, so the 204
carried text/html and content-length headers that Rack 3 lint (rackup
under rake dev) rejects on a bodyless status.

Assisted-by: GLM-5.3 via pi
change troryrltxqoukurvzxnuunsvxtntrssp
commit b2e7d1fed96d496a9a2e34aa9186545f8529db95
author Alpha Chen <alpha@kejadlen.dev>
date
parent zztrxqwy
diff --git a/lib/pro_tacts/web.rb b/lib/pro_tacts/web.rb
index dd93a44..fb182e1 100644
--- a/lib/pro_tacts/web.rb
+++ b/lib/pro_tacts/web.rb
@@ -382,7 +382,7 @@ module ProTacts
     # the request's own validity first, the conditionals on stored
     # state after — so a card that cannot be stored hears
     # CARDDAV:valid-address-data even when its If-Match is stale too.
-    #: (String id) -> String
+    #: (String id) -> String?
     def write_card(id)
       # The body is the one binary input: Rack requires input in
       # ASCII-8BIT and Rack::RewindableInput enforces it again. Relabel
@@ -438,7 +438,11 @@ module ProTacts
       # contributes properties at write time, that PUT must stop
       # sending one.
       response["ETag"] = stored.etag
-      ""
+
+      # A returned "" would land in the body and pin text/html and
+      # content-length onto the 204, which a bodyless status must not
+      # carry (Rack 3's lint rejects both); nil leaves it bodyless.
+      nil
     end
 
     # A parse that answers nil rather than raising when the bytes are
diff --git a/test/pro_tacts/test_web.rb b/test/pro_tacts/test_web.rb
index d90400c..00f9345 100644
--- a/test/pro_tacts/test_web.rb
+++ b/test/pro_tacts/test_web.rb
@@ -218,6 +218,11 @@ class WebTest < Minitest::Test
       etag = %("#{Digest::SHA256.hexdigest(updated)}")
       assert_equal etag, last_response["ETag"]
 
+      # A bodyless status must carry neither header: Rack 3's lint rejects
+      # both on a 204, and `rake dev` runs under it.
+      assert_nil last_response["Content-Type"]
+      assert_nil last_response["Content-Length"]
+
       # The tag the PUT returned is the one every read now reports, which
       # is what lets a client keep syncing against its own write.
       get "/dav/addressbook/aiden.vcf"