From 5c86444421bdfd690dfa912e2a8de5e4b8966687 Mon Sep 17 00:00:00 2001 From: Brandon Kraft Date: Tue, 19 May 2026 15:39:07 -0500 Subject: [PATCH 1/3] Add blurhash term to JSON-LD context Adds the `blurhash` => `toot:blurhash` mapping to the outbound JSON-LD context so attachments that include a `blurhash` property are strictly correct JSON-LD. Mirrors Mastodon's own outbound context shape (Mastodon coined the property under the `toot:` namespace), so this is pure parity. Real-world consumers (Mastodon, Pixelfed) read `blurhash` by property name regardless of context, so the practical effect is JSON-LD correctness for downstream emitters that already populate the property on image attachments. --- .github/changelog/add-jsonld-blurhash-term | 4 ++++ includes/activity/class-activity.php | 1 + 2 files changed, 5 insertions(+) create mode 100644 .github/changelog/add-jsonld-blurhash-term diff --git a/.github/changelog/add-jsonld-blurhash-term b/.github/changelog/add-jsonld-blurhash-term new file mode 100644 index 0000000000..b1beb1fb9c --- /dev/null +++ b/.github/changelog/add-jsonld-blurhash-term @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Add the `blurhash` term to the outbound JSON-LD `@context` so attachments that include a `blurhash` property are strictly correct JSON-LD, matching Mastodon's own context shape. diff --git a/includes/activity/class-activity.php b/includes/activity/class-activity.php index 28a5e30ae6..8f23a5932e 100644 --- a/includes/activity/class-activity.php +++ b/includes/activity/class-activity.php @@ -40,6 +40,7 @@ class Activity extends Base_Object { array( 'toot' => 'http://joinmastodon.org/ns#', 'QuoteRequest' => 'toot:QuoteRequest', + 'blurhash' => 'toot:blurhash', ), ); From 368552ea0748a37ff5c93f13c2fc319427903ac1 Mon Sep 17 00:00:00 2001 From: Brandon Kraft Date: Tue, 19 May 2026 15:56:50 -0500 Subject: [PATCH 2/3] Apply blurhash term mapping to Base_Object context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prior commit added the `blurhash` => `toot:blurhash` mapping (and the `toot` namespace) to `Activity::JSON_LD_CONTEXT`, but production payloads serialize the activity's object and `Activity::get_json_ld_context()` resolves the emitted context via the object's class constant (`$class::JSON_LD_CONTEXT`) when an object is present — only falling back to `static::JSON_LD_CONTEXT` (Activity's own) when there isn't one. Outbound photo posts always have an object, so the term mapping on `Activity` was never consulted for them and the `blurhash` property on `Image` attachments still serialized without a JSON-LD term declaration. Add `toot` => `http://joinmastodon.org/ns#` and `blurhash` => `toot:blurhash` to `Base_Object::JSON_LD_CONTEXT` so the mapping actually lands in outbound envelopes. Keep the existing entry on `Activity::JSON_LD_CONTEXT` as defensive belt-and-suspenders — it costs nothing and remains correct for the no-object fallback path. --- includes/activity/class-base-object.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/activity/class-base-object.php b/includes/activity/class-base-object.php index 5668cd0ed8..8424ad33e0 100644 --- a/includes/activity/class-base-object.php +++ b/includes/activity/class-base-object.php @@ -162,6 +162,8 @@ class Base_Object extends Generic_Object { '@id' => 'gts:always', '@type' => '@id', ), + 'toot' => 'http://joinmastodon.org/ns#', + 'blurhash' => 'toot:blurhash', ), ); From 72335f3d8c06c6112b81c0eba0c8dcddc7ac7ed5 Mon Sep 17 00:00:00 2001 From: Brandon Kraft Date: Wed, 20 May 2026 08:56:59 -0500 Subject: [PATCH 3/3] Update outbox snapshot tests for new context terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toot/blurhash term additions to Base_Object::JSON_LD_CONTEXT in the prior commit changed the literal shape of the outbound @context object, breaking two snapshot-matching tests in Test_Outbox::test_add. Add the same two terms to the expected JSON in both snapshots so they match the new context shape exactly. No behavior change — the test fixtures are the only callers that pinned the literal context bytes. --- tests/phpunit/tests/includes/collection/class-test-outbox.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/tests/includes/collection/class-test-outbox.php b/tests/phpunit/tests/includes/collection/class-test-outbox.php index eefe982682..a53580ff7a 100644 --- a/tests/phpunit/tests/includes/collection/class-test-outbox.php +++ b/tests/phpunit/tests/includes/collection/class-test-outbox.php @@ -105,8 +105,8 @@ public function test_compare_objects() { public function activity_object_provider() { $home_url = \addcslashes( \home_url(), '/' ); - $note1_json = '{"@context":["https:\/\/www.w3.org\/ns\/activitystreams",{"Hashtag":"as:Hashtag","sensitive":"as:sensitive","dcterms":"http:\/\/purl.org\/dc\/terms\/","gts":"https:\/\/gotosocial.org\/ns#","schema":"http:\/\/schema.org\/","exifData":"schema:exifData","PropertyValue":"schema:PropertyValue","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"canReply":{"@id":"gts:canReply","@type":"@id"},"canLike":{"@id":"gts:canLike","@type":"@id"},"canAnnounce":{"@id":"gts:canAnnounce","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"},"always":{"@id":"gts:always","@type":"@id"}}],"actor":"http:\/\/example.org\/?author=1","id":"http:\/\/example.org\/?post_type=ap_outbox\u0026p=351","type":"Create","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"object":{"id":"https:\/\/example.com\/1","type":"Note","content":"\u003Cp\u003EThis is a note\u003C\/p\u003E","contentMap":{"en":"\u003Cp\u003EThis is a note\u003C\/p\u003E"},"tag":[],"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"mediaType":"text\/html"}}'; - $note2_json = '{"@context":["https:\/\/www.w3.org\/ns\/activitystreams",{"Hashtag":"as:Hashtag","sensitive":"as:sensitive","dcterms":"http:\/\/purl.org\/dc\/terms\/","gts":"https:\/\/gotosocial.org\/ns#","schema":"http:\/\/schema.org\/","exifData":"schema:exifData","PropertyValue":"schema:PropertyValue","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"canReply":{"@id":"gts:canReply","@type":"@id"},"canLike":{"@id":"gts:canLike","@type":"@id"},"canAnnounce":{"@id":"gts:canAnnounce","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"},"always":{"@id":"gts:always","@type":"@id"}}],"actor":"http:\/\/example.org\/?author=0","id":"http:\/\/example.org\/?post_type=ap_outbox\u0026p=352","type":"Create","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"object":{"id":"https:\/\/example.com\/2","type":"Note","content":"\u003Cp\u003EThis is another note\u003C\/p\u003E","contentMap":{"en":"\u003Cp\u003EThis is another note\u003C\/p\u003E"},"tag":[],"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"mediaType":"text\/html"}}'; + $note1_json = '{"@context":["https:\/\/www.w3.org\/ns\/activitystreams",{"Hashtag":"as:Hashtag","sensitive":"as:sensitive","dcterms":"http:\/\/purl.org\/dc\/terms\/","gts":"https:\/\/gotosocial.org\/ns#","schema":"http:\/\/schema.org\/","exifData":"schema:exifData","PropertyValue":"schema:PropertyValue","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"canReply":{"@id":"gts:canReply","@type":"@id"},"canLike":{"@id":"gts:canLike","@type":"@id"},"canAnnounce":{"@id":"gts:canAnnounce","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"},"always":{"@id":"gts:always","@type":"@id"},"toot":"http:\/\/joinmastodon.org\/ns#","blurhash":"toot:blurhash"}],"actor":"http:\/\/example.org\/?author=1","id":"http:\/\/example.org\/?post_type=ap_outbox\u0026p=351","type":"Create","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"object":{"id":"https:\/\/example.com\/1","type":"Note","content":"\u003Cp\u003EThis is a note\u003C\/p\u003E","contentMap":{"en":"\u003Cp\u003EThis is a note\u003C\/p\u003E"},"tag":[],"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"mediaType":"text\/html"}}'; + $note2_json = '{"@context":["https:\/\/www.w3.org\/ns\/activitystreams",{"Hashtag":"as:Hashtag","sensitive":"as:sensitive","dcterms":"http:\/\/purl.org\/dc\/terms\/","gts":"https:\/\/gotosocial.org\/ns#","schema":"http:\/\/schema.org\/","exifData":"schema:exifData","PropertyValue":"schema:PropertyValue","interactionPolicy":{"@id":"gts:interactionPolicy","@type":"@id"},"canQuote":{"@id":"gts:canQuote","@type":"@id"},"canReply":{"@id":"gts:canReply","@type":"@id"},"canLike":{"@id":"gts:canLike","@type":"@id"},"canAnnounce":{"@id":"gts:canAnnounce","@type":"@id"},"automaticApproval":{"@id":"gts:automaticApproval","@type":"@id"},"manualApproval":{"@id":"gts:manualApproval","@type":"@id"},"always":{"@id":"gts:always","@type":"@id"},"toot":"http:\/\/joinmastodon.org\/ns#","blurhash":"toot:blurhash"}],"actor":"http:\/\/example.org\/?author=0","id":"http:\/\/example.org\/?post_type=ap_outbox\u0026p=352","type":"Create","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"object":{"id":"https:\/\/example.com\/2","type":"Note","content":"\u003Cp\u003EThis is another note\u003C\/p\u003E","contentMap":{"en":"\u003Cp\u003EThis is another note\u003C\/p\u003E"},"tag":[],"to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"mediaType":"text\/html"}}'; $event_json = '{"@context":["https:\/\/schema.org\/","https:\/\/www.w3.org\/ns\/activitystreams",{"pt":"https:\/\/joinpeertube.org\/ns#","mz":"https:\/\/joinmobilizon.org\/ns#","status":"http:\/\/www.w3.org\/2002\/12\/cal\/ical#status","commentsEnabled":"pt:commentsEnabled","isOnline":"mz:isOnline","timezone":"mz:timezone","participantCount":"mz:participantCount","anonymousParticipationEnabled":"mz:anonymousParticipationEnabled","joinMode":{"@id":"mz:joinMode","@type":"mz:joinModeType"},"externalParticipationUrl":{"@id":"mz:externalParticipationUrl","@type":"schema:URL"},"repliesModerationOption":{"@id":"mz:repliesModerationOption","@type":"@vocab"},"contacts":{"@id":"mz:contacts","@type":"@id"}}],"actor":"http:\/\/example.org\/?author=1","id":"http:\/\/example.org\/?post_type=ap_outbox\u0026p=353","type":"Create","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"object":{"id":"https:\/\/example.com\/3","type":"Event","content":"\u003Cp\u003EYou should not miss this Event!\u003C\/p\u003E","contentMap":{"en":"\u003Cp\u003EYou should not miss this Event!\u003C\/p\u003E"},"name":"WP Test Event","nameMap":{"en":"WP Test Event"},"endTime":"2030-02-29T17:00:00+01:00","location":[{"id":"https:\/\/example.com\/place\/1","type":"Place","attributedTo":"https:\/\/wp-test.event-federation.eu\/@test","name":"Fediverse Place","address":{"type":"PostalAddress","addressCountry":"FediCountry","addressLocality":"FediTown","postalCode":"1337","streetAddress":"FediStreet"}},{"type":"VirtualLocation","url":"https:\/\/example.com\/VirtualMeetingRoom"}],"startTime":"2030-02-29T16:00:00+01:00","to":["https:\/\/www.w3.org\/ns\/activitystreams#Public"],"mediaType":"text\/html","tag":[],"timezone":"Europe\/Vienna","category":"MOVEMENTS_POLITICS","joinMode":"external"}}'; return array( array(