Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
7d91fb3
fix(comments): rebuild the comment composer on the create-post UX
tsahimatsliah Aug 3, 2026
66fac44
fix(comments): restore composer scroll when entering markdown mode
tsahimatsliah Aug 3, 2026
4b43de0
refactor(comments): align the composer with repo conventions
tsahimatsliah Aug 3, 2026
d4532d6
fix(types): satisfy the strict guard on every file this branch touches
tsahimatsliah Aug 3, 2026
00eb900
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 3, 2026
7bdce24
feat(comments): full-screen mobile composer + keyboard-safe drawers
tsahimatsliah Aug 3, 2026
7997979
fix(comments): portal the mobile composer drawer to the root
tsahimatsliah Aug 3, 2026
ec57d86
fix(composer): dedupe the stacked kind picker, schedule from the head…
tsahimatsliah Aug 3, 2026
3cd496c
fix(composer): autofocus queued replies, open overflow menu, bridge f…
tsahimatsliah Aug 3, 2026
0980ff0
fix(drawer): lock the page scroll behind open drawers
tsahimatsliah Aug 4, 2026
75deddf
fix(drawer): lock the html scroller too, not just body
tsahimatsliah Aug 4, 2026
cf620bb
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 4, 2026
d286e57
fix(composer): equal padding on all drawer sides
tsahimatsliah Aug 4, 2026
2dfc541
fix(composer): one 20px frame on every side of the comment drawer
tsahimatsliah Aug 4, 2026
5039039
fix(composer): address review — scope the lock, gate the viewport hook
tsahimatsliah Aug 4, 2026
b7ba137
Merge remote-tracking branch 'origin/main' into claude/mobile-comment…
tsahimatsliah Aug 4, 2026
4c133bd
chore: re-trigger CI
tsahimatsliah Aug 4, 2026
854d955
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 4, 2026
265ebb3
chore(composer): trim commentary to load-bearing notes
tsahimatsliah Aug 4, 2026
8f8cd16
fix(composer): apply the second internal-review round
tsahimatsliah Aug 5, 2026
3ae56de
chore: appease react/destructuring-assignment in NewComment spec
tsahimatsliah Aug 5, 2026
14053fd
chore(composer): second commentary trim
tsahimatsliah Aug 5, 2026
9057644
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 5, 2026
a922924
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 5, 2026
1c60c7d
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
tsahimatsliah Aug 8, 2026
79e3aea
fix(comments): review follow-ups on the composer rebuild
rebelchris Aug 27, 2026
9f08296
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
rebelchris Aug 27, 2026
12ff6fa
fix(tools): move ToolDiscussion to CommentInput after main merge
rebelchris Aug 27, 2026
caefe7e
Merge branch 'main' into claude/mobile-comment-input-fix-2e164b
rebelchris Aug 27, 2026
9f2dee7
fix(tools): collapse ToolDiscussion's NewComment props for prettier
rebelchris Aug 28, 2026
4ae0c04
feat(comments): restore the notification opt-in in the full-screen co…
rebelchris Aug 28, 2026
b1a7ee0
fix(drawer): dialog semantics — focus trap, Escape, focus restoration
rebelchris Aug 28, 2026
d67e78f
refactor(comments): route open-comment requests through ActivePostCon…
rebelchris Aug 28, 2026
f05ccf9
fix(companion): keep the composer inline inside the extension sidebar
rebelchris Aug 28, 2026
cc085b0
fix(fields): let rows floor the markdown textarea again; trim comments
rebelchris Aug 28, 2026
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
18 changes: 9 additions & 9 deletions packages/extension/src/companion/CompanionDiscussion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ import { useBackgroundRequest } from '@dailydotdev/shared/src/hooks/companion';
import { generateCommentsQueryKey } from '@dailydotdev/shared/src/lib/query';
import { getCompanionWrapper } from '@dailydotdev/shared/src/lib/extension';
import { ProfileImageSize } from '@dailydotdev/shared/src/components/ProfilePicture';
import CommentInputOrModal from '@dailydotdev/shared/src/components/comments/CommentInputOrModal';
import type { CommentInputProps } from '@dailydotdev/shared/src/components/comments/CommentInput';
import CommentInput from '@dailydotdev/shared/src/components/comments/CommentInput';

const CompanionCommentInput = (props: CommentInputProps): ReactElement => (
<CommentInput {...props} forceInline />
);

