Skip to content

Add bounded sidebar task navigation stack - #721

Merged
Nek-12 merged 95 commits into
mainfrom
KENT-356-sidebar-stack
Aug 8, 2026
Merged

Add bounded sidebar task navigation stack#721
Nek-12 merged 95 commits into
mainfrom
KENT-356-sidebar-stack

Conversation

@Nek-12

@Nek-12 Nek-12 commented Aug 8, 2026

Copy link
Copy Markdown
Member

Summary

  • add a reusable, typed, 50-entry sidebar navigation stack with Back, whole-stack close, cycle truncation, root ownership, and stale-capability protection
  • preserve Task Detail drafts, presentation, and virtualized scroll state across related Task and related New Task traversal without keeping inactive destinations mounted
  • keep Project deletion and typed missing-entity dismissal at destination-owned read/mutation seams, including raw Project-not-found RPC classification
  • preserve overlay mode and immutable Task Detail composition metadata during dependency traversal
  • update the Desktop product specification for the observable bounded-navigation contract

Product decisions

  • idle Link Workflow and project-scoped Workflow Editor remain mounted when existing reads cannot distinguish a deleted Project from a valid empty/unlinked result; no detection-only query or server read-semantics change was added
  • broader mounted-owner guard expansion is deferred to KENT-457
  • the production file-count guideline is 37 for the canonical Project-not-found RPC registry change; production changed LoC remains under the 1,800 hard cap

Verification

  • frozen frontend dependency install
  • Desktop lint (only the pre-existing ReorderableList.tsx Fast Refresh warning)
  • Desktop typecheck
  • dependency, TypeScript, test, and architecture policy suites
  • full Desktop suite: 76 files / 355 tests, plus 9 ui-kit tests
  • Desktop build
  • git diff --check

Final classifier from merge-base c945238c9: production 37 files / 1,774 changed LoC; tests 14 files / 1,270 changed LoC; docs 1 file / 35 changed LoC; generated 0.

Summary by CodeRabbit

  • New Features
    • Added stack-based sidebar navigation for task details, related tasks, workflows, and project editing.
    • Preserves task drafts, focus, and scroll position while navigating between sidebar pages.
    • Added task-detail pop-out support and improved Inbox traversal.
    • Added safeguards for pending task creation and dependency actions.
  • Bug Fixes
    • Missing tasks and projects now dismiss or navigate back cleanly instead of showing generic errors.
    • Project and workflow deletion now refreshes related content and avoids closing unrelated sidebar views.
  • Style
    • Added directional sidebar transitions with reduced-motion support.

Nek-12 added 30 commits August 4, 2026 15:12
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 8, 2026

Copy link
Copy Markdown

Deploying kent with  Cloudflare Pages  Cloudflare Pages

Latest commit: ca8baeb
Status: ✅  Deploy successful!
Preview URL: https://2807451f.kent-3kj.pages.dev
Branch Preview URL: https://kent-356-sidebar-stack.kent-3kj.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

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

Next review available in: 16 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 11a779bd-b671-4598-af93-e0de2c2563c8

📥 Commits

Reviewing files that changed from the base of the PR and between 639d576 and 5ea3067.

