Skip to content

feat(kanban): add editable swimlanes - #61

Merged
cnjack merged 2 commits into
mainfrom
codex/editable-swimlanes
Jul 29, 2026
Merged

feat(kanban): add editable swimlanes#61
cnjack merged 2 commits into
mainfrom
codex/editable-swimlanes

Conversation

@cnjack

@cnjack cnjack commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • add board-level editable swimlanes with stable keys and card frontmatter mapping
  • add add/rename/reorder/delete flows, Unassigned handling, Peek selection, and recoverable priority conversion
  • keep Desktop, Web, and board-react behavior aligned through the shared board layer
  • add UI/data design docs, mockups, i18n updates, unit coverage, and Playwright E2E coverage

Design review

  • reviewed with Grok CLI and Kimi CLI
  • incorporated concurrency, persistence, migration locking, duplicate-key, accessibility, and drag-order findings

Validation

  • 22 related unit tests passed
  • 5 swimlane Playwright E2E tests passed
  • Desktop build passed
  • Web build passed
  • board-react build passed
  • 38 Rust core tests passed
  • i18n extraction/compilation and visual QA passed

Summary by CodeRabbit

  • New Features

    • Added editable custom swimlanes with persistent lane assignments.
    • Manage swimlanes with add, rename, reorder, color, and delete actions.
    • Choose whether cards remain unassigned or move before deleting a lane.
    • Assign or clear swimlanes directly from card details.
    • Convert priority- or assignee-based lanes into editable swimlanes with progress and recovery support.
    • Added warnings and filtering for missing or invalid swimlane assignments.
    • Added status-column management, including colors, WIP limits, ordering, and done-column selection.
  • Bug Fixes

    • Improved reliability when saving card updates and switching between cards quickly.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cnjack, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 30b2b7c0-5153-4c14-b92e-a70b0bfffcbd

📥 Commits

Reviewing files that changed from the base of the PR and between 7737c2c and 2ce22a5.

