Skip to content

Fix sidebar reordering on vault data changes#53

Merged
dannysmith merged 3 commits into
mainfrom
fix/sidebar-reorder-on-data-change
Mar 28, 2026
Merged

Fix sidebar reordering on vault data changes#53
dannysmith merged 3 commits into
mainfrom
fix/sidebar-reorder-on-data-change

Conversation

@dannysmith

@dannysmith dannysmith commented Mar 28, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #51

  • Rust: Sort entity lists (all_tasks(), all_projects(), all_areas()) alphabetically by title in VaultIndex, so the "natural order" returned to the frontend is deterministic. Previously, HashMap iteration order + rayon parallel scanning made this effectively random on every vault refresh.
  • Frontend: When a stored sidebar order exists, append new entity IDs (items created on disk externally) to the end instead of silently dropping them. This also fixes a secondary bug where new areas/projects were invisible in the sidebar after any manual reorder.

Test plan

  • All Rust tests pass (277 passed)
  • All frontend sidebar order tests pass (20 passed, 2 updated to reflect new append behavior)
  • Clippy clean, Prettier/ESLint/ast-grep clean
  • Manual testing: reordering areas and projects in sidebar, adding tasks on disk, verifying order stability

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Improvements

    • Sidebar areas and projects now display in consistent alphabetical order.
    • Newly discovered items are appended to your saved sidebar order instead of being omitted.
  • Bug Fixes

    • Task-to-project/area association is more accurate for WikiLink-formatted references, reducing false matches.
  • Tests

    • Updated and expanded tests to cover the new ordering and WikiLink matching behavior.

Two root causes: HashMap non-deterministic iteration in Rust and rayon
parallel scan ordering meant the "natural order" of entities was
effectively random on every refresh. Additionally, new items created on
disk were invisible in the sidebar when a manual order existed.

Fixes:
- Sort entity lists alphabetically by title in Rust VaultIndex for
  stable default ordering
- Append new entity IDs to stored sidebar order instead of silently
  dropping them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Deterministic entity ordering and wikilink-aware matching were introduced: vault index methods now return alphabetically sorted vectors; sidebar ordering preserves stored order and appends new items; multiple task-to-project/area matches were changed from substring checks to a new matchesWikilinkTitle helper.

Changes

Cohort / File(s) Summary
Vault Index Sorting
tdn-desktop/src-tauri/src/vault/manager.rs
all_tasks, all_projects, all_areas now clone values into Vecs and return them sorted by title case-insensitively (deterministic order).
Sidebar Order Logic & Tests
tdn-desktop/src/hooks/use-sidebar-order.ts, tdn-desktop/src/hooks/use-sidebar-order.test.ts
effectiveAreaOrder and getEffectiveProjectOrder now preserve stored relative order for existing IDs and append newly discovered IDs; tests updated to expect appended new items.
WikiLink Matching Utility & Tests
tdn-desktop/src/lib/wikilink.ts, tdn-desktop/src/lib/wikilink.test.ts
Added exported matchesWikilinkTitle(reference, title) and new unit tests covering exact wikilink/title matching, alias/heading cases, and non-wikilink/null inputs.
Services: Vault Queries
tdn-desktop/src/services/vault/queries.ts
Replaced substring-based matching with matchesWikilinkTitle(...) in helpers (projects-by-area, tasks-by-project, area-direct-tasks, completion/counts) to use exact wikilink title matching.
UI Components: Views & Kanban
tdn-desktop/src/components/views/AreaView.tsx, .../NoAreaView.tsx, .../ProjectView.tsx, .../TodayView.tsx, .../WeekView.tsx, tdn-desktop/src/components/kanban/AreaKanbanBoard.tsx
Replaced t.project?.includes(...) / t.area?.includes(...) substring checks with matchesWikilinkTitle(...) in multiple places where tasks are associated with projects or areas; imported new helper where needed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • Desktop app UI #24 — Modifies VaultIndex retrieval methods to return sorted vectors (same manager methods changed here).
  • Desktop App Code Reviews #32 — Introduces/adjusts wikilink utilities and call sites; related to switching substring checks to wikilink-aware helpers.

Poem

