commits
* fix(security): force undici >=7.24.0 to resolve GHSA-f269-vfmq-vjvj
Adds pnpm override for undici to fix HIGH severity vulnerability:
- CVE: GHSA-f269-vfmq-vjvj
- Issue: Malicious WebSocket 64-bit length overflows parser
- Path: isomorphic-dompurify > jsdom > undici
- Fixed versions: >=7.24.0
* chore: update lockfile for undici override
fix: standardize auto-add workflow, use PROJECT_BOARD_TOKEN
When text is selected in the MarkdownEditor and a URL (http/https) is
pasted, the selected text is preserved and wrapped as [text](url)
instead of being replaced. Normal paste behavior is unchanged when no
text is selected or when pasted content is not a URL.
Closes singi-labs/barazo-workspace#124
* refactor: update imports for @singi-labs/barazo-lexicons rename
Package renamed from @singi-labs/lexicons to @singi-labs/barazo-lexicons
for consistency with @singi-labs/sifa-lexicons naming convention.
Updates imports, package.json dependency, and Dockerfile filter.
* chore: regenerate lockfile for @singi-labs/barazo-lexicons rename
Admin page (/admin/rules):
- CRUD for community rules with inline form
- Drag-free reorder via up/down arrows with save button
- Version history note on edits
- Soft-delete (archive) with confirmation
Public page (/rules):
- Server-rendered page listing active rules in order
- Accessible to all visitors (no auth required)
Also adds:
- API client functions for all 6 rules endpoints
- CommunityRule types and related interfaces
- Rules nav item in admin sidebar
Closes singi-labs/barazo-workspace#97
Two issues caused the CI build to fail:
1. Turbopack can't resolve .js → .tsx imports inside node_modules
(the plugin uses ESM .js extensions required by its NodeNext config).
Fix: create .js symlinks pointing to .tsx/.ts source files after
copying the plugin into node_modules.
2. tsc follows the pnpm symlink to ../barazo-plugins where @types/react
isn't installed, causing type-check failures.
Fix: install plugin-signatures devDependencies during CI setup.
* feat(login): improve login UX with explainer and simpler labels
- Simplify "Handle or DID" label to just "Handle" (DID/URI input still
works silently)
- Remove asterisk from sole required field
- Remove technical helper text about DID/URI formats
- Add collapsible "What is an AT Protocol identity?" explainer that
educates new users without cluttering the default view
- Update placeholder to alice.bsky.social for broader recognition
Inspired by npmx.dev's onboarding pattern.
* test(login): update tests for new placeholder and explainer links
- Update placeholder assertion from jay.bsky.team to alice.bsky.social
- Handle multiple Bluesky links (explainer + PDS section) in PDS host test
loadBundledPlugins was fire-and-forget (void), so the plugin registry
Map was still empty when React re-rendered PluginSlot. By awaiting
the loader before calling setPlugins, the registry is populated before
the re-render that causes PluginSlot to read from it.
Plugin exports now point to source .ts/.tsx files. Next.js compiles
them via transpilePackages, eliminating the need for a separate tsc
build that fails without @types/react in Docker. CI simplified too —
just clone the repo, no build needed.
The plugin frontend tsc build in Docker fails because @types/react
isn't installed in the workspace context (it's a devDependency of
the plugins monorepo, not the workspace). Since we only need the
JS output for bundling, --skipLibCheck skips type validation.
The web Dockerfile didn't copy the plugins workspace package, so
Turbopack couldn't resolve @barazo/plugin-signatures during next build.
Adds the plugin package.json to the deps stage and copies source +
builds frontend types in the builder stage.
* fix(plugins): restore static import for proper bundling + add plugins to CI
The variable import path from bca0280 bypassed webpack bundling,
preventing the plugin module from being included in the client bundle.
Restores the static import so Next.js can resolve and bundle the
plugin frontend at build time.
CI now clones and builds barazo-plugins (frontend only) alongside
barazo-lexicons so TypeScript can resolve the workspace dependency.
* fix(ci): remove plugins cache to ensure fresh clone with latest exports
The cache key was based on pnpm-lock.yaml which doesn't change when
plugins repo changes. Remove caching for now so CI always gets the
latest plugin-signatures with explicit exports entries.
* fix(ci): replace plugin symlink with copy for Turbopack compatibility
Turbopack cannot follow pnpm link: symlinks outside the project root.
After pnpm install creates the symlink, replace it with a real copy
so Turbopack can resolve and bundle the plugin frontend modules.
* 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
* feat(plugins): wire up plugin frontend components and fix option labels
- Add plugin frontend loader that dynamically imports bundled plugin
register functions and wires them into the slot registry
- Call loader from PluginProvider after fetching enabled plugins
- Add @barazo/plugin-signatures as workspace dependency
- Add transpilePackages for plugin packages in next.config
- Format select option labels in SettingsField (replace underscores,
capitalize words) so "first_per_thread" displays as "First Per Thread"
* fix(plugins): use variable import path to avoid CI typecheck failure
The static import('@barazo/plugin-signatures/frontend/register') made
TypeScript resolve the module at compile time, which fails in CI where
the workspace plugin package isn't available. Using a variable path
bypasses static resolution while keeping the explicit return type.
* 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.
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.
Add optional chaining for plugin.dependencies to prevent crash
when API response omits the field.
Show a Bluesky butterfly icon with "Bluesky" label instead of the full
bsky.app/profile URL to reduce visual clutter in the AT Protocol stats
section.
apiFetch now safely handles empty response bodies by reading text first
before parsing JSON, preventing the JSON.parse error on DELETE 204/200
responses. LikeButton's error handler now treats "Not Found" during
unlike as a successful state transition instead of reverting, preventing
count inflation from repeated like/unlike cycles.
DELETE endpoints (like unlike/reaction delete) return 204 with empty body.
apiFetch was unconditionally calling response.json(), causing
"JSON.parse: unexpected end of data" errors.
* feat(reactions): disable reaction buttons on own content
Add isOwnContent prop to TopicView and ReplyCard, passed through to
ReactionBar (disabled) and LikeButton (new disabled prop). Prevents
clicking before the API 403 would reject it.
* style(reactions): fix Prettier formatting in reply-card
* feat(plugins): add marketplace browse tab and version comparison (P2.12 M3)
Add tabbed UI to admin plugins page with Installed and Browse tabs.
Browse tab searches the plugin registry and allows installing plugins.
Installed tab shows "Update available" badge when registry has a newer version.
* style: format marketplace components with prettier
* feat(plugins): update API client methods and add registry endpoints
* feat(plugins): add PluginContext provider and usePlugins hook
Provides plugin state to the component tree: fetches enabled plugins
from the API on mount, exposes isPluginEnabled/getPluginSettings
helpers, and a refreshPlugins method for admin pages to trigger
re-fetch after changes. Gracefully handles unauthenticated users
by returning an empty plugin list.
* feat(plugins): add PluginSlot component and plugin registry
* feat(plugins): wire admin plugins page to real API
* test(plugins): add tests for plugin frontend system
Tests for plugin component registry, PluginSlot component, and
PluginProvider context covering registration, rendering, error
boundaries, context propagation, and accessibility.
* feat(plugins): add PluginSlot placements across app
* fix(plugins): resolve type errors and lint issues in plugin tests
- Fix PluginSource type in test mocks ('builtin' -> 'core'/'official')
- Fix PluginSettingsSchema shape in test mocks (use empty object)
- Use mutable ref pattern for context capture in tests (avoids TS narrowing)
- Remove unused imports (screen, SlotName)
- Return safe default from usePlugins hook when no provider (SSR/test compat)
- Update admin plugins page test for new wired implementation
* style(plugins): format files with prettier
Add ThreadHoverContext so all segments of the same ancestor line
highlight together when any segment is hovered. Change chevron
color from text-border to text-muted-foreground for better contrast.
The repository-dispatch action doesn't follow GitHub org renames.
Update from barazo-forum to singi-labs to fix staging deploys.
Slate (cool/blue-tinted) clashed with the Flexoki warm tones used
across the Singi Labs design system. Sand (warm neutral) aligns with
the brand palette (#1C1B1A dark bg, #FFFCF0 light fg).
- Replace slate.css/slate-dark.css imports with sand equivalents
- Remap all --color-slate-* tokens to --color-sand-*
- Update semantic mappings and dark mode overrides
- Update hardcoded primary-foreground from #1c2024 to #21201c (sand-12)
* chore: add .claude/worktrees to gitignore
* fix(routes): support subcategory URLs with catch-all route
Change category route from [slug] to [...slug] so subcategory paths
like /c/feedback/bug-reports resolve correctly instead of returning 404.
Sitemap now generates nested paths for subcategories.
* style(routes): fix prettier formatting in category page
ThreadLine width now matches the indent step (22/16/8px) instead of
44px. Remove the separate marginLeft wrapper div -- ancestor lines
themselves provide all indentation. Fixes excessive horizontal space
consumption at deeper nesting levels.
* chore: add .claude/worktrees to gitignore
* fix(topics): show pinned topics in dedicated section on homepage
Pinned topics appeared in both Recent and Popular lists. Extract them
into a single deduplicated "Pinned Topics" section at the top and
filter them out of the other two lists.
* chore: add .claude/worktrees to gitignore
* feat(layout): add "Your Data" link to footer navigation
Link to the seed "Your Data" page (/p/your-data) from the forum footer,
positioned first in the footer nav to surface AT Protocol's public data
model prominently.
* chore: add .claude/worktrees to gitignore
* feat(web): update threading constants for redesign
Replace viewport-specific indent caps with a universal cap of 10,
viewport-specific indent steps in pixels, and a line opacity gradient.
* feat(web): add useThreadIndent hook replacing useVisualIndentCap
Returns viewport-aware indent step (px) and chevron visibility.
Desktop: 22px + chevron, tablet: 16px + chevron, mobile: 8px no chevron.
* feat(web): redesign ThreadLine with chevron and opacity support
Add CaretDown/CaretRight chevron indicators (hidden on mobile via
showChevron prop). Add opacity prop for ancestor line fade effect.
Line and chevron share hover color transition.
* feat(web): add AncestorLines component for ancestor thread lines
Renders one ThreadLine per ancestor depth with opacity fading from
right (direct parent, full opacity) to left (distant ancestor, dim).
Each line independently collapses its ancestor's sub-thread.
* feat(web): rewrite ReplyBranch with unified line system
Remove border-l nesting divs. Pass ancestor line data through
recursion so each comment renders all ancestor lines to its left.
Use pixel-based indent steps. Cap visual depth at 10.
* feat(web): wire ReplyThread to new indent hook and ReplyBranch props
Replace useVisualIndentCap with useThreadIndent. Pass indentStep
and showChevron through to ReplyBranch. Remove old hook.
overflow-x-hidden on the ForumLayout wrapper creates a new scroll
container, breaking position: sticky on the reply composer (#181).
Replace with overflow-x-clip which clips overflow without creating
a scroll context, preserving both sticky behavior and mobile
horizontal overflow prevention.
Replace fixed positioning with sticky bottom-0 so the reply bar
sticks to the viewport while scrolling but settles into document
flow at the page bottom, making the footer accessible.
The topic creation and edit pages never passed categories to TopicForm,
causing it to fall back to a hardcoded "General Discussion" entry.
Now both pages fetch the full category list from the API on mount.
* feat(types): add isPinned, isLocked, pinnedScope to Topic interface
* feat(api): add pinTopic, lockTopic, deleteTopicMod client functions
Add typed API client functions for moderation action endpoints so the
ModerationControls component can call pin, lock, and delete operations.
* feat(web): add pin icon and visual indicator to pinned TopicCard rows
* feat(web): add pinned/regular section separator to TopicList
* feat(web): wire moderation controls to API in topic detail
TopicDetailClient now derives isLocked from topic.isLocked and
determines moderator status from useAuth() role. Passes isModerator,
isPinned, isLocked, and onModerationAction to TopicView, which
renders ModerationControls with pin/lock/delete buttons.
handleModerationAction calls pinTopic, lockTopic, or deleteTopicMod
via the API client, then refreshes the page via router.refresh().
Removes the standalone isLocked prop from TopicDetailClientProps.
* feat(web): add pin scope selector (category/forum-wide) to moderation controls
Pin confirmation dialog now shows radio buttons for selecting pin scope.
Forum-wide option only visible to admins. ConfirmDialog extended with
children prop. TopicView and TopicDetailClient updated to pass isAdmin
and scope options through the action chain.
* feat(web): soft warning when 5+ topics pinned in category
* feat(auth): accept DID and AT-URI formats in login input
Login field now normalizes additional AT Protocol identifier formats:
- at://handle and at://did:... (strips at:// prefix)
- did:plc:... and did:web:... (passed through without lowercasing)
- AT-URIs with path segments (extracts authority only)
- bsky.app profile URLs with DIDs
DIDs are kept case-sensitive since the DID spec requires it.
Updates label and helper text to reflect broader input support.
* style(auth): fix prettier formatting in login tests
* feat(routing): migrate to AT Protocol-style URLs
Change topic URLs from /t/{slug}/{rkey} to /{handle}/{rkey} and profile
URLs from /u/{handle} to /profile/{handle}. This aligns with AT Protocol
conventions used by Bluesky, Frontpage, and other ecosystem projects,
and fixes a latent collision bug where rkey-only lookups could return
the wrong topic when two users share an rkey.
- Add getTopicUrl (author-scoped) and getReplyUrl helpers in format.ts
- Add getTopicByAuthorAndRkey and getReplyByAuthorAndRkey API client methods
- Restructure app router: [handle]/[rkey] replaces t/[slug]/[rkey],
profile/[handle] replaces u/[handle]
- Add reply permalink stub at [handle]/[rkey]/[replyAuthor]/[replyRkey]
- Update all components to use new URL patterns
- Add authorHandle to SearchResult and SearchSuggestion types
- Add subjectAuthorDid/Handle to Notification type
- Update mock data, MSW handlers, and all tests
- Fix JSON-LD URL bug (was using encodeURIComponent(title))
Depends on: barazo-forum/barazo-api#139
* style(formatting): fix Prettier issues in 4 files
* fix(a11y): update pa11y-ci and mobile audit URLs for new route structure
* test(mocks): add deep reply thread and subcategories to mock data
Add mockDeepReplies (15-level chain) for testing deeply nested comment
rendering on mobile. Add subcategories under Feedback and AT Protocol
in mockCategories to match barazo-web#175. Wire deep replies into MSW
handler so they appear when browsing with mock data.
* fix(replies): count all descendants in collapsed thread message
The "N replies hidden" message on collapsed threads only counted direct
children, not the full subtree. A thread with 12 nested replies would
show "1 reply hidden" when collapsed. Add countDescendants() to
recursively count all descendants and use it in the collapse indicator.
* style(replies): fix prettier formatting in reply-branch
* feat(categories): add flattenCategoryTree utility
* feat(categories): add parent category selector to admin form
* feat(categories): add drag-and-drop reordering and reparenting
* feat(categories): show hierarchy in topic category picker
* fix(deps): regenerate lockfile for pinned @dnd-kit versions
* style(categories): fix prettier formatting
* refactor(admin): replace success toasts with proximate button-state feedback
Replace far-corner toast notifications with a two-layer feedback system:
- SaveButton component cycles idle/saving/saved for form saves
- Visual changes (item disappears, dialog closes) serve as feedback
for deletions, toggles, and dialog submissions
Add useSaveState hook (idle -> saving -> saved -> idle with 2s auto-
reset timer) and SaveButton component with CheckCircle icon, aria-live
status region for screen readers, and matching admin button styling.
Update 4 hooks, 8 pages, 6 child components to use the new system.
Remove all 24 success toast calls and useToast imports from admin.
Move toast viewport to bottom-left (aligned with admin sidebar).
Remove useToast mock from 6 admin test files.
Net change: -62 lines, 21 new tests (1021 total).
* style: fix prettier formatting in 4 components
* feat(breadcrumbs): collapse to parent back-link on mobile
Mobile breadcrumbs wrapped across multiple lines on narrow screens.
Now shows a single parent back-link with CaretLeft icon (44px touch
target) on mobile, full trail on desktop. Removes redundant topic
title from visual breadcrumbs on thread pages while preserving it
in JSON-LD structured data via new jsonLdItems prop.
* fix(breadcrumbs): update tests for dual mobile/desktop render
Tests using getByText('Home') failed in CI because the mobile
back-link and desktop breadcrumb list both render the same text.
Switch to querying within the nav landmark or using getAllByRole.
TopicView rendered topic.authorDid as plain text while TopicCard and
ReplyCard already used the enriched author profile. Now shows avatar,
display name, and a clickable link to the user profile, with graceful
fallback to DID when the author profile is unavailable.
Dependabot PRs don't receive repo secrets under pull_request events,
causing DEPLOY_PAT to be empty. Switch to pull_request_target which
runs in the base branch context with full secret access. Safe because
the workflow only runs for dependabot[bot] and only executes pnpm
install -- no PR-supplied code is run.
Commits pushed with the default GITHUB_TOKEN don't trigger other
workflows (GitHub's anti-recursion guard). Switch to DEPLOY_PAT so
the lockfile fix commit triggers CI.
* feat(a11y): add required/optional indicators to all form fields
Create shared FormLabel component with aria-hidden asterisks for required
fields and "(optional)" text for optional fields. Replaces 13 raw <label>
elements across all forms. Adds HTML required attribute to required inputs.
Fixes accessibility bug in onboarding-field-input.tsx where asterisk
lacked aria-hidden. Updates test queries to use getByRole (which respects
aria-hidden) instead of getByLabelText.
* style: format with prettier
Dependabot doesn't handle pnpm catalogs correctly -- it resolves
catalog: specifiers to concrete versions in the lockfile, causing
frozen-lockfile failures in CI. This workflow detects Dependabot PRs
that touch package.json or the lockfile, regenerates pnpm-lock.yaml,
and commits the fix back to the PR branch.
Authors can now delete their own replies via a delete button in the
reply footer. Shows a confirmation dialog before calling the existing
DELETE /api/replies/:uri endpoint. Deleted replies render as tombstones.
PR #153 rebase accidentally reverted ci.yml to pre-optimization state
due to inverted --ours/--theirs semantics during git rebase. This
restores all optimizations from PRs #150-#153:
- Job timeouts on all jobs
- Composite setup action (.github/actions/setup/)
- Lexicons build caching
- Next.js build cache (shared between build and accessibility)
- Job dependency graph (lint/typecheck/test -> build -> accessibility)
- Path filters for PRs (skip CI on docs-only changes)
- --prod flag on security audit
Move Reply button from left group to right group and add permalink
icon to OP footer, matching the comment footer's split layout pattern:
read signals (reactions, likes, timestamps) left, forward actions
(link, edit, reply, report) right. Fixes WCAG 3.2.4 consistent
identification violation where Reply jumped positions between OP
and comments.
The ChatCircle icon on the original post looked like a reply button but
was a static display. Now it opens the reply composer targeting the OP,
matching the behavior of reply buttons on comment cards.
The API returns { actions: [...] } but the frontend expected { entries: [...] },
causing undefined.map() crash when switching to the Action Log tab.
* feat(pages): migrate accessibility page to CMS
- Update footer link from /accessibility to /p/accessibility
- Remove hardcoded accessibility page and test
* fix(ci): remove /accessibility from Lighthouse URLs
The hardcoded /accessibility page is replaced by the CMS page at
/p/accessibility which requires the API backend. Since the Lighthouse CI
only runs the Next.js standalone server, CMS pages can't be tested.
Accessibility coverage for CMS pages is handled by vitest-axe tests.
* fix(post): show feedback when posts are held for moderation
Previously, held posts appeared to succeed silently -- users saw
"Reply posted" or got redirected but their content never appeared.
Changes:
- Parse API error responses as JSON, show human-readable messages
instead of raw JSON strings (fixes #75)
- Add CreateTopicResponse/CreateReplyResponse types with
moderationStatus field (fixes #74)
- Show "pending review" message when topic/reply is held
- Detect "Onboarding required" errors and trigger the onboarding
modal instead of showing a raw error (fixes #76)
- Extract throwApiError() helper for consistent error handling
- Update tests for new error message format
Fixes barazo-forum/barazo-workspace#74
Fixes barazo-forum/barazo-workspace#75
Fixes barazo-forum/barazo-workspace#76
* feat(onboarding): render tosUrl as clickable link for tos_acceptance fields
Split tos_acceptance from custom_checkbox case in OnboardingFieldInput
so that when config.tosUrl is set, a "Read full Terms of Service" link
renders below the checkbox label. Opens in new tab with noopener noreferrer.
* feat(admin): add tosUrl input for tos_acceptance field type
Show a conditional "Terms of Service URL" input in the onboarding field
form when the field type is tos_acceptance. Clears config.tosUrl when
the input is emptied. Admins can now link to an external ToS document.
The admin users and plugins pages showed "API may be unreachable" errors
because their backend endpoints are planned for P3.2 and not yet
implemented. Replace with clear "Coming in P3" placeholder states.
* feat(pages): migrate accessibility page to CMS
- Update footer link from /accessibility to /p/accessibility
- Remove hardcoded accessibility page and test
* fix(ci): remove /accessibility from Lighthouse URLs
The hardcoded /accessibility page is replaced by the CMS page at
/p/accessibility which requires the API backend. Since the Lighthouse CI
only runs the Next.js standalone server, CMS pages can't be tested.
Accessibility coverage for CMS pages is handled by vitest-axe tests.
* fix(security): force undici >=7.24.0 to resolve GHSA-f269-vfmq-vjvj
Adds pnpm override for undici to fix HIGH severity vulnerability:
- CVE: GHSA-f269-vfmq-vjvj
- Issue: Malicious WebSocket 64-bit length overflows parser
- Path: isomorphic-dompurify > jsdom > undici
- Fixed versions: >=7.24.0
* chore: update lockfile for undici override
* refactor: update imports for @singi-labs/barazo-lexicons rename
Package renamed from @singi-labs/lexicons to @singi-labs/barazo-lexicons
for consistency with @singi-labs/sifa-lexicons naming convention.
Updates imports, package.json dependency, and Dockerfile filter.
* chore: regenerate lockfile for @singi-labs/barazo-lexicons rename
Admin page (/admin/rules):
- CRUD for community rules with inline form
- Drag-free reorder via up/down arrows with save button
- Version history note on edits
- Soft-delete (archive) with confirmation
Public page (/rules):
- Server-rendered page listing active rules in order
- Accessible to all visitors (no auth required)
Also adds:
- API client functions for all 6 rules endpoints
- CommunityRule types and related interfaces
- Rules nav item in admin sidebar
Closes singi-labs/barazo-workspace#97
Two issues caused the CI build to fail:
1. Turbopack can't resolve .js → .tsx imports inside node_modules
(the plugin uses ESM .js extensions required by its NodeNext config).
Fix: create .js symlinks pointing to .tsx/.ts source files after
copying the plugin into node_modules.
2. tsc follows the pnpm symlink to ../barazo-plugins where @types/react
isn't installed, causing type-check failures.
Fix: install plugin-signatures devDependencies during CI setup.
* feat(login): improve login UX with explainer and simpler labels
- Simplify "Handle or DID" label to just "Handle" (DID/URI input still
works silently)
- Remove asterisk from sole required field
- Remove technical helper text about DID/URI formats
- Add collapsible "What is an AT Protocol identity?" explainer that
educates new users without cluttering the default view
- Update placeholder to alice.bsky.social for broader recognition
Inspired by npmx.dev's onboarding pattern.
* test(login): update tests for new placeholder and explainer links
- Update placeholder assertion from jay.bsky.team to alice.bsky.social
- Handle multiple Bluesky links (explainer + PDS section) in PDS host test
* fix(plugins): restore static import for proper bundling + add plugins to CI
The variable import path from bca0280 bypassed webpack bundling,
preventing the plugin module from being included in the client bundle.
Restores the static import so Next.js can resolve and bundle the
plugin frontend at build time.
CI now clones and builds barazo-plugins (frontend only) alongside
barazo-lexicons so TypeScript can resolve the workspace dependency.
* fix(ci): remove plugins cache to ensure fresh clone with latest exports
The cache key was based on pnpm-lock.yaml which doesn't change when
plugins repo changes. Remove caching for now so CI always gets the
latest plugin-signatures with explicit exports entries.
* fix(ci): replace plugin symlink with copy for Turbopack compatibility
Turbopack cannot follow pnpm link: symlinks outside the project root.
After pnpm install creates the symlink, replace it with a real copy
so Turbopack can resolve and bundle the plugin frontend modules.
* 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
* feat(plugins): wire up plugin frontend components and fix option labels
- Add plugin frontend loader that dynamically imports bundled plugin
register functions and wires them into the slot registry
- Call loader from PluginProvider after fetching enabled plugins
- Add @barazo/plugin-signatures as workspace dependency
- Add transpilePackages for plugin packages in next.config
- Format select option labels in SettingsField (replace underscores,
capitalize words) so "first_per_thread" displays as "First Per Thread"
* fix(plugins): use variable import path to avoid CI typecheck failure
The static import('@barazo/plugin-signatures/frontend/register') made
TypeScript resolve the module at compile time, which fails in CI where
the workspace plugin package isn't available. Using a variable path
bypasses static resolution while keeping the explicit return type.
* 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.
apiFetch now safely handles empty response bodies by reading text first
before parsing JSON, preventing the JSON.parse error on DELETE 204/200
responses. LikeButton's error handler now treats "Not Found" during
unlike as a successful state transition instead of reverting, preventing
count inflation from repeated like/unlike cycles.
* feat(plugins): add marketplace browse tab and version comparison (P2.12 M3)
Add tabbed UI to admin plugins page with Installed and Browse tabs.
Browse tab searches the plugin registry and allows installing plugins.
Installed tab shows "Update available" badge when registry has a newer version.
* style: format marketplace components with prettier
* feat(plugins): update API client methods and add registry endpoints
* feat(plugins): add PluginContext provider and usePlugins hook
Provides plugin state to the component tree: fetches enabled plugins
from the API on mount, exposes isPluginEnabled/getPluginSettings
helpers, and a refreshPlugins method for admin pages to trigger
re-fetch after changes. Gracefully handles unauthenticated users
by returning an empty plugin list.
* feat(plugins): add PluginSlot component and plugin registry
* feat(plugins): wire admin plugins page to real API
* test(plugins): add tests for plugin frontend system
Tests for plugin component registry, PluginSlot component, and
PluginProvider context covering registration, rendering, error
boundaries, context propagation, and accessibility.
* feat(plugins): add PluginSlot placements across app
* fix(plugins): resolve type errors and lint issues in plugin tests
- Fix PluginSource type in test mocks ('builtin' -> 'core'/'official')
- Fix PluginSettingsSchema shape in test mocks (use empty object)
- Use mutable ref pattern for context capture in tests (avoids TS narrowing)
- Remove unused imports (screen, SlotName)
- Return safe default from usePlugins hook when no provider (SSR/test compat)
- Update admin plugins page test for new wired implementation
* style(plugins): format files with prettier
Slate (cool/blue-tinted) clashed with the Flexoki warm tones used
across the Singi Labs design system. Sand (warm neutral) aligns with
the brand palette (#1C1B1A dark bg, #FFFCF0 light fg).
- Replace slate.css/slate-dark.css imports with sand equivalents
- Remap all --color-slate-* tokens to --color-sand-*
- Update semantic mappings and dark mode overrides
- Update hardcoded primary-foreground from #1c2024 to #21201c (sand-12)
* chore: add .claude/worktrees to gitignore
* fix(routes): support subcategory URLs with catch-all route
Change category route from [slug] to [...slug] so subcategory paths
like /c/feedback/bug-reports resolve correctly instead of returning 404.
Sitemap now generates nested paths for subcategories.
* style(routes): fix prettier formatting in category page
* chore: add .claude/worktrees to gitignore
* feat(web): update threading constants for redesign
Replace viewport-specific indent caps with a universal cap of 10,
viewport-specific indent steps in pixels, and a line opacity gradient.
* feat(web): add useThreadIndent hook replacing useVisualIndentCap
Returns viewport-aware indent step (px) and chevron visibility.
Desktop: 22px + chevron, tablet: 16px + chevron, mobile: 8px no chevron.
* feat(web): redesign ThreadLine with chevron and opacity support
Add CaretDown/CaretRight chevron indicators (hidden on mobile via
showChevron prop). Add opacity prop for ancestor line fade effect.
Line and chevron share hover color transition.
* feat(web): add AncestorLines component for ancestor thread lines
Renders one ThreadLine per ancestor depth with opacity fading from
right (direct parent, full opacity) to left (distant ancestor, dim).
Each line independently collapses its ancestor's sub-thread.
* feat(web): rewrite ReplyBranch with unified line system
Remove border-l nesting divs. Pass ancestor line data through
recursion so each comment renders all ancestor lines to its left.
Use pixel-based indent steps. Cap visual depth at 10.
* feat(web): wire ReplyThread to new indent hook and ReplyBranch props
Replace useVisualIndentCap with useThreadIndent. Pass indentStep
and showChevron through to ReplyBranch. Remove old hook.
* feat(types): add isPinned, isLocked, pinnedScope to Topic interface
* feat(api): add pinTopic, lockTopic, deleteTopicMod client functions
Add typed API client functions for moderation action endpoints so the
ModerationControls component can call pin, lock, and delete operations.
* feat(web): add pin icon and visual indicator to pinned TopicCard rows
* feat(web): add pinned/regular section separator to TopicList
* feat(web): wire moderation controls to API in topic detail
TopicDetailClient now derives isLocked from topic.isLocked and
determines moderator status from useAuth() role. Passes isModerator,
isPinned, isLocked, and onModerationAction to TopicView, which
renders ModerationControls with pin/lock/delete buttons.
handleModerationAction calls pinTopic, lockTopic, or deleteTopicMod
via the API client, then refreshes the page via router.refresh().
Removes the standalone isLocked prop from TopicDetailClientProps.
* feat(web): add pin scope selector (category/forum-wide) to moderation controls
Pin confirmation dialog now shows radio buttons for selecting pin scope.
Forum-wide option only visible to admins. ConfirmDialog extended with
children prop. TopicView and TopicDetailClient updated to pass isAdmin
and scope options through the action chain.
* feat(web): soft warning when 5+ topics pinned in category
* feat(auth): accept DID and AT-URI formats in login input
Login field now normalizes additional AT Protocol identifier formats:
- at://handle and at://did:... (strips at:// prefix)
- did:plc:... and did:web:... (passed through without lowercasing)
- AT-URIs with path segments (extracts authority only)
- bsky.app profile URLs with DIDs
DIDs are kept case-sensitive since the DID spec requires it.
Updates label and helper text to reflect broader input support.
* style(auth): fix prettier formatting in login tests
* feat(routing): migrate to AT Protocol-style URLs
Change topic URLs from /t/{slug}/{rkey} to /{handle}/{rkey} and profile
URLs from /u/{handle} to /profile/{handle}. This aligns with AT Protocol
conventions used by Bluesky, Frontpage, and other ecosystem projects,
and fixes a latent collision bug where rkey-only lookups could return
the wrong topic when two users share an rkey.
- Add getTopicUrl (author-scoped) and getReplyUrl helpers in format.ts
- Add getTopicByAuthorAndRkey and getReplyByAuthorAndRkey API client methods
- Restructure app router: [handle]/[rkey] replaces t/[slug]/[rkey],
profile/[handle] replaces u/[handle]
- Add reply permalink stub at [handle]/[rkey]/[replyAuthor]/[replyRkey]
- Update all components to use new URL patterns
- Add authorHandle to SearchResult and SearchSuggestion types
- Add subjectAuthorDid/Handle to Notification type
- Update mock data, MSW handlers, and all tests
- Fix JSON-LD URL bug (was using encodeURIComponent(title))
Depends on: barazo-forum/barazo-api#139
* style(formatting): fix Prettier issues in 4 files
* fix(a11y): update pa11y-ci and mobile audit URLs for new route structure
* test(mocks): add deep reply thread and subcategories to mock data
Add mockDeepReplies (15-level chain) for testing deeply nested comment
rendering on mobile. Add subcategories under Feedback and AT Protocol
in mockCategories to match barazo-web#175. Wire deep replies into MSW
handler so they appear when browsing with mock data.
* fix(replies): count all descendants in collapsed thread message
The "N replies hidden" message on collapsed threads only counted direct
children, not the full subtree. A thread with 12 nested replies would
show "1 reply hidden" when collapsed. Add countDescendants() to
recursively count all descendants and use it in the collapse indicator.
* style(replies): fix prettier formatting in reply-branch
* feat(categories): add flattenCategoryTree utility
* feat(categories): add parent category selector to admin form
* feat(categories): add drag-and-drop reordering and reparenting
* feat(categories): show hierarchy in topic category picker
* fix(deps): regenerate lockfile for pinned @dnd-kit versions
* style(categories): fix prettier formatting
* refactor(admin): replace success toasts with proximate button-state feedback
Replace far-corner toast notifications with a two-layer feedback system:
- SaveButton component cycles idle/saving/saved for form saves
- Visual changes (item disappears, dialog closes) serve as feedback
for deletions, toggles, and dialog submissions
Add useSaveState hook (idle -> saving -> saved -> idle with 2s auto-
reset timer) and SaveButton component with CheckCircle icon, aria-live
status region for screen readers, and matching admin button styling.
Update 4 hooks, 8 pages, 6 child components to use the new system.
Remove all 24 success toast calls and useToast imports from admin.
Move toast viewport to bottom-left (aligned with admin sidebar).
Remove useToast mock from 6 admin test files.
Net change: -62 lines, 21 new tests (1021 total).
* style: fix prettier formatting in 4 components
* feat(breadcrumbs): collapse to parent back-link on mobile
Mobile breadcrumbs wrapped across multiple lines on narrow screens.
Now shows a single parent back-link with CaretLeft icon (44px touch
target) on mobile, full trail on desktop. Removes redundant topic
title from visual breadcrumbs on thread pages while preserving it
in JSON-LD structured data via new jsonLdItems prop.
* fix(breadcrumbs): update tests for dual mobile/desktop render
Tests using getByText('Home') failed in CI because the mobile
back-link and desktop breadcrumb list both render the same text.
Switch to querying within the nav landmark or using getAllByRole.
Dependabot PRs don't receive repo secrets under pull_request events,
causing DEPLOY_PAT to be empty. Switch to pull_request_target which
runs in the base branch context with full secret access. Safe because
the workflow only runs for dependabot[bot] and only executes pnpm
install -- no PR-supplied code is run.
* feat(a11y): add required/optional indicators to all form fields
Create shared FormLabel component with aria-hidden asterisks for required
fields and "(optional)" text for optional fields. Replaces 13 raw <label>
elements across all forms. Adds HTML required attribute to required inputs.
Fixes accessibility bug in onboarding-field-input.tsx where asterisk
lacked aria-hidden. Updates test queries to use getByRole (which respects
aria-hidden) instead of getByLabelText.
* style: format with prettier
Dependabot doesn't handle pnpm catalogs correctly -- it resolves
catalog: specifiers to concrete versions in the lockfile, causing
frozen-lockfile failures in CI. This workflow detects Dependabot PRs
that touch package.json or the lockfile, regenerates pnpm-lock.yaml,
and commits the fix back to the PR branch.
PR #153 rebase accidentally reverted ci.yml to pre-optimization state
due to inverted --ours/--theirs semantics during git rebase. This
restores all optimizations from PRs #150-#153:
- Job timeouts on all jobs
- Composite setup action (.github/actions/setup/)
- Lexicons build caching
- Next.js build cache (shared between build and accessibility)
- Job dependency graph (lint/typecheck/test -> build -> accessibility)
- Path filters for PRs (skip CI on docs-only changes)
- --prod flag on security audit
Move Reply button from left group to right group and add permalink
icon to OP footer, matching the comment footer's split layout pattern:
read signals (reactions, likes, timestamps) left, forward actions
(link, edit, reply, report) right. Fixes WCAG 3.2.4 consistent
identification violation where Reply jumped positions between OP
and comments.
* feat(pages): migrate accessibility page to CMS
- Update footer link from /accessibility to /p/accessibility
- Remove hardcoded accessibility page and test
* fix(ci): remove /accessibility from Lighthouse URLs
The hardcoded /accessibility page is replaced by the CMS page at
/p/accessibility which requires the API backend. Since the Lighthouse CI
only runs the Next.js standalone server, CMS pages can't be tested.
Accessibility coverage for CMS pages is handled by vitest-axe tests.
* fix(post): show feedback when posts are held for moderation
Previously, held posts appeared to succeed silently -- users saw
"Reply posted" or got redirected but their content never appeared.
Changes:
- Parse API error responses as JSON, show human-readable messages
instead of raw JSON strings (fixes #75)
- Add CreateTopicResponse/CreateReplyResponse types with
moderationStatus field (fixes #74)
- Show "pending review" message when topic/reply is held
- Detect "Onboarding required" errors and trigger the onboarding
modal instead of showing a raw error (fixes #76)
- Extract throwApiError() helper for consistent error handling
- Update tests for new error message format
Fixes barazo-forum/barazo-workspace#74
Fixes barazo-forum/barazo-workspace#75
Fixes barazo-forum/barazo-workspace#76
* feat(onboarding): render tosUrl as clickable link for tos_acceptance fields
Split tos_acceptance from custom_checkbox case in OnboardingFieldInput
so that when config.tosUrl is set, a "Read full Terms of Service" link
renders below the checkbox label. Opens in new tab with noopener noreferrer.
* feat(admin): add tosUrl input for tos_acceptance field type
Show a conditional "Terms of Service URL" input in the onboarding field
form when the field type is tos_acceptance. Clears config.tosUrl when
the input is emptied. Admins can now link to an external ToS document.
* feat(pages): migrate accessibility page to CMS
- Update footer link from /accessibility to /p/accessibility
- Remove hardcoded accessibility page and test
* fix(ci): remove /accessibility from Lighthouse URLs
The hardcoded /accessibility page is replaced by the CMS page at
/p/accessibility which requires the API backend. Since the Lighthouse CI
only runs the Next.js standalone server, CMS pages can't be tested.
Accessibility coverage for CMS pages is handled by vitest-axe tests.