⛔ Files ignored due to path filters (10)
  • packages/board-react/dist/index.js is excluded by !**/dist/**
  • packages/board-react/dist/style.css is excluded by !**/dist/**
  • shared/i18n/locales/en/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/en/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ja/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ja/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ko/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ko/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/zh/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/zh/messages.po is excluded by !**/i18n/locales/**
📒 Files selected for processing (15)
  • internal-docs/kanban/gaps-and-roadmap.md
  • packages/board-react/src/JTypeBoard.tsx
  • services/jtype-web/frontend/src/pages/WebBoardView.tsx
  • shared/components/board/BoardSurface.tsx
  • shared/components/board/BoardSwimlanes.tsx
  • shared/components/board/LaneDetailsPopover.tsx
  • shared/components/board/StatusActionsMenu.tsx
  • shared/components/board/StatusManagerDialog.tsx
  • shared/components/board/SwimlaneConversionDialog.tsx
  • shared/components/board/SwimlaneDeleteDialog.tsx
  • shared/components/board/SwimlaneManagerDialog.tsx
  • shared/lib/board.ts
  • tests/e2e/board-swimlanes.spec.ts
  • tests/fixtures/board-swimlanes.tsx
  • tests/unit/boardSwimlanes.spec.ts
📝 Walkthrough

Walkthrough

Adds persistent custom swimlanes across the shared board model, desktop, web, and board-react consumers. It introduces management, deletion, assignment, dangling-reference, and derived-lane conversion flows, with accompanying UI mockups, design documentation, unit tests, and Playwright coverage.

Changes

Custom swimlane management

Layer / File(s) Summary
Swimlane design contracts
internal-docs/kanban/*
Defines persistent lane keys, frontmatter mapping, Unassigned and dangling semantics, management dialogs, deletion strategies, conversion markers, and UI behavior.
Canonical swimlane model
shared/lib/board.ts, shared/components/board/types.ts, shared/components/board/controls.tsx
Adds swimlane types, normalization and validation helpers, frontmatter patching, filtering, canonical configuration aliases, and swimlane-aware component contracts.
Board management and conversion UI
shared/components/board/*
Adds editable swimlane rendering, lane/status managers, deletion and conversion dialogs, custom-lane menus, missing-card handling, and Peek assignment with debounced patch flushing.
Desktop, web, and embedded integration
src/components/BoardView.tsx, services/jtype-web/frontend/src/pages/WebBoardView.tsx, packages/board-react/src/*, src/lib/types.ts
Uses canonical configuration and card patching across consumers, synchronizes document hashes and bulk updates, preserves card bodies, removes swimlane fields from templates, and displays swimlane details.
Validation and E2E coverage
tests/unit/*, tests/e2e/*, tests/fixtures/*, playwright.board.config.ts
Tests custom mapping, validation, dangling references, deletion modes, conversion, rendering, and board-react persistence through unit and Playwright suites.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • cnjack/jtype#35: Shares the board rendering and configuration integration points modified by this swimlane work.
  • cnjack/jtype#47: Shares embedded board integration changes involving BoardSurface, BoardPeek, and board-react wiring.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding editable swimlanes to Kanban boards.
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.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/editable-swimlanes

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: 12

🧹 Nitpick comments (7)
packages/board-react/src/JTypeBoard.tsx (1)

262-279: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

In-place mutation of snap.metaByPath mutates a React state object.

snapRef.current is the same object held in snapshot state, so writing into its map updates rendered state invisibly (no re-render, and any memo keyed on snapshot won't see it). It works today because the map is only read for base-hash lookups, but a copy-on-write snapshot would be safer against future memoization.

🤖 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 `@packages/board-react/src/JTypeBoard.tsx` around lines 262 - 279, Update
saveDocContent so it does not mutate snap.metaByPath in place after
client.saveDocument; create a copy-on-write snapshot/map update and keep the
updated snapshot reference synchronized with the relevant state/ref used by
JTypeBoard. Preserve the existing metadata merge and saved contentHash behavior
for the relativePath entry.
shared/components/board/BoardSurface.tsx (1)

392-426: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Silent drop of lane move/color actions while a mutation is in flight.

customLaneMutationBusy returns early with no feedback, so a quick second "Move up" or color click is discarded invisibly. Consider disabling the affordance (pass a busy flag to BoardSwimlanes) rather than swallowing the intent.

🤖 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 392 - 426, Update the
custom lane controls around moveCustomLane and setCustomLaneColor so in-flight
mutations expose a busy state to BoardSwimlanes and disable the move/color
affordances while customLaneMutationBusy is true, instead of silently ignoring
clicks. Preserve the existing mutation lock and reset it in finally.
shared/components/board/StatusManagerDialog.tsx (1)

104-229: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

This status action menu duplicates statusMenu in BoardSwimlanes.tsx almost verbatim.

Rename / move / done-toggle / WIP / color swatches / delete are repeated in both files, including the color palette markup and disabled rules. Extracting one StatusActionsMenu component (props: column, index, siblings, actions, portalClassName) would keep the two surfaces from drifting.

🤖 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/StatusManagerDialog.tsx` around lines 104 - 229,
Extract the duplicated status action menu markup from StatusManagerDialog and
BoardSwimlanes into a shared StatusActionsMenu component, accepting column,
index, siblings, actions, and portalClassName props. Move the rename, reorder,
done-toggle, WIP, color palette, and delete behavior—including disabled
rules—into that component, then replace both existing implementations with it
while preserving their current behavior and styling.
shared/components/board/BoardSwimlanes.tsx (1)

98-118: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: memoize the partition and reuse groupValueOf for header counts.

partitionSwimlanes plus the per-column cards.filter(...) re-run on every render (including menu open/close and copy-state changes), and the header count re-implements the grouping switch already exported as groupValueOf. A useMemo on the grid plus deriving counts from it would remove both the duplication and the repeated O(cards × columns) work.

Also applies to: 529-534

🤖 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/BoardSwimlanes.tsx` around lines 98 - 118, In
BoardSwimlanes, memoize the partitionSwimlanes result and derive header counts
from the memoized grid instead of rerunning per-column cards.filter calls on
every render. Reuse the exported groupValueOf logic for grouping/count
derivation, preserving the existing custom-lane handling and visible-lane
behavior while removing the duplicated grouping switch.
shared/components/board/SwimlaneManagerDialog.tsx (1)

338-341: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the index from the row key. lane.key is the stable identity this PR introduces; appending -${index} makes React treat a reordered lane as a new node, remounting inputs/popovers mid-edit. If the intent was to survive duplicate keys, note that validateSwimlanes already surfaces duplicate_swimlane_key.

🤖 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/SwimlaneManagerDialog.tsx` around lines 338 - 341,
Update the row key in the SwimlaneManagerDialog lane list to use only the stable
lane.key value, removing the index suffix. Preserve validateSwimlanes
duplicate-key handling and leave the row’s other attributes unchanged.
shared/components/board/SwimlaneConversionDialog.tsx (1)

85-87: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded English plural for card counts. Both dialogs render {count} <Trans>cards</Trans>, separating the number from the translatable unit so locales with different plural rules or word order can't be translated correctly. Use Lingui's Plural macro at each site.

  • shared/components/board/SwimlaneConversionDialog.tsx#L85-L87: replace with <Plural value={row.cardCount} one="# card" other="# cards" />.
  • shared/components/board/SwimlaneManagerDialog.tsx#L436-L438: replace with <Plural value={counts.get(lane.key) ?? 0} one="# card" other="# cards" /> (the “Used by” row near Line 552 uses the same pattern).
🤖 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/SwimlaneConversionDialog.tsx` around lines 85 - 87,
Replace the separated count and hardcoded “cards” text with Lingui’s Plural
macro in shared/components/board/SwimlaneConversionDialog.tsx lines 85-87, using
row.cardCount with singular and plural card forms. Apply the same change in
shared/components/board/SwimlaneManagerDialog.tsx lines 436-438, using
counts.get(lane.key) ?? 0; also update the nearby “Used by” row in that file if
it uses the same pattern.
shared/components/board/SwimlaneDeleteDialog.tsx (1)

45-48: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Effect reads targets but omits it from deps. If targets is computed after the lane is set (parent recompute), targetKey stays "". It also trips react-hooks/exhaustive-deps, which the repo otherwise silences explicitly. Either add the dep with a guard or annotate the intent.

🤖 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/SwimlaneDeleteDialog.tsx` around lines 45 - 48,
Update the useEffect in SwimlaneDeleteDialog to include targets in its
dependency handling so targetKey refreshes when targets are recomputed after the
lane changes. Add an appropriate guard if needed to avoid unintended resets, and
preserve the existing mode reset behavior while satisfying the repository’s
exhaustive-deps convention.
🤖 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/gaps-and-roadmap.md`:
- Line 42: Update the roadmap’s aggregate counts and priority scheduling
sections to remove C4 from pending scope or explicitly label those references as
historical, keeping them consistent with C4’s implemented status in the roadmap
table.

In `@packages/board-react/src/JTypeBoard.tsx`:
- Around line 347-365: Add a readOnly guard at the start of updateCards,
matching setConfig’s existing behavior, so bulk card updates return without
writing when the adapter is configured read-only; preserve the current update,
reload, progress, and error handling for writable configurations.

In `@services/jtype-web/frontend/src/pages/WebBoardView.tsx`:
- Around line 365-394: In the updateCards handler, remove the per-card
setMetaByPath call inside the updates loop and commit the accumulated
workingMeta once after processing, or rely on the subsequent load refresh.
Ensure missing metadata entries are handled so skipped update.cardId values do
not leave the swimlane conversion pending; preserve progress reporting and error
reload behavior.

In `@shared/components/board/BoardSurface.tsx`:
- Around line 276-375: Prevent the conversion effect around the shown useEffect
from retrying indefinitely when card updates make no observable progress. Track
consecutive passes with the same pending card update set, and after the defined
retry limit surface a conversionError and stop conversion instead of
unconditionally scheduling another pass; reset the no-progress tracking when the
pending set changes or progress occurs, while preserving normal migration
completion.

In `@shared/components/board/BoardSwimlanes.tsx`:
- Around line 443-448: Update the copy click handler in BoardSwimlanes around
the navigator.clipboard call to guard the clipboard object before invoking
writeText, and handle rejected writeText promises without creating an unhandled
rejection. Only update copied state and schedule its reset after a successful
copy; leave the behavior unchanged when the API is unavailable or the permission
is denied.
- Around line 400-405: Move the Details PopoverButton and its associated popover
out of the MenuItem structure in BoardSwimlanes, and control or trigger it
independently from the menu’s close-on-click behavior. Preserve the existing
Details label, icon, styling, and popover content while ensuring keyboard and
mouse activation reliably opens it after the menu closes.
- Around line 642-646: Update the status swimlane menu flow around statusMenu so
neighbour lookup and boundary checks use the status lane list (visibleLanes),
not the unrelated columns array. Pass the appropriate lane collection or
explicit neighbour array into statusMenu(lane, laneIndex), while preserving the
existing ordering behavior for other swimlane dimensions.

In `@shared/components/board/SwimlaneConversionDialog.tsx`:
- Around line 74-95: The empty-state message in the rows list should use the
same condition as the confirm-button logic. Update the conditional rendering
within the SwimlaneConversionDialog component to check rows.length === 0 instead
of total < 1, then remove the now-unused total value from the component if
nothing else references it.

In `@shared/components/board/SwimlaneDeleteDialog.tsx`:
- Around line 110-119: In SwimlaneDeleteDialog, move the ListboxSelect out of
the Radio option and render it as a sibling after the Radio, enabled only when
mode === "move". Keep its targetKey/onChange behavior intact, and ensure target
validation or updates remain outside the RadioGroup so the radio contains no
nested interactive control.

In `@shared/components/board/SwimlaneManagerDialog.tsx`:
- Around line 536-541: Update the clipboard handler around
navigator.clipboard.writeText so rejected writes are handled explicitly,
including permission-denied and unavailable-context cases. Preserve the existing
success behavior that sets copiedKey and clears it after the timeout, while
adding the appropriate user feedback through the dialog’s established UI state
or notification mechanism.

In `@shared/lib/board.ts`:
- Around line 154-156: Update the patch.custom loop in the board update flow to
skip entries whose keys are reserved core frontmatter fields, including
swimlaneKey, status, and other canonical fields. Apply only non-reserved custom
entries to next, preserving the existing null-to-empty-string conversion.

In `@tests/e2e/board-swimlanes.spec.ts`:
- Around line 94-100: Update the test around the swimlane removal flow to record
fixture action calls, then assert that the card-moving updateCards action occurs
before the setConfig call that removes the Growth lane. Keep the existing
final-state assertions, and identify the lane-removal call specifically rather
than asserting against unrelated configuration updates.

---

Nitpick comments:
In `@packages/board-react/src/JTypeBoard.tsx`:
- Around line 262-279: Update saveDocContent so it does not mutate
snap.metaByPath in place after client.saveDocument; create a copy-on-write
snapshot/map update and keep the updated snapshot reference synchronized with
the relevant state/ref used by JTypeBoard. Preserve the existing metadata merge
and saved contentHash behavior for the relativePath entry.

In `@shared/components/board/BoardSurface.tsx`:
- Around line 392-426: Update the custom lane controls around moveCustomLane and
setCustomLaneColor so in-flight mutations expose a busy state to BoardSwimlanes
and disable the move/color affordances while customLaneMutationBusy is true,
instead of silently ignoring clicks. Preserve the existing mutation lock and
reset it in finally.

In `@shared/components/board/BoardSwimlanes.tsx`:
- Around line 98-118: In BoardSwimlanes, memoize the partitionSwimlanes result
and derive header counts from the memoized grid instead of rerunning per-column
cards.filter calls on every render. Reuse the exported groupValueOf logic for
grouping/count derivation, preserving the existing custom-lane handling and
visible-lane behavior while removing the duplicated grouping switch.

In `@shared/components/board/StatusManagerDialog.tsx`:
- Around line 104-229: Extract the duplicated status action menu markup from
StatusManagerDialog and BoardSwimlanes into a shared StatusActionsMenu
component, accepting column, index, siblings, actions, and portalClassName
props. Move the rename, reorder, done-toggle, WIP, color palette, and delete
behavior—including disabled rules—into that component, then replace both
existing implementations with it while preserving their current behavior and
styling.

In `@shared/components/board/SwimlaneConversionDialog.tsx`:
- Around line 85-87: Replace the separated count and hardcoded “cards” text with
Lingui’s Plural macro in shared/components/board/SwimlaneConversionDialog.tsx
lines 85-87, using row.cardCount with singular and plural card forms. Apply the
same change in shared/components/board/SwimlaneManagerDialog.tsx lines 436-438,
using counts.get(lane.key) ?? 0; also update the nearby “Used by” row in that
file if it uses the same pattern.

In `@shared/components/board/SwimlaneDeleteDialog.tsx`:
- Around line 45-48: Update the useEffect in SwimlaneDeleteDialog to include
targets in its dependency handling so targetKey refreshes when targets are
recomputed after the lane changes. Add an appropriate guard if needed to avoid
unintended resets, and preserve the existing mode reset behavior while
satisfying the repository’s exhaustive-deps convention.

In `@shared/components/board/SwimlaneManagerDialog.tsx`:
- Around line 338-341: Update the row key in the SwimlaneManagerDialog lane list
to use only the stable lane.key value, removing the index suffix. Preserve
validateSwimlanes duplicate-key handling and leave the row’s other attributes
unchanged.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3b48ba1b-a9c6-4790-9c22-9316ac051f94

📥 Commits

Reviewing files that changed from the base of the PR and between ef7f90c and 7737c2c.

⛔ Files ignored due to path filters (16)
  • internal-docs/kanban/mockups/swimlane-management-ui.png is excluded by !**/*.png
  • packages/board-react/dist/index.d.ts is excluded by !**/dist/**
  • packages/board-react/dist/index.js is excluded by !**/dist/**
  • packages/board-react/dist/style.css is excluded by !**/dist/**
  • services/jtype-web/frontend/src/i18n/locales/en/messages.po is excluded by !**/i18n/locales/**
  • services/jtype-web/frontend/src/i18n/locales/ja/messages.po is excluded by !**/i18n/locales/**
  • services/jtype-web/frontend/src/i18n/locales/ko/messages.po is excluded by !**/i18n/locales/**
  • services/jtype-web/frontend/src/i18n/locales/zh/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/en/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/en/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ja/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ja/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ko/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/ko/messages.po is excluded by !**/i18n/locales/**
  • shared/i18n/locales/zh/messages.mjs is excluded by !**/i18n/locales/**
  • shared/i18n/locales/zh/messages.po is excluded by !**/i18n/locales/**
📒 Files selected for processing (27)
  • internal-docs/kanban/gaps-and-roadmap.md
  • internal-docs/kanban/mockups/swimlane-management-ui.html
  • internal-docs/kanban/swimlane-management-design.md
  • internal-docs/kanban/swimlane-management-ui-design.md
  • packages/board-react/src/CardDetail.tsx
  • packages/board-react/src/JTypeBoard.tsx
  • packages/board-react/src/boardData.ts
  • packages/board-react/src/strings.ts
  • playwright.board.config.ts
  • services/jtype-web/frontend/src/pages/WebBoardView.tsx
  • shared/components/board/BoardPeek.tsx
  • shared/components/board/BoardSurface.tsx
  • shared/components/board/BoardSwimlanes.tsx
  • shared/components/board/StatusManagerDialog.tsx
  • shared/components/board/SwimlaneConversionDialog.tsx
  • shared/components/board/SwimlaneDeleteDialog.tsx
  • shared/components/board/SwimlaneManagerDialog.tsx
  • shared/components/board/controls.tsx
  • shared/components/board/types.ts
  • shared/lib/board.ts
  • src/components/BoardView.tsx
  • src/lib/types.ts
  • tests/e2e/board-swimlanes.spec.ts
  • tests/fixtures/board-swimlanes.html
  • tests/fixtures/board-swimlanes.tsx
  • tests/unit/boardReactViewPrefs.spec.ts
  • tests/unit/boardSwimlanes.spec.ts

Comment thread internal-docs/kanban/gaps-and-roadmap.md
Comment thread packages/board-react/src/JTypeBoard.tsx
Comment thread services/jtype-web/frontend/src/pages/WebBoardView.tsx
Comment thread shared/components/board/BoardSurface.tsx
Comment thread shared/components/board/BoardSwimlanes.tsx Outdated
Comment thread shared/components/board/SwimlaneConversionDialog.tsx
Comment thread shared/components/board/SwimlaneDeleteDialog.tsx Outdated
Comment on lines +536 to +541
onClick={() => {
void navigator.clipboard?.writeText(lane.key).then(() => {
setCopiedKey(lane.key);
window.setTimeout(() => setCopiedKey(null), 1600);
});
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Unhandled rejection when clipboard write is denied. navigator.clipboard.writeText rejects on permission denial or in non-secure contexts (e.g. embedded webviews); with only .then() this surfaces as an unhandled rejection and the user gets no feedback.

🛡️ Proposed fix
                                   onClick={() => {
-                                    void navigator.clipboard?.writeText(lane.key).then(() => {
-                                      setCopiedKey(lane.key);
-                                      window.setTimeout(() => setCopiedKey(null), 1600);
-                                    });
+                                    void navigator.clipboard
+                                      ?.writeText(lane.key)
+                                      .then(() => {
+                                        setCopiedKey(lane.key);
+                                        window.setTimeout(() => setCopiedKey(null), 1600);
+                                      })
+                                      .catch(() =>
+                                        setRowError({ key: lane.key, message: t`Copy failed.` }),
+                                      );
                                   }}
📝 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
onClick={() => {
void navigator.clipboard?.writeText(lane.key).then(() => {
setCopiedKey(lane.key);
window.setTimeout(() => setCopiedKey(null), 1600);
});
}}
onClick={() => {
void navigator.clipboard
?.writeText(lane.key)
.then(() => {
setCopiedKey(lane.key);
window.setTimeout(() => setCopiedKey(null), 1600);
})
.catch(() =>
setRowError({ key: lane.key, message: t`Copy failed.` }),
);
}}
🤖 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/SwimlaneManagerDialog.tsx` around lines 536 - 541,
Update the clipboard handler around navigator.clipboard.writeText so rejected
writes are handled explicitly, including permission-denied and
unavailable-context cases. Preserve the existing success behavior that sets
copiedKey and clears it after the timeout, while adding the appropriate user
feedback through the dialog’s established UI state or notification mechanism.

Comment thread shared/lib/board.ts
Comment thread tests/e2e/board-swimlanes.spec.ts
@cnjack
cnjack merged commit 7833439 into main Jul 29, 2026
3 checks passed
@cnjack
cnjack deleted the codex/editable-swimlanes branch July 29, 2026 15:45
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