Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
627fc67
Simplify workspace navigation and session state handling
tonyfresher Sep 24, 2026
0e77f7b
Refine chat selector and view submission state
tonyfresher Sep 25, 2026
584b392
Show pinned chat title and simplify pin toggle
tonyfresher Sep 25, 2026
f306014
Split new chat empty states by tab
tonyfresher Sep 25, 2026
08bfcbb
Merge remote-tracking branch 'origin/main' into tinkering/view-chats
tonyfresher Oct 5, 2026
5efba8d
Merge remote-tracking branch 'origin/main' into tinkering/view-chats
tonyfresher Oct 5, 2026
f5297a6
Keep CLI view handoffs selected in Collab tabs
tonyfresher Oct 5, 2026
81f1dc7
Move chat pin controls into the selector
tonyfresher Oct 5, 2026
9a2a5c5
Update workspace skill for pending view creation
tonyfresher Oct 5, 2026
68a21b8
Open new views after CLI creation
tonyfresher Oct 5, 2026
3c37df0
Refresh view data before opening newly created views
tonyfresher Oct 5, 2026
f6f774e
Preserve the selected model when forking Codex view chats
tonyfresher Oct 5, 2026
560ee11
Separate pin and unpin controls in the chat selector
tonyfresher Oct 5, 2026
c63ea49
Show inherited chat context as a linked card
tonyfresher Oct 5, 2026
1bf08fc
Allow wider pinned chat titles
tonyfresher Oct 5, 2026
d123e6c
Share source settings across chat forks
tonyfresher Oct 5, 2026
a001c60
Allow chat popup content to overflow
tonyfresher Oct 5, 2026
68289f9
Allow resizing chat popups and remember their width
tonyfresher Oct 5, 2026
15da0c8
Archive chats when views are deleted or discarded
tonyfresher Oct 6, 2026
b366d2c
Clear archived chats from every Collab browser tab
tonyfresher Oct 7, 2026
7600a59
Simplify view context and make --from-session optional
tonyfresher Oct 7, 2026
9412fc4
Keep view build state tied to its execution chat
tonyfresher Oct 7, 2026
6986de3
Simplify applet and session context
tonyfresher Oct 7, 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
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/api/app-ui-keys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const appUiKeys = {
all: ['app-ui'] as const,
selectedSession: (workspaceId: string) => ['app-ui', 'selected-session', workspaceId] as const
sessionSelection: (workspaceId: string) => ['app-ui', 'session-selection', workspaceId] as const
}
4 changes: 1 addition & 3 deletions client/api/workspace-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ export const workspaceKeys = {
widgets: (id: string) => ['workspaces', 'widgets', id] as const,
appletThumbnails: (id: string) => ['workspaces', 'applet-thumbnails', id] as const,
views: (id: string) => ['workspaces', 'views', id] as const,
viewBuilders: (id: string) => ['workspaces', 'view-builders', id] as const,
sessions: (id: string) => ['workspaces', 'sessions', id] as const,
events: (id: string, sessionId: string) => ['workspaces', 'events', id, sessionId] as const,
sessionConfig: (id: string, sessionId: string) =>
['workspaces', 'sessionConfig', id, sessionId] as const,
session: (id: string, sessionId: string) => ['workspaces', 'session', id, sessionId] as const,
mcp: (id: string) => ['workspaces', 'mcp', id] as const,
agent: (id: string) => ['workspaces', 'agent', id] as const,
skills: (id: string) => ['workspaces', 'skills', id] as const,
Expand Down
10 changes: 2 additions & 8 deletions client/app/routes/WorkspaceRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
useWorkspaceLayoutCtx
} from '@/client/features/workspace/WorkspaceLayoutContext'
import { WorkspaceScreen } from '@/client/features/workspace/WorkspaceScreen'
import { useViewBuilders, useViews } from '@/client/features/views/api'
import { useViews } from '@/client/features/views/api'
import { useWidgets } from '@/client/features/overview/api'
import { useWorkspaceEvent } from '@/client/runtime/useWorkspaceEvents'

