From b349f2db619a3a7e7df9c9377f91af6df984aa50 Mon Sep 17 00:00:00 2001 From: Aravind Date: Fri, 14 Aug 2026 18:01:43 +0530 Subject: [PATCH] fix pubilsh job posting --- .../ai-assistant/capabilities/admin.js | 14 +++-- src/components/ai-assistant/useAssistant.js | 57 +++++++++++++++++-- src/lib/skills/positionFlow.js | 18 ++++-- 3 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/components/ai-assistant/capabilities/admin.js b/src/components/ai-assistant/capabilities/admin.js index 28e58d4..328c8d8 100644 --- a/src/components/ai-assistant/capabilities/admin.js +++ b/src/components/ai-assistant/capabilities/admin.js @@ -967,9 +967,14 @@ const positionsNeedingAttention = (f) => { * * A draft is a real record with real values on it and no way for anyone to * apply to it, which makes it the one thing on this page that is genuinely - * unfinished. Each one is offered with the control that finishes it: the same - * authoring form, told which draft it is continuing, with everything already - * entered still in place. + * unfinished. Each one is named, with what it already holds. + * + * It reports, it does not reopen. This used to attach "Continue to save" to + * every draft, routing to the authoring form — so a question asked in the panel + * could end with the reader back inside Create Position, and a position that + * had been saved minutes earlier was presented as still needing saving. The + * draft is on the Positions list marked Draft and its own card opens the form; + * the panel says where it is rather than taking anyone there. * * Read from `postings` by status — no list of drafts is kept anywhere, and a * position stops appearing here the moment it is published. @@ -991,9 +996,8 @@ const positionDrafts = (f) => { p.location, 'Not published yet, so nobody can apply and no screening runs.', ].filter(Boolean).join(' · '), - action: { label: 'Continue to save', to: `/admin/positions/new?draft=${encodeURIComponent(p.id)}` }, }))), - note('Continuing reopens the same form with everything already entered — the vetting weights included. Publishing it there is what makes it an active position.') + note('Each one is on the Positions list marked Draft. Opening its card reopens the form with everything already entered — the vetting weights included — and publishing it there is what makes it an active position.') ); }; diff --git a/src/components/ai-assistant/useAssistant.js b/src/components/ai-assistant/useAssistant.js index f525ec8..4a4407d 100644 --- a/src/components/ai-assistant/useAssistant.js +++ b/src/components/ai-assistant/useAssistant.js @@ -110,6 +110,50 @@ async function streamDocument({ document, signal, onFrame, onDone }) { else if (latest.length) onDone(latest); } +/** + * The Create Position form, as an address. + * + * Named once because two things have to agree about it: nothing stored may + * offer a way back into the authoring form after a position has been saved. + */ +const AUTHORING_ROUTE = '/admin/positions/new'; + +/** + * A stored thread, with any completed-then-reopen action stripped out. + * + * A reply is persisted as the blocks it rendered, so a thread written before + * this was fixed still carries the old "Continue to save" — a live control + * routing back into Create Position, offered on a position that was saved long + * ago. Removing it in code was not enough; the ones already written down had to + * stop working too. + * + * Deliberately narrow: only actions pointing at the authoring form are dropped, + * and only from stored threads. Every other block, chip and link is left as it + * was written, so a thread reads exactly as it did apart from the one control + * that should never have been there. + */ +function withoutAuthoringActions(messages = []) { + const leadsToForm = (target) => String(target || '').includes(AUTHORING_ROUTE); + + return messages.map((message) => { + if (message.role !== 'assistant') return message; + + const followUp = message.followUp?.filter((chip) => !leadsToForm(chip.route)); + const blocks = message.blocks?.map((block) => ( + block?.type === 'insights' + ? { + ...block, + items: block.items.map(({ action, ...item }) => ( + leadsToForm(action?.to) ? item : { ...item, ...(action ? { action } : null) } + )), + } + : block + )); + + return { ...message, ...(followUp ? { followUp } : null), ...(blocks ? { blocks } : null) }; + }); +} + /** * Conversation state for one page context. * @@ -174,7 +218,7 @@ export function useConversation({ React.useEffect(() => { try { const raw = sessionStorage.getItem(storageKey); - setMessages(raw ? JSON.parse(raw) : []); + setMessages(raw ? withoutAuthoringActions(JSON.parse(raw)) : []); } catch { setMessages([]); } @@ -491,12 +535,17 @@ export function useConversation({ setError(null); setFlow(null); - messagesRef.current = record.messages; - setMessages(record.messages); + /* Same rule as loading a stored thread: a conversation kept in History was + written before this was fixed too, and reopening it must not hand back a + route into the authoring form. */ + const messages = withoutAuthoringActions(record.messages); + + messagesRef.current = messages; + setMessages(messages); conversationRef.current = record.id; try { - sessionStorage.setItem(storageKey, JSON.stringify(record.messages)); + sessionStorage.setItem(storageKey, JSON.stringify(messages)); sessionStorage.setItem(idKey, record.id); } catch { // The reopened thread stays in memory only. diff --git a/src/lib/skills/positionFlow.js b/src/lib/skills/positionFlow.js index 05237f4..9d85385 100644 --- a/src/lib/skills/positionFlow.js +++ b/src/lib/skills/positionFlow.js @@ -462,7 +462,9 @@ export function positionCreatedReply(position) { const isDraft = position.status === 'draft'; return doc( - text(isDraft ? 'Saved as a draft.' : 'Position created successfully.'), + /* Draft and published are different outcomes, so they are named + differently: one was saved, the other went live. */ + text(isDraft ? 'Saved as a draft.' : 'Position published successfully.'), list([ position.company, position.title, @@ -493,9 +495,17 @@ export function positionFailedReply() { * no navigation, and the same answer as asking it by hand. */ export const createdFollowUp = (position) => (position.status === 'draft' - /* A draft is unfinished, so the way on is the form that finishes it — the - same route the Positions card's Continue uses. */ - ? [{ label: 'Continue to save', route: `/admin/positions/new?draft=${encodeURIComponent(position.id)}` }] + /** + * A saved draft offers no action, and that is the fix. + * + * This used to offer "Continue to save", routing back to the Create Position + * form. It made the completed state look unfinished: the reader had just been + * told the position was saved, and was immediately asked to save it again — + * by a button that put them back in the form they had just left. The write + * has happened, the record exists with `status: draft`, and the way to finish + * a draft later is its own card on the Positions list. + */ + ? [] : [ { label: 'View position', route: `/admin/positions/${position.id}` }, { label: 'Match candidates', prompt: `Who matches ${position.title}?` },