Skip to content

Commit 6d288e8

Browse files
committed
feat(desktop): the app's switches right in the desktop panel
The owner found the real mouse and keyboard switch well hidden in the bot's details. The desktop panel now shows the options of the app the bot is using under its last action: the real mouse and keyboard (on grants to use) and "While you are away", the same switches and dialogs as in the details.
1 parent 7d76e7a commit 6d288e8

8 files changed

Lines changed: 53 additions & 2 deletions

File tree

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// The options of the app a bot is using, right in its desktop panel (spec
2+
// 24.9, 24.10): the real mouse and keyboard and use while the owner is
3+
// away, the same switches as in the bot's details, where the owner looks.
4+
5+
import { useT } from "../../i18n";
6+
import type { Bot } from "../../lib/protocol.gen";
7+
import { useDesktopGrants } from "./DesktopGrants";
8+
import { RealInput } from "./RealInput";
9+
import { Unattended } from "./Unattended";
10+
11+
export function AppOptions({ bot, app }: { bot: Bot; app: string }) {
12+
const words = useT().desktop.panel;
13+
const grants = useDesktopGrants(bot.id);
14+
const named = app.toLowerCase();
15+
const grant =
16+
grants?.find((grant) => grant.scope === "app" && grant.appName?.toLowerCase() === named) ??
17+
grants?.find((grant) => grant.scope === "desktop");
18+
if (!grant) {
19+
return null;
20+
}
21+
return (
22+
<section
23+
aria-label={words.options(app)}
24+
className="flex flex-wrap items-center gap-x-5 gap-y-1 rounded-lg border border-line bg-sunken px-3 pt-1 pb-2"
25+
>
26+
{grant.level === "act" && <RealInput bot={bot} grant={grant} app={app} />}
27+
<Unattended bot={bot} grant={grant} app={app} />
28+
</section>
29+
);
30+
}

‎app/src/features/desktop/DesktopGrants.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { RealInput } from "./RealInput";
1212
import { Unattended } from "./Unattended";
1313

