From e7c6550b87acd6752c6012ce820ff73dbf39a3d5 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Sun, 4 Oct 2026 14:46:15 -0300 Subject: [PATCH] feat(crews): the work folder as an icon in the crew header The crew header shows only the folder icon instead of the whole path: its tooltip says where the crew works and that a click opens it, in the system's file manager. --- app/src/features/crews/CrewView.tsx | 11 ++++++----- app/src/features/crews/folders.test.tsx | 11 +++++++++-- app/src/i18n/en/crews.ts | 1 + app/src/i18n/es/crews.ts | 1 + app/src/i18n/pt-BR/crews.ts | 1 + docs/spec.md | 2 +- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/src/features/crews/CrewView.tsx b/app/src/features/crews/CrewView.tsx index 62b166c3..ff8e3784 100644 --- a/app/src/features/crews/CrewView.tsx +++ b/app/src/features/crews/CrewView.tsx @@ -76,15 +76,16 @@ export function CrewView({ crew }: { crew: Crew }) { {words.bots(bots.length)} {crew.paused && ` · ${words.pausedNote}`} - · + {/* Only the icon: the path is in its tooltip, and a click opens it. */}
diff --git a/app/src/features/crews/folders.test.tsx b/app/src/features/crews/folders.test.tsx index 87b15cd2..b70c72e6 100644 --- a/app/src/features/crews/folders.test.tsx +++ b/app/src/features/crews/folders.test.tsx @@ -63,7 +63,12 @@ describe("work folders", () => { renderApp(fake, host); await crewOpened("Ops"); - fireEvent.click(screen.getByRole("button", { name: /^Works in / })); + // Only the icon: the path is in the tooltip. + const folder = screen.getByRole("button", { name: `Open work folder: ${ops.workFolder}` }); + expect(folder.textContent).toBe(""); + expect(folder.title).toBe(`Works in ${ops.workFolder} +Click to open it`); + fireEvent.click(folder); expect(host.opened).toEqual([ops.workFolder]); host.nextFolder = PROJECT; @@ -74,7 +79,9 @@ describe("work folders", () => { expect(within(confirm).getByText(/restarts when it finishes/)).toBeDefined(); fireEvent.click(within(confirm).getByRole("button", { name: "Move" })); - expect(await screen.findByText(`Works in ${PROJECT}`)).toBeDefined(); + expect( + await screen.findByRole("button", { name: `Open work folder: ${PROJECT}` }), + ).toBeDefined(); const move = fake.calls.find((call) => call.method === "crews.setWorkFolder"); expect(move?.params).toEqual({ crewId: ops.id, workFolder: PROJECT }); }); diff --git a/app/src/i18n/en/crews.ts b/app/src/i18n/en/crews.ts index 9dc3efdc..ec2dab7f 100644 --- a/app/src/i18n/en/crews.ts +++ b/app/src/i18n/en/crews.ts @@ -62,6 +62,7 @@ export const crews = { moreActions: "More crew actions", folder: (path: string) => `Works in ${path}`, openFolder: "Open work folder", + openFolderHint: "Click to open it", changeFolder: "Change work folder…", moveTitle: (crew: string) => `Move ${crew} to another folder?`, moveBody: (path: string) => diff --git a/app/src/i18n/es/crews.ts b/app/src/i18n/es/crews.ts index 19f6eebc..e213087e 100644 --- a/app/src/i18n/es/crews.ts +++ b/app/src/i18n/es/crews.ts @@ -55,6 +55,7 @@ export const crews: Messages["crews"] = { moreActions: "Más acciones del equipo", folder: (path: string) => `Trabaja en ${path}`, openFolder: "Abrir carpeta de trabajo", + openFolderHint: "Haz clic para abrirla", changeFolder: "Cambiar carpeta de trabajo…", moveTitle: (crew: string) => `¿Mover ${crew} a otra carpeta?`, moveBody: (path: string) => diff --git a/app/src/i18n/pt-BR/crews.ts b/app/src/i18n/pt-BR/crews.ts index de69119f..e6c0e60c 100644 --- a/app/src/i18n/pt-BR/crews.ts +++ b/app/src/i18n/pt-BR/crews.ts @@ -56,6 +56,7 @@ export const crews: Messages["crews"] = { moreActions: "Mais ações da equipe", folder: (path: string) => `Trabalha em ${path}`, openFolder: "Abrir pasta de trabalho", + openFolderHint: "Clique para abrir", changeFolder: "Mudar pasta de trabalho…", moveTitle: (crew: string) => `Mudar ${crew} para outra pasta?`, moveBody: (path: string) => diff --git a/docs/spec.md b/docs/spec.md index 235c8193..0492f65a 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -831,7 +831,7 @@ Layout, como um app de mensagens: - **Esforço do bot** (7.4), no compositor, ao lado do modelo: um botão com o nível em uso ("Médio") abre para cima "Esforço", um controle deslizante de cinco pontos, de "Mais rápido" a "Mais inteligente" (Baixo, Médio, Alto, Extra alto, Máximo), como o dos apps do Claude. O ponto **recomendado** vem marcado: é o nível que o modelo do bot usa sozinho (`effortDefault`). Embaixo, o nome do nível e uma linha que fala do bot pelo nome ("Scout equilibra rapidez e raciocínio, bom para quase tudo"); o pé lembra que mais esforço gasta o limite do plano mais rápido e, fora do recomendado, traz "Usar o recomendado". As palavras seguem o controle na hora, mas o bot só é avisado quando o controle fica 400 ms num ponto ou o menu fecha, para não reiniciar a cada passo. Parar no ponto recomendado grava `default`: o bot segue o nível do modelo, também depois de trocar de modelo. Enquanto o Claude Code não disse o nível do modelo, nada vem marcado e o botão diz só "Esforço". Num modelo sem níveis (Haiku) o botão fica apagado e o menu explica, sem o controle. Com o bot ocupado, a linha acima do compositor avisa que ele muda quando terminar. - **Espaço da conversa** (8.6), no compositor, antes do esforço: um anel que enche com a parte da janela em uso e passa à cor de aviso a partir de 80% do caminho até a compactação automática. Um clique abre "Espaço da conversa": o uso ("556k / 1M (56%)"), uma barra com a marca de onde a conversa se compacta sozinha, quanto falta para isso ("Faltam 411k para ela se compactar sozinha, em 967k"), uma linha dizendo que conversa mais cheia gasta mais do plano a cada mensagem, e **Compactar agora** (`bots.compact`), com a linha do que isso faz. Durante a compactação o botão diz "Compactando…" e o anel pulsa; com o bot ocupado, a linha acima do compositor avisa que ele compacta quando terminar; com o bot parado, o botão fica desligado e diz por quê. Sem `Bot.context`, o anel não aparece. No chat, a compactação vira um aviso no idioma do dono: feita a pedido, feita sozinha porque a conversa encheu, ou o motivo de não ter dado. - Num chat estreito, a linha de baixo do compositor nunca se divide em duas: o modo e o esforço mostram só o ícone, as setinhas dos menus somem e o nome do modelo é cortado por último, em vez de tudo sair da caixa. No cabeçalho, o @ do bot é cortado antes do nome, e Pausar e Reiniciar mostram só o ícone (o nome fica na dica e para o leitor de tela). -- **Área principal com uma crew:** a timeline (messages entre os bots e do dono) e as tasks. Embaixo do nome, "Trabalha em