# Filesystem Browser UI — Design & Implementation

> **Active Project** — design and implementation in progress.

**Status:** Implemented
**Date:** 2026-03-05 (updated 2026-03-06)

## Implementation Summary

The `<acequia-file-browser>` custom element is fully built and integrated into both the server dashboard and BrowserDAV.

**Files:** `public/components/acequia-file-browser/` (7 files)

| File | Purpose |
|------|---------|
| `acequia-file-browser.js` | Orchestrator — Shadow DOM, attribute observation, layout |
| `webdav-client.js` | WebDAV HTTP client (PROPFIND, GET, PUT, DELETE, MKCOL) |
| `file-tree.js` | Collapsible directory tree panel (left) |
| `file-list.js` | File grid/list with sorting, context menu, drag-drop upload |
| `access-resolver.js` | Resolves `.acequia-access.json` sidecars for access badges |
| `sidecar-editor.js` | Visual editor for access rules (read mode, recursive, deny patterns) |
| `styles.js` | CSS custom properties for theming (`--fb-*` variables) |

**Integrations:**
- **Server dashboard** (`public/dashboard.html` + `dashboard.js`) — "File Browser" tab for owners, uses auth cookie
- **BrowserDAV** (`acequia/{sub}/BrowserDAV/index.html` + `main.js`) — embedded section, uses per-session owner bypass token, light theme CSS overrides

**What shipped vs design:**
- Tree + list dual-pane layout — shipped as designed
- Access indicator badges (public/private/denied) — shipped
- Sidecar editor with live JSON preview — shipped
- CRUD operations (upload, mkdir, rename, delete) — shipped
- CSS custom properties theming — shipped (dark default, light for BrowserDAV)
- Context menus — shipped
- Multi-select + bulk operations — deferred
- Drag-and-drop move/copy between directories — deferred (upload drag-drop works)
- Cascading rule visualization in footer — simplified (shows source sidecar, not full inheritance chain)

---

## Problem (original design context)

BrowserDAV now supports dotfile write-gated filtering and `.acequia-access.json` sidecar files for anonymous public access. But there's no UI for:

1. **Navigating the filesystem** served by BrowserDAV
2. **Seeing which files/folders are public** (via sidecar rules) vs private
3. **Editing `.acequia-access.json` sidecar files** — currently requires manual file creation
4. **Visualizing cascading access rules** — when a parent directory's sidecar applies recursively to children, users can't see the effective state

Beyond access management, a filesystem browser component would serve multiple use cases: CRUD operations, drag-and-drop file management, URL copying, and future interactions.

## Goals

- **Reusable custom element** (`<acequia-file-browser>`) usable across Acequia apps — ✓
- **Public/private visibility** — clear per-item indicators showing access state — ✓
- **Sidecar editing** — GUI for creating/editing `.acequia-access.json` with validation — ✓
- **Cascading rule visualization** — show inherited vs explicit access rules — Partial
- **CRUD + drag/drop** — file/folder management with move, copy, delete, upload — ✓ (upload drag-drop; move/copy deferred)
- **URL copying** — quick copy of file/folder WebDAV URLs — ✓

---

## Comparable Approaches

| Product / Library | Type | Navigation | Public/Private Indicator | Access Rule Editing | Cascading Visualization |
|---|---|---|---|---|---|
| **Google Drive** | SaaS | Breadcrumb + flat list | People icon on shared items | Side panel: per-person roles + "Anyone with link" toggle | None (Google is removing folder-level overrides) |
| **Nextcloud** | Self-hosted | Tree sidebar + breadcrumb | Share icon on shared items; avatar for received shares | Sidebar tabs: internal shares, public links (with password/expiry options) | "Others with access" collapsible showing inherited shares; ACL for group folders with Inherit/Allow/Deny per level |
| **FileCloud** | Enterprise | Tree + breadcrumb | None per-file; actions disabled when denied | "Manage Folder Permissions" dialog with tabs | **Best-in-class**: Explicit vs Inherited vs Effective permissions in separate table sections; "Check Access" tab to query any user's effective permissions |
| **SharePoint** | Enterprise | Tree + breadcrumb | None per-file | "Manage access" panel listing groups/users | Shows "inheriting from parent" vs "unique permissions" state per folder |
| **Dropbox** | SaaS | Breadcrumb + flat list | People icon on shared items | Sharing panel: per-collaborator role dropdowns | None (flat model) |
| **Syncfusion File Manager** | Commercial component | Tree + breadcrumb + search | None — permission state inferred from disabled actions | Server-side `AccessRule` objects with path patterns + role assignments | Rules evaluated server-side; UI disables toolbar/context menu items based on resolved permissions |
| **FileBrowser (filebrowser.xyz)** | Self-hosted (Go) | Breadcrumb + flat list | None | Per-user scope directory + View/Create/Rename/Delete/Share checkboxes | None (user-scoped root model) |
| **Filestash** | Self-hosted | Breadcrumb + flat list | None | Backend-agnostic; no built-in access control | None |
| **filemanager-element** | Web Component | Grid/row, lazy folders | `readonly` attribute only | None built-in | None |
| **js-fileexplorer** | Pure JS widget | Tree + detail, mobile-friendly | `canmodify` per-folder (binary) | None built-in | None |
| **SVAR File Manager** | Svelte/React/Vue | Tree + breadcrumb; list/tiles/split views | None | None built-in | None |
| **Webix File Manager** | Commercial | Tree + breadcrumb + search; list/tiles/split | None | None built-in | None |

