Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/fork-from-here-mobile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-ax/agents-server-ui': patch
---

Make the "Fork from here" affordance work in the mobile Expo DOM embed. Two pieces: (1) wire the fork-anchor map in `ChatLogView` (the view the mobile embed mounts) so `EntityTimeline` actually receives the per-row callbacks; (2) add a `:global(html[data-electric-mobile-dom='true']) .forkButton { opacity: 1 }` rule in `UserMessage.module.css` so the button is visible without a hover/tap (touch devices don't fire `:hover`). The fork POST and post-fork navigation already route through the existing `serverFetch` + `onRequestOpenEntity` callback, so no changes to the mobile package itself.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@
opacity: 1;
}

:global(html[data-electric-mobile-dom='true']) .forkButton {
opacity: 1;
}

.forkButton:hover {
background: var(--ds-gray-a4);
}
Expand Down
32 changes: 31 additions & 1 deletion packages/agents-server-ui/src/components/views/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ export function ChatLogView({
inlineQueuedMessages?: Array<OptimisticInboxMessage>
}): React.ReactElement {
const connectUrl = isSpawning ? null : entityUrl
const { timelineRows, pendingInbox, entities, loading, error } =
const { timelineRows, pendingInbox, entities, db, loading, error } =
useEntityTimeline(baseUrl || null, connectUrl)
const { forkEntity } = useElectricAgents()
const navigate = useNavigate()
const processedInboxKeys = useMemo(
() =>
Expand Down Expand Up @@ -103,6 +104,34 @@ export function ChatLogView({
}
}, [error, navigate, isSpawning])

const forkFromHereByInboxKey = useMemo(() => {
if (!forkEntity || !connectUrl || !db) return undefined
const runOffsets = db.collections.runs.__electricRowOffsets
if (!runOffsets) return undefined
const map = new Map<string, () => void>()
let anchor: EventPointer | null = null
for (const row of visibleRows) {
if (row.run && row.run.status === `completed`) {
const pointer = runOffsets.get(row.run.key)
if (pointer) anchor = pointer
}
if (row.inbox && anchor) {
const capturedAnchor = anchor
map.set(row.$key, () => {
void forkEntity(connectUrl, { pointer: capturedAnchor })
.then((res) =>
navigate({
to: `/entity/$`,
params: { _splat: res.url.replace(/^\//, ``) },
})
)
.catch(() => {})
})
}
}
return map
}, [visibleRows, db, forkEntity, connectUrl, navigate])

return (
<EntityTimeline
rows={visibleRows}
Expand All @@ -115,6 +144,7 @@ export function ChatLogView({
entityUrl={connectUrl}
entities={entities}
scrollToBottomSignal={scrollToBottomSignal}
forkFromHereByInboxKey={forkFromHereByInboxKey}
/>
)
}
Expand Down
Loading