Duplicate notice
A row that appears under a Company you just saved when Ascern finds one that looks the same. It points it out and never blocks the save.
Playground
Change the controls to try each prop. The code under the preview updates as you go and lists only the props you changed; copy it into your page.
import { DuplicateNotice } from '@/ui/duplicate-notice';<DuplicateNotice name="Halcyon" />In the table
Right under the row that was saved, full width, on the sunken fill. It stays until dismissed or the value changes. Press × to dismiss it, then Replay to save again.
import { useState, type CSSProperties } from 'react';
import { Button } from '@/ui/button';
import { DuplicateNotice } from '@/ui/duplicate-notice';
import { moveList } from '@/ui/motion';
const row = 'grid h-10 grid-cols-3 items-center border-b border-border px-150 text-body leading-body';
const listRow = (name: string) => ({ 'data-list-row': '', style: { '--list-row': name } as CSSProperties });
// Halcyon was just saved with halcyonbank.com. Press × to dismiss the notice, then Replay to save again.
export default function DuplicateNoticeTable() {
const [shown, setShown] = useState(true);
return (
<div className="flex w-full flex-col items-start gap-200">
<div className="w-full overflow-hidden rounded-large inset-ring inset-ring-border">
<div className={`${row} font-medium text-text-subtle`}>
<span>Company</span>
<span>Website</span>
<span>Owner</span>
</div>
<div className={row} {...listRow('orbital')}>
<span className="font-medium text-text">Orbital Freight</span>
<span className="text-text">orbitalfreight.com</span>
<span className="text-text-subtle">Priya Shah</span>
</div>
<div className={row} {...listRow('halcyon')}>
<span className="font-medium text-text">Halcyon</span>
<span className="text-text">halcyonbank.com</span>
<span className="text-text-subtle">Priya Shah</span>
</div>
{shown ? (
<DuplicateNotice
name="Halcyon"
matches={[{ name: 'Halcyon Bank', reasons: 'same website, similar name', href: '#halcyon-bank' }]}
onDismiss={() => setShown(false)}
/>
) : null}
<div className={row} {...listRow('brightwave')}>
<span className="font-medium text-text">Brightwave Payments</span>
<span className="text-text">brightwave.io</span>
<span className="text-text-subtle">Tom Lindqvist</span>
</div>
</div>
<Button size="sm" disabled={shown} onClick={(event) => moveList(() => setShown(true), event.currentTarget)}>
Replay
</Button>
</div>
);
}Several matches
One notice per saved row, listing each match once with all its reasons.
import { DuplicateNotice } from '@/ui/duplicate-notice';
// Each match once, with all its reasons. Names show as stored, in any script.
export default function DuplicateNoticeSeveral() {
return (
<DuplicateNotice
className="w-full"
name="Brightwave"
matches={[
{ name: 'Brightwave Payments', reasons: 'same LinkedIn page, similar name', href: '#brightwave-payments' },
{ name: 'Công ty Brightwave Việt Nam', reasons: 'similar name', href: '#brightwave-vn' },
]}
onDismiss={() => {}}
/>
);
}Connection status
Whether Ascern can still read a member's mail, calendar or meetings, when it last did, and the one thing to do about it. A row in Settings, a dot in the account menu, a banner across the app while it needs action.
Empty state
What a list or page shows when there is nothing in it: why it is empty and the one next step. First run, no match, not connected yet, or failed to load.