* feat: add ticket numbers to cards with workspace prefix
- Add cardNumber to cards
- Add cardPrefix/cardCounter to workspace
- Populate initial prefixes and numbers via new migration
- Introduce generateWorkspacePrefix util and export it
- Include cardNumber in card-related API responses and search results
- Render and display tickets as PREFIX-NUMBER in UI
- Update Card component to accept ticketNumber
- Update CardModal and board cards to show number when available
- Add cardNumber to CommandPalette search results type
* feat: regen migration
* feat: enhance card and workspace schemas with cardNumber and indexing
- Updated the card repository to allocate card numbers atomically per workspace.
- Modified the card schema to include a cardNumber field and added an index on listId and cardNumber for improved query performance.
- Enhanced the workspace schema to include an index on cardPrefix for optimized lookups.
- Adjusted the migration journal to reflect the new schema changes and their timestamps.
- Updated the regex in workspace repository to allow alphanumeric prefixes in ticket IDs.
---------
Co-authored-by: Henry <henry_ball@hotmail.co.uk>
Previously, archiving or unarchiving a board left the user on the
(now archived/unarchived) board view with no clear navigation path.
This redirects to /boards after either action, matching the existing
behaviour of delete and move operations.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two related avatar bugs:
1. getAvatarUrl() returned empty string for S3 keys, so uploaded
avatars never displayed. Now constructs the full URL using
NEXT_PUBLIC_STORAGE_URL and NEXT_PUBLIC_AVATAR_BUCKET_NAME,
with support for both path-style (MinIO) and virtual-hosted
(Tigris/AWS S3) URLs.
2. Avatar crop scaled canvas by devicePixelRatio (4x pixels on
Retina), producing blobs that exceeded the 2MB upload limit
even for small source images. Now caps output at 512x512 and
uses quality=0.85 for toBlob().
Closes#440, closes#441
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add card context menu and duplication functionality
* Implemented a context menu for cards allowing actions such as copying links, duplicating cards, and managing members, labels, and due dates
* Added modals for card duplication and context actions.
* Updated API with a new endpoint for duplicating cards, including options for copying labels, members, and checklists
* refactor: remove cardPublicId from context menu and related components
* Removed cardPublicId prop from CardContextMenu and CardContextMembersModal for cleaner context handling
* Updated scrollbar styling
* feat: add delete card functionality to context menu
* adding font size
* removing trash
* moving font menu
* chore: revert port change in compose
---------
Co-authored-by: Henry <henry_ball@hotmail.co.uk>
* feat: workspace start of week column
* feat(l10n): add workspace setting for the first day of the week
Fixes#361
* feat: add Saturday as option
* chore: fix migration order
* chore: fix merge
---------
Co-authored-by: Henry <henry_ball@hotmail.co.uk>
* fix: add 404 not found page
Closes#320
- Add custom 404 page matching the app's design system
- Include PatternedBackground and proper dark mode support
- Provide navigation links to homepage and boards
- Add i18n support with @lingui/react
* fix: add 404 redirects for non-existent boards and cards
Add useEffect hooks to redirect to /404 page when:
- Board does not exist in board view
- Card does not exist in card view
This addresses the cases mentioned in maintainer feedback where
the static 404 page wasn't being used for dynamic routes.
Addresses review feedback on PR #349
* feat: redirect to 404 for non-existent public workspaces and cards
Per maintainer feedback:
- Add redirect logic to public boards view when workspace doesn't exist
- Add redirect logic to public card modal when card doesn't exist
- Uses error?.data?.code === 'NOT_FOUND' check as suggested
- Closes modal before redirecting for card modal
* feat(api): add webhook CRUD API router and tests
Add tRPC router for managing workspace webhooks:
- list, create, update, delete endpoints (admin role required)
- test endpoint to send a synthetic payload to a webhook URL
- URL validation, event subscription filtering
- Unit tests for all router procedures
- Integration tests with PGlite test database
- Add vitest config and test infrastructure for API package
Depends on #391 (DB schema & repository).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(api): use assertPermission instead of assertUserInWorkspace
Replace assertUserInWorkspace with assertPermission("workspace:manage")
per project conventions. The permissions system is the preferred
authorization approach for new code.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): use @kan/db alias instead of relative imports in tests
Replace relative path imports (../../db/src/...) with the @kan/db
alias configured in vitest.config.ts for consistency and robustness.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(api): use webhookUrlSchema in router input validation
Cherry-pick router-related changes from b2cc9ac:
- Use extracted webhookUrlSchema zod validator in create/update
input schemas for consistent SSRF checks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(api): replace dynamic import with static import for webhook utility
Add packages/api/src/utils/webhook.ts with sendWebhookToUrl,
createCardWebhookPayload, and webhookUrlSchema. Replace the dynamic
import() in the test endpoint with a static import at the top of the
file for better tree-shaking, type-checking, and readability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): align sendWebhooksForWorkspace tests with merged PR #392
The merged delivery utility uses client-side event filtering
(getActiveByWorkspaceId takes 2 args, not 3). Update test assertions
to match the actual implementation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(web): add webhook management UI
Add settings page for managing workspace webhooks:
- Add webhooks page route and settings navigation link
- Add webhook list view with status toggles and action menus
- Add create/edit modal with URL validation and event selection
- Add delete confirmation dialog
- Add WEBHOOKS_ENABLED env flag for feature gating
Depends on #393 (CRUD API router).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): remove dead env vars, extract TableRow, import webhookEvents
- Remove unused WEBHOOK_URL and WEBHOOK_SECRET env vars (leftovers
from earlier env-var-based design)
- Move TableRow component outside WebhookList to avoid re-creation
on every render
- Import webhookEvents from @kan/db/schema instead of hardcoding
- Simplify formatDate to only handle Date objects (strings are not
returned by tRPC/Superjson)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): gate webhooks settings tab to admin role
The webhook API requires admin role, but the settings tab was visible
to all users (condition: true). Now matches the API's authorization
requirement, addressing reviewer feedback on PR #394.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(web): use webhookEvents constant for form defaults
Replace hardcoded event arrays with [...webhookEvents] in
NewWebhookModal so default values stay in sync if new events
are added to the schema.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(web): use date-fns with locale for webhook date formatting
Replace hardcoded toLocaleDateString('en-US') with date-fns format()
using the useLocalisation() hook's dateLocale, matching the pattern
used throughout the codebase (ActivityList, DateSelector, etc.).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* init: schema migration, isArchived added to
board table. refactor: board repo for isArchived filtering
* init: archived, unarchived API procedures. refactor: all query
* fix: migration error
* feat: add tabbed navigation for boards view
* Implemented a Listbox for mobile and a tabbed navigation for desktop to switch between "Boards" and "Archived" views.
* Introduced state management for active tab selection.
* Updated UI components to reflect the new navigation structure.
* init: frontend/boards lists & tabs
* chore: fixed font styling and spacing
* init:boardDropdown / boardView.
* chore:added translations
* Remove .cursor plan file from repo
* fix:build erros
* revert: remove locales changes
* fix:reverted changes under locales, replaced the archive and unarchive endpoints. Reorder migrations
* fix:migration issue
* fix: update journal.json
---------
Co-authored-by: Henry <henry_ball@hotmail.co.uk>
* fix: allow invited users to sign up when registration is disabled
Move sign-up restriction logic from better-auth's disableSignUp config
to the existing user.create.before database hook, which already checks
for pending invitations. The frontend signup and login pages now detect
invite flows (?next=/invite/...) and bypass the disabled UI accordingly.
Closes#411
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add regression tests for sign-up hook invite bypass
Verify that the user.create.before database hook correctly:
- allows sign-up when registration is not disabled
- blocks sign-up when disabled and no invitation exists
- allows sign-up when disabled but a pending invitation exists
- respects BETTER_AUTH_ALLOWED_DOMAINS in combination with invites
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test: add OIDC/social sign-up path coverage for invite bypass
Address review suggestion: add explicit tests verifying the
user.create.before hook handles OIDC/social sign-ups the same way as
email/password — invited users are allowed, uninvited users are blocked.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add notifications schema
* chore: gen migration
* feat: add mention email template
* feat: add sendMentionEmail func
* feat: add repo funcs
* feat: update card router to send emails on mention
* fix: update the editor suggestion to show all members
* feat: skip pending members in sendMentionEmails
* feat: update comments to use tiptap editor
- Use /boards/{id} for private board links so copied link works; keep pretty URL for public boards
- Show matching path in board header (pretty path for public, boards/id for private)
- Copy link button copies URL to clipboard instead of opening in new tab (opening a new tab seems redundant since we're already on it)
- Edit workspace URL button links to /settings/workspace instead of account settings
* refactor: replace presigned URL uploads with backend upload endpoints
* feat: update avatar upload to use new endpoint
* refactor: use createS3Client in auth hooks
* feat: generate presigned URLs for avatars
* fix: show avatar image in user menu
* fix: hide tooltip if content is empty
* fix: support external avatar URLs in generateAvatarUrl
* fix: remove content type restriction on attachments
* fix: prevent workspace.byId validation errors when publicId is empty
* fix: add keys to shortcut elements
* fix: prevent card.byId validation errors when cardPublicId is empty
* chore: remove invalid config option from next.config.js
* feat: setup schema for workspace roles
* chore: regen migration
* feat: add publicId to workspace roles
* feat: setup default permissions
* feat: add repo funcs
* feat: setup basic router interactions
* feat: add card permissions
* feat: assert permissions for lists
* feat: assert board permissions
* feat: assert permission for remaining routes
* feat: add permissions page to settings
* feat: enable updating member roles
* feat: order members by role and createdAt
* feat: allow editing individual permissions
* feat: reset role defaults
* feat: clear all permission overrides
* feat: allow users to delete entities they have created
* feat: set roleId when inviting new members
* feat: disable UI elements if user does not have permissions
* feat: allow admins to assign the admin role to other users
* feat: allow delete:list as default
* refactor: centre permissions modal
* chore: translations