Skip to content

feat: navegar entre pastas e criar pastas (LUI-179) - #13

Merged
argentinaluiz merged 3 commits into
mainfrom
feature/lui-179-navegar-e-criar-pastas
Oct 15, 2026
Merged

argentinaluiz merged 3 commits into
mainfrom
feature/lui-179-navegar-e-criar-pastas

Conversation

@argentinaluiz

Copy link
Copy Markdown
Contributor

O que muda

Navegar entre pastas e criar pastas (LUI-179, da spec LUI-177): GET /items/{id} com a trilha, type=folder e qualquer pasta ativa em GET /items/{id}/children, POST /folders com 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

argentinaluiz and others added 2 commits October 15, 2026 18:21
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>
Copilot AI lite review requested due to automatic review settings October 15, 2026 21:48
@argentinaluiz

Copy link
Copy Markdown
Contributor Author

Revisão de conformidade: LUI-179

# Severidade Achado Situação Motivo
1 média O teste do CA6 não chama GET /me nem prova que a conta estava somente leitura Corrigido /me hoje só devolve id e email, então o readOnly não é afirmável por ele; o teste passa a provar o Dado recusando um upload antes de criar a pasta, e mantém o usedBytes igual
2 baixa A linha da rota / no LLD ficou desatualizada Corrigido A linha ganhou GET /items/root, POST /folders, o breadcrumb, "Nova pasta" e abrir pasta
Relatório do revisor, na íntegra

Achados: (1) média, apps/api/test/items-folders.e2e-spec.ts, teste do LUI-179-CA6: o cenário exige GET /me com o mesmo usedBytes e readOnly verdadeiro; o teste só compara uploads.usedBytes(userId) antes e depois e nunca chama /me, então não prova a conta somente leitura. Direção: chamar auth.me antes e depois e afirmar readOnly: true e usedBytes igual. (2) baixa, docs/lld.md, linha da rota /: agora usa o mesmo FolderView, chama GET /items/root, mostra "Nova pasta" (POST /folders) e breadcrumb, e abre pastas; a linha segue listando só GET /me e GET /items/root/children. Direção: atualizar a linha.

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; resolveDestinationFolder, IsFileName movido e ProblemDetailsFilter completos; aparência contra o Figma; descrição de Dialog, Toast, ButtonSecondary e das opções novas do Menu no design-system.md; código HTTP do notFound() dentro do Suspense.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment on lines +152 to +155
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}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 51 to +56
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,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +264 to +266
expect((folderList.body as ListBody).items.map((i) => i.id)).toEqual([
(inFolder.body as { id: string }).id,
]);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@argentinaluiz
argentinaluiz merged commit 2594d33 into main Oct 15, 2026
1 of 2 checks passed
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.

2 participants