refactor(desktop): colocate hook/component families into scoped folders

Single-scoped helpers/sub-files were sitting flat in shared/grab-bag dirs.
Fold each family into its own folder (index = the export, dir resolution keeps
public import paths intact), dropping the now-redundant filename prefix:

- session/hooks/use-prompt-actions.ts (+ -utils, + tests)
  -> use-prompt-actions/{index,utils}.ts (+ tests)
- components/assistant-ui/thread* + assistant/system/user message renderers
  -> assistant-ui/thread/{index,content,status,message-parts,timestamp,types,
     list,timeline,timeline-data,assistant-message,system-message,user-message,
     user-edit-composer,user-message-text} (+ tests)
- components/assistant-ui/tool-fallback(+model)/tool-approval
  -> assistant-ui/tool/{fallback,fallback-model,approval} (+ tests)

Pure move + import rewrites; no behaviour change. App-wide shared primitives
(markdown-text, directive-text, tooltip-icon-button, clarify-tool, ansi-text,
message-render-boundary) stay flat. desktop-controller intentionally left in
app/ (route root; foldering would churn ~80 relative imports for no gain).
This commit is contained in:
Brooklyn Nicholson 2026-06-30 02:42:07 -05:00
parent c9269fbfb6
commit fa7bce0789
32 changed files with 40 additions and 40 deletions

View file

