astro-atmosphere-comments 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +36 -27
  3. package/components/AtmosphereComments.astro +378 -117
  4. package/components/AtmosphereReactions.astro +145 -98
  5. package/components/Composer.astro +5 -2
  6. package/components/SignInForm.astro +65 -7
  7. package/components/atmosphere.css +267 -2
  8. package/index.d.ts +11 -0
  9. package/index.mjs +53 -32
  10. package/lib/client/account.ts +661 -0
  11. package/lib/client/composer.ts +23 -10
  12. package/lib/client/subscribe.ts +106 -0
  13. package/lib/http.ts +28 -0
  14. package/lib/permissions.ts +132 -0
  15. package/lib/public-settings.ts +20 -0
  16. package/lib/quoted.ts +4 -0
  17. package/lib/settings.ts +2 -0
  18. package/lib/thread.ts +36 -6
  19. package/lib/uris.ts +39 -0
  20. package/locales/de.json +20 -4
  21. package/locales/en.json +48 -13
  22. package/locales/es.json +20 -4
  23. package/locales/fr.json +20 -4
  24. package/locales/it.json +20 -4
  25. package/locales/ja.json +20 -4
  26. package/locales/nl.json +20 -4
  27. package/locales/pt.json +20 -4
  28. package/package.json +2 -7
  29. package/routes/callback.astro +42 -0
  30. package/routes/client-metadata.json.ts +3 -2
  31. package/routes/following.ts +28 -0
  32. package/routes/identity.ts +69 -0
  33. package/routes/media.ts +7 -10
  34. package/routes/post.ts +2 -1
  35. package/routes/profile.ts +20 -0
  36. package/routes/richtext.ts +30 -0
  37. package/routes/thread.ts +23 -3
  38. package/routes/typeahead.ts +7 -23
  39. package/virtual.d.ts +5 -0
  40. package/bin/cli.mjs +0 -31
  41. package/lib/oauth.ts +0 -110
  42. package/lib/reader.ts +0 -130
  43. package/lib/store.ts +0 -46
  44. package/lib/visitor.ts +0 -42
  45. package/routes/callback.ts +0 -30
  46. package/routes/comment.ts +0 -97
  47. package/routes/delete.ts +0 -41
  48. package/routes/follow.ts +0 -71
  49. package/routes/jwks.json.ts +0 -13
  50. package/routes/login.ts +0 -58
  51. package/routes/logout.ts +0 -20
  52. package/routes/me.ts +0 -12
  53. package/routes/react.ts +0 -48
  54. package/routes/state.ts +0 -24
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 (7 November 2026)
4
+
5
+ **Buttons for everyone, and Standard.site support for readers.** The buttons and sign-in change applies to every site; Subscribe and recommending are optional.
6
+
7
+ - **Signed-out readers see the same buttons:** Like, Repost, Quote and a "Write a comment…" box show under every post. Pressing one opens a sign-in overlay that says what it's for, and what they pressed is finished once they're back. The sign-in form and "Reply on Bluesky instead" moved into that overlay, with the detail on how signing in works behind a "How does signing in work?" toggle. New keys `signIn.more`, `comment.signIn`. In `AtmosphereReactions`, Reply and Quote now open it too, rather than going to Bluesky (⌘-click still does). Sites that style `.atmo__signed-out` should check their rules: it's gone. `signIn.heading` and `signInFirst.…` wording changed, and the `label` prop now sets the overlay's heading when commenting.
8
+
9
+ - **`standardSite` option:** `{ publication: "at://did:…/site.standard.publication/…" }`. The package still doesn't publish Standard.site records; it works with any tool that does.
10
+ - **Subscribe:** a button under each post, with Like and Repost (`.atmo-subscribe`), writes a `site.standard.graph.subscription` record to the reader's own account. Anyone not signed in gets the sign-in form and is subscribed once they're back. Only your publication can be subscribed to. Anywhere else, with `AtmosphereReactions` in your layout, a `<button data-atmosphere-subscribe hidden>` becomes Subscribe / ✓ Subscribed.
11
+ - **Menu beside Subscribe:** a line on what subscribing does, Copy link, RSS feed (with the option's `feed`), View on Bluesky and View on Standard Reader.
12
+ - **Like also recommends:** with the option set, Like on a post whose page has `<link rel="site.standard.document">` also writes a `site.standard.graph.recommend` record, and un-liking removes it. Likes on comments and on the posts `AtmosphereReactions` shows stay Bluesky likes. Recommend counts aren't shown yet.
13
+ - **Signing in shows it's started:** the Sign in button is disabled with a spinner from the moment a reader presses it, presses Enter or picks a suggested account, until the browser leaves for their account host. New class `.atmo__spinner`.
14
+ - **Quote preview closer to Bluesky:** it shows the quoted post's age and the card's divider with the domain, and leaves out empty text.
15
+ - **One approval:** whichever button a reader presses, signing in asks for everything at once. Standard.site's permission (`include:site.standard.authSocial`) is part of the first sign-in, with no second trip to the account host.
16
+
17
+ ### Upgrading from 0.5
18
+
19
+ - **If you set `standardSite`, readers already signed in are signed out once** and sign in again, so everything is approved together. Without the option, sign-ins carry on as they were.
20
+ - New wording keys (`subscribe.…`, `signInFirst.likePost`, `signInFirst.repostPost`, `signInFirst.quotePost`, `comment.prompt`), in English until they're translated.
21
+
22
+ ## 0.5.0 (4 November 2026)
23
+
24
+ **Readers' sign-ins now stay in their own browsers.** This changes how the package works, so read "Upgrading" below.
25
+
26
+ - **Browser sign-in:** readers sign in as a browser ("public") OAuth client, with `@atproto/oauth-client-browser`. Their sign-in is kept in their browser's storage, with a key the browser won't let even the page's code read out, and comments, likes, reposts, deletes and follows go from their browser straight to their own account host. Your server never sees a sign-in, sets no cookies and stores nothing, so it has no way to post for anyone. Signing out cancels the sign-in at the reader's host and deletes it from their browser. Account hosts give browser sign-ins a shorter life (Bluesky's last about two weeks).
27
+ - **One approval for everything:** signing in asks for all that readers can do here at once (posting; liking and reposting; deleting their comments; following your accounts), plus hiding comments when the account is one of your own (`allowedAuthors`). Deleting and following were asked for separately before; with the sign-in in the browser, separate trips to the account host only confused readers. Permissions are grouped in `lib/permissions.ts`. Readers whose sign-in lacks a group (one from before it was added) are asked for it when it's first needed, after the page says what it's for, which account, and that their account host may ask them to sign in again first. `SignInForm` takes a `permissions` prop for anything more.
28
+ - **Hide comments:** signed in as the account that posted the announcement, a Hide button under other people's comments hides them here and on Bluesky (the announcement's threadgate, as the Bluesky app does). The first hide asks for that permission. Show them again in the Bluesky app. Fires `atmosphere:hidden` (`detail`: `uri`).
29
+ - **Respecting "logged-out" privacy:** accounts that use Bluesky's "Discourage apps from showing my account to logged-out users" are no longer shown on the page, which is public. Their comments become a short "Not shown here" placeholder (nothing about them reaches the page) with the replies under them kept, and they're left out of the like and repost faces. New wording key `comment.withheld`; style `.atmo-comment__withheld`.
30
+ - **Fewer third parties:** account lookups while signing in go through your server (`/atmosphere/identity`), so readers' browsers contact only their own account host.
31
+ - Under the sign-in button, a line says the sign-in stays in the browser, with a link to your privacy policy if you set `policyUri`.
32
+ - Routes: `login`, `logout`, `me`, `comment`, `react`, `delete`, `follow`, `state` and `jwks.json` are gone. New: `identity`, `profile`, `richtext` (links and mentions in a comment, before it's posted) and `following`; `callback` is now a page; `POST /atmosphere/thread` refreshes a thread after a reader posts. The `typeahead` limits are per address for everyone.
33
+ - The `keygen` command is gone: there's no key.
34
+ - New wording keys (`signIn.access`, `signIn.policy`, `composer.needsPermission`, `comment.hide…`, `comment.deleteElsewhere`, `permission.…`, `callback.signingIn`) in all eight languages; `messages.newPermissions` is gone and `messages.needsPermission` is now general.
35
+
36
+ ### Upgrading from 0.4
37
+
38
+ - Remove `ATMOSPHERE_PRIVATE_JWK` and `ATMOSPHERE_DATA_DIR` from your server's environment (and your deploy's secrets), and delete the data folder (default `.data/atmosphere`). The sign-ins in it can't be used without it: their tokens only work with keys kept in those files.
39
+ - **Readers will need to sign in again.** Their old sign-ins (made with the server key) may still be listed at their account host until they expire; they can remove them there.
40
+ - If you set a Content Security Policy, allow `connect-src` to readers' account hosts (any `https:` address), since the browser now talks to them directly.
41
+ - The site no longer needs a disk that persists.
42
+
3
43
  ## 0.4.2 (4 November 2026)
4
44
 
5
45
  - Links open in a new tab: those in the sign-in form, "Reply on Bluesky instead", the credit, and the thread's names, dates, faces and links in comments, so readers don't lose their place (and a half-written comment).
package/README.md CHANGED
@@ -10,6 +10,8 @@ Under each post, readers type their Bluesky handle (or that of any other [Atmosp
10
10
  - **@mention** people, with suggestions as they type;
11
11
  - **delete** their own comments.
12
12
 
13
+ And signed in as one of your own accounts, you can **hide** comments under your posts, here and on Bluesky.
14
+
13
15
  Everything they post is written to **their own account**, as ordinary Bluesky posts, likes and reposts: comments are replies to your post's announcement on Bluesky. They can delete them in any Bluesky app, and people who prefer the app can reply there instead; it all shows up on your page the same way.
14
16
 
15
17
  The component also **shows the thread**: who liked and reposted your post, and the comments, nested, with their own Reply, Like and Repost.
@@ -20,17 +22,17 @@ See it on [saltedlolly.com](https://saltedlolly.com/2005/07/13/summer-jam-party-
20
22
 
21
23
  1. You announce each post on Bluesky (a post linking to it). That post's thread is the comment section.
22
24
  2. A reader signs in with **AT Protocol OAuth, straight to their own account host** (Bluesky's, for most people), which asks them to approve your site, named by its address (example.com). There is no third-party login service in between, and your site never sees a password.
23
- 3. Your site asks only for what commenting needs: to create posts, to create and delete likes and reposts, and to see which posts the reader has already liked or reposted. Permission to delete posts is asked for separately, only the first time a reader deletes one of their comments on your site, and the site only ever uses it on their own comments under your posts. Permission to follow is asked for the same way, the first time a reader presses Follow, and only your own accounts can be followed through it. Readers can revoke it all at any time in their Bluesky settings.
24
- 4. Your server keeps each reader's sign-in (a random cookie in their browser; their tokens on your server), and posts on their behalf when they press a button.
25
+ 3. **The sign-in stays in the reader's browser.** Your server never sees it: comments, likes and reposts go from the reader's browser straight to their own account host. So your server has no way to post for anyone, a copy of your server's files holds nothing about your readers, and when a reader signs out, the sign-in is cancelled at their host and deleted from their browser. That's it: nothing is left anywhere.
26
+ 4. Your site asks, in one approval, only for what readers can do on it: to create posts, to like and repost, to delete their comments and to follow your accounts (and, when one of your own accounts signs in, to hide comments; with the `standardSite` option, to subscribe and recommend). Each is used only on your posts, the comments under them and your accounts. Readers can see and cancel it all at their account host.
25
27
 
26
- Readers who'd rather not sign in can use the **Reply on Bluesky instead** link under the form.
28
+ Everyone sees Like, Repost, Quote and a box to write a comment. Pressing any of them signed out opens an overlay to sign in, and what they pressed is finished once they're back. Readers who'd rather not sign in can use the **Reply on Bluesky instead** link in the overlay.
27
29
 
28
- Images (avatars, link-card pictures) and account searches (the suggestions under the sign-in box and for @mentions) go through your server, so readers' browsers never contact Bluesky directly.
30
+ Images (avatars, link-card pictures), account searches (the suggestions under the sign-in box and for @mentions) and account lookups when signing in go through your server, so readers' browsers contact only their own account host, never Bluesky's other servers.
29
31
 
30
32
  ## Requirements
31
33
 
32
34
  - **Astro 7** with an on-demand (server) adapter, such as [`@astrojs/node`](https://docs.astro.build/en/guides/integrations-guide/node/). The pages themselves can stay static; the integration adds a few server routes.
33
- - **Node 20.19 or later**, with a **disk that persists** between deploys and restarts: sign-ins are stored as files. (Serverless hosts without a lasting disk aren't supported yet.)
35
+ - **Node 20.19 or later.** Nothing is stored on your server (no sign-ins, no cookies, no files), so no disk or database is needed.
34
36
  - A **Bluesky account that announces your posts**. Each post needs its announcement's address, or a [Standard.site](https://standard.site) record that points to it (see below).
35
37
  - Your site's **`site`** set in `astro.config`.
36
38
 
@@ -64,17 +66,9 @@ export default defineConfig({
64
66
 
65
67
  To find an account's DID: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=you.bsky.social`.
66
68
 
67
- ### 2. Create your site's signing key
68
-
69
- ```sh
70
- npx astro-atmosphere-comments keygen
71
- ```
72
-
73
- This adds `ATMOSPHERE_PRIVATE_JWK` to your `.env` without printing it. Your site signs its requests to readers' account hosts with it; the public half is published at `/atmosphere/jwks.json`, and the site's details for account hosts (its name, logo and permissions) at `/oauth-client-metadata.json`. That second file has to be at your site's root: it's the conventional address, and only then does Bluesky's approval page name your site by its address alone. Set the same variable on your server, keep `.env` out of Git, and keep a backup: **a new key signs every reader out.**
74
-
75
- On the server, also set `ATMOSPHERE_DATA_DIR` to a folder **outside your deploy folder** (for example `/var/lib/example-com/atmosphere`), so deploys don't sign readers out. It defaults to `.data/atmosphere` in the project.
69
+ The integration also publishes your site's details for account hosts (its name, logo and permissions) at `/oauth-client-metadata.json`. That file has to be at your site's root: it's the conventional address, and only then does Bluesky's approval page name your site by its address alone. There's no key or secret to set up.
76
70
 
77
- ### 3. Put the component under your posts
71
+ ### 2. Put the component under your posts
78
72
 
79
73
  ```astro
80
74
  ---
@@ -86,7 +80,7 @@ import AtmosphereComments from "astro-atmosphere-comments/AtmosphereComments.ast
86
80
 
87
81
  `post` can be the announcement's **bsky.app address** or its **AT URI**. Leave it out and the component uses the page's **Standard.site** record instead: if your page has `<link rel="site.standard.document" href="at://…">` and that record's `bskyPostRef` points to the announcement (as Standard.site publishers do), it just works. With no announcement to respond to, the component shows nothing (apart from your slot content).
88
82
 
89
- ### 4. Optional: likes and reposts on your other Bluesky posts
83
+ ### 3. Optional: likes and reposts on your other Bluesky posts
90
84
 
91
85
  If your pages show your Bluesky posts elsewhere (a "latest posts" feed on your homepage, say), readers can like and repost them there too. Put `AtmosphereReactions` once in your layout, and mark each post's element:
92
86
 
@@ -109,13 +103,23 @@ import AtmosphereReactions from "astro-atmosphere-comments/AtmosphereReactions.a
109
103
 
110
104
  Each marked post gets a row of icons: Reply, Repost, Quote and Like, with counts for Repost and Like. Readers already signed in on your site like and repost straight away, and Reply and Quote open the comment box in an overlay (with the quote preview); what they post goes to Bluesky, not your page, and the overlay says so with a link to it. Anyone else is offered the sign-in form in an overlay when they press Like or Repost (their like or repost is done when they come back), and Reply and Quote open the post on Bluesky. It works with posts from loaders such as [`@ascorbic/bluesky-loader`](https://www.npmjs.com/package/@ascorbic/bluesky-loader) or embeds such as [`@astro-community/astro-embed-bluesky`](https://www.npmjs.com/package/@astro-community/astro-embed-bluesky) (wrap the embed in the marked element). Only posts by your `allowedAuthors` can be liked or reposted through your site.
111
105
 
112
- A "follow me" link can become a **Follow** button for signed-in readers: mark it with the account's DID and handle. It shows "Following" once they do (a second press unfollows, as on Bluesky). The first follow asks the reader's account host for one more permission. Only your `allowedAuthors` can be followed through your site, and the account's owner (who can't follow themselves) keeps the plain link.
106
+ A "follow me" link can become a **Follow** button for signed-in readers: mark it with the account's DID and handle. It shows "Following" once they do (a second press unfollows, as on Bluesky). Only your `allowedAuthors` can be followed through your site, and the account's owner (who can't follow themselves) keeps the plain link.
113
107
 
114
108
  ```astro
115
109
  <a href="https://bsky.app/profile/you.bsky.social"
116
110
  data-atmosphere-follow="did:plc:…" data-atmosphere-follow-handle="you.bsky.social">Follow me on Bluesky</a>
117
111
  ```
118
112
 
113
+ If you publish [Standard.site](https://standard.site) records, set the integration's `standardSite` option. Readers can then **subscribe** to your publication from your site, and read new posts in apps such as Leaflet or Standard Reader. `AtmosphereComments` shows a Subscribe button under each post, with Like and Repost. To put a button anywhere else (with `AtmosphereReactions` in your layout), mark it as below; it stays hidden without the option or JavaScript. Anyone not signed in is offered the sign-in form, and subscribed once they're back; a second press unsubscribes.
114
+
115
+ ```astro
116
+ <button type="button" data-atmosphere-subscribe hidden>
117
+ <span data-atmosphere-subscribe-label>Subscribe</span>
118
+ </button>
119
+ ```
120
+
121
+ Without a `data-atmosphere-subscribe-label` element the button keeps your own content, and you show the state with `[aria-pressed="true"]` in your CSS.
122
+
119
123
  So readers can see which account they're using, `AtmosphereReactions` also fills any element marked `data-atmosphere-account` (in your header, say) with the signed-in reader's avatar. Clicking it opens a small menu: "Signed in as @handle" and Sign out. When nobody's signed in it stays empty, so give it `hidden`:
120
124
 
121
125
  ```astro
@@ -130,7 +134,7 @@ Run the dev server on `127.0.0.1` and open the site there, not at `localhost`:
130
134
  npx astro dev --host 127.0.0.1
131
135
  ```
132
136
 
133
- Locally the integration uses the protocol's development mode, so no key is needed and it works without a public address. Bluesky's approval screen will say a development app is asking; that's expected.
137
+ Locally the integration uses the protocol's development mode, so it works without a public address. Bluesky's approval screen will say a development app is asking, and sign-ins last only about a day; that's expected.
134
138
 
135
139
  ## Options
136
140
 
@@ -139,7 +143,8 @@ Locally the integration uses the protocol's development mode, so no key is neede
139
143
  | `clientName` | yes | Your site's name, given to readers' account hosts. (Bluesky's approval page shows your site's address instead, unless Bluesky has marked your site as trusted.) |
140
144
  | `allowedAuthors` | yes | DIDs of the accounts whose posts can be commented on through your site. Comments from these accounts get the Author badge. |
141
145
  | `logoUri` | | A square logo for the sign-in screen (absolute `https://` address). |
142
- | `policyUri`, `tosUri` | | Your privacy policy and terms, shown on the sign-in screen. |
146
+ | `policyUri`, `tosUri` | | Your privacy policy and terms, shown on the sign-in screen. The privacy policy is also linked under the sign-in form. |
147
+ | `standardSite` | | `{ publication: "at://did:…/site.standard.publication/…", feed: "/feed/" }`, your [Standard.site](https://standard.site) publication and, optionally, your feed for the Subscribe menu (Copy link, RSS feed, View on Bluesky, View on Standard Reader). Turns on the Subscribe button (above), and makes Like under a post also recommend the post's record, when its page has `<link rel="site.standard.document">` (the button's tooltip says so); un-liking removes the recommend. Likes on comments stay Bluesky likes. Adds Standard.site's permission to the one approval. This package doesn't publish the records: use your own script or a publishing tool. |
143
148
  | `hidden` | | Comments never to show: bsky.app post or profile addresses, AT URIs, handles or DIDs. Replies you hide in the Bluesky app, and posts with Bluesky's moderation labels (spam and so on), are left out anyway. |
144
149
  | `base` | | Where the integration's routes live. Default `/atmosphere`. |
145
150
  | `locale` | | The language of the wording, such as `"fr"` or `"pt-BR"` (see Languages). |
@@ -150,16 +155,16 @@ Locally the integration uses the protocol's development mode, so no key is neede
150
155
  | Prop | What it does |
151
156
  | --- | --- |
152
157
  | `post` | The announcement: bsky.app address or AT URI. Optional with Standard.site (above). |
153
- | `label` | The sign-in form's heading. Default "Sign in to leave a comment, like or repost". |
158
+ | `label` | The sign-in overlay's heading when commenting. Default "Sign in to leave a comment". |
154
159
  | `authorLabel` | The badge on your own comments. Default "Author". |
155
- | `blueskyLink` | "Rather not sign in here? Reply on Bluesky instead →" under the sign-in form, linking to the announcement (with `u-syndication`, so microformats readers learn it's the post's copy on Bluesky). Default `true`; `blueskyLink={false}` hides it. |
160
+ | `blueskyLink` | "Rather not sign in here? Reply on Bluesky instead →" in the sign-in overlay, linking to the announcement (with `u-syndication`, so microformats readers learn it's the post's copy on Bluesky). Default `true`; `blueskyLink={false}` hides it. |
156
161
  | `locale`, `messages` | The same as the integration's options, for this one component. |
157
162
  | `locale`, `messages` (also on `AtmosphereReactions`) | See Languages. |
158
163
  | `credit` | A small "Powered by Atmosphere Comments for Astro" link at the end. Default `true`; set `credit={false}` to hide it. |
159
164
 
160
165
  **Slots:** `after-box` goes between the comment box and the comments (a note about other ways to respond, say); the default slot goes after the comments (other responses, older comments).
161
166
 
162
- **Layout, top to bottom:** likes and reposts, the sign-in form and the "Reply on Bluesky instead" line (or, signed in, Like, Repost, Quote and the comment box), your `after-box` content, the comments, your default slot content.
167
+ **Layout, top to bottom:** likes and reposts, Like, Repost, Quote and the comment box, your `after-box` content, the comments, your default slot content.
163
168
 
164
169
  ## Languages
165
170
 
@@ -207,17 +212,21 @@ The component fires (bubbling):
207
212
  - `atmosphere:commented` after a comment, reply or quote is posted (`detail`: `uri`, `url`, `text`, `author`, `createdAt`, `quote`, `parent`);
208
213
  - `atmosphere:reacted` after a like or repost of the post, or undoing one (`detail`: `kind`, `on`, `author`).
209
214
  - `atmosphere:deleted` after a reader deletes one of their comments (`detail`: `uri`).
215
+ - `atmosphere:hidden` after the announcement's author hides a comment (`detail`: `uri`).
210
216
 
211
217
  ## Security and privacy
212
218
 
213
- - **What your server stores:** for each signed-in reader, their DID and their OAuth tokens (which can only post, like and repost as described above, and delete posts if the reader allowed it), plus a random cookie value. Files are readable only by your site's user. Mention this in your privacy policy.
214
- - **What it refuses:** requests that change something must come from your own pages (checked by `Origin`); only your announcements and comments in their threads can be acted on; only a reader's own replies under your announcements can be deleted; comments, likes and searches are rate-limited per account, and searches by readers who aren't signed in per address and overall; the image route only passes on avatars and link pictures it has just shown.
215
- - **Moderation:** comments appear automatically. Hide one in the Bluesky app (it disappears here too), block the account, or add it to `hidden`.
216
- - Comments are plain text, as on Bluesky: links, @mentions and #hashtags become links.
219
+ - **Readers' sign-ins stay in their browsers.** The OAuth library (`@atproto/oauth-client-browser`) keeps the tokens in the browser's storage, together with a key the browser makes and won't let even the page's own code read out; the tokens only work with that key. Your server never receives them, sets no cookies and stores nothing about readers. Signing out cancels the sign-in at the reader's account host and deletes it from the browser.
220
+ - **What that does and doesn't promise.** Your server can't act for anyone, ever. While a reader has your page open, though, your page's code can use their sign-in, as with any web app they sign in to, so the code that runs matters: it's this package's open source code, and comments are only ever shown as plain text (links, @mentions and #hashtags become links; nothing is rendered as HTML). A [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP) on your site adds a second line of defence.
221
+ - **What's checked before anything is sent** (by the browser code, `lib/client/account.ts`): only your announcements and the comments under them can be replied to, liked, reposted or quoted; only a reader's own replies under your announcements can be deleted; only your `standardSite` publication can be subscribed to, and only documents in its account recommended; only your `allowedAuthors` can be followed; only an announcement's author can hide comments under it.
222
+ - **What your server does:** it shows the threads, looks up accounts and suggestions, finds the links and mentions in a comment before it's posted, and passes on avatars and link pictures it has just shown. None of it needs to know who a reader is. Each route that calls Bluesky is rate-limited per address, with an overall cap; requests that aren't simple reads must come from your own pages (checked by `Origin`). Readers' addresses are used only for those limits, never stored.
223
+ - **Trade-off:** account hosts trust browser sign-ins less than ones kept on a server, so they last less long (Bluesky's currently last about two weeks), and there's no way for a site to sign everyone out at once. For a reader who'd rather not trust your server, that's the point.
224
+ - **People who'd rather not be shown publicly:** Bluesky's setting "Discourage apps from showing my account to logged-out users" is respected, as your page is public. A comment from such an account shows only as "Not shown here…" (no name, picture or words reach the page), the replies under it stay, and the account is left out of the faces of who liked and reposted.
225
+ - **Moderation:** comments appear automatically. Hide one with Hide under it (signed in as the account that posted the announcement) or in the Bluesky app; it disappears here too. Show it again in the Bluesky app. You can also block the account, or add it to `hidden`.
217
226
 
218
227
  ## Limitations (for now)
219
228
 
220
- - Needs a Node server with a lasting disk (no serverless or edge hosts yet).
229
+ - Needs an on-demand (server) adapter, for the few routes above. Serverless hosts should work, as nothing is stored, but haven't been tried.
221
230
  - Rate limits are kept in memory, so they apply per server process.
222
231
  - Quoting individual comments isn't supported.
223
232