Skip to content

feat: folders are fs-authoritative — empty folders are first-class - #63

Merged
samkeen merged 2 commits into
mainfrom
feat/folders-fs-authoritative
Jul 21, 2026
Merged

feat: folders are fs-authoritative — empty folders are first-class#63
samkeen merged 2 commits into
mainfrom
feat/folders-fs-authoritative

Conversation

@samkeen

@samkeen samkeen commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

The file tree is now one-to-one with the filesystem in both directions. Folders are user-authored structure (the content sibling of notes), never projected into the index — data-model.md "Folders" (2026-07-21).

  • Vault::list_dirs — the tree's structure half, a live fs walk (dot-dirs skipped, the ingest walk's routing rule): empty folders show, external mkdirs appear on the watcher pulse, no staleness window exists.
  • Vault::create_dir — the desktop's New-folder is now a real mkdir -p on disk, replacing the session-scoped pendingDirs staging (which lost folders on restart/vault-switch and showed folders the fs didn't have).
  • move_dir/delete_dir already resolved against the disk, so empty folders move/rename/delete everywhere (b2 mv, b2 rm -r, the tree) — now pinned by test; folder deletes in the UI always confirm (an "empty" folder may still hold unindexed files, and remove_dir_all takes everything).

Summary by CodeRabbit

  • New Features

    • Added support for creating folders directly in the vault, including nested parent folders.
    • Folder listings now include empty folders and reflect the live vault structure.
    • Folder creation and browsing are available throughout the desktop app.
  • Bug Fixes

    • Empty folders now persist across restarts and remain consistent when moved or deleted.
    • Added clearer messages for invalid folder paths and existing folder or file conflicts.
  • Tests

    • Expanded coverage for empty folders, hidden folders, nested creation, moving, and deletion.

The file tree is now one-to-one with the filesystem in both directions.
Folders are user-authored *structure* (the content sibling of notes),
never projected into the index — data-model.md "Folders" (2026-07-21).

- Vault::list_dirs — the tree's structure half, a live fs walk (dot-dirs
  skipped, the ingest walk's routing rule): empty folders show, external
  mkdirs appear on the watcher pulse, no staleness window exists.
