Repository navigation
feat: navegar entre pastas e criar pastas (LUI-179) - #13
Conversation
Acceptance file docs/tickets/LUI-177/LUI-179/aceite.md (CA1-CA12). The OpenAPI snapshot gains GET /items/{id} (ItemDetailDto with the trail), the optional type=folder query on GET /items/{id}/children and POST /folders (CreateFolderDto, 201 ItemDto); no table changes.
Decision: the depth counts the folders of the path without the root, so a folder directly in "Meus arquivos" is level 1 and the 50th level is the last one allowed. This makes the limit testable with a chain of exactly 50 folders.
Decision: the HTTP tests seed trees and pending items straight in the database, because a 50-level chain cannot be built through routes without repeating what CA3 already proves; only CA3 and the browser tests create folders through the real route.
Decision: creating a folder takes the per-user advisory lock (pg_advisory_xact_lock on the owner id) before checking depth and writing, so the lock the move ticket (LUI-181) will reuse is registered in the LLD now.
Decision: the LLD records the type=folder filter, the rule that the list accepts any active folder of the User, the /pasta/[id] route and the advisory lock (CA12); the subtree-height rule of the move is registered too, as the move ticket depends on it.
Files and interfaces the implementation will touch:
- apps/api/src/modules/items: folder creation, the trail query (recursive, SQL via $queryRaw, up to 50 levels), the type filter and any-active-folder listing in children; FoldersController (POST /folders) and ItemsController.get.
- apps/api/openapi.json: regenerated from the controllers and DTOs.
- apps/api/test: items-folders.e2e-spec.ts.
- apps/web: route /pasta/[id], breadcrumb with the collapsed middle on mobile, "Nova pasta" dialog and toast (Server Action), folder rows opening on double click, "Enviar" to the open folder, not-found page.
- apps/web/e2e: folders.spec.ts and the folder helpers in support/items.ts.
- docs/lld.md: sections 3.3, 4.1 and the web route map.
Existing tests whose behavior changes: items-root-children.e2e-spec.ts, "responde not_found para uma pasta que não é a raiz, enquanto só ela é aceita", which must now use an id that is not an active folder of the User.
Data changes: none.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
GET /items/{id} returns the item and its trail, the children list accepts
any active folder and the type=folder filter, and POST /folders creates a
folder under a per-user advisory lock and the 50-level limit. The web gets
the /pasta/[id] route, the breadcrumb, "Nova pasta" with a toast, and the
upload button sends files to the open folder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Revisão de conformidade: LUI-179
Relatório do revisor, na íntegraAchados: (1) média, apps/api/test/items-folders.e2e-spec.ts, teste do LUI-179-CA6: o cenário exige Conferido sem achado: CA1 a CA12 no aceite com os mesmos IDs, sem critério novo; contrato e schema iguais ao plano; regra de profundidade e lock conforme a decisão do plano; LLD atualizado; teste de items-root-children alterado como previsto; asserções de CA1 a CA5 e CA7 a CA11 cobrem os cenários. Não conferido: execução de código e testes; |
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
4 open findings
What changed in this PR
Adds folder navigation and folder creation across the API and web Drive experience.
Changes:
- Adds folder detail, breadcrumb, pagination, creation, and depth-limit APIs.
- Adds
/pasta/[id], folder navigation, creation dialog, toast feedback, and folder-aware uploads. - Adds API and Playwright acceptance tests plus updated contracts and documentation.
| File | Description |
|---|---|
| docs/tickets/LUI-177/LUI-179/aceite.md | Updated as part of this pull request. |
| docs/lld.md | Updated as part of this pull request. |
| apps/web/lib/drive.ts | Updated as part of this pull request. |
| apps/web/lib/dal/items.ts | Updated as part of this pull request. |
| apps/web/lib/api/types.ts | Updated as part of this pull request. |
| apps/web/lib/api/items.ts | Updated as part of this pull request. |
| apps/web/lib/api/error-messages.ts | Updated as part of this pull request. |
| apps/web/e2e/support/items.ts | Updated as part of this pull request. |
| apps/web/e2e/folders.spec.ts | Updated as part of this pull request. |
| apps/web/components/uploads/upload-button.tsx | Updated as part of this pull request. |
| apps/web/components/ui/toast.tsx | Updated as part of this pull request. |
| apps/web/components/ui/menu.tsx | Updated as part of this pull request. |
| apps/web/components/ui/dialog.tsx | Updated as part of this pull request. |
| apps/web/components/ui/button-secondary.tsx | Updated as part of this pull request. |
| apps/web/components/navigation/breadcrumb.tsx | Updated as part of this pull request. |
| apps/web/components/files/openable-row.tsx | Updated as part of this pull request. |
| apps/web/components/files/new-folder-button.tsx | Updated as part of this pull request. |
| apps/web/components/files/folder-view.tsx | Updated as part of this pull request. |
| apps/web/components/files/folder-contents.tsx | Updated as part of this pull request. |
| apps/web/components/files/file-row.tsx | Updated as part of this pull request. |
| apps/web/app/vitrine/drive/page.tsx | Updated as part of this pull request. |
| apps/web/app/(drive)/pasta/[id]/page.tsx | Updated as part of this pull request. |
| apps/web/app/(drive)/pasta/[id]/not-found.tsx | Updated as part of this pull request. |
| apps/web/app/(drive)/page.tsx | Updated as part of this pull request. |
| apps/web/app/(drive)/layout.tsx | Updated as part of this pull request. |
| apps/web/app/(drive)/actions.ts | Updated as part of this pull request. |
| apps/api/test/support/items.ts | Updated as part of this pull request. |
| apps/api/test/items-root-children.e2e-spec.ts | Updated as part of this pull request. |
| apps/api/test/items-folders.e2e-spec.ts | Updated as part of this pull request. |
| apps/api/src/modules/uploads/dto/create-upload.dto.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/items.service.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/items.repository.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/items.module.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/items.controller.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/folders.controller.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/errors/max-depth-exceeded.error.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/entities/item.entity.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/dto/list-children-query.dto.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/dto/item-detail.dto.ts | Updated as part of this pull request. |
| apps/api/src/modules/items/dto/create-folder.dto.ts | Updated as part of this pull request. |
| apps/api/src/common/validation/file-name.decorator.ts | Updated as part of this pull request. |
| apps/api/openapi.json | Updated as part of this pull request. |
🧠 Review effort: Lite
| SELECT i.id, i.name, i.parent_id, i.updated_at, p.level + 1 | ||
| FROM items i | ||
| JOIN path p ON i.id = p.parent_id | ||
| WHERE i.owner_id = ${ownerId}::uuid AND p.level < ${PATH_QUERY_MAX_LEVEL} |
There was a problem hiding this comment.
A lixeira e o status trashed ainda não existem: a spec LUI-177 diz que nenhum estado da lixeira é antecipado neste ticket. A regra do LLD (descendentes de pasta na lixeira ficam inacessíveis) e a validação da cadeia ativa até a raiz entram na funcionalidade 04, que é quem passa a marcar pastas como trashed. Hoje a cadeia só tem itens ativos, então não há caminho que o Copilot descreve.
| const after = | ||
| page.cursor === undefined ? undefined : decodeItemCursor(page.cursor); | ||
| const rootId = await this.items.ensureRoot(ownerId, uuidv7); | ||
| const parentId = await this.resolveDestinationFolder(ownerId, folderId); | ||
| // Um Item a mais que a página diz se há outra depois dela. | ||
| const rows = await this.items.listActiveChildren( | ||
| rootId, | ||
| parentId, |
There was a problem hiding this comment.
Mesma resposta do comentário sobre a trilha: sem lixeira não há ancestral trashed para esta checagem. A validação da cadeia ativa fica para a funcionalidade 04.
| expect((folderList.body as ListBody).items.map((i) => i.id)).toEqual([ | ||
| (inFolder.body as { id: string }).id, | ||
| ]); |
There was a problem hiding this comment.
Corrigido no commit a9921a3: o teste agora confere type, name, sizeBytes e mimeType da resposta da pasta aninhada.
| expect(refusedUpload.status).toBe(413); | ||
| expect(refusedUpload.body.code).toBe('quota_exceeded'); | ||
| expect(response.status).toBe(201); | ||
| expect(await uploads.usedBytes(userId)).toBe(usedBefore); |
There was a problem hiding this comment.
O GET /me hoje só devolve id e email, então não há readOnly para afirmar. O teste prova o somente leitura recusando um POST /uploads com quota_exceeded antes de criar a pasta, e confere que o usedBytes fica igual depois.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>


O que muda
Navegar entre pastas e criar pastas (LUI-179, da spec LUI-177):
GET /items/{id}com a trilha,type=foldere qualquer pasta ativa emGET /items/{id}/children,POST /folderscom lock consultivo por Usuário e limite de 50 níveis. Na web, a rota/pasta/[id], o breadcrumb (que colapsa no celular), "Nova pasta" com toast e o "Enviar" levando à pasta aberta. O LLD registra o filtro, a rota, a regra de profundidade e o lock.Aceite
docs/tickets/LUI-177/LUI-179/aceite.md
Pendente
Validação visual contra o Figma do LUI-178 (390 e 1280 px, temas claro e escuro): o MCP do Figma estava sem autenticação.
🤖 Generated with Claude Code