feat: profile pictures for spaces and maintained resources, with SVG support (#632, #634) - #636
Merged
Merged
Conversation
A space or maintained resource that declares schema:image on its own IRI now shows that picture above its title and main URI, plainly -- no tilted-square mask like user icons get, and no fallback: a resource without a declared picture renders exactly as before. Only declarations signed by a current admin of the governing space count, checked by the new get-resource-profile-picture query: the resource's npa:hasGoverningSpaceRef edge (reflexive for a space itself, the maintainer space for a maintained resource) is joined to the admin RoleInstantiations of that ref and on to npa:AccountState, so a space's picture is not something an unrelated agent can set -- unlike a user's self-declared one, which keeps the subject-agnostic get-all-user-profile-pics. The picture is fetched inside the resource's existing asynchronous data update, next to the view displays, so rendering never waits on it and a post-publish forced refresh picks up a new picture with the rest of the structure. No About-page option to set one yet (per the issue): a picture is declared by publishing the triple. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A schema:image value may now be a literal holding the SVG itself, instead of a link to an image file -- for users as well as for spaces and maintained resources. The new ProfilePicture is the one place that interprets the value: SVG markup is reduced to the same static subset as an SVG view's output (Utils.sanitizeSvg), gets an xmlns injected when the author left it out (an <img> parses SVG as XML and fails without one), and travels on as a data: URI. Every place that shows a picture stays a plain <img>, so the tilted-square mask on user icons, the object-fit rules on resource pictures and the sizing in list rows keep working for either kind of value -- and inside an <img> an SVG document can neither script nor fetch, so a picture can never reach into the page showing it. UserData now skips values that are usable as neither, rather than parsing them as IRIs: a bare Values.iri there would abort the whole user-data load on the first SVG literal it met. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The patterns behind isValidLiteralSerialization and getParsedLiteral had the shape ^"(([^\\"]|\\\\|\\")*)"..., an alternation inside a greedy star, which Java's regex engine evaluates with one stack frame per character. A value of a few thousand characters -- an SVG picture pasted into a value field, say -- therefore threw StackOverflowError inside the publish form's validator and turned the request into a 500. The quoted part is now scanned linearly; only the @lang / ^^<iri> suffix is still matched by a regex. getEscapedLiteralString never escaped quotes either: replaceAll("\"", "\\\\\"") passes \" as a replacement string, where the backslash is an escape, so it emitted a bare quote and any value containing one round-tripped into an unparseable serialization. It now uses String.replace, which takes its arguments literally. UtilsTest.getEscapedLiteralStringEscapesQuotesCorrectly had codified that bug -- it asserted the input came back unchanged, while its backslash sibling asserted real escaping -- so its expectation is corrected here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drawing tools export SVG with the paint in a style attribute (style="fill:rgb(120,184,134);") rather than in presentation attributes. The sanitizer allows no style attribute -- it is the one attribute whose value can pull in external resources -- so those declarations were dropped and every shape fell back to the SVG default fill: an all-black figure wherever such a picture or figure was shown. The safe declarations are now rewritten into the equivalent presentation attributes before sanitizing, so the existing allow-list validates them like any other attribute. Nothing new is allowed through: the property must be one of the paint/text ones, and the value may contain no function call other than rgb/rgba/hsl/hsla(...), so url(...) and expression(...) cannot survive. stroke-miterlimit, clip-rule and stroke-dashoffset join the attribute list, as tool exports commonly set them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
🎉 This PR is included in version 5.12.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #632, closes #634.
Profile pictures for spaces and maintained resources (#632)
A space or maintained resource that declares
schema:imageon its own IRI shows that picture above its title and main URI, plainly — no tilted-square mask like user icons get. With no picture declared, the header renders exactly as before.Only declarations signed by a current admin of the governing space count. The new query
RALK8_WQ…/get-resource-profile-picturefollows the shape ofget-view-displays-unresolved: it runs onrepo/fulland makes one federated hop torepo/spacesfor the authority gate (npa:hasGoverningSpaceRef→ adminRoleInstantiation→npa:AccountState), reflexive for a space itself and pointing at the maintainer space for a maintained resource. Candidates are found subject-first, so that hop only ever sees a handful of rows. The maintainer tier is deliberately not accepted, and the gate is not pinned to a single ref — for an identifier with rival claimants, an admin of any claiming ref can declare a picture.The picture is fetched inside the resource's existing asynchronous data update, next to the view displays, so rendering never waits on it and a post-publish forced refresh picks up a new picture with the rest of the structure.
Per the issue, there is no About-page option to set one yet: a picture is declared by publishing the triple.
SVG markup as a profile picture (#634)
A
schema:imagevalue may be a literal holding the SVG itself rather than a link — for users as well as for spaces and maintained resources.ProfilePictureis the one place that interprets the value: SVG markup goes throughUtils.sanitizeSvg, gets anxmlnsinjected when the author left it out (an<img>parses SVG as XML and fails without one), and travels on as adata:URI. Every picture site stays a plain<img>, so the mask on user icons,object-fiton resource pictures and list-row sizing keep working for either kind of value — and inside an<img>an SVG document can neither script nor fetch, so a picture cannot reach into the page showing it.Setting one through the UI needs a template whose field accepts a literal. The live one is URL-only and cannot be superseded from another account (latest-version resolution joins on the same pubkey), so this is published instead:
RAz5tNdG…/template— one statement with annt:ValuePlaceholder("image URL, or SVG markup in quotes"), space-governed byhttps://w3id.org/spaces/knowledgepixels/nanodashso its members can edit it collaborativelyRA6owO2C…—<kind> a gen:MaintainedResource ; gen:isMaintainedBy <…/nanodash>RATPxXJT…— the "update profile image..." action points at the template's embedded IRINo code change is needed for governed float: the action URL carries
template-version=latest, andTemplateData.getLatestTemplateIdroutes governed pins throughget-latest-governed-version.Two bugs found on the way
Long literals overflowed the stack.
isValidLiteralSerialization/getParsedLiteralused patterns shaped^"(([^\\"]|\\\\|\\")*)"…— an alternation inside a greedy star, which Java's regex engine evaluates with one stack frame per character. A ~15 KB SVG pasted into a value field threwStackOverflowErrorin the publish form's validator, i.e. a 500. The quoted part is now scanned linearly.getEscapedLiteralStringnever escaped quotes.replaceAll("\"", "\\\\\"")passes\"as a replacement string, where the backslash is an escape, so it emitted a bare quote and any value containing one round-tripped into an unparseable serialization. Now usesString.replace.UtilsTest.getEscapedLiteralStringEscapesQuotesCorrectlyhad codified that bug (it asserted the input came back unchanged, while its backslash sibling asserted real escaping), so its expectation is corrected here.SVG exported by drawing tools rendered all black. Serif/Affinity-style exports put every paint in
style="fill:rgb(…)", and the sanitizer allows nostyleattribute — so the declarations were dropped and every shape fell back to the SVG default fill. Safe declarations are now rewritten into the equivalent presentation attributes before sanitizing, so the existing allow-list validates them like any other attribute: the property must be one of the paint/text ones, and the value may contain no function call other thanrgb/rgba/hsl/hsla(…), sourl(…)andexpression(…)cannot survive. This also fixes inline SVG in cells, lists and paragraphs, not just profile pictures.Verification
Driven against a local instance on a separate port (dev server untouched, nothing published from it):
test/grouprenders; the authority join was validated against live data with a stand-in predicate before publishing, a bogus-role variant returns 0 rows, and the two non-space subjects in the network that do carryschema:imageare correctly rejected23 new unit tests (
ProfilePictureTest,LiteralSerializationTest,SvgStyleSanitizationTest); full suite green at 1176.🤖 Generated with Claude Code