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
29 changes: 20 additions & 9 deletions apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13842,10 +13842,16 @@ export function AgentChatPane({
style={{ aspectRatio: "560 / 300" }}
exit={{ opacity: 0, scale: 0.8, transition: { duration: 0.3, ease: "easeOut" } }}
>
{/* `h-auto` + `max-h-full` left the image at its
natural height once the box shrank, so a short
window cropped the wordmark against the column's
`overflow-hidden` instead of scaling it. Filling
the box and letterboxing inside it with
`object-contain` keeps the shrink proportional. */}
<img
src="./logo.png"
alt="ADE"
className="relative z-10 h-auto max-h-full w-full max-w-full object-contain"
className="relative z-10 h-full w-full object-contain"
/>
</motion.div>

Expand All @@ -13867,14 +13873,6 @@ export function AgentChatPane({
</div>
) : null}

{onImportedSession && !appPanelOpen ? (
<ImportFloatingBadge
projectRoot={projectRoot}
disabled={!laneId || draftLaunchTargetIsAutoCreate}
onOpen={() => setImportBrowserOpen(true)}
/>
) : null}

{/* Launch shelf — everything that answers "where does this
run". It is drawn as a recessed drawer tucked under the
composer rather than as another centered band: the lane
Expand Down Expand Up @@ -14016,6 +14014,19 @@ export function AgentChatPane({
<WorkActivityModule />
</motion.div>
) : null}

{/* Last row in the column. Sat between the composer and
the launch shelf before, where it wedged itself into
the shelf's row and overlapped it in a narrow window;
at the bottom it is a hint below everything it can
act on rather than a band cutting the stack in two. */}
{onImportedSession && !appPanelOpen ? (
<ImportFloatingBadge
projectRoot={projectRoot}
disabled={!laneId || draftLaunchTargetIsAutoCreate}
onOpen={() => setImportBrowserOpen(true)}
/>
) : null}
</div>
</div>
{appPanelOpen ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,22 @@ describe("ImportFloatingBadge", () => {

it("dismisses machine-locally per project without opening", () => {
const onOpen = vi.fn();
render(<ImportFloatingBadge projectRoot="/Users/dev/ade" onOpen={onOpen} />);
const { container } = render(<ImportFloatingBadge projectRoot="/Users/dev/ade" onOpen={onOpen} />);
fireEvent.click(screen.getByLabelText("Hide import hint"));
expect(onOpen).not.toHaveBeenCalled();
expect(readImportBadgeDismissed("/Users/dev/ade")).toBe(true);
expect(screen.queryByText("Import your chats from outside ADE")).toBeNull();
// Not merely invisible — zero nodes. The badge is a row in a `gap-3`
// flex column, so anything it leaves behind once retired (an empty
// wrapper included) spends gap the retired hint no longer earns.
expect(container.firstChild).toBeNull();
});

it("occupies no layout row for a project that never opts in", () => {
const { container } = render(<ImportFloatingBadge projectRoot={null} onOpen={vi.fn()} />);
expect(container.firstChild).toBeNull();
expect(screen.queryByText("Import your chats from outside ADE")).toBeNull();
expect(screen.queryByLabelText("Hide import hint")).toBeNull();
});

it("stays visible for a different project on this machine", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ export function ImportFloatingBadge({
if (!projectRoot || dismissed) return null;

return (
<div className="flex w-full justify-center">
// `shrink-0` because the draft column this sits in is a height-capped
// flex-col where the logo is the only row meant to absorb overflow.
<div className="flex w-full shrink-0 justify-center">
<div
className={cn(
"relative inline-flex items-center gap-3 rounded-full border border-violet-300/25 bg-gradient-to-r from-violet-500/18 via-[#1A1830] to-cyan-400/12 px-3 py-1.5 shadow-[0_10px_28px_rgba(88,28,135,0.28)]",
Expand Down
17 changes: 16 additions & 1 deletion docs/features/chat/composer-and-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,29 @@ so a new-chat composer is centered rather than shifted for invisible chrome.

### Empty draft surface

The draft (new-chat) branch is three elements, not seven: the wordmark, the
The draft (new-chat) branch is three elements at its core, not seven: the wordmark, the
composer, and a **launch shelf** tucked under the composer, with the activity
module below. There is no standing "Start a new conversation" caption — the
wordmark already identifies the app, so the line was a band of vertical space
spent restating what the user could see. Only a non-default mode still writes a
line there (`isOrchestratorDraft` renders "Orchestrate a swarm of agents"),
because that names something the surface does not otherwise show.

The column's row order is wordmark, the optional orchestrator heading, composer,
launch shelf, activity module, then the `ImportFloatingBadge` import hint as the
last row. The hint used to sit between the composer and the shelf, where it
wedged itself into the shelf's row and overlapped it in a narrow window; at the
bottom it reads as a hint below
everything it can act on rather than a band cutting the stack in two. Every row
below the wordmark is `shrink-0` — the wordmark is the only row meant to absorb
overflow in this height-capped flex column, and it does so by filling its
aspect-ratio box and letterboxing inside it (`h-full w-full object-contain`).
Sizing it `h-auto max-h-full` instead left the image at its natural height once
the box shrank, so a short window cropped the wordmark against the enclosing
`overflow-hidden` wrapper rather than scaling it down proportionally. The badge
renders zero nodes when it is dismissed or has no project, so a retired hint
spends none of the column's `gap-3`; both are pinned by tests.

The shelf holds everything that answers **where this runs**, as two adjacent
dropdowns plus two labelled actions: `DraftMachinePicker`, then a `LaneCombobox`
(mounted `compact`, so its 28px trigger matches the composer pills above it),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ continuation metadata is recorded as soon as ADE knows the provider target.
| `apps/ade-cli/src/services/sync/syncRemoteCommandService.ts`, `apps/desktop/src/main/services/sync/syncRemoteCommandService.ts` | Registers `work.listExternalSessions` and `work.importExternalSession` for paired controllers. The desktop file is a re-export of the ade-cli implementation. |
| `apps/desktop/src/shared/types/sync.ts` | Sync command DTO aliases for external-session list/import payloads and results. |
| `apps/desktop/src/renderer/components/terminals/importSessions/ImportSessionBrowser.tsx` | Desktop two-stage browser/details flow: provider filters, search (which spans the sampled `messages` text, not just titles), project/all scope, progressive scans, full details with a bounded scrollable message sample, target lane selection, imported/active badges, Open-in-ADE, and safe action dispatch. Asks for 200 rows per provider to match the service's project-scope discovery window; the default 50 threw away most of what was already scanned. A provider that fails its scan leaves a muted per-provider notice ("OpenCode CLI not found…") instead of an unexplained empty list, and only a total scan failure still becomes the blocking error state. The dialog height is content-driven rather than pinned, because the details stage is far shorter than the list stage. |
| `apps/desktop/src/renderer/components/terminals/importSessions/ImportFloatingBadge.tsx` | The "Import your chats from outside ADE" hint on the Work draft surface, plus its machine-local per-project dismissal (`readImportBadgeDismissed`). It is the last row of the draft column, below the activity module, and is `shrink-0` because the wordmark above it is the only row meant to absorb overflow when the column is height-capped. It returns `null` — not an empty wrapper — once dismissed or when there is no project root, so a retired hint spends none of the column's row gap. |
| `apps/desktop/src/shared/externalSessionAffordances.ts`, `apps/desktop/src/renderer/components/terminals/importSessions/affordances.ts` | Canonical capability-to-action mapper for the 2x2 Continue/Copy x ADE-chat/CLI-session policy, plus the renderer compatibility export. Shared directly with the TUI. |
| `apps/desktop/src/renderer/components/terminals/importSessions/sessionPresentation.ts` | Pure desktop heading/time/anchor helpers. Provider titles win, then the opening prompt (`preview`), then cwd + relative time. `sessionAnchors` returns the row's "started"/"latest" pair and drops whichever one the heading is already showing, so a row never prints the same sentence twice. |
| `apps/desktop/src/renderer/components/terminals/importSessions/contract.ts` | Renderer bridge/types/display helpers for external sessions. |
Expand Down
Loading