From fcbb8437b2dac614a8df601112d9b99a47029f43 Mon Sep 17 00:00:00 2001 From: Kobe Attias Date: Mon, 21 Sep 2026 21:10:20 -0400 Subject: [PATCH] Replace the AI Mode document tabs with detail blocks and header publish controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The document pane's Document / Publish tabs become a strip of detail blocks — Applying to, Funding goal, Authors, Cover image for a proposal; Funding amount, Short description, Contacts, Cover image for an RFP — each opening a popover with just its section of the form (or, for the RFP picker, the existing modal). The strip shows as many blocks as the pane is wide enough for, in a fixed order, and folds the rest into the "All details" block, which opens the full form and says how many it holds (hooks/useFittingCount). The workspace header gains the document's title, a "N remaining" pill and a Publish button that stays disabled until nothing required is missing; a published document shows a Published pill and an Update button. The form now lives as long as the note does, so switching views no longer rehydrates it from stale content. Below the tablet breakpoint the drawer trades the strip for a Document | Details switch, with the remaining count on Details and the publish controls inline. Supporting changes: completion.ts derives what is missing from the zod schema without touching form errors; PublishStatusPill; PublishButton gains requireComplete and reads Update for published works; the funding section is split into FundingOpportunityField, FundingGoalField and a FundingGoalSection with a shared useSelectedGrant; sections take a className so they render bare in a popover; PublishingForm gains showFooter; NotebookTabs loses its labels prop and the header its "Esc to close" hint. Verified with type-check, eslint and prettier only. Co-Authored-By: Claude Fable 5.1 --- components/AIMode/AIModeOverlay.tsx | 38 ++- components/AIMode/DocumentPane.tsx | 265 ++++++++++++------ .../AIMode/document/PublishControls.tsx | 22 ++ .../document/details/AllDetailsBlock.tsx | 64 +++++ .../AIMode/document/details/DetailBlock.tsx | 139 +++++++++ .../document/details/DetailBlockStrip.tsx | 96 +++++++ .../details/SelectGrantForNoteModal.tsx | 24 ++ .../AIMode/document/details/detailBlocks.ts | 138 +++++++++ components/Notebook/NotebookTabs.tsx | 6 +- .../Notebook/PublishingForm/PublishButton.tsx | 20 +- .../PublishingForm/PublishStatusPill.tsx | 93 ++++++ .../Notebook/PublishingForm/completion.ts | 43 +++ .../components/AuthorsSection.tsx | 6 +- .../components/ContactsSection.tsx | 6 +- .../components/FundingGoalField.tsx | 40 +++ .../components/FundingGoalSection.tsx | 15 + .../components/FundingOpportunityField.tsx | 93 ++++++ .../components/FundingSection.tsx | 139 +-------- .../components/GrantDescriptionSection.tsx | 6 +- .../components/GrantFundingAmountSection.tsx | 6 +- .../PublishingForm/components/SectionProps.ts | 4 + .../components/WorkImageSection.tsx | 6 +- components/Notebook/PublishingForm/index.tsx | 24 +- .../PublishingForm/useSelectedGrant.ts | 34 +++ hooks/useFittingCount.ts | 69 +++++ 25 files changed, 1135 insertions(+), 261 deletions(-) create mode 100644 components/AIMode/document/PublishControls.tsx create mode 100644 components/AIMode/document/details/AllDetailsBlock.tsx create mode 100644 components/AIMode/document/details/DetailBlock.tsx create mode 100644 components/AIMode/document/details/DetailBlockStrip.tsx create mode 100644 components/AIMode/document/details/SelectGrantForNoteModal.tsx create mode 100644 components/AIMode/document/details/detailBlocks.ts create mode 100644 components/Notebook/PublishingForm/PublishStatusPill.tsx create mode 100644 components/Notebook/PublishingForm/completion.ts create mode 100644 components/Notebook/PublishingForm/components/FundingGoalField.tsx create mode 100644 components/Notebook/PublishingForm/components/FundingGoalSection.tsx create mode 100644 components/Notebook/PublishingForm/components/FundingOpportunityField.tsx create mode 100644 components/Notebook/PublishingForm/components/SectionProps.ts create mode 100644 components/Notebook/PublishingForm/useSelectedGrant.ts create mode 100644 hooks/useFittingCount.ts diff --git a/components/AIMode/AIModeOverlay.tsx b/components/AIMode/AIModeOverlay.tsx index c72baac15..b49391ec0 100644 --- a/components/AIMode/AIModeOverlay.tsx +++ b/components/AIMode/AIModeOverlay.tsx @@ -12,9 +12,8 @@ import { useAIMode } from './AIModeContext'; import { ChatPane } from './ChatPane'; import { ConversationList } from './ConversationList'; import { DocumentCard } from './DocumentCard'; -import { DocumentPane } from './DocumentPane'; +import { DocumentPane, type DocumentPaneView } from './DocumentPane'; import { useAIModeChat } from './useAIModeChat'; -import type { NotebookTab } from '@/components/Notebook/NotebookTabs'; import type { PublishingDefaultArticleType } from '@/contexts/PublishingHostContext'; import { useAIModeDocument } from './useAIModeDocument'; import { AI_MODE_NAME } from './copy'; @@ -92,8 +91,8 @@ export function AIModeOverlay() { anchor: 'left', }); // Document or details in the right pane; details wants a wider floor. - const [documentTab, setDocumentTab] = useState('document'); - const documentMinWidth = documentTab === 'details' ? DETAILS_MIN_WIDTH : DOCUMENT_MIN_WIDTH; + const [documentView, setDocumentView] = useState('document'); + const documentMinWidth = documentView === 'details' ? DETAILS_MIN_WIDTH : DOCUMENT_MIN_WIDTH; // The document may grow until the chat is down to its minimum column. const documentMaxWidth = Math.max( documentMinWidth, @@ -117,7 +116,7 @@ export function AIModeOverlay() { const [documentOpen, setDocumentOpen] = useState(false); useEffect(() => { setDocumentOpen(noteId != null && !isBelowTabletRef.current); - setDocumentTab('document'); + setDocumentView('document'); }, [noteId]); // What the conversation set out to write, from its opening message, so the @@ -132,6 +131,11 @@ export function AIModeOverlay() { const openDocument = useCallback(() => setDocumentOpen(true), []); const closeDocument = useCallback(() => setDocumentOpen(false), []); const showDocument = noteId != null && documentOpen; + const documentTitle = doc.content?.title?.trim() || state.note?.title?.trim() || 'Document'; + + // The document column puts its publish controls up in the header, where + // they read as the workspace's, not the pane's. It renders into this slot. + const [publishControlsSlot, setPublishControlsSlot] = useState(null); // The turn that created the document, for seating its card in the transcript. const documentCardExecutionId = useMemo(() => { @@ -151,7 +155,7 @@ export function AIModeOverlay() { const documentCard = noteId != null ? (