### Key Observations

1. **No product does public/private per-file badges well.** Nextcloud and Google Drive show a "shared" icon but don't differentiate public-link vs restricted-sharing at a glance. This is a gap we can fill.

2. **FileCloud's three-layer model (explicit → inherited → effective)** is the gold standard for cascading permission visualization. Most tools skip this entirely.

3. **Existing web components (filemanager-element, js-fileexplorer)** are CRUD-focused with no access control awareness. We'd build our own anyway to integrate with the Acequia token/sidecar system.

4. **Navigation style matters for our use case.** A tree sidebar is best for seeing cascading rules across directory depth. Breadcrumb-only views hide the hierarchy that makes sidecar inheritance visible.

---

## Design

### Architecture: `<acequia-file-browser>` Custom Element

A framework-agnostic web component following the existing Acequia app component pattern (Chat, Camera apps use `extends HTMLElement` + `connectedCallback` + `render`).

```html
<acequia-file-browser
  root-url="/groups/browserdav/abc123/mydir/"
  auth-token="eyJ..."
  show-access-indicators
  editable
></acequia-file-browser>
```

**Attributes:**
- `root-url` — WebDAV base URL for this filesystem
- `auth-token` — Bearer token for authenticated requests (optional)
- `show-access-indicators` — Show public/private badges and sidecar rule overlays
- `editable` — Enable CRUD operations (upload, mkdir, delete, move, rename)
- `readonly` — Disable all mutations
- `select-mode` — Emit `file-selected` events instead of navigating (picker mode)

**Events emitted:**
- `file-selected` — `{ path, name, type, size, mime }`
- `directory-changed` — `{ path }` (navigation)
- `access-changed` — `{ path, config }` (sidecar edited)

### Layout: Tree + Detail Pane

```
┌─────────────────────────────────────────────────────────┐
│  📁 mydir/                          [↑ Upload] [+ New]  │
│  /public/images/                    ← breadcrumb        │
├──────────────┬──────────────────────────────────────────┤
│ Tree Sidebar │  Detail Pane (list view)                 │
│              │                                          │
│ ▼ 📁 mydir  │  Name          Size    Modified   Access  │
│   ▼ 📁 public│  ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈  │
│     🌐 images│  📁 thumbs    —       Mar 4     🌐       │
│     📁 docs  │  🖼 hero.jpg  245 KB  Mar 3     🌐       │
│   🔒 private │  🖼 logo.png  12 KB   Mar 1     🌐       │
│   📁 config  │  📄 style.css 8 KB    Feb 28    🌐       │
│              │  🚫 .env      —       —         🔒       │
│              │                                          │
├──────────────┴──────────────────────────────────────────┤
│  Access Rules: 🌐 Public (anonymous read)               │
│  Source: /public/.acequia-access.json (recursive)       │
│  Deny patterns: .env, *.key                             │
│  [Edit Access Rules...]                                 │
└─────────────────────────────────────────────────────────┘
```

**Tree sidebar:**
- Expandable directory tree with lazy loading (PROPFIND depth:1 per expansion)
- Icons encode access state:
  - 🌐 = public (sidecar grants anonymous read)
  - 🔒 = private (no sidecar, auth required)
  - 📁 = neutral (inherits from parent, or auth disabled)
- Folders with their own `.acequia-access.json` show a small dot/badge indicating "has explicit rules"
- Clicking a folder navigates the detail pane; double-click expands in tree

**Detail pane (list view):**
- Sortable columns: Name, Size, Modified, Access
- Access column shows 🌐 (public), 🔒 (private), or 🚫 (denied by pattern)
- Dotfiles hidden unless user has write access (consistent with server-side filtering)
- Right-click context menu: Open, Copy URL, Rename, Delete, Move, Download
- Multi-select with shift/ctrl-click for bulk operations
- Drag-and-drop for move/copy between directories

