The MCP add_card_comment and update_card_comment tools send `{ content }`
to Kan API, but the API expects `{ comment }`. This causes a 400 Bad Request
("comment: Required") when trying to add or update card comments.
Fixed by mapping `{ comment: content }` in both tool handlers.
Co-authored-by: Craft Agent <agents-noreply@craft.do>
* feat: add ability to move boards between workspaces
Implements the "Move to workspace" feature (#344) allowing users to
relocate a board and all its contents (lists, cards, labels, checklists,
comments, activity) to a different workspace.
Key design decisions:
- Card member assignments are cleared on move (they reference
workspace-scoped members that may not exist in the target workspace)
- Comments and activity history are preserved (they reference global
user IDs, not workspace members)
- Slug conflicts in the target workspace are auto-resolved by
appending a UID suffix
- Permission model: requires board:edit in source workspace and
board:create in target workspace
- Templates and archived boards cannot be moved
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: consolidate board queries in move mutation
Address review feedback:
- Consolidate 3 separate board queries into a single findFirst()
that fetches all needed fields (id, name, slug, type, isArchived,
workspaceId, createdBy)
- Fix slug fallback to use board.name instead of publicId for
human-readable URLs
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: filter guest workspaces from move board destination list
Guests typically lack board:create permission in the target workspace,
so showing them as destinations leads to a confusing server rejection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract getBoardForMove repo function
Moves the inline board query from the move mutation into the repo
layer, consistent with how every other board mutation fetches data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for board.move mutation
10 test cases covering auth, validation, permissions, slug conflict
resolution, and the happy path. Follows webhook.test.ts patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: mark locale files as linguist-generated
GitHub will now auto-collapse compiled translation files (messages.json,
messages.ts, messages.po) in PR diffs and exclude them from language
stats. This makes PRs that touch i18n strings much easier to review.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove locale file changes from PR
Reverts locale file diffs and .gitattributes to match main, per review
feedback. The locale changes were unrelated translation updates that
inflated the PR diff.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove locale file changes from PR
Per @hjball's review: locale compilation/translations are handled
automatically on merge to main, so this PR shouldn't carry them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: align locale files with upstream/main
Previous removal commit used local main, which had drifted from
upstream. Re-syncing to upstream/main so the PR carries no locale diff.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(board-move): tighten deletedAt handling per review
Three changes addressing @hjball's review comments, all about the
schema treating deletedAt as optional metadata while the move-board
flow needs it as a load-bearing invariant.
1. getBoardForMove now filters isNull(deletedAt). Moving a tombstoned
board has no defensible semantics. Replaces the implicit
"the board exists in the table" check with an explicit
"the board is not soft-deleted" check.
2. Move-flow's clearing of cardToWorkspaceMembers now spans every
card under every list ever associated with this board, including
soft-deleted ones. If we leave member assignments on a deleted
card and that card is later restored, the assignments would
resurrect rogue references to workspace members from the OLD
workspace. Removed the isNull filters on both lists and cards in
that loop.
3. Move-flow now refuses to move into a soft-deleted target
workspace. workspaceRepo.getByPublicId did not previously project
deletedAt; extended its column selection so the call-site guard
in board.move can check it. (A wider fix to make the repo treat
deleted-as-not-found across all 14+ callers is left for a
separate PR — narrow scope here.)
Plus one regression test: throws NOT_FOUND when target workspace is
soft-deleted. All 11 board-move tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat: kan mcp server initial attempt
* fix: card was missing options and added default fallbacks
* fix: label creating with better information for colors and presets
The AWS SDK throws "Region is missing" when S3Client is constructed
with an empty-string region, so any S3 feature (avatars, attachments,
presigned URL generation) is silently broken when S3_REGION is unset
— even though .env.example ships it unset and env.ts declares it
optional. The schema and the runtime disagreed about whether the var
was required.
Default to "us-east-1" in createS3Client. S3-compatible providers
(MinIO, Backblaze B2, R2, DigitalOcean Spaces, Wasabi) ignore the
region entirely; real AWS S3 users should set S3_REGION explicitly
to their bucket's actual region (clarified in .env.example).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each workspace package had its exports map pointing `types` at
`./dist/...d.ts` while `default` pointed at `./src/...ts`. The dist
directory is a build artifact only refreshed when someone runs
`pnpm build` in that package — meaning every other package in the
monorepo typechecks against last week's type signatures.
Concrete failure mode: contributor edits a function in
packages/shared/src/utils/foo.ts (changes a parameter type, adds a
new export, etc.), runs `pnpm typecheck` from root, sees green. The
check actually validated against the stale dist/.d.ts. Real type
errors stay invisible until CI builds shared first, by which point
the diff is already pushed.
Fix: point `types` at the same `src/` paths the runtime resolves to.
TypeScript reads .ts source as types fine when consumers share the
same TS version, which a monorepo guarantees. Verified end-to-end:
adding a new export to shared and immediately typechecking @kan/api
now picks it up without rebuilding shared, and breaking a return
type immediately fails the consumer's typecheck.
Applied to @kan/api, @kan/db, @kan/logger, @kan/shared.
@kan/email is intentionally left as-is because its source is .tsx
(JSX) — pointing types at .tsx would force every consumer to enable
--jsx in their tsconfig, which is a worse cascade than the stale
dist problem we're solving.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add cardPublicId to card webhook payloads
* fix: include card publicId in webhook payloads
Add data.card.publicId to webhook payloads while keeping data.card.id for compatibility, and update card/webhook call sites and tests so webhook consumers can reliably use public IDs.
* 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>
The getSocialProviders endpoint was manually pushing "oidc" to the
providers array when OIDC env vars are present, but Better Auth already
includes the OIDC provider in ctx.context.socialProviders when it is
configured. This resulted in ["oidc","oidc"] being returned from
/api/auth/social-providers, causing a duplicate provider registration
that led to a TypeError: Cannot read properties of null (reading 'id')
during the OIDC login callback, preventing all OIDC logins.
Fix: remove the manual providers.push("oidc") block as it is redundant.
* 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
* 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>
* 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>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Henry <30578846+hjball@users.noreply.github.com>
* feat(api): add webhook delivery utility and card event integration
Add the core webhook delivery logic and wire it into card mutations:
- Add sendWebhookToUrl() with HMAC-SHA256 signing, 10s timeout
- Add sendWebhooksForWorkspace() for fan-out delivery (fire-and-forget)
- Add createCardWebhookPayload() for building webhook payloads
- Fire webhooks on card create, update, move, and delete events
- Add unit tests for webhook utility functions
Depends on #391 (DB schema & repository).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): use correct boardId in webhook payloads and add rejection safety
- Fix bug where workspaceId was incorrectly passed as boardId in all
webhook payloads — now uses board's publicId via boardPublicId
- Replace void sendWebhooksForWorkspace() with .catch() to prevent
unhandled promise rejections if the DB query inside fails
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(api): add SSRF protection to webhook delivery
Block webhook URLs targeting internal networks:
- Require HTTPS (reject HTTP)
- Block localhost, 127.0.0.1, ::1, 0.0.0.0
- Block cloud metadata endpoints (169.254.169.254, metadata.google.internal)
- Block private IP ranges (10.x, 172.16-31.x, 192.168.x)
- Add tests for all blocked URL patterns
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(api): use WebhookEvent type from schema instead of duplicating
Replace the hardcoded WebhookEventType union with the canonical
WebhookEvent type from @kan/db/schema, addressing reviewer feedback
on PR #392.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(api): improve webhook delivery safety and validation
Cherry-pick delivery-related changes from b2cc9ac:
- Extract URL validation into reusable webhookUrlSchema zod validator
for SSRF checks
- Wrap sendWebhooksForWorkspace in try/catch to prevent unhandled
promise rejections
- Document SSRF risk mitigation on sendWebhookToUrl
- Add corresponding tests
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>