Skip to content

fix(web): render multi-level subtasks in sidebar task tree#1204

Merged
carlosflorencio merged 4 commits into
mainfrom
feature/task-not-on-the-side-6ja
May 31, 2026
Merged

fix(web): render multi-level subtasks in sidebar task tree#1204
carlosflorencio merged 4 commits into
mainfrom
feature/task-not-on-the-side-6ja

Conversation

@carlosflorencio
Copy link
Copy Markdown
Member

@carlosflorencio carlosflorencio commented May 31, 2026

Grandchild tasks existed in the store but never appeared in the sidebar because the renderer only walked two levels. This replaces the flat root/subtask split with a recursive tree so any depth is visible, with collapse and sibling-scoped drag-and-drop at every level.

Important Changes

  • Recursive TaskTreeNode / TaskTreeLevel renderer replaces the hardcoded 2-level sidebar walk
  • Depth-aware row indent (row-indent.ts) capped at visual depth 6 to protect narrow sidebars
  • Mobile task switcher now maps parentTaskId so subtasks nest on mobile for the first time
  • Gated POST /api/v1/_test/tasks harness route seeds depth ≥ 2 chains for e2e (bypasses service depth guard)

Validation

  • make -C apps/backend fmt test lint
  • pnpm --filter @kandev/web typecheck lint test run
  • E2E: sidebar-multilevel-subtasks.spec.ts (depth-3 desktop + mid-level collapse), mobile-sidebar-subtasks.spec.ts (depth-2 mobile), existing depth-1 sidebar spec

Possible Improvements

Low risk — production CreateTask still enforces depth ≤ 1; this only affects display of existing deep trees and e2e-seeded data.

Checklist

  • I have performed a self-review of my code.
  • I have manually tested my changes and they work as expected.
  • My changes have tests that cover the new functionality and edge cases.
  • If my change touches UI files (apps/web/), I have added or updated Playwright e2e tests in apps/web/e2e/ and verified them with make test-e2e.

Made with Cursor

The sidebar renderer was hardcoded to two levels, silently dropping
depth-2+ tasks. Replace with recursive tree rendering, depth-aware
indent, mobile parentTaskId mapping, and e2e test seeding for deep trees.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

Adds backend test-harness task seeding, depth/indent infrastructure, level-scoped sortable DnD plus recursive TaskSwitcher rendering, mobile integration for parent linkage, E2E and unit tests, and depth-aware TaskItem indentation to support arbitrary-depth task hierarchies.

Changes

Multi-level subtask hierarchy support

Layer / File(s) Summary
Row indentation and task-counting infrastructure
apps/web/lib/sidebar/row-indent.ts, apps/web/lib/sidebar/row-indent.test.ts, apps/web/lib/sidebar/apply-view.ts, apps/web/lib/sidebar/apply-view-count.test.ts
Adds RowIndent, computeRowIndent, resolveRowDepth, and countGroupTasks plus tests to compute clamped pixel indentation and count all descendants in a task subtree.
Backend test-harness task seeding endpoint
apps/backend/internal/office/testharness/routes.go, apps/backend/internal/office/testharness/routes_test.go
Adds POST /api/v1/_test/tasks to seed tasks (validates fields, optional parent check, defaults state, persists bypassing service guards) and tests for multi-level chains and validation errors.
TaskItem depth-aware indentation
apps/web/components/task/task-item.tsx
Extends TaskItem with optional depth prop and applies computed indentation/connector positioning instead of legacy isSubTask padding.
DnD refactor and recursive tree rendering
apps/web/components/task/task-switcher-subtask-dnd.tsx, apps/web/components/task/task-switcher.tsx
Replaces per-parent DnD with SortableTaskNode/SortableTaskLevel level-scoped reorder primitives and refactors TaskSwitcher to a recursive TaskTree model carrying depth, collapsed state, and reorder callbacks.
Mobile task-switcher parent linkage and UI updates
apps/web/components/task/mobile/session-task-switcher-sheet-hooks.ts, apps/web/components/task/mobile/session-mobile-top-bar.tsx
Forwards parentTaskId through kanban mappings/upserts and adds data-testid/aria-label to the mobile menu button for consistent nesting and testing.
E2E helper and multi-level subtask scenarios
apps/web/e2e/helpers/api-client.ts, apps/web/e2e/tests/task/mobile-sidebar-subtasks.spec.ts, apps/web/e2e/tests/task/sidebar-multilevel-subtasks.spec.ts
Adds ApiClient.seedTask to call the test-harness endpoint and E2E specs that seed three-level chains and verify depth attributes, DOM nesting, and collapse/expand behavior.
TaskSwitcher unit tests
apps/web/components/task/task-switcher.test.tsx
Unit tests validating nested rendering, data-depth attributes, collapse behavior, group totals across descendants, descendant counts on toggle badges, and disabled-reorder styling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kdlbs/kandev#1192: The main PR adds a test-harness endpoint to seed arbitrarily deep task chains, which directly contrasts with PR #1192's new backend validation rejecting kanban subtasks beyond depth 1 via ErrSubtaskDepthExceeded.
  • kdlbs/kandev#962: Related refactor of subtask DnD and ordering; both change sidebar/task-switcher DnD approaches.
  • kdlbs/kandev#1012: Overlaps on TaskItem layout changes (connector/indent vs. menu layout adjustments).