**Access rules footer:**
- Shows the effective access state for the current directory
- Indicates whether the rule is explicit (this directory) or inherited (from parent)
- "Edit Access Rules" button opens the sidecar editor

### Access State Indicators

Three visual states per item, derived from sidecar rules:

| Icon | State | Meaning |
|------|-------|---------|
| 🌐 | Public | Anonymous read access (sidecar `read: "anonymous"` applies) |
| 🔒 | Private | Auth required (no sidecar, or `read: "authenticated"`) |
| 🚫 | Denied | Matches a deny pattern (even within a public directory) |

**Cascading logic for the tree sidebar:**
1. Start at root — default 🔒 (private)
2. Walk down tree; when a directory has `.acequia-access.json` with `read: "anonymous"`:
   - That directory gets 🌐
   - If `recursive: true`, all children get 🌐 (unless they have their own sidecar)
3. When a child directory has its own sidecar, it overrides the parent (nearest-wins)
4. Files matching `denyPatterns` show 🚫 even inside 🌐 directories

**Color coding (CSS):**
```css
.access-public  { color: #28a745; }  /* green */
.access-private { color: #6c757d; }  /* gray */
.access-denied  { color: #dc3545; }  /* red */
```

### Sidecar Editor

A slide-out panel or modal for editing `.acequia-access.json`:

```
┌─────────────────────────────────────────────┐
│  Access Rules: /public/                     │
│                                             │
│  Read Access                                │
│  ○ Private (require authentication)         │
│  ● Public (allow anonymous read)            │
│                                             │
│  ☑ Apply recursively to subdirectories      │
│                                             │
│  Deny Patterns                              │
│  ┌─────────────────────────────────────┐    │
│  │ .env                                │    │
│  │ *.key                               │    │
│  │ .*                                  │    │
│  └─────────────────────────────────────┘    │
│  [+ Add pattern]                            │
│                                             │
│  Preview JSON                               │
│  ┌─────────────────────────────────────┐    │
│  │ {                                   │    │
│  │   "read": "anonymous",             │    │
│  │   "recursive": true,               │    │
│  │   "denyPatterns": [".env", "*.key"] │    │
│  │ }                                   │    │
│  └─────────────────────────────────────┘    │
│                                             │
│  Effective State                            │
│  Files in this directory: 🌐 Public         │
│  Subdirectories: 🌐 Public (recursive)      │
│  .env, *.key files: 🚫 Denied              │
│                                             │
│  ⚠ Overrides parent rule at /              │
│                                             │
│              [Cancel]  [Save]               │
└─────────────────────────────────────────────┘
```

**Features:**
- **Radio buttons** for read access (private/public) — simple binary choice
- **Recursive checkbox** — only shown when read is public
- **Deny pattern list** — editable list with add/remove, common pattern suggestions (`.env`, `*.key`, `.*`)
- **Live JSON preview** — shows the exact JSON that will be written to `.acequia-access.json`
- **Validation** — checks JSON structure before saving; shows errors inline
- **Effective state summary** — shows what the rules mean in plain language
- **Override warning** — when this sidecar overrides a parent's recursive rule, call it out
- **Save** — writes `.acequia-access.json` via PUT to the WebDAV server

### Cascading Rule Visualization

When viewing any directory, the access rules footer shows the full inheritance chain:

```
Access Rules for /public/images/photos/

  🌐 Public (anonymous read) — inherited from /public/
     Source: /public/.acequia-access.json
     recursive: true
     denyPatterns: .env, *.key

  No override at this directory.
  [Add override...] [Edit parent rule...]
```

If the directory has its own sidecar that overrides:

```
Access Rules for /public/images/private-photos/

  🔒 Private (authentication required) — explicit
     Source: /public/images/private-photos/.acequia-access.json

  Overrides parent rule:
     🌐 /public/ → recursive: true (would apply here)

  [Edit rule...] [Remove override (inherit from parent)]
```

### CRUD Operations

| Operation | Trigger | Implementation |
|-----------|---------|----------------|
| **Upload** | Drag files onto detail pane, or toolbar button | PUT to WebDAV path |
| **New folder** | Toolbar button or right-click | MKCOL |
| **Delete** | Right-click or Delete key (with confirm) | DELETE |
| **Rename** | Right-click or F2 | MOVE (same parent, new name) |
| **Move** | Drag to tree sidebar folder | MOVE |
| **Copy** | Ctrl+drag, or right-click | COPY |
| **Copy URL** | Right-click menu | Copy WebDAV URL to clipboard |
| **Download** | Right-click or double-click file | GET + save |