🐰
I hopped through files with nimble paws,
Sorted titles, fixed surprise re-laws.
Kept your order, then added the new,
Wikilinks matched — tidy and true.
A cheerful hop for tidy views! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main issue being fixed: sidebar reordering when vault data changes, which aligns with the PR's primary objective from issue #51.
Linked Issues check ✅ Passed The PR directly addresses issue #51 by ensuring deterministic entity ordering (via VaultIndex sorting), preserving user sidebar order when new items are added, and fixing wikilink matching to prevent false-positive associations that could affect sidebar visibility and ordering.
Out of Scope Changes check ✅ Passed All changes are within scope: VaultIndex sorting for determinism, sidebar order preservation logic, wikilink matching improvements (fixing false positives affecting ordering), and corresponding test updates directly support the PR's objectives.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.

✏️ 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 fix/sidebar-reorder-on-data-change

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.

@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: 1

🧹 Nitpick comments (1)
tdn-desktop/src-tauri/src/vault/manager.rs (1)

883-917: Consider adding a test to verify alphabetical sort order.

The existing tests verify counts but don't confirm the sorting behavior. Adding a test with entities having titles in non-alphabetical order would validate the deterministic ordering fix.

📝 Example test for sort order verification
#[test]
fn vault_index_all_tasks_sorted_alphabetically() {
    let tasks = vec![
        Task { title: "Zebra Task".to_string(), ..test_task("task-z", "/tasks/z.md") },
        Task { title: "alpha task".to_string(), ..test_task("task-a", "/tasks/a.md") },
        Task { title: "Beta Task".to_string(), ..test_task("task-b", "/tasks/b.md") },
    ];
    let index = VaultIndex::from_scans(tasks, vec![], vec![]);

    let all = index.all_tasks();
    assert_eq!(all[0].title, "alpha task"); // lowercase 'a' sorts first
    assert_eq!(all[1].title, "Beta Task");
    assert_eq!(all[2].title, "Zebra Task");
}

Based on learnings: "Write comprehensive tests for business logic".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tdn-desktop/src-tauri/src/vault/manager.rs` around lines 883 - 917, Add tests
that assert alphabetical sort order for VaultIndex results: the current tests
only check counts, so add new unit tests (e.g.,
vault_index_all_tasks_sorted_alphabetically,
vault_index_all_projects_sorted_alphabetically,
vault_index_all_areas_sorted_alphabetically) that create items with out-of-order
titles and assert
VaultIndex::from_scans(...).all_tasks()/all_projects()/all_areas() return
entries in deterministic alphabetical order by title; locate the implementations
that produce the lists (VaultIndex::from_scans and the
all_tasks/all_projects/all_areas methods) and ensure the tests reference the
Task/Project/Area title fields via the existing test helper
test_task/test_project/test_area to validate ordering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tdn-desktop/src/hooks/use-sidebar-order.ts`:
- Around line 64-66: Replace substring matching p.area?.includes(area.title)
with an exact WikiLink equality check: compare p.area to the full wiki link
string built from area.title (e.g., p.area === `[[${area.title}]]`) or normalize
both sides by stripping/adding the double-bracket wiki syntax before comparing;
update the same change wherever p.area?.includes(area.title) appears
(use-sidebar-order.ts, queries.ts occurrences, WeekView.tsx, TodayView.tsx) so
projects only match when the wiki link equals the area title exactly.

---

Nitpick comments:
In `@tdn-desktop/src-tauri/src/vault/manager.rs`:
- Around line 883-917: Add tests that assert alphabetical sort order for
VaultIndex results: the current tests only check counts, so add new unit tests
(e.g., vault_index_all_tasks_sorted_alphabetically,
vault_index_all_projects_sorted_alphabetically,
vault_index_all_areas_sorted_alphabetically) that create items with out-of-order
titles and assert
VaultIndex::from_scans(...).all_tasks()/all_projects()/all_areas() return
entries in deterministic alphabetical order by title; locate the implementations
that produce the lists (VaultIndex::from_scans and the
all_tasks/all_projects/all_areas methods) and ensure the tests reference the
Task/Project/Area title fields via the existing test helper
test_task/test_project/test_area to validate ordering.
🪄 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

Run ID: b3fdb1b2-140d-4e53-bf50-3973f962c41e

📥 Commits

Reviewing files that changed from the base of the PR and between 55bbf04 and afe4e91.

