~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

web: Writes, reports and notifications go through grain-serve

Merged
Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:web/writes-client

What changes

Stacked on web/reads (on web/client); merge those first.

Writes. $lib/utils/records has createRecord, putRecord, deleteRecord, applyWrites and uploadBlob, each calling its social.grain.repo.* method with the generated schema, taking what the old ones took (no repo: the cookie's account). Favorites, follows, blocks, comments, stories, galleries (create, edit, applyWrites with social.grain.repo.applyWrites#create|update|delete), group submissions, items and declines, the profile editor and the Bluesky cross-post all write through them. Muting is social.grain.graph.muteActor/unmuteActor, deleting a gallery social.grain.gallery.deleteGallery, deleting the account social.grain.server.deleteAccount.

Reports are com.atproto.moderation.createReport. The modal still offers grain's labels; each is sent as the reasonType grain-serve files under that label ($lib/labels's REPORT_REASONS, the inverse of grain::labels::label_for): atproto's for spam, misleading, sexual, rude and other, Ozone's reasonViolenceGraphicContent and reasonChildSafetyOther for gore and child safety, social.grain.moderation.defs#reasonCopyright and #reasonNudity for the two neither has.

Notifications are social.grain.notification.listNotifications; the badge is getUnreadCount's count where it was unseenCount; marking seen is updateSeen (in the first pull). Each item now also carries indexedAt and isRead and the page seenAt, which the page does not show yet; the list is ordered by indexedAt.

The profile editor reads its record's existing avatar blob ref with com.atproto.repo.getRecord at the account's own PDS ($lib/pds, the PDS found from its DID document at plc.directory or /.well-known/did.json), since grain-serve's views give the avatar as a CDN URL and no longer serve getRecord.

Why

The last slice of moving the web onto grain-serve. After it, the only $hatk/client imports left in web/ are the spaces calls below, for the spaces port.

How it was checked

New test/web/writes.test.ts: each write's method and body, applyWrites' write types and its no-op for none, an upload's bytes, every label having a report reason, the PDS found from a did:plc and a did:web document and the record read there. npm run check and vp test pass locally; the runner's verdict is below. Not tried in a browser against grain-serve.

Shortcomings

  • The Bluesky cross-post is refused. grain-serve's createRecord takes only collections it indexes and answers UnknownCollection for app.bsky.feed.post, so cross-posting from create, edit and stories fails until grain-serve writes posts it does not index (or the web stops offering it).
  • Called here, not served by grain-serve yet: graph.muteActor, graph.unmuteActor, gallery.deleteGallery, server.deleteAccount (and, from the last pull, getBlocks, getMutes, story.getArchive, story.markViewed, server.getSitemap). They answer 501 until it does.
  • Still on the old methods through $hatk/client, for the spaces port: private galleries (createPrivateGallery, getPrivateGallery, listPrivateGalleries, listSharedGalleries, getSpaceMembers, addSpaceMember, getSpaceSupport) and groups and pools (createGroup, getGroup, getGroupHost, listGroups, getGroupSubmissions, joinGroup, leaveGroup, listPoolGalleries, listPoolFeed, getPoolGallery, createPoolGallery, deletePoolGallery, createPoolComment, deletePoolComment, putPoolFavorite), in lib/queries.ts, lib/mutations.ts, FavoriteButton, CommentSheet, routes/private/* and routes/group/[did]/create. With them go the $hatk aliases in svelte.config.js and the old plugin in vitest.config.ts (with server/).
  • The profile editor now depends on plc.directory answering, as the gallery editor already did.

Serving in production

As proposed in the first pull: the node adapter as a web service beside grain-serve behind Caddy on one host, /xrpc/*, /oauth/*, /oauth-client-metadata.json, /og/* and /.well-known/* to grain-serve, the rest to web with GRAIN_SERVE_URL=http://grain-serve:2600.

🤖 Generated with Claude Code

Approved

This pull moves the web's writes, reports and notifications onto grain-serve's social.grain.* methods. It adds createRecord, putRecord, deleteRecord, applyWrites and uploadBlob in $lib/utils/records. It adds REPORT_REASONS in $lib/labels and a $lib/pds module that finds the PDS from the DID document.

I read only part of the diff. It is about 5,100 lines, and I read records.ts, pds.ts and the REPORT_REASONS map in labels.ts. I did not read the route, component and test changes. In what I read I found no bugs. The write wrappers map one-to-one onto their methods and drop repo. applyWrites does nothing for an empty list. pdsOf handles both did:plc and did:web, and it drops a failed lookup from its cache so the next call retries. Every grain label has a report reason.

All four checks (check, test, rust, harness) were still queued when I looked, so nothing has verified this yet. Please read their results before merging. The pull also says it was not tried in a browser.

Two things the pull already states, which I'm repeating because they affect users:

  • Bluesky cross-posting from create, edit and stories will fail until grain-serve accepts app.bsky.feed.post.
  • Mute, unmute, delete gallery and delete account answer 501 until grain-serve serves them.

Both are acceptable on a feature branch that doesn't deploy. Keep them on the list to close before this reaches main.

Approved

The only change since my last round is in preferenceSync.ts. readPreferences and keepPreferences now keep the preference set only in the browser. This fixes a real hazard: on the server, one module serves every request, so a module-level current could carry one user's preferences into another's request. The new test covers it. After a server-side read, the next write re-fetches getPreferences instead of reusing the kept set.

My earlier findings still stand: I found no bugs, and the caveats the pull states are acceptable on a feature branch. The four checks on the new commit (check, test, rust, harness) were all still queued when I looked, so please read their results before merging. The pull also says it was not tried in a browser.

Merged as 2ce916f
Part of a stack of 2
  1. web: Writes, reports and notifications go through grain-serve Landed as 2ce916f
  2. main