1414
/** The bot's grants, read when shown and kept current by `bot.desktop`. */
15-
function useDesktopGrants(botId: Bot["id"]): DesktopGrant[] | null {
15+
export function useDesktopGrants(botId: Bot["id"]): DesktopGrant[] | null {
1616
const api = useApi();
1717
const [grants, setGrants] = useState<DesktopGrant[] | null>(null);
1818
useEffect(() => {

‎app/src/features/desktop/DesktopPanel.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { attempt } from "../../ui/toast";
1616
import { BotAvatar } from "../bots/BotAvatar";
1717
import { LiveView, type StageAction } from "../browser/LiveView";
1818
import { ComputerFooter } from "../terminal/ComputerDock";
19+
import { AppOptions } from "./AppOptions";
1920
import { useDesktopView } from "./useDesktopView";
2021

2122
type Words = ReturnType<typeof useT>["desktop"]["panel"];
@@ -144,6 +145,7 @@ export function DesktopPanel({ bot, onClose }: { bot: Bot; onClose(): void }) {
144145
<BotAvatar color={bot.color} size={18} mood={stopped ? "sleeping" : "working"} />
145146
<span className="truncate">{actionText(state?.action ?? null, t)}</span>
146147
</p>
148+
<AppOptions bot={bot} app={window.app} />
147149
</>
148150
)}
149151
<p className="mt-auto text-muted text-xs">{t.shortcut}</p>

‎app/src/features/desktop/panel.test.tsx‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,20 @@ describe("the desktop panel", () => {
108108
});
109109
expect(await within(panel()).findByText("Shopping list - Notepad")).toBeDefined();
110110
});
111+
112+
test("has the app's options right there, where the owner looks", async () => {
113+
const { fake, scout } = await openScout();
114+
act(() => {
115+
fake.desktop.grant(scout.id, "C:Windows\notepad.exe", "Notepad", "act");
116+
fake.desktop.use(scout.id, NOTES);
117+
});
118+
const options = await within(panel()).findByRole("region", { name: "Options for Notepad" });
119+
const real = within(options).getByRole("switch", { name: "Real mouse and keyboard: Notepad" });
120+
fireEvent.click(real);
121+
fireEvent.click(screen.getByRole("button", { name: "Turn on" }));
122+
await waitFor(() => expect(fake.desktop.grants[0]?.realInput).toBe(true));
123+
expect(
124+
within(options).getByRole("switch", { name: "While you are away: Notepad" }),
125+
).toBeDefined();
126+
});
111127
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const desktop = {
2121
panel: {
2222
label: (bot: string) => `${bot}'s desktop`,
2323
screen: (bot: string, app: string) => `${app}, as ${bot} sees it`,
24+
options: (app: string) => `Options for ${app}`,
2425
heading: "Desktop",
2526
live: "Live",
2627
stoppedBadge: "Stopped",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const desktop: Messages["desktop"] = {
2020
panel: {
2121
label: (bot) => `Escritorio de ${bot}`,
2222
screen: (bot, app) => `${app}, como lo ve ${bot}`,
23+
options: (app) => `Opciones en ${app}`,
2324
heading: "Escritorio",
2425
live: "En vivo",
2526
stoppedBadge: "Detenido",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const desktop: Messages["desktop"] = {
2020
panel: {
2121
label: (bot) => `Desktop de ${bot}`,
2222
screen: (bot, app) => `${app}, como ${bot} vê`,
23+
options: (app) => `Opções no ${app}`,
2324
heading: "Desktop",
2425
live: "Ao vivo",
2526
stoppedBadge: "Parado",

‎docs/spec.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ No servidor `botloft` (11), como as do navegador.
17851785
- o que nunca é liberado (24.3).
17861786
O botão de confirmar só acende depois de o dono marcar "Entendi os riscos"; a caixa começa desmarcada toda vez. A data em que ele aceitou fica gravada com a permissão (`accepted_risks_at`) e fica lá se ele desliga.
17871787
- **Sem você na frente**: em cada permissão, de ver ou de usar, um interruptor, desligado de início. Ligar abre a tela de riscos acima; desligar é na hora. O app chama `desktop.setOptions {grantId, unattended, acceptedRisks}`; o daemon recusa ligar sem `acceptedRisks: true`.
1788-
- **Mouse e teclado de verdade**: em cada permissão de usar um app, um interruptor, desligado de início (só nas de usar). Ligar abre antes um diálogo, "Deixar <bot> usar seu mouse e teclado de verdade no <app>?", que diz o que muda: quando a acessibilidade não alcança, o bot move o cursor e digita como se fosse o dono; enquanto ele age, o mouse e o teclado são dele, com o aviso na tela; mexer no mouse ou numa tecla o para na hora, e Ctrl+Alt+Esc para todos; nunca campos de senha nem teclas do Windows. Desligar é na hora. O app chama `desktop.setOptions`.
1788+
- **Mouse e teclado de verdade**: em cada permissão de usar um app, um interruptor, desligado de início (só nas de usar). Ligar abre antes um diálogo, "Deixar <bot> usar seu mouse e teclado de verdade no <app>?", que diz o que muda: quando a acessibilidade não alcança, o bot move o cursor e digita como se fosse o dono; enquanto ele age, o mouse e o teclado são dele, com o aviso na tela; mexer no mouse ou numa tecla o para na hora, e Ctrl+Alt+Esc para todos; nunca campos de senha nem teclas do Windows. Desligar é na hora. O app chama `desktop.setOptions`. Os mesmos interruptores (mouse e teclado de verdade, nas permissões de usar, e "Sem você na frente") aparecem também no painel "Desktop" (24.9), embaixo da linha do que o bot fez, para o app da janela que ele usa (a permissão desse app pelo nome, senão a do desktop inteiro): é onde o dono está olhando quando o bot pede.
17891789

17901790
### 24.11 Dados
17911791

0 commit comments

Comments
 (0)