Files
PaperClipAI/ui/src/components/chat-thread-presets.ts
T
DottaandClaude Fable 5 048e79f094 Fix conference room chat scroll, identity, feed links (PAP-13345)
- Give the assistant chat body its own scroll container so loading or
  scrolling a chat never moves the page or the sidebar.
- Render the conference room with issue-chat density: the board user's
  messages are right-aligned blue bubbles, and the company user directory
  now resolves real names/avatars instead of "You".
- Report heartbeat scheduling suppression from the selected-agent-chat
  comment route and surface it as an inline notice, so a send on a
  worktree instance no longer looks like it silently did nothing.
- Deep-link Agent Feed events on board_chat issues to /board-chat/{ref}
  (they are hidden from the issues list, so the old link was a broken
  /issues/<uuid>), and stop showing the board user's own comment events.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 00:36:19 +00:00

111 lines
3.9 KiB
TypeScript

export type IssueChatThreadPresetName = "task" | "assistant";
export type IssueChatThreadVariant = "full" | "embedded";
export type IssueChatThreadDensity = "comfortable" | "compact";
export type IssueChatThreadWorkingIndicator = "expanded" | "collapsed";
export type IssueChatThreadComposerSubmitKey = "mod-enter" | "enter";
export interface IssueChatThreadPresetConfig {
preset: IssueChatThreadPresetName;
density: IssueChatThreadDensity;
workingIndicator: IssueChatThreadWorkingIndicator;
variant: IssueChatThreadVariant;
showComposer: boolean;
showJumpToLatest: boolean;
autoScrollToLatestOnInitialLoad: boolean;
emptyMessage: string;
suppressIssueStatusNotices: boolean;
enableReassign: boolean;
showBackgroundWorkChildren: boolean;
composerSubmitKey: IssueChatThreadComposerSubmitKey;
composerSingleLine: boolean;
}
export interface IssueChatThreadPresetOverrides {
variant?: IssueChatThreadVariant;
showComposer?: boolean;
showJumpToLatest?: boolean;
autoScrollToLatestOnInitialLoad?: boolean;
emptyMessage?: string;
suppressIssueStatusNotices?: boolean;
enableReassign?: boolean;
showBackgroundWorkChildren?: boolean;
}
type IssueChatThreadPresetDefinition = Omit<
IssueChatThreadPresetConfig,
"preset" | "variant" | "showJumpToLatest" | "emptyMessage"
> & {
variant: IssueChatThreadVariant;
showJumpToLatest: (variant: IssueChatThreadVariant) => boolean;
emptyMessage: (variant: IssueChatThreadVariant) => string;
};
const taskEmptyMessage = (variant: IssueChatThreadVariant) =>
variant === "embedded"
? "No run output yet."
: "This task conversation is empty. Start with a message below.";
const ISSUE_CHAT_THREAD_PRESETS: Record<IssueChatThreadPresetName, IssueChatThreadPresetDefinition> = {
task: {
density: "comfortable",
workingIndicator: "expanded",
variant: "full",
showComposer: true,
showJumpToLatest: (variant) => variant === "full",
autoScrollToLatestOnInitialLoad: true,
emptyMessage: taskEmptyMessage,
suppressIssueStatusNotices: false,
enableReassign: false,
showBackgroundWorkChildren: true,
composerSubmitKey: "mod-enter",
composerSingleLine: false,
},
assistant: {
// Comfortable density matches the issue-chat message rendering: the
// board user's own messages are right-aligned blue bubbles (PAP-13345).
density: "comfortable",
workingIndicator: "collapsed",
variant: "full",
showComposer: true,
showJumpToLatest: () => false,
autoScrollToLatestOnInitialLoad: true,
emptyMessage: () => "Send a message to start the conversation.",
suppressIssueStatusNotices: true,
enableReassign: false,
showBackgroundWorkChildren: false,
composerSubmitKey: "enter",
composerSingleLine: true,
},
};
export function resolveIssueChatThreadPreset(
preset: IssueChatThreadPresetName = "task",
overrides: IssueChatThreadPresetOverrides = {},
): IssueChatThreadPresetConfig {
const definition = ISSUE_CHAT_THREAD_PRESETS[preset];
const variant = overrides.variant ?? definition.variant;
return {
preset,
density: definition.density,
workingIndicator: definition.workingIndicator,
variant,
showComposer: overrides.showComposer ?? definition.showComposer,
showJumpToLatest: overrides.showJumpToLatest ?? definition.showJumpToLatest(variant),
autoScrollToLatestOnInitialLoad:
overrides.autoScrollToLatestOnInitialLoad ?? definition.autoScrollToLatestOnInitialLoad,
emptyMessage: overrides.emptyMessage ?? definition.emptyMessage(variant),
suppressIssueStatusNotices:
overrides.suppressIssueStatusNotices ?? definition.suppressIssueStatusNotices,
enableReassign: overrides.enableReassign ?? definition.enableReassign,
showBackgroundWorkChildren:
overrides.showBackgroundWorkChildren ?? definition.showBackgroundWorkChildren,
composerSubmitKey: definition.composerSubmitKey,
composerSingleLine: definition.composerSingleLine,
};
}