diff --git a/frontend/src/components/ai-elements/prompt-input.tsx b/frontend/src/components/ai-elements/prompt-input.tsx index 5177b72206..d8d5c626e1 100644 --- a/frontend/src/components/ai-elements/prompt-input.tsx +++ b/frontend/src/components/ai-elements/prompt-input.tsx @@ -414,8 +414,8 @@ export const PromptInputActionAddAttachments = ({ return ( { - e.preventDefault(); + closeOnClick={false} + onClick={() => { attachments.openFileDialog(); }} > diff --git a/frontend/src/components/pages/topics/Tab.Messages/index.tsx b/frontend/src/components/pages/topics/Tab.Messages/index.tsx index a01c010406..01207e1de7 100644 --- a/frontend/src/components/pages/topics/Tab.Messages/index.tsx +++ b/frontend/src/components/pages/topics/Tab.Messages/index.tsx @@ -89,7 +89,7 @@ import { isServerless } from '../../../../config'; import { useQueryStateWithCallback } from '../../../../hooks/use-query-state-with-callback'; import { PayloadEncoding } from '../../../../protogen/redpanda/api/console/v1alpha1/common_pb'; import { appGlobal } from '../../../../state/app-global'; -import { useTopicSettingsStore } from '../../../../stores/topic-settings-store'; +import { DEFAULT_SORTING, useTopicSettingsStore } from '../../../../stores/topic-settings-store'; import { IsDev } from '../../../../utils/env'; import { sanitizeString, wrapFilterFragment } from '../../../../utils/filter-helper'; import { trimSlidingWindow } from '../../../../utils/message-table-helpers'; @@ -472,7 +472,7 @@ export const TopicMessageView: FC = (props) => { getDefaultValue: () => getSorting(props.topic.topicName), }, 'sort', - sortingParser.withDefault([]) + sortingParser.withDefault(DEFAULT_SORTING) ); // Continuous pagination toggle state @@ -572,11 +572,16 @@ export const TopicMessageView: FC = (props) => { }) : messages; - // For continuous pagination, just use the filtered messages directly - // We don't use placeholders as they cause page content to shift + // Continuous pagination disables header sorting, so the table's sorted row + // model can't impose newest-first there. Force it for the "Newest" fetch only + // (timestamp desc, then offset desc as a cross-partition tiebreak). Other + // origins keep the streamed order. + // In the normal paginated view the table sorts client-side via the default + // `sorting` state (DEFAULT_SORTING = timestamp desc, offset desc), so we pass + // the rows through untouched here. const filteredMessages = continuousPaginationEnabled && startOffset === PartitionOffsetOrigin.EndMinusResults - ? [...baseFilteredMessages].sort((a, b) => b.timestamp - a.timestamp) + ? [...baseFilteredMessages].sort((a, b) => b.timestamp - a.timestamp || b.offset - a.offset) : baseFilteredMessages; // Convert @computed activePreviewTags to useMemo diff --git a/frontend/src/components/redpanda-ui/components/dropdown-menu.tsx b/frontend/src/components/redpanda-ui/components/dropdown-menu.tsx index ab457ed87f..ef94d1350c 100644 --- a/frontend/src/components/redpanda-ui/components/dropdown-menu.tsx +++ b/frontend/src/components/redpanda-ui/components/dropdown-menu.tsx @@ -236,6 +236,8 @@ function DropdownMenuContent({ type DropdownMenuItemProps = React.ComponentProps & { inset?: boolean; variant?: 'default' | 'destructive'; + /** @deprecated Base UI menu items have no `onSelect`. Use `onClick` (add `closeOnClick={false}` to keep the menu open). */ + onSelect?: never; }; function DropdownMenuItem({ @@ -276,6 +278,8 @@ function DropdownMenuItem({ type DropdownMenuCheckboxItemProps = React.ComponentProps & { inset?: boolean; + /** @deprecated Base UI menu items have no `onSelect`. Use `onClick` (add `closeOnClick={false}` to keep the menu open). */ + onSelect?: never; }; function DropdownMenuCheckboxItem({ @@ -313,6 +317,8 @@ function DropdownMenuCheckboxItem({ type DropdownMenuRadioItemProps = React.ComponentProps & { inset?: boolean; + /** @deprecated Base UI menu items have no `onSelect`. Use `onClick` (add `closeOnClick={false}` to keep the menu open). */ + onSelect?: never; }; function DropdownMenuRadioItem({ className, children, disabled, inset, ...props }: DropdownMenuRadioItemProps) { diff --git a/frontend/src/stores/topic-settings-store.ts b/frontend/src/stores/topic-settings-store.ts index 151e6a6c54..ddf9e04b9c 100644 --- a/frontend/src/stores/topic-settings-store.ts +++ b/frontend/src/stores/topic-settings-store.ts @@ -111,7 +111,15 @@ export type TopicSettingsStore = { clearAllSettings: () => void; }; -const DEFAULT_SORTING: SortingState = []; +// Default display order for the message list: newest-first by timestamp, with +// offset descending as a cross-partition tiebreak (offset isn't global across +// partitions, so timestamp is the primary key). The backend's listMessages +// stream isn't globally timestamp-sorted across partitions, so this default +// drives the table's client-side sort to give users a sensible newest-first view. +export const DEFAULT_SORTING: SortingState = [ + { id: 'timestamp', desc: true }, + { id: 'offset', desc: true }, +]; const DEFAULT_SEARCH_PARAMS: TopicSearchParams = { offsetOrigin: -1, @@ -214,7 +222,11 @@ export const useTopicSettingsStore = create()( getSorting: (topicName: string) => { const topic = get().perTopicSettings.find((t) => t.topicName === topicName); - return topic?.searchParams.sorting ?? DEFAULT_SORTING; + const sorting = topic?.searchParams.sorting; + // Treat an empty/unset sort as "use the newest-first default" so the + // default applies on every fresh load, including returning users whose + // persisted settings still hold the old empty sort. + return sorting && sorting.length > 0 ? sorting : DEFAULT_SORTING; }, setSearchParams: (topicName: string, searchParams: Partial) => {