@blogmcp/community 0.0.0-stage → 0.2.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 (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +243 -2
  3. package/dist/blogmcp-community.global.js +10 -0
  4. package/dist/chunks/mount-WadUnaTa.js +6436 -0
  5. package/dist/core/attrs.d.ts +99 -0
  6. package/dist/core/avatars.d.ts +16 -0
  7. package/dist/core/badge.d.ts +30 -0
  8. package/dist/core/centre.d.ts +28 -0
  9. package/dist/core/client.d.ts +27 -0
  10. package/dist/core/digest-token.d.ts +2 -0
  11. package/dist/core/discussion.d.ts +175 -0
  12. package/dist/core/download.d.ts +4 -0
  13. package/dist/core/format.d.ts +24 -0
  14. package/dist/core/i18n.d.ts +28 -0
  15. package/dist/core/names.d.ts +15 -0
  16. package/dist/core/notice.d.ts +50 -0
  17. package/dist/core/onboarding.d.ts +92 -0
  18. package/dist/core/outcomes.d.ts +29 -0
  19. package/dist/core/panel.d.ts +96 -0
  20. package/dist/core/profile.d.ts +40 -0
  21. package/dist/core/push.d.ts +63 -0
  22. package/dist/core/realtime.d.ts +35 -0
  23. package/dist/core/receipt.d.ts +5 -0
  24. package/dist/core/settings.d.ts +53 -0
  25. package/dist/core/text.d.ts +35 -0
  26. package/dist/core/turnstile.d.ts +33 -0
  27. package/dist/element.d.ts +47 -0
  28. package/dist/global.d.ts +11 -0
  29. package/dist/index.d.ts +33 -0
  30. package/dist/index.js +45 -0
  31. package/dist/mount.d.ts +105 -0
  32. package/dist/react.d.ts +28 -0
  33. package/dist/react.js +55 -0
  34. package/dist/ui/AuthorAvatar.vue.d.ts +11 -0
  35. package/dist/ui/BadgeView.vue.d.ts +13 -0
  36. package/dist/ui/ConfirmDialog.vue.d.ts +20 -0
  37. package/dist/ui/DiscussionView.vue.d.ts +44 -0
  38. package/dist/ui/LinkedText.vue.d.ts +6 -0
  39. package/dist/ui/MemberSettingsView.vue.d.ts +13 -0
  40. package/dist/ui/MessageComposer.vue.d.ts +26 -0
  41. package/dist/ui/MessageItem.vue.d.ts +6 -0
  42. package/dist/ui/MessageMenu.vue.d.ts +10 -0
  43. package/dist/ui/MyCommunityView.vue.d.ts +16 -0
  44. package/dist/ui/NoticeFormView.vue.d.ts +26 -0
  45. package/dist/ui/NoticeStatusView.vue.d.ts +15 -0
  46. package/dist/ui/NotificationCentreView.vue.d.ts +12 -0
  47. package/dist/ui/OnboardingSteps.vue.d.ts +11 -0
  48. package/dist/ui/ProfileView.vue.d.ts +13 -0
  49. package/dist/ui/ReactionBar.vue.d.ts +13 -0
  50. package/dist/ui/ReportDialog.vue.d.ts +15 -0
  51. package/dist/ui/StatementView.vue.d.ts +15 -0
  52. package/dist/ui/WelcomeView.vue.d.ts +12 -0
  53. package/dist/ui/context.d.ts +21 -0
  54. package/dist/ui/discussion-context.d.ts +19 -0
  55. package/dist/ui/types.d.ts +7 -0
  56. package/dist/version.d.ts +1 -0
  57. package/dist/vue.d.ts +200 -0
  58. package/dist/vue.js +201 -0
  59. package/package.json +80 -4
  60. package/src/core/attrs.ts +186 -0
  61. package/src/core/avatars.ts +44 -0
  62. package/src/core/badge.ts +120 -0
  63. package/src/core/centre.ts +136 -0
  64. package/src/core/client.ts +54 -0
  65. package/src/core/digest-token.ts +26 -0
  66. package/src/core/discussion.ts +876 -0
  67. package/src/core/download.ts +21 -0
  68. package/src/core/format.ts +112 -0
  69. package/src/core/i18n.ts +49 -0
  70. package/src/core/names.ts +51 -0
  71. package/src/core/notice.ts +178 -0
  72. package/src/core/onboarding.ts +492 -0
  73. package/src/core/outcomes.ts +86 -0
  74. package/src/core/panel.ts +446 -0
  75. package/src/core/profile.ts +144 -0
  76. package/src/core/push.ts +174 -0
  77. package/src/core/realtime.ts +39 -0
  78. package/src/core/receipt.ts +28 -0
  79. package/src/core/settings.ts +363 -0
  80. package/src/core/text.ts +174 -0
  81. package/src/core/turnstile.ts +98 -0
  82. package/src/element.ts +165 -0
  83. package/src/global.ts +26 -0
  84. package/src/index.ts +70 -0
  85. package/src/mount.ts +234 -0
  86. package/src/react.ts +97 -0
  87. package/src/shims.d.ts +11 -0
  88. package/src/ui/AuthorAvatar.vue +26 -0
  89. package/src/ui/BadgeView.vue +51 -0
  90. package/src/ui/ConfirmDialog.vue +38 -0
  91. package/src/ui/DiscussionView.vue +334 -0
  92. package/src/ui/LinkedText.vue +24 -0
  93. package/src/ui/MemberSettingsView.vue +175 -0
  94. package/src/ui/MessageComposer.vue +141 -0
  95. package/src/ui/MessageItem.vue +223 -0
  96. package/src/ui/MessageMenu.vue +67 -0
  97. package/src/ui/MyCommunityView.vue +378 -0
  98. package/src/ui/NoticeFormView.vue +244 -0
  99. package/src/ui/NoticeStatusView.vue +82 -0
  100. package/src/ui/NotificationCentreView.vue +159 -0
  101. package/src/ui/OnboardingSteps.vue +174 -0
  102. package/src/ui/ProfileView.vue +91 -0
  103. package/src/ui/ReactionBar.vue +41 -0
  104. package/src/ui/ReportDialog.vue +88 -0
  105. package/src/ui/StatementView.vue +141 -0
  106. package/src/ui/WelcomeView.vue +72 -0
  107. package/src/ui/context.ts +75 -0
  108. package/src/ui/discussion-context.ts +30 -0
  109. package/src/ui/styles.css +259 -0
  110. package/src/ui/types.ts +7 -0
  111. package/src/version.ts +3 -0
  112. package/src/vue.ts +242 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lukas Zubal
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,244 @@
1
- # Temporary Holding Version
1
+ # @blogmcp/community
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The community elements for your own site, over `@blogmcp/sdk/community`:
4
+
5
+ - `<blogmcp-discussion>`: the discussion under an article. A two-level thread, the composer, reactions,
6
+ reports, blocks, the inline onboarding, and the states of a member's own messages ("awaiting review",
7
+ removed, deleted). Where the tenant asks for the art. 9 consent, the onboarding asks it the way the tenant set
8
+ it in kb-api: one consent, or one per controller the consent text names, and a refusal that still lets the
9
+ member write in public where the tenant allows it. Your site passes nothing for this;
10
+ - `<blogmcp-notice-form>`: the notice form for anyone, signed in or not (DSA art. 16), with Cloudflare
11
+ Turnstile;
12
+ - `<blogmcp-my-community>`: the reader's „Moje komunita" panel. It holds statements of reasons and appeals,
13
+ reports, notifications, the consent (to withdraw it, or to give one the member refused), the export and the
14
+ profile's deletion. It also renders a statement
15
+ page (`decision-id`) and a notifier's status page (`#t=<receipt>` in the address).
16
+
17
+ With the community module (M2) there are five more:
18
+
19
+ - `<blogmcp-notification-centre>`: the member's notifications, newest first. Older ones load a page at a time,
20
+ „Jen nepřečtené" filters them, each one or all can be marked read, and each row links its subject where your
21
+ site has a page for it (a decision's statement, the settings, „Moje komunita"). It links your settings page;
22
+ - `<blogmcp-badge>`: the unread count for your header, a link to the centre. It asks once who is viewing, then
23
+ the count every 60 s while the tab is visible and again when it is shown again. It renders nothing for a
24
+ visitor, a reader who never joined, a module that is off, or nothing unread;
25
+ - `<blogmcp-member-settings>`: the six notification switches (replies, mentions, topics, browser notifications,
26
+ the daily e-mail digest, the quiet night 22:00–7:00), each saved on its own; this browser's notifications;
27
+ and the digest e-mail's unsubscribe link (below);
28
+ - `<blogmcp-profile handle="…">`: a member's public profile: name, avatar, since when they are a member, the
29
+ bio as text and the trust level as a label („Nový člen", „Člen", „Stálý člen", „Moderátor"), never a number;
30
+ - `<blogmcp-welcome>`: the page after joining: the rules in summary and three links (the discussions, the
31
+ settings, the member's own profile; a visitor gets your sign-in link instead).
32
+
33
+ `<blogmcp-discussion>` also links every `@handle` in a comment to that member's profile page (for a signed-in
34
+ reader; an anonymous one reads the mention as text), and takes live updates through its `realtime` property
35
+ (below).
36
+
37
+ Each element lives in a shadow root and renders text only. A member's links pass through `sanitizeUrl` and
38
+ carry `rel="nofollow ugc noopener noreferrer"`. Every text comes from the SDK's catalogue
39
+ (`communityMessage`), in Czech or English.
40
+
41
+ ## Your site's side: one relative base
42
+
43
+ The elements never call kb-api. They POST to **your site's** relative base: `api-base`, by default
44
+ `/api/community` under your `site-base` (the root when you set none):
45
+
46
+ - a headless site or the storefront, served at the root: the BFF from `@blogmcp/sdk/community/server`,
47
+ mounted at `/api/community`;
48
+ - a managed blog, served under `/blog`: its own `/api` proxy, so set `site-base="/blog"`. The element then
49
+ posts to `/blog/api/community`, and its default pages are `/blog/community/…`.
50
+
51
+ > **On a managed blog, never `api-base="/api/community"`.** On the tenant's host, `/api/` is kb-api itself, not
52
+ > the blog. A request there skips the blog's proxy, so it carries no reader session. Every reader reads as
53
+ > signed out, every write is refused, and the notice form reaches no route. The defaults have the same trap:
54
+ > without `site-base`, the links go to `/community/…`, outside the blog.
55
+
56
+ ```ts
57
+ // e.g. app/routes/api/community.$.ts (POST only)
58
+ import { createCommunityBff, communityServerEnv, fromWebRequest } from '@blogmcp/sdk/community/server'
59
+
60
+ const bff = createCommunityBff({
61
+ // kb-api as your server reaches it, WITH the `/api` prefix your instance serves it under:
62
+ // KB_API_URL = https://<your instance>/api (or `${process.env.BLOGMCP_API_URL}/api`).
63
+ kbApiUrl: process.env.KB_API_URL!,
64
+ siteUrls: ['https://www.example.com'],
65
+ memberToken: async (http) => /* your session → the member's JWT, or null */ null,
66
+ ...communityServerEnv(process.env), // COMMUNITY_NOTICE_KEY
67
+ })
68
+
69
+ export async function action({ request }: { request: Request }) {
70
+ const { http, respond } = fromWebRequest(request)
71
+ return respond(await bff.handle(http))
72
+ }
73
+ ```
74
+
75
+ An absolute `api-base` (or `site-base`) is refused, and the element renders nothing: kb-api's community
76
+ routes answer no CORS.
77
+
78
+ ## Mount the elements
79
+
80
+ **Vue / Nuxt**
81
+
82
+ ```vue
83
+ <script setup lang="ts">
84
+ import { KbDiscussion } from '@blogmcp/community/vue'
85
+ </script>
86
+ <template>
87
+ <article>…</article>
88
+ <KbDiscussion :article-id="article.id" api-base="/api/community" sign-in-href="/prihlaseni" />
89
+ </template>
90
+ ```
91
+
92
+ **React / Next.js**
93
+
94
+ ```tsx
95
+ import { KbDiscussion } from '@blogmcp/community/react' // a client component
96
+
97
+ <KbDiscussion articleId={article.id} apiBase="/api/community" signInHref="/sign-in" />
98
+ ```
99
+
100
+ `KbNoticeForm`, `KbMyCommunity`, `KbNotificationCentre`, `KbBadge`, `KbMemberSettings`, `KbProfile` and
101
+ `KbWelcome` work the same way. Each wrapper mounts its element after hydration and
102
+ removes it on unmount. It renders a single empty `<div data-blogmcp-community="…">` as the mount point: the
103
+ discussion is part of the page's flow, so the element goes where the wrapper is.
104
+
105
+ `mountDiscussion(o)`, `mountNoticeForm(o)`, `mountMyCommunity(o)`, `mountNotificationCentre(o)`, `mountBadge(o)`,
106
+ `mountMemberSettings(o)`, `mountProfile(o)` and `mountWelcome(o)` do the same from any client-side hook. Each
107
+ returns the unmount function.
108
+
109
+ **Plain HTML**
110
+
111
+ ```html
112
+ <blogmcp-discussion article-id="…" api-base="/api/community" sign-in-href="/prihlaseni"></blogmcp-discussion>
113
+ <script src="https://cdn.jsdelivr.net/npm/@blogmcp/community@0.2.0/dist/blogmcp-community.global.js" nonce="…"></script>
114
+ ```
115
+
116
+ The script build (`@blogmcp/community/global`) registers all eight elements. It exposes
117
+ `window.BlogmcpCommunity` and gives the elements its own `nonce`.
118
+
119
+ ## Attributes
120
+
121
+ | Element | Attribute | |
122
+ |---|---|---|
123
+ | all | `api-base` | your relative base (default `/api/community` under `site-base`) |
124
+ | all | `site-base` | the path your site is served under (`/blog` on a managed blog); the defaults go under it |
125
+ | all | `lang` | `cs` or `en` (`sk` reads Czech); default `<html lang>` |
126
+ | all | `routes` | JSON with your pages (below) |
127
+ | all | `nonce` | CSP nonce for the element's stylesheet and the Turnstile script |
128
+ | `<blogmcp-discussion>` | `article-id` | the article |
129
+ | | `sign-in-href` | your sign-in page; the current path goes in its fragment |
130
+ | | `poll-ms` | how often to check for new comments while the discussion is in view (default 20000; 0 = off) |
131
+ | `<blogmcp-notice-form>` | `target` | JSON: `{ conversationId, seq }`, `{ memberId }`, `{ handle }` or `{ articleId }` |
132
+ | | `article-id` | or the article; without either, the discussion's report link (`#c=…&s=…`). With no target at all, the element shows where a report starts and links to `rulesPath` instead of the form |
133
+ | | `turnstile-site-key` | your Turnstile site key; without it no widget renders |
134
+ | `<blogmcp-my-community>` | `decision-id` | open this statement of reasons |
135
+ | | `sign-in-href` | your sign-in page |
136
+ | `<blogmcp-notification-centre>` | `sign-in-href` | your sign-in page (a visitor's link) |
137
+ | `<blogmcp-badge>` | `poll-ms` | how often to read the count while the tab is visible (default 60000, and never less — a smaller value reads as 60000; 0 = once, and when the tab is shown again) |
138
+ | `<blogmcp-member-settings>` | `sw-path` | your service worker route (default `${site-base}/sw.js`) |
139
+ | | `sign-in-href` | your sign-in page |
140
+ | `<blogmcp-profile>` | `handle` | the member's handle (an `@` in front is fine) |
141
+ | | `sign-in-href` | your sign-in page (profiles are for signed-in readers) |
142
+ | `<blogmcp-welcome>` | `sign-in-href` | your sign-in page (in the profile link's place, for a visitor) |
143
+
144
+ `routes` names your community pages: `statementPath` (with `{id}`), `myCommunityPath`, `rulesPath`,
145
+ `reportPath`, `noticesPath` and, with the module, `profilePath` (with `{handle}`), `settingsPath`,
146
+ `notificationsPath`, `welcomePath` and `discussionsPath`. Each is a full path on your site, used as it is. A page you leave out
147
+ falls back to the managed blog's pages: `${site-base}/community/…`. `articlePath` (with `{slug}`; no default)
148
+ is your article page — the managed blogs' `/blog/articles/{slug}`, a shop's `/blog/{slug}`: the notification
149
+ centre links a reply or a mention to its message there (`…/<slug>#bcm-msg-<seq>`), and without it those rows stay
150
+ text. The discussion gives every message the anchor `bcm-msg-<seq>` and, opened at one, scrolls to it itself once
151
+ its first page is in (the rows live in a shadow root, where the browser's own jump cannot reach). The tenant's own statement and „Moje
152
+ komunita" paths, set in its community settings, come before that fallback.
153
+ `consultationPath` (no default) turns the regulated preset's adverse-event note into a link to your
154
+ consultation page.
155
+ The receipt in a status link always goes in the fragment (`#t=…`). The page clears it from the address bar
156
+ as soon as it reads it.
157
+
158
+ A `transport` property replaces the HTTP layer, for tests.
159
+
160
+ ## Live updates (`realtime`)
161
+
162
+ The discussion keeps itself fresh by polling. For live updates, import the SDK's realtime entry yourself and pass
163
+ its `createRealtimeSession` as the discussion's `realtime` property (a property, never an attribute). Your bundle
164
+ then needs `centrifuge` (≥ 5.7), the entry's peer dependency; the elements never import either, so a site
165
+ without it ships no socket code.
166
+
167
+ ```ts
168
+ import { createRealtimeSession } from '@blogmcp/sdk/community/realtime'
169
+ import { KbDiscussion } from '@blogmcp/community/react'
170
+
171
+ <KbDiscussion articleId={article.id} apiBase="/api/community" realtime={createRealtimeSession} />
172
+ // plain: document.querySelector('blogmcp-discussion').realtime = createRealtimeSession
173
+ ```
174
+
175
+ The element starts the session over its own discussion after the first page is read, and stops it when it
176
+ unmounts. It starts none for an anonymous reader, nor while the community's live updates are off (the module is
177
+ `read_only` or off, or the instance runs no realtime): the discussion polls then, with no socket. A session that
178
+ falls back to polling — kb-api answers `REALTIME_OFF`, or the server ends the connection for good — makes the
179
+ discussion say once that live updates are off; it is never reopened. When the session was revoked (4501), the
180
+ session asks kb-api for a fresh token once first: a member whose own password change ended the old session keeps
181
+ the page live under the new one; a refusal, or a second 4501, falls back for good. While the socket cannot get
182
+ through (the subscription is not live 15 s after connecting, or after the connection dropped), the discussion polls
183
+ meanwhile and says so once; Centrifuge keeps reconnecting, and the sentence goes when it is live again. An article
184
+ without comments yet has no channel to subscribe to: it polls for the first one and is never called off for that.
185
+
186
+ The composer knows the member's mention limit (`me/get`'s `caps.mentionsPerMessage`, by trust level): a comment
187
+ that mentions more distinct `@handle`s says so as it is typed and is not sent (kb-api refuses it too).
188
+
189
+ The URL to connect to comes from kb-api's token answer (the instance's own host, `/rt/…`), never from your
190
+ configuration.
191
+
192
+ ## Browser notifications and the digest's unsubscribe link
193
+
194
+ `<blogmcp-member-settings>` shows this browser's notifications only while the community offers them. „Zapnout
195
+ upozornění" (the member's click; nothing is registered on page load) registers **your** service worker at
196
+ `sw-path`, asks the member's permission, subscribes the browser and hands the subscription to kb-api; „Vypnout"
197
+ ends it. Serve the worker from `@blogmcp/sdk/community/sw` (`communityServiceWorkerSource`, as
198
+ `COMMUNITY_SW_CONTENT_TYPE`, `Cache-Control: no-cache`) at that route, and the manifest from
199
+ `@blogmcp/sdk/community/manifest`. Inside Facebook's or Instagram's app the element offers „Otevřít v prohlížeči"
200
+ with the page's URL instead; on an iPhone or iPad it explains that the site must be added to the home screen first.
201
+ After a key rotation (or kb-api's „switch it on again" notice) it asks the member to switch them on again. The
202
+ switch reads on only for a subscription this member made in this browser (a shared browser shows another member's
203
+ as off) and that kb-api still holds: with the permission granted, the element asks kb-api on load whether it still
204
+ holds this browser's endpoint (`me/push/status` — a read: no worker registered, no permission asked, nothing
205
+ subscribed). kb-api drops a subscription after a sign-out everywhere or a dead endpoint without telling the page;
206
+ the switch then reads off and stays off until the member presses „Zapnout upozornění" — a sign-out everywhere never
207
+ brings notifications back on its own. When kb-api cannot answer (the status, or the current key's `me/push/vapid` that is checked first), the switch
208
+ reads off too, without a sentence. The element also clears `#u=` from the address when
209
+ it is misconfigured and renders nothing.
210
+
211
+ The digest e-mail links to your settings page with the unsubscribe token in the fragment (`#u=…`). The element
212
+ reads it once, clears it from the address bar, and sends it only when the member clicks „Odhlásit denní souhrn"
213
+ (a mail scanner that opens the link unsubscribes nobody). That works without a session. Route your settings page
214
+ (`settingsPath`) where kb-api's e-mails point.
215
+
216
+ Events (bubbling, composed):
217
+ - `bcm-posted`, `{ state: 'visible' | 'held' }`: the reader's comment went out;
218
+ - `bcm-notice-submitted`, `{ contactKept }`: a notice was received.
219
+
220
+ ## Look
221
+
222
+ Theme the elements with the custom properties on them: `blogmcp-discussion { --bcm-accent: #2b7a4b }`.
223
+ The properties are `--bcm-bg`, `-fg`, `-muted`, `-line`, `-soft`, `-accent`, `-accent-fg`, `-danger`,
224
+ `-ok`, `-warn-bg`, `-shadow` and `-z`, plus `--bcm-font` and `--bcm-font-size`. Dark mode follows
225
+ `prefers-color-scheme`.
226
+
227
+ ## Content Security Policy
228
+
229
+ - The styles are one `<style>` in each shadow root, carrying the nonce you pass.
230
+ - `connect-src 'self'` is enough: the elements only talk to your own base. With live updates on a headless
231
+ site, add your instance's host (`https://<instance host>` and `wss://<instance host>`): the socket goes there.
232
+ - Browser notifications need `worker-src 'self'` (and `manifest-src 'self'` for the manifest).
233
+ - The notice form with Turnstile needs `script-src` and `frame-src` for `https://challenges.cloudflare.com`.
234
+ Only a page with the notice form loads that script.
235
+
236
+ ## Development
237
+
238
+ ```bash
239
+ npm run build # ESM + the <script> build (+ size budget, 200 KiB gzip)
240
+ npm test # vitest (happy-dom): controllers, store, onboarding, outcomes, elements, text-only and closed-path scans
241
+ npm run test:e2e # Playwright: desktop + iPhone profile + a plain bundler, mocked community API (ports 8286/8287);
242
+ # m2.e2e.ts drives the centre (its link to a reply's message), the badge, the settings,
243
+ # a profile, the welcome page and the composer's mention limit
244
+ ```