refactor(ts-migration): Phase 11 batch 8 — the assistant panel components

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-18 15:37:09 +05:30
parent 3f835eee93
commit 7a95d95151
13 changed files with 21 additions and 26 deletions

View File

@@ -57,8 +57,7 @@ function ConstrainedTag() {
); );
} }
/** @param {any} props */ export function AgentBadge({ page }: any) {
export function AgentBadge({ page }) {
const { agent, covers } = useActiveAgent(); const { agent, covers } = useActiveAgent();
return ( return (

View File

@@ -86,9 +86,8 @@ function FeedbackControls({ feedback, onFeedback }) {
} }
export const Message = React.memo( export const Message = React.memo(
/** @param {any} props */
({ role, text, blocks, streaming, stopped, onPrompt, onConfirm = null, ({ role, text, blocks, streaming, stopped, onPrompt, onConfirm = null,
feedback = null, onFeedback = null }) => { feedback = null, onFeedback = null }: any) => {
if (role === 'user') { if (role === 'user') {
return ( return (
<div className="flex justify-end"> <div className="flex justify-end">

View File

@@ -15,8 +15,7 @@ import { useAssistantPanel } from './AssistantPanelContext';
* It self-hides on pages with no assistant, so a page can render it * It self-hides on pages with no assistant, so a page can render it
* unconditionally and never needs to know the placement rules. * unconditionally and never needs to know the placement rules.
*/ */
/** @param {any} props */ export function AssistantTrigger({ className = '', size = 'default' }: any) {
export function AssistantTrigger({ className = '', size = 'default' }) {
const { supported, isOpen, toggle, context } = useAssistantPanel(); const { supported, isOpen, toggle, context } = useAssistantPanel();
if (!supported) return null; if (!supported) return null;

View File

@@ -23,14 +23,13 @@ import { cn } from '@/lib/utils';
* *
* Arrow keys move between chips, so the whole set is one tab stop. * Arrow keys move between chips, so the whole set is one tab stop.
*/ */
/** @param {any} props */
export function PromptChips({ export function PromptChips({
prompts = [], onSelect, max = 0, align = 'center', className = '', prompts = [], onSelect, max = 0, align = 'center', className = '',
/* Taken out of the tab order while the row is collapsed but still mounted: /* 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 a chip inside a zero-height container is invisible, and a Tab that lands on
something invisible is a keyboard user losing their place. */ something invisible is a keyboard user losing their place. */
focusable = true, focusable = true,
}) { }: any) {
const chipRefs = React.useRef([]); const chipRefs = React.useRef([]);
const visible = max ? prompts.slice(0, max) : prompts; const visible = max ? prompts.slice(0, max) : prompts;

View File

@@ -17,7 +17,6 @@ import { cn } from '@/lib/utils';
* gain — the suggestion chips already teach the range of what can be asked, and * 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. * they do it without a timer or a rerender every four seconds.
*/ */
/** @param {any} props */
export function PromptInput({ export function PromptInput({
value, value,
onChange, onChange,
@@ -28,7 +27,7 @@ export function PromptInput({
autoFocus = false, autoFocus = false,
align = 'left', align = 'left',
className = '', className = '',
}) { }: any) {
const textareaRef = React.useRef(null); const textareaRef = React.useRef(null);
const [focused, setFocused] = React.useState(false); const [focused, setFocused] = React.useState(false);

View File

@@ -121,14 +121,14 @@ const TONE_BADGE = {
/* ── Individual blocks ──────────────────────────────────────────────────── */ /* ── Individual blocks ──────────────────────────────────────────────────── */
const TextBlock = React.memo(/** @param {any} props */ ({ block }) => ( const TextBlock = React.memo(({ block }: any) => (
<p className="text-body-sm leading-relaxed text-ink-2"> <p className="text-body-sm leading-relaxed text-ink-2">
<Inline value={block.text} /> <Inline value={block.text} />
</p> </p>
)); ));
TextBlock.displayName = 'TextBlock'; TextBlock.displayName = 'TextBlock';
const HeadingBlock = React.memo(/** @param {any} props */ ({ block }) => ( const HeadingBlock = React.memo(({ block }: any) => (
<div className="space-y-0.5"> <div className="space-y-0.5">
<h4 className="font-heading text-body-sm font-semibold text-ink-1">{block.text}</h4> <h4 className="font-heading text-body-sm font-semibold text-ink-1">{block.text}</h4>
{block.sub && <p className="text-caption text-ink-3">{block.sub}</p>} {block.sub && <p className="text-caption text-ink-3">{block.sub}</p>}
@@ -137,7 +137,7 @@ const HeadingBlock = React.memo(/** @param {any} props */ ({ block }) => (
HeadingBlock.displayName = 'HeadingBlock'; HeadingBlock.displayName = 'HeadingBlock';
/** Headline figures. Two columns keeps the numbers large in a 380px panel. */ /** 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) => (
<div className="grid grid-cols-2 gap-2 [[data-wide]_&]:grid-cols-3"> <div className="grid grid-cols-2 gap-2 [[data-wide]_&]:grid-cols-3">
{block.items.map((item) => ( {block.items.map((item) => (
<div <div
@@ -172,7 +172,7 @@ const KpisBlock = React.memo(/** @param {any} props */ ({ block }) => (
KpisBlock.displayName = 'KpisBlock'; KpisBlock.displayName = 'KpisBlock';
/** Pass/warn checks. The icon carries the state so colour is not the only cue. */ /** 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) => (
<ul className="space-y-1.5"> <ul className="space-y-1.5">
{block.items.map((item) => ( {block.items.map((item) => (
<li key={item.label} className="flex items-start gap-2"> <li key={item.label} className="flex items-start gap-2">
@@ -204,7 +204,7 @@ const StatusBlock = React.memo(/** @param {any} props */ ({ block }) => (
)); ));
StatusBlock.displayName = 'StatusBlock'; StatusBlock.displayName = 'StatusBlock';
const MetersBlock = React.memo(/** @param {any} props */ ({ block }) => ( const MetersBlock = React.memo(({ block }: any) => (
<div className="space-y-2.5"> <div className="space-y-2.5">
{block.items.map((item) => ( {block.items.map((item) => (
<div key={item.label} className="space-y-1"> <div key={item.label} className="space-y-1">
@@ -226,7 +226,7 @@ MetersBlock.displayName = 'MetersBlock';
* A table. Scrolls horizontally inside its own container so a wide comparison * A table. Scrolls horizontally inside its own container so a wide comparison
* never widens the panel. * never widens the panel.
*/ */
const TableBlock = React.memo(/** @param {any} props */ ({ block }) => { const TableBlock = React.memo(({ block }: any) => {
const cell = (value) => { const cell = (value) => {
if (value == null || value === '') return <span className="text-ink-4">—</span>; if (value == null || value === '') return <span className="text-ink-4">—</span>;
if (typeof value === 'object') { if (typeof value === 'object') {
@@ -287,7 +287,7 @@ const TableBlock = React.memo(/** @param {any} props */ ({ block }) => {
TableBlock.displayName = 'TableBlock'; TableBlock.displayName = 'TableBlock';
/** Stage progression, widths relative to the largest stage. */ /** 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); const max = Math.max(...block.steps.map((s) => s.count), 1);
return ( return (
@@ -314,7 +314,7 @@ const FunnelBlock = React.memo(/** @param {any} props */ ({ block }) => {
}); });
FunnelBlock.displayName = 'FunnelBlock'; FunnelBlock.displayName = 'FunnelBlock';
const ListBlock = React.memo(/** @param {any} props */ ({ block }) => { const ListBlock = React.memo(({ block }: any) => {
const Tag = block.ordered ? 'ol' : 'ul'; const Tag = block.ordered ? 'ol' : 'ul';
return ( return (
<Tag className="space-y-1.5"> <Tag className="space-y-1.5">
@@ -357,7 +357,7 @@ ListBlock.displayName = 'ListBlock';
* clickable and doing nothing. An item may carry a hint *and* an action: the * clickable and doing nothing. An item may carry a hint *and* an action: the
* hint explains the record, the action opens it. * hint explains the record, the action opens it.
*/ */
const InsightsBlock = React.memo(/** @param {any} props */ ({ block, onPrompt }) => ( const InsightsBlock = React.memo(({ block, onPrompt }: any) => (
<div className="space-y-2"> <div className="space-y-2">
{block.items.map((item, i) => { {block.items.map((item, i) => {
const surface = cn( const surface = cn(
@@ -434,7 +434,7 @@ const InsightsBlock = React.memo(/** @param {any} props */ ({ block, onPrompt })
InsightsBlock.displayName = 'InsightsBlock'; InsightsBlock.displayName = 'InsightsBlock';
/** Recommended steps — numbered, because order is the recommendation. */ /** Recommended steps — numbered, because order is the recommendation. */
const ActionsBlock = React.memo(/** @param {any} props */ ({ block }) => ( const ActionsBlock = React.memo(({ block }: any) => (
<ol className="space-y-2"> <ol className="space-y-2">
{block.items.map((item, i) => ( {block.items.map((item, i) => (
<li key={i} className="flex gap-2.5 rounded-xl border border-border bg-surface px-3 py-2.5 shadow-xs"> <li key={i} className="flex gap-2.5 rounded-xl border border-border bg-surface px-3 py-2.5 shadow-xs">
@@ -453,7 +453,7 @@ const ActionsBlock = React.memo(/** @param {any} props */ ({ block }) => (
)); ));
ActionsBlock.displayName = 'ActionsBlock'; ActionsBlock.displayName = 'ActionsBlock';
const BadgesBlock = React.memo(/** @param {any} props */ ({ block }) => ( const BadgesBlock = React.memo(({ block }: any) => (
<div className="flex flex-wrap gap-1.5"> <div className="flex flex-wrap gap-1.5">
{block.items.map((item) => ( {block.items.map((item) => (
<Badge key={item.label} variant={TONE_BADGE[item.tone] || 'soft'} size="lg"> <Badge key={item.label} variant={TONE_BADGE[item.tone] || 'soft'} size="lg">
@@ -464,7 +464,7 @@ const BadgesBlock = React.memo(/** @param {any} props */ ({ block }) => (
)); ));
BadgesBlock.displayName = 'BadgesBlock'; BadgesBlock.displayName = 'BadgesBlock';
const TimelineBlock = React.memo(/** @param {any} props */ ({ block }) => ( const TimelineBlock = React.memo(({ block }: any) => (
<ol className="relative space-y-3"> <ol className="relative space-y-3">
{block.items.map((item, i) => ( {block.items.map((item, i) => (
<li key={i} className="flex gap-3"> <li key={i} className="flex gap-3">
@@ -497,7 +497,7 @@ const TimelineBlock = React.memo(/** @param {any} props */ ({ block }) => (
)); ));
TimelineBlock.displayName = 'TimelineBlock'; TimelineBlock.displayName = 'TimelineBlock';
const NoteBlock = React.memo(/** @param {any} props */ ({ block }) => ( const NoteBlock = React.memo(({ block }: any) => (
<p className="border-l-2 border-border pl-2.5 text-caption italic leading-relaxed text-ink-3"> <p className="border-l-2 border-border pl-2.5 text-caption italic leading-relaxed text-ink-3">
<Inline value={block.text} /> <Inline value={block.text} />
</p> </p>
@@ -514,7 +514,7 @@ NoteBlock.displayName = 'NoteBlock';
* shape the table does not carry renders nothing rather than something * shape the table does not carry renders nothing rather than something
* improvised. * improvised.
*/ */
const SkillSectionBlock = React.memo(/** @param {any} props */ ({ block }) => { const SkillSectionBlock = React.memo(({ block }: any) => {
const section = block.section; const section = block.section;
const Component = SECTION_COMPONENTS[section?.shape || section?.type]; 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 * - **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. * 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 [state, setState] = React.useState('pending');
const approve = React.useCallback(() => { const approve = React.useCallback(() => {
@@ -673,7 +673,7 @@ const RENDERERS = {
* blocks has consistent rhythm — a heading hugs what follows it, everything * blocks has consistent rhythm — a heading hugs what follows it, everything
* else breathes. * 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) => (
<div className="space-y-3"> <div className="space-y-3">
{blocks.map((block, i) => { {blocks.map((block, i) => {
const Renderer = RENDERERS[block.type]; const Renderer = RENDERERS[block.type];