Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/agents-mobile-comments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@electric-ax/agents-mobile": patch
"@electric-ax/agents-server-ui": patch
---

Bring session comments to mobile (desktop parity): a comment mode in the native composer, tap-a-row-to-reply forwarded from the embed timeline, and a dedicated comments-only view reachable from the session menu. No server API changes.
38 changes: 34 additions & 4 deletions packages/agents-mobile/app/session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ import SessionChatLogDomEmbedModule from '@electric-ax/agents-server-ui/src/embe
import SessionStateInspectorDomEmbedModule from '@electric-ax/agents-server-ui/src/embed/SessionStateInspectorDomEmbed'
import { getActiveServerHeadersSnapshot } from '@electric-ax/agents-server-ui/src/lib/auth-fetch'
import type { OptimisticInboxMessage } from '@electric-ax/agents-server-ui/src/lib/sendMessage'
import type {
EntityTimelineCommentRow,
SelectedCommentTarget,
} from '@electric-ax/agents-server-ui/src/lib/comments'

const HEADER_HEIGHT = 44

Expand All @@ -34,8 +38,11 @@ type SessionDomEmbedProps = {
theme: `light` | `dark`
scrollToBottomSignal?: number
inlineQueuedMessages?: Array<OptimisticInboxMessage>
inlineComments?: Array<EntityTimelineCommentRow>
bottomInset?: number
commentsOnly?: boolean
onRequestOpenEntity: (entityUrl: string) => Promise<void>
onRequestReplyToComment?: (target: SelectedCommentTarget) => void
style?: StyleProp<ViewStyle>
matchContents?: boolean
serverHeaders?: { url: string; headers: Record<string, string> } | null
Expand Down Expand Up @@ -81,19 +88,35 @@ function SessionRouteInner({
const [inlineQueuedMessages, setInlineQueuedMessages] = useState<
Array<OptimisticInboxMessage>
>([])
const [inlineComments, setInlineComments] = useState<
Array<EntityTimelineCommentRow>
>([])
const [replyTarget, setReplyTarget] = useState<SelectedCommentTarget | null>(
null
)

const entityUrl = Array.isArray(params.entityUrl)
? params.entityUrl[0]
: (params.entityUrl ?? ``)
const view = params.view === `state-explorer` ? `state-explorer` : `chat`
const view: EmbedViewId =
params.view === `state-explorer`
? `state-explorer`
: params.view === `comments`
? `comments`
: `chat`

// Drop any pending reply target when the session or view changes.
useEffect(() => {
setReplyTarget(null)
}, [entityUrl, view])

// Read once per render — the DOM embed receives this as a prop and
// re-registers it on its side of the JS-context boundary.
const serverHeaders = getActiveServerHeadersSnapshot()

const embedTop = insets.top + HEADER_HEIGHT
const composerInset =
view === `chat`
view !== `state-explorer`
? Math.max(0, chatComposerHeight + keyboardInset - CHAT_COMPOSER_OVERLAP)
: 0
const embedFrame = useMemo(
Expand Down Expand Up @@ -141,7 +164,7 @@ function SessionRouteInner({
{ backgroundColor: tokens.bg },
]}
>
{view === `chat` ? (
{view !== `state-explorer` ? (
<SessionChatLogDomEmbed
style={[styles.domEmbedWeb, embedSize]}
matchContents={false}
Expand All @@ -150,9 +173,12 @@ function SessionRouteInner({
theme={scheme}
scrollToBottomSignal={chatLogScrollSignal}
inlineQueuedMessages={inlineQueuedMessages}
inlineComments={inlineComments}
bottomInset={composerInset}
commentsOnly={view === `comments`}
serverHeaders={serverHeaders}
onRequestOpenEntity={async (target) => openSession(target)}
onRequestReplyToComment={(target) => setReplyTarget(target)}
dom={domOptions(styles, embedSize, tokens.bg)}
/>
) : (
Expand All @@ -169,17 +195,21 @@ function SessionRouteInner({
)}
</View>

{view === `chat` ? (
{view !== `state-explorer` ? (
<ChatSessionScreen
entityUrl={entityUrl}
view={view}
onBack={goBack}
onSetView={setView}
onOpenEntity={openSession}
onOpenStateSource={openStateSource}
onComposerHeightChange={setChatComposerHeight}
onSendMessage={() => setChatLogScrollSignal(Date.now())}
onInlineQueuedMessagesChange={setInlineQueuedMessages}
onInlineCommentsChange={setInlineComments}
onShare={openShare}
commentTarget={replyTarget}
onClearCommentTarget={() => setReplyTarget(null)}
/>
) : (
<StateInspectorSessionScreen
Expand Down
3 changes: 3 additions & 0 deletions packages/agents-mobile/src/components/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export type IconName =
| `info`
| `swap`
| `chat`
| `comment`
| `database`
| `radio`
| `arrow-up`
Expand Down Expand Up @@ -69,6 +70,8 @@ const PATHS: Record<IconName, string> = {
info: `M12 8v.01M11 12h1v4h1M12 21a9 9 0 1 1 0-18 9 9 0 0 1 0 18Z`,
swap: `M7 4l-3 3 3 3M4 7h13M17 14l3 3-3 3M20 17H7`,
chat: `M4 4h16v12H8l-4 4Z`,
// Lucide `message-square` — distinct from the simpler `chat` bubble.
comment: `M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z`,
database: `M5 5c0-1.1 3.1-2 7-2s7 .9 7 2v14c0 1.1-3.1 2-7 2s-7-.9-7-2V5ZM5 12c0 1.1 3.1 2 7 2s7-.9 7-2`,
radio: `M4.9 19.1a10 10 0 0 1 0-14.2M7.8 16.2a6 6 0 0 1 0-8.4M10.6 13.4a2 2 0 0 1 0-2.8M14 12h.01M16.2 7.8a6 6 0 0 1 0 8.4M19.1 4.9a10 10 0 0 1 0 14.2`,
'arrow-up': `M12 19V5M5 12l7-7 7 7`,
Expand Down
18 changes: 18 additions & 0 deletions packages/agents-mobile/src/components/SessionMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export function SessionMenu({
entity,
view,
onSetView,
commentsEnabled = false,
signalError,
onSignal,
onStopImmediately,
Expand All @@ -129,6 +130,8 @@ export function SessionMenu({
entity: ElectricEntity | null
view: EmbedViewId
onSetView: (view: EmbedViewId) => void
/** Show the Comments view entry — entity type declares the comments collection. */
commentsEnabled?: boolean
signalError?: string | null
onSignal?: (signal: EntitySignal) => void
onStopImmediately?: () => void
Expand Down Expand Up @@ -341,6 +344,21 @@ export function SessionMenu({
active={view === `chat`}
onPress={() => handlePick(`chat`)}
/>
{commentsEnabled && (
<BottomSheetItem
label="Comments"
icon={
<Icon
name="comment"
size={18}
color={tokens.text2}
strokeWidth={2}
/>
}
active={view === `comments`}
onPress={() => handlePick(`comments`)}
/>
)}
<BottomSheetItem
label="State explorer"
icon={
Expand Down
2 changes: 1 addition & 1 deletion packages/agents-mobile/src/lib/embedView.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type EmbedViewId = `chat` | `state-explorer`
export type EmbedViewId = `chat` | `comments` | `state-explorer`
Loading