📒 Files selected for processing (59)
  • .kent/plans/KENT-356.md
  • apps/desktop/src/api/errors.test.ts
  • apps/desktop/src/api/errors.ts
  • apps/desktop/src/api/index.ts
  • apps/desktop/src/api/rpcErrorCodes.ts
  • apps/desktop/src/app-facade/projectDeletionEvents.test.ts
  • apps/desktop/src/app-facade/projectDeletionEvents.ts
  • apps/desktop/src/app-facade/sidebarContext.ts
  • apps/desktop/src/app/AppChrome.tsx
  • apps/desktop/src/app/AttentionNotificationController.tsx
  • apps/desktop/src/app/ProjectMissingMutationSeams.test.tsx
  • apps/desktop/src/app/attentionNotificationSurfaces.ts
  • apps/desktop/src/app/sidebar.tsx
  • apps/desktop/src/app/sidebarDestinationPolicy.ts
  • apps/desktop/src/app/sidebarDestinations.test.tsx
  • apps/desktop/src/app/sidebarDestinations.tsx
  • apps/desktop/src/app/sidebarPageContext.ts
  • apps/desktop/src/app/sidebarProvider.test.tsx
  • apps/desktop/src/app/sidebarProvider.tsx
  • apps/desktop/src/app/sidebarStack.ts
  • apps/desktop/src/app/startup/styles.css
  • apps/desktop/src/features/board/BoardColumns.tsx
  • apps/desktop/src/features/board/BoardNoWorkflowState.tsx
  • apps/desktop/src/features/board/BoardRoute.tsx
  • apps/desktop/src/features/board/BoardTaskSearch.test.tsx
  • apps/desktop/src/features/board/TaskSearchChrome.tsx
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.test.ts
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.ts
  • apps/desktop/src/features/home/HomeRoute.tsx
  • apps/desktop/src/features/home/ProjectRow.tsx
  • apps/desktop/src/features/home/SidebarInboxNav.tsx
  • apps/desktop/src/features/home/useHomeData.test.tsx
  • apps/desktop/src/features/project-edit/ProjectDeleteButton.test.tsx
  • apps/desktop/src/features/project-edit/ProjectDeleteButton.tsx
  • apps/desktop/src/features/project-edit/ProjectEditRoute.test.tsx
  • apps/desktop/src/features/project-edit/ProjectEditRoute.tsx
  • apps/desktop/src/features/task-detail/StandaloneTaskRoute.tsx
  • apps/desktop/src/features/task-detail/TaskDependenciesArea.test.tsx
  • apps/desktop/src/features/task-detail/TaskDependenciesArea.tsx
  • apps/desktop/src/features/task-detail/TaskDetailContent.tsx
  • apps/desktop/src/features/task-detail/TaskDetailList.tsx
  • apps/desktop/src/features/task-detail/TaskDetailRetainedState.test.tsx
  • apps/desktop/src/features/task-detail/TaskDetailSurface.sidebar.test.tsx
  • apps/desktop/src/features/task-detail/TaskDetailSurface.tsx
  • apps/desktop/src/features/tasks/NewTaskDialog.tsx
  • apps/desktop/src/features/workflow-editor/WorkflowDeleteButton.test.tsx
  • apps/desktop/src/features/workflow-editor/WorkflowDeleteButton.tsx
  • apps/desktop/src/features/workflow-editor/WorkflowEditorRoute.tsx
  • apps/desktop/src/features/workflows/LinkWorkflowSidebar.tsx
  • apps/desktop/src/features/workflows/WorkflowCreateForm.tsx
  • apps/desktop/src/features/workflows/WorkflowLibraryRoute.tsx
  • apps/desktop/src/shared/workflow-deletion/useWorkflowDeleteLauncher.tsx
  • apps/desktop/src/test-support/sidebar/index.ts
  • apps/desktop/src/test-support/task-detail/index.ts
  • apps/desktop/src/ui/VirtualizedInfiniteList.test.tsx
  • apps/desktop/src/ui/VirtualizedInfiniteList.tsx
  • apps/desktop/src/ui/index.ts
  • apps/desktop/src/ui/virtualizedPixelOffsetRequest.ts
  • docs/dev/specs/desktop-gui.md
📝 Walkthrough

Walkthrough

Changes

Sidebar stack and destination lifecycle