- Vault::create_dir — the desktop's New-folder is now a real `mkdir -p`
  on disk, replacing the session-scoped pendingDirs staging (which lost
  folders on restart/vault-switch and showed folders the fs didn't have).
- move_dir/delete_dir already resolved against the disk, so empty folders
  move/rename/delete everywhere (b2 mv, b2 rm -r, the tree) — now pinned
  by test; folder deletes in the UI always confirm (an "empty" folder may
  still hold unindexed files, and remove_dir_all takes everything).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 43 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ebb23705-70f5-476b-83e6-3911afa84b8f

📥 Commits

Reviewing files that changed from the base of the PR and between 39d8e9e and f4f6f9b.

📒 Files selected for processing (8)
  • CLAUDE.md
  • crates/b2-core/src/dirs.rs
  • crates/b2-core/src/vault.rs
  • crates/b2-desktop/src/commands.rs
  • planning/data-model.md
  • planning/tasks.md
  • ui/src/api.ts
  • ui/src/main.ts
📝 Walkthrough

Walkthrough

Filesystem-backed folder listing and creation are added across the core vault, desktop IPC, and frontend. Empty folders are read live from disk, created through OS operations, rendered in the tree, and covered by core and desktop tests. The previous staged-folder state is removed.

Changes

Filesystem-authoritative folder flow

Layer / File(s) Summary
Core folder operations
crates/b2-core/src/dirs.rs, crates/b2-core/src/vault.rs, crates/b2-core/src/error.rs, crates/b2-core/src/lib.rs
Adds live directory listing, normalized recursive creation, structured errors, and façade methods.
Desktop IPC exposure
crates/b2-desktop/src/commands.rs, crates/b2-desktop/src/error.rs, crates/b2-desktop/src/main.rs
Registers list_dirs and create_dir, delegates to the vault, and maps directory errors to user-facing messages.
Frontend directory state and API
ui/src/api.ts, ui/src/types.ts, ui/src/state.ts, ui/src/main.ts
Loads filesystem directories into state.dirs, adds IPC methods and result types, and replaces pendingDirs creation with immediate disk creation.
Tree, move, and delete integration
ui/src/render.ts, ui/src/move.ts, ui/src/newentry.ts, ui/src/main.ts
Builds trees and move destinations from live directories while remapping expanded and selected state during operations.
Validation and planning
crates/b2-core/tests/*, crates/b2-desktop/src/commands.rs, ui/src/move.test.ts, planning/*, CLAUDE.md
Tests empty-folder listing, creation, validation, move, deletion, and documents filesystem-authoritative folder behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

  • AlteredCraft/B2#59 — Related frontend folder navigation and creation changes that this PR replaces with live filesystem state.

Suggested reviewers: claude

Poem

A rabbit hops where empty folders grow,
From disk they bloom in rows below.
No staged leaf can drift away,
The tree reads truth each bright new day.
mkdir -p, then carrots too!

🚥 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 accurately summarizes the main change: folder structure is now filesystem-authoritative and empty folders are treated as first-class.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/folders-fs-authoritative

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

🧹 Nitpick comments (1)
CLAUDE.md (1)

126-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the architecture contract internally complete.

This contradicts lines 111-118’s “no durable state outside Markdown” invariant, and the declared façade surface omits list_resources, move_resource, and move_dir. Update both statements so this guide remains the authoritative contract.

Also applies to: 175-177

🤖 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 `@CLAUDE.md` around lines 126 - 132, Update the architecture contract in
CLAUDE.md so the “no durable state outside Markdown” invariant explicitly
permits authoritative user-created filesystem folders. Expand the declared
façade surface to include list_resources, move_resource, and move_dir, and apply
the same corrections to the corresponding repeated contract section.
🤖 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 `@crates/b2-core/src/dirs.rs`:
- Around line 33-69: Update collect_dirs to inspect each directory entry’s
symlink metadata and skip symlink entries before the is_dir check, preventing
traversal outside root or recursive links. Update create_dir to resolve and
validate each existing parent component without following symlinks, rejecting
symlinked parents before creation while preserving DirDestination and
DirTargetExists errors.

In `@planning/data-model.md`:
- Around line 67-70: The directory-creation wording must reflect the
non-idempotent create_dir contract: in planning/data-model.md lines 67-70,
replace “mkdir -p” with wording that says creation creates missing parents while
rejecting an existing target; make the same wording change in planning/tasks.md
lines 92-94, replacing “real mkdir -p.”
- Around line 72-74: The filesystem-authority claims must be scoped to managed
non-dot directories because Vault::list_dirs omits dot-folders. In
planning/data-model.md lines 72-74, replace the one-to-one filesystem wording
with “one-to-one with non-dot managed directories”; apply the same scoped
wording to the current-state summary in planning/tasks.md lines 89-97.

In `@ui/src/main.ts`:
- Around line 212-216: Update loadNotes() to fetch notes, resources, and dirs
into temporary results before mutating state, so a listDirs() failure cannot
clear or partially commit existing data. Propagate or return the refresh failure
instead of resolving normally, and update the caller around the success message
at lines 541-552 to report “Created …” only after loadNotes() completes
successfully.

---

Nitpick comments:
In `@CLAUDE.md`:
- Around line 126-132: Update the architecture contract in CLAUDE.md so the “no
durable state outside Markdown” invariant explicitly permits authoritative
user-created filesystem folders. Expand the declared façade surface to include
list_resources, move_resource, and move_dir, and apply the same corrections to
the corresponding repeated contract section.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e6bf698f-c315-4a42-996b-d889d8c8020e

📥 Commits

Reviewing files that changed from the base of the PR and between 16534e6 and 39d8e9e.

📒 Files selected for processing (21)
  • CLAUDE.md
  • crates/b2-core/src/dirs.rs
  • crates/b2-core/src/error.rs
  • crates/b2-core/src/lib.rs
  • crates/b2-core/src/vault.rs
  • crates/b2-core/tests/dirs.rs
  • crates/b2-core/tests/mv.rs
  • crates/b2-core/tests/rm_delete.rs
  • crates/b2-desktop/src/commands.rs
  • crates/b2-desktop/src/error.rs
  • crates/b2-desktop/src/main.rs
  • planning/data-model.md
  • planning/tasks.md
  • ui/src/api.ts
  • ui/src/main.ts
  • ui/src/move.test.ts
  • ui/src/move.ts
  • ui/src/newentry.ts
  • ui/src/render.ts
  • ui/src/state.ts
  • ui/src/types.ts

Comment thread crates/b2-core/src/dirs.rs
Comment thread planning/data-model.md Outdated
Comment thread planning/data-model.md Outdated
Comment thread ui/src/main.ts Outdated
- loadNotes: fetch all three tree lists before committing state (a
  mid-refresh failure can't half-update the tree), and resolve a boolean
  so create flows don't overwrite the failure toast with "Created …".
- Docs/comments: create_dir is create-with-parents *refusing* an occupied
  target (not mkdir -p's idempotence); scope the one-to-one claim to the
  vault's managed (non-dot) subtree; scope the CLAUDE.md invariant to
  B2-derived state and complete the façade surface list.
- dirs.rs: document that list_dirs deliberately mirrors the ingest walk's
  symlink-following traversal — diverging would desync tree structure
  from indexed content; a symlink policy must change both walks together.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@samkeen
samkeen merged commit 5b8c43a into main Jul 21, 2026
1 check passed
@samkeen
samkeen deleted the feat/folders-fs-authoritative branch July 21, 2026 19:56
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