Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BRANDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ All SDK colors are expressed as `--dc-*` CSS custom properties. Host application

| Token | Light | Dark | Usage |
|-------|-------|------|-------|
| `--dc-verified` | `#16a34a` | `#22c55e` | Verified citation |
| `--dc-verified` | `#10b981` | `#34d399` | Verified citation (emerald-500 / emerald-400) |
| `--dc-partial` | `#f59e0b` | `#fbbf24` | Partial match |
| `--dc-destructive` | `#ef4444` | `#f87171` | Not found / error |
| `--dc-pending` | `#a1a1aa` | `#71717a` | Pending / loading |
Expand Down
4 changes: 4 additions & 0 deletions docs/_sass/custom/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,10 @@ pre {
border: 1px solid var(--border-color);
}

.search-result-doc .search-result-icon {
color: var(--link-color);
}

// -----------------------------------------------------
// Tables - Clean single thin borders (no double borders)
// border-collapse eliminates double borders between cells
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/branding.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The Tailwind classes `bg-dc-*`, `text-dc-*`, `border-dc-*` are registered via `@

| Token | Light Default | Dark Default | Usage |
|-------|--------------|--------------|-------|
| `--dc-verified` | `#16a34a` | `#22c55e` | Verified/success indicator |
| `--dc-verified` | `#10b981` | `#34d399` | Verified/success indicator (emerald-500 / emerald-400) |
| `--dc-partial` | `#f59e0b` | `#fbbf24` | Partial match / warning indicator |
| `--dc-destructive` | `#ef4444` | `#f87171` | Error/not-found indicator |
| `--dc-pending` | `#a1a1aa` | `#71717a` | Pending/loading indicator |
Expand Down
32 changes: 16 additions & 16 deletions docs/styling.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ Override these CSS variables to theme all DeepCitation components at once:
```css
:root {
/* Status indicator colors */
--dc-verified-color: #16a34a; /* Green - verified/exact match (default: green-600) */
--dc-partial-color: #f59e0b; /* Amber - partial match (default: amber-500) */
--dc-error-color: #ef4444; /* Red - not found/hallucination (default: red-500) */
--dc-pending-color: #9ca3af; /* Gray - loading/pending (default: gray-400) */
--dc-verified: #10b981; /* Emerald - verified/exact match (default: emerald-500) */
--dc-partial: #f59e0b; /* Amber - partial match (default: amber-500) */
--dc-destructive: #ef4444; /* Red - not found/hallucination (default: red-500) */
--dc-pending: #9ca3af; /* Gray - loading/pending (default: gray-400) */

/* Wavy underline for "not found" status (non-linter variants) */
--dc-wavy-underline-color: #ef4444; /* Default: red-500 */
Expand All @@ -53,10 +53,10 @@ Override these CSS variables to theme all DeepCitation components at once:
```css
@media (prefers-color-scheme: dark) {
:root {
--dc-verified-color: #4ade80; /* green-400 */
--dc-partial-color: #fbbf24; /* amber-400 */
--dc-error-color: #f87171; /* red-400 */
--dc-pending-color: #6b7280; /* gray-500 */
--dc-verified: #34d399; /* emerald-400 */
--dc-partial: #fbbf24; /* amber-400 */
--dc-destructive: #f87171; /* red-400 */
--dc-pending: #6b7280; /* gray-500 */
--dc-linter-success: #6aab85;
--dc-linter-warning: #fbbf24;
--dc-linter-error: #d47d7c;
Expand All @@ -68,10 +68,10 @@ Override these CSS variables to theme all DeepCitation components at once:

/* Or with a class-based approach (Tailwind dark mode) */
.dark {
--dc-verified-color: #4ade80;
--dc-partial-color: #fbbf24;
--dc-error-color: #f87171;
--dc-pending-color: #6b7280;
--dc-verified: #34d399;
--dc-partial: #fbbf24;
--dc-destructive: #f87171;
--dc-pending: #6b7280;
--dc-document-canvas-bg-light: #f3f4f6;
--dc-document-canvas-bg-dark: #1f2937;
}
Expand Down Expand Up @@ -165,22 +165,22 @@ Target specific citation elements using data attributes and selectors:

/* Verified citations - specific styling */
[data-dc-indicator="verified"] {
color: var(--dc-verified-color);
color: var(--dc-verified);
}

/* Partial match citations */
[data-dc-indicator="partial"] {
color: var(--dc-partial-color);
color: var(--dc-partial);
}

/* Not found / hallucination citations */
[data-dc-indicator="error"] {
color: var(--dc-error-color);
color: var(--dc-destructive);
}

/* Pending / loading citations */
[data-dc-indicator="pending"] {
color: var(--dc-pending-color);
color: var(--dc-pending);
}

/* Citation trigger element */
Expand Down
6 changes: 3 additions & 3 deletions src/__tests__/CitationContentDisplay.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ describe("CitationContentDisplay — footnote variant", () => {
/>,
);
const sup = container.querySelector("sup");
expect(sup?.className).toContain("text-green-600");
expect(sup?.className).toContain("text-dc-verified");
});

it("renders amber for partial match status", () => {
Expand All @@ -89,7 +89,7 @@ describe("CitationContentDisplay — footnote variant", () => {
/>,
);
const sup = container.querySelector("sup");
expect(sup?.className).toContain("text-amber-500");
expect(sup?.className).toContain("text-dc-partial");
});

it("renders red for miss status", () => {
Expand All @@ -105,7 +105,7 @@ describe("CitationContentDisplay — footnote variant", () => {
/>,
);
const sup = container.querySelector("sup");
expect(sup?.className).toContain("text-red-500");
expect(sup?.className).toContain("text-dc-destructive");
});

it("applies wavy underline style for miss state", () => {
Expand Down
5 changes: 2 additions & 3 deletions src/__tests__/UrlCitationComponent.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ describe("UrlCitationComponent", () => {
const checkIcon = container.querySelector("svg");
expect(checkIcon).toBeInTheDocument();

// The wrapper should have green color class
const statusWrapper = container.querySelector(".text-green-600");
expect(statusWrapper).toBeInTheDocument();
// The check icon uses the --dc-verified CSS custom property so host themes can override it
expect(checkIcon).toHaveStyle({ color: "var(--dc-verified, #10b981)" });
});

it("shows lock icon when blocked", () => {
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/caretIndicator.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,16 @@ describe("Caret Indicator Variant", () => {
});

// ==========================================================================
// ACTIVE DARKENING
// COLOR STAYS GRAY REGARDLESS OF OPEN STATE (no inverted active style)
// ==========================================================================

it("uses inverted text color (text-white) when open", () => {
it("uses gray text when open (no inverted active style)", () => {
const { container } = render(<CitationStatusIndicator {...baseProps} isOpen={true} popoverSide="bottom" />);
const pill = container.querySelector("[data-dc-indicator='caret']") as HTMLElement;
expect(pill.classList.contains("text-white")).toBe(true);
expect(pill.classList.contains("text-slate-500")).toBe(true);
});

it("uses lighter gray (text-slate-500) when closed", () => {
it("uses gray text when closed", () => {
const { container } = render(<CitationStatusIndicator {...baseProps} isOpen={false} />);
const pill = container.querySelector("[data-dc-indicator='caret']") as HTMLElement;
expect(pill.classList.contains("text-slate-500")).toBe(true);
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/rendering/htmlRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ describe("renderCitationsAsHtml", () => {

it("generates dark theme styles", () => {
const output = renderCitationsAsHtml(simpleInput, { theme: "dark" });
expect(output.styles).toContain("#4ade80"); // dark mode green
expect(output.styles).toContain("#34d399"); // dark mode emerald-400
});

it("generates auto theme styles with media query", () => {
Expand Down
22 changes: 15 additions & 7 deletions src/drawing/citationDrawing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import { safeSplit } from "../utils/regexSafety.js";

/**
* Highlight color category for citation annotations.
* - 'blue': exact / full-phrase match
* - 'green': exact / full-phrase match (VERIFIED)
* - 'amber': partial match (anchorText-only or value-only)
* - 'red': not-found (AI claimed location overlay)
* - 'blue': legacy alias for 'green' — kept for backward compatibility
*/
export type HighlightColor = "blue" | "amber" | "red";
export type HighlightColor = "green" | "blue" | "amber" | "red";

// =============================================================================
// Color Constants
Expand All @@ -28,9 +29,14 @@ export type HighlightColor = "blue" | "amber" | "red";
/** Border width for citation bracket outlines (px). */
export const CITATION_LINE_BORDER_WIDTH = 2;

/** Blue bracket color for exact/full-phrase matches. */
/** Green bracket color for verified / exact-match citations (BRANDING.md VERIFIED, emerald-500). */
export const SIGNAL_GREEN = "#10b981";
/** Lighter green for dark-mode contexts (BRANDING.md VERIFIED luminous, emerald-400). */
export const SIGNAL_GREEN_DARK = "#34d399";

/** @deprecated Use SIGNAL_GREEN. Kept for any external consumers still referencing blue brackets. */
export const SIGNAL_BLUE = "#005595";
/** Lighter blue for dark-mode contexts. */
/** @deprecated Use SIGNAL_GREEN_DARK. */
export const SIGNAL_BLUE_DARK = "#77bff6";

/** Amber bracket color for partial matches (Tailwind amber-400). */
Expand Down Expand Up @@ -88,12 +94,14 @@ export function getBracketWidth(height: number): number {

/**
* Returns the bracket stroke color for a given highlight category.
* Blue for exact matches, amber for partial matches, red for not-found.
* Green for verified/exact matches, amber for partial matches, red for not-found.
* "blue" is a legacy alias that resolves to the deprecated SIGNAL_BLUE value.
*/
export function getBracketColor(highlightColor: HighlightColor = "blue"): string {
export function getBracketColor(highlightColor: HighlightColor = "green"): string {
if (highlightColor === "amber") return SIGNAL_AMBER;
if (highlightColor === "red") return SIGNAL_RED;
return SIGNAL_BLUE;
if (highlightColor === "blue") return SIGNAL_BLUE; // legacy
return SIGNAL_GREEN;
}

// =============================================================================
Expand Down
2 changes: 2 additions & 0 deletions src/drawing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export {
SIGNAL_AMBER,
SIGNAL_BLUE,
SIGNAL_BLUE_DARK,
SIGNAL_GREEN,
SIGNAL_GREEN_DARK,
SIGNAL_RED,
SPOTLIGHT_BORDER_RADIUS,
SPOTLIGHT_PADDING,
Expand Down
9 changes: 5 additions & 4 deletions src/react/Citation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
SPINNER_TIMEOUT_MS,
TAP_SLOP_PX,
TOUCH_CLICK_DEBOUNCE_MS,
VERIFIED_COLOR_STYLE,
} from "./constants.js";
import { DefaultPopoverContent, type PopoverViewState } from "./DefaultPopoverContent.js";
import { resolveEvidenceSrc, resolveExpandedImage } from "./EvidenceTray.js";
Expand Down Expand Up @@ -1490,12 +1491,12 @@ const PendingDot = () => (
);

/**
* Green verified checkmark indicator.
* Uses green-600 color to match DOT_COLORS.green for visual consistency.
* Verified checkmark indicator.
* Color tracks --dc-verified so it stays in sync with the status dot and quote border.
*/
const VerifiedCheck = () => (
<span aria-hidden="true">
<CheckIcon className={cn("w-full h-full", "text-green-600 dark:text-green-500")} />
<span aria-hidden="true" style={VERIFIED_COLOR_STYLE}>
<CheckIcon className="w-full h-full" />
</span>
);

Expand Down
31 changes: 25 additions & 6 deletions src/react/CitationAnnotationOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,31 @@ import {
BOX_PADDING,
CITATION_LINE_BORDER_WIDTH,
computeKeySpanHighlight,
getBracketColor,
getBracketWidth,
OVERLAY_COLOR,
SPOTLIGHT_BORDER_RADIUS,
SPOTLIGHT_PADDING,
} from "../drawing/citationDrawing.js";
import type { DeepTextItem } from "../types/boxes.js";
import { HITBOX_EXTEND_8 } from "./constants.js";
import {
ERROR_COLOR_DEFAULT,
ERROR_COLOR_VAR,
HITBOX_EXTEND_8,
PARTIAL_COLOR_DEFAULT,
PARTIAL_COLOR_VAR,
VERIFIED_COLOR_DEFAULT,
VERIFIED_COLOR_VAR,
} from "./constants.js";
import { useTranslation } from "./i18n.js";
import { CloseIcon } from "./icons.js";
import { toPercentRect } from "./overlayGeometry.js";

// Hoisted bracket color strings — all inputs are static module-level constants,
// so these never change and avoid per-render string allocations during zoom/pan.
const VERIFIED_BRACKET_COLOR = `var(${VERIFIED_COLOR_VAR}, ${VERIFIED_COLOR_DEFAULT})`;
const PARTIAL_BRACKET_COLOR = `var(${PARTIAL_COLOR_VAR}, ${PARTIAL_COLOR_DEFAULT})`;
const ERROR_BRACKET_COLOR = `var(${ERROR_COLOR_VAR}, ${ERROR_COLOR_DEFAULT})`;

const NONE: React.CSSProperties = { pointerEvents: "none" };

/** Dismiss button size in px (matches Tailwind `size-7` = 1.75rem = 28px). */
Expand Down Expand Up @@ -50,7 +63,8 @@ function SecondaryBrackets({
const rect = toPercentRect(deepItem, renderScale, imageNaturalWidth, imageNaturalHeight);
if (!rect) return null;

const bracketColor = getBracketColor(color === "muted" ? "blue" : "amber");
// amber → partial-match color; muted → verified color at lower opacity (distal supporting evidence)
const bracketColor = color === "muted" ? VERIFIED_BRACKET_COLOR : PARTIAL_BRACKET_COLOR;
const opacity = color === "muted" ? 0.35 : 0.5;

const baseLeft = parseFloat(rect.left);
Expand Down Expand Up @@ -143,9 +157,14 @@ export function CitationAnnotationOverlay({
// Bail out if geometry is invalid (zero dimensions, NaN, Infinity, etc.)
if (!rect) return null;

const bracketColor = getBracketColor(
highlightColor === "amber" ? "amber" : highlightColor === "red" ? "red" : "blue",
);
// All bracket colors resolve through --dc-* tokens so a host override to any
// one token automatically keeps brackets, status indicators, and quote borders in sync.
const bracketColor =
highlightColor === "amber"
? PARTIAL_BRACKET_COLOR
: highlightColor === "red"
? ERROR_BRACKET_COLOR
: VERIFIED_BRACKET_COLOR;

// Compute pixel height for bracket width calculation
const heightPx = phraseMatchDeepItem.height * renderScale.y;
Expand Down
19 changes: 8 additions & 11 deletions src/react/CitationContentDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { isUrlCitation } from "../types/citation.js";
import { getInteractionClasses } from "./CitationContentDisplay.utils.js";
import { CitationStatusIndicator, type CitationStatusIndicatorProps } from "./CitationStatusIndicator.js";
import {
CARET_INDICATOR_SIZE_STYLE,
DOT_COLORS,
DOT_INDICATOR_SIZE_STYLE,
ERROR_COLOR_STYLE,
Expand All @@ -22,7 +23,7 @@ import {
SUPERSCRIPT_STYLE,
VERIFIED_COLOR_STYLE,
} from "./constants.js";
import { CheckIcon, XIcon } from "./icons.js";
import { CheckIcon, ChevronDownIcon, XIcon } from "./icons.js";
import { handleImageError } from "./imageUtils.js";
import type { CitationContent, CitationRenderProps, CitationVariant } from "./types.js";
import { cn } from "./utils.js";
Expand Down Expand Up @@ -151,11 +152,11 @@ export const CitationContentDisplay = ({
if (shouldShowSpinner) {
footnoteStatusClasses = "text-slate-500 dark:text-slate-400";
} else if (isMiss) {
footnoteStatusClasses = "text-red-500 dark:text-red-400";
footnoteStatusClasses = "text-dc-destructive";
} else if (isPartialMatch) {
footnoteStatusClasses = "text-amber-500 dark:text-amber-400";
footnoteStatusClasses = "text-dc-partial";
} else if (isVerified) {
footnoteStatusClasses = "text-green-600 dark:text-green-500";
footnoteStatusClasses = "text-dc-verified";
} else {
footnoteStatusClasses = "text-slate-500 dark:text-slate-400";
}
Expand Down Expand Up @@ -315,14 +316,10 @@ export const CitationContentDisplay = ({
return <span className={cn("rounded-full", DOT_COLORS[dotColor])} style={DOT_INDICATOR_SIZE_STYLE} />;
}
if (iv === "caret") {
const colorStyle = isMiss
? ERROR_COLOR_STYLE
: isPartialMatch
? PARTIAL_COLOR_STYLE
: VERIFIED_COLOR_STYLE;
const caretColor = isMiss ? "text-red-500 dark:text-red-400" : "text-slate-500 dark:text-slate-400";
return (
<span className="inline-flex" style={{ ...INDICATOR_SIZE_STYLE, ...colorStyle }}>
{isMiss ? <XIcon /> : <CheckIcon />}
<span className={cn("inline-flex", caretColor)} style={CARET_INDICATOR_SIZE_STYLE}>
<ChevronDownIcon />
</span>
);
}
Expand Down
Loading
Loading