From 7a95d951519d9c0488eac13de7ee9b516a56527b Mon Sep 17 00:00:00 2001 From: Aravind Date: Fri, 18 Sep 2026 15:37:09 +0530 Subject: [PATCH] =?UTF-8?q?refactor(ts-migration):=20Phase=2011=20batch=20?= =?UTF-8?q?8=20=E2=80=94=20the=20assistant=20panel=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The thirteen `.jsx` files under `src/components/ai-assistant/`, which completes that directory: 24/24 files migrated and all 24 linted under their new extensions. 39 errors, and converting 21 `/** @param {any} props */` hatches to `: any` cleared 33 of them — the same pattern as batch 6, and the same reason: without the JSDoc, TypeScript infers every destructured prop as required, so `ResponseBlocks` and `AssistantMessage` alone produced 31 complaints about call sites that were always correct. The remaining six were already there before this batch and are unchanged. 13/13 erase byte-identically. `.jsx` -> `.tsx` is the move that can drop unused imports through esbuild's loader difference; it did not here, and the bundle hash is unchanged. Measured against `3f835ee`: typecheck 6 errors, unchanged; no new error anywhere lint exit 0, 0 errors, 289 warnings, 24/24 linted by name npm test 1684/1691, the same 7 failures verbatim build exit 0, identical bundle hash 74d17e2d… type erasure 96/96 byte-identical across Phase 11 so far No baseline artifact touched. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8 --- .../{AgentBadge.jsx => AgentBadge.tsx} | 3 +- .../{AgentContext.jsx => AgentContext.tsx} | 0 ...istantMessage.jsx => AssistantMessage.tsx} | 3 +- ...{AssistantPanel.jsx => AssistantPanel.tsx} | 0 ...lContext.jsx => AssistantPanelContext.tsx} | 0 ...istantTrigger.jsx => AssistantTrigger.tsx} | 3 +- .../{KrowAssistant.jsx => KrowAssistant.tsx} | 0 .../{PageContext.jsx => PageContext.tsx} | 0 .../{PromptChips.jsx => PromptChips.tsx} | 3 +- .../{PromptInput.jsx => PromptInput.tsx} | 3 +- .../{ResizeDivider.jsx => ResizeDivider.tsx} | 0 ...{ResponseBlocks.jsx => ResponseBlocks.tsx} | 32 +++++++++---------- .../ai-assistant/{index.jsx => index.tsx} | 0 13 files changed, 21 insertions(+), 26 deletions(-) rename src/components/ai-assistant/{AgentBadge.jsx => AgentBadge.tsx} (97%) rename src/components/ai-assistant/{AgentContext.jsx => AgentContext.tsx} (100%) rename src/components/ai-assistant/{AssistantMessage.jsx => AssistantMessage.tsx} (98%) rename src/components/ai-assistant/{AssistantPanel.jsx => AssistantPanel.tsx} (100%) rename src/components/ai-assistant/{AssistantPanelContext.jsx => AssistantPanelContext.tsx} (100%) rename src/components/ai-assistant/{AssistantTrigger.jsx => AssistantTrigger.tsx} (98%) rename src/components/ai-assistant/{KrowAssistant.jsx => KrowAssistant.tsx} (100%) rename src/components/ai-assistant/{PageContext.jsx => PageContext.tsx} (100%) rename src/components/ai-assistant/{PromptChips.jsx => PromptChips.tsx} (99%) rename src/components/ai-assistant/{PromptInput.jsx => PromptInput.tsx} (99%) rename src/components/ai-assistant/{ResizeDivider.jsx => ResizeDivider.tsx} (100%) rename src/components/ai-assistant/{ResponseBlocks.jsx => ResponseBlocks.tsx} (95%) rename src/components/ai-assistant/{index.jsx => index.tsx} (100%) diff --git a/src/components/ai-assistant/AgentBadge.jsx b/src/components/ai-assistant/AgentBadge.tsx similarity index 97% rename from src/components/ai-assistant/AgentBadge.jsx rename to src/components/ai-assistant/AgentBadge.tsx index fb406b0..f0d9a50 100644 --- a/src/components/ai-assistant/AgentBadge.jsx +++ b/src/components/ai-assistant/AgentBadge.tsx @@ -57,8 +57,7 @@ function ConstrainedTag() { ); } -/** @param {any} props */ -export function AgentBadge({ page }) { +export function AgentBadge({ page }: any) { const { agent, covers } = useActiveAgent(); return ( diff --git a/src/components/ai-assistant/AgentContext.jsx b/src/components/ai-assistant/AgentContext.tsx similarity index 100% rename from src/components/ai-assistant/AgentContext.jsx rename to src/components/ai-assistant/AgentContext.tsx diff --git a/src/components/ai-assistant/AssistantMessage.jsx b/src/components/ai-assistant/AssistantMessage.tsx similarity index 98% rename from src/components/ai-assistant/AssistantMessage.jsx rename to src/components/ai-assistant/AssistantMessage.tsx index 8d67fc0..b3d5e59 100644 --- a/src/components/ai-assistant/AssistantMessage.jsx +++ b/src/components/ai-assistant/AssistantMessage.tsx @@ -86,9 +86,8 @@ function FeedbackControls({ feedback, onFeedback }) { } export const Message = React.memo( - /** @param {any} props */ ({ role, text, blocks, streaming, stopped, onPrompt, onConfirm = null, - feedback = null, onFeedback = null }) => { + feedback = null, onFeedback = null }: any) => { if (role === 'user') { return (
diff --git a/src/components/ai-assistant/AssistantPanel.jsx b/src/components/ai-assistant/AssistantPanel.tsx similarity index 100% rename from src/components/ai-assistant/AssistantPanel.jsx rename to src/components/ai-assistant/AssistantPanel.tsx diff --git a/src/components/ai-assistant/AssistantPanelContext.jsx b/src/components/ai-assistant/AssistantPanelContext.tsx similarity index 100% rename from src/components/ai-assistant/AssistantPanelContext.jsx rename to src/components/ai-assistant/AssistantPanelContext.tsx diff --git a/src/components/ai-assistant/AssistantTrigger.jsx b/src/components/ai-assistant/AssistantTrigger.tsx similarity index 98% rename from src/components/ai-assistant/AssistantTrigger.jsx rename to src/components/ai-assistant/AssistantTrigger.tsx index b1bf0b5..cde3c62 100644 --- a/src/components/ai-assistant/AssistantTrigger.jsx +++ b/src/components/ai-assistant/AssistantTrigger.tsx @@ -15,8 +15,7 @@ import { useAssistantPanel } from './AssistantPanelContext'; * It self-hides on pages with no assistant, so a page can render it * unconditionally and never needs to know the placement rules. */ -/** @param {any} props */ -export function AssistantTrigger({ className = '', size = 'default' }) { +export function AssistantTrigger({ className = '', size = 'default' }: any) { const { supported, isOpen, toggle, context } = useAssistantPanel(); if (!supported) return null; diff --git a/src/components/ai-assistant/KrowAssistant.jsx b/src/components/ai-assistant/KrowAssistant.tsx similarity index 100% rename from src/components/ai-assistant/KrowAssistant.jsx rename to src/components/ai-assistant/KrowAssistant.tsx diff --git a/src/components/ai-assistant/PageContext.jsx b/src/components/ai-assistant/PageContext.tsx similarity index 100% rename from src/components/ai-assistant/PageContext.jsx rename to src/components/ai-assistant/PageContext.tsx diff --git a/src/components/ai-assistant/PromptChips.jsx b/src/components/ai-assistant/PromptChips.tsx similarity index 99% rename from src/components/ai-assistant/PromptChips.jsx rename to src/components/ai-assistant/PromptChips.tsx index 29fcb68..a718778 100644 --- a/src/components/ai-assistant/PromptChips.jsx +++ b/src/components/ai-assistant/PromptChips.tsx @@ -23,14 +23,13 @@ import { cn } from '@/lib/utils'; * * Arrow keys move between chips, so the whole set is one tab stop. */ -/** @param {any} props */ export function PromptChips({ prompts = [], onSelect, max = 0, align = 'center', className = '', /* Taken out of the tab order while the row is collapsed but still mounted: a chip inside a zero-height container is invisible, and a Tab that lands on something invisible is a keyboard user losing their place. */ focusable = true, -}) { +}: any) { const chipRefs = React.useRef([]); const visible = max ? prompts.slice(0, max) : prompts; diff --git a/src/components/ai-assistant/PromptInput.jsx b/src/components/ai-assistant/PromptInput.tsx similarity index 99% rename from src/components/ai-assistant/PromptInput.jsx rename to src/components/ai-assistant/PromptInput.tsx index f2453da..1b346a9 100644 --- a/src/components/ai-assistant/PromptInput.jsx +++ b/src/components/ai-assistant/PromptInput.tsx @@ -17,7 +17,6 @@ import { cn } from '@/lib/utils'; * gain — the suggestion chips already teach the range of what can be asked, and * they do it without a timer or a rerender every four seconds. */ -/** @param {any} props */ export function PromptInput({ value, onChange, @@ -28,7 +27,7 @@ export function PromptInput({ autoFocus = false, align = 'left', className = '', -}) { +}: any) { const textareaRef = React.useRef(null); const [focused, setFocused] = React.useState(false); diff --git a/src/components/ai-assistant/ResizeDivider.jsx b/src/components/ai-assistant/ResizeDivider.tsx similarity index 100% rename from src/components/ai-assistant/ResizeDivider.jsx rename to src/components/ai-assistant/ResizeDivider.tsx diff --git a/src/components/ai-assistant/ResponseBlocks.jsx b/src/components/ai-assistant/ResponseBlocks.tsx similarity index 95% rename from src/components/ai-assistant/ResponseBlocks.jsx rename to src/components/ai-assistant/ResponseBlocks.tsx index 6d40381..439ccb2 100644 --- a/src/components/ai-assistant/ResponseBlocks.jsx +++ b/src/components/ai-assistant/ResponseBlocks.tsx @@ -121,14 +121,14 @@ const TONE_BADGE = { /* ── Individual blocks ──────────────────────────────────────────────────── */ -const TextBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const TextBlock = React.memo(({ block }: any) => (

)); TextBlock.displayName = 'TextBlock'; -const HeadingBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const HeadingBlock = React.memo(({ block }: any) => (

{block.text}

{block.sub &&

{block.sub}

} @@ -137,7 +137,7 @@ const HeadingBlock = React.memo(/** @param {any} props */ ({ block }) => ( HeadingBlock.displayName = 'HeadingBlock'; /** Headline figures. Two columns keeps the numbers large in a 380px panel. */ -const KpisBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const KpisBlock = React.memo(({ block }: any) => (
{block.items.map((item) => (
( KpisBlock.displayName = 'KpisBlock'; /** Pass/warn checks. The icon carries the state so colour is not the only cue. */ -const StatusBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const StatusBlock = React.memo(({ block }: any) => (
    {block.items.map((item) => (
  • @@ -204,7 +204,7 @@ const StatusBlock = React.memo(/** @param {any} props */ ({ block }) => ( )); StatusBlock.displayName = 'StatusBlock'; -const MetersBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const MetersBlock = React.memo(({ block }: any) => (
    {block.items.map((item) => (
    @@ -226,7 +226,7 @@ MetersBlock.displayName = 'MetersBlock'; * A table. Scrolls horizontally inside its own container so a wide comparison * never widens the panel. */ -const TableBlock = React.memo(/** @param {any} props */ ({ block }) => { +const TableBlock = React.memo(({ block }: any) => { const cell = (value) => { if (value == null || value === '') return —; if (typeof value === 'object') { @@ -287,7 +287,7 @@ const TableBlock = React.memo(/** @param {any} props */ ({ block }) => { TableBlock.displayName = 'TableBlock'; /** Stage progression, widths relative to the largest stage. */ -const FunnelBlock = React.memo(/** @param {any} props */ ({ block }) => { +const FunnelBlock = React.memo(({ block }: any) => { const max = Math.max(...block.steps.map((s) => s.count), 1); return ( @@ -314,7 +314,7 @@ const FunnelBlock = React.memo(/** @param {any} props */ ({ block }) => { }); FunnelBlock.displayName = 'FunnelBlock'; -const ListBlock = React.memo(/** @param {any} props */ ({ block }) => { +const ListBlock = React.memo(({ block }: any) => { const Tag = block.ordered ? 'ol' : 'ul'; return ( @@ -357,7 +357,7 @@ ListBlock.displayName = 'ListBlock'; * clickable and doing nothing. An item may carry a hint *and* an action: the * hint explains the record, the action opens it. */ -const InsightsBlock = React.memo(/** @param {any} props */ ({ block, onPrompt }) => ( +const InsightsBlock = React.memo(({ block, onPrompt }: any) => (
    {block.items.map((item, i) => { const surface = cn( @@ -434,7 +434,7 @@ const InsightsBlock = React.memo(/** @param {any} props */ ({ block, onPrompt }) InsightsBlock.displayName = 'InsightsBlock'; /** Recommended steps — numbered, because order is the recommendation. */ -const ActionsBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const ActionsBlock = React.memo(({ block }: any) => (
      {block.items.map((item, i) => (
    1. @@ -453,7 +453,7 @@ const ActionsBlock = React.memo(/** @param {any} props */ ({ block }) => ( )); ActionsBlock.displayName = 'ActionsBlock'; -const BadgesBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const BadgesBlock = React.memo(({ block }: any) => (
      {block.items.map((item) => ( @@ -464,7 +464,7 @@ const BadgesBlock = React.memo(/** @param {any} props */ ({ block }) => ( )); BadgesBlock.displayName = 'BadgesBlock'; -const TimelineBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const TimelineBlock = React.memo(({ block }: any) => (
        {block.items.map((item, i) => (
      1. @@ -497,7 +497,7 @@ const TimelineBlock = React.memo(/** @param {any} props */ ({ block }) => ( )); TimelineBlock.displayName = 'TimelineBlock'; -const NoteBlock = React.memo(/** @param {any} props */ ({ block }) => ( +const NoteBlock = React.memo(({ block }: any) => (

        @@ -514,7 +514,7 @@ NoteBlock.displayName = 'NoteBlock'; * shape the table does not carry renders nothing rather than something * improvised. */ -const SkillSectionBlock = React.memo(/** @param {any} props */ ({ block }) => { +const SkillSectionBlock = React.memo(({ block }: any) => { const section = block.section; const Component = SECTION_COMPONENTS[section?.shape || section?.type]; @@ -564,7 +564,7 @@ SkillSectionBlock.displayName = 'SkillSectionBlock'; * - **Not pretend to be finished.** Once approved, the block stays visible * and says so. Replacing it with a tick would lose what was agreed to. */ -const ConfirmationBlock = React.memo(/** @param {any} props */ ({ block, onConfirm }) => { +const ConfirmationBlock = React.memo(({ block, onConfirm }: any) => { const [state, setState] = React.useState('pending'); const approve = React.useCallback(() => { @@ -673,7 +673,7 @@ const RENDERERS = { * blocks has consistent rhythm — a heading hugs what follows it, everything * else breathes. */ -export const ResponseDocument = React.memo(/** @param {any} props */ ({ blocks = [], streaming = false, onPrompt, onConfirm }) => ( +export const ResponseDocument = React.memo(({ blocks = [], streaming = false, onPrompt, onConfirm }: any) => (
        {blocks.map((block, i) => { const Renderer = RENDERERS[block.type]; diff --git a/src/components/ai-assistant/index.jsx b/src/components/ai-assistant/index.tsx similarity index 100% rename from src/components/ai-assistant/index.jsx rename to src/components/ai-assistant/index.tsx