📒 Files selected for processing (3)
  • tdn-desktop/src-tauri/src/vault/manager.rs
  • tdn-desktop/src/hooks/use-sidebar-order.test.ts
  • tdn-desktop/src/hooks/use-sidebar-order.ts

Comment on lines +64 to +66
const area = areas.find(a => a.id === containerId)
if (!area) return []
return projects.filter(p => p.area?.includes(area.title)).map(p => p.id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for consistent wikilink matching patterns across the codebase
rg -n --type=ts "\.area\??\.(includes|match|indexOf)" tdn-desktop/src/

Repository: dannysmith/taskdn

Length of output: 777


🏁 Script executed:

cat -n tdn-desktop/src/hooks/use-sidebar-order.ts | head -80

Repository: dannysmith/taskdn

Length of output: 3732


🏁 Script executed:

# Check type definitions for area property
fd --type=ts -e ".ts" tdn-desktop/src | xargs grep -l "area.*:" | head -5

Repository: dannysmith/taskdn

Length of output: 230


🏁 Script executed:

# Look for type definitions or interfaces that define the area property
rg -n "area\s*\?*:\s*" tdn-desktop/src --type=ts -B 2 -A 1 | head -40

Repository: dannysmith/taskdn

Length of output: 2484


🏁 Script executed:

# Search for tests that cover area filtering
rg -n "area.*includes\|Homework.*Work\|substring" tdn-desktop/src/test --type=ts -A 3 -B 3

Repository: dannysmith/taskdn

Length of output: 43


🏁 Script executed:

# Look for tests of getEffectiveProjectOrder or area filtering logic
rg -n "getEffectiveProjectOrder\|filter.*area" tdn-desktop/src/test --type=ts -A 5

Repository: dannysmith/taskdn

Length of output: 43


🏁 Script executed:

# Check if there are any tests with multiple areas that have similar names
rg -n "Work.*Homework\|area.*title" tdn-desktop/src/test --type=ts -B 2 -A 2

Repository: dannysmith/taskdn

Length of output: 43


🏁 Script executed:

# Verify the exact type definition for area property to be completely certain
rg -n "interface.*Project\|type.*Project" tdn-desktop/src --type=ts -A 15 | head -50

Repository: dannysmith/taskdn

Length of output: 43


🏁 Script executed:

# Check the actual Project type definition
fd -name "*.ts" -path "*/types/*" tdn-desktop/src | xargs grep -l "Project" | head -3

Repository: dannysmith/taskdn

Length of output: 291


🏁 Script executed:

# Search for Project type definition with simpler pattern
rg "export (type|interface) Project" tdn-desktop/src -A 20

Repository: dannysmith/taskdn

Length of output: 6475


🏁 Script executed:

# Alternative: look in types directory
fd "." tdn-desktop/src/types -type f | head -10

Repository: dannysmith/taskdn

Length of output: 231


Fix substring matching bug in area filtering — affects multiple files.

p.area?.includes(area.title) incorrectly matches projects when one area title is a substring of another. For example, a project with area: "[[Homework]]" would match both "Work" and "Homework" because "[[Homework]]".includes("Work") is true.

This bug affects 6 locations: use-sidebar-order.ts:66, queries.ts:188, queries.ts:202, WeekView.tsx:137, WeekView.tsx:148, and TodayView.tsx:130. Apply the fix consistently across all occurrences using exact string equality for WikiLink matching:

Proposed fix
-        return projects.filter(p => p.area?.includes(area.title)).map(p => p.id)
+        return projects
+          .filter(p => p.area === `[[${area.title}]]`)
+          .map(p => p.id)
📝 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
const area = areas.find(a => a.id === containerId)
if (!area) return []
return projects.filter(p => p.area?.includes(area.title)).map(p => p.id)
const area = areas.find(a => a.id === containerId)
if (!area) return []
return projects
.filter(p => p.area === `[[${area.title}]]`)
.map(p => p.id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tdn-desktop/src/hooks/use-sidebar-order.ts` around lines 64 - 66, Replace
substring matching p.area?.includes(area.title) with an exact WikiLink equality
check: compare p.area to the full wiki link string built from area.title (e.g.,
p.area === `[[${area.title}]]`) or normalize both sides by stripping/adding the
double-bracket wiki syntax before comparing; update the same change wherever
p.area?.includes(area.title) appears (use-sidebar-order.ts, queries.ts
occurrences, WeekView.tsx, TodayView.tsx) so projects only match when the wiki
link equals the area title exactly.

dannysmith and others added 2 commits March 28, 2026 00:10
Replace count-only assertions with tests that create items in reverse
order and verify alphabetical output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace p.area?.includes(area.title) with exact wikilink comparison
using a new matchesWikilinkTitle() utility. The substring check caused
false positives — e.g., area "Work" would match projects in
"[[Homework]]".

Affects: sidebar ordering, queries, TodayView, WeekView, ProjectView,
AreaView, NoAreaView, AreaKanbanBoard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tdn-desktop/src/services/vault/queries.ts (1)

214-234: Consider extracting a shared project-task filter helper.

Line 217-219 and Line 232-234 duplicate the same filter (also repeated in Line 196). A small helper would reduce drift risk.

♻️ Proposed refactor
 export function useVaultHelpers() {
   const { tasks, projects, areas } = useVaultData()
@@
+  const tasksForProjectTitle = (projectTitle: string) =>
+    tasks.filter(t => matchesWikilinkTitle(t.project, projectTitle))
@@
     getTasksByProjectId: (projectId: string) => {
       const project = projectsById.get(projectId)
       if (!project) return []
-      return tasks.filter(t => matchesWikilinkTitle(t.project, project.title))
+      return tasksForProjectTitle(project.title)
     },
@@
-      const projectTasks = tasks.filter(t =>
-        matchesWikilinkTitle(t.project, project.title)
-      )
+      const projectTasks = tasksForProjectTitle(project.title)
@@
-      const projectTasks = tasks.filter(t =>
-        matchesWikilinkTitle(t.project, project.title)
-      )
+      const projectTasks = tasksForProjectTitle(project.title)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tdn-desktop/src/services/vault/queries.ts` around lines 214 - 234, Duplicate
filtering logic for project tasks appears in getProjectCompletion and
getTaskCounts (and elsewhere); extract a small helper such as
getTasksForProject(projectId: string) or getTasksForProjectTitle(title: string)
that looks up projectsById, returns [] when missing, and runs tasks.filter(t =>
matchesWikilinkTitle(t.project, project.title)); replace the inline filters in
getProjectCompletion, getTaskCounts (and the other occurrence) to call this
helper so behavior and null-handling remain identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tdn-desktop/src/services/vault/queries.ts`:
- Around line 214-234: Duplicate filtering logic for project tasks appears in
getProjectCompletion and getTaskCounts (and elsewhere); extract a small helper
such as getTasksForProject(projectId: string) or getTasksForProjectTitle(title:
string) that looks up projectsById, returns [] when missing, and runs
tasks.filter(t => matchesWikilinkTitle(t.project, project.title)); replace the
inline filters in getProjectCompletion, getTaskCounts (and the other occurrence)
to call this helper so behavior and null-handling remain identical.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e775624d-d39c-4d24-9709-e3651558f44d

📥 Commits

Reviewing files that changed from the base of the PR and between afe4e91 and df693d3.

📒 Files selected for processing (11)
  • tdn-desktop/src-tauri/src/vault/manager.rs
  • tdn-desktop/src/components/kanban/AreaKanbanBoard.tsx
  • tdn-desktop/src/components/views/AreaView.tsx
  • tdn-desktop/src/components/views/NoAreaView.tsx
  • tdn-desktop/src/components/views/ProjectView.tsx
  • tdn-desktop/src/components/views/TodayView.tsx
  • tdn-desktop/src/components/views/WeekView.tsx
  • tdn-desktop/src/hooks/use-sidebar-order.ts
  • tdn-desktop/src/lib/wikilink.test.ts
  • tdn-desktop/src/lib/wikilink.ts
  • tdn-desktop/src/services/vault/queries.ts
✅ Files skipped from review due to trivial changes (1)
  • tdn-desktop/src/lib/wikilink.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tdn-desktop/src/hooks/use-sidebar-order.ts

@dannysmith dannysmith merged commit ae90887 into main Mar 28, 2026
4 checks passed
@dannysmith dannysmith deleted the fix/sidebar-reorder-on-data-change branch March 28, 2026 01:31
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.

Sidebar Reorders itself when data changes on disk (tdn-desktop)

1 participant