Skip to content

feat(kanban): unify cloud + local boards on the document model#45

Open
cnjack wants to merge 3 commits into
mainfrom
feat/kanban-unification-v2
Open

feat(kanban): unify cloud + local boards on the document model#45
cnjack wants to merge 3 commits into
mainfrom
feat/kanban-unification-v2

Conversation

@cnjack

@cnjack cnjack commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What & why

Retires the orphaned cloud-DB kanban (/kanban, kanban_* tables — unreachable from the UI and not synced to desktop) and converges everything on the document-backed board: cards are .md notes, boards are .board views, all synced through the documents table. Every relational feature is rebuilt fresh, keyed by documents.id.

Changes

Retire ③ (cloud-DB board)

  • Delete Kanban.tsx + /kanban route, handlers/kanban/*, kanban_* models, hub kanban events, kanban_tests/e2e. Migration 0018 drops all 9 kanban_* tables.

Kanban MCP + CLI (rebuilt on documents)

  • Separate kanban MCP server at POST /mcp/kanban (mcp/kanban_tools.rs, ServerKind).
  • Local-first jtype board/card CLI on jtype-core (list_boards, set_frontmatter_field); jtype mcp-stdio --kanban bridge.

Feature parity on the shared BoardSurface

  • Colored labels (resolveTags/autoTagColor), member assignee (listMembers), per-card activity timeline (document_versions + author username).

Comments / webhooks / ticket-links

  • 0019 card_comments (FK documents.id).
  • 0020 webhooks + delivery worker, re-homed into save_document_version so REST + desktop sync push + conflict + live all fire; SSRF-hardened (IPv4-mapped/fe80:: IPv6 + delivery-time DNS-rebinding re-check).
  • 0021 card_tickets + board_sequences for Jira-style /browse/OCCSV-3371 (atomic LAST_INSERT_ID allocator, idempotent per document).

Adversarial review

Three review passes (34 agents) over the diff; real findings fixed: LAST_INSERT_IDCAST AS SIGNED (was 500ing every allocation), idempotent concurrent allocate, webhook SSRF gaps + the sync-push trigger gap.

Test plan

  • jtype-web / jtype-core / jtype-cli cargo check --tests — 0 warnings
  • web tsc + vite build; desktop tsc
  • CLI board/card commands smoke-tested end-to-end
  • colored labels / member dropdown / activity verified in a browser harness
  • DB migrations 0018–0021 applied against a live MySQL (needs a deploy)

⚠️ Migrations 0018–0021 assume the kanban_* tables are empty (demo) — no data migration script. Deferred (documented in internal-docs/kanban/): markdown ticket auto-linkify, webhook card-archived event, desktop ticket read-only cache.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added Jira-style ticket links with per-card ticket allocation and ticket redirect support.
    • Added board-level label color configuration and ticket badges in board cards.
    • Added document-backed card comments and generalized webhook management.
  • Architecture

    • Unified kanban into the document-based model using board view (.board) + card notes (.md) with cloud-local interoperability.
    • Updated the CLI and MCP tooling to operate in a local-first way; MCP is now split for notes vs kanban tools.
  • Removals

    • Retired the cloud DB kanban subsystem and decommissioned the legacy Kanban page/routes.

Retire the orphaned cloud-DB kanban (③) and converge on the document-backed
board (.md cards + .board views synced via the documents table) as the single
source of truth, then rebuild every relational feature on documents.id.

Cleanup / retire
- Remove Kanban.tsx + /kanban route, handlers/kanban/*, kanban_* models, hub
  kanban events, kanban_tests/e2e; migration 0018 drops all 9 kanban_* tables.
- Strip cloud-DB kanban from MCP/CLI.

Kanban MCP + CLI (rebuilt on the document model)
- Separate kanban MCP server at POST /mcp/kanban (mcp/kanban_tools.rs, ServerKind).
- Local-first `jtype board/card` CLI on jtype-core (list_boards, set_frontmatter_field).

Feature parity (shared BoardSurface)
- Colored labels (resolveTags/autoTagColor), member assignee (listMembers),
  activity timeline (document_versions + author username).

Comments / webhooks / ticket-links (fresh, keyed by documents.id)
- 0019 card_comments; 0020 webhooks + delivery worker re-homed into
  save_document_version (covers REST + sync push + conflict + live), SSRF-hardened
  (IPv4-mapped/fe80 IPv6 + delivery-time DNS-rebinding re-check); 0021 card_tickets
  + board_sequences for /browse/OCCSV-3371 (atomic LAST_INSERT_ID allocator,
  idempotent per document).

Verified: jtype-core/jtype-web/jtype-cli cargo check --tests, web tsc+build,
desktop tsc. Findings from adversarial review fixed (LAST_INSERT_ID CAST AS
SIGNED, idempotent allocate, webhook SSRF + sync-push trigger gap).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR fully retires the cloud DB kanban subsystem: it drops all kanban_* tables via migration 0018, deletes handlers/kanban/*, removes the Kanban.tsx frontend page, and strips kanban API types. In their place it introduces document-model comments (card_comments), generalized webhooks, and Jira-style ticket links (board_sequences/card_tickets), with matching backend handlers, frontend API, and routing. The CLI and MCP server are rewritten to operate locally on vault .board/.md files.

Changes

Kanban Unification v2

Layer / File(s) Summary
Design docs and planning
internal-docs/kanban/unification-v2.md, internal-docs/kanban/ticket-links.md, internal-docs/kanban/gaps-and-roadmap.md, internal-docs/doc-kanban-unification/design.md, internal-docs/web-board-alignment/design.md
New unification-v2 and ticket-links design documents define the document-model architecture, DB schema, migration order, rollout plan, and acceptance criteria. Prior kanban/alignment docs are annotated with retirement/history notices dated 2026-06-23.
DB schema: drop kanban, add card_comments/webhooks/card_tickets
services/jtype-web/migrations/0018_drop_kanban.*.sql, services/jtype-web/migrations/0019_card_comments.*.sql, services/jtype-web/migrations/0020_webhooks.*.sql, services/jtype-web/migrations/0021_card_tickets.*.sql, services/jtype-web/src/db/migrations.rs
Migrations 0018–0021 drop all kanban_* tables and create card_comments (document-keyed), webhooks/webhook_deliveries (generalized), and board_sequences/card_tickets (ticket allocation). Migration registry updated.
jtype-core: board scanning, set_frontmatter_field
services/jtype-core/src/lib.rs, services/jtype-web/Cargo.toml
Exports parse_frontmatter, adds BoardColumnInfo/BoardSummaryInfo structs, list_boards vault-walker, and set_frontmatter_field helper with unit tests. jtype-web gains path dependency on jtype-core.
Remove kanban handlers, models, hub events, routes
services/jtype-web/src/handlers/kanban/*, services/jtype-web/src/hub.rs, services/jtype-web/src/db/models.rs, services/jtype-web/src/handlers/mod.rs, services/jtype-web/src/tasks/cleanup_trash.rs, services/jtype-web/src/tasks/mod.rs
Deletes all handlers/kanban/* modules, removes all kanban WorkspaceEvent variants, drops all kanban DB model structs, removes kanban_card_trash cleanup, deregisters handler and task module exports.
comments.rs: re-key from card_id to document_id
services/jtype-web/src/handlers/comments.rs
Renames KanbanCommentCardComment, switches path param from card_id to document_id, replaces card existence guard with document guard, redirects all SQL to card_comments.
webhooks.rs: generalize to board_ref, add IPv6 SSRF
services/jtype-web/src/handlers/webhooks.rs
Renames KanbanWebhookWebhook, replaces board_id with board_ref, switches SQL to webhooks/webhook_deliveries, adds is_blocked_v6/is_blocked_ip IPv6 SSRF checks, extends domain blocklist, updates enqueue_event to accept board_ref: Option.
tickets.rs: Jira-style ticket allocation and browse
services/jtype-web/src/handlers/tickets.rs, services/jtype-web/src/lib.rs
Introduces AllocateRequest/Ticket, idempotent transactional allocate (with unique-violation retry), list_tickets, resolve_ticket endpoint; routes registered in Axum router.
document.rs: author_username, fire_card_webhook on save
services/jtype-web/src/handlers/document.rs
list_versions LEFT JOINs users for author_username. save_document_version wraps save_document_version_inner and fires fire_card_webhook on non-unchanged .md saves for document-backed cards.
webhook_delivery worker: table rename + delivery-time SSRF
services/jtype-web/src/tasks/webhook_delivery.rs
Switches SQL from kanban_webhook_* to webhook_* tables, adds ssrf_blocked() DNS re-check at delivery time, updates success/failure status tracking.
Route wiring, task docs, Dockerfile
services/jtype-web/src/lib.rs, services/jtype-web/src/tasks/mod.rs, services/jtype-web/Dockerfile
Registers new comment/webhook/ticket endpoints in Axum router, removes kanban route block, updates task module docs. Dockerfile copies jtype-core dependency.
MCP server: split notes and kanban into /mcp and /mcp/kanban
services/jtype-web/src/mcp/kanban_tools.rs, services/jtype-web/src/mcp/mod.rs, services/jtype-web/src/mcp/tools.rs, services/jtype-web/tests/mcp_tests.rs
Adds kanban_tools module with vault-based board/card tool catalog at /mcp/kanban, introduces ServerKind discriminator, splits router and dispatch, removes kanban tools from notes tools.rs (makes helpers pub(super)), updates MCP tests.
CLI: local-first kanban and --kanban MCP flag
services/jtype-cli/src/kanban.rs, services/jtype-cli/src/main.rs, services/jtype-cli/src/client.rs, services/jtype-cli/src/mcpstdio.rs, services/jtype-cli/tests/e2e.sh
Rewrites kanban.rs from HTTP/ApiClient to local vault operations (list/show boards, list/create/move/set cards). Updates main.rs BoardCmd/CardCmd, removes resolve_remote_ws, adds --kanban flag. post_mcp_raw and mcpstdio::run gain path/endpoint parameters. E2E tests remove kanban seeding.
Shared board model: labels, ticket field, tag colors
shared/lib/board.ts, src/lib/types.ts, shared/components/board/BoardSurface.tsx, shared/components/board/controls.tsx, src/components/BoardView.tsx
BoardViewConfig gains labels/ticketKey; BoardLabelDef type added; BoardViewCard gains ticket field; TAG_COLORS, autoTagColor, resolveTags, collectTagOptions exported. BoardSurface renders ticket badge. ListboxSelect no longer defaults to first option on unmatched value. Desktop BoardView wired to new fields.
WebBoardView: labels, assignees, comments, activity, tickets, webhooks
services/jtype-web/frontend/src/pages/WebBoardView.tsx
Extends BoardConfigJSON with labels/ticketKey, resolves tags via resolveTags, fetches members for assigneeOptions, wires activity/comment callbacks, lazily allocates tickets into displayCards, adds WebhooksDialog for outbound webhook management.
Frontend API contract and ticket browse routing
services/jtype-web/frontend/src/api.ts, services/jtype-web/frontend/src/main.tsx, services/jtype-web/frontend/src/pages/Workspace.tsx
api.ts removes api.kanban and all Kanban types; adds CardComment, Webhook*, Ticket models and endpoints. main.tsx adds TicketRedirect for /workspaces/:id/tickets/:ticket cross-workspace resolution. Workspace.tsx handles ?doc= deep-link.

Sequence Diagram(s)

sequenceDiagram
  participant CLI_MCP as jtype-cli (--kanban)
  participant MCPKanban as POST /mcp/kanban
  participant kanban_tools
  participant VaultDocs as Vault .board/.md docs
  participant SaveDoc as save_document_version
  participant WebhookEnqueue as enqueue_event
  participant DeliveryWorker as webhook_delivery worker
  participant ExternalWebhook as External webhook target

  CLI_MCP->>MCPKanban: JSON-RPC tools/call (create_card)
  MCPKanban->>kanban_tools: call("create_card", args)
  kanban_tools->>VaultDocs: scan .board files → find board
  kanban_tools->>VaultDocs: save new .md card with frontmatter
  VaultDocs-->>kanban_tools: saved
  kanban_tools-->>MCPKanban: isError: false

  Note over SaveDoc,DeliveryWorker: When WebBoardView saves a card
  SaveDoc->>VaultDocs: save_document_version_inner
  SaveDoc->>WebhookEnqueue: fire_card_webhook (board_ref, card metadata)
  WebhookEnqueue->>WebhookEnqueue: INSERT webhook_deliveries (board_ref IS NULL OR matches)
  DeliveryWorker->>WebhookEnqueue: poll due deliveries
  DeliveryWorker->>DeliveryWorker: ssrf_blocked(target_url)
  DeliveryWorker->>ExternalWebhook: HMAC-signed POST payload
  ExternalWebhook-->>DeliveryWorker: 200 OK
  DeliveryWorker->>WebhookEnqueue: UPDATE webhook_deliveries status=succeeded
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • cnjack/jtype#10: Shares the same MCP/OAuth/token/CLI integration foundation that this PR extends by adding /mcp/kanban, updating mcpstdio/post_mcp_raw endpoint handling, and splitting the tool catalog.
  • cnjack/jtype#11: Directly preceded the local-first CLI refactor continued here — this PR removes resolve_remote_ws, switches BoardCmd/CardCmd to vault-local operations, and adds the --kanban MCP flag on the same main.rs surface.
  • cnjack/jtype#44: Introduced the kanban webhook handler that this PR replaces with the generalized webhooks.rs + webhook_deliveries/webhooks schema, HMAC delivery flow, and updated enqueue_event.

Poem

🐰 Hop hop, the old board tables fall,
No more kanban_* to recall!
.board files bloom, .md cards gleam,
Tickets get numbers — a Jira-like dream.
The rabbit writes webhooks with HMAC delight,
And /workspaces/:id/tickets/:ticket shines so bright! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: unifying cloud-based and local Kanban boards onto a single document-backed model.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/kanban-unification-v2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.0)
services/jtype-web/src/lib.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
services/jtype-web/src/handlers/webhooks.rs (1)

107-126: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Block all non-global IP ranges in the webhook SSRF guard.

The shared predicate still allows non-global targets such as IPv4 CGNAT 100.64.0.0/10, IPv4 multicast/reserved ranges, and IPv6 multicast/documentation ranges. Since both create-time validation and the delivery-time DNS recheck use this path, those addresses can still be reached by webhook delivery.

🛡️ Proposed hardening
 pub(crate) fn is_blocked_v4(ip: std::net::Ipv4Addr) -> bool {
+    let octets = ip.octets();
     ip.is_loopback()
         || ip.is_private()
         || ip.is_link_local()
         || ip.is_unspecified()
         || ip.is_broadcast()
-        || ip.octets()[0] == 0
+        || octets[0] == 0
+        || (octets[0] == 100 && (octets[1] & 0b1100_0000) == 0b0100_0000) // 100.64.0.0/10
+        || (octets[0] == 198 && (octets[1] == 18 || octets[1] == 19)) // 198.18.0.0/15
+        || octets[0] >= 224 // multicast + reserved
 }
 
 pub(crate) fn is_blocked_v6(ip: std::net::Ipv6Addr) -> bool {
@@
     if let Some(v4) = ip.to_ipv4_mapped().or_else(|| ip.to_ipv4()) {
         return is_blocked_v4(v4);
     }
+    let segments = ip.segments();
     ip.is_loopback()
         || ip.is_unspecified()
-        || (ip.segments()[0] & 0xfe00) == 0xfc00 // ULA fc00::/7
-        || (ip.segments()[0] & 0xffc0) == 0xfe80 // link-local fe80::/10
+        || (segments[0] & 0xfe00) == 0xfc00 // ULA fc00::/7
+        || (segments[0] & 0xffc0) == 0xfe80 // link-local fe80::/10
+        || (segments[0] & 0xff00) == 0xff00 // multicast ff00::/8
+        || (segments[0] == 0x2001 && segments[1] == 0x0db8) // documentation 2001:db8::/32
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/jtype-web/src/handlers/webhooks.rs` around lines 107 - 126, The
is_blocked_v4 and is_blocked_v6 functions do not block all non-global IP ranges,
allowing CGNAT ranges like 100.64.0.0/10, IPv4 multicast ranges, IPv6 multicast
ranges, and IPv6 documentation ranges through. Add additional checks to
is_blocked_v4 to block IPv4 multicast (224.0.0.0/4) and CGNAT (100.64.0.0/10)
ranges by checking the first octet and appropriate bits, and add checks to
is_blocked_v6 to block IPv6 multicast (ff00::/8) and documentation ranges
(2001:db8::/32) by examining the segments, or alternatively use the is_global()
method if available to return true only for genuinely global addresses.
services/jtype-web/src/handlers/mod.rs (1)

5-20: 📐 Maintainability & Code Quality | 🟠 Major

Add integration tests for comments, tickets, and webhooks handlers.

The comments, tickets, and webhooks handler modules define multiple API endpoints (list_comments, create_comment, delete_comment, allocate, list_tickets, resolve_ticket, browse, list_webhooks, create_webhook, delete_webhook) that require corresponding test files. Per the coding guidelines, all handlers in services/jtype-web/src/handlers/**/*.rs must have integration tests. Create comments_tests.rs, tickets_tests.rs, and webhooks_tests.rs under services/jtype-web/tests/ with coverage for each endpoint using the web-service test harness (common::setup().await, tower::ServiceExt::oneshot, common::uid(), common::wname()).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/jtype-web/src/handlers/mod.rs` around lines 5 - 20, Create three new
integration test files in services/jtype-web/tests/ named comments_tests.rs,
tickets_tests.rs, and webhooks_tests.rs to test the handler modules exported in
mod.rs. Each test file should contain test cases covering all endpoints defined
in the respective handler modules: comments_tests.rs should test list_comments,
create_comment, and delete_comment endpoints; tickets_tests.rs should test
allocate, list_tickets, and resolve_ticket endpoints; webhooks_tests.rs should
test browse, list_webhooks, create_webhook, and delete_webhook endpoints. Each
test should use common::setup().await to initialize the test harness,
tower::ServiceExt::oneshot to send requests to the service, and common::uid()
and common::wname() for test data generation as needed.

Source: Coding guidelines

🧹 Nitpick comments (1)
shared/components/board/BoardSurface.tsx (1)

720-724: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use semantic shared color tokens for the ticket badge.

The new shared badge adds palette-specific color utilities; use semantic token classes so desktop and web theming stay aligned. As per coding guidelines, shared/**/*.{ts,tsx,css}: “Shared components must use semantic Tailwind classes only (e.g. text-brand, bg-brand-soft) from shared/styles/tokens.css and never hardcode hex values.”

Proposed tokenized badge colors
-                              <span className="mb-0.5 inline-block rounded bg-stone-100 px-1 py-0.5 font-mono text-[10px] font-medium tracking-tight text-stone-500">
+                              <span className="mb-0.5 inline-block rounded bg-brand-soft px-1 py-0.5 font-mono text-[10px] font-medium tracking-tight text-brand-dark">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/components/board/BoardSurface.tsx` around lines 720 - 724, The ticket
badge in the span element uses hardcoded Tailwind color classes (bg-stone-100
and text-stone-500) instead of semantic color tokens. Replace these
palette-specific utilities with semantic token classes from
shared/styles/tokens.css following the shared component guidelines. Identify the
span element displaying the card.ticket and swap the hardcoded color classes
(bg-stone-100 for background and text-stone-500 for text) with the appropriate
semantic token classes (e.g., bg-brand-soft, text-brand patterns) that maintain
consistency across desktop and web theming.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal-docs/kanban/ticket-links.md`:
- Around line 40-59: The ticket_key column in the card_tickets table definition
is using VARCHAR(12), but the actual migration uses VARCHAR(16). Update the
card_tickets table definition to change the ticket_key column from VARCHAR(12)
to VARCHAR(16) to align the design document with the implemented schema
migration. The board_sequences table ticket_key column can remain as VARCHAR(12)
since that matches the migration.
- Around line 97-104: The browse handler in handlers/tickets.rs (lines 214-248)
queries ticket key-number pairs across multiple workspaces without a
deterministic ordering in the SQL WHERE clause, causing arbitrary results when
duplicates exist across workspaces. Fix this by either adding an ORDER BY clause
to the query that uses a stable tie-breaker like workspace creation date or
workspace join date to ensure consistent ordering, or modify the query logic to
detect and return a conflict/disambiguation error when the same ticket
key-number pair exists in multiple workspaces that the user can access,
preventing non-deterministic behavior in the response.

In `@internal-docs/kanban/unification-v2.md`:
- Around line 64-72: The webhook schema reference in the table incorrectly uses
`board_document_id` keyed to the board's `document_id`, which would cause the
webhook scope to change when files are renamed or moved. Replace the webhook
entry `webhooks.board_document_id` with `webhooks.board_ref` and change its
relationship to reference the board's `board_ref` (from frontmatter), not the
`document_id`, to maintain consistent scope across file operations and align
with the actual migration contract. Apply the same change to the additional
occurrence mentioned in lines 112-119.
- Around line 62-72: The final sentence states that ticket strings will change
with board key renames, which contradicts the requirement that allocated ticket
IDs like OCCSV-3371 must remain stable after board-key renames. Correct this
statement to clarify that allocated ticket IDs stored in the card_tickets table
are immutable snapshots of ticket_key plus number, and therefore remain valid
and unchanged regardless of subsequent board-key renames. Explain that the
document_id foreign key relationship in card_tickets ensures ticket stability is
maintained independent of any board-level changes.

In `@services/jtype-cli/src/kanban.rs`:
- Around line 82-99: The code currently accepts any status value without
verifying it exists as a valid column in the board configuration, which can
result in orphaned cards. Before writing the card frontmatter in the
build_card_content function call and in any move/set operations, add validation
to confirm that the provided status is a valid column key within the board
object b. Retrieve the board's column definitions (likely from the board schema
or configuration) and check that the status parameter matches one of the valid
columns. If the status is invalid, return an appropriate error message to the
user indicating the valid column options.
- Around line 87-100: The `rel` variable is constructed solely from
`slugify(title)`, which means cards with identical titles will target the same
markdown file path and overwrite each other. Before calling `save_note_local`,
check if a file already exists at the calculated `rel` path, and if a collision
is detected, append a numeric suffix to the slug to generate a unique path. This
prevents overwriting existing cards while maintaining the slugified title as the
base of the filename.

In `@services/jtype-cli/src/main.rs`:
- Around line 332-347: Card mutation operations (move and set) should not create
a vault directory if one does not exist, as these commands require existing
board and card files. Replace the call to vault_or_init with require_vault in
the CardCmd::Move handler and the CardCmd::Set handler. Keep the CardCmd::Create
handler unchanged as it is a bootstrap operation that should initialize the
vault if needed. Update both move_card_local and set_card_local call sites to
use require_vault instead of vault_or_init.

In `@services/jtype-web/frontend/src/pages/WebBoardView.tsx`:
- Around line 501-504: The button with `absolute` positioning and the className
containing "bottom-4 right-4" needs to be anchored to the board container, but
the parent wrapper at line 485 lacks positioning, causing it to anchor to an
outer layout instead. Add `relative` positioning to the wrapper container at
line 485 to establish it as the positioning context for the absolutely
positioned webhooks button, ensuring the button stays within the board view
boundaries.

In `@services/jtype-web/migrations/0021_card_tickets.up.sql`:
- Line 22: The UNIQUE KEY constraint uq_card_tickets_ref on the card_tickets
table starts with workspace_id, which means database queries filtering by just
(ticket_key, number) without workspace_id will not use this index and will
require expensive table scans. Add a separate, non-unique index on the
card_tickets table with the columns (ticket_key, number) to efficiently support
the workspace-agnostic browse flow queries. Place this new index definition in
the same migration file after the unique constraint definition.

In `@services/jtype-web/src/handlers/document.rs`:
- Around line 314-317: The comment for the persist document version function
incorrectly claims that all write paths including conflict resolution notify via
webhook, but the actual implementation in resolve_conflict and other operations
shows this is not true. Update the comment in document.rs to accurately reflect
that only certain paths (specifically save_document_version, not
save_merged_document) fire the kanban webhook, and clarify that conflict
resolution strategies like accept_local, accept_cloud, and manual_merge in
sync.rs do not trigger the webhook notification because they call
save_merged_document directly instead of save_document_version. Also document
that other operations like trash/restore and publish/unpublish similarly bypass
the webhook.

In `@services/jtype-web/src/handlers/tickets.rs`:
- Around line 225-232: The SQL query in the sqlx::query for browsing tickets
uses LIMIT 1 without filtering by a specific workspace_id or ordering
deterministically, causing nondeterministic results when a user belongs to
multiple workspaces that contain the same ticket_key and number combination. Add
either a workspace_id parameter to the WHERE clause to filter for a specific
workspace, or add an ORDER BY clause with a deterministic ordering to ensure
consistent results, and handle the case where multiple matching tickets exist
across workspaces by either requiring workspace context in the request or
explicitly handling the ambiguity in the application logic.
- Around line 56-79: The resolve_document_id function accepts any document type
in the workspace, which allows editors to allocate tickets to non-card documents
like `.board` files or ordinary notes. After calling resolve_document_id in the
allocate function, add validation to verify that the resolved document is a
`.md` card with `board:` frontmatter before proceeding with ticket allocation.
You can query the documents table to check the file type and frontmatter
metadata, returning an appropriate error if the document does not meet the card
criteria.

In `@services/jtype-web/src/mcp/kanban_tools.rs`:
- Around line 180-188: The create_card function always uses the same slug-based
path format {dir}/{slug(title)}.md for saving cards, which means duplicate
titles that produce identical slugs will silently overwrite existing card files.
Implement collision detection before the api_post call to check if a file
already exists at the target path, and if it does, append a numeric suffix or
similar unique identifier to the slug (e.g., {dir}/{slug(title)}_1.md) to
prevent overwrites. This ensures that each card creation either creates a new
file or fails gracefully rather than silently clobbering existing cards.
- Around line 229-233: The issue is that the clear field logic in the
update_card function is unreachable because the opt function drops empty strings
upstream before the v.is_empty() check can ever be true, preventing fields like
assignee and due from being cleared. To fix this, modify the logic to
differentiate between a field argument that was provided but is empty (which
should clear the field) versus a field argument that was not provided at all
(which should be skipped). Instead of relying on opt to return
Some(empty_string), you need to check if the field key exists in args separately
from whether its value is empty, so that empty strings can be passed as None to
set_frontmatter_field to clear the fields as documented.

In `@services/jtype-web/src/tasks/webhook_delivery.rs`:
- Around line 105-127: When delivery is blocked by the SSRF check in the
ssrf_blocked conditional block, the code updates the webhook_deliveries table
but fails to also update the webhooks table with metadata fields
last_delivery_at and last_status, causing webhooks to display stale status
information. After the webhook_deliveries UPDATE query executes in the
ssrf_blocked branch, add a second query to update the webhooks table to set
last_status to the same status value (either "dead" or "failed" based on the
dead flag) and last_delivery_at to the current timestamp, ensuring webhook
metadata remains consistent with the delivery state.

In `@services/jtype-web/tests/mcp_tests.rs`:
- Around line 117-119: The test coverage currently only validates the notes
catalog at the `/mcp` endpoint but does not cover the new Kanban catalog at
`/mcp/kanban`. Add a new test case that verifies the Kanban catalog exposes
board and card tools (such as board-related and card-related operations).
Additionally, add a negative assertion in the existing test (around the for loop
that checks the expected list with "list_workspaces", "list_notes", etc.) to
verify that `/mcp` does not expose board or card tools, ensuring the tools are
properly isolated to their respective endpoints.

In `@shared/lib/board.ts`:
- Around line 220-221: The TAG_COLORS constant exports hardcoded hex color
values, which violates the shared code styling guidelines that require semantic
token usage. Replace the hardcoded hex values in the TAG_COLORS array with
semantic token references from shared/styles/tokens.css. Update each color in
the array to reference the appropriate semantic token variable instead of the
raw hex value to ensure theming compliance.

In `@src/lib/types.ts`:
- Around line 28-30: The labels field defines an inline object shape { label:
string; color?: string | null } which violates the project's typing guidelines.
Extract this object shape into a separate interface (such as BoardLabel or
Label) with properties for label and color, then update the labels field to
reference this new interface as an array type instead of using the inline object
definition.

---

Outside diff comments:
In `@services/jtype-web/src/handlers/mod.rs`:
- Around line 5-20: Create three new integration test files in
services/jtype-web/tests/ named comments_tests.rs, tickets_tests.rs, and
webhooks_tests.rs to test the handler modules exported in mod.rs. Each test file
should contain test cases covering all endpoints defined in the respective
handler modules: comments_tests.rs should test list_comments, create_comment,
and delete_comment endpoints; tickets_tests.rs should test allocate,
list_tickets, and resolve_ticket endpoints; webhooks_tests.rs should test
browse, list_webhooks, create_webhook, and delete_webhook endpoints. Each test
should use common::setup().await to initialize the test harness,
tower::ServiceExt::oneshot to send requests to the service, and common::uid()
and common::wname() for test data generation as needed.

In `@services/jtype-web/src/handlers/webhooks.rs`:
- Around line 107-126: The is_blocked_v4 and is_blocked_v6 functions do not
block all non-global IP ranges, allowing CGNAT ranges like 100.64.0.0/10, IPv4
multicast ranges, IPv6 multicast ranges, and IPv6 documentation ranges through.
Add additional checks to is_blocked_v4 to block IPv4 multicast (224.0.0.0/4) and
CGNAT (100.64.0.0/10) ranges by checking the first octet and appropriate bits,
and add checks to is_blocked_v6 to block IPv6 multicast (ff00::/8) and
documentation ranges (2001:db8::/32) by examining the segments, or alternatively
use the is_global() method if available to return true only for genuinely global
addresses.

---

Nitpick comments:
In `@shared/components/board/BoardSurface.tsx`:
- Around line 720-724: The ticket badge in the span element uses hardcoded
Tailwind color classes (bg-stone-100 and text-stone-500) instead of semantic
color tokens. Replace these palette-specific utilities with semantic token
classes from shared/styles/tokens.css following the shared component guidelines.
Identify the span element displaying the card.ticket and swap the hardcoded
color classes (bg-stone-100 for background and text-stone-500 for text) with the
appropriate semantic token classes (e.g., bg-brand-soft, text-brand patterns)
that maintain consistency across desktop and web theming.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 67dc1ff2-821a-4d3f-89be-3593c36997c1

📥 Commits

Reviewing files that changed from the base of the PR and between a891cc9 and 9868660.

⛔ Files ignored due to path filters (1)
  • services/jtype-web/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (55)
  • internal-docs/doc-kanban-unification/design.md
  • internal-docs/kanban/design.md
  • internal-docs/kanban/gaps-and-roadmap.md
  • internal-docs/kanban/next-features-design.md
  • internal-docs/kanban/ticket-links.md
  • internal-docs/kanban/unification-v2.md
  • internal-docs/web-board-alignment/design.md
  • services/jtype-cli/src/client.rs
  • services/jtype-cli/src/kanban.rs
  • services/jtype-cli/src/main.rs
  • services/jtype-cli/src/mcpstdio.rs
  • services/jtype-cli/tests/e2e.sh
  • services/jtype-core/src/lib.rs
  • services/jtype-web/Cargo.toml
  • services/jtype-web/frontend/src/api.ts
  • services/jtype-web/frontend/src/main.tsx
  • services/jtype-web/frontend/src/pages/Kanban.tsx
  • services/jtype-web/frontend/src/pages/WebBoardView.tsx
  • services/jtype-web/frontend/src/pages/Workspace.tsx
  • services/jtype-web/migrations/0018_drop_kanban.down.sql
  • services/jtype-web/migrations/0018_drop_kanban.up.sql
  • services/jtype-web/migrations/0019_card_comments.down.sql
  • services/jtype-web/migrations/0019_card_comments.up.sql
  • services/jtype-web/migrations/0020_webhooks.down.sql
  • services/jtype-web/migrations/0020_webhooks.up.sql
  • services/jtype-web/migrations/0021_card_tickets.down.sql
  • services/jtype-web/migrations/0021_card_tickets.up.sql
  • services/jtype-web/src/db/migrations.rs
  • services/jtype-web/src/db/models.rs
  • services/jtype-web/src/handlers/comments.rs
  • services/jtype-web/src/handlers/document.rs
  • services/jtype-web/src/handlers/kanban/board.rs
  • services/jtype-web/src/handlers/kanban/card.rs
  • services/jtype-web/src/handlers/kanban/column.rs
  • services/jtype-web/src/handlers/kanban/label.rs
  • services/jtype-web/src/handlers/kanban/mod.rs
  • services/jtype-web/src/handlers/mod.rs
  • services/jtype-web/src/handlers/tickets.rs
  • services/jtype-web/src/handlers/webhooks.rs
  • services/jtype-web/src/hub.rs
  • services/jtype-web/src/lib.rs
  • services/jtype-web/src/mcp/kanban_tools.rs
  • services/jtype-web/src/mcp/mod.rs
  • services/jtype-web/src/mcp/tools.rs
  • services/jtype-web/src/tasks/cleanup_trash.rs
  • services/jtype-web/src/tasks/mod.rs
  • services/jtype-web/src/tasks/webhook_delivery.rs
  • services/jtype-web/tests/kanban_e2e_tests.rs
  • services/jtype-web/tests/kanban_tests.rs
  • services/jtype-web/tests/mcp_tests.rs
  • shared/components/board/BoardSurface.tsx
  • shared/components/board/controls.tsx
  • shared/lib/board.ts
  • src/components/BoardView.tsx
  • src/lib/types.ts
💤 Files with no reviewable changes (9)
  • services/jtype-web/src/handlers/kanban/board.rs
  • services/jtype-web/tests/kanban_e2e_tests.rs
  • services/jtype-web/src/handlers/kanban/mod.rs
  • services/jtype-web/src/handlers/kanban/card.rs
  • services/jtype-web/frontend/src/pages/Kanban.tsx
  • services/jtype-web/src/handlers/kanban/column.rs
  • services/jtype-web/tests/kanban_tests.rs
  • services/jtype-web/src/handlers/kanban/label.rs
  • services/jtype-web/src/hub.rs

Comment thread internal-docs/kanban/ticket-links.md
Comment thread internal-docs/kanban/ticket-links.md Outdated
Comment thread internal-docs/kanban/unification-v2.md
Comment thread internal-docs/kanban/unification-v2.md
Comment thread services/jtype-cli/src/kanban.rs
Comment thread services/jtype-web/src/mcp/kanban_tools.rs
Comment thread services/jtype-web/src/tasks/webhook_delivery.rs
Comment thread services/jtype-web/tests/mcp_tests.rs
Comment thread shared/lib/board.ts Outdated
Comment on lines +220 to +221
/** Palette for auto-assigned tag colors (deterministic by label). */
export const TAG_COLORS = ["#ef4444", "#f59e0b", "#eab308", "#22c55e", "#14b8a6", "#0ea5e9", "#6366f1", "#a855f7", "#ec4899", "#78716c"];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace hardcoded tag palette hex values with semantic tokens.

TAG_COLORS introduces hardcoded hex values in shared code. Please switch this palette to token-backed semantic values (from shared/styles/tokens.css) so shared theming stays policy-compliant.

As per coding guidelines shared/**/*.{ts,tsx,css}: shared code must use semantic styling tokens and “never hardcode hex values”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/lib/board.ts` around lines 220 - 221, The TAG_COLORS constant exports
hardcoded hex color values, which violates the shared code styling guidelines
that require semantic token usage. Replace the hardcoded hex values in the
TAG_COLORS array with semantic token references from shared/styles/tokens.css.
Update each color in the array to reference the appropriate semantic token
variable instead of the raw hex value to ensure theming compliance.

