Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions app/src/features/crews/CrewView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,16 @@ export function CrewView({ crew }: { crew: Crew }) {
{words.bots(bots.length)}
{crew.paused && ` · ${words.pausedNote}`}
</span>
<span aria-hidden>·</span>
{/* Only the icon: the path is in its tooltip, and a click opens it. */}
<button
type="button"
onClick={openFolder}
title={words.openFolder}
className="flex min-w-0 items-center gap-1 rounded-lg px-1 hover:bg-sunken hover:text-ink"
title={`${words.folder(crew.workFolder)}
${words.openFolderHint}`}
aria-label={`${words.openFolder}: ${crew.workFolder}`}
className="grid size-6 shrink-0 place-items-center rounded-lg hover:bg-sunken hover:text-ink"
>
<Folder aria-hidden size={13} className="shrink-0" />
<span className="truncate">{words.folder(crew.workFolder)}</span>
<Folder aria-hidden size={14} />
</button>
</p>
</div>
Expand Down
11 changes: 9 additions & 2 deletions app/src/features/crews/folders.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 });
});
Expand Down
1 change: 1 addition & 0 deletions app/src/i18n/en/crews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
1 change: 1 addition & 0 deletions app/src/i18n/es/crews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
1 change: 1 addition & 0 deletions app/src/i18n/pt-BR/crews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand Down
2 changes: 1 addition & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pasta>", que abre a pasta no Explorer; o menu da crew tem "Abrir pasta de trabalho" e "Mudar pasta de trabalho…", que abre o seletor de pastas do Windows e confirma antes de mudar, avisando que cada bot reinicia quando terminar o que está fazendo.
- **Área principal com uma crew:** a timeline (messages entre os bots e do dono) e as tasks. Embaixo do nome, ao lado do número de bots, só o ícone da pasta de trabalho: o tooltip diz "Trabalha em <pasta>" e "Clique para abrir", e o clique abre a pasta no gerenciador de arquivos do sistema (Explorer, Finder ou o do Linux, por `open_path`, 15.2); o menu da crew tem "Abrir pasta de trabalho" e "Mudar pasta de trabalho…", que abre o seletor de pastas do Windows e confirma antes de mudar, avisando que cada bot reinicia quando terminar o que está fazendo.
- **Nova crew:** nome; "Para que é esta equipe?", o objetivo que vira as instruções do chefe, com a explicação de que a equipe começa com um Chefe que planeja o trabalho e sugere os bots de que precisa; "Pasta de trabalho", com "Escolher pasta…" (o seletor do Windows; sem escolha, "Uma pasta nova dentro do Botloft", a `shared\`); e "Modelo do Chefe". Criada, o app abre o chat do chefe.
- **Chefe:** uma coroa ao lado do nome na barra lateral e nos cartões da crew, e "Chefe" no cabeçalho do bot, com a dica "Lidera <crew>: planeja o trabalho e sugere bots novos". O menu do bot tem "Tornar chefe da equipe" ou "Deixar de ser chefe".
- **Excluir** (7.6): "Excluir bot" é a última ação do menu do bot (no cabeçalho e no clique direito) e "Excluir equipe" a última do menu da crew, as duas em vermelho, depois de Arquivar. A confirmação leva o nome no título ("Excluir Scout?") e diz, em palavras simples, o que sai e o que fica: o bot para agora e sai do Botloft de vez, com a conversa, as rotinas e as tarefas de que fazia parte, e não dá para desfazer; se ele é o chefe, que a equipe fica sem chefe; e, num quadro à parte, que a pasta dele continua no computador, com o caminho para copiar. A da crew diz quantos bots param e saem com ela, e mostra a pasta de trabalho, lembrando que a pasta de cada bot também fica. No mesmo quadro, uma caixa desmarcada: "Mandar esta pasta para a Lixeira" (na crew, "Mandar as pastas da equipe para a Lixeira"). Marcada, o texto do quadro passa a dizer que a pasta vai para a Lixeira, de onde ainda dá para recuperar, e a crew mostra a pasta dela no Botloft em vez da `shared\`; se o dono escolheu a pasta de trabalho, o quadro diz que a pasta de cada bot vai e a escolhida fica onde está. O app manda `recycleFolder` só com a caixa marcada. Se a pasta não pôde ir (`folder.recycled` com `error`), um aviso diz que ela não foi para a Lixeira e continua lá, com o caminho e o motivo. Confirmado, o app tira o bot ou a crew da tela sem esperar o aviso do daemon; se falhar, um aviso diz que não foi possível e nada muda. Excluído o bot aberto, fica a página da crew; excluída a crew aberta, nenhuma fica escolhida (sem crews, a tela de boas-vindas). Uma message de um bot que saiu, arquivado ou excluído, aparece na conversa e na timeline como de "um bot que não está mais na equipe".
Expand Down
Loading