The fifteen files under `src/components/agents/` (including `skills/`)
and `src/components/skills/`. 15/15 erase byte-identically; bundle hash
unchanged.
Renaming raised 43 errors and 29 of them came from one thing: these
files carry 24 `/** @param {any} props */` annotations, one per
component, and JSDoc stops applying at the extension boundary. Without
them TypeScript infers every destructured prop as required, so a call
site passing seven of nine props is an error — which is how a file that
declared its props `any` ended up with fourteen complaints about missing
`className`. Restoring the author's own declaration as `: any` is not
blanket typing; it is the annotation that was already there, in the only
form that still works.
`Workspace` in `AgentCanvas` was the one component in that file its
author left without the hatch. It now matches its siblings.
The rest were five separate things:
- `React.isValidElement(children)` no longer narrows enough to read
`children.props.id`: React 19 types `ReactElement`'s props as
`unknown`. `isValidElement<any>` says what the `cloneElement` call
beneath it has always assumed. The migration plan predicted this
site by name.
- `useSkillSections(page, placement)` is called with one argument by
`UiEditingProvider`, which its doc comment explicitly permits —
"called with no placement it returns every section on the page". The
parameter simply lacked its optional marker. The marker erases, so
the emitted signature is unchanged.
- `new Date(b.at) - new Date(a.at)` is valueOf coercion, which
JavaScript performs and TypeScript refuses to describe. Cast rather
than rewritten to `.getTime()`: that would change the emitted code,
and this comparison orders the list.
- `Object.values<any>` on a tally, the same inference gap as earlier
batches, which also fixed a `ReactNode` complaint downstream of it.
All fifteen are linted under their new extensions, checked by name.
Measured against `446df7b`:
typecheck 6 errors, down from 9; no new error anywhere
lint exit 0, 0 errors, 289 warnings, 15/15 linted by name
npm test 1684/1691, the same 7 failures verbatim
build exit 0, identical bundle hash 74d17e2d…
type erasure 72/72 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
109 lines
4.0 KiB
TypeScript
109 lines
4.0 KiB
TypeScript
import * as React from 'react';
|
|
import { Check, Layers, LayoutTemplate, Sparkles } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* Which surface this capability should run on.
|
|
*
|
|
* This used to be a read-out — two rows saying "✓ Owliver / — Board". It is now
|
|
* the choice it always described, and the constraint is the important part:
|
|
* **what can be picked is derived from the definition, never from a name.** A
|
|
* skill's facets say whether it teaches Owliver, draws on a page, or both, and
|
|
* an option the definition does not support is offered disabled with the reason
|
|
* rather than hidden — a reader deciding between surfaces should be able to see
|
|
* that the other one exists and why it is not available here.
|
|
*
|
|
* `Both` appears only when the definition genuinely supports both. Selecting it
|
|
* forks nothing: there is one logical skill, and this is a mode on it.
|
|
*/
|
|
|
|
const OPTIONS = [
|
|
{
|
|
id: 'owliver',
|
|
icon: Sparkles,
|
|
label: 'Owliver',
|
|
hint: 'Can be asked for in the assistant.',
|
|
supported: (type) => type === 'owliver' || type === 'both',
|
|
},
|
|
{
|
|
id: 'board',
|
|
icon: LayoutTemplate,
|
|
label: 'Board',
|
|
hint: 'Draws a section on the page.',
|
|
supported: (type) => type === 'board' || type === 'both',
|
|
},
|
|
{
|
|
id: 'both',
|
|
icon: Layers,
|
|
label: 'Both',
|
|
hint: 'Assistant and page.',
|
|
supported: (type) => type === 'both',
|
|
},
|
|
];
|
|
|
|
/** The surfaces this definition can be run on, in order. */
|
|
export const supportedSurfaces = (type) =>
|
|
OPTIONS.filter((o) => o.supported(type)).map((o) => o.id);
|
|
|
|
export function SurfaceSelect({ type, value, onChange, name }: any) {
|
|
return (
|
|
<div role="radiogroup" aria-label={`Run ${name} as`} className="space-y-1.5">
|
|
{OPTIONS.map((option) => {
|
|
const available = option.supported(type);
|
|
const selected = available && value === option.id;
|
|
const Icon = option.icon;
|
|
|
|
return (
|
|
<button
|
|
key={option.id}
|
|
type="button"
|
|
role="radio"
|
|
aria-checked={selected}
|
|
disabled={!available}
|
|
onClick={() => available && onChange(option.id)}
|
|
className={cn(
|
|
`flex w-full items-start gap-2 rounded-xl border px-2.5 py-2 text-left
|
|
transition-colors duration-base focus-visible:outline-none
|
|
focus-visible:ring-2 focus-visible:ring-krow-blue/40`,
|
|
!available && 'cursor-not-allowed border-dashed border-border bg-surface-subtle/40',
|
|
available && !selected && 'border-border bg-surface hover:border-krow-blue/30',
|
|
selected && 'border-krow-blue bg-krow-blue-tint'
|
|
)}
|
|
>
|
|
{/* The mark carries the state for anyone not reading colour; the
|
|
`aria-checked` above carries it for anyone not reading at all. */}
|
|
<span
|
|
className={cn(
|
|
'mt-px grid h-4 w-4 shrink-0 place-items-center rounded-full border',
|
|
selected ? 'border-krow-blue bg-krow-blue text-white' : 'border-border bg-surface'
|
|
)}
|
|
aria-hidden="true"
|
|
>
|
|
{selected && <Check className="h-2.5 w-2.5" />}
|
|
</span>
|
|
|
|
<span className="min-w-0 flex-1">
|
|
<span
|
|
className={cn(
|
|
'flex items-center gap-1.5 text-body-sm font-medium',
|
|
!available ? 'text-ink-4' : selected ? 'text-krow-blue' : 'text-ink-1'
|
|
)}
|
|
>
|
|
<Icon className="h-3.5 w-3.5 shrink-0" aria-hidden="true" />
|
|
{option.label}
|
|
</span>
|
|
<span className="mt-0.5 block text-caption leading-snug text-ink-3">
|
|
{available
|
|
? option.hint
|
|
: `This capability does not declare ${option.id === 'both' ? 'both surfaces' : `a ${option.label} surface`}.`}
|
|
</span>
|
|
</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default SurfaceSelect;
|