position ui and owliver update

This commit is contained in:
2026-08-14 01:18:59 +05:30
parent 1c653264b5
commit cccada9bd2
29 changed files with 3718 additions and 1313 deletions

View File

@@ -1,6 +1,8 @@
import * as React from 'react';
import { Link } from 'react-router-dom';
import { ArrowDownRight, ArrowUpRight, Check, ChevronRight, TriangleAlert } from 'lucide-react';
import {
ArrowDownRight, ArrowRight, ArrowUpRight, Check, ChevronRight, TriangleAlert,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Badge } from '@/components/ui/badge';
import { ProgressBar, toneForScore } from '@/components/ds/Progress';
@@ -280,11 +282,17 @@ ListBlock.displayName = 'ListBlock';
* position they were working on. This is the default for a
* recommended person: inspecting a candidate is part of the
* conversation, not a trip to another page.
* `to` leaves for a route the app already has. Reserved for the explicit
* "open the full record" case.
* `to` leaves for a route the app already has, with the whole card as the
* target. Reserved for items whose only meaning is "open this".
* `action` a named link inside the card — `{ label, to }`. For an item that
* has *both* a conversation and a record: the card keeps asking
* Owliver, and the link is the one explicit way to leave. Reading a
* card can then never navigate by accident, which matters most where
* the body is an explanation worth reading.
*
* `hint` is for an item that can do neither — it says why, instead of looking
* clickable and doing nothing.
* 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(({ block, onPrompt }) => (
<div className="space-y-2">
@@ -298,7 +306,8 @@ const InsightsBlock = React.memo(({ block, onPrompt }) => (
'transition-colors hover:border-krow-blue/50',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-krow-blue/50'
);
const actionable = Boolean(item.prompt && onPrompt) || Boolean(item.to);
const asksOwliver = Boolean(item.prompt && onPrompt);
const actionable = asksOwliver || Boolean(item.to);
const body = (
<>
@@ -315,7 +324,37 @@ const InsightsBlock = React.memo(({ block, onPrompt }) => (
</>
);
if (item.prompt && onPrompt) {
/* A named link cannot live inside a button, so when an item has both, the
card is a plain container: the readable area is its own button and the
action sits beside it. One click target each, never nested. */
if (item.action?.to) {
return (
<div key={i} className={surface}>
{asksOwliver ? (
<button
type="button"
onClick={() => onPrompt(item.prompt)}
className="block w-full rounded-lg text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-krow-blue/50"
>
{body}
</button>
) : body}
<div className="mt-2 flex justify-end">
<Link
to={item.action.to}
className="inline-flex items-center gap-0.5 rounded text-caption font-semibold text-krow-blue transition-colors hover:underline
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-krow-blue/50"
>
{item.action.label}
<ArrowRight className="h-3 w-3" aria-hidden="true" />
</Link>
</div>
</div>
);
}
if (asksOwliver) {
return (
<button key={i} type="button" onClick={() => onPrompt(item.prompt)} className={interactive}>
{body}