@ -8,7 +8,7 @@ import { $composerAttachments, type ComposerAttachment } from '@/store/composer'
import { $busy, $connection, $messages, $sessions, setSessions } from '@/store/session'
import type { SessionInfo } from '@/types/hermes'
import { uploadComposerAttachment, usePromptActions } from './use-prompt-actions'
import { uploadComposerAttachment, usePromptActions } from '.'
vi.mock('@/hermes', () => ({
getProfiles: vi.fn(async () => ({ profiles: [] })),

View file

@ -72,7 +72,7 @@ import type {
SessionSteerResponse,
SessionTitleResponse,
SlashExecResponse
} from '../../types'
} from '../../../types'
import {
_submitInFlight,
@ -93,7 +93,7 @@ import {
visibleUserIndexAtOrdinal,
visibleUserOrdinal,
withSessionBusyRetry
} from './use-prompt-actions-utils'
} from './utils'
interface HandoffResult {
ok: boolean

View file

@ -15,7 +15,7 @@ import {
slashStatusText,
visibleUserIndexAtOrdinal,
visibleUserOrdinal
} from './use-prompt-actions-utils'
} from './utils'
describe('isSessionIdCandidate', () => {
it('accepts the timestamped and hex id forms', () => {

View file

@ -13,7 +13,7 @@ import {
useState
} from 'react'
import { ToolFallback } from '@/components/assistant-ui/tool-fallback'
import { ToolFallback } from '@/components/assistant-ui/tool/fallback'
import { Button } from '@/components/ui/button'
import { Kbd } from '@/components/ui/kbd'
import { Textarea } from '@/components/ui/textarea'
@ -25,7 +25,7 @@ import { $clarifyRequest, clearClarifyRequest } from '@/store/clarify'
import { $gateway } from '@/store/gateway'
import { notifyError } from '@/store/notifications'
import { selectMessageRunning } from './tool-fallback-model'
import { selectMessageRunning } from './tool/fallback-model'
interface ClarifyArgs {
question?: string

View file

@ -13,10 +13,10 @@ import {
contentHasVisibleText,
messageContentText,
pickPrimaryPreviewTarget
} from '@/components/assistant-ui/thread-content'
import { MESSAGE_PARTS_COMPONENTS } from '@/components/assistant-ui/thread-message-parts'
import { StreamStallIndicator } from '@/components/assistant-ui/thread-status'
import { formatMessageTimestamp } from '@/components/assistant-ui/thread-timestamp'
} from '@/components/assistant-ui/thread/content'
import { MESSAGE_PARTS_COMPONENTS } from '@/components/assistant-ui/thread/message-parts'
import { StreamStallIndicator } from '@/components/assistant-ui/thread/status'
import { formatMessageTimestamp } from '@/components/assistant-ui/thread/timestamp'
import { TooltipIconButton } from '@/components/assistant-ui/tooltip-icon-button'
import { PreviewAttachment } from '@/components/chat/preview-attachment'
import { Codicon } from '@/components/ui/codicon'

View file

@ -10,7 +10,7 @@ import { AssistantRuntimeProvider, type ThreadMessage, useExternalStoreRuntime }
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { Thread } from './thread'
import { Thread } from '.'
const createdAt = new Date('2026-06-01T00:00:00.000Z')

View file

@ -6,7 +6,7 @@ import {
messageContentText,
partText,
pickPrimaryPreviewTarget
} from './thread-content'
} from './content'
describe('partText', () => {
it('returns plain strings as-is', () => {

View file

@ -1,17 +1,17 @@
import { type FC, useCallback, useMemo, useState } from 'react'
import { AssistantMessage } from '@/components/assistant-ui/assistant-message'
import { SystemMessage } from '@/components/assistant-ui/system-message'
import { ThreadMessageList } from '@/components/assistant-ui/thread-list'
import { AssistantMessage } from '@/components/assistant-ui/thread/assistant-message'
import { ThreadMessageList } from '@/components/assistant-ui/thread/list'
import {
BackgroundResumeNotice,
CenteredThreadSpinner,
ResponseLoadingIndicator
} from '@/components/assistant-ui/thread-status'
import { ThreadTimeline } from '@/components/assistant-ui/thread-timeline'
import { type RestoreMessageTarget } from '@/components/assistant-ui/thread-types'
import { UserEditComposer } from '@/components/assistant-ui/user-edit-composer'
import { UserMessage } from '@/components/assistant-ui/user-message'
} from '@/components/assistant-ui/thread/status'
import { SystemMessage } from '@/components/assistant-ui/thread/system-message'
import { ThreadTimeline } from '@/components/assistant-ui/thread/timeline'
import { type RestoreMessageTarget } from '@/components/assistant-ui/thread/types'
import { UserEditComposer } from '@/components/assistant-ui/thread/user-edit-composer'
import { UserMessage } from '@/components/assistant-ui/thread/user-message'
import { Intro, type IntroProps } from '@/components/chat/intro'
import { ConfirmDialog } from '@/components/ui/confirm-dialog'
import type { HermesGateway } from '@/hermes'

View file

@ -24,7 +24,7 @@ import {
} from '@/store/thread-scroll'
import { isSecondaryWindow } from '@/store/windows'
import { MessageRenderBoundary } from './message-render-boundary'
import { MessageRenderBoundary } from '../message-render-boundary'
type ThreadMessageComponents = ComponentProps<typeof ThreadPrimitive.MessageByIndex>['components']

View file

@ -3,7 +3,7 @@ import { type ComponentProps, type FC, type ReactNode, useEffect, useRef, useSta
import { ClarifyTool } from '@/components/assistant-ui/clarify-tool'
import { MarkdownText, MarkdownTextContent } from '@/components/assistant-ui/markdown-text'
import { ToolFallback, ToolGroupSlot } from '@/components/assistant-ui/tool-fallback'
import { ToolFallback, ToolGroupSlot } from '@/components/assistant-ui/tool/fallback'
import { useElapsedSeconds } from '@/components/chat/activity-timer'
import { ActivityTimerText } from '@/components/chat/activity-timer-text'
import { DisclosureRow } from '@/components/chat/disclosure-row'

View file

@ -3,7 +3,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar
import { useEffect, useState } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Thread } from './thread'
import { Thread } from '.'
const createdAt = new Date('2026-05-01T00:00:00.000Z')

View file

@ -1,7 +1,7 @@
import { MessagePrimitive, useAuiState } from '@assistant-ui/react'
import { type FC } from 'react'
import { messageContentText } from '@/components/assistant-ui/thread-content'
import { messageContentText } from '@/components/assistant-ui/thread/content'
import { Codicon } from '@/components/ui/codicon'
import { LinkifiedText } from '@/lib/external-link'
import { cn } from '@/lib/utils'

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { activeTimelineIndex, deriveTimelineEntries, timelinePreview } from './thread-timeline-data'
import { activeTimelineIndex, deriveTimelineEntries, timelinePreview } from './timeline-data'
describe('timelinePreview', () => {
it('collapses whitespace to a single line', () => {

View file

@ -9,7 +9,7 @@ import {
deriveTimelineEntries,
type TimelineEntry,
type TimelineSourceMessage
} from './thread-timeline-data'
} from './timeline-data'
const MIN_ENTRIES = 4
const VIEWPORT = '[data-slot="aui_thread-viewport"]'

View file

@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
import { formatMessageTimestamp } from './thread-timestamp'
import { formatMessageTimestamp } from './timestamp'
const labels = {
today: (time: string) => `Today at ${time}`,

View file

@ -52,7 +52,7 @@ import {
USER_ACTION_ICON_BUTTON_CLASS,
USER_ACTION_ICON_SIZE,
USER_BUBBLE_BASE_CLASS
} from '@/components/assistant-ui/user-message'
} from '@/components/assistant-ui/thread/user-message'
import { Codicon } from '@/components/ui/codicon'
import type { HermesGateway } from '@/hermes'
import { useI18n } from '@/i18n'

View file

@ -13,7 +13,7 @@ import { describe, expect, it, vi } from 'vitest'
import { useIncrementalExternalStoreRuntime } from '@/lib/incremental-external-store-runtime'
import { Thread } from './thread'
import { Thread } from '.'
const createdAt = new Date('2026-05-01T00:00:00.000Z')

View file

@ -2,9 +2,9 @@ import { ActionBarPrimitive, BranchPickerPrimitive, MessagePrimitive, useAuiStat
import { type FC, type ReactNode, useCallback, useRef, useState } from 'react'
import { DirectiveContent } from '@/components/assistant-ui/directive-text'
import { messageAttachmentRefs, messageContentText } from '@/components/assistant-ui/thread-content'
import { type RestoreMessageTarget } from '@/components/assistant-ui/thread-types'
import { UserMessageText } from '@/components/assistant-ui/user-message-text'
import { messageAttachmentRefs, messageContentText } from '@/components/assistant-ui/thread/content'
import { type RestoreMessageTarget } from '@/components/assistant-ui/thread/types'
import { UserMessageText } from '@/components/assistant-ui/thread/user-message-text'
import { Codicon } from '@/components/ui/codicon'
import { useResizeObserver } from '@/hooks/use-resize-observer'
import { useI18n } from '@/i18n'

View file

@ -7,7 +7,7 @@ import { $activeSessionId } from '@/store/session'
import { clearDismissedToolRows } from '@/store/tool-dismiss'
import { $toolDisclosureStates } from '@/store/tool-view'
import { Thread } from './thread'
import { Thread } from '../thread'
// Regression coverage for the "approval must never be buried" bug. Tools now
// render as a flat list (no collapsible "N steps" group), so a pending tool's

View file

@ -6,8 +6,8 @@ import { $gateway } from '@/store/gateway'
import { $approvalRequest, clearAllPrompts, setApprovalRequest } from '@/store/prompts'
import { $activeSessionId } from '@/store/session'
import { PendingApprovalFallback, PendingToolApproval } from './tool-approval'
import type { ToolPart } from './tool-fallback-model'
import { PendingApprovalFallback, PendingToolApproval } from './approval'
import type { ToolPart } from './fallback-model'
// Radix's DropdownMenu touches pointer-capture + scrollIntoView, which jsdom
// doesn't implement; stub them so the menu can open in tests.

View file

@ -27,7 +27,7 @@ import {
registerApprovalInlineAnchor
} from '@/store/prompts'
import type { ToolPart } from './tool-fallback-model'
import type { ToolPart } from './fallback-model'
// Inline approval control. Rendered as a compact button strip
// under the pending tool row that raised the approval (the row already shows

View file

@ -9,7 +9,7 @@ import {
inlineDiffFromResult,
MAX_TOOL_RENDER_CHARS,
type ToolPart
} from './tool-fallback-model'
} from './fallback-model'
const part = (overrides: Partial<ToolPart>): ToolPart => ({
args: {},

View file

@ -30,7 +30,7 @@ import { $toolInlineDiffs } from '@/store/tool-diffs'
import { $toolRowDismissed, dismissToolRow } from '@/store/tool-dismiss'
import { $toolDisclosureOpen, $toolViewMode, setToolDisclosureOpen } from '@/store/tool-view'
import { PendingToolApproval } from './tool-approval'
import { PendingToolApproval } from './approval'
import {
buildToolView,
clampForDisplay,
@ -48,7 +48,7 @@ import {
toolPartDisclosureId,
type ToolStatus,
type ToolTitleAction
} from './tool-fallback-model'
} from './fallback-model'
// `true` when a ToolEntry is rendered inside an embedding wrapper that owns
// the per-row chrome (timer / preview). The flat ToolGroupSlot sets this

View file

@ -3,7 +3,7 @@
import { useStore } from '@nanostores/react'
import { type FormEvent, useCallback, useEffect, useState } from 'react'
import { PendingApprovalFallback } from '@/components/assistant-ui/tool-approval'
import { PendingApprovalFallback } from '@/components/assistant-ui/tool/approval'
import { Button } from '@/components/ui/button'
import {
Dialog,