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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

This file tracks product releases for Roomote (single monorepo version). Automated release entries are prepended by `pnpm run version`.

## 1.19.6 (2026-10-31)

Roomote 1.19.6 restores reliable all-repository automations across GitHub organizations and makes it easier to clean up sessions without losing your place.

### Highlights

- Run all-repository automations when multiple GitHub organizations are connected.
- Select and delete sessions in bulk from either overview, keeping your current view and filters.

### Patch changes

- All-repository automations now start reliably when connected GitHub repositories span multiple organizations, instead of failing to create source-control credentials after another GitHub App installation is added.
- Select and delete multiple sessions from the Sessions list or board again, with page-scoped selection and failed deletions retained for retry. Deleting one or several sessions keeps you in the current overview with its filters and page instead of redirecting home.

## 1.19.5 (2026-10-29)

Roomote 1.19.5 improves GitHub work across organizations and restores integration-key access for scheduled and manually run custom automations.
Expand Down
9 changes: 9 additions & 0 deletions apps/docs/fast-sessions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ reversible and hides the session and its linked tasks from ordinary lists and
search until it is unarchived. Deleting a session uses the existing confirmation
and cleanup safeguards.

In the Sessions list or board, choose **Select sessions** to show checkboxes.
Select sessions individually, or choose **All** to select every session you can
manage on the current page. **Delete (n)** confirms and deletes the selection
using the same cleanup safeguards as individual deletion. If some deletions
fail, those sessions stay selected for retry. Exit selection mode to resume
opening sessions normally; changing views, filters, or pages clears selection.
Deleting one or several sessions from either overview keeps you on that view
with its current filters and page.

On desktop, you can keep several task workspaces open beside the session,
resize them, and switch between the conversation and task panels without losing
your place. On smaller screens, Roomote shows one workspace at a time so the
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/self-hosting/operations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ one. Check the running application version and applied schema migration at any
time under **Settings → Deployment → Diagnostics**; both are also recorded in
every backup bundle's manifest.

When upgrading from Roomote 1.19.5 or earlier to 1.19.6 or later, upgrade all
servers handling sign-in and account linking together. Restart any sign-in or
account-linking attempts begun before the upgrade. Existing signed-in sessions
are retained, and registered OAuth callback URLs do not need to change.

Roomote surfaces release history and available updates in the web app:

- **Admins** on self-hosted deployments see an update notice in the sidenav when
Expand Down
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"@aws-sdk/client-s3": "3.1000.0",
"@aws-sdk/s3-request-presigner": "3.1000.0",
"@better-auth/drizzle-adapter": "^1.6.22",
"@better-auth/drizzle-adapter": "^1.7.12",
"@codemirror/lang-yaml": "^6.0.0",
"@codemirror/merge": "^6.0.0",
"@codemirror/state": "^6.0.0",
Expand Down Expand Up @@ -92,7 +92,7 @@
"@zxcvbn-ts/core": "^4.1.2",
"ai": "^6.0.116",
"ansi-to-react": "^6.2.6",
"better-auth": "^1.6.22",
"better-auth": "^1.7.12",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Migrate account unlinking to the new local-row ID contract. Better Auth 1.7.12 changes /unlink-account to look up account.id, whereas 1.6.22 matched the provider-issued account.accountId plus providerId. createUseUnlinkOAuthLinkedAccount still sends the latter, sourced by LinkedAccounts.tsx from the linked-account queries, so Unlink now fails with 400 ACCOUNT_NOT_FOUND for Microsoft Teams, GitLab, Gitea, Bitbucket, and Azure DevOps. An isolated probe against the installed 1.7.12 handler reproduced the failure with the provider ID and succeeded with the local row ID. Expose and pass authAccounts.id through those query/UI/hook contracts and update the client input type; the new sign-in/link integration tests do not exercise unlinking.