Source: Coding guidelines

Comment thread src/lib/types.ts Outdated
Comment on lines +28 to +30
/** Board-level label definitions giving tags an explicit color (else auto-colored). */
labels?: { label: string; color?: string | null }[];
/** Board ticket-id prefix (e.g. OCCSV) for /browse links; ticket numbers are cloud-only. */

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use an interface for the new label object shape.

The added labels field introduces a new inline object shape; this should be an interface per project typing rules.

Proposed refactor
+export interface BoardLabelDef {
+  label: string;
+  color?: string | null;
+}
+
 export type BoardConfig = {
@@
-  labels?: { label: string; color?: string | null }[];
+  labels?: BoardLabelDef[];

As per coding guidelines src/**/*.{ts,tsx}: “Use interface for defining object shapes in TypeScript frontend code.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/** Board-level label definitions giving tags an explicit color (else auto-colored). */
labels?: { label: string; color?: string | null }[];
/** Board ticket-id prefix (e.g. OCCSV) for /browse links; ticket numbers are cloud-only. */
export interface BoardLabelDef {
label: string;
color?: string | null;
}
export type BoardConfig = {
/** Board-level label definitions giving tags an explicit color (else auto-colored). */
labels?: BoardLabelDef[];
/** Board ticket-id prefix (e.g. OCCSV) for /browse links; ticket numbers are cloud-only. */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/types.ts` around lines 28 - 30, The labels field defines an inline
object shape { label: string; color?: string | null } which violates the
project's typing guidelines. Extract this object shape into a separate interface
(such as BoardLabel or Label) with properties for label and color, then update
the labels field to reference this new interface as an array type instead of
using the inline object definition.

Source: Coding guidelines

cnjack and others added 2 commits June 23, 2026 23:45
Workspace-scope ticket resolution and drop the ambiguous workspace-agnostic
/browse handler+route; resolve only via the workspace-scoped route (frontend
TicketRedirect). Restrict ticket allocation to .md cards with board: frontmatter
(same card test as fire_card_webhook). Fire the card webhook after conflict
resolution's merged-save (accept_local/accept_cloud/manual_merge) — other write
paths unchanged.

Also from the review triage: SSRF guard now blocks CGNAT/multicast/reserved/
benchmark v4 and IPv6 multicast/doc ranges (is_global is unstable on 1.89, so
bitmasks); webhook delivery keeps webhooks.last_status in step on blocked
deliveries; CLI/MCP avoid slug-collision card overwrites; CLI validates column
keys and uses require_vault for card mutations; MCP update_card can clear fields;
add /mcp/kanban catalog tests; align ticket-links/unification-v2 docs with the
implemented schema (board_ref, VARCHAR(16), ticket-id stability).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jtype-web depends on the sibling crate via `jtype-core = { path = "../jtype-core" }`,
added when the kanban CLI/MCP was rebuilt on jtype-core. The Dockerfile builder
(WORKDIR /app) only copied jtype-web's own Cargo.toml+src, so the path resolved
to /jtype-core which was never in the image — `cargo build --release` failed at
dependency resolution. Copy services/jtype-core to /jtype-core (its target/ is
already stripped by .dockerignore). Verified: image builds and the container
boots, serves the SPA, and registers a user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
services/jtype-web/src/handlers/tickets.rs (1)

89-178: 📐 Maintainability & Code Quality | 🟠 Major

Add test coverage for the ticket API endpoints.

The new ticket endpoints (POST /api/v1/workspaces/:workspace_id/tickets/allocate, GET /api/v1/workspaces/:workspace_id/tickets, and GET /api/v1/workspaces/:workspace_id/tickets/:ticket) are registered in the router but have no corresponding tests. Per the coding guidelines for services/jtype-web/src/handlers/**/*.rs, new endpoints must include test cases. Create services/jtype-web/tests/tickets_tests.rs with integration tests covering allocate, list, and resolve operations using the established web-service test conventions (common::setup().await, common::req(), etc.).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/jtype-web/src/handlers/tickets.rs` around lines 89 - 178, The ticket
API endpoints including the allocate function and associated list/resolve
operations lack corresponding test coverage. Create a new integration test file
at services/jtype-web/tests/tickets_tests.rs that uses the established testing
conventions from the project (such as common::setup().await and common::req())
to add comprehensive test cases covering the allocate endpoint success and
failure scenarios (including concurrent allocation conflicts), the list endpoint
for retrieving tickets, and the resolve endpoint for looking up tickets by key.
Ensure tests validate both happy paths and edge cases such as workspace
permission validation and idempotent behavior of the allocate function.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@services/jtype-web/src/handlers/tickets.rs`:
- Around line 89-178: The ticket API endpoints including the allocate function
and associated list/resolve operations lack corresponding test coverage. Create
a new integration test file at services/jtype-web/tests/tickets_tests.rs that
uses the established testing conventions from the project (such as
common::setup().await and common::req()) to add comprehensive test cases
covering the allocate endpoint success and failure scenarios (including
concurrent allocation conflicts), the list endpoint for retrieving tickets, and
the resolve endpoint for looking up tickets by key. Ensure tests validate both
happy paths and edge cases such as workspace permission validation and
idempotent behavior of the allocate function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da23bfd1-a6d3-4e8f-9779-32fad442c884

📥 Commits

Reviewing files that changed from the base of the PR and between 9868660 and da6a69d.

📒 Files selected for processing (19)
  • internal-docs/kanban/ticket-links.md
  • internal-docs/kanban/unification-v2.md
  • services/jtype-cli/src/kanban.rs
  • services/jtype-cli/src/main.rs
  • services/jtype-web/Dockerfile
  • services/jtype-web/frontend/src/api.ts
  • services/jtype-web/frontend/src/main.tsx
  • services/jtype-web/frontend/src/pages/WebBoardView.tsx
  • services/jtype-web/frontend/src/pages/Workspace.tsx
  • services/jtype-web/src/handlers/document.rs
  • services/jtype-web/src/handlers/sync.rs
  • services/jtype-web/src/handlers/tickets.rs
  • services/jtype-web/src/handlers/webhooks.rs
  • services/jtype-web/src/lib.rs
  • services/jtype-web/src/mcp/kanban_tools.rs
  • services/jtype-web/src/tasks/webhook_delivery.rs
  • services/jtype-web/tests/mcp_tests.rs
  • shared/lib/board.ts
  • src/lib/types.ts
✅ Files skipped from review due to trivial changes (1)
  • internal-docs/kanban/unification-v2.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • services/jtype-web/frontend/src/pages/Workspace.tsx
  • src/lib/types.ts
  • services/jtype-web/src/lib.rs
  • shared/lib/board.ts
  • services/jtype-web/src/handlers/document.rs
  • services/jtype-web/src/mcp/kanban_tools.rs
  • services/jtype-web/frontend/src/pages/WebBoardView.tsx
  • services/jtype-cli/src/kanban.rs
  • services/jtype-web/src/handlers/webhooks.rs
  • services/jtype-web/src/tasks/webhook_delivery.rs
  • services/jtype-cli/src/main.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant