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
1 change: 1 addition & 0 deletions apps/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@ace/ui": "workspace:*",
"@xterm/addon-fit": "catalog:ui",
"@xterm/xterm": "catalog:ui",
"agentation": "catalog:ui",
"electrobun": "catalog:desktop",
"react": "catalog:ui",
"react-dom": "catalog:ui"
Expand Down
27 changes: 27 additions & 0 deletions apps/app/public/agentation-license.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
PolyForm Shield License 1.0.0

Copyright (c) 2026 Benji Taylor

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to use,
copy, modify, and distribute the Software, subject to the following conditions:

1. You may not use the Software to provide a product or service that competes
with the Software or any product or service offered by the Licensor that
includes the Software.

2. You may not remove or obscure any licensing, copyright, or other notices
included in the Software.

3. If you distribute the Software or any derivative works, you must include a
copy of this license.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

For more information, see https://polyformproject.org/licenses/shield/1.0.0
86 changes: 86 additions & 0 deletions apps/app/src/annotations.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Agentation } from "agentation";
import { useEffect, useState } from "react";

const LAUNCHER = 'button[aria-label="Start feedback mode"], button[aria-label="Exit"]';

// Ace owns visibility; Agentation's session hide setting would disable the native shortcut.
sessionStorage.removeItem("agentation-session-toolbar-hidden");

export default function Annotations() {
const [container, setContainer] = useState<HTMLElement | null>(null);
useEffect(() => {
// Keep feedback inside the active dialog's focus and accessibility boundary.
function followDialog() {
const dialogs = document.querySelectorAll<HTMLElement>(
'[data-slot="dialog-content"][data-open]',
);
setContainer(dialogs.item(dialogs.length - 1));
}
const observer = new MutationObserver(followDialog);
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["data-open"],
});
followDialog();
return () => observer.disconnect();
}, []);

useEffect(() => {
let shadow: ShadowRoot | undefined;
let pending = location.hash === "#annotations-toggle";
if (pending) history.replaceState(null, "", location.pathname + location.search);

function toggle() {
if (!shadow) {
pending = !pending;
return;
}
shadow.querySelector<HTMLButtonElement>(LAUNCHER)!.click();
}

// Agentation 3.1.2 has no controlled-open API. Keep it mounted so toggling retains feedback.
const style = document.createElement("style");
style.textContent = `
[data-agentation-toolbar]:has(button[aria-label="Start feedback mode"][aria-expanded="false"]) {
display: none;
}
[data-agentation-settings-panel] div:has(> div > input[aria-label="Hide Until Restart"]) {
display: none;
}
`;
function attach() {
const root = document.querySelector(".ace-annotations")?.shadowRoot;
if (!root) return;
if (!root.querySelector(LAUNCHER)) {
observer.observe(root, { childList: true, subtree: true });
return;
}
shadow = root;
root.append(style);
root.host.setAttribute("data-ace-ready", "");
observer.disconnect();
if (pending) toggle();
}
const observer = new MutationObserver(attach);
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener("ace:annotations-toggle", toggle);
attach();
return () => {
observer.disconnect();
window.removeEventListener("ace:annotations-toggle", toggle);
shadow?.host.removeAttribute("data-ace-ready");
style.remove();
};
}, []);

return (
<Agentation
appName="Ace"
className="ace-annotations"
enableKeyboardShortcuts={false}
portalContainer={container}
/>
);
}
9 changes: 8 additions & 1 deletion apps/app/src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { lazy, Suspense, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";

import {
Button,
Expand Down Expand Up @@ -45,6 +45,8 @@ import { Settings } from "./settings";
import { SetupProject } from "./setup-project";
import { UpdateNotice } from "./updates";

const Annotations = lazy(() => import("./annotations"));

// Older versions saved an inline GitHub item per project; Ace now opens items on GitHub.
localStorage.removeItem(`ace:github-targets:${host.url}`);

Expand Down Expand Up @@ -692,6 +694,11 @@ export function App() {
/>
)}
{desktop && <UpdateNotice onOpen={() => window.dispatchEvent(new Event("ace:updates"))} />}
{desktop && (
<Suspense fallback={null}>
<Annotations />
</Suspense>
)}
<Toaster />
</TooltipProvider>
</ThemeProvider>
Expand Down
4 changes: 4 additions & 0 deletions apps/app/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,7 @@ body {
:root[data-layout-resize="none"] * {
cursor: not-allowed !important;
}

.ace-annotations:not([data-ace-ready]) {
visibility: hidden;
}
7 changes: 7 additions & 0 deletions apps/desktop/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ ApplicationMenu.setApplicationMenu([
action: "channels-toggle",
accelerator: "CmdOrCtrl+Shift+b",
},
{ type: "divider" },
{
label: "Toggle Annotations",
action: "annotations-toggle",
accelerator: "CmdOrCtrl+Shift+a",
},
],
},
]);
Expand Down Expand Up @@ -217,6 +223,7 @@ Electrobun.events.on("application-menu-clicked", ({ data }) => {
"prs",
"nav-toggle",
"channels-toggle",
"annotations-toggle",
].includes(
data.action,
)
Expand Down
4 changes: 4 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions docs/desktop.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ channel in the selected project. Switching projects restores each project's sele
Settings live in the account menu and Cmd+comma. Cmd+B toggles navigation; Cmd+Shift+B toggles the
channel sidebar. Project paths are scoped to their host until repository-based identity lands.

Cmd+Shift+A (View → Toggle Annotations) opens or closes Agentation's feedback toolbar in the
desktop app. Its collapsed launcher stays hidden. Select an element, add a note, and copy the
feedback into a channel's composer. Saved annotations remain available when the toolbar reopens.
The desktop bundle includes Agentation's license at `Contents/Resources/app/web/agentation-license.txt`.

## Process and installation contract

- The desktop connects to Ace Helper. Closing or quitting the desktop leaves hosting available.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@webgpu/types": "0.1.69",
"agentation": "3.1.2",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"comark": "0.3.2",
Expand Down