Suggested reviewers

  • zeval

Poem

🐰 In burrows of code the branches spread,

From root to leaf the subtasks thread.
With pixels guarding every line,
The tree grows deep — our tests align.
Hooray, the nesting now is fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.13% 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
Title check ✅ Passed The title clearly and concisely summarizes the main change: enabling multi-level subtask rendering in the sidebar task tree, which is the core objective of this PR.
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.
Description check ✅ Passed The pull request description includes all required sections: a clear summary of the problem and solution, important architectural changes, validation steps with commands, and risk assessment.

✏️ 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 feature/task-not-on-the-side-6ja

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 and usage tips.

@claude
Copy link
Copy Markdown

claude Bot commented May 31, 2026

Claude finished @carlosflorencio's task in 4m 28s —— View job


Code Review

  • Read code review skill and guidelines
  • Review backend changes (routes.go, routes_test.go)
  • Review frontend components (task-switcher.tsx, task-item.tsx, task-switcher-subtask-dnd.tsx)
  • Review mobile changes
  • Review sidebar utilities (apply-view.ts, row-indent.ts)
  • Review E2E tests
  • Post inline comments for findings
  • Post summary report

Review complete. Verdict: Ready to merge with suggestions — no blockers found.

2 suggestions (non-blocking):

  1. buildKanbanTaskUpsert doesn't map parentTaskId, so subtasks created via the task-creation dialog won't appear nested for the rest of that session (see inline comment on session-task-switcher-sheet-hooks.ts:108)
  2. countGroupTasks has no cycle guard — cheap visited-set would make the invariant explicit (see inline comment on apply-view.ts:456)

The recursive TaskTreeNode / TaskTreeLevel architecture is clean, DnD scoping is correctly sibling-only at each level, test coverage (unit + E2E desktop + mobile) is solid, and the backend harness route is minimal and correctly gated behind KANDEV_E2E_MOCK.

Comment thread apps/web/lib/sidebar/apply-view.ts
@claude
Copy link
Copy Markdown

claude Bot commented May 31, 2026

Findings

