Barazo default frontend barazo.forum
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(types): align frontend with lexicon v0.3.0 (#200)

* fix(plugins): defensive null checks for settingsSchema and dependents

The API may not return settingsSchema or dependents fields. Use
optional chaining and nullish coalescing to prevent Object.keys()
and .length crashes on undefined values.

* feat(plugins): add toast notifications for plugin actions

Show toast confirmations when enabling, disabling, saving settings,
or uninstalling plugins. Uses existing useToast hook matching the
pattern from admin pages.

* feat(types): align frontend types with lexicon v0.3.0

- Rename Topic.createdAt → publishedAt (also in CreateTopicResponse,
SearchResult)
- Remove contentFormat from Topic and Reply types
- Add optional site field to Topic type
- Update all mock data, MSW handlers, components, and tests

authored by

Guido X Jansen and committed by
GitHub
8202d099 0473f9b6

+40 -51
+2 -2
src/app/[handle]/[rkey]/page.tsx
··· 74 74 title: topic.title, 75 75 description, 76 76 type: 'article', 77 - publishedTime: topic.createdAt, 77 + publishedTime: topic.publishedAt, 78 78 }, 79 79 } 80 80 : {}), ··· 184 184 '@type': 'Person', 185 185 identifier: topic.authorDid, 186 186 }, 187 - datePublished: topic.createdAt, 187 + datePublished: topic.publishedAt, 188 188 dateModified: topic.lastActivityAt, 189 189 commentCount: topic.replyCount, 190 190 interactionStatistic: {
+2 -2
src/app/search/page.test.tsx
··· 113 113 communityDid: 'did:plc:community', 114 114 replyCount: 5, 115 115 reactionCount: 12, 116 - createdAt: '2026-02-14T00:00:00Z', 116 + publishedAt: '2026-02-14T00:00:00Z', 117 117 rank: 0.95, 118 118 rootUri: null, 119 119 rootTitle: null, ··· 168 168 communityDid: 'did:plc:community', 169 169 replyCount: null, 170 170 reactionCount: 4, 171 - createdAt: '2026-02-14T00:00:00Z', 171 + publishedAt: '2026-02-14T00:00:00Z', 172 172 rank: 0.8, 173 173 rootUri: 'at://did:plc:user/forum.barazo.topic.post/abc', 174 174 rootTitle: 'Original Topic',
+8 -8
src/app/sitemap.test.ts
··· 60 60 authorDid: 'did:plc:author1', 61 61 title: 'Hello World', 62 62 content: 'First post', 63 - contentFormat: null, 64 63 category: 'general', 64 + site: null, 65 65 tags: null, 66 66 communityDid: 'did:plc:test', 67 67 cid: 'bafyabc', ··· 75 75 pinnedAt: null, 76 76 categoryMaturityRating: 'safe' as const, 77 77 lastActivityAt: '2025-06-15T12:00:00Z', 78 - createdAt: '2025-06-01T00:00:00Z', 78 + publishedAt: '2025-06-01T00:00:00Z', 79 79 indexedAt: '2025-06-01T00:00:00Z', 80 80 }, 81 81 { ··· 84 84 authorDid: 'did:plc:author2', 85 85 title: 'Second Topic', 86 86 content: 'Another post', 87 - contentFormat: null, 88 87 category: 'general', 88 + site: null, 89 89 tags: null, 90 90 communityDid: 'did:plc:test', 91 91 cid: 'bafydef', ··· 99 99 pinnedAt: null, 100 100 categoryMaturityRating: 'safe' as const, 101 101 lastActivityAt: '2025-06-10T08:00:00Z', 102 - createdAt: '2025-06-10T00:00:00Z', 102 + publishedAt: '2025-06-10T00:00:00Z', 103 103 indexedAt: '2025-06-10T00:00:00Z', 104 104 }, 105 105 ], ··· 223 223 authorDid: 'did:plc:author1', 224 224 title: 'Safe Topic', 225 225 content: 'Safe content', 226 - contentFormat: null, 227 226 category: 'general', 227 + site: null, 228 228 tags: null, 229 229 communityDid: 'did:plc:test', 230 230 cid: 'bafysafe', ··· 238 238 pinnedAt: null, 239 239 categoryMaturityRating: 'safe' as const, 240 240 lastActivityAt: '2025-06-15T12:00:00Z', 241 - createdAt: '2025-06-01T00:00:00Z', 241 + publishedAt: '2025-06-01T00:00:00Z', 242 242 indexedAt: '2025-06-01T00:00:00Z', 243 243 }, 244 244 { ··· 247 247 authorDid: 'did:plc:author2', 248 248 title: 'Adult Topic', 249 249 content: 'Adult content', 250 - contentFormat: null, 251 250 category: 'adult-zone', 251 + site: null, 252 252 tags: null, 253 253 communityDid: 'did:plc:test', 254 254 cid: 'bafyadult', ··· 262 262 pinnedAt: null, 263 263 categoryMaturityRating: 'adult' as const, 264 264 lastActivityAt: '2025-06-10T08:00:00Z', 265 - createdAt: '2025-06-10T00:00:00Z', 265 + publishedAt: '2025-06-10T00:00:00Z', 266 266 indexedAt: '2025-06-10T00:00:00Z', 267 267 }, 268 268 ],
-1
src/components/reply-branch.test.tsx
··· 61 61 avatarUrl: null, 62 62 }, 63 63 content: `Reply ${overrides.uri}`, 64 - contentFormat: null, 65 64 rootUri: TOPIC_URI, 66 65 rootCid: 'bafyrei-root', 67 66 parentCid: 'bafyrei-parent',
+1 -1
src/components/search-result-card.tsx
··· 53 53 )} 54 54 55 55 <div className="mt-2 flex items-center gap-3 text-xs text-muted-foreground"> 56 - <span>{formatDate(result.createdAt)}</span> 56 + <span>{formatDate(result.publishedAt)}</span> 57 57 <span className="flex items-center gap-1"> 58 58 <Heart size={12} aria-hidden="true" /> 59 59 {result.reactionCount}
+2 -2
src/components/topic-view.test.tsx
··· 43 43 const baseTopic = topic 44 44 const editedTopic = { 45 45 ...baseTopic, 46 - indexedAt: new Date(new Date(baseTopic.createdAt).getTime() + 60_000).toISOString(), 46 + indexedAt: new Date(new Date(baseTopic.publishedAt).getTime() + 60_000).toISOString(), 47 47 } 48 48 49 49 const mockReactions = [ ··· 203 203 }) 204 204 205 205 describe('edited indicator', () => { 206 - it('shows "(edited)" when indexedAt is more than 30s after createdAt', () => { 206 + it('shows "(edited)" when indexedAt is more than 30s after publishedAt', () => { 207 207 render(<TopicView topic={editedTopic} />) 208 208 expect(screen.getByText('(edited)')).toBeInTheDocument() 209 209 })
+2 -2
src/components/topic-view.tsx
··· 140 140 <span>{topic.author?.displayName ?? topic.author?.handle ?? topic.authorDid}</span> 141 141 </Link> 142 142 <span aria-hidden="true">·</span> 143 - <time dateTime={topic.createdAt}>{formatRelativeTime(topic.createdAt)}</time> 144 - {isEdited(topic.createdAt, topic.indexedAt) && ( 143 + <time dateTime={topic.publishedAt}>{formatRelativeTime(topic.publishedAt)}</time> 144 + {isEdited(topic.publishedAt, topic.indexedAt) && ( 145 145 <span 146 146 className="text-muted-foreground" 147 147 title={`Edited ${new Date(topic.indexedAt).toLocaleString()}`}
+4 -5
src/lib/api/types.ts
··· 113 113 author?: AuthorProfile 114 114 title: string 115 115 content: string 116 - contentFormat: string | null 117 116 category: string 117 + site: string | null 118 118 tags: string[] | null 119 119 communityDid: string 120 120 cid: string ··· 128 128 pinnedAt: string | null 129 129 categoryMaturityRating: MaturityRating 130 130 lastActivityAt: string 131 - createdAt: string 131 + publishedAt: string 132 132 indexedAt: string 133 133 } 134 134 ··· 164 164 category: string 165 165 authorHandle: string 166 166 moderationStatus: ModerationStatus 167 - createdAt: string 167 + publishedAt: string 168 168 } 169 169 170 170 // --- Replies --- ··· 175 175 authorDid: string 176 176 author?: AuthorProfile 177 177 content: string 178 - contentFormat: string | null 179 178 rootUri: string 180 179 rootCid: string 181 180 parentUri: string ··· 249 248 communityDid: string 250 249 replyCount: number | null 251 250 reactionCount: number 252 - createdAt: string 251 + publishedAt: string 253 252 rank: number 254 253 rootUri: string | null 255 254 rootTitle: string | null
-1
src/lib/build-reply-tree.test.ts
··· 17 17 rkey: overrides.uri.split('/').pop()!, 18 18 authorDid: 'did:plc:user-001', 19 19 content: 'Test reply', 20 - contentFormat: null, 21 20 rootUri: TOPIC_URI, 22 21 rootCid: TOPIC_CID, 23 22 parentCid: 'bafyreir0',
+17 -25
src/mocks/data.ts
··· 254 254 author: mockAuthorProfiles[0]!, 255 255 title: 'Welcome to Barazo Forums', 256 256 content: 'This is the first topic on our new federated forum platform.', 257 - contentFormat: null, 258 257 category: 'general', 258 + site: null, 259 259 tags: ['welcome', 'introduction'], 260 260 communityDid: COMMUNITY_DID, 261 261 cid: 'bafyreib1', ··· 269 269 pinnedAt: null, 270 270 categoryMaturityRating: 'safe', 271 271 lastActivityAt: NOW, 272 - createdAt: TWO_DAYS_AGO, 272 + publishedAt: TWO_DAYS_AGO, 273 273 indexedAt: TWO_DAYS_AGO, 274 274 }, 275 275 { ··· 279 279 author: mockAuthorProfiles[1]!, 280 280 title: 'Building with the AT Protocol', 281 281 content: 'A deep dive into building applications on the AT Protocol.', 282 - contentFormat: null, 283 282 category: 'development', 283 + site: null, 284 284 tags: ['atproto', 'tutorial'], 285 285 communityDid: COMMUNITY_DID, 286 286 cid: 'bafyreib2', ··· 294 294 pinnedAt: null, 295 295 categoryMaturityRating: 'safe', 296 296 lastActivityAt: YESTERDAY, 297 - createdAt: TWO_DAYS_AGO, 297 + publishedAt: TWO_DAYS_AGO, 298 298 indexedAt: TWO_DAYS_AGO, 299 299 }, 300 300 { ··· 304 304 author: mockAuthorProfiles[2]!, 305 305 title: 'Feature Request: Dark Mode Improvements', 306 306 content: 'I would love to see more theme customization options.', 307 - contentFormat: null, 308 307 category: 'feedback', 308 + site: null, 309 309 tags: ['feature-request', 'ui'], 310 310 communityDid: COMMUNITY_DID, 311 311 cid: 'bafyreib3', ··· 319 319 pinnedAt: null, 320 320 categoryMaturityRating: 'safe', 321 321 lastActivityAt: YESTERDAY, 322 - createdAt: YESTERDAY, 322 + publishedAt: YESTERDAY, 323 323 indexedAt: YESTERDAY, 324 324 }, 325 325 { ··· 329 329 author: mockAuthorProfiles[3]!, 330 330 title: 'Understanding Portable Identity', 331 331 content: 'How does identity work across federated services?', 332 - contentFormat: null, 333 332 category: 'general', 333 + site: null, 334 334 tags: ['identity', 'federation'], 335 335 communityDid: COMMUNITY_DID, 336 336 cid: 'bafyreib4', ··· 344 344 pinnedAt: null, 345 345 categoryMaturityRating: 'safe', 346 346 lastActivityAt: NOW, 347 - createdAt: YESTERDAY, 347 + publishedAt: YESTERDAY, 348 348 indexedAt: YESTERDAY, 349 349 }, 350 350 { ··· 354 354 author: mockAuthorProfiles[4]!, 355 355 title: 'Self-Hosting Guide', 356 356 content: 'Step-by-step guide to running your own Barazo instance.', 357 - contentFormat: null, 358 357 category: 'meta', 358 + site: null, 359 359 tags: ['self-hosting', 'guide'], 360 360 communityDid: COMMUNITY_DID, 361 361 cid: 'bafyreib5', ··· 369 369 pinnedAt: null, 370 370 categoryMaturityRating: 'safe', 371 371 lastActivityAt: NOW, 372 - createdAt: NOW, 372 + publishedAt: NOW, 373 373 indexedAt: NOW, 374 374 }, 375 375 ] ··· 382 382 authorDid: mockUsers[2]!.did, 383 383 title: '[Deleted by author]', 384 384 content: '', 385 - contentFormat: null, 386 385 category: 'general', 386 + site: null, 387 387 tags: null, 388 388 communityDid: COMMUNITY_DID, 389 389 cid: 'bafyreib6', ··· 397 397 pinnedAt: null, 398 398 categoryMaturityRating: 'safe', 399 399 lastActivityAt: YESTERDAY, 400 - createdAt: YESTERDAY, 400 + publishedAt: YESTERDAY, 401 401 indexedAt: YESTERDAY, 402 402 } 403 403 ··· 407 407 authorDid: mockUsers[3]!.did, 408 408 title: '[Removed by moderator]', 409 409 content: '', 410 - contentFormat: null, 411 410 category: 'general', 411 + site: null, 412 412 tags: null, 413 413 communityDid: COMMUNITY_DID, 414 414 cid: 'bafyreib7', ··· 422 422 pinnedAt: null, 423 423 categoryMaturityRating: 'safe', 424 424 lastActivityAt: NOW, 425 - createdAt: NOW, 425 + publishedAt: NOW, 426 426 indexedAt: NOW, 427 427 } 428 428 ··· 446 446 communityDid: COMMUNITY_DID, 447 447 replyCount: 5, 448 448 reactionCount: 12, 449 - createdAt: TWO_DAYS_AGO, 449 + publishedAt: TWO_DAYS_AGO, 450 450 rank: 0.95, 451 451 rootUri: null, 452 452 rootTitle: null, ··· 463 463 communityDid: COMMUNITY_DID, 464 464 replyCount: 8, 465 465 reactionCount: 23, 466 - createdAt: TWO_DAYS_AGO, 466 + publishedAt: TWO_DAYS_AGO, 467 467 rank: 0.82, 468 468 rootUri: null, 469 469 rootTitle: null, ··· 480 480 communityDid: COMMUNITY_DID, 481 481 replyCount: null, 482 482 reactionCount: 4, 483 - createdAt: TWO_DAYS_AGO, 483 + publishedAt: TWO_DAYS_AGO, 484 484 rank: 0.71, 485 485 rootUri: mockTopics[0]!.uri, 486 486 rootTitle: 'Welcome to Barazo Forums', ··· 557 557 authorDid: mockUsers[1]!.did, 558 558 author: mockAuthorProfiles[1]!, 559 559 content: 'Welcome! Excited to see this forum take shape.', 560 - contentFormat: null, 561 560 rootUri: TOPIC_URI, 562 561 rootCid: TOPIC_CID, 563 562 parentUri: TOPIC_URI, ··· 578 577 author: mockAuthorProfiles[2]!, 579 578 content: 580 579 'Thanks for starting this community! The AT Protocol integration is really interesting.', 581 - contentFormat: null, 582 580 rootUri: TOPIC_URI, 583 581 rootCid: TOPIC_CID, 584 582 parentUri: `at://${mockUsers[1]!.did}/forum.barazo.reply.post/3kf6aaa`, ··· 598 596 authorDid: mockUsers[0]!.did, 599 597 author: mockAuthorProfiles[0]!, 600 598 content: 'Agreed! Portable identity changes everything.', 601 - contentFormat: null, 602 599 rootUri: TOPIC_URI, 603 600 rootCid: TOPIC_CID, 604 601 parentUri: `at://${mockUsers[2]!.did}/forum.barazo.reply.post/3kf6bbb`, ··· 618 615 authorDid: mockUsers[3]!.did, 619 616 author: mockAuthorProfiles[3]!, 620 617 content: 'One question: how does content moderation work across federated instances?', 621 - contentFormat: null, 622 618 rootUri: TOPIC_URI, 623 619 rootCid: TOPIC_CID, 624 620 parentUri: TOPIC_URI, ··· 639 635 author: mockAuthorProfiles[4]!, 640 636 content: 641 637 'Great question! Each community has its own moderation policies, but the AT Protocol labeling system allows cross-community signals.', 642 - contentFormat: null, 643 638 rootUri: TOPIC_URI, 644 639 rootCid: TOPIC_CID, 645 640 parentUri: `at://${mockUsers[3]!.did}/forum.barazo.reply.post/3kf6ddd`, ··· 662 657 rkey: '3kf6fff', 663 658 authorDid: mockUsers[2]!.did, 664 659 content: '', 665 - contentFormat: null, 666 660 rootUri: TOPIC_URI, 667 661 rootCid: TOPIC_CID, 668 662 parentUri: TOPIC_URI, ··· 682 676 rkey: '3kf6ggg', 683 677 authorDid: mockUsers[3]!.did, 684 678 content: '[Removed by moderator]', 685 - contentFormat: null, 686 679 rootUri: TOPIC_URI, 687 680 rootCid: TOPIC_CID, 688 681 parentUri: TOPIC_URI, ··· 745 738 authorDid: mockUsers[userIndex]!.did, 746 739 author: mockAuthorProfiles[userIndex]!, 747 740 content, 748 - contentFormat: null, 749 741 rootUri: DEEP_TOPIC_URI, 750 742 rootCid: DEEP_TOPIC_CID, 751 743 parentUri,
+2 -2
src/mocks/handlers.ts
··· 285 285 }, 286 286 title: body.title, 287 287 content: body.content, 288 - contentFormat: null, 289 288 category: body.category, 289 + site: null, 290 290 tags: [], 291 291 communityDid: 'did:plc:test-community-123', 292 292 cid: `bafyreib-${rkey}`, 293 293 replyCount: 0, 294 294 reactionCount: 0, 295 295 lastActivityAt: now, 296 - createdAt: now, 296 + publishedAt: now, 297 297 indexedAt: now, 298 298 } 299 299 return HttpResponse.json(newTopic, { status: 201 })