upload volledige repo

This commit is contained in:
kodi
2026-03-11 09:39:41 +01:00
commit ce420cbb0e
110 changed files with 5660 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# AGENTS.md
Dit document beschrijft hoe AI agents in dit project moeten werken.
## Rollen
### GPT
Verantwoordelijk voor:
- architectuur
- analyse
- taakopdeling
- teststrategie
- Codex prompts
- review van wijzigingen
### Codex
Verantwoordelijk voor:
- implementeren van kleine wijzigingen
- toevoegen van tests
- aanpassen van documentatie
Codex mag **geen architectuur wijzigen** zonder instructie.
+137
View File
@@ -0,0 +1,137 @@
# API_GOLDEN.md
Dit document definieert stabiele API responses.
Velden mogen niet wijzigen zonder golden tests te updaten.
## Browse
### `GET /api/browse`
Response shape:
```json
{
"path": "storage1",
"directories": [],
"files": []
}
```
## File Ops (direct)
### `POST /api/files/mkdir`
Success:
```json
{ "path": "storage1/parent/new_dir" }
```
Conflict (`already_exists`):
```json
{
"error": {
"code": "already_exists",
"message": "Target path already exists",
"details": { "path": "storage1/parent/new_dir" }
}
}
```
Invalid name (`invalid_request`):
```json
{
"error": {
"code": "invalid_request",
"message": "Invalid name",
"details": { "name": "bad/name" }
}
}
```
### `POST /api/files/rename`
Success:
```json
{ "path": "storage1/parent/new_name.ext" }
```
Conflict (`already_exists`) + invalid name (`invalid_request`) gebruiken dezelfde error-shape als mkdir.
### `POST /api/files/delete`
Success:
```json
{ "path": "storage1/parent/file_or_empty_dir" }
```
Non-empty directory:
```json
{
"error": {
"code": "directory_not_empty",
"message": "Directory is not empty",
"details": { "path": "storage1/parent/non_empty" }
}
}
```
## Task-based create endpoints
### `POST /api/files/copy`
### `POST /api/files/move`
Success (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
## Tasks read endpoints
### `GET /api/tasks`
Response shape:
```json
{
"items": [
{
"id": "<uuid>",
"operation": "copy",
"status": "running",
"source": "storage1/a.txt",
"destination": "storage2/a.txt",
"created_at": "2026-03-10T10:00:00Z",
"finished_at": null
}
]
}
```
### `GET /api/tasks/{task_id}`
Response shape:
```json
{
"id": "<uuid>",
"operation": "move",
"status": "running",
"source": "storage1/a.txt",
"destination": "storage2/a.txt",
"done_bytes": 1024,
"total_bytes": 4096,
"done_items": null,
"total_items": null,
"current_item": "storage1/a.txt",
"failed_item": null,
"error_code": null,
"error_message": null,
"created_at": "2026-03-10T10:00:00Z",
"started_at": "2026-03-10T10:00:01Z",
"finished_at": null
}
```
Task not found:
```json
{
"error": {
"code": "task_not_found",
"message": "Task was not found",
"details": { "task_id": "task-missing" }
}
}
```
+87
View File
@@ -0,0 +1,87 @@
# BACKEND_V1_CONSOLIDATION.md
## Doel
Consolidatie van de huidige backend v1 contracten en beperkingen.
## Huidige endpoints
Directe endpoints (geen task-creatie):
- `GET /api/browse`
- `POST /api/files/mkdir`
- `POST /api/files/rename`
- `POST /api/files/delete`
- `GET /api/tasks`
- `GET /api/tasks/{task_id}`
Task-based create endpoints:
- `POST /api/files/copy`
- `POST /api/files/move`
## Semantiek per endpoint
### `GET /api/browse`
- Browse van een directory binnen whitelist roots.
- Hidden files standaard verborgen, optioneel via `show_hidden=true`.
### `POST /api/files/mkdir`
- Maakt directory op exact `parent_path + name`.
- Geen impliciete pad-normalisatie buiten `path_guard` validatie.
### `POST /api/files/rename`
- Hernoemt binnen dezelfde parent directory.
- Geen verborgen move naar andere map.
### `POST /api/files/delete`
- Verwijdert file direct.
- Verwijdert alleen lege directory.
- Non-empty directory => conflict.
### `POST /api/files/copy`
- File-only copy.
- `destination` is volledig doelpad (geen "copy into directory").
- Task wordt aangemaakt (`202`, `task_id`, `queued`) en daarna uitgevoerd.
### `POST /api/files/move`
- File-only move.
- `destination` is volledig doelpad.
- Task wordt aangemaakt (`202`, `task_id`, `queued`) en daarna uitgevoerd.
- Same-root: native move.
- Cross-root: copy + delete binnen dezelfde task.
### `GET /api/tasks`
- Lijst van tasks, gesorteerd op `created_at DESC`.
- Item bevat minimaal: `id`, `operation`, `status`, `source`, `destination`, `created_at`, `finished_at`.
### `GET /api/tasks/{task_id}`
- Detailstatus inclusief progress/foutvelden.
- Statusset: `queued`, `running`, `completed`, `failed`.
## Foutmodel per endpointgroep
### Browse/file-validatie
- `invalid_request`
- `path_traversal_detected`
- `path_outside_whitelist`
- `invalid_root_alias`
- `path_not_found`
- `type_conflict`
- `already_exists`
- `directory_not_empty` (delete)
### Task create runtime
- validatiefouten vóór task-creatie: directe API-fout, geen task
- runtime-fouten tijdens task-uitvoering: task naar `failed` met `io_error`
### Tasks read
- `task_not_found` bij onbekend task id
## Expliciete v1-beperkingen
- copy: file-only
- move: file-only
- delete: alleen file + lege directory
- geen recursive delete
- geen directory copy/move
- geen rollback
- geen cancel/retry
- geen batch-operaties
@@ -0,0 +1,21 @@
# BOOKMARKS_V1_CONSOLIDATION.md
## Endpoints
- `POST /api/bookmarks`
- `GET /api/bookmarks`
- `DELETE /api/bookmarks/{bookmark_id}`
## Duplicate policy
- Een bookmark is uniek op `path`.
- Een tweede create met hetzelfde pad geeft `409 already_exists`.
## Validatie
- `path` wordt centraal via `path_guard.resolve_path(...)` gevalideerd.
- Dit dekt whitelist, traversal en root-alias validatie.
- `label` mag niet leeg zijn (`trim()`), anders `400 invalid_request`.
## Model v1
- `id`
- `path`
- `label`
- `created_at`
+22
View File
@@ -0,0 +1,22 @@
# CHANGE_POLICY.md
## Toegestaan zonder toestemming
- documentatie verbeteren
- tests toevoegen
- logging verbeteren
- kleine bugfixes
## Eerst voorstel nodig
- API wijzigingen
- dependency toevoegen
- database schema wijziging
- frontend flow aanpassen
## Expliciete goedkeuring vereist
- security model aanpassen
- whitelist wijzigen
- directory structuur aanpassen
- destructieve acties veranderen
+274
View File
@@ -0,0 +1,274 @@
# COPY_MOVE_TASKS_DESIGN.md
## Doel
Ontwerpvoorstel voor de volgende implementatieslice: `copy`, `move` en `tasks`.
Dit document bevat alleen ontwerpkeuzes. Er is geen code-implementatie in deze stap.
## 1. Destination-semantiek (expliciet)
V1 kiest een strikte semantiek:
- `destination` is altijd het volledige beoogde eindpad (inclusief bestandsnaam).
- `destination` mag in v1 **niet** geïnterpreteerd worden als "copy/move into existing directory".
- Als `destination` al bestaat (file of directory) -> `already_exists` (409).
Voorbeeld v1:
- source: `storage1/a/file.txt`
- destination: `storage2/b/file_copy.txt`
Niet toegestaan als impliciete directory-target in v1:
- destination: `storage2/b/` met verwachting dat `file.txt` automatisch eronder komt.
Reden:
- voorkomt ambigu gedrag
- eenvoudiger API-contract
- minder regressierisico
## 2. Scopevoorstel `copy` v1
### Keuze
- `copy` ondersteunt in v1 **alleen files**.
- directory-recursie schuift door naar een latere fase.
### Motivatie
- Complexiteit: recursieve copy voegt veel edge-cases toe (diepe bomen, symlinks, partial failures).
- Testbaarheid: file-only maakt golden/regressie tests kleiner en deterministischer.
- Regressierisico: lagere kans op onverwachte performance/security regressies.
### Conflictgedrag
- destination bestaat al -> `already_exists` (409).
- geen overwrite/merge in v1.
### Uitvoering
- altijd task-based (async).
- create response: `task_id` + `queued`.
### Foutmodel copy
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `type_conflict` (409) (source is geen file)
- `already_exists` (409)
- `io_error` (500)
## 3. Scopevoorstel `move` v1
### Rename vs move
- `rename` blijft naamwijziging binnen dezelfde parent directory.
- `move` is padwijziging naar een ander eindpad (zelfde root of cross-root).
### Cross-root gedrag
- toegestaan als source en destination beide binnen whitelist vallen.
- zelfde root: native rename/move waar mogelijk.
- cross-root: copy+delete binnen dezelfde task.
### Keuze
- `move` ondersteunt in v1 **alleen files**.
- directory-move buiten scope in v1.
### Conflictgedrag
- destination bestaat al -> `already_exists` (409).
- geen overwrite in v1.
### Foutmodel move
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `type_conflict` (409) (source is geen file)
- `already_exists` (409)
- `io_error` (500)
## 4. Symlinkbeleid (copy/move)
### Source
- v1 file-only: source mag geen symlink zijn.
- als source symlink resolvet naar pad buiten whitelist -> geblokkeerd (`path_outside_whitelist`).
- als source symlink resolvet binnen whitelist: in v1 alsnog afwijzen als `type_conflict` om semantiek simpel te houden.
### Destination
- destination wordt via `path_guard` canoniek gevalideerd.
- destination parent moet binnen whitelist liggen.
- destination parent die via symlink buiten whitelist valt -> blokkeren (`path_outside_whitelist`).
### Recursieve escapes
- Niet van toepassing in v1 (geen directory-recursie).
- Voor latere directory-copy geldt: elk bezocht pad moet per entry containment-check krijgen.
## 5. Task persistence en history
### Relatie tasks vs history
- In v1 zijn `tasks` en `history` aparte modellen/tabelrollen:
- `tasks`: actuele en afgeronde task-status/progress
- `history`: audit-log van uitgevoerde operaties
- history kan vanuit task-completion gevuld worden, maar is niet hetzelfde model.
### Retentie
- v1 bewaart `completed` en `failed` tasks persistent (geen automatische cleanup in scope).
### Sortering GET /api/tasks
- standaard sortering: `created_at DESC` (nieuwste eerst).
## 6. Taskmodel
### Wanneer task verplicht is
- `copy` en `move` altijd task-based.
### Statussen
- `queued`
- `running`
- `completed`
- `failed`
### Progress
- file-only v1:
- `done_bytes`
- `total_bytes`
- `done_items`/`total_items` optioneel en kunnen `null` blijven in v1.
### Partial failure
- fail-fast.
- eerste fatale fout -> `failed`.
- geen rollback in v1.
- taskdetail bevat `failed_item`, `error_code`, `error_message`.
### Duplicate create requests (correctie)
- v1 gedrag is **niet idempotent**.
- twee identieke requests mogen twee verschillende tasks creëren.
## 7. API-voorstel
### Endpoints
- `POST /api/files/copy`
- `POST /api/files/move`
- `GET /api/tasks/{task_id}`
- `GET /api/tasks`
### Request/response shapes
#### POST /api/files/copy
Request:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_copy.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### POST /api/files/move
Request:
```json
{
"source": "storage1/path/file.txt",
"destination": "storage2/path/file_moved.txt"
}
```
Response (202):
```json
{
"task_id": "<uuid>",
"status": "queued"
}
```
#### GET /api/tasks/{task_id}
Response (200):
```json
{
"id": "<uuid>",
"operation": "copy",
"status": "running",
"source": "storage1/a/file.txt",
"destination": "storage2/b/file.txt",
"done_bytes": 1024,
"total_bytes": 4096,
"done_items": null,
"total_items": null,
"current_item": "storage1/a/file.txt",
"failed_item": null,
"error_code": null,
"error_message": null,
"created_at": "2026-03-10T00:00:00Z",
"started_at": "2026-03-10T00:00:01Z",
"finished_at": null
}
```
#### GET /api/tasks
Response (200):
```json
{
"items": [
{
"id": "<uuid>",
"operation": "move",
"status": "completed",
"source": "storage1/a/file.txt",
"destination": "storage2/b/file.txt",
"created_at": "2026-03-10T00:00:00Z",
"finished_at": "2026-03-10T00:00:05Z"
}
]
}
```
### Waarom `source` en `destination` ook in task-list
- Ja, opnemen in `GET /api/tasks`.
- Reden: operator kan zonder extra detail-calls direct zien wat elke task doet.
- Dit maakt UI en troubleshooting eenvoudiger.
### Error codes (versmald)
- `invalid_request` (400)
- `path_traversal_detected` (403)
- `path_outside_whitelist` (403)
- `invalid_root_alias` (403)
- `path_not_found` (404)
- `task_not_found` (404)
- `type_conflict` (409)
- `already_exists` (409)
- `io_error` (500)
`internal_error` wordt in v1 niet als aparte publieke code gebruikt; onverwachte fouten worden genormaliseerd naar `io_error` of framework-500.
## 8. Teststrategie
### Golden tests
- `POST /api/files/copy` success (task create shape)
- `POST /api/files/move` success (task create shape)
- `already_exists` voor copy/move
- `type_conflict` als source geen file is
- `invalid_request` voor ongeldige payload
- traversal/root errors voor source en destination
- `GET /api/tasks/{task_id}` shapes voor `queued/running/completed/failed`
- `GET /api/tasks` list shape inclusief `source` en `destination`
### Regressietests
- cross-root move gebruikt copy+delete pad correct
- file copy/move met unicode namen
- grote files progress updates blijven monotone stijging
- duplicate create requests leveren 2 verschillende `task_id`s (niet-idempotent)
### Securitytests
- traversal blokkade op source/destination
- destination outside whitelist blokkade
- invalid root alias blokkade
- symlink source wordt afgewezen
- symlinked destination parent buiten whitelist wordt afgewezen
## Implementatiegrenzen voor volgende stap
- Geen wijziging aan bestaande endpoints:
- browse
- mkdir
- rename
- delete
- Geen nieuwe dependencies zonder expliciete motivatie.
+50
View File
@@ -0,0 +1,50 @@
# COPY_V1_CONSOLIDATION.md
## Task lifecycle (copy v1)
`POST /api/files/copy`:
1. Request wordt gevalideerd.
2. Bij geldige input wordt direct een task aangemaakt met status `queued`.
3. Een achtergrond-runner zet de task op `running` en vult progress (`done_bytes`, `total_bytes`).
4. Eindstatus:
- `completed` bij succesvolle file copy
- `failed` bij runtime I/O fout (`error_code = io_error`)
## Validatiefouten vs runtime-fouten
Validatiefouten (voor task-creatie):
- `invalid_request`
- `path_traversal_detected`
- `path_outside_whitelist`
- `invalid_root_alias`
- `path_not_found`
- `type_conflict`
- `already_exists`
Gedrag:
- request faalt direct met error response
- er wordt geen task aangemaakt
Runtime-fouten (na task-creatie):
- `io_error`
Gedrag:
- request zelf retourneert `202` met `task_id`
- task gaat naar `failed`
- foutdetails verschijnen via `GET /api/tasks/{task_id}`
## Copy metadata in v1 (`copy_file(...)`)
V1 kopieert:
- file-inhoud (byte stream)
- basic filesystem metadata via `copystat` (mtime/atime/mode waar ondersteund)
V1 doet niet expliciet:
- ownership/ACL normalisatie
- extended attributes beleid
## Destination-semantiek (expliciet)
`destination` blijft in v1 altijd het volledige doelpad.
- Geen impliciete "copy into existing directory" interpretatie.
- Als destination al bestaat: `already_exists`.
@@ -0,0 +1,49 @@
# MKDIR_RENAME_CONSOLIDATION.md
## Gedragsbevestiging
- `POST /api/files/rename` werkt alleen binnen dezelfde parent directory.
- De target wordt opgebouwd als `parent(source_path) + new_name`.
- `rename` laat geen verborgen move toe.
- `new_name` accepteert geen padsegmenten (`/`, `\\`, `..`), dus geen directorywissel.
- `validate_name` wordt toegepast op:
- `mkdir.name`
- `rename.new_name`
## Error model voor mkdir/rename
Standaard error shape:
```json
{
"error": {
"code": "...",
"message": "...",
"details": {"...": "..."}
}
}
```
Ondersteunde codes in deze slice:
- `invalid_request`
- ongeldige naam (`..`, leeg, of met `/`/`\\`)
- HTTP `400`
- `path_traversal_detected`
- traversal in aangeleverd pad (`../`)
- HTTP `403`
- `path_not_found`
- bronpad of parent pad bestaat niet
- HTTP `404`
- `already_exists`
- doelpad bestaat al (file of directory)
- HTTP `409`
- `io_error`
- onverwachte filesystem fout tijdens operatie
- HTTP `500`
## Scopebevestiging
Deze consolidatie wijzigt niet:
- browse endpoint
- delete/copy/move/tasks/frontend
+123
View File
@@ -0,0 +1,123 @@
# PRODUCT_SPEC.md
## Project
Web-based Storage Manager
Een webapplicatie waarmee gebruikers bestanden op
whitelisted storage volumes kunnen beheren.
De applicatie draait in een containeromgeving.
---
# Doel
Een veilige webinterface bouwen voor filesystem beheer.
Gebruikers moeten via een browser:
- directories kunnen browsen
- bestanden kunnen kopiëren
- bestanden kunnen verplaatsen
- bestanden kunnen hernoemen
- bestanden kunnen verwijderen
- mappen kunnen aanmaken
- bookmarks kunnen opslaan
De applicatie moet geschikt zijn voor gebruik binnen een
self-hosted infrastructuur.
---
# Architectuur uitgangspunten
Backend
Python + FastAPI
Frontend
lichte JS UI zonder zware frameworks.
Database
SQLite voor:
- tasks
- bookmarks
- history
---
# Storage model
De applicatie mag alleen werken binnen
**whitelisted root directories**.
Voorbeeld:
/mnt/storage1
/mnt/storage2
/media/archive
Alle paden moeten binnen deze roots blijven.
Traversal en symlink escapes moeten geblokkeerd worden.
---
# Functionaliteiten
## Directory browsing
De UI moet directories kunnen tonen.
Response moet bevatten:
- directories
- files
- metadata (size, modified)
---
## File operations
Ondersteunde acties:
rename
move
copy
delete
create directory
---
## Task system
Langlopende acties (copy/move):
- krijgen een task id
- status wordt opgeslagen
- progress kan opgehaald worden via API
---
# Security eisen
- path validation
- whitelist enforcement
- symlink protection
- traversal protection
---
# Out of scope
Deze functionaliteit hoort **niet** bij versie 1:
- user management
- permissions management
- cloud storage
- distributed storage
- multi-node clusters
+20
View File
@@ -0,0 +1,20 @@
# SAFE_FILES.md
## Safe to edit
- backend code
- frontend js
- tests
- documentatie
## Ask first
- database schema
- container config
- security modules
## Do not edit
- deployment configs
- storage volume mappings
- whitelist configuration
+72
View File
@@ -0,0 +1,72 @@
# STEP1_5_CONSOLIDATION.md
## Doel
Consolidatie van stap 1 t/m 5 (backend skeleton, path_guard, unit tests, browse endpoint, golden tests).
## 1. Test-run context
Tests draaien vanuit repository root met:
- `PYTHONPATH=webui`
Definitieve testcommando's voor deze fase:
- `PYTHONPATH=webui python3 -m unittest discover -s webui/backend/tests/unit -p "test_*.py" -v`
- `PYTHONPATH=webui python3 -m unittest discover -s webui/backend/tests/golden -p "test_*.py" -v`
- `PYTHONPATH=webui python3 -m unittest discover -s webui/backend/tests -p "test_*.py" -v`
## 2. Minimale dependencies voor stap 1-5
Vastgelegd in:
- `webui/backend/requirements.txt`
Benodigd voor deze fase:
- FastAPI app + browse endpoint
- unit tests
- golden tests
Dependencies:
- `fastapi==0.111.0`
- `starlette==0.37.2`
- `pydantic==2.12.5`
- `httpx==0.27.2`
- `anyio==4.4.0`
- `sniffio==1.3.1`
## 3. Minimale wijzigingen in deze fase
Functioneel gewijzigde bestanden:
- `webui/backend/app/api/routes_browse.py`
- `webui/backend/app/dependencies.py`
- `webui/backend/app/main.py`
Testmatig gewijzigde bestanden:
- `webui/backend/tests/golden/test_api_browse_golden.py`
- `webui/backend/tests/golden/test_api_errors_golden.py`
Waarom golden tests van TestClient naar `httpx.ASGITransport` zijn omgezet:
- In deze omgeving blokkeerde de TestClient-runner op sync execution pad.
- `httpx.ASGITransport` voert requests in-memory tegen de ASGI app uit zonder die blokkade.
- Dit is een testuitvoerbaarheidsfix, geen API-contractwijziging.
Waarom async wijzigingen nodig waren:
- Error-pad en dependency-pad moesten async blijven om runtime-blokkade in deze omgeving te vermijden.
- De wijzigingen zijn intern uitvoeringsgericht; het HTTP-contract blijft gelijk.
## 4. Contractbehoud (expliciet)
Door deze consolidatiestap is niets gewijzigd aan:
- endpointpad: `GET /api/browse`
- query parameters: `path`, `show_hidden`
- response shape: `path`, `directories[]`, `files[]` met dezelfde velden
- error codes: o.a. `invalid_root_alias`, `path_traversal_detected`, `path_not_found`, `path_type_conflict`
- hidden files policy: default hidden uit, optioneel via `show_hidden=true`
## 5. Scopebevestiging
Niet geïmplementeerd in deze consolidatiestap:
- rename
- mkdir
- delete
- copy
- move
- tasks/worker
- frontend
+34
View File
@@ -0,0 +1,34 @@
# TEST_STRATEGY.md
## Unit tests
Test individuele functies:
- path validation
- whitelist checks
- helper functions
- task status transitions
## Feature tests
Test volledige flows:
- browse directory
- copy
- move
- rename
- delete
- create folder
## Regression tests
Bescherm tegen bekende problemen:
- path traversal
- unicode filenames
- grote directories
- nested directories
## API Golden tests
Controleer dat response formats niet veranderen.
+233
View File
@@ -0,0 +1,233 @@
# UI_DUAL_PANE_DESIGN.md
## Doel van deze notitie
Deze notitie beschrijft de ontwerpstap voor **Dual-pane UI v2** op basis van de huidige v1.1 UI en `UI_VISION_MC.md`.
Randvoorwaarden voor deze stap:
- geen backendwijzigingen
- geen nieuwe dependencies
- geen multi-select
- geen viewer/editor
- geen keyboard-uitbreiding (behalve als latere notitie)
---
## 1) Ombouw van single-page UI naar twee panelen
### Huidige situatie (v1.1)
- één browsepaneel met één `currentPath`
- globale selectie voor dat paneel
- acties (mkdir/rename/delete/copy/move) vanuit die ene context
### Doelsituatie (v2)
- twee browsepanelen naast elkaar: `left` en `right`
- elk paneel heeft:
- eigen path input + go
- eigen breadcrumbs
- eigen directory/file lijst
- eigen geselecteerd item
- één paneel is altijd **actief**
- acties worden contextueel uitgevoerd vanuit actief paneel
- tasks- en bookmarks-sectie blijven gedeeld (onder of naast de panelen)
### UI-structuur op hoog niveau
- Header: algemene status
- Main layout:
- Paneel links (`left-pane`)
- Paneel rechts (`right-pane`)
- Utility-kolom (tasks + bookmarks)
- Actieknoppen:
- per paneel (mkdir)
- actieve-paneelacties (rename/delete/copy/move)
### Bookmark-open gedrag (expliciete keuze)
- Een klik op een bookmark opent altijd in het **actieve paneel**.
- Motivatie (kort): dit is het meest voorspelbaar, sluit aan op het active/inactive-pane model, en voorkomt verborgen side-effects in het niet-actieve paneel.
---
## 2) Benodigde state per paneel
## State-model
```js
state = {
panes: {
left: {
currentPath: "storage1",
showHidden: false,
selectedItem: null, // { path, name, kind: "file"|"directory" }
entries: { directories: [], files: [] }
},
right: {
currentPath: "storage1",
showHidden: false,
selectedItem: null,
entries: { directories: [], files: [] }
}
},
activePane: "left", // "left" | "right"
selectedTaskId: null,
pollHandle: null
}
```
### Verplichte kernstate
- `current path` per paneel
- `selected item` per paneel
- `active pane` globaal
### Afgeleide helperstate
- `inactivePane = activePane === "left" ? "right" : "left"`
- `canRename/canDelete` op basis van selectie actief paneel
- `canCopy/canMove` alleen als selectie actief paneel een file is
---
## 3) Copy/move van actief paneel naar ander paneel
## Semantiek v2
- Bron komt altijd uit `selectedItem` van `activePane`.
- Doelcontext is standaard het **andere paneel**.
- `destination` blijft volledig doelpad (geen impliciete shell-achtige "copy into" semantics).
### Destination-bepaling
- `targetBasePath = panes[inactivePane].currentPath`
- `destination = targetBasePath + "/" + sourceItem.name`
- UI toont dit voorstel in prompt/confirm en laat aanpassen toe.
### Copy flow
1. User selecteert file in actief paneel.
2. User kiest `Copy`.
3. UI bouwt default destination op basis van inactieve paneel-path + bestandsnaam.
4. UI roept `POST /api/files/copy` met `{source, destination}`.
5. Bij `202`: tasks refresh + taskdetail naar nieuw task_id.
6. Na start: beide panelen refresh (best effort), zodat user contextueel resultaat ziet.
### Move flow
1. Zelfde als copy, maar endpoint `POST /api/files/move`.
2. Bij `202`: tasks refresh + taskdetail selecteren.
3. Beide panelen refreshen (source kan verdwijnen, destination verschijnen).
### Refresh-semantiek per actie (expliciet)
- `mkdir`: refresh alleen het actieve paneel.
- `rename`: refresh alleen het actieve paneel.
- `delete`: refresh alleen het actieve paneel.
- `copy`: refresh beide panelen.
- `move`: refresh beide panelen.
Rationale (kort):
- Lokale mutaties (`mkdir/rename/delete`) zijn paneelgebonden en blijven daardoor snel en voorspelbaar.
- Transfer-acties (`copy/move`) raken bron- en doelcontext, dus beide panelen verversen voorkomt stale state.
### Foutafhandeling
- Validatiefouten vóór task-creatie direct tonen op actiegebied actief paneel.
- Runtime-fouten via task-status (`failed`) zichtbaar in tasklijst/detail.
---
## 4) Bestaande backend-endpoints die hergebruikt worden
Geen contractwijzigingen; hergebruik van bestaande endpoints:
Browse/file ops:
- `GET /api/browse`
- `POST /api/files/mkdir`
- `POST /api/files/rename`
- `POST /api/files/delete`
- `POST /api/files/copy`
- `POST /api/files/move`
Tasks:
- `GET /api/tasks`
- `GET /api/tasks/{task_id}`
Bookmarks:
- `GET /api/bookmarks`
- `POST /api/bookmarks`
- `DELETE /api/bookmarks/{bookmark_id}`
---
## 5) Waarschijnlijk te wijzigen bestanden
Primair frontend:
- `webui/html/index.html`
- layout naar dual-pane
- pane-specifieke controls/containers
- `webui/html/app.js`
- state refactor naar `panes.left/right`
- browse/render/actions per pane
- active-pane switching
- copy/move destination vanuit ander pane
- `webui/html/style.css`
- twee panelen visueel gelijkwaardig
- actieve-paneel-highlight
- responsieve fallback (mobiel: gestapeld)
Tests:
- `webui/backend/tests/golden/test_ui_smoke_golden.py`
- assertions bijwerken naar dual-pane markup/ids
Niet gepland in deze stap:
- backend python-bestanden
- API schemas/routes/services
---
## 6) Regressierisico
## Hoog risico
- Stateverwarring tussen links/rechts paneel (actie op verkeerde paneelcontext).
- Onjuiste destination-opbouw bij copy/move (per ongeluk vanuit actief i.p.v. inactief pad).
## Middel risico
- Disable/enable logica van knoppen niet synchroon met actieve paneelselectie.
- Refresh-volgorde na acties waardoor UI tijdelijk stale data toont.
- Bookmarks die per ongeluk in het verkeerde paneel openen als `activePane` niet consequent wordt gebruikt.
## Laag risico
- CSS regressies in mobile layout.
- Kleine tekst/label inconsistenties in error/statusweergave.
## Mitigatie
- Duidelijke pane-identifiers in DOM en handlers (`left`/`right`).
- Eén centrale helper voor `getActivePane()` en `getInactivePane()`.
- Eén centrale helper voor copy/move destination-opbouw.
- Smoke tests expliciet op dual-pane hoofdstructuur.
---
## 7) Aan te passen/toe te voegen UI smoke tests
## Aanpassen bestaande smoke test
`test_ui_smoke_golden.py`:
- huidige checks op enkel browsepaneel vervangen door dual-pane checks.
Nieuwe/gewijzigde assertions:
- UI mount bestaat op `/ui`.
- HTML bevat beide hoofdpanelen:
- `id="left-pane"`
- `id="right-pane"`
- HTML bevat actieve-paneel-indicator/container (bijv. class of data-attribute).
- Assets blijven gemapt:
- `/ui/app.js`
- `/ui/style.css`
## Kleine extra smoke checks (zonder backenduitbreiding)
- basisactieknoppen aanwezig voor actieve-paneelacties.
- tasks- en bookmarks-panelen blijven aanwezig in HTML.
Geen end-to-end browserautomatisering in deze stap.
---
## Niet in scope voor deze implementatieslice
- multi-select
- viewer/editor
- cancel/retry
- history UI
- nieuwe backend endpoints
- keyboard mapping uitbreiding (eventueel later)
+141
View File
@@ -0,0 +1,141 @@
# UI_VISION_MC.md
## Doel
De webapp moet geleidelijk evolueren naar een web-based bestandsbeheerder
met een workflow die geïnspireerd is op Midnight Commander.
Het doel is **niet** om Midnight Commander exact te klonen,
maar om de belangrijkste werkprincipes over te nemen:
- twee panelen naast elkaar
- snel wisselen tussen panelen
- actief paneel en inactief paneel
- copy/move van actief paneel naar ander paneel
- keyboard-efficiënte bediening
- focus op snelheid en weinig muisafhankelijkheid
---
## Ontwerpprincipes
- web-native uitvoering, geen letterlijke terminal-clone
- behoud van bestaande backend API-contracten
- incrementele UI-ontwikkeling in kleine stappen
- veiligheid en voorspelbaarheid blijven leidend
- bestaande werkende functionaliteit mag niet regressief kapot gaan
---
## Doelbeeld
De UI moet uiteindelijk bestaan uit:
### 1. Dubbel paneel
- een linker paneel
- een rechter paneel
- elk paneel heeft een eigen current path
- elk paneel toont directories en files
- elk paneel heeft een eigen selectie
### 2. Actief paneel
- één paneel is actief
- acties worden gestart vanuit het actieve paneel
- het andere paneel is standaard de doelcontext voor copy/move
### 3. Navigatie
- pad boven elk paneel zichtbaar
- directorynavigatie per paneel
- later uitbreidbaar met keyboard shortcuts
### 4. Bestandsacties
- rename
- mkdir
- delete
- copy
- move
### 5. Taken
- tasklijst zichtbaar
- status polling blijft mogelijk
- copy/move blijven task-based
---
## Buiten scope voor de eerstvolgende UI-stap
Deze dingen worden nu nog niet gebouwd:
- multi-select
- Insert-achtige selectie
- viewer
- editor
- menubalk zoals F9
- chmod/chown/symlink tools
- zoeken
- directory compare
- volledige keyboard mapping
---
## Eerstvolgende UI-doel
De eerstvolgende UI-stap is:
### Dual-pane UI v2
Deze stap bevat alleen:
- links en rechts paneel
- per paneel eigen current path
- actief paneel
- visuele actieve paneel-indicatie
- copy/move gebruiken standaard het andere paneel als destination context
- bestaande backend-endpoints blijven ongewijzigd
Nog niet in deze stap:
- multi-select
- uitgebreide keyboard controls
- viewer/editor
- history UI
- nieuwe backend features
---
## Verwachte impact
Waarschijnlijk vooral wijziging in:
- `webui/html/index.html`
- `webui/html/app.js`
- `webui/html/style.css`
Backend hergebruik:
- `GET /api/browse`
- `POST /api/files/mkdir`
- `POST /api/files/rename`
- `POST /api/files/delete`
- `POST /api/files/copy`
- `POST /api/files/move`
- `GET /api/tasks`
- `GET /api/tasks/{task_id}`
- `GET /api/bookmarks`
- `POST /api/bookmarks`
- `DELETE /api/bookmarks/{bookmark_id}`
---
## Acceptatiecriteria voor de volgende UI-stap
De dual-pane stap is pas klaar als:
- beide panelen onafhankelijk kunnen browsen
- elk paneel een eigen path toont
- één paneel actief is
- actief paneel visueel herkenbaar is
- copy/move logisch vanuit actief naar ander paneel werken
- bestaande functionaliteit niet stuk gaat
- bestaande tests blijven slagen
- UI smoke tests waar nodig zijn bijgewerkt