Suggestion (recommended, doesn't block)

  1. buildKanbanTaskUpsert missing parentTaskIdapps/web/components/task/mobile/session-task-switcher-sheet-hooks.ts:105

    • Issue: toSheetItem and mapSnapshotToKanban now carry parentTaskId, but buildKanbanTaskUpsert (called by handleTaskCreated when a task is created via the dialog) doesn't map parent_id from the API response.
    • Why: A subtask created in-session via the dialog won't appear nested in the sidebar for the rest of that session — it falls into the root bucket until the next server-driven refresh. Now that the sidebar recursively renders parentTaskId, this gap is more visible.
    • Fix: Add parentTaskId: task.parent_id ?? undefined to the object returned by buildKanbanTaskUpsert (~line 329).
  2. countGroupTasks has no cycle guardapps/web/lib/sidebar/apply-view.ts:443

    • Issue: The stack-based traversal would loop infinitely if subTasksByParentId ever contained a cycle (A → B → A).
    • Why: The data layer prevents cycles today, but a visited-set is cheap insurance that makes the invariant explicit and survives future callers/tests that may not share that guarantee.
    • Fix: Add const visited = new Set<string>() and if (visited.has(task.id)) continue; visited.add(task.id) inside the loop. Fix this →

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready to merge with suggestions

The recursive TaskTreeNode / TaskTreeLevel approach is clean and well-bounded. The TaskTreeContext pattern correctly avoids prop-drilling across arbitrary depths, DnD scoping is correctly sibling-only at each level, and the MAX_VISUAL_DEPTH = 6 cap protects narrow sidebars. The backend harness addition is minimal, well-guarded by KANDEV_E2E_MOCK, and thoroughly tested. Unit test coverage for the new utilities (row-indent.ts, countGroupTasks, TaskSwitcher recursive rendering) and E2E coverage for desktop and mobile are solid.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 15 files

You’re at about 93% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 31, 2026

Greptile Summary

This PR replaces the hardcoded two-level sidebar walker with a mutually recursive TaskTreeNode/TaskTreeLevel renderer that surfaces grandchild tasks (and beyond) that already existed in the store but were previously invisible. It also wires parentTaskId into the mobile switcher's snapshot mapper and the buildKanbanTaskUpsert path, adds a depth-capped row-indent utility, and gates a test-harness POST /api/v1/_test/tasks route for seeding depth-≥-2 chains in e2e.

  • Recursive renderer replaces GroupTaskList/SortableTaskBlock; per-level DnD (SortableTaskLevel) replaces the split group/subtask DnD hooks, with cursor-grab correctly scoped to draggable levels only.
  • countGroupTasks (iterative DFS with cycle guard) is introduced and now used for both the group header count and the subtask-toggle badge, eliminating the previously reported asymmetry.
  • buildKanbanTaskUpsert now maps task.parent_id → parentTaskId, fixing the mobile-sidebar regression where WS-upserted subtasks re-appeared at root level.

Confidence Score: 5/5

Safe to merge — all three issues flagged in the previous review have been addressed, and the new recursive renderer is architecturally sound.

All prior findings are resolved: buildKanbanTaskUpsert maps parent_id, the toggle badge uses countGroupTasks matching the group header, and non-draggable rows omit the grab cursor. The recursive renderer cannot loop infinitely because separateSubtasks removes cycle members from rootTasks. The harness route follows established gating patterns.

No files require special attention.

Important Files Changed

Filename Overview
apps/web/components/task/task-switcher.tsx Core recursive renderer. Replaces flat GroupTaskList with TaskTreeNode/TaskTreeLevel mutual recursion; introduces TaskTreeContext. countGroupTasks now used for both group header and toggle badge — previous asymmetry resolved.
apps/web/components/task/task-switcher-subtask-dnd.tsx Refactored to generic SortableTaskNode/SortableTaskLevel. isDraggable=false path renders without grab cursor; grab-cursor concern from previous review is resolved.
apps/web/components/task/mobile/session-task-switcher-sheet-hooks.ts parentTaskId mapped in both mapSnapshotToKanban/toSheetItem and buildKanbanTaskUpsert, fixing the prior P1 where WS-upserted subtasks reset to root level in the mobile sidebar.
apps/web/lib/sidebar/apply-view.ts Adds countGroupTasks — iterative DFS with visited-set cycle guard. applyView/separateSubtasks unchanged; the existing map already contained all ancestor-child pairs.
apps/web/lib/sidebar/row-indent.ts New utility computing pixel indentation capped at MAX_VISUAL_DEPTH=6. Depth-0 connectorLeftPx is -6px but never applied (guarded by depth>0 check in task-item.tsx).
apps/backend/internal/office/testharness/routes.go Adds POST /api/v1/_test/tasks harness route bypassing depth guard intentionally; gated behind KANDEV_E2E_MOCK consistent with existing harness routes.

Reviews (4): Last reviewed commit: "docs(web): move SortableTaskNode JSDoc t..." | Re-trigger Greptile

Map parentTaskId in buildKanbanTaskUpsert so in-session subtask creation
nests immediately on mobile. Add visited-set guard to countGroupTasks.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 31, 2026

Claude finished @carlosflorencio's task in 3m 33s —— View job


Code Review

  • Load review skill and guidelines
  • Review backend changes (routes.go, routes_test.go)
  • Review frontend core (task-switcher.tsx, task-switcher-subtask-dnd.tsx, task-item.tsx)
  • Review mobile changes (session-task-switcher-sheet-hooks.ts, session-mobile-top-bar.tsx)
  • Review sidebar utilities (apply-view.ts, row-indent.ts)
  • Review E2E tests and unit tests
  • Check fixes from previous review round (mobile upsert + cycle guard)
  • Post inline comments and summary

Findings

Suggestion (recommended, doesn't block)

  1. Grab cursor on non-reorderable rowstask-switcher-subtask-dnd.tsx:75
    cursor-grab is unconditional on the inner handle div inside SortableTaskNode. When a caller omits onReorderSubtasks, SortableTaskLevel skips the DnD context, but the grab cursor still renders on every child row — including rows that can't be dragged. This is more visible now that every depth renders through SortableTaskNode. See inline comment for a targeted fix.

  2. Toggle badge shows direct-child count; collapse hides all descendantstask-switcher.tsx:239
    subtaskCount: subs!.length counts immediate children. But collapsing a parent hides its entire subtree (because TaskTreeLevel short-circuits on subsHidden). Before this PR the cap was depth 1, so these always agreed. Now a badge of "1" can collapse 4+ rows. countGroupTasks(subs!, ctx.subTasksByParentId) would keep the count aligned with what actually disappears. See inline comment.

Previous-round fixes verified ✓

  • buildKanbanTaskUpsert now maps parentTaskId: task.parent_id ?? undefined (commit 8e0ff26) — subtasks created in-session via the dialog nest immediately on mobile.
  • countGroupTasks has a visited-set cycle guard + regression test in apply-view-count.test.ts (commit 8e0ff26).

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready to merge with suggestions

The recursive TaskTreeNode / TaskTreeLevel architecture is clean and well-scoped — TaskTreeContext correctly avoids prop-drilling across arbitrary depths, DnD is correctly sibling-scoped at each level, and MAX_VISUAL_DEPTH = 6 protects narrow sidebars. The backend harness route is minimal, correctly gated behind KANDEV_E2E_MOCK, and the parent-exists validation prevents orphan rows. Unit test coverage (row-indent.ts, countGroupTasks, TaskSwitcher recursive rendering) and E2E coverage (depth-3 desktop + mobile) are solid.

Comment thread apps/web/components/task/task-switcher-subtask-dnd.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/backend/internal/office/testharness/routes.go (1)

37-48: 💤 Low value

Optional: Consider consistent error response pattern across handlers.

The new errJSON helper and constants errInvalidJSONPrefix/respKeyError are only used by the new seedTaskHandler, while existing handlers in this file (e.g., lines 188, 192, 262) inline gin.H{"error": ...}. This creates a minor inconsistency within the file.

For a uniform codebase, either adopt errJSON across all handlers or stick with the inline pattern. Since this is test-harness code, the current mix is low-risk.

🤖 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 `@apps/backend/internal/office/testharness/routes.go` around lines 37 - 48, The
file mixes a new errJSON helper and constants (errInvalidJSONPrefix,
respKeyError) with older handlers that inline gin.H{"error": ...}; pick one
consistent approach and apply it across handlers: either replace inline
responses in existing handlers (those that currently call c.JSON(...,
gin.H{"error": ...})) to use errJSON(c, code, msg) and use errInvalidJSONPrefix
where malformed-JSON messages are constructed, or remove the unused
errJSON/consts and keep the inline pattern; update all occurrences of error
responses and tests accordingly so seedTaskHandler and the other handlers all
use the same respKeyError/errJSON API or the inline gin.H literal.
🤖 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.

Nitpick comments:
In `@apps/backend/internal/office/testharness/routes.go`:
- Around line 37-48: The file mixes a new errJSON helper and constants
(errInvalidJSONPrefix, respKeyError) with older handlers that inline
gin.H{"error": ...}; pick one consistent approach and apply it across handlers:
either replace inline responses in existing handlers (those that currently call
c.JSON(..., gin.H{"error": ...})) to use errJSON(c, code, msg) and use
errInvalidJSONPrefix where malformed-JSON messages are constructed, or remove
the unused errJSON/consts and keep the inline pattern; update all occurrences of
error responses and tests accordingly so seedTaskHandler and the other handlers
all use the same respKeyError/errJSON API or the inline gin.H literal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90f3b7c8-8b16-48f6-9436-761754bac150

📥 Commits

Reviewing files that changed from the base of the PR and between 1931619 and 8e0ff26.

📒 Files selected for processing (15)
  • apps/backend/internal/office/testharness/routes.go
  • apps/backend/internal/office/testharness/routes_test.go
  • apps/web/components/task/mobile/session-mobile-top-bar.tsx
  • apps/web/components/task/mobile/session-task-switcher-sheet-hooks.ts
  • apps/web/components/task/task-item.tsx
  • apps/web/components/task/task-switcher-subtask-dnd.tsx
  • apps/web/components/task/task-switcher.test.tsx
  • apps/web/components/task/task-switcher.tsx
  • apps/web/e2e/helpers/api-client.ts
  • apps/web/e2e/tests/task/mobile-sidebar-subtasks.spec.ts
  • apps/web/e2e/tests/task/sidebar-multilevel-subtasks.spec.ts
  • apps/web/lib/sidebar/apply-view-count.test.ts
  • apps/web/lib/sidebar/apply-view.ts
  • apps/web/lib/sidebar/row-indent.test.ts
  • apps/web/lib/sidebar/row-indent.ts

Comment thread apps/web/components/task/task-switcher.tsx
Skip sortable wiring on non-draggable tree levels so nested rows no
longer show a grab cursor when reorder is disabled. Subtask collapse
badges now count all descendants to match what collapse hides.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 31, 2026

Claude finished @carlosflorencio's task in 2m 45s —— View job


Code Review

  • Load review skill and guidelines
  • Read latest commit changes (b89fafd — gate drag cursor + descendant subtask counts)
  • Review all changed files in the PR diff
  • Check that previous review suggestions are properly addressed
  • Post inline comments for any new findings
  • Post summary report

Findings

Previous-round fixes verified ✓

  • buildKanbanTaskUpsert missing parentTaskId — fixed at session-task-switcher-sheet-hooks.ts:315 (parentTaskId: task.parent_id ?? undefined). In-session subtask creation now nests immediately on mobile.
  • countGroupTasks cycle guard — fixed in apply-view.ts:448 with a visited Set; regression test added in apply-view-count.test.ts.
  • Grab cursor on non-reorderable rows — fixed by extracting DraggableSortableTaskNode (private, always-calls-hook) and wrapping it in a public SortableTaskNode that short-circuits to a plain <div> when isDraggable=false. SortableTaskLevel passes isDraggable = !!onReorder down. Unit test added.
  • Toggle badge showing direct-child count instead of descendant count — fixed in task-switcher.tsx:240 via countGroupTasks(subs!, ctx.subTasksByParentId). Unit test added.

Nitpick (no-block)

  1. Orphaned JSDoc blocktask-switcher-subtask-dnd.tsx:24-33
    Two consecutive /**…*/ blocks precede DraggableSortableTaskNode; only the second attaches to the function. The valuable architectural note about the handle/subtree-split is invisible to IDE hover. Suggested fix posted as inline comment — merge both blocks into one, or move the architectural description to the public SortableTaskNode wrapper.

Summary

Severity Count
Blocker 0
Suggestion 0
Nitpick 1

Verdict: Ready to merge

All four suggestions from the two prior review rounds are correctly addressed and backed by new unit tests. The DraggableSortableTaskNode extraction is clean and satisfies the React hooks rule. The recursive TaskTreeNode/TaskTreeLevel architecture, sibling-scoped DnD, depth-capped indentation, backend harness route, and E2E coverage are all solid. The one nitpick (orphaned JSDoc) is cosmetic and doesn't affect behaviour.

Comment thread apps/web/components/task/task-switcher-subtask-dnd.tsx Outdated
Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented May 31, 2026

Claude finished @carlosflorencio's task in 1m 50s —— View job


Code Review

  • Load review skill and guidelines
  • Review latest commit (306d16c — move SortableTaskNode JSDoc to exported wrapper)
  • Verify all previous-round suggestions are addressed
  • Final pass over full diff for any remaining issues
  • Post summary

All previous-round findings resolved ✓

Round Finding Status
1 buildKanbanTaskUpsert missing parentTaskId — in-session subtask creation didn't nest on mobile Fixed in 8e0ff26 (parentTaskId: task.parent_id ?? undefined at line 315)
1 countGroupTasks no cycle guard — infinite loop risk on cyclic data Fixed in 8e0ff26visited Set added with regression test in apply-view-count.test.ts
2 Grab cursor on non-reorderable rows — cursor-grab rendered even when DnD was skipped Fixed in b89fafdSortableTaskLevel passes isDraggable = !!onReorder; SortableTaskNode short-circuits to a plain wrapper when false
2 Toggle badge showed direct-child count; collapse hides whole subtree Fixed in b89fafd — badge now uses countGroupTasks(subs!, ctx.subTasksByParentId)
3 Orphaned JSDoc block — architectural note invisible to IDE hover Fixed in 306d16c — consolidated into the exported SortableTaskNode JSDoc; private DraggableSortableTaskNode gets a one-liner

Summary

Verdict: Ready to merge

No open findings. The recursive TaskTreeNode/TaskTreeLevel architecture is clean — TaskTreeContext avoids prop-drilling across arbitrary depths, DnD is correctly sibling-scoped at each level, and MAX_VISUAL_DEPTH = 6 protects narrow sidebars. The backend harness route is minimal, correctly gated behind KANDEV_E2E_MOCK, and parent-existence is validated before seeding. Unit coverage (row-indent, countGroupTasks, TaskSwitcher recursive rendering + cursor + badge + collapse) and E2E coverage (depth-3 desktop + depth-2 mobile) are solid.

@carlosflorencio carlosflorencio merged commit 41ad777 into main May 31, 2026
46 checks passed
@carlosflorencio carlosflorencio deleted the feature/task-not-on-the-side-6ja branch May 31, 2026 20:37
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