**Drag and drop patterns:**
- Drag file/folder over tree sidebar → highlight target folder, drop to move
- Ctrl+drag → copy instead of move
- Drag from OS filesystem → upload files
- Multi-select + drag moves all selected items
- Ghost preview shows item count badge for multi-select
- Invalid drop targets (e.g., onto a file) → cursor shows "not allowed"
- Escape cancels drag

### Data Flow

```
                  ┌───────────────────┐
                  │ <acequia-file-    │
                  │  browser>         │
                  │                   │
 WebDAV           │  ┌─────────────┐  │
 Server ◄────────►│  │ DataSource  │  │  ← PROPFIND, GET, PUT, DELETE, MKCOL, MOVE
 (Nephele or      │  │ (fetch API) │  │
  BrowserDAV)     │  └──────┬──────┘  │
                  │         │         │
                  │  ┌──────▼──────┐  │
                  │  │ AccessState │  │  ← reads .acequia-access.json via GET
                  │  │ Resolver    │  │  ← caches sidecar data, resolves cascading rules
                  │  └──────┬──────┘  │
                  │         │         │
                  │  ┌──────▼──────┐  │
                  │  │ TreeView +  │  │  ← renders tree sidebar
                  │  │ DetailPane  │  │  ← renders file list with access indicators
                  │  └──────┬──────┘  │
                  │         │         │
                  │  ┌──────▼──────┐  │
                  │  │ SidecarEdit │  │  ← modal/panel for editing rules
                  │  └─────────────┘  │
                  └───────────────────┘
```

**AccessState Resolver:**
- On directory navigation, checks for `.acequia-access.json` in current directory
- Walks up parent directories to find inherited rules (cached from tree expansion)
- Evaluates effective state per-item in the listing (public/private/denied)
- Updates tree sidebar icons when rules change

### File Structure

**Design proposed** `acequia/stigmergic/BrowserDAV/components/`. **Actual location:** `public/components/acequia-file-browser/` — served from the node WebDAV server's public directory so both the dashboard and BrowserDAV can load it.

```
public/components/acequia-file-browser/
  acequia-file-browser.js      ← main custom element (orchestrator, Shadow DOM)
  file-tree.js                 ← tree sidebar sub-component
  file-list.js                 ← detail pane sub-component
  sidecar-editor.js            ← access rule editor panel
  access-resolver.js           ← sidecar resolution + cascading logic
  webdav-client.js             ← WebDAV HTTP client
  styles.js                    ← shared CSS (template literal, custom properties)
```

Each sub-component is a standalone custom element that can be composed:
- `<file-tree>` — tree sidebar, emits `directory-selected`
- `<file-list>` — sortable table, emits `file-action` events
- `<sidecar-editor>` — rule editing panel, emits `access-changed`

### Integration with BrowserDAV Dashboard

Add a new section to `index.html` below the directories section:

```html
<div class="section">
    <h2 class="section-title">📂 File Browser</h2>
    <div class="card">
        <acequia-file-browser
            id="fileBrowser"
            show-access-indicators
            editable
        ></acequia-file-browser>
    </div>
</div>
```

The component auto-discovers the WebDAV root URL from the BrowserDAV app's configuration.

### Integration with Server Dashboard

The same component works on the Nephele server's `dashboard.html` with a different `root-url`:

```html
<acequia-file-browser
    root-url="https://stigmergic.acequia.live/"
    auth-token="${ownerToken}"
    show-access-indicators
    editable
></acequia-file-browser>
```

---

## Open Questions

1. **~~Sidecar editor access control~~** — *Resolved: Write access required. In BrowserDAV, the owner bypass token grants full write access to the local file browser. On the server dashboard, the owner's auth cookie provides it. For remote users, dotfile filtering blocks sidecar visibility/editing for non-writers.*

2. **Batch operations** — Should "Make public" work on multiple selected folders at once? (Deferred — multi-select not yet implemented.)

3. **Preview pane** — Should the file browser include a file preview panel (images, text, markdown)? (Deferred — nice to have.)

4. **Offline state** — When BrowserDAV's File System Access API handles are unavailable (permission revoked), how should the tree reflect this? (Not yet handled.)

5. **Real-time updates** — Should the tree auto-refresh when files change on disk? (Not yet handled — manual refresh via re-navigation.)

6. **Mobile/touch testing** — Context menus and drag-drop need touch device testing.
