feat(files): a Files section over the caller's whole space
Nightly Build / build (push) Successful in 5m36s
Nightly Build / build (push) Successful in 5m36s
Until now file browsing existed only inside a project, and the two memory
stores were reachable only by the agent's tools. `#files` is the general
surface: home, both memory stores, the shared folders and projects the
caller belongs to, plus the read-only skills and docs trees.
The root is virtual, and that is the design. Anchoring at `~` is wrong:
the explorer reads host-side, while `shared/`, `projects/`, `skills/` and
`docs/` are bind mounts inside the container — a page rooted at the home
would show less than the user has with no way to reach the rest, and on
native Linux would show Docker's empty mountpoint stubs, a door that
appears to work and leads nowhere. So level 0 is a synthetic list from the
new `GET /api/files/roots`, serialized from the caller's `UserFs` plus the
two virtual memory roots. It sends `kind`, never a label: labels are copy
and get translated.
`GET /api/files/dir` now answers `{ path, can_write, entries }`, and a
memory path is classified before `resolve_view_path` (which refuses one)
and listed from `memory_docs`: one level derived from the flat key space
by the pure `memory_docs::immediate_children`, over a single query whose
unslashed prefix also spots an exact note as "not a directory". Memory is
read-only from the page — every writer routes through `resolve_view_path`,
and `shared-memory/*` is `@fs_write require` for the agent, so a button
that walks past that rule is a decision of its own.
The explorer moves out of projects into `shared/file-explorer.js`, taking
`root` + `rootLabel` and reading `can_write` from the listing rather than
from its host: writability changes per branch and comes from the same
`UserFs::can_write_to` the server rejects writes with, so the buttons
offered and the writes accepted cannot disagree. Deep-linking needed it
steerable without a two-way binding, hence `rel` in and
`explorer-navigate` out — the event fires only for a click, never for a
`rel` the host set, so echoing it back is a no-op.
The URL carries the agent path of the open folder in one parameter, the
same vocabulary the assistant uses, so a link is shareable and pasteable
into a conversation; which root it belongs to is derived, not stored.
docs/: a new files.md, plus two pages this made false — shared-folders.md
claimed in three places that a shared folder has no explorer, and
memory.md never said a user can now read their own notes.
This commit is contained in:
+36
-20
@@ -8,6 +8,7 @@ export default {
|
||||
'nav.inbox': 'Inbox',
|
||||
'nav.dashboard': 'Dashboard',
|
||||
'nav.projects': 'Projects',
|
||||
'nav.files': 'Files',
|
||||
'nav.tasks': 'Task Manager',
|
||||
'nav.tasks.running': 'Running Tasks',
|
||||
'nav.tasks.cron': 'Cron Jobs',
|
||||
@@ -266,26 +267,40 @@ export default {
|
||||
'projects.share.access.readwrite':'Read & write',
|
||||
'projects.tabs.files': 'Files',
|
||||
'projects.tabs.sharing': 'Sharing',
|
||||
'projects.files.col.name': 'Name',
|
||||
'projects.files.col.created': 'Created',
|
||||
'projects.files.col.modified': 'Modified',
|
||||
'projects.files.col.size': 'Size',
|
||||
'projects.files.empty': 'This folder is empty.',
|
||||
'projects.files.refresh': 'Refresh',
|
||||
'projects.files.drop': 'Drop files here to upload',
|
||||
'projects.files.btn.new_folder': 'New folder',
|
||||
'projects.files.btn.upload': 'Upload',
|
||||
'projects.files.btn.download': 'Download ZIP',
|
||||
'projects.files.uploading': 'Uploading…',
|
||||
'projects.files.action.download': 'Download',
|
||||
'projects.files.action.rename': 'Rename',
|
||||
'projects.files.action.delete': 'Delete',
|
||||
'projects.files.confirm.delete_file': 'Delete "{name}"?',
|
||||
'projects.files.confirm.delete_dir': 'Delete the folder "{name}" and everything inside it?',
|
||||
'projects.files.modal.mkdir': 'New folder',
|
||||
'projects.files.modal.rename': 'Rename "{name}"',
|
||||
'projects.files.modal.name': 'Name',
|
||||
'projects.files.error.name': 'Enter a valid name (no slashes).',
|
||||
|
||||
// ── File explorer (shared component: projects, Files section) ───────────────
|
||||
'files.col.name': 'Name',
|
||||
'files.col.created': 'Created',
|
||||
'files.col.modified': 'Modified',
|
||||
'files.col.size': 'Size',
|
||||
'files.empty': 'This folder is empty.',
|
||||
'files.refresh': 'Refresh',
|
||||
'files.drop': 'Drop files here to upload',
|
||||
'files.btn.new_folder': 'New folder',
|
||||
'files.btn.upload': 'Upload',
|
||||
'files.btn.download': 'Download ZIP',
|
||||
'files.uploading': 'Uploading…',
|
||||
'files.action.download': 'Download',
|
||||
'files.action.rename': 'Rename',
|
||||
'files.action.delete': 'Delete',
|
||||
'files.confirm.delete_file': 'Delete "{name}"?',
|
||||
'files.confirm.delete_dir': 'Delete the folder "{name}" and everything inside it?',
|
||||
'files.modal.mkdir': 'New folder',
|
||||
'files.modal.rename': 'Rename "{name}"',
|
||||
'files.modal.name': 'Name',
|
||||
'files.error.name': 'Enter a valid name (no slashes).',
|
||||
'files.title': 'Files',
|
||||
'files.note.roots': 'Everywhere you can go: your home, your two memory stores, and the folders shared with you. These are the same paths the assistant uses.',
|
||||
'files.back': 'All files',
|
||||
'files.badge.readonly': 'Read-only',
|
||||
'files.error.unknown_path': '"{path}" is not one of your folders.',
|
||||
'files.root.home': 'Home',
|
||||
'files.root.user_memory': 'Personal memory',
|
||||
'files.root.shared_memory': 'Shared memory',
|
||||
'files.root.shared': 'Shared folder',
|
||||
'files.root.project': 'Project',
|
||||
'files.root.skills': 'Skills',
|
||||
'files.root.docs': 'Documentation',
|
||||
|
||||
// ── Project detail ──────────────────────────────────────────────────────────
|
||||
'project_board.back': 'Projects',
|
||||
@@ -1226,6 +1241,7 @@ export default {
|
||||
'common.cancel': 'Cancel',
|
||||
'common.loading': 'Loading…',
|
||||
'common.saved': 'Saved',
|
||||
'common.create': 'Create',
|
||||
|
||||
// ── Shared Folders (blueprint §6) ────────────────────────────────────────────
|
||||
'nav.shared_folders': 'Shared Folders',
|
||||
|
||||
+36
-20
@@ -8,6 +8,7 @@ export default {
|
||||
'nav.inbox': 'Boîte de réception',
|
||||
'nav.dashboard': 'Tableau de bord',
|
||||
'nav.projects': 'Projets',
|
||||
'nav.files': 'Fichiers',
|
||||
'nav.tasks': 'Gestionnaire de tâches',
|
||||
'nav.tasks.running': 'Tâches en cours',
|
||||
'nav.tasks.cron': 'Tâches Cron',
|
||||
@@ -266,26 +267,40 @@ export default {
|
||||
'projects.share.access.readwrite':'Lecture et écriture',
|
||||
'projects.tabs.files': 'Fichiers',
|
||||
'projects.tabs.sharing': 'Partage',
|
||||
'projects.files.col.name': 'Nom',
|
||||
'projects.files.col.created': 'Création',
|
||||
'projects.files.col.modified': 'Modification',
|
||||
'projects.files.col.size': 'Taille',
|
||||
'projects.files.empty': 'Ce dossier est vide.',
|
||||
'projects.files.refresh': 'Actualiser',
|
||||
'projects.files.drop': 'Déposez les fichiers ici pour les envoyer',
|
||||
'projects.files.btn.new_folder': 'Nouveau dossier',
|
||||
'projects.files.btn.upload': 'Envoyer',
|
||||
'projects.files.btn.download': 'Télécharger (ZIP)',
|
||||
'projects.files.uploading': 'Envoi…',
|
||||
'projects.files.action.download': 'Télécharger',
|
||||
'projects.files.action.rename': 'Renommer',
|
||||
'projects.files.action.delete': 'Supprimer',
|
||||
'projects.files.confirm.delete_file': 'Supprimer « {name} » ?',
|
||||
'projects.files.confirm.delete_dir': 'Supprimer le dossier « {name} » et tout son contenu ?',
|
||||
'projects.files.modal.mkdir': 'Nouveau dossier',
|
||||
'projects.files.modal.rename': 'Renommer « {name} »',
|
||||
'projects.files.modal.name': 'Nom',
|
||||
'projects.files.error.name': 'Saisissez un nom valide (sans barres obliques).',
|
||||
|
||||
// ── File explorer (shared component: projects, Files section) ───────────────
|
||||
'files.col.name': 'Nom',
|
||||
'files.col.created': 'Création',
|
||||
'files.col.modified': 'Modification',
|
||||
'files.col.size': 'Taille',
|
||||
'files.empty': 'Ce dossier est vide.',
|
||||
'files.refresh': 'Actualiser',
|
||||
'files.drop': 'Déposez les fichiers ici pour les envoyer',
|
||||
'files.btn.new_folder': 'Nouveau dossier',
|
||||
'files.btn.upload': 'Envoyer',
|
||||
'files.btn.download': 'Télécharger (ZIP)',
|
||||
'files.uploading': 'Envoi…',
|
||||
'files.action.download': 'Télécharger',
|
||||
'files.action.rename': 'Renommer',
|
||||
'files.action.delete': 'Supprimer',
|
||||
'files.confirm.delete_file': 'Supprimer « {name} » ?',
|
||||
'files.confirm.delete_dir': 'Supprimer le dossier « {name} » et tout son contenu ?',
|
||||
'files.modal.mkdir': 'Nouveau dossier',
|
||||
'files.modal.rename': 'Renommer « {name} »',
|
||||
'files.modal.name': 'Nom',
|
||||
'files.error.name': 'Saisissez un nom valide (sans barres obliques).',
|
||||
'files.title': 'Fichiers',
|
||||
'files.note.roots': 'Tout ce que vous pouvez atteindre : votre dossier personnel, vos deux mémoires et les dossiers partagés avec vous. Ce sont les chemins qu\'utilise l\'assistant.',
|
||||
'files.back': 'Tous les fichiers',
|
||||
'files.badge.readonly': 'Lecture seule',
|
||||
'files.error.unknown_path': '« {path} » ne fait pas partie de vos dossiers.',
|
||||
'files.root.home': 'Dossier personnel',
|
||||
'files.root.user_memory': 'Mémoire personnelle',
|
||||
'files.root.shared_memory': 'Mémoire partagée',
|
||||
'files.root.shared': 'Dossier partagé',
|
||||
'files.root.project': 'Projet',
|
||||
'files.root.skills': 'Compétences',
|
||||
'files.root.docs': 'Documentation',
|
||||
|
||||
// ── Détail du projet ────────────────────────────────────────────────────────
|
||||
'project_board.back': 'Projets',
|
||||
@@ -1213,6 +1228,7 @@ export default {
|
||||
'common.cancel': 'Annuler',
|
||||
'common.loading': 'Chargement…',
|
||||
'common.saved': 'Enregistré',
|
||||
'common.create': 'Créer',
|
||||
|
||||
// ── Shared Folders (blueprint §6) ────────────────────────────────────────────
|
||||
'nav.shared_folders': 'Dossiers partagés',
|
||||
|
||||
+36
-20
@@ -8,6 +8,7 @@ export default {
|
||||
'nav.inbox': 'Richieste',
|
||||
'nav.dashboard': 'Dashboard',
|
||||
'nav.projects': 'Progetti',
|
||||
'nav.files': 'File',
|
||||
'nav.tasks': 'Gestione attività',
|
||||
'nav.tasks.running': 'Attività in corso',
|
||||
'nav.tasks.cron': 'Attività ricorrenti',
|
||||
@@ -290,26 +291,40 @@ export default {
|
||||
'projects.share.access.readwrite':'Lettura e scrittura',
|
||||
'projects.tabs.files': 'File',
|
||||
'projects.tabs.sharing': 'Condivisione',
|
||||
'projects.files.col.name': 'Nome',
|
||||
'projects.files.col.created': 'Creazione',
|
||||
'projects.files.col.modified': 'Ultima modifica',
|
||||
'projects.files.col.size': 'Dimensione',
|
||||
'projects.files.empty': 'Questa cartella è vuota.',
|
||||
'projects.files.refresh': 'Aggiorna',
|
||||
'projects.files.drop': 'Trascina qui i file per caricarli',
|
||||
'projects.files.btn.new_folder': 'Nuova cartella',
|
||||
'projects.files.btn.upload': 'Carica',
|
||||
'projects.files.btn.download': 'Scarica ZIP',
|
||||
'projects.files.uploading': 'Caricamento…',
|
||||
'projects.files.action.download': 'Scarica',
|
||||
'projects.files.action.rename': 'Rinomina',
|
||||
'projects.files.action.delete': 'Elimina',
|
||||
'projects.files.confirm.delete_file': 'Eliminare "{name}"?',
|
||||
'projects.files.confirm.delete_dir': 'Eliminare la cartella "{name}" e tutto il suo contenuto?',
|
||||
'projects.files.modal.mkdir': 'Nuova cartella',
|
||||
'projects.files.modal.rename': 'Rinomina "{name}"',
|
||||
'projects.files.modal.name': 'Nome',
|
||||
'projects.files.error.name': 'Inserisci un nome valido (senza barre).',
|
||||
|
||||
// ── File explorer (shared component: projects, Files section) ───────────────
|
||||
'files.col.name': 'Nome',
|
||||
'files.col.created': 'Creazione',
|
||||
'files.col.modified': 'Ultima modifica',
|
||||
'files.col.size': 'Dimensione',
|
||||
'files.empty': 'Questa cartella è vuota.',
|
||||
'files.refresh': 'Aggiorna',
|
||||
'files.drop': 'Trascina qui i file per caricarli',
|
||||
'files.btn.new_folder': 'Nuova cartella',
|
||||
'files.btn.upload': 'Carica',
|
||||
'files.btn.download': 'Scarica ZIP',
|
||||
'files.uploading': 'Caricamento…',
|
||||
'files.action.download': 'Scarica',
|
||||
'files.action.rename': 'Rinomina',
|
||||
'files.action.delete': 'Elimina',
|
||||
'files.confirm.delete_file': 'Eliminare "{name}"?',
|
||||
'files.confirm.delete_dir': 'Eliminare la cartella "{name}" e tutto il suo contenuto?',
|
||||
'files.modal.mkdir': 'Nuova cartella',
|
||||
'files.modal.rename': 'Rinomina "{name}"',
|
||||
'files.modal.name': 'Nome',
|
||||
'files.error.name': 'Inserisci un nome valido (senza barre).',
|
||||
'files.title': 'File',
|
||||
'files.note.roots': 'Tutto quello che puoi raggiungere: la tua home, le tue due memorie e le cartelle condivise con te. Sono gli stessi percorsi che usa l\'assistente.',
|
||||
'files.back': 'Tutti i file',
|
||||
'files.badge.readonly': 'Sola lettura',
|
||||
'files.error.unknown_path': '"{path}" non è una delle tue cartelle.',
|
||||
'files.root.home': 'Home',
|
||||
'files.root.user_memory': 'Memoria personale',
|
||||
'files.root.shared_memory': 'Memoria condivisa',
|
||||
'files.root.shared': 'Cartella condivisa',
|
||||
'files.root.project': 'Progetto',
|
||||
'files.root.skills': 'Skill',
|
||||
'files.root.docs': 'Documentazione',
|
||||
|
||||
// ── Dettaglio progetto ──────────────────────────────────────────────────────
|
||||
'project_board.back': 'Progetti',
|
||||
@@ -1213,6 +1228,7 @@ export default {
|
||||
'common.cancel': 'Annulla',
|
||||
'common.loading': 'Caricamento…',
|
||||
'common.saved': 'Salvato',
|
||||
'common.create': 'Crea',
|
||||
|
||||
// ── Cartelle condivise (blueprint §6) ────────────────────────────────────────
|
||||
'nav.shared_folders': 'Cartelle condivise',
|
||||
|
||||
Reference in New Issue
Block a user