Conversation
The chat transcript, composer, activity feed and model controls are shared between the notebook assistant and the upcoming AI Mode overlay, so they live one level up instead of under Notebook/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
useNotebookChat, useNotebookChatList and useNotebookChatSocket took a noteId and called NotebookChatService directly. They now take a ChatTransport, an interface over the six REST calls and the socket URL, with notebookChatTransport(noteId) and assistantChatTransport() as the two implementations. The notebook panel builds its transport once per note, so its behaviour is unchanged. Adds AssistantChatService against /api/research_ai/assistant/chats/, the ASSISTANT_CHAT socket route, and the optional notes field the assistant surface returns on a chat. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
AIModeProvider owns the overlay: ?ai=1 opens it and ?ai=1&aiChat=<id> selects a conversation, so a reload lands on the same place and any client-side navigation closes it by dropping the params. The overlay body is lazy-loaded, sits at z-9500 below modals and tooltips, locks body scroll, and closes on Esc unless a real modal is showing. The sidebar gains an Assistant item that toggles the overlay in place. The three panes are placeholders for now. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The left pane lists assistant conversations newest first with a spinner on running turns, a Document badge for chats whose detail has been loaded, and rename behind a row menu. The middle pane composes ChatTranscript and ChatComposer with the shared model controls, an empty state with static starter prompts, and autoscroll that stays put once the reader scrolls up. A conversation is only created on the first send. The list polls every 5s while any row reports an active turn and refreshes when the open chat's turn status or title changes. Below the tablet breakpoint the list opens in a bottom drawer. Send outcomes now carry the error code and body, so a 429 shows the budget reset time and a 409 usage_work_in_progress gets its own copy. The list hook exposes the server's detail on access failures, rendered verbatim instead of hiding the surface. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The right pane opens by itself when a conversation gains a note (notes[0] on the representation, or a succeeded create_note in the activity) and renders the note read-only in the block editor. It refetches when either signal reports a newer version: a succeeded edit_note carrying note_version_id, or a note_version_created frame on the note's socket. Version ids are compared, never assumed ordered, so duplicate and reordered events are harmless. While the model composes an edit_note the stream's tool_draft prose is appended below the settled content as the section being written; when a turn runs with no draft streaming, an in-progress row shows phase.label so the page never sits frozen. The badge counts level 1 and 2 headings plus one for an open draft. An Open in notebook link deep-links via the note's organization slug. Below the tablet breakpoint the pane is a bottom drawer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…azily Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mirrors the notebook's two writing presets so a funder can start a request for proposals and a researcher a proposal from the empty state, alongside the research and funding-search starters. Each prefills the composer. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The notebook chat panel owned ~400 lines that turn a newer agent-authored note version into an in-note diff review: hearing versions from the note socket, the chat's activity and a reconnect probe; fetching the pinned version; building or folding the overlay; persisting the accept or reject projection; and the locks and epochs guarding all of it across note switches. That logic now lives in useNoteAgentReview, keyed on the note, its editor, the loaded version and the open chat, so the upcoming AI Mode document pane can run the same review over its own editor. The failure banner moves to NoteReviewBanner for the same reason. The panel keeps the controls placement and the onReviewChange handoff to the note page; behaviour is unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ts read-only BlockEditor gains an autofocus prop (default: editable) so a second editor on the page, like the AI Mode document, doesn't steal focus from the chat composer. useBlockEditor no longer assigns window.editor, which clobbered the notebook's editor whenever another editor mounted; nothing read it. The text bubble menu no longer shows while the editor is read-only via setEditable(false). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The document pane now mounts the real editor editable, autosaving through useUpdateNote with the review's persistable projection, and runs useNoteAgentReview over it: every version the assistant writes is spliced into the live editor as highlighted insertions and struck removals with accept/reject controls, exactly as in the notebook. The editor is read-only while a turn is drafting or working, so a user edit can't make the assistant's next edit_note stale mid-turn, and editable once settled. The section badge counts headings in the live editor document. The note is loaded once per note for the editor's initial content; later versions reach it through the review rather than a reload. Below the tablet breakpoint the pane mounts in the drawer only, read-only, so a note never has two editors open at once. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A card seated under the turn that created the document (or trailing the transcript when that turn's activity isn't loaded) names it, shows whether the assistant is still writing, and opens it: the pane beside the chat on desktop, the swipeable drawer on mobile. The pane still opens by itself on desktop when a conversation gains a note; on mobile it never does, the card is the way in. ChatTranscript gains a renderExecutionExtra slot for host-specific content under a turn, and the tool-call activity type declares the note_id and note_title a succeeded create_note carries. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The drawer portals to the body at z-1001, which put it behind the AI Mode overlay at z-9500 — opening the conversation list or the document on mobile showed nothing. The drawer takes a zIndex now (default unchanged), and AI Mode opens its drawers at 9600, below BaseModal. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-state copy tiptap re-applies the editable option it was created with, so toggling editability with setEditable was undone on the next render and the document stayed editable while the assistant was writing. BlockEditor gains a locked prop that folds into the option itself and keeps the editor instance; the document pane locks while a turn is drafting or working. The pane and the transcript card stop claiming nothing is written once the chat's activity reports a succeeded edit_note, the section badge ignores empty headings, and the draft section no longer nests a div in a paragraph. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The overlay portals to the body, takes focus on open, and marks the other top-level elements inert while it is open, so nothing behind it — a notebook editor that autofocuses after its note loads, say — can keep keyboard focus and swallow keys, including the Escape meant for the overlay. Layers that mount later (menus, tooltips, modals) stay live. The overlay's own drawers render inside it via a new SwipeableDrawer container prop. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mounting the editor on a note the assistant just created dispatched a document-changing transaction (UniqueID stamping the schema's empty heading), which fired update and autosaved an editor-authored first version. The assistant's first edit_note then arrived with a null expected version, was rejected as stale, and had to re-read and retry — the red X on the first Edited the note row. The pane now skips saves while the note has no written version and the document is empty. Unlocking at the end of a turn also saved a version, because tiptap's setEditable emits update by default; the editor hook passes emitUpdate false, since editability isn't a content change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The app router keeps useSearchParams in sync with native history calls, and unlike router.replace this neither re-fetches nor re-renders the page behind the overlay on every chat switch. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…graph-first notes The document pane takes the larger share once open (58%, 520 to 980px) and the chat narrows to a column beside it. Conversation rows show only the title and timestamp; the preview and Document badge are gone. Opening a settled conversation crashed: the editable editor's custom document schema requires a leading heading, and an assistant-written note that opens with a paragraph made UniqueID's id-stamping transaction throw on load. BlockEditor gains a requireTitle prop (default true, so the notebook is unchanged) and the AI Mode pane turns it off. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ument Both side panes now drag: the conversation list between 200 and 440px, the document from 55% of the viewport down to 420px or up to whatever leaves the chat its 360px minimum. Widths persist per browser. The logic lives in a new generic useResizableWidth hook, anchored to either viewport edge; the notebook's useAgentChatWidth becomes a thin wrapper over it with identical behaviour. ResizeHandle is the shared handle with pointer drag and arrow-key nudging. The document pane drops its gray gutter and card: a white surface with the page in a centered column. Opening a conversation used to write an editor-authored version and rename the note to its first section heading: the editor's creation-phase id-stamping transaction reached the autosave before the instance was handed to the pane, and the notebook's title derivation took the first heading. The pane now ignores updates from an instance it doesn't hold yet and never derives a title, since the assistant names the note. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… gutter fixes useJumpToLatest owns sticky-bottom transcript scrolling for both the AI Mode chat and the notebook panel; the AI Mode chat shows a floating jump control once the reader scrolls up. ConversationMenu (rename, confirmed delete) and ConversationTitleField are shared by the sidebar rows and the chat header; delete goes through a new transport method against the assistant surface's DELETE endpoint and lands on the new-conversation screen when it was the open chat. Sidebar rows: gray selected state, 13px titles. The document editor keeps the 64px gutter its block handle renders into and the pane clips its own overflow, so the handle no longer floats over the chat. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The composer clears the moment a message is sent and only refills if the send fails; a rename shows at once in the sidebar and the chat header, retires when the listing confirms it, and rolls back if the request fails. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Skeleton placeholders replace the loader circles in the conversation list, the chat and the document pane, aligned to the real content and fading into it. The chat header takes its title from the listing while the chat loads, so a refresh never flashes Untitled; the document toggle is a right-panel icon; the composer drops its own top border under the pane's separator. The document pane loses its title bar and lets the chat header toggle it. Two guards protect notes from a mounting editor: an empty document is never autosaved from this surface, and an editor that comes up empty for a note with text is treated as a failed load and locked, after a note was found overwritten with an empty version. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The publishing form read four things from NotebookContext: the note, the editor, and the note's two details-saver functions. PublishingHost is a narrow context carrying exactly those (plus an optional default work type); NotebookProvider supplies it from state it already holds, and the form and the published-status strip read it. The notebook is unchanged. The AI Mode document pane hosts the same form under a Document / Publish tab pair, supplying the host from the loaded note, the pane's editor and a details saver for the note. The editor stays mounted behind the form. The pane's width floor widens while the form shows, and the conversation's opening message preselects proposal or RFP for a note with no work type. Modal and ConfirmPublishModal now stack at 9999, above the overlay. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, gray sidebar The overlay is now called ResearchHub AI. The new-conversation screen greets the user by first name over a centered composer, with three equal starter cards beneath it that mirror the sidebar's Publish menu (request for proposal, research proposal) plus a funding search. Picking a card starts the conversation with its message rather than prefilling the box; sendText is the shared path for cards and the composer alike. The sidebar sits on gray-100 with the selected row, hover and skeleton bars adjusted to read on it; the composer area matches the chat's gray-50. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 99ebfaeaf9
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| chatId != null && (chat.access === 'not_found' || chat.access === 'unauthorized'); | ||
| const composerDisabled = | ||
| listBlocked || chatUnavailable || (chatId != null && chat.access === 'loading'); | ||
| const composerBusy = chat.isBusy || creatingChat; |
There was a problem hiding this comment.
Wait for model selection before enabling send
The composer becomes sendable while modelSelection.status === 'loading'. In that state useAgentModelSelection returns an empty request, so a user who sends immediately after opening AI Mode silently starts and permanently pins the conversation to the server-default model instead of their stored selection. This busy guard should include model loading, as the notebook panel's equivalent guard already does.
Useful? React with 👍 / 👎.
| editable, | ||
| autofocus, | ||
| locked, | ||
| requireTitle, |
There was a problem hiding this comment.
Hide editor menus while the document is locked
Passing locked only changes Tiptap's editable flag, while BlockEditor continues mounting all mutation-capable menus whenever editable is true. During an active assistant turn, existing selections can therefore leave table, column, image, or block controls available, and those controls dispatch commands programmatically despite DOM editing being disabled, allowing the document to change while it is supposed to be locked and potentially making the assistant edit stale. The menus should also be gated by !locked or individually check editor.isEditable.
Useful? React with 👍 / 👎.
| const handleKeyDown = (event: KeyboardEvent) => { | ||
| if (event.key !== 'Escape' || event.defaultPrevented) return; | ||
| if (isForeignDialogOpen()) return; | ||
| close(); | ||
| }; |
There was a problem hiding this comment.
Let an open drawer consume Escape
When either mobile drawer is open, pressing Escape closes the entire AI Mode overlay as well as the drawer. SwipeableDrawer handles Escape without preventing the event, and this document-level listener ignores dialogs contained inside the overlay, so event.defaultPrevented remains false and close() always runs. Detect an open internal drawer here or have the drawer claim the Escape event so the first press only dismisses the topmost layer.
Useful? React with 👍 / 👎.
The delete confirmation looks up the notes the conversation created when it opens and, if there are any, adds an unchecked option to delete the document too; otherwise it stays in the notebook. The option maps to the assistant surface's DELETE ...?delete_notes=true, which soft-deletes the note the way the notebook does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Chat prose, the user bubble, and the composer move from 14px to a new 15px text-md token; the composer's focus ring becomes a gray border. Assistant text now types out: a clock-paced reveal hook keys progress per turn so streamed narration and the settled answer it becomes share one pass instead of typing twice, and the transcript follows the growing text via a resize observer. Only turns watched live animate. The shared agent-chat layer drops its notebook naming, since both the notebook chat and the research assistant use it: types/agentChat, types/agentModels, hooks/useAgentChat, hooks/useAgentChatSocket, and the AgentChat/AgentChatListItem types. The note-scoped service, transport, and socket route keep their notebook names. AgentChatPanel moves under components/Notebook, where its dependencies live, leaving components/AgentChat as the shared kit. The model preference storage key is renamed with it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # components/AgentChat/CreditMeter.tsx # components/Notebook/AgentChatPanel.tsx # hooks/useAgentChat.ts # hooks/useAgentModelSelection.ts # hooks/useAgentModels.ts # types/agentModels.ts
The model picker becomes a BaseMenu, the Radix dropdown the rest of the app uses, so keyboard navigation, focus return, outside click, and Escape come from Radix instead of a hand-rolled outside-click hook and document listener. The effort panel is not a menu — its pills and slider are adjusted in place — so it moves onto a new shared Popover wrapper in components/ui, a thin layer over the Radix popover with the chrome three feature components already hand-roll. The trigger chip forwards its ref and reads its open state from Radix's data-state. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Five sort controls each rebuilt the same menu: find the selected label, draw a trigger, list the options as radio rows, close on pick. SortMenu now does that once, on BaseMenu's new radio group and radio item, and the journal, peer-review, grant, and proposal feeds use it. MenuTrigger is the icon-label-chevron button those menus and the composer share, with chip, inline, and touch variants; ChoicePills and FieldLabel take the pill row and the small uppercase caption out of ModelControls. The chat picker moves onto BaseMenu, and the three moderator popovers onto the shared PopoverContent. The feed sort dropdown is replaced at its two call sites; the inline dropdown and the nav pill dropdown had no consumers and are deleted. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Search results live in the top-bar modal now; the /search page was an artifact. Gone with it: the full-search hook, the sort controls and empty state, the full-search service call with its highlight and transform helpers, the response and filter types, the two sanitizer libs, and the SortDropdown whose only consumer was the page's sort control. The route's tendrils are unwound: the modal no longer prefetches, pushes to, or restores a query from the page; the top-bar button no longer echoes a page query; the topbar route table, the feed content component, and the feed-source analytics hook no longer know the route. The feed's search-highlight plumbing was fed only by the deleted service, so the searchMetadata field, the highlights prop threaded through the entry and item components, and the sanitized-HTML rendering branches go too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fort panel The backend admits only site moderators and hub editors to the research assistant, so the sidebar entry now shows only for them, and on mobile they get an Assistant tab in the Peer Review slot that toggles the overlay in place. Three call sites each spelled that audience check differently; isHubEditorOrModerator in utils/permissions is now the one definition, and the note editor and funding home tabs use it too. Once a chat's effort is locked, the effort panel only says so. Thinking and temperature depend on the effort the chat runs at, and offering them under a lock read as a control that half worked. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Resolve NoteEditorLayout.tsx in favour of main: the notebook assistant is shown to all signed-in users, so the isHubEditorOrModerator import is gone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|



No description provided.