Layer / File(s) Summary
Stack contracts and lifecycle engine
apps/desktop/src/app-facade/sidebarContext.ts, apps/desktop/src/app/sidebarStack.ts, apps/desktop/src/app/sidebarProvider.tsx, apps/desktop/src/api/*
Replaces the legacy sidebar controller with typed root, shell, page, and navigator contracts. Adds bounded stack navigation, retained entries, stale-capability handling, lifecycle settlement, and typed missing-entity detection.
Shell rendering and owned-root wiring
apps/desktop/src/app/AppChrome.tsx, apps/desktop/src/app/sidebar.tsx, apps/desktop/src/features/board/*, apps/desktop/src/features/home/*, apps/desktop/src/features/workflows/*, apps/desktop/src/app/startup/styles.css
Connects the stack to the sidebar shell and route-owned roots. Updates opening, resizing, controls, page rendering, route cleanup, and directional animations.
Destination navigation and deletion handling
apps/desktop/src/app/sidebarDestinations.tsx, apps/desktop/src/features/project-edit/*, apps/desktop/src/features/workflow-editor/*, apps/desktop/src/features/workflows/*, apps/desktop/src/app-facade/projectDeletionEvents.ts
Adds navigator-based destination completion, pending New Task handling, workflow follow-up navigation, scoped pop-out behavior, project deletion changes, and missing-project dismissal.
Task state, dependency navigation, and scroll restoration
apps/desktop/src/features/task-detail/*, apps/desktop/src/features/tasks/NewTaskDialog.tsx, apps/desktop/src/ui/*
Adds retained Task Detail state, dependency navigation availability, related-task creation routing, typed task dismissal, and validated one-time virtualized pixel-offset restoration.
Validation fixtures and specification
apps/desktop/src/app/sidebarProvider.test.tsx, apps/desktop/src/app/sidebarDestinations.test.tsx, apps/desktop/src/test-support/*, docs/dev/specs/desktop-gui.md
Adds coverage for stack lifecycle, navigation outcomes, retained state, deletion behavior, missing entities, scroll restoration, and the updated Desktop sidebar behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Route
  participant SidebarRootOwner
  participant SidebarProvider
  participant SidebarDestinationView
  participant TaskDetailSurface
  Route->>SidebarRootOwner: create owned sidebar scope
  SidebarRootOwner->>SidebarProvider: open destination
  SidebarProvider->>SidebarDestinationView: render current stack page
  SidebarDestinationView->>TaskDetailSurface: render Task Detail with navigator
  TaskDetailSurface->>SidebarProvider: navigate back when task is missing
  SidebarProvider-->>SidebarRootOwner: settle root lifecycle
Loading
sequenceDiagram
  participant TaskDetailContent
  participant SidebarPageNavigator
  participant TaskDetailList
  participant VirtualizedInfiniteList
  TaskDetailContent->>SidebarPageNavigator: capture retained draft and scroll state
  SidebarPageNavigator-->>TaskDetailContent: restore retained Task Detail state
  TaskDetailContent->>TaskDetailList: pass restoration request
  TaskDetailList->>VirtualizedInfiniteList: forward pixel offset
  VirtualizedInfiniteList->>VirtualizedInfiniteList: apply offset once
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.03% 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 clearly and concisely summarizes the main change: adding a bounded sidebar task navigation stack.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch KENT-356-sidebar-stack
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch KENT-356-sidebar-stack

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 402f04ee51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/features/task-detail/TaskDetailContent.tsx Outdated
Comment thread apps/desktop/src/app/sidebarDestinations.tsx
Comment thread apps/desktop/src/app/sidebarDestinations.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (13)
apps/desktop/src/app/sidebarProvider.test.tsx (3)

362-366: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use jest-dom matchers for attribute and disabled assertions.

@testing-library/jest-dom is available in this suite. toHaveAttribute and toBeDisabled report the actual element state on failure, while getAttribute(...)/hasAttribute(...) only report null or false.

♻️ Proposed change
-    expect(screen.getByTestId("app-sidebar-page").getAttribute("data-direction")).toBe("push");
+    expect(screen.getByTestId("app-sidebar-page")).toHaveAttribute("data-direction", "push");
 
     fireEvent.click(screen.getByTestId("toggle-availability"));
-    expect(headerButtons()[0]?.hasAttribute("disabled")).toBe(true);
-    expect(headerButtons()[1]?.hasAttribute("disabled")).toBe(true);
+    expect(headerButtons()[0]).toBeDisabled();
+    expect(headerButtons()[1]).toBeDisabled();
🤖 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/desktop/src/app/sidebarProvider.test.tsx` around lines 362 - 366, Update
the assertions in the sidebar test around the direction and availability checks
to use jest-dom matchers: assert the page element with toHaveAttribute and
assert each header button with toBeDisabled, preserving the existing expected
values.

193-220: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Bound the Back traversal loop.

The while (result.current.shell.canGoBack) loop depends on back() reducing the stack on every iteration. If a regression makes back() return "accepted" without changing the entries, this test hangs until the Vitest timeout rather than failing with a clear message. Add an explicit iteration guard.

♻️ Proposed guard
     const visited: string[] = [];
-    while (result.current.shell.canGoBack) {
+    for (let step = 0; result.current.shell.canGoBack; step += 1) {
+      if (step > 50) {
+        throw new Error("Sidebar Back did not reduce the stack.");
+      }
       const current = result.current.shell.activeDestination;
🤖 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/desktop/src/app/sidebarProvider.test.tsx` around lines 193 - 220, Add an
explicit maximum-iteration guard to the Back traversal loop in the test,
limiting iterations to the expected bounded history size while retaining the
canGoBack condition. Ensure the test fails clearly if back() repeatedly returns
"accepted" without reducing the stack, and preserve the existing visited-count
and destination assertions.

310-345: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Restore real timers even when an assertion fails.

Line 311 enables fake timers and Line 344 restores them. If any assertion between those lines fails, vi.useRealTimers() never runs. Every later test in this file then runs with fake timers, which produces misleading cascading failures. Move the restoration into an afterEach hook.

♻️ Proposed change
 describe("SidebarProvider stack", () => {
+  afterEach(() => {
+    vi.useRealTimers();
+  });
+
     expect(result.current.shell.activeDestination).toBeNull();
-    vi.useRealTimers();
   });
🤖 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/desktop/src/app/sidebarProvider.test.tsx` around lines 310 - 345, Move
fake-timer cleanup for the test containing “retains widths per profile and keeps
the closing page rendered through the exit phase” into an afterEach hook,
ensuring vi.useRealTimers() runs even when assertions fail. Remove the inline
restoration and keep the test’s timer setup behavior unchanged.
apps/desktop/src/features/task-detail/TaskDetailRetainedState.test.tsx (1)

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

Assert the focus behavior that the test name claims.

The test name states that first-open focus is preserved. The body passes initialFocus: { kind: "dependencies" } but never asserts that the Dependencies area receives focus. The current assertions only verify the decoded fallback draft and the selected tab. Add an assertion for the focused dependencies element, or rename the test to describe only the retained-state fallback.

🤖 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/desktop/src/features/task-detail/TaskDetailRetainedState.test.tsx`
around lines 85 - 95, Update the test case “ignores malformed retained state and
preserves first-open focus” to assert that the Dependencies area receives focus
after mounting with initialFocus kind “dependencies”; retain the existing
fallback draft and selected-tab assertions.
apps/desktop/src/features/home/useHomeData.test.tsx (1)

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

Reset the shared navigator between tests.

sidebarNavigator is created once at module scope and is reused by every test in this file. createTestSidebarNavigator returns mock functions, so call history accumulates across tests. A future assertion on sidebarNavigator.replace or sidebarNavigator.push would then observe calls from an earlier test. Create the navigator inside each test, or clear it in a beforeEach hook.

♻️ Proposed reset
+beforeEach(() => {
+  vi.clearAllMocks();
+});
🤖 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/desktop/src/features/home/useHomeData.test.tsx` at line 322, Reset the
shared sidebarNavigator mock state between tests by moving its creation into
each test or clearing its mock functions in a beforeEach hook. Update the
existing sidebarNavigator setup while preserving the current test behavior and
assertions.
apps/desktop/src/test-support/task-detail/index.ts (1)

423-424: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Let the caller supply the root controller.

mountTaskDetailSurface always installs a throwaway createTestSidebarController() as the SidebarRootContext value. Callers can only observe root opens through the separate openSidebar option. A test that relies on the context instead of the prop silently observes nothing. Add an option for the controller so the fixture has one place that defines root-open observation.

♻️ Proposed change
   retainedState?: unknown;
+  rootController?: SidebarRootController | undefined;
   sidebarMode?: SidebarMode | undefined;
       children: createElement(SidebarRootContext.Provider, {
-        value: createTestSidebarController(),
+        value: options.rootController ?? createTestSidebarController(),
🤖 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/desktop/src/test-support/task-detail/index.ts` around lines 423 - 424,
Update mountTaskDetailSurface to accept an optional root sidebar controller in
its options, and pass that controller as the SidebarRootContext.Provider value
instead of always creating one with createTestSidebarController(). Preserve the
existing default for callers that do not provide the option, and use the
supplied controller so context-based root-open observations share the fixture’s
configured controller.

Source: Coding guidelines

apps/desktop/src/ui/VirtualizedInfiniteList.test.tsx (1)

11-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reset every virtualizer mock between tests.

beforeEach only clears scrollToOffset and resets getVirtualItems. getOffsetForIndex, scrollToIndex, and measureElement keep call history across tests. That state can leak if a later test asserts on them.

♻️ Proposed reset
   beforeEach(() => {
+    vi.clearAllMocks();
+    virtualizer.getTotalSize.mockReturnValue(120);
     virtualizer.getVirtualItems.mockReturnValue([]);
-    virtualizer.scrollToOffset.mockClear();
   });

Also applies to: 60-80, 89-117

🤖 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/desktop/src/ui/VirtualizedInfiniteList.test.tsx` around lines 11 - 34,
Reset all virtualizer mock call histories in the test setup before each test,
including getOffsetForIndex, scrollToIndex, and measureElement alongside the
existing resets for scrollToOffset and getVirtualItems. Update the beforeEach
associated with the virtualizer mock so later tests cannot observe calls from
earlier tests.
docs/dev/specs/desktop-gui.md (1)

274-303: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the sidebar-stack rules into their own section.

These 30 bullets describe the sidebar navigation stack, lifecycle, ownership, and restoration. They are inserted into the middle of the Task Dependencies bullet list. The list resumes dependency-row rules at Line 304 ("Each relationship row has an accessible trailing Remove action"), so the dependency rules are now split across two blocks.

Add a dedicated section, for example ## Sidebar Navigation, and keep only the dependency-specific navigation bullets (Lines 276-279, 300-301) in Task Dependencies. This keeps one primary responsibility per section and keeps the dependency rules contiguous.

🤖 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 `@docs/dev/specs/desktop-gui.md` around lines 274 - 303, Create a dedicated
“Sidebar Navigation” section for the sidebar stack, lifecycle, ownership, and
restoration bullets currently mixed into “Task Dependencies.” Keep only the
dependency-specific navigation rules—related Task selection, Dependency Add,
related creation behavior, and navigation availability—under “Task
Dependencies,” ensuring the remaining dependency-row rules stay contiguous.

Source: Coding guidelines

apps/desktop/src/app-facade/projectDeletionEvents.test.ts (2)

55-59: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the teardown into afterEach.

Lines 57 to 59 unmount the hook, unsubscribe the observer, and clear the query client only when every assertion passes. A failed assertion leaves the observer subscribed and the query client populated, which can affect the following table case. Register the teardown in afterEach instead.

🤖 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/desktop/src/app-facade/projectDeletionEvents.test.ts` around lines 55 -
59, Move the cleanup calls from the test body into an afterEach teardown for the
relevant test suite, ensuring view.unmount(), unsubscribe(), and
queryClient.clear() run even when assertions fail. Keep the existing setup and
assertions unchanged.

15-20: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Two table rows exercise the same query key.

"Link Workflow" and "Project Workflow Editor" both pass queryKeys.projectWorkflowLinks("project-1"). The second row runs the identical assertions against the identical key, so it adds no coverage and only doubles the run time. Either remove one row, or point the Project Workflow Editor row at the query key that owner actually reads.

🤖 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/desktop/src/app-facade/projectDeletionEvents.test.ts` around lines 15 -
20, Remove the duplicate table row in the parameterized test, or update the
“Project Workflow Editor” case to use the distinct query key that its owner
reads instead of queryKeys.projectWorkflowLinks("project-1"). Keep each scenario
mapped to a unique query key.
apps/desktop/src/features/project-edit/ProjectDeleteButton.tsx (1)

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

navigator shadows the global navigator object.

The prop name navigator hides window.navigator in every scope inside this component. The code here does not use the DOM object, so behavior is correct today. A later addition such as navigator.clipboard would resolve to the sidebar navigator and fail. The same shadowing now exists in sidebarDestinations.tsx, ProjectEditRoute.tsx, and WorkflowEditorRoute.tsx. Consider sidebarNavigator or pageNavigator for the public prop.

🤖 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/desktop/src/features/project-edit/ProjectDeleteButton.tsx` at line 25,
Rename the navigator prop and its usages in ProjectDeleteButton to
sidebarNavigator or pageNavigator, and apply the same public-parameter rename to
the corresponding symbols in sidebarDestinations.tsx, ProjectEditRoute.tsx, and
WorkflowEditorRoute.tsx so the global navigator remains accessible.
apps/desktop/src/app/sidebar.tsx (1)

292-306: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document why PageBoundary must stay a per-entry component identity.

Boundary in sidebarStack.createEntry is a new function component for every stack entry, and it renders only a Fragment. The remount of the destination subtree depends entirely on that changing component identity. A later change that hoists or memoizes Boundary would silently keep inactive destinations mounted and break retained-state isolation. The name also suggests error isolation, which this component does not provide.

Add a short comment here, or rename the field to state the lifecycle 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 `@apps/desktop/src/app/sidebar.tsx` around lines 292 - 306, Document the
lifecycle purpose of the PageBoundary component around SidebarDestinationView:
its per-entry identity must remain distinct so destination subtrees remount and
retained state stays isolated. Add a brief comment or rename the related
boundary field to avoid implying error isolation, without changing the existing
behavior.
apps/desktop/src/api/errors.test.ts (1)

16-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Name the generic RPC code and add a negative Task case.

Line 19 uses the literal -32000 while the neighboring assertions use rpcErrorCodes. The literal exercises the structured-data path, so give it a name that states that intent. isTaskMissingError also has no negative assertion, so a regression to message-based detection would stay undetected on the Task side.

♻️ Proposed change
+const genericRpcErrorCode = -32000;
+
 describe("sidebar missing-entity errors", () => {
   it("recognizes typed Task and Project missing errors without parsing messages", () => {
     const error = (code: number, data?: Readonly<Record<string, string>>) =>
       new RpcError({ code, data, message: "changed", method: "owner.operation" });
     expect(isTaskMissingError(error(rpcErrorCodes.workflowTaskNotFound))).toBe(true);
-    expect(isProjectMissingError(error(-32000, { reason: "project_not_found" }))).toBe(true);
+    expect(isTaskMissingError(new Error("workflow_task_not_found"))).toBe(false);
+    expect(isProjectMissingError(error(genericRpcErrorCode, { reason: "project_not_found" }))).toBe(true);
     expect(isProjectMissingError(error(rpcErrorCodes.projectNotFound))).toBe(true);
     expect(isProjectMissingError(new Error("project_not_found"))).toBe(false);
   });
 });
🤖 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/desktop/src/api/errors.test.ts` around lines 16 - 21, Update the test
helper or constants in the error test to name the generic RPC code used for the
structured-data project case instead of the raw -32000 literal, and add a
negative isTaskMissingError assertion using an Error message that should not be
recognized as a missing task. Keep the existing positive task and project
assertions unchanged.
🤖 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 `@apps/desktop/src/app-facade/sidebarContext.ts`:
- Around line 142-165: Update SidebarRootOwner to track whether the owner is
still active, and mark it inactive during the cleanup effect before releasing
and clearing handles. In the open callback, reject calls made after cleanup by
returning a handle whose lifecycle is already resolved with "released", without
invoking roots.open; preserve the existing handle tracking and release behavior
while active.

In `@apps/desktop/src/app/sidebarDestinations.test.tsx`:
- Around line 78-83: Extend the “deduplicates only Task Detail destinations”
test to assert that two taskDetail destinations with different taskID values are
not equal, while preserving the existing equal-ID and custom-destination
assertions in sidebarDestinationPolicy.equals.

In `@apps/desktop/src/app/sidebarDestinations.tsx`:
- Around line 127-129: Handle rejected post-close navigation promises through
the shared follow helper in apps/desktop/src/app/sidebarDestinations.tsx:127-129
by attaching a catch that reports a danger status via useStatusController,
covering onCreated and onLinked. At
apps/desktop/src/app/sidebarDestinations.tsx:155-157, route
navigation.openWorkflowEditor through follow instead of using bare void; no
separate error handling is needed there.

In `@apps/desktop/src/app/sidebarStack.ts`:
- Around line 42-46: Update notify to publish a fresh view object rather than
the existing view reference, ensuring availability mutations trigger React state
updates. Preserve the current capability check and use a shallow copy when
calling publish.

In `@apps/desktop/src/features/project-edit/ProjectEditRoute.tsx`:
- Around line 77-82: Update MissingProjectDismissal to capture the result of
navigator.back() and render the existing ErrorState whenever the outcome is not
"accepted"; retain the current null rendering after a successful dismissal.

In `@apps/desktop/src/ui/VirtualizedInfiniteList.tsx`:
- Around line 257-268: The useEffect applying validatedPixelOffsetRequest must
not consume the request until offsetPx is reachable in the virtualizer’s current
scroll range. Update the guard or readiness condition around
virtualizer.scrollToOffset so early fixed-row renders leave
lastPixelOffsetKeyRef pending, allowing later page loads to retry; alternatively
require an explicit data-ready signal before marking the request key applied.

---

Nitpick comments:
In `@apps/desktop/src/api/errors.test.ts`:
- Around line 16-21: Update the test helper or constants in the error test to
name the generic RPC code used for the structured-data project case instead of
the raw -32000 literal, and add a negative isTaskMissingError assertion using an
Error message that should not be recognized as a missing task. Keep the existing
positive task and project assertions unchanged.

In `@apps/desktop/src/app-facade/projectDeletionEvents.test.ts`:
- Around line 55-59: Move the cleanup calls from the test body into an afterEach
teardown for the relevant test suite, ensuring view.unmount(), unsubscribe(),
and queryClient.clear() run even when assertions fail. Keep the existing setup
and assertions unchanged.
- Around line 15-20: Remove the duplicate table row in the parameterized test,
or update the “Project Workflow Editor” case to use the distinct query key that
its owner reads instead of queryKeys.projectWorkflowLinks("project-1"). Keep
each scenario mapped to a unique query key.

In `@apps/desktop/src/app/sidebar.tsx`:
- Around line 292-306: Document the lifecycle purpose of the PageBoundary
component around SidebarDestinationView: its per-entry identity must remain
distinct so destination subtrees remount and retained state stays isolated. Add
a brief comment or rename the related boundary field to avoid implying error
isolation, without changing the existing behavior.

In `@apps/desktop/src/app/sidebarProvider.test.tsx`:
- Around line 362-366: Update the assertions in the sidebar test around the
direction and availability checks to use jest-dom matchers: assert the page
element with toHaveAttribute and assert each header button with toBeDisabled,
preserving the existing expected values.
- Around line 193-220: Add an explicit maximum-iteration guard to the Back
traversal loop in the test, limiting iterations to the expected bounded history
size while retaining the canGoBack condition. Ensure the test fails clearly if
back() repeatedly returns "accepted" without reducing the stack, and preserve
the existing visited-count and destination assertions.
- Around line 310-345: Move fake-timer cleanup for the test containing “retains
widths per profile and keeps the closing page rendered through the exit phase”
into an afterEach hook, ensuring vi.useRealTimers() runs even when assertions
fail. Remove the inline restoration and keep the test’s timer setup behavior
unchanged.

In `@apps/desktop/src/features/home/useHomeData.test.tsx`:
- Line 322: Reset the shared sidebarNavigator mock state between tests by moving
its creation into each test or clearing its mock functions in a beforeEach hook.
Update the existing sidebarNavigator setup while preserving the current test
behavior and assertions.

In `@apps/desktop/src/features/project-edit/ProjectDeleteButton.tsx`:
- Line 25: Rename the navigator prop and its usages in ProjectDeleteButton to
sidebarNavigator or pageNavigator, and apply the same public-parameter rename to
the corresponding symbols in sidebarDestinations.tsx, ProjectEditRoute.tsx, and
WorkflowEditorRoute.tsx so the global navigator remains accessible.

In `@apps/desktop/src/features/task-detail/TaskDetailRetainedState.test.tsx`:
- Around line 85-95: Update the test case “ignores malformed retained state and
preserves first-open focus” to assert that the Dependencies area receives focus
after mounting with initialFocus kind “dependencies”; retain the existing
fallback draft and selected-tab assertions.

In `@apps/desktop/src/test-support/task-detail/index.ts`:
- Around line 423-424: Update mountTaskDetailSurface to accept an optional root
sidebar controller in its options, and pass that controller as the
SidebarRootContext.Provider value instead of always creating one with
createTestSidebarController(). Preserve the existing default for callers that do
not provide the option, and use the supplied controller so context-based
root-open observations share the fixture’s configured controller.

In `@apps/desktop/src/ui/VirtualizedInfiniteList.test.tsx`:
- Around line 11-34: Reset all virtualizer mock call histories in the test setup
before each test, including getOffsetForIndex, scrollToIndex, and measureElement
alongside the existing resets for scrollToOffset and getVirtualItems. Update the
beforeEach associated with the virtualizer mock so later tests cannot observe
calls from earlier tests.

In `@docs/dev/specs/desktop-gui.md`:
- Around line 274-303: Create a dedicated “Sidebar Navigation” section for the
sidebar stack, lifecycle, ownership, and restoration bullets currently mixed
into “Task Dependencies.” Keep only the dependency-specific navigation
rules—related Task selection, Dependency Add, related creation behavior, and
navigation availability—under “Task Dependencies,” ensuring the remaining
dependency-row rules stay contiguous.
🪄 Autofix

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2062b621-6600-45ca-95bb-e2e0d76d67bd

📥 Commits

Reviewing files that changed from the base of the PR and between 4a4892b and 402f04e.

📒 Files selected for processing (53)
  • .kent/plans/KENT-356.md
  • apps/desktop/src/api/errors.test.ts
  • apps/desktop/src/api/errors.ts
  • apps/desktop/src/api/index.ts
  • apps/desktop/src/api/rpcErrorCodes.ts
  • apps/desktop/src/app-facade/projectDeletionEvents.test.ts
  • apps/desktop/src/app-facade/projectDeletionEvents.ts
  • apps/desktop/src/app-facade/sidebarContext.ts
  • apps/desktop/src/app/AppChrome.tsx
  • apps/desktop/src/app/AttentionNotificationController.tsx
  • apps/desktop/src/app/ProjectMissingMutationSeams.test.tsx
  • apps/desktop/src/app/sidebar.tsx
  • apps/desktop/src/app/sidebarDestinationPolicy.ts
  • apps/desktop/src/app/sidebarDestinations.test.tsx
  • apps/desktop/src/app/sidebarDestinations.tsx
  • apps/desktop/src/app/sidebarPageContext.ts
  • apps/desktop/src/app/sidebarProvider.test.tsx
  • apps/desktop/src/app/sidebarProvider.tsx
  • apps/desktop/src/app/sidebarStack.ts
  • apps/desktop/src/app/startup/styles.css
  • apps/desktop/src/features/board/BoardColumns.tsx
  • apps/desktop/src/features/board/BoardNoWorkflowState.tsx
  • apps/desktop/src/features/board/BoardRoute.tsx
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.test.ts
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.ts
  • apps/desktop/src/features/home/HomeRoute.tsx
  • apps/desktop/src/features/home/ProjectRow.tsx
  • apps/desktop/src/features/home/SidebarInboxNav.tsx
  • apps/desktop/src/features/home/useHomeData.test.tsx
  • apps/desktop/src/features/project-edit/ProjectDeleteButton.test.tsx
  • apps/desktop/src/features/project-edit/ProjectDeleteButton.tsx
  • apps/desktop/src/features/project-edit/ProjectEditRoute.tsx
  • apps/desktop/src/features/task-detail/StandaloneTaskRoute.tsx
  • apps/desktop/src/features/task-detail/TaskDependenciesArea.test.tsx
  • apps/desktop/src/features/task-detail/TaskDependenciesArea.tsx
  • apps/desktop/src/features/task-detail/TaskDetailContent.tsx
  • apps/desktop/src/features/task-detail/TaskDetailList.tsx
  • apps/desktop/src/features/task-detail/TaskDetailRetainedState.test.tsx
  • apps/desktop/src/features/task-detail/TaskDetailSurface.sidebar.test.tsx
  • apps/desktop/src/features/task-detail/TaskDetailSurface.tsx
  • apps/desktop/src/features/tasks/NewTaskDialog.tsx
  • apps/desktop/src/features/workflow-editor/WorkflowEditorRoute.tsx
  • apps/desktop/src/features/workflows/LinkWorkflowSidebar.tsx
  • apps/desktop/src/features/workflows/WorkflowCreateForm.tsx
  • apps/desktop/src/features/workflows/WorkflowLibraryRoute.tsx
  • apps/desktop/src/shared/workflow-deletion/useWorkflowDeleteLauncher.tsx
  • apps/desktop/src/test-support/sidebar/index.ts
  • apps/desktop/src/test-support/task-detail/index.ts
  • apps/desktop/src/ui/VirtualizedInfiniteList.test.tsx
  • apps/desktop/src/ui/VirtualizedInfiniteList.tsx
  • apps/desktop/src/ui/index.ts
  • apps/desktop/src/ui/virtualizedPixelOffsetRequest.ts
  • docs/dev/specs/desktop-gui.md
💤 Files with no reviewable changes (2)
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.test.ts
  • apps/desktop/src/features/board/taskDetailRouteLifecycle.ts

Comment thread apps/desktop/src/app-facade/sidebarContext.ts
Comment thread apps/desktop/src/app/sidebarDestinations.test.tsx
Comment thread apps/desktop/src/app/sidebarDestinations.tsx
Comment thread apps/desktop/src/app/sidebarStack.ts
Comment thread apps/desktop/src/features/project-edit/ProjectEditRoute.tsx Outdated
Comment thread apps/desktop/src/ui/VirtualizedInfiniteList.tsx

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51d3393dff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/app/sidebarDestinations.tsx Outdated
Comment thread apps/desktop/src/app/sidebarDestinations.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 41537eb5ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/features/project-edit/ProjectEditRoute.tsx Outdated
Comment thread apps/desktop/src/features/task-detail/TaskDependenciesArea.test.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ca8baebd90

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/api/errors.ts
@Nek-12
Nek-12 merged commit 1e4ba37 into main Aug 8, 2026
23 of 25 checks passed
@Nek-12
Nek-12 deleted the KENT-356-sidebar-stack branch August 8, 2026 21:50
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