Expand Down Expand Up @@ -44,7 +44,6 @@ function WorkspaceContent({ id }: WorkspaceRouteProps) {
const { isLoading: layoutLoading } = useWorkspaceLayoutCtx()
const widgets = useWidgets(id)
const views = useViews(id)
const builders = useViewBuilders(id)
useAppletCacheInvalidation()

useWorkspaceEvent(event => {
Expand All @@ -62,11 +61,7 @@ function WorkspaceContent({ id }: WorkspaceRouteProps) {
})

const fresh =
layoutLoading ||
selectedSessionId === undefined ||
widgets.isLoading ||
views.isLoading ||
builders.isLoading
layoutLoading || selectedSessionId === undefined || widgets.isLoading || views.isLoading

return (
<SidebarLayout>
Expand All @@ -81,7 +76,6 @@ function WorkspaceContent({ id }: WorkspaceRouteProps) {
key={collabEnabled ? 'browser-tab' : 'shared'}
widgets={widgets.data}
views={views.data ?? []}
builders={builders.data ?? []}
/>
)}
</SidebarLayout>
Expand Down
21 changes: 11 additions & 10 deletions client/features/applets/applet-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,20 +115,21 @@ describe('bridge validation', () => {
})

describe('sendChatMessage validation', () => {
test('trims the message and stamps the applet source the host attached', () => {
test('trims the message and stamps the applet identity and attachment source', () => {
const ws = `ws-${crypto.randomUUID()}`
const { calls } = subscribeChat(ws)
const { bridge } = appletRuntime(ws).connect(WIDGET)

bridge.sendChatMessage({
message: ' Chase order A-1042 ',
applet: { kind: 'view', id: 'forged' },
attachments: [{ type: 'text', label: 'Order', text: 'A-1042', source: 'forged' }]
})

expect(calls).toEqual([
{
message: 'Chase order A-1042',
source: 'widget:clock',
applet: { kind: 'widget', id: 'clock' },
attachments: [{ type: 'text', label: 'Order', text: 'A-1042', source: 'widget:clock' }]
}
])
Expand All @@ -149,18 +150,18 @@ describe('sendChatMessage validation', () => {
expect(calls).toEqual([
{
message: 'Legacy',
source: 'widget:clock',
applet: { kind: 'widget', id: 'clock' },
attachments: [
{ type: 'text', label: 'Context', text: '{"order":"1042"}', source: 'widget:clock' }
]
},
{ message: 'Message only', source: 'widget:clock', attachments: [] },
{ message: 'Message only', applet: { kind: 'widget', id: 'clock' }, attachments: [] },
{
message: 'Array context',
source: 'widget:clock',
applet: { kind: 'widget', id: 'clock' },
attachments: [{ type: 'text', label: 'Context', text: '[]', source: 'widget:clock' }]
},
{ message: 'Object', source: 'widget:clock', attachments: [] }
{ message: 'Object', applet: { kind: 'widget', id: 'clock' }, attachments: [] }
])
})

Expand Down Expand Up @@ -310,10 +311,10 @@ describe('sendChatMessage rate limiting', () => {
view.bridge.sendChatMessage({ message: 'Sync now' })
widget.bridge.sendChatMessage({ message: 'Something else' })

expect(calls.map(c => [c.source, c.message])).toEqual([
['widget:clock', 'Sync now'],
['view:board', 'Sync now'],
['widget:clock', 'Something else']
expect(calls.map(c => [c.applet, c.message])).toEqual([
[{ kind: 'widget', id: 'clock' }, 'Sync now'],
[{ kind: 'view', id: 'board' }, 'Sync now'],
[{ kind: 'widget', id: 'clock' }, 'Something else']
])
})

Expand Down
11 changes: 8 additions & 3 deletions client/features/applets/applet-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type {
AttachmentInput,
AttachmentOrigin
} from '@/lib/types'
import type { AppletContext } from '@/lib/moi-context'
import { isWorkspaceAttachmentPath, MAX_UPLOAD_BYTES } from '@/lib/message-attachments'
import { useEffect } from 'react'

Expand All @@ -47,12 +48,12 @@ export type AppletIdentity = { kind: AppletKind; name: string }

export const appletSource = ({ kind, name }: AppletIdentity): string => `${kind}:${name}`

// A chat message fired from applet UI. `source` is stamped from the identity
// A chat message fired from applet UI. `applet` is stamped from the identity
// the bridge was attached with, so an applet can neither omit it nor claim to
// be another applet.
export type AppletChatMessage = {
message: string
source: string
applet: AppletContext
attachments: (AttachmentInput & AttachmentOrigin)[]
}

Expand Down Expand Up @@ -191,7 +192,11 @@ function createRuntime(workspaceId: string) {
snapshotAttachmentInput(item, source)
)
if (!admitChatMessage(identity, source, message)) return
emitter.emit('sendChatMessage', { message, source, attachments })
emitter.emit('sendChatMessage', {
message,
applet: { kind: identity.kind, id: identity.name },
attachments
})
} catch (error) {
const message = errorMessage(error)
toast.add({ title: 'Couldn’t send message', description: message, type: 'error' })
Expand Down
Loading
Loading