Skip to content

feat(#106): Add directory UI + filesystem browse endpoint#119

Merged
Abernaughty merged 4 commits intomainfrom
feat/106a-add-directory-ui
Apr 4, 2026
Merged

feat(#106): Add directory UI + filesystem browse endpoint#119
Abernaughty merged 4 commits intomainfrom
feat/106a-add-directory-ui

Conversation

@Abernaughty
Copy link
Copy Markdown
Owner

@Abernaughty Abernaughty commented Apr 4, 2026

Summary

Phase A of #106 — adds "Add directory" UI to the workspace selector with both a text input and a server-side directory browser.

Closes the Phase A checklist items from #106:

  • "Add directory" option at bottom of workspace dropdown (text input + confirm)
  • Path validation feedback (exists / doesn't exist / already added)
  • Browse mode with server-side directory navigation

Deferred:

  • "Create new project" option — needs backend scaffolding logic
  • GitHub repo picker — Phase 3 (needs GitHub MCP integration)

Changes

Backend (dev-suite/src/api/)

GET /filesystem/browse — new endpoint that lists subdirectories at a given path:

  • Accepts ?path=/some/dir (defaults to user home) and ?show_hidden=false
  • Returns { current_path, parent_path, entries[] } where each entry has name, path, has_children, is_project
  • Project detection: checks for .git, package.json, pyproject.toml, Cargo.toml, go.mod, pom.xml, build.gradle, Makefile
  • Projects sorted first, then alphabetical
  • Permission errors handled gracefully (skip unreadable dirs, 403 for unreadable target)

New models: BrowseDirectoryEntry, BrowseDirectoryResponse

Dashboard (dashboard/)

WorkspaceSelector.svelte — enhanced with:

  • "Add directory" button — cyan + styled, appears at bottom of dropdown after divider
  • Text input mode — type/paste a path, ✓ to confirm, ✕ to cancel, Enter/Escape keyboard support
  • Browse mode — click 📂 icon to toggle into server-side directory browser:
    • Breadcrumb path bar with clickable segments for navigation
    • Scrollable directory listing with 📦 PROJECT badges and 📁 folder icons
    • Parent directory (↑ ..) navigation
    • "Select this directory" confirmation button
    • "Type path" button to switch back to text input
  • Client-side duplicate check before API call
  • Outside-click dismissal resets all add-directory state
  • Auto-focus on text input when it appears

New proxy route: /api/filesystem/browse/+server.ts

New types: BrowseDirectoryEntry, BrowseDirectoryResponse in api.ts

Testing

Manual testing required:

  1. Open Chat panel → workspace dropdown → click "Add directory"
  2. Text input: type a valid absolute path → confirm → workspace added and selected
  3. Text input: type invalid path → backend error surfaces inline
  4. Text input: type duplicate path → client-side "Already in workspace list" error
  5. Browse mode: click 📂 → navigate directories → select → workspace added
  6. Keyboard: Enter confirms, Escape cancels
  7. Outside click closes dropdown and resets state

Screenshots

UI screenshots available after local testing

Issue

Partial fix for #106 (Phase A only — Phase B = Planner agent)

Summary by CodeRabbit

  • New Features
    • Added "Add directory" flow to workspace dropdown for typing or confirming directory paths
    • Added server-backed directory browser with breadcrumb navigation (includes parent “..”) to explore and select directories
  • Usability
    • Keyboard shortcuts: Enter to confirm, Escape to cancel; auto-focus for add input
    • Improved inline error display and outside-click behavior that cancels add/browse
    • Increased workspace dropdown max height for better visibility

Phase A of #106 — Workspace selector "Add directory" with text input
and server-side directory browser.

Backend:
- GET /filesystem/browse endpoint — lists subdirectories at a path
  with project detection (package.json, pyproject.toml, .git, etc.)
  and has_children metadata for lazy tree expansion
- BrowseDirectoryEntry + BrowseDirectoryResponse Pydantic models
- Defaults to user home dir, filters hidden dirs, sorts projects first

Dashboard:
- WorkspaceSelector "Add directory" button in dropdown
- Text input mode: paste/type path, Enter to confirm, Escape to cancel
- Browse mode: breadcrumb navigation + directory listing with project
  badges, parent navigation, "Select this directory" confirmation
- Client-side duplicate check before API call
- Outside-click dismissal resets add-directory state
- Auto-focus on input appearance

Proxy:
- /api/filesystem/browse SvelteKit server route

Types:
- BrowseDirectoryEntry + BrowseDirectoryResponse TS interfaces
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf90dee6-bea2-464e-99ee-371943d47c7e

📥 Commits

Reviewing files that changed from the base of the PR and between 52b5eeb and 1e3f4e9.

📒 Files selected for processing (1)
  • dev-suite/src/api/main.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • dev-suite/src/api/main.py

📝 Walkthrough

Walkthrough

Adds an "Add directory" flow to the workspace selector with inline input, server-backed directory browsing and breadcrumb navigation, keyboard/focus handlers, outside-click dismissal, SvelteKit proxy route, new TypeScript/Pydantic types, and a FastAPI endpoint that enumerates child directories and detects projects.

Changes

Cohort / File(s) Summary
Workspace Selector UI
dashboard/src/lib/components/WorkspaceSelector.svelte
Adds "Add directory" flow: toggleable input, keyboard (Enter/Escape) and focus handling, inline validation/errors, server-backed browse navigator with breadcrumbs and parent (“..”), outside-click dismissal resetting state, hides store errors during add, and increases dropdown max-height to 400px.
Frontend Types
dashboard/src/lib/types/api.ts
Adds BrowseDirectoryEntry and BrowseDirectoryResponse TypeScript interfaces for filesystem browse responses.
SvelteKit Proxy Route
dashboard/src/routes/api/filesystem/browse/+server.ts
New GET proxy that forwards path and show_hidden to backend /filesystem/browse, normalizes query params, and returns { data, errors } with propagated status on failure.
Backend API Models
dev-suite/src/api/models.py
Adds Pydantic models BrowseDirectoryEntry and BrowseDirectoryResponse.
Backend Filesystem Browse Endpoint
dev-suite/src/api/main.py
New authenticated GET /filesystem/browse endpoint: resolves/validates target directory, enumerates immediate child directories (optionally hiding dot entries), computes is_project and has_children, handles permission/errors (403 for unreadable target, omits unreadable children), sorts projects first, and returns BrowseDirectoryResponse wrapped in ApiResponse.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as Dashboard UI
    participant Proxy as SvelteKit Proxy
    participant Backend as FastAPI
    participant FS as File System

    User->>UI: Open workspace dropdown → choose "Add directory"
    UI->>UI: Show input & browse UI, focus input
    User->>UI: Click "Browse" or enter a path
    UI->>Proxy: GET /api/filesystem/browse?path=...&show_hidden=...
    Proxy->>Backend: Proxy GET /filesystem/browse?path=...&show_hidden=...
    Backend->>FS: Resolve path, list immediate child directories
    Backend->>FS: Inspect children for project markers and subdirs
    FS-->>Backend: Return entries & metadata
    Backend-->>Proxy: BrowseDirectoryResponse (entries, parent_path, current_path)
    Proxy-->>UI: Return entries → render breadcrumb + list
    User->>UI: Select entry or press Enter to confirm
    UI->>workspacesStore: addDirectory(newDirPath)
    workspacesStore-->>UI: success → close dropdown
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through folders, breadcrumb bright,

I sniffed for markers hidden out of sight,
I nudged a path into the workspace gate,
I pressed Enter—now new burrows await,
🥕 A tiny hop and all is right!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and accurately summarizes the main changes: adding an 'Add directory' UI component and a filesystem browse endpoint, both of which are directly reflected in the changeset across frontend (WorkspaceSelector.svelte) and backend (browse endpoint).

✏️ 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 feat/106a-add-directory-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dashboard/src/lib/components/WorkspaceSelector.svelte`:
- Around line 167-168: The catch block currently assigns the raw transport error
message to browseError, which leads the template to render backend errors
verbatim; instead change the catch handling in the try/catch around the browse
fetch (the code assigning browseError in the catch) to set a neutral unavailable
state (e.g., set browseError to null and/or set a boolean like browseUnavailable
= true) and ensure the template uses this flag to show the neutral “browser
unavailable / type a path instead” empty-state UI and the existing manual path
fallback rather than displaying the raw error; update any other catch sites that
assign browseError (notably the occurrences around lines 302-304) to follow the
same pattern.
- Around line 145-150: Reset any stale browse selection when starting or failing
a browse: in openBrowseMode() and where fetchBrowse('')/fetchBrowse calls begin,
set browsePath = null (or '') along with browseError = null and addDirError =
null so previous successful selection is cleared; in the fetchBrowse error/catch
handling ensure browsePath is also cleared. Also gate the "Select this
directory" action (the handler that reads browsePath to add a directory) to only
proceed when browseError is falsy and browseLoading is false (and browsePath is
non-empty), so it cannot add the previous folder after a 403/network failure.
Update references to browsePath, browseError, browseLoading, openBrowseMode, and
fetchBrowse accordingly.

In `@dev-suite/src/api/main.py`:
- Around line 421-440: The per-child probe currently catches PermissionError but
still appends the directory, causing inaccessible folders to be shown and
leaving FileNotFoundError/NotADirectoryError uncaught; update the try/except
around the child inspection (the block that computes child_contents, is_project
and has_children for each child) to catch PermissionError, FileNotFoundError and
NotADirectoryError and on those exceptions skip the child (use continue) instead
of setting is_project/has_children and appending a BrowseDirectoryEntry for that
child, so only fully inspected entries are added to entries.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61bf7dd4-1051-4ec9-99ba-2e4182dd49af

📥 Commits

Reviewing files that changed from the base of the PR and between 818e94d and 8d90f3d.

📒 Files selected for processing (5)
  • dashboard/src/lib/components/WorkspaceSelector.svelte
  • dashboard/src/lib/types/api.ts
  • dashboard/src/routes/api/filesystem/browse/+server.ts
  • dev-suite/src/api/main.py
  • dev-suite/src/api/models.py

…ble, skip unreadable dirs

Fixes all 3 CodeRabbit Major/Minor items from PR #119 review:

1. Clear stale browse selection on each fetchBrowse() call — prevents
   "Select this directory" from adding the previous folder after a
   403/network failure. Also gate select button on browseError/Loading.

2. Replace raw transport error with neutral browseUnavailable state —
   shows "Browser unavailable — use Type path below" instead of
   verbatim fetch errors. Graceful degradation per dashboard guidelines.

3. Skip unreadable child dirs instead of listing them with unknown
   metadata. Catch FileNotFoundError/NotADirectoryError alongside
   PermissionError for race conditions.
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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
dev-suite/src/api/main.py (2)

421-432: Minor cleanup: redundant is_dir() check and double iteration.

Line 423's if child.is_dir() guard is always true (already filtered at line 417). The child.iterdir() is called twice—once for child_contents and once for has_children. Consider consolidating:

♻️ Proposed refactor to remove redundancy
             try:
-                # Detect project markers
-                child_contents = set(c.name for c in child.iterdir()) if child.is_dir() else set()
-                is_project = bool(child_contents & _PROJECT_MARKERS)
-                has_children = any(
-                    c.is_dir() and (show_hidden or not c.name.startswith("."))
-                    for c in child.iterdir()
-                    if c.is_dir()
-                )
+                # Single pass: detect project markers and visible subdirectories
+                child_names: set[str] = set()
+                has_children = False
+                for c in child.iterdir():
+                    child_names.add(c.name)
+                    if not has_children and c.is_dir() and (show_hidden or not c.name.startswith(".")):
+                        has_children = True
+                is_project = bool(child_names & _PROJECT_MARKERS)
             except (PermissionError, FileNotFoundError, NotADirectoryError):
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev-suite/src/api/main.py` around lines 421 - 432, The block calculating
child_contents and has_children redundantly calls child.iterdir() twice and
checks child.is_dir() unnecessarily; refactor inside the loop that processes
directory entries so you iterate child.iterdir() once, build child_contents (set
of entry names) and compute has_children (any entry.is_dir() and (show_hidden or
not entry.name.startswith("."))) in that single pass, then set is_project =
bool(child_contents & _PROJECT_MARKERS); update references to use these
variables and remove the redundant child.is_dir() guard.

391-396: Consider path restrictions for production deployments.

The endpoint allows browsing any readable directory on the server filesystem. While require_auth provides access control, in production environments you may want to restrict browsing to a configurable allowlist of root directories (e.g., user home, /projects) to limit exposure if the API secret is compromised.

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

In `@dev-suite/src/api/main.py` around lines 391 - 396, The browse_filesystem
endpoint (function browse_filesystem) currently lets authenticated users list
any readable server path; to harden production, add a configurable allowlist
(e.g., ALLOWED_BROWSE_PATHS or app config value) and validate the incoming path
against it before performing filesystem operations: normalize/resolve the
requested path (use pathlib.Path.resolve()) and ensure it is contained within
one of the allowed root directories, returning a 403/ApiResponse error if not
allowed; keep require_auth as-is but enforce this allowlist check early in
browse_filesystem and make the allowlist loadable from environment/config for
deployment-specific roots.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@dev-suite/src/api/main.py`:
- Around line 440-441: The outer exception handler that currently does "except
PermissionError: _error(f\"Permission denied: {target}\", 403)" must also handle
TOCTOU outcomes where the target is removed or replaced between is_dir() and
iterdir(); update that handler to also catch FileNotFoundError and
NotADirectoryError (or add separate except clauses) and call _error with an
appropriate status (e.g., 404 for FileNotFoundError/NotADirectoryError) so the
function handling directory iteration (referencing target and the iterdir()
call) returns a proper client error instead of a 500.

---

Nitpick comments:
In `@dev-suite/src/api/main.py`:
- Around line 421-432: The block calculating child_contents and has_children
redundantly calls child.iterdir() twice and checks child.is_dir() unnecessarily;
refactor inside the loop that processes directory entries so you iterate
child.iterdir() once, build child_contents (set of entry names) and compute
has_children (any entry.is_dir() and (show_hidden or not
entry.name.startswith("."))) in that single pass, then set is_project =
bool(child_contents & _PROJECT_MARKERS); update references to use these
variables and remove the redundant child.is_dir() guard.
- Around line 391-396: The browse_filesystem endpoint (function
browse_filesystem) currently lets authenticated users list any readable server
path; to harden production, add a configurable allowlist (e.g.,
ALLOWED_BROWSE_PATHS or app config value) and validate the incoming path against
it before performing filesystem operations: normalize/resolve the requested path
(use pathlib.Path.resolve()) and ensure it is contained within one of the
allowed root directories, returning a 403/ApiResponse error if not allowed; keep
require_auth as-is but enforce this allowlist check early in browse_filesystem
and make the allowlist loadable from environment/config for deployment-specific
roots.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18e8404f-41bc-45f3-9d79-9a8825c28a42

📥 Commits

Reviewing files that changed from the base of the PR and between 8d90f3d and 5d36a4f.

📒 Files selected for processing (2)
  • dashboard/src/lib/components/WorkspaceSelector.svelte
  • dev-suite/src/api/main.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • dashboard/src/lib/components/WorkspaceSelector.svelte

- Outer exception: catch FileNotFoundError/NotADirectoryError alongside
  PermissionError to handle TOCTOU race where target dir is removed
  between is_dir() check and iterdir() call (returns 404 not 500)
- Single-pass iterdir: consolidate double child.iterdir() into one loop
  that collects child names and checks for visible subdirectories
- Fix import sort order (I001) from main merge
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
dev-suite/src/api/main.py (1)

24-57: ⚠️ Potential issue | 🟡 Minor

Fix import sorting to pass CI.

The pipeline failed with ruff I001: "Import block is un-sorted or un-formatted." Run ruff check --fix or manually reorder the imports according to the project's ruff configuration.

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

In `@dev-suite/src/api/main.py` around lines 24 - 57, The import block is unsorted
per ruff; reorder and/or auto-fix the imports so they follow the project's ruff
import ordering (standard library first: __future__, asyncio, logging, os,
pathlib.Path, contextlib.asynccontextmanager; third-party next:
dotenv.load_dotenv, fastapi.Depends/FastAPI/HTTPException/Query/Request,
fastapi.middleware.cors.CORSMiddleware, sse_starlette.EventSourceResponse; local
package imports last: .auth.require_auth, .events.SSEEvent/event_bus, and all
symbols from .models). You can run `ruff check --fix` or manually reorder the
import lines to match that grouping and alphabetical order within groups.
♻️ Duplicate comments (1)
dev-suite/src/api/main.py (1)

440-441: ⚠️ Potential issue | 🟠 Major

Handle TOCTOU race in outer exception block.

The target directory could be deleted or replaced between the is_dir() check (line 407) and iterdir() (line 416). Currently only PermissionError is caught, so FileNotFoundError or NotADirectoryError would propagate as a 500.

🛡️ Proposed fix
     except PermissionError:
         _error(f"Permission denied: {target}", 403)
+    except (FileNotFoundError, NotADirectoryError):
+        _error(f"Not a directory: {target}", 400)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev-suite/src/api/main.py` around lines 440 - 441, The except block that
currently only catches PermissionError should also handle TOCTOU-related errors
raised between the is_dir() check and the iterdir() call: catch
FileNotFoundError and NotADirectoryError in the same error-handling section (or
expand the try to include both is_dir() and iterdir()) and call _error(...) with
appropriate status codes (e.g., FileNotFoundError -> _error(f"Not found:
{target}", 404); NotADirectoryError -> _error(f"Not a directory: {target}",
400)) so these race conditions no longer raise a 500; adjust the except clause
around the code that uses is_dir() and iterdir() accordingly.
🧹 Nitpick comments (1)
dev-suite/src/api/main.py (1)

423-428: Remove redundant is_dir() checks.

Line 423: child.is_dir() is always true here since line 417 already filtered non-directories.

Lines 425-428: The if c.is_dir() filter is redundant with c.is_dir() and ... in the condition.

♻️ Suggested simplification
             try:
                 # Detect project markers
-                child_contents = set(c.name for c in child.iterdir()) if child.is_dir() else set()
+                child_contents = set(c.name for c in child.iterdir())
                 is_project = bool(child_contents & _PROJECT_MARKERS)
                 has_children = any(
                     c.is_dir() and (show_hidden or not c.name.startswith("."))
                     for c in child.iterdir()
-                    if c.is_dir()
                 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dev-suite/src/api/main.py` around lines 423 - 428, The code performs
redundant is_dir() checks for "child" and within the has_children generator:
remove the unnecessary child.is_dir() in the child_contents computation (since
the outer loop already ensures directories) and simplify the has_children
generator by dropping the if c.is_dir() filter while keeping the c.is_dir()
check in the condition or vice versa—i.e., choose one place to test
directoryness; update the expressions computing child_contents, is_project, and
has_children (references: child, child_contents, is_project, has_children,
_PROJECT_MARKERS) so each directory is only checked once.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@dev-suite/src/api/main.py`:
- Around line 24-57: The import block is unsorted per ruff; reorder and/or
auto-fix the imports so they follow the project's ruff import ordering (standard
library first: __future__, asyncio, logging, os, pathlib.Path,
contextlib.asynccontextmanager; third-party next: dotenv.load_dotenv,
fastapi.Depends/FastAPI/HTTPException/Query/Request,
fastapi.middleware.cors.CORSMiddleware, sse_starlette.EventSourceResponse; local
package imports last: .auth.require_auth, .events.SSEEvent/event_bus, and all
symbols from .models). You can run `ruff check --fix` or manually reorder the
import lines to match that grouping and alphabetical order within groups.

---

Duplicate comments:
In `@dev-suite/src/api/main.py`:
- Around line 440-441: The except block that currently only catches
PermissionError should also handle TOCTOU-related errors raised between the
is_dir() check and the iterdir() call: catch FileNotFoundError and
NotADirectoryError in the same error-handling section (or expand the try to
include both is_dir() and iterdir()) and call _error(...) with appropriate
status codes (e.g., FileNotFoundError -> _error(f"Not found: {target}", 404);
NotADirectoryError -> _error(f"Not a directory: {target}", 400)) so these race
conditions no longer raise a 500; adjust the except clause around the code that
uses is_dir() and iterdir() accordingly.

---

Nitpick comments:
In `@dev-suite/src/api/main.py`:
- Around line 423-428: The code performs redundant is_dir() checks for "child"
and within the has_children generator: remove the unnecessary child.is_dir() in
the child_contents computation (since the outer loop already ensures
directories) and simplify the has_children generator by dropping the if
c.is_dir() filter while keeping the c.is_dir() check in the condition or vice
versa—i.e., choose one place to test directoryness; update the expressions
computing child_contents, is_project, and has_children (references: child,
child_contents, is_project, has_children, _PROJECT_MARKERS) so each directory is
only checked once.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2921528a-9ada-43ba-baae-8b5dc6de32a7

📥 Commits

Reviewing files that changed from the base of the PR and between 5d36a4f and 52b5eeb.

📒 Files selected for processing (2)
  • dev-suite/src/api/main.py
  • dev-suite/src/api/models.py
✅ Files skipped from review due to trivial changes (1)
  • dev-suite/src/api/models.py

@Abernaughty Abernaughty merged commit e43843c into main Apr 4, 2026
3 checks passed
@Abernaughty Abernaughty deleted the feat/106a-add-directory-ui branch April 4, 2026 19:48
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