interface CompanionDiscussionProps {
post: PostBootData;
Expand All @@ -24,8 +29,7 @@ export function CompanionDiscussion({
style,
className,
onShowUpvoted,
}: CompanionDiscussionProps): ReactElement {
const commentClasses = { tab: '!min-h-[14.5rem]' };
}: CompanionDiscussionProps): ReactElement | null {
const { openShareComment } = useShareComment(Origin.Companion);
useBackgroundRequest(
generateCommentsQueryKey({ postId: post?.id, sortBy: undefined }),
Expand All @@ -49,19 +53,15 @@ export function CompanionDiscussion({
<NewComment
size={ProfileImageSize.Medium}
post={post}
className={{
...commentClasses,
container: 'companion-new-comment-button mb-4',
}}
CommentInputOrModal={CommentInputOrModal}
className={{ container: 'companion-new-comment-button mb-4' }}
CommentInput={CompanionCommentInput}
/>
<PostComments
post={post}
origin={Origin.Companion}
onShare={(comment) => openShareComment(comment, post)}
onClickUpvote={onShowUpvoted}
modalParentSelector={getCompanionWrapper}
className={commentClasses}
/>
</div>
</div>
Expand Down
103 changes: 103 additions & 0 deletions packages/shared/src/components/comments/CommentInput.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { act, render, screen } from '@testing-library/react';
import React from 'react';
import type { Post } from '../../graphql/posts';
import { useViewSize } from '../../hooks';
import CommentInput from './CommentInput';

const mockComposerProps = jest.fn();
const mockDrawerProps = jest.fn();

jest.mock('../../hooks', () => {
const actual = jest.requireActual('../../hooks');

return {
...actual,
useViewSize: jest.fn(),
};
});

jest.mock('../../hooks/post/useMutateComment', () => ({
useMutateComment: () => ({
mutateComment: jest.fn(),
isLoading: false,
isSuccess: false,
}),
}));

jest.mock('../fields/MarkdownInput/CommentMarkdownInput', () => ({
CommentMarkdownInput: (props: Record<string, unknown>) => {
mockComposerProps(props);
return <div>composer</div>;
},
}));

jest.mock('../drawers/Drawer', () => {
const actual = jest.requireActual('../drawers/Drawer');

return {
...actual,
Drawer: ({ children, ...props }: React.PropsWithChildren) => {
mockDrawerProps(props);
return <div>{children}</div>;
},
};
});

const post = { id: 'post-1', source: { id: 'source-1' } } as Post;

describe('CommentInput', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('opens as a full-screen drawer on mobile, focused and filling it', () => {
jest.mocked(useViewSize).mockReturnValue(false);
render(<CommentInput post={post} onClose={jest.fn()} />);

expect(screen.getByText('composer')).toBeInTheDocument();
expect(mockDrawerProps).toHaveBeenCalledWith(
expect.objectContaining({
isFullScreen: true,
appendOnRoot: true,
}),
);
expect(mockComposerProps).toHaveBeenCalledWith(
expect.objectContaining({ fills: true, autoFocus: true }),
);
});

it('drops the drawer default padding so content is not padded twice', () => {
// Including the bottom: the composer's action bar carries the safe area.
jest.mocked(useViewSize).mockReturnValue(false);
render(<CommentInput post={post} onClose={jest.fn()} />);

const { className } = mockDrawerProps.mock.calls[0][0];
expect(className.wrapper).toContain('!p-0');
});

it('keeps the draft when crossing the breakpoint remounts the editor', () => {
jest.mocked(useViewSize).mockReturnValue(true);
const { rerender } = render(<CommentInput post={post} />);

const { onChange } = mockComposerProps.mock.calls.at(-1)[0];
act(() => onChange('my draft'));

jest.mocked(useViewSize).mockReturnValue(false);
rerender(<CommentInput post={post} />);

expect(mockComposerProps).toHaveBeenLastCalledWith(
expect.objectContaining({ initialContent: 'my draft' }),
);
});

it('stays inline on desktop', () => {
jest.mocked(useViewSize).mockReturnValue(true);
render(<CommentInput post={post} onClose={jest.fn()} />);

expect(screen.getByText('composer')).toBeInTheDocument();
expect(mockDrawerProps).not.toHaveBeenCalled();
expect(mockComposerProps).toHaveBeenCalledWith(
expect.objectContaining({ fills: false }),
);
});
});
69 changes: 69 additions & 0 deletions packages/shared/src/components/comments/CommentInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { ReactElement } from 'react';
import React, { useState } from 'react';
import type { CommentMarkdownInputProps } from '../fields/MarkdownInput/CommentMarkdownInput';
import { CommentMarkdownInput } from '../fields/MarkdownInput/CommentMarkdownInput';
import { WriteCommentContext } from '../../contexts/WriteCommentContext';
import { useMutateComment } from '../../hooks/post/useMutateComment';
import { useViewSize, ViewSize } from '../../hooks';
import { Drawer, DrawerPosition } from '../drawers/Drawer';

export interface CommentInputProps extends CommentMarkdownInputProps {
onClose?: () => void;
/** Inline on small viewports too — the companion must not cover the host page. */
forceInline?: boolean;
}

export default function CommentInput({
onClose,
className,
forceInline = false,
...props
}: CommentInputProps): ReactElement {
const isFullScreen = !useViewSize(ViewSize.Laptop) && !forceInline;
// The draft lives above the drawer/inline swap at the Laptop breakpoint.
const [draft, setDraft] = useState<string>();

const mutateCommentResult = useMutateComment({
post: props.post,
editCommentId: props.editCommentId,
parentCommentId: props.parentCommentId,
onCommented: props.onCommented,
});

const composer = (
<CommentMarkdownInput
{...props}
autoFocus={isFullScreen || props.autoFocus}
fills={isFullScreen}
className={isFullScreen ? undefined : className}
initialContent={draft ?? props.initialContent}
onChange={(value) => {
setDraft(value);
props.onChange?.(value);
}}
onClose={onClose}
/>
);

return (
<WriteCommentContext.Provider
value={{ mutateComment: mutateCommentResult }}
>
{isFullScreen ? (
<Drawer
isOpen
isFullScreen
// Transformed ancestors (`animate-composer-in`) trap position: fixed
appendOnRoot
position={DrawerPosition.Bottom}
onClose={() => onClose?.()}
className={{ wrapper: 'flex flex-col !p-0' }}
>
{composer}
</Drawer>
) : (
composer
)}
</WriteCommentContext.Provider>
);
}
51 changes: 0 additions & 51 deletions packages/shared/src/components/comments/CommentInputOrModal.tsx

This file was deleted.

16 changes: 6 additions & 10 deletions packages/shared/src/components/comments/MainComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ import { useEditCommentProps } from '../../hooks/post/useEditCommentProps';
import { useLogContext } from '../../contexts/LogContext';
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';

const CommentInputOrModal = dynamic(
() =>
import(
/* webpackChunkName: "commentInputOrModal" */ './CommentInputOrModal'
),
const CommentInput = dynamic(
() => import(/* webpackChunkName: "commentInput" */ './CommentInput'),
);

type ClassName = {
Expand Down Expand Up @@ -210,29 +207,28 @@ export default function MainComment({
</div>
)}
{editProps && (
<CommentInputOrModal
<CommentInput
{...editProps}
post={props.post}
onCommented={(...params) => {
onEdit(null);
onCommented?.(...params);
}}
onClose={() => onEdit(null)}
className={{ input: className?.commentBox }}
className={className?.commentBox}
/>
)}
{commentId === comment.id && (
<div className={classNames(isModalThread && 'mt-2')}>
<CommentInputOrModal
<CommentInput
{...replyProps}
post={props.post}
onCommented={(...params) => {
onReplyTo(null);
onCommented?.(...params);
}}
onClose={() => onReplyTo(null)}
className={{ input: className?.commentBox }}
replyToCommentId={commentId}
className={className?.commentBox}
/>
</div>
)}
Expand Down
16 changes: 6 additions & 10 deletions packages/shared/src/components/comments/SubComment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ import type { CommentMarkdownInputProps } from '../fields/MarkdownInput/CommentM
import { useComments } from '../../hooks/post';
import { useEditCommentProps } from '../../hooks/post/useEditCommentProps';

const CommentInputOrModal = dynamic(
() =>
import(
/* webpackChunkName: "commentInputOrModal" */ './CommentInputOrModal'
),
const CommentInput = dynamic(
() => import(/* webpackChunkName: "commentInput" */ './CommentInput'),
);

export interface SubCommentProps
Expand Down Expand Up @@ -116,29 +113,28 @@ function SubComment({
</CommentBox>
)}
{editProps && (
<CommentInputOrModal
<CommentInput
{...editProps}
post={props.post}
onCommented={(data, isNew) => {
onEdit(null);
onCommented?.(data, isNew);
}}
onClose={() => onEdit(null)}
className={{ input: className }}
className={className}
/>
)}
{commentId === comment.id && inputProps && (
<div className={classNames(isModalThread && 'mt-2')}>
<CommentInputOrModal
<CommentInput
{...inputProps}
className={{ input: className }}
className={className}
post={props.post}
onCommented={(...params) => {
onReplyTo(null);
onCommented?.(...params);
}}
onClose={() => onReplyTo(null)}
replyToCommentId={commentId}
/>
</div>
)}
Expand Down
Loading
Loading