Skip to content

Commit 7ec583b

Browse files
authored
Merge pull request #157 from httpsphl/feat/crew-folder-icon
feat(crews): the work folder as an icon in the crew header
2 parents 1866561 + e7c6550 commit 7ec583b

6 files changed

Lines changed: 19 additions & 8 deletions

File tree

‎app/src/features/crews/CrewView.tsx‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,15 +76,16 @@ export function CrewView({ crew }: { crew: Crew }) {
7676
{words.bots(bots.length)}
7777
{crew.paused && ` · ${words.pausedNote}`}
7878
</span>
79-
<span aria-hidden>·</span>
79+
{/* Only the icon: the path is in its tooltip, and a click opens it. */}
8080
<button
8181
type="button"
8282
onClick={openFolder}
83-
title={words.openFolder}
84-
className="flex min-w-0 items-center gap-1 rounded-lg px-1 hover:bg-sunken hover:text-ink"
83+
title={`${words.folder(crew.workFolder)}
84+
${words.openFolderHint}`}
85+
aria-label={`${words.openFolder}: ${crew.workFolder}`}
86+
className="grid size-6 shrink-0 place-items-center rounded-lg hover:bg-sunken hover:text-ink"
8587
>
86-
<Folder aria-hidden size={13} className="shrink-0" />
87-
<span className="truncate">{words.folder(crew.workFolder)}</span>
88+
<Folder aria-hidden size={14} />
8889
</button>
8990
</p>
9091
</div>

‎app/src/features/crews/folders.test.tsx‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ describe("work folders", () => {
6363
renderApp(fake, host);
6464
await crewOpened("Ops");
6565

66-
fireEvent.click(screen.getByRole("button", { name: /^Works in / }));
66+
// Only the icon: the path is in the tooltip.
67+
const folder = screen.getByRole("button", { name: `Open work folder: ${ops.workFolder}` });
68+
expect(folder.textContent).toBe("");
69+
expect(folder.title).toBe(`Works in ${ops.workFolder}
70+
Click to open it`);
71+
fireEvent.click(folder);
6772
expect(host.opened).toEqual([ops.workFolder]);
6873

6974
host.nextFolder = PROJECT;
@@ -74,7 +79,9 @@ describe("work folders", () => {
7479
expect(within(confirm).getByText(/restarts when it finishes/)).toBeDefined();
7580
fireEvent.click(within(confirm).getByRole("button", { name: "Move" }));
7681

77-
expect(await screen.findByText(`Works in ${PROJECT}`)).toBeDefined();
82+
expect(
83+
await screen.findByRole("button", { name: `Open work folder: ${PROJECT}` }),
84+
).toBeDefined();
7885
const move = fake.calls.find((call) => call.method === "crews.setWorkFolder");
7986
expect(move?.params).toEqual({ crewId: ops.id, workFolder: PROJECT });
8087
});

‎app/src/i18n/en/crews.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export const crews = {
6262
moreActions: "More crew actions",
6363
folder: (path: string) => `Works in ${path}`,
6464
openFolder: "Open work folder",
65+
openFolderHint: "Click to open it",
6566
changeFolder: "Change work folder…",
6667
moveTitle: (crew: string) => `Move ${crew} to another folder?`,
6768
moveBody: (path: string) =>

‎app/src/i18n/es/crews.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const crews: Messages["crews"] = {
5555
moreActions: "Más acciones del equipo",
5656
folder: (path: string) => `Trabaja en ${path}`,
5757
openFolder: "Abrir carpeta de trabajo",
58+
openFolderHint: "Haz clic para abrirla",
5859
changeFolder: "Cambiar carpeta de trabajo…",
5960
moveTitle: (crew: string) => `¿Mover ${crew} a otra carpeta?`,
6061
moveBody: (path: string) =>

‎app/src/i18n/pt-BR/crews.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const crews: Messages["crews"] = {
5656
moreActions: "Mais ações da equipe",
5757
folder: (path: string) => `Trabalha em ${path}`,
5858
openFolder: "Abrir pasta de trabalho",
59+
openFolderHint: "Clique para abrir",
5960
changeFolder: "Mudar pasta de trabalho…",
6061
moveTitle: (crew: string) => `Mudar ${crew} para outra pasta?`,
6162
moveBody: (path: string) =>

‎docs/spec.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -833,7 +833,7 @@ Layout, como um app de mensagens:
833833
- **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.
834834
- **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.
835835
- 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).
836-
- **Á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.
836+
- **Á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.
837837
- **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.
838838
- **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".
839839
- **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".

0 commit comments

Comments
 (0)