"better-sse": "^0.16.1",
"change-case": "^5.4.4",
"class-variance-authority": "^0.7.1",
Expand Down
19 changes: 11 additions & 8 deletions apps/web/src/app/(authenticated)/sessions/SessionBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {

import { BasicTooltip, Button, Funnel } from '@/components/system';
import { useSessionStatusMutation } from '@/components/sessions/use-session-status-mutation';
import { useSessionSelectionActive } from '@/components/sessions/SessionSelection';
import {
canDropSessionBoardCard,
getSessionBoardDropStatus,
Expand Down Expand Up @@ -454,6 +455,8 @@ export function SessionBoardCard({
}) {
const reducedMotion = useReducedMotion() ?? false;
const registry = useContext(SessionBoardCardRegistryContext);
const selecting = useSessionSelectionActive();
const canDrag = canManage && !selecting;
const dnd = useContext(SessionBoardDndContext);
const [isFlying, setIsFlying] = useState(false);
const dragData = useRef<SessionBoardDragData>({
Expand All @@ -470,7 +473,7 @@ export function SessionBoardCard({
const draggable = useDraggable({
id: sessionId,
data: dragData.current,
disabled: !canManage || dnd?.statusMutationPending === true,
disabled: !canDrag || dnd?.statusMutationPending === true,
});

useLayoutEffect(() => {
Expand Down Expand Up @@ -529,14 +532,14 @@ export function SessionBoardCard({
<div
ref={setDragNodeRef}
style={{ transform: dragTransform }}
className={`relative w-full ${canManage ? 'cursor-grab active:cursor-grabbing' : ''}`}
{...(canManage ? draggable.attributes : {})}
{...(canManage ? dragListeners : {})}
role={canManage ? 'group' : undefined}
aria-label={canManage ? `Move ${title} to another status` : undefined}
className={`relative w-full ${canDrag ? 'cursor-grab active:cursor-grabbing' : ''}`}
{...(canDrag ? draggable.attributes : {})}
{...(canDrag ? dragListeners : {})}
role={canDrag ? 'group' : undefined}
aria-label={canDrag ? `Move ${title} to another status` : undefined}
aria-pressed={undefined}
title={canManage ? 'Drag to change session status' : undefined}
data-session-board-drag-activator={canManage ? true : undefined}
title={canDrag ? 'Drag to change session status' : undefined}
data-session-board-drag-activator={canDrag ? true : undefined}
>
{children}
</div>
Expand Down
78 changes: 47 additions & 31 deletions apps/web/src/app/(authenticated)/sessions/SessionCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Link from 'next/link';
import { format, formatDistanceToNow, isSameDay, isSameYear } from 'date-fns';
import type { SessionManualStatus } from '@roomote/types';

Expand All @@ -21,6 +20,11 @@ import { SessionInferenceCostBreakdown } from '@/components/sessions/SessionInfe
import { PrivateSessionIcon } from '@/components/sessions/PrivateSessionIcon';
import { getSessionSurfaceLabel } from '@/components/sessions/session-surfaces';
import { TaskAutomationIcon } from '@/components/tasks/TaskAutomationIcon';
import {
SessionSelectionControl,
SessionSelectionLink,
SessionSelectionRow,
} from '@/components/sessions/SessionSelection';
import { SessionActions } from '../../(sandbox)/sessions/[sessionId]/SessionDeleteAction';

type SessionCardData = {
Expand Down Expand Up @@ -108,41 +112,53 @@ export function SessionCard({
const accessibleActivityTimestamp = activityDate.toLocaleString();

return (
<div className="ph-no-capture group/card relative flex w-full @container items-start gap-3 p-4 transition-colors hover:bg-accent-foreground/10 group-data-[dragging=true]/board:hover:bg-transparent">
<Link
<SessionSelectionRow
sessionId={session.id}
className="ph-no-capture group/card relative flex w-full @container items-start gap-3 p-4 transition-colors hover:bg-accent-foreground/10 group-data-[dragging=true]/board:hover:bg-transparent"
>
<SessionSelectionLink
sessionId={session.id}
title={session.title}
canManage={session.canManage ?? false}
href={`/sessions/${session.id}`}
className="absolute inset-0 z-0 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<span className="sr-only">{session.title}</span>
</Link>
</SessionSelectionLink>
<div className="pointer-events-none relative z-10 mt-1 shrink-0">
{session.ownerKind === 'automation' ? (
<span
className="flex size-8 items-center justify-center overflow-clip rounded-full border border-border bg-white dark:bg-muted"
aria-hidden="true"
>
<TaskAutomationIcon
automationKey={session.ownerAutomation}
className="size-7"
<SessionSelectionControl
sessionId={session.id}
title={session.title}
canManage={session.canManage ?? false}
>
{session.ownerKind === 'automation' ? (
<span
className="flex size-8 items-center justify-center overflow-clip rounded-full border border-border bg-white dark:bg-muted"
aria-hidden="true"
>
<TaskAutomationIcon
automationKey={session.ownerAutomation}
className="size-7"
/>
</span>
) : (
<Avatar
imageUrl={session.ownerImageUrl}
name={ownerDisplayName}
email={session.ownerEmail ?? undefined}
size="md"
alt=""
loading="eager"
decoding="sync"
/>
</span>
) : (
<Avatar
imageUrl={session.ownerImageUrl}
name={ownerDisplayName}
email={session.ownerEmail ?? undefined}
size="md"
alt=""
loading="eager"
decoding="sync"
/>
)}
{session.unread && session.ownerUserId === viewerUserId ? (
<span
aria-label="Unread activity"
className="absolute top-0 right-0 size-2 rounded-full bg-accent-foreground ring-2 ring-background"
/>
) : null}
)}
{session.unread && session.ownerUserId === viewerUserId ? (
<span
aria-label="Unread activity"
className="absolute top-0 right-0 size-2 rounded-full bg-accent-foreground ring-2 ring-background"
/>
) : null}
</SessionSelectionControl>
</div>
<div className="pointer-events-none relative z-10 min-w-0 flex-1">
<div className="flex items-start justify-between gap-2 text-xs text-muted-foreground/75 md:items-center">
Expand Down Expand Up @@ -230,6 +246,6 @@ export function SessionCard({
/>
</div>
) : null}
</div>
</SessionSelectionRow>
);
}
Loading
Loading