API reference¶
The top-level colorsense package is the canonical public API: everything below is
re-exported there, so from colorsense import ... is the supported import path. Anything
not listed here is internal and free to change between releases.
Entry point¶
colorsense.analyze
async
¶
analyze(
url: str,
*,
config_path: str | Path | None = None,
viewport: Viewport = DEFAULT_VIEWPORT,
themes: tuple[Theme, ...] = DEFAULT_THEMES,
politeness: PolitenessPolicy | None = None,
max_total_seconds: float | None = None,
browser_args: tuple[str, ...] = (),
include_tokens: bool = False,
) -> AnalysisResult
Analyze url and return a typed AnalysisResult.
Async-native: the requested themes are rendered concurrently — sharing one lazily
launched headless Chromium, each theme in its own browser context — gated by
politeness; the rest of the pipeline is pure CPU work, offloaded
to worker threads via asyncio.to_thread so the event loop stays responsive.
Awaitable directly from an asyncio event loop (e.g. a FastAPI async def endpoint).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Page to analyze. Any |
required |
config_path
|
str | Path | None
|
Path to a palette config YAML to override the default. When |
None
|
viewport
|
Viewport
|
Render viewport; defaults to 1280x800 at 1x scale. |
DEFAULT_VIEWPORT
|
themes
|
tuple[Theme, ...]
|
Themes to render, in priority order (the first is "primary": it is the theme
kept when near-identical renders collapse). Duplicates are ignored. Defaults to
light only — most sites have no dark mode and a second theme roughly doubles
the work. Pass |
DEFAULT_THEMES
|
politeness
|
PolitenessPolicy | None
|
Fetch policy (robots gate, rate limit, render cache). A conservative
default |
None
|
max_total_seconds
|
float | None
|
Optional overall deadline for the entire call — every theme render
plus the CPU classification — enforced via |
None
|
browser_args
|
tuple[str, ...]
|
Extra command-line arguments for the call's Chromium launch, appended
to the library's own launch arguments and passed verbatim to Chromium (the
library does not validate or allowlist the flags — mechanism, not policy). Every
render of this call — all themes share one browser — launches with them.
Canonical use case: |
()
|
include_tokens
|
bool
|
When |
False
|
Returns:
| Type | Description |
|---|---|
AnalysisResult
|
A typed |
AnalysisResult
|
the cross-theme third-party colors, and the run metadata. |
Raises:
| Type | Description |
|---|---|
UnsupportedSchemeError
|
If the URL scheme is not fetchable
under the policy: only |
RobotsDisallowedError
|
If |
RenderError
|
If the page fails to render or navigate (DNS, timeout, TLS, or navigation error). |
AnalysisTimeoutError
|
If |
colorsense.DEFAULT_VIEWPORT
module-attribute
¶
DEFAULT_VIEWPORT = Viewport(
width=1280, height=800, device_scale_factor=1.0
)
Result & contracts¶
colorsense.AnalysisResult
pydantic-model
¶
Bases: BaseModel
The top-level typed result returned by analyze.
Per-theme analysis (the color-keyed index, the role-keyed usage view, divergence, and
opt-in tokens) lives on each ThemePalette in themes.
This aggregate is immutable: it is frozen=True and its sequence field
(third_party_colors) is a tuple, so neither reassigning an attribute
(result.url = ...) nor mutating a sequence in place is possible. The themes
dict is protected from reassignment by frozen but its contents are not
deep-frozen.
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"ColorUsage": {
"description": "A measured color and where it is used \u2014 one entry of the color-keyed canonical inventory.\n\n``prominence`` is the overall ranking signal blending area-truth (primary) with routed\nvote mass (secondary), so dominant backgrounds rank high while zero-area brand accents\n(CTA/link colors) are not buried; the ``colors`` tuple is sorted by it, descending,\nwith a ``hex`` tiebreak. ``area`` is the raw screenshot area fraction the color's\ncluster covers (an auditable signal, not a probability). ``usages`` lists every\n[`UsageRole`][colorsense.UsageRole] the color appears in, most-used first\n(``weight`` descending, ``hex`` tiebreak).",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The measured color this inventory entry describes."
},
"prominence": {
"description": "Overall ranking signal blending area-truth (primary) with routed vote mass (secondary); the ``colors`` tuple is sorted by it, descending.",
"examples": [
0.83
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Prominence",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"usages": {
"description": "Every usage role the color appears in, most-used first (``weight`` descending).",
"items": {
"$ref": "#/$defs/Usage"
},
"title": "Usages",
"type": "array"
}
},
"required": [
"color",
"prominence",
"area"
],
"title": "ColorUsage",
"type": "object"
},
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
},
"DesignToken": {
"description": "A declared design token (CSS custom property) in the public result.\n\n``name`` is the declared property name (e.g. ``--fgColor-default``); ``color`` is its\nvalue resolved in the rendered theme; ``semantic_role`` is the inferred semantic role.",
"properties": {
"name": {
"description": "Declared property name, including the leading ``--``.",
"examples": [
"--fgColor-default"
],
"title": "Name",
"type": "string"
},
"color": {
"$ref": "#/$defs/Color",
"description": "The token's value resolved in the rendered theme."
},
"semantic_role": {
"$ref": "#/$defs/TokenSemanticRole",
"description": "The inferred semantic role for the token."
}
},
"required": [
"name",
"color",
"semantic_role"
],
"title": "DesignToken",
"type": "object"
},
"DivergenceItem": {
"description": "A declared-but-unused or used-but-undeclared palette discrepancy.\n\nKeyed by [`UsageRole`][colorsense.UsageRole] \u2014 the role-keyed usage view is where\ndeclared token intent is reconciled against measured usage.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role the discrepancy was found under."
},
"color": {
"$ref": "#/$defs/Color",
"description": "The declared-but-unused or used-but-undeclared color."
},
"note": {
"description": "Human-readable explanation of the discrepancy.",
"examples": [
"declared as brand_primary but not measured in use"
],
"title": "Note",
"type": "string"
}
},
"required": [
"role",
"color",
"note"
],
"title": "DivergenceItem",
"type": "object"
},
"PropertyFamily": {
"description": "Which family of CSS properties paints the color \u2014 the rollup axis over roles.\n\nCoarser than [`UsageRole`][colorsense.UsageRole]: every role belongs to exactly one\nfamily (the mapping is [`UsageRole.property_family`][colorsense.UsageRole.property_family]).\n\nAttributes:\n BACKGROUND: ``background-color`` / ``background-image`` fills.\n TEXT: The ``color`` property (typography).\n BORDER: The ``border-color`` property.",
"enum": [
"background",
"text",
"border"
],
"title": "PropertyFamily",
"type": "string"
},
"RunMetadata": {
"description": "Provenance for a single ``analyze`` run.\n\nRecords which themes were requested versus actually analyzed (later themes whose\nrender is perceptually identical to the primary are collapsed away) and the fetch\npolicy in effect. A run reduced to a single theme iff ``len(themes_analyzed) == 1``.",
"properties": {
"themes_requested": {
"description": "Themes the caller requested.",
"examples": [
[
"light",
"dark"
]
],
"items": {
"$ref": "#/$defs/Theme"
},
"title": "Themes Requested",
"type": "array"
},
"themes_analyzed": {
"description": "Themes actually analyzed; a run reduced to a single theme iff this has length 1 (perceptually-identical later themes are collapsed away).",
"examples": [
[
"light"
]
],
"items": {
"$ref": "#/$defs/Theme"
},
"title": "Themes Analyzed",
"type": "array"
},
"user_agent": {
"default": "",
"description": "User-Agent string used for fetches.",
"title": "User Agent",
"type": "string"
},
"respect_robots": {
"default": true,
"description": "Whether robots.txt was honored for this run.",
"title": "Respect Robots",
"type": "boolean"
}
},
"title": "RunMetadata",
"type": "object"
},
"Theme": {
"description": "Color scheme a site is rendered under.\n\nAttributes:\n LIGHT: Light color scheme (the default).\n DARK: Dark color scheme (``prefers-color-scheme: dark``).",
"enum": [
"light",
"dark"
],
"title": "Theme",
"type": "string"
},
"ThemePalette": {
"description": "Everything derived for a single rendered theme.\n\n* ``colors`` \u2014 the **canonical, color-keyed index**: every measured color\n ([`ColorUsage`][colorsense.ColorUsage]) with where it is used and an overall\n ``prominence`` ranking. Answers \"how is each color used?\". Third-party-dominated\n colors are excluded (they live on ``AnalysisResult.third_party_colors``).\n* ``usage`` \u2014 the **role-keyed projection** ([`UsagePalette`][colorsense.UsagePalette]):\n the reconciled posterior over usage roles (measured usage pooled with declared token\n intent). Answers \"which colors paint each role?\".\n* ``divergence`` \u2014 declared-vs-measured discrepancies, keyed by usage role.\n* ``tokens`` \u2014 declared design tokens, opt-in: ``None`` means tokens were **not\n requested** (``include_tokens=False``, the default); ``()`` means tokens were\n requested but no usable color tokens were found \u2014 the page declares no custom\n properties at all, or every declaration was filtered as non-meaningful (no\n resolvable color, ``ignore`` semantic role, or zero classification weight).",
"properties": {
"theme": {
"$ref": "#/$defs/Theme",
"description": "The theme this palette was derived for."
},
"colors": {
"description": "Canonical color-keyed index: every measured color with where it is used, ranked by ``prominence`` (third-party-dominated colors excluded).",
"items": {
"$ref": "#/$defs/ColorUsage"
},
"title": "Colors",
"type": "array"
},
"usage": {
"$ref": "#/$defs/UsagePalette",
"description": "Role-keyed projection: which colors paint each usage role (measured pooled with token intent)."
},
"divergence": {
"description": "Declared-vs-measured discrepancies, keyed by usage role.",
"items": {
"$ref": "#/$defs/DivergenceItem"
},
"title": "Divergence",
"type": "array"
},
"tokens": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/DesignToken"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Declared design tokens, opt-in: ``None`` means tokens were not requested; ``()`` means requested but none were usable.",
"title": "Tokens"
}
},
"required": [
"theme",
"usage"
],
"title": "ThemePalette",
"type": "object"
},
"TokenSemanticRole": {
"description": "Semantic role inferred for a declared design token (CSS custom property).\n\nAttributes:\n BRAND_PRIMARY: Primary brand color.\n BRAND_SECONDARY: Secondary brand color.\n BRAND_ACCENT: Accent brand color.\n INTERACTIVE: Interactive-element color (links, controls).\n SURFACE_BASE: Base page/surface background.\n SURFACE_RAISED: Raised surface background (cards, modals).\n TEXT_BODY: Body text color.\n NEUTRAL: Neutral/gray with no specific role.\n BORDER: Border/divider color.\n TEXT_ON: Foreground meant to sit on a colored fill (an \"on\" color).\n STATUS: Status color (success/warning/error/info).\n IGNORE: Not a meaningful palette token (filtered out).",
"enum": [
"brand_primary",
"brand_secondary",
"brand_accent",
"interactive",
"surface_base",
"surface_raised",
"text_body",
"neutral",
"border",
"text_on",
"status",
"ignore"
],
"title": "TokenSemanticRole",
"type": "string"
},
"Usage": {
"description": "One usage slot of a color in the color-keyed index ([`ColorUsage`][colorsense.ColorUsage]).\n\nA color may be used in several roles (e.g. the same gray as ``text`` *and* ``border``);\neach gets its own ``Usage``. ``role`` is the [`UsageRole`][colorsense.UsageRole] this\nslot describes and ``property_family`` is its [`PropertyFamily`][colorsense.PropertyFamily]\nrollup (denormalized \u2014 always ``role.property_family`` \u2014 so consumers can group by family\nwithout recomputing). ``weight`` is this color's share of *its own* usages \u2014 the role's\nrouted mass over the color's total routed mass, so a color's ``weight`` values sum to\n~1.0. ``components`` is normalized evidence within this slot: which component types\ncontributed the color to this role (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to\n~1.0 when non-empty.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role this slot describes."
},
"property_family": {
"$ref": "#/$defs/PropertyFamily",
"description": "Rollup family for ``role`` (denormalized \u2014 always ``role.property_family``)."
},
"weight": {
"description": "This role's share of the color's total routed mass; a color's ``weight`` values sum to ~1.0.",
"examples": [
0.7
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Weight",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed the color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"role",
"property_family",
"weight"
],
"title": "Usage",
"type": "object"
},
"UsageEntry": {
"description": "One color's standing within a usage role (role-keyed projection).\n\n``probability`` is the posterior prominence of this color *within its role*\n(entries of one role sum to ~1.0). ``area`` is the raw screenshot area fraction\nthe color's cluster covers \u2014 an auditable signal, not a probability. ``components``\nis normalized evidence: which component types contributed this color to this\nrole (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to ~1.0 when non-empty.",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The color this role entry describes."
},
"probability": {
"description": "Posterior prominence of this color within its role; entries of one role sum to ~1.0.",
"examples": [
0.55
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Probability",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed this color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"color",
"probability",
"area"
],
"title": "UsageEntry",
"type": "object"
},
"UsagePalette": {
"description": "The role-keyed palette projection: which colors paint each usage role.\n\n``mapping`` is guaranteed to contain every [`UsageRole`][colorsense.UsageRole]; a\nrole with no detected entries maps to an empty tuple. This invariant is enforced by an\nafter-validator that backfills any missing roles, so even ``UsagePalette()`` and\nthe empty-input path expose all eight keys.",
"properties": {
"mapping": {
"additionalProperties": {
"items": {
"$ref": "#/$defs/UsageEntry"
},
"type": "array"
},
"description": "Colors painting each [`UsageRole`][colorsense.UsageRole]; guaranteed to contain every role (an unused role maps to ``()``).",
"propertyNames": {
"$ref": "#/$defs/UsageRole"
},
"title": "Mapping",
"type": "object"
}
},
"title": "UsagePalette",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
},
"Viewport": {
"description": "Rendering viewport.",
"properties": {
"width": {
"description": "Viewport width in CSS pixels.",
"examples": [
1280
],
"minimum": 1,
"title": "Width",
"type": "integer"
},
"height": {
"description": "Viewport height in CSS pixels.",
"examples": [
800
],
"minimum": 1,
"title": "Height",
"type": "integer"
},
"device_scale_factor": {
"description": "Device pixel ratio (DIP\u2192raster multiplier); 1.0 is non-retina, 2.0 is retina.",
"examples": [
1.0
],
"exclusiveMinimum": 0.0,
"title": "Device Scale Factor",
"type": "number"
}
},
"required": [
"width",
"height",
"device_scale_factor"
],
"title": "Viewport",
"type": "object"
}
},
"description": "The top-level typed result returned by ``analyze``.\n\nPer-theme analysis (the color-keyed index, the role-keyed usage view, divergence, and\nopt-in tokens) lives on each [`ThemePalette`][colorsense.ThemePalette] in ``themes``.\n\nThis aggregate is **immutable**: it is ``frozen=True`` and its sequence field\n(``third_party_colors``) is a tuple, so neither reassigning an attribute\n(``result.url = ...``) nor mutating a sequence in place is possible. The ``themes``\ndict is protected from reassignment by ``frozen`` but its contents are not\ndeep-frozen.",
"properties": {
"url": {
"description": "The analyzed page URL.",
"examples": [
"https://example.com/"
],
"title": "Url",
"type": "string"
},
"viewport": {
"$ref": "#/$defs/Viewport",
"description": "Viewport the page was rendered at."
},
"themes": {
"additionalProperties": {
"$ref": "#/$defs/ThemePalette"
},
"description": "Per-theme analysis, keyed by [`Theme`][colorsense.Theme].",
"propertyNames": {
"$ref": "#/$defs/Theme"
},
"title": "Themes",
"type": "object"
},
"third_party_colors": {
"description": "Colors attributed to third-party/vendor widgets, held out of the per-theme index.",
"items": {
"$ref": "#/$defs/Color"
},
"title": "Third Party Colors",
"type": "array"
},
"metadata": {
"$ref": "#/$defs/RunMetadata",
"description": "Provenance for the run (themes requested/analyzed, fetch policy)."
}
},
"required": [
"url",
"viewport"
],
"title": "AnalysisResult",
"type": "object"
}
Config:
frozen:True
Fields:
-
url(str) -
viewport(Viewport) -
themes(dict[Theme, ThemePalette]) -
third_party_colors(tuple[Color, ...]) -
metadata(RunMetadata)
third_party_colors
pydantic-field
¶
third_party_colors: tuple[Color, ...]
Colors attributed to third-party/vendor widgets, held out of the per-theme index.
metadata
pydantic-field
¶
metadata: RunMetadata
Provenance for the run (themes requested/analyzed, fetch policy).
colorsense.RunMetadata
pydantic-model
¶
Bases: BaseModel
Provenance for a single analyze run.
Records which themes were requested versus actually analyzed (later themes whose
render is perceptually identical to the primary are collapsed away) and the fetch
policy in effect. A run reduced to a single theme iff len(themes_analyzed) == 1.
Show JSON schema:
{
"$defs": {
"Theme": {
"description": "Color scheme a site is rendered under.\n\nAttributes:\n LIGHT: Light color scheme (the default).\n DARK: Dark color scheme (``prefers-color-scheme: dark``).",
"enum": [
"light",
"dark"
],
"title": "Theme",
"type": "string"
}
},
"description": "Provenance for a single ``analyze`` run.\n\nRecords which themes were requested versus actually analyzed (later themes whose\nrender is perceptually identical to the primary are collapsed away) and the fetch\npolicy in effect. A run reduced to a single theme iff ``len(themes_analyzed) == 1``.",
"properties": {
"themes_requested": {
"description": "Themes the caller requested.",
"examples": [
[
"light",
"dark"
]
],
"items": {
"$ref": "#/$defs/Theme"
},
"title": "Themes Requested",
"type": "array"
},
"themes_analyzed": {
"description": "Themes actually analyzed; a run reduced to a single theme iff this has length 1 (perceptually-identical later themes are collapsed away).",
"examples": [
[
"light"
]
],
"items": {
"$ref": "#/$defs/Theme"
},
"title": "Themes Analyzed",
"type": "array"
},
"user_agent": {
"default": "",
"description": "User-Agent string used for fetches.",
"title": "User Agent",
"type": "string"
},
"respect_robots": {
"default": true,
"description": "Whether robots.txt was honored for this run.",
"title": "Respect Robots",
"type": "boolean"
}
},
"title": "RunMetadata",
"type": "object"
}
Config:
frozen:True
Fields:
-
themes_requested(tuple[Theme, ...]) -
themes_analyzed(tuple[Theme, ...]) -
user_agent(str) -
respect_robots(bool)
themes_analyzed
pydantic-field
¶
themes_analyzed: tuple[Theme, ...]
Themes actually analyzed; a run reduced to a single theme iff this has length 1 (perceptually-identical later themes are collapsed away).
respect_robots
pydantic-field
¶
respect_robots: bool = True
Whether robots.txt was honored for this run.
colorsense.ThemePalette
pydantic-model
¶
Bases: BaseModel
Everything derived for a single rendered theme.
colors— the canonical, color-keyed index: every measured color (ColorUsage) with where it is used and an overallprominenceranking. Answers "how is each color used?". Third-party-dominated colors are excluded (they live onAnalysisResult.third_party_colors).usage— the role-keyed projection (UsagePalette): the reconciled posterior over usage roles (measured usage pooled with declared token intent). Answers "which colors paint each role?".divergence— declared-vs-measured discrepancies, keyed by usage role.tokens— declared design tokens, opt-in:Nonemeans tokens were not requested (include_tokens=False, the default);()means tokens were requested but no usable color tokens were found — the page declares no custom properties at all, or every declaration was filtered as non-meaningful (no resolvable color,ignoresemantic role, or zero classification weight).
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"ColorUsage": {
"description": "A measured color and where it is used \u2014 one entry of the color-keyed canonical inventory.\n\n``prominence`` is the overall ranking signal blending area-truth (primary) with routed\nvote mass (secondary), so dominant backgrounds rank high while zero-area brand accents\n(CTA/link colors) are not buried; the ``colors`` tuple is sorted by it, descending,\nwith a ``hex`` tiebreak. ``area`` is the raw screenshot area fraction the color's\ncluster covers (an auditable signal, not a probability). ``usages`` lists every\n[`UsageRole`][colorsense.UsageRole] the color appears in, most-used first\n(``weight`` descending, ``hex`` tiebreak).",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The measured color this inventory entry describes."
},
"prominence": {
"description": "Overall ranking signal blending area-truth (primary) with routed vote mass (secondary); the ``colors`` tuple is sorted by it, descending.",
"examples": [
0.83
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Prominence",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"usages": {
"description": "Every usage role the color appears in, most-used first (``weight`` descending).",
"items": {
"$ref": "#/$defs/Usage"
},
"title": "Usages",
"type": "array"
}
},
"required": [
"color",
"prominence",
"area"
],
"title": "ColorUsage",
"type": "object"
},
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
},
"DesignToken": {
"description": "A declared design token (CSS custom property) in the public result.\n\n``name`` is the declared property name (e.g. ``--fgColor-default``); ``color`` is its\nvalue resolved in the rendered theme; ``semantic_role`` is the inferred semantic role.",
"properties": {
"name": {
"description": "Declared property name, including the leading ``--``.",
"examples": [
"--fgColor-default"
],
"title": "Name",
"type": "string"
},
"color": {
"$ref": "#/$defs/Color",
"description": "The token's value resolved in the rendered theme."
},
"semantic_role": {
"$ref": "#/$defs/TokenSemanticRole",
"description": "The inferred semantic role for the token."
}
},
"required": [
"name",
"color",
"semantic_role"
],
"title": "DesignToken",
"type": "object"
},
"DivergenceItem": {
"description": "A declared-but-unused or used-but-undeclared palette discrepancy.\n\nKeyed by [`UsageRole`][colorsense.UsageRole] \u2014 the role-keyed usage view is where\ndeclared token intent is reconciled against measured usage.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role the discrepancy was found under."
},
"color": {
"$ref": "#/$defs/Color",
"description": "The declared-but-unused or used-but-undeclared color."
},
"note": {
"description": "Human-readable explanation of the discrepancy.",
"examples": [
"declared as brand_primary but not measured in use"
],
"title": "Note",
"type": "string"
}
},
"required": [
"role",
"color",
"note"
],
"title": "DivergenceItem",
"type": "object"
},
"PropertyFamily": {
"description": "Which family of CSS properties paints the color \u2014 the rollup axis over roles.\n\nCoarser than [`UsageRole`][colorsense.UsageRole]: every role belongs to exactly one\nfamily (the mapping is [`UsageRole.property_family`][colorsense.UsageRole.property_family]).\n\nAttributes:\n BACKGROUND: ``background-color`` / ``background-image`` fills.\n TEXT: The ``color`` property (typography).\n BORDER: The ``border-color`` property.",
"enum": [
"background",
"text",
"border"
],
"title": "PropertyFamily",
"type": "string"
},
"Theme": {
"description": "Color scheme a site is rendered under.\n\nAttributes:\n LIGHT: Light color scheme (the default).\n DARK: Dark color scheme (``prefers-color-scheme: dark``).",
"enum": [
"light",
"dark"
],
"title": "Theme",
"type": "string"
},
"TokenSemanticRole": {
"description": "Semantic role inferred for a declared design token (CSS custom property).\n\nAttributes:\n BRAND_PRIMARY: Primary brand color.\n BRAND_SECONDARY: Secondary brand color.\n BRAND_ACCENT: Accent brand color.\n INTERACTIVE: Interactive-element color (links, controls).\n SURFACE_BASE: Base page/surface background.\n SURFACE_RAISED: Raised surface background (cards, modals).\n TEXT_BODY: Body text color.\n NEUTRAL: Neutral/gray with no specific role.\n BORDER: Border/divider color.\n TEXT_ON: Foreground meant to sit on a colored fill (an \"on\" color).\n STATUS: Status color (success/warning/error/info).\n IGNORE: Not a meaningful palette token (filtered out).",
"enum": [
"brand_primary",
"brand_secondary",
"brand_accent",
"interactive",
"surface_base",
"surface_raised",
"text_body",
"neutral",
"border",
"text_on",
"status",
"ignore"
],
"title": "TokenSemanticRole",
"type": "string"
},
"Usage": {
"description": "One usage slot of a color in the color-keyed index ([`ColorUsage`][colorsense.ColorUsage]).\n\nA color may be used in several roles (e.g. the same gray as ``text`` *and* ``border``);\neach gets its own ``Usage``. ``role`` is the [`UsageRole`][colorsense.UsageRole] this\nslot describes and ``property_family`` is its [`PropertyFamily`][colorsense.PropertyFamily]\nrollup (denormalized \u2014 always ``role.property_family`` \u2014 so consumers can group by family\nwithout recomputing). ``weight`` is this color's share of *its own* usages \u2014 the role's\nrouted mass over the color's total routed mass, so a color's ``weight`` values sum to\n~1.0. ``components`` is normalized evidence within this slot: which component types\ncontributed the color to this role (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to\n~1.0 when non-empty.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role this slot describes."
},
"property_family": {
"$ref": "#/$defs/PropertyFamily",
"description": "Rollup family for ``role`` (denormalized \u2014 always ``role.property_family``)."
},
"weight": {
"description": "This role's share of the color's total routed mass; a color's ``weight`` values sum to ~1.0.",
"examples": [
0.7
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Weight",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed the color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"role",
"property_family",
"weight"
],
"title": "Usage",
"type": "object"
},
"UsageEntry": {
"description": "One color's standing within a usage role (role-keyed projection).\n\n``probability`` is the posterior prominence of this color *within its role*\n(entries of one role sum to ~1.0). ``area`` is the raw screenshot area fraction\nthe color's cluster covers \u2014 an auditable signal, not a probability. ``components``\nis normalized evidence: which component types contributed this color to this\nrole (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to ~1.0 when non-empty.",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The color this role entry describes."
},
"probability": {
"description": "Posterior prominence of this color within its role; entries of one role sum to ~1.0.",
"examples": [
0.55
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Probability",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed this color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"color",
"probability",
"area"
],
"title": "UsageEntry",
"type": "object"
},
"UsagePalette": {
"description": "The role-keyed palette projection: which colors paint each usage role.\n\n``mapping`` is guaranteed to contain every [`UsageRole`][colorsense.UsageRole]; a\nrole with no detected entries maps to an empty tuple. This invariant is enforced by an\nafter-validator that backfills any missing roles, so even ``UsagePalette()`` and\nthe empty-input path expose all eight keys.",
"properties": {
"mapping": {
"additionalProperties": {
"items": {
"$ref": "#/$defs/UsageEntry"
},
"type": "array"
},
"description": "Colors painting each [`UsageRole`][colorsense.UsageRole]; guaranteed to contain every role (an unused role maps to ``()``).",
"propertyNames": {
"$ref": "#/$defs/UsageRole"
},
"title": "Mapping",
"type": "object"
}
},
"title": "UsagePalette",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
}
},
"description": "Everything derived for a single rendered theme.\n\n* ``colors`` \u2014 the **canonical, color-keyed index**: every measured color\n ([`ColorUsage`][colorsense.ColorUsage]) with where it is used and an overall\n ``prominence`` ranking. Answers \"how is each color used?\". Third-party-dominated\n colors are excluded (they live on ``AnalysisResult.third_party_colors``).\n* ``usage`` \u2014 the **role-keyed projection** ([`UsagePalette`][colorsense.UsagePalette]):\n the reconciled posterior over usage roles (measured usage pooled with declared token\n intent). Answers \"which colors paint each role?\".\n* ``divergence`` \u2014 declared-vs-measured discrepancies, keyed by usage role.\n* ``tokens`` \u2014 declared design tokens, opt-in: ``None`` means tokens were **not\n requested** (``include_tokens=False``, the default); ``()`` means tokens were\n requested but no usable color tokens were found \u2014 the page declares no custom\n properties at all, or every declaration was filtered as non-meaningful (no\n resolvable color, ``ignore`` semantic role, or zero classification weight).",
"properties": {
"theme": {
"$ref": "#/$defs/Theme",
"description": "The theme this palette was derived for."
},
"colors": {
"description": "Canonical color-keyed index: every measured color with where it is used, ranked by ``prominence`` (third-party-dominated colors excluded).",
"items": {
"$ref": "#/$defs/ColorUsage"
},
"title": "Colors",
"type": "array"
},
"usage": {
"$ref": "#/$defs/UsagePalette",
"description": "Role-keyed projection: which colors paint each usage role (measured pooled with token intent)."
},
"divergence": {
"description": "Declared-vs-measured discrepancies, keyed by usage role.",
"items": {
"$ref": "#/$defs/DivergenceItem"
},
"title": "Divergence",
"type": "array"
},
"tokens": {
"anyOf": [
{
"items": {
"$ref": "#/$defs/DesignToken"
},
"type": "array"
},
{
"type": "null"
}
],
"default": null,
"description": "Declared design tokens, opt-in: ``None`` means tokens were not requested; ``()`` means requested but none were usable.",
"title": "Tokens"
}
},
"required": [
"theme",
"usage"
],
"title": "ThemePalette",
"type": "object"
}
Config:
frozen:True
Fields:
-
theme(Theme) -
colors(tuple[ColorUsage, ...]) -
usage(UsagePalette) -
divergence(tuple[DivergenceItem, ...]) -
tokens(tuple[DesignToken, ...] | None)
colors
pydantic-field
¶
colors: tuple[ColorUsage, ...]
Canonical color-keyed index: every measured color with where it is used, ranked by prominence (third-party-dominated colors excluded).
usage
pydantic-field
¶
usage: UsagePalette
Role-keyed projection: which colors paint each usage role (measured pooled with token intent).
divergence
pydantic-field
¶
divergence: tuple[DivergenceItem, ...]
Declared-vs-measured discrepancies, keyed by usage role.
tokens
pydantic-field
¶
tokens: tuple[DesignToken, ...] | None = None
Declared design tokens, opt-in: None means tokens were not requested; () means requested but none were usable.
colorsense.ColorUsage
pydantic-model
¶
Bases: BaseModel
A measured color and where it is used — one entry of the color-keyed canonical inventory.
prominence is the overall ranking signal blending area-truth (primary) with routed
vote mass (secondary), so dominant backgrounds rank high while zero-area brand accents
(CTA/link colors) are not buried; the colors tuple is sorted by it, descending,
with a hex tiebreak. area is the raw screenshot area fraction the color's
cluster covers (an auditable signal, not a probability). usages lists every
UsageRole the color appears in, most-used first
(weight descending, hex tiebreak).
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
},
"PropertyFamily": {
"description": "Which family of CSS properties paints the color \u2014 the rollup axis over roles.\n\nCoarser than [`UsageRole`][colorsense.UsageRole]: every role belongs to exactly one\nfamily (the mapping is [`UsageRole.property_family`][colorsense.UsageRole.property_family]).\n\nAttributes:\n BACKGROUND: ``background-color`` / ``background-image`` fills.\n TEXT: The ``color`` property (typography).\n BORDER: The ``border-color`` property.",
"enum": [
"background",
"text",
"border"
],
"title": "PropertyFamily",
"type": "string"
},
"Usage": {
"description": "One usage slot of a color in the color-keyed index ([`ColorUsage`][colorsense.ColorUsage]).\n\nA color may be used in several roles (e.g. the same gray as ``text`` *and* ``border``);\neach gets its own ``Usage``. ``role`` is the [`UsageRole`][colorsense.UsageRole] this\nslot describes and ``property_family`` is its [`PropertyFamily`][colorsense.PropertyFamily]\nrollup (denormalized \u2014 always ``role.property_family`` \u2014 so consumers can group by family\nwithout recomputing). ``weight`` is this color's share of *its own* usages \u2014 the role's\nrouted mass over the color's total routed mass, so a color's ``weight`` values sum to\n~1.0. ``components`` is normalized evidence within this slot: which component types\ncontributed the color to this role (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to\n~1.0 when non-empty.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role this slot describes."
},
"property_family": {
"$ref": "#/$defs/PropertyFamily",
"description": "Rollup family for ``role`` (denormalized \u2014 always ``role.property_family``)."
},
"weight": {
"description": "This role's share of the color's total routed mass; a color's ``weight`` values sum to ~1.0.",
"examples": [
0.7
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Weight",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed the color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"role",
"property_family",
"weight"
],
"title": "Usage",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
}
},
"description": "A measured color and where it is used \u2014 one entry of the color-keyed canonical inventory.\n\n``prominence`` is the overall ranking signal blending area-truth (primary) with routed\nvote mass (secondary), so dominant backgrounds rank high while zero-area brand accents\n(CTA/link colors) are not buried; the ``colors`` tuple is sorted by it, descending,\nwith a ``hex`` tiebreak. ``area`` is the raw screenshot area fraction the color's\ncluster covers (an auditable signal, not a probability). ``usages`` lists every\n[`UsageRole`][colorsense.UsageRole] the color appears in, most-used first\n(``weight`` descending, ``hex`` tiebreak).",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The measured color this inventory entry describes."
},
"prominence": {
"description": "Overall ranking signal blending area-truth (primary) with routed vote mass (secondary); the ``colors`` tuple is sorted by it, descending.",
"examples": [
0.83
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Prominence",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"usages": {
"description": "Every usage role the color appears in, most-used first (``weight`` descending).",
"items": {
"$ref": "#/$defs/Usage"
},
"title": "Usages",
"type": "array"
}
},
"required": [
"color",
"prominence",
"area"
],
"title": "ColorUsage",
"type": "object"
}
Config:
frozen:True
Fields:
prominence
pydantic-field
¶
prominence: float
Overall ranking signal blending area-truth (primary) with routed vote mass (secondary); the colors tuple is sorted by it, descending.
area
pydantic-field
¶
area: float
Raw screenshot area fraction the color's cluster covers (auditable, not a probability).
colorsense.Usage
pydantic-model
¶
Bases: BaseModel
One usage slot of a color in the color-keyed index (ColorUsage).
A color may be used in several roles (e.g. the same gray as text and border);
each gets its own Usage. role is the UsageRole this
slot describes and property_family is its PropertyFamily
rollup (denormalized — always role.property_family — so consumers can group by family
without recomputing). weight is this color's share of its own usages — the role's
routed mass over the color's total routed mass, so a color's weight values sum to
~1.0. components is normalized evidence within this slot: which component types
contributed the color to this role (e.g. {card_bg: 0.7, modal_bg: 0.3}), summing to
~1.0 when non-empty.
Show JSON schema:
{
"$defs": {
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
},
"PropertyFamily": {
"description": "Which family of CSS properties paints the color \u2014 the rollup axis over roles.\n\nCoarser than [`UsageRole`][colorsense.UsageRole]: every role belongs to exactly one\nfamily (the mapping is [`UsageRole.property_family`][colorsense.UsageRole.property_family]).\n\nAttributes:\n BACKGROUND: ``background-color`` / ``background-image`` fills.\n TEXT: The ``color`` property (typography).\n BORDER: The ``border-color`` property.",
"enum": [
"background",
"text",
"border"
],
"title": "PropertyFamily",
"type": "string"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
}
},
"description": "One usage slot of a color in the color-keyed index ([`ColorUsage`][colorsense.ColorUsage]).\n\nA color may be used in several roles (e.g. the same gray as ``text`` *and* ``border``);\neach gets its own ``Usage``. ``role`` is the [`UsageRole`][colorsense.UsageRole] this\nslot describes and ``property_family`` is its [`PropertyFamily`][colorsense.PropertyFamily]\nrollup (denormalized \u2014 always ``role.property_family`` \u2014 so consumers can group by family\nwithout recomputing). ``weight`` is this color's share of *its own* usages \u2014 the role's\nrouted mass over the color's total routed mass, so a color's ``weight`` values sum to\n~1.0. ``components`` is normalized evidence within this slot: which component types\ncontributed the color to this role (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to\n~1.0 when non-empty.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role this slot describes."
},
"property_family": {
"$ref": "#/$defs/PropertyFamily",
"description": "Rollup family for ``role`` (denormalized \u2014 always ``role.property_family``)."
},
"weight": {
"description": "This role's share of the color's total routed mass; a color's ``weight`` values sum to ~1.0.",
"examples": [
0.7
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Weight",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed the color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"role",
"property_family",
"weight"
],
"title": "Usage",
"type": "object"
}
Config:
frozen:True
Fields:
-
role(UsageRole) -
property_family(PropertyFamily) -
weight(float) -
components(dict[ComponentType, float])
property_family
pydantic-field
¶
property_family: PropertyFamily
Rollup family for role (denormalized — always role.property_family).
weight
pydantic-field
¶
weight: float
This role's share of the color's total routed mass; a color's weight values sum to ~1.0.
components
pydantic-field
¶
components: dict[ComponentType, float]
Normalized evidence: which component types contributed the color to this role (each value in [0, 1], summing to ~1.0 when non-empty).
colorsense.UsagePalette
pydantic-model
¶
Bases: BaseModel
The role-keyed palette projection: which colors paint each usage role.
mapping is guaranteed to contain every UsageRole; a
role with no detected entries maps to an empty tuple. This invariant is enforced by an
after-validator that backfills any missing roles, so even UsagePalette() and
the empty-input path expose all eight keys.
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
},
"UsageEntry": {
"description": "One color's standing within a usage role (role-keyed projection).\n\n``probability`` is the posterior prominence of this color *within its role*\n(entries of one role sum to ~1.0). ``area`` is the raw screenshot area fraction\nthe color's cluster covers \u2014 an auditable signal, not a probability. ``components``\nis normalized evidence: which component types contributed this color to this\nrole (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to ~1.0 when non-empty.",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The color this role entry describes."
},
"probability": {
"description": "Posterior prominence of this color within its role; entries of one role sum to ~1.0.",
"examples": [
0.55
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Probability",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed this color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"color",
"probability",
"area"
],
"title": "UsageEntry",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
}
},
"description": "The role-keyed palette projection: which colors paint each usage role.\n\n``mapping`` is guaranteed to contain every [`UsageRole`][colorsense.UsageRole]; a\nrole with no detected entries maps to an empty tuple. This invariant is enforced by an\nafter-validator that backfills any missing roles, so even ``UsagePalette()`` and\nthe empty-input path expose all eight keys.",
"properties": {
"mapping": {
"additionalProperties": {
"items": {
"$ref": "#/$defs/UsageEntry"
},
"type": "array"
},
"description": "Colors painting each [`UsageRole`][colorsense.UsageRole]; guaranteed to contain every role (an unused role maps to ``()``).",
"propertyNames": {
"$ref": "#/$defs/UsageRole"
},
"title": "Mapping",
"type": "object"
}
},
"title": "UsagePalette",
"type": "object"
}
Config:
frozen:True
Fields:
-
mapping(dict[UsageRole, tuple[UsageEntry, ...]])
Validators:
-
_backfill_roles
mapping
pydantic-field
¶
mapping: dict[UsageRole, tuple[UsageEntry, ...]]
Colors painting each UsageRole; guaranteed to contain every role (an unused role maps to ()).
colorsense.UsageEntry
pydantic-model
¶
Bases: BaseModel
One color's standing within a usage role (role-keyed projection).
probability is the posterior prominence of this color within its role
(entries of one role sum to ~1.0). area is the raw screenshot area fraction
the color's cluster covers — an auditable signal, not a probability. components
is normalized evidence: which component types contributed this color to this
role (e.g. {card_bg: 0.7, modal_bg: 0.3}), summing to ~1.0 when non-empty.
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"ComponentType": {
"description": "Visual component a rendered element belongs to (source of a measured color).\n\nPublic: keys the fine-grained component evidence in the result tree \u2014 both\n[`Usage`][colorsense.Usage]``.components`` on the color-keyed index and\n[`UsageEntry`][colorsense.UsageEntry]``.components`` on the role-keyed projection \u2014\nnaming which component types contributed a color to a usage role.\n\nAttributes:\n PAGE_BG: Page background.\n PAGE_TEXT: Page body text.\n HEADER_BG: Header background.\n HEADER_TEXT: Header text.\n NAV_BG: Nav background.\n NAV_TEXT: Nav text.\n FOOTER_BG: Footer background.\n FOOTER_TEXT: Footer text.\n HERO_BG: Hero background.\n HERO_TEXT: Hero text.\n CARD_BG: Card background.\n CARD_TEXT: Card text.\n CTA_BG: Primary call-to-action button background.\n CTA_TEXT: Primary call-to-action button text/label.\n LINK: Hyperlink text color.\n BUTTON_SECONDARY: Secondary button background.\n MODAL_BG: Modal/dialog background.\n INPUT_BG: Form input background.\n BORDER: Border/divider color.\n BADGE: Badge/chip/pill background.\n THIRD_PARTY: Color from an embedded third-party widget.",
"enum": [
"page_bg",
"page_text",
"header_bg",
"header_text",
"nav_bg",
"nav_text",
"footer_bg",
"footer_text",
"hero_bg",
"hero_text",
"card_bg",
"card_text",
"cta_bg",
"cta_text",
"link",
"button_secondary",
"modal_bg",
"input_bg",
"border",
"badge",
"third_party"
],
"title": "ComponentType",
"type": "string"
}
},
"description": "One color's standing within a usage role (role-keyed projection).\n\n``probability`` is the posterior prominence of this color *within its role*\n(entries of one role sum to ~1.0). ``area`` is the raw screenshot area fraction\nthe color's cluster covers \u2014 an auditable signal, not a probability. ``components``\nis normalized evidence: which component types contributed this color to this\nrole (e.g. ``{card_bg: 0.7, modal_bg: 0.3}``), summing to ~1.0 when non-empty.",
"properties": {
"color": {
"$ref": "#/$defs/Color",
"description": "The color this role entry describes."
},
"probability": {
"description": "Posterior prominence of this color within its role; entries of one role sum to ~1.0.",
"examples": [
0.55
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Probability",
"type": "number"
},
"area": {
"description": "Raw screenshot area fraction the color's cluster covers (auditable, not a probability).",
"examples": [
0.31
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Area",
"type": "number"
},
"components": {
"additionalProperties": {
"type": "number"
},
"description": "Normalized evidence: which component types contributed this color to this role (each value in ``[0, 1]``, summing to ~1.0 when non-empty).",
"examples": [
{
"card_bg": 0.7,
"modal_bg": 0.3
}
],
"propertyNames": {
"$ref": "#/$defs/ComponentType"
},
"title": "Components",
"type": "object"
}
},
"required": [
"color",
"probability",
"area"
],
"title": "UsageEntry",
"type": "object"
}
Config:
frozen:True
Fields:
-
color(Color) -
probability(float) -
area(float) -
components(dict[ComponentType, float])
probability
pydantic-field
¶
probability: float
Posterior prominence of this color within its role; entries of one role sum to ~1.0.
area
pydantic-field
¶
area: float
Raw screenshot area fraction the color's cluster covers (auditable, not a probability).
components
pydantic-field
¶
components: dict[ComponentType, float]
Normalized evidence: which component types contributed this color to this role (each value in [0, 1], summing to ~1.0 when non-empty).
colorsense.DivergenceItem
pydantic-model
¶
Bases: BaseModel
A declared-but-unused or used-but-undeclared palette discrepancy.
Keyed by UsageRole — the role-keyed usage view is where
declared token intent is reconciled against measured usage.
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
}
},
"description": "A declared-but-unused or used-but-undeclared palette discrepancy.\n\nKeyed by [`UsageRole`][colorsense.UsageRole] \u2014 the role-keyed usage view is where\ndeclared token intent is reconciled against measured usage.",
"properties": {
"role": {
"$ref": "#/$defs/UsageRole",
"description": "The usage role the discrepancy was found under."
},
"color": {
"$ref": "#/$defs/Color",
"description": "The declared-but-unused or used-but-undeclared color."
},
"note": {
"description": "Human-readable explanation of the discrepancy.",
"examples": [
"declared as brand_primary but not measured in use"
],
"title": "Note",
"type": "string"
}
},
"required": [
"role",
"color",
"note"
],
"title": "DivergenceItem",
"type": "object"
}
Config:
frozen:True
Fields:
colorsense.DesignToken
pydantic-model
¶
Bases: BaseModel
A declared design token (CSS custom property) in the public result.
name is the declared property name (e.g. --fgColor-default); color is its
value resolved in the rendered theme; semantic_role is the inferred semantic role.
Show JSON schema:
{
"$defs": {
"Color": {
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
},
"TokenSemanticRole": {
"description": "Semantic role inferred for a declared design token (CSS custom property).\n\nAttributes:\n BRAND_PRIMARY: Primary brand color.\n BRAND_SECONDARY: Secondary brand color.\n BRAND_ACCENT: Accent brand color.\n INTERACTIVE: Interactive-element color (links, controls).\n SURFACE_BASE: Base page/surface background.\n SURFACE_RAISED: Raised surface background (cards, modals).\n TEXT_BODY: Body text color.\n NEUTRAL: Neutral/gray with no specific role.\n BORDER: Border/divider color.\n TEXT_ON: Foreground meant to sit on a colored fill (an \"on\" color).\n STATUS: Status color (success/warning/error/info).\n IGNORE: Not a meaningful palette token (filtered out).",
"enum": [
"brand_primary",
"brand_secondary",
"brand_accent",
"interactive",
"surface_base",
"surface_raised",
"text_body",
"neutral",
"border",
"text_on",
"status",
"ignore"
],
"title": "TokenSemanticRole",
"type": "string"
}
},
"description": "A declared design token (CSS custom property) in the public result.\n\n``name`` is the declared property name (e.g. ``--fgColor-default``); ``color`` is its\nvalue resolved in the rendered theme; ``semantic_role`` is the inferred semantic role.",
"properties": {
"name": {
"description": "Declared property name, including the leading ``--``.",
"examples": [
"--fgColor-default"
],
"title": "Name",
"type": "string"
},
"color": {
"$ref": "#/$defs/Color",
"description": "The token's value resolved in the rendered theme."
},
"semantic_role": {
"$ref": "#/$defs/TokenSemanticRole",
"description": "The inferred semantic role for the token."
}
},
"required": [
"name",
"color",
"semantic_role"
],
"title": "DesignToken",
"type": "object"
}
Config:
frozen:True
Fields:
-
name(str) -
color(Color) -
semantic_role(TokenSemanticRole)
semantic_role
pydantic-field
¶
semantic_role: TokenSemanticRole
The inferred semantic role for the token.
colorsense.Color
pydantic-model
¶
Bases: BaseModel
An sRGB color with cached OKLCH coordinates.
hex is always the opaque normalized lowercase 7-char sRGB hex string
(#rrggbb) — alpha is carried separately in alpha and never encoded in the
hex (the invariant color/primitives.py establishes; fixed-length hexes are also
what keeps lexicographic tie-breaks well-defined). lightness/chroma/hue
are the OKLCH coordinates of the (composited) color; alpha is the source alpha.
Show JSON schema:
{
"description": "An sRGB color with cached OKLCH coordinates.\n\n``hex`` is always the *opaque* normalized lowercase 7-char sRGB hex string\n(``#rrggbb``) \u2014 alpha is carried separately in ``alpha`` and never encoded in the\nhex (the invariant ``color/primitives.py`` establishes; fixed-length hexes are also\nwhat keeps lexicographic tie-breaks well-defined). ``lightness``/``chroma``/``hue``\nare the OKLCH coordinates of the (composited) color; ``alpha`` is the source alpha.",
"properties": {
"hex": {
"description": "Opaque normalized lowercase 7-char sRGB hex (``#rrggbb``); alpha is never encoded here (it lives in ``alpha``).",
"examples": [
"#1f6feb",
"#ffffff",
"#0d1117"
],
"pattern": "^#[0-9a-f]{6}$",
"title": "Hex",
"type": "string"
},
"lightness": {
"description": "OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.",
"examples": [
0.62
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Lightness",
"type": "number"
},
"chroma": {
"description": "OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.",
"examples": [
0.18
],
"minimum": 0.0,
"title": "Chroma",
"type": "number"
},
"hue": {
"description": "OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).",
"examples": [
256.3
],
"exclusiveMaximum": 360.0,
"minimum": 0.0,
"title": "Hue",
"type": "number"
},
"alpha": {
"default": 1.0,
"description": "Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.",
"examples": [
1.0
],
"maximum": 1.0,
"minimum": 0.0,
"title": "Alpha",
"type": "number"
}
},
"required": [
"hex",
"lightness",
"chroma",
"hue"
],
"title": "Color",
"type": "object"
}
Config:
frozen:True
Fields:
hex
pydantic-field
¶
hex: str
Opaque normalized lowercase 7-char sRGB hex (#rrggbb); alpha is never encoded here (it lives in alpha).
lightness
pydantic-field
¶
lightness: float
OKLCH lightness of the (composited) color; 0.0 is black, 1.0 is white.
chroma
pydantic-field
¶
chroma: float
OKLCH chroma (saturation) of the (composited) color; 0.0 is achromatic. Unbounded above, though sRGB colors rarely exceed ~0.4.
hue
pydantic-field
¶
hue: float
OKLCH hue angle in degrees; achromatic colors are normalized to 0.0 (never NaN).
alpha
pydantic-field
¶
alpha: float = 1.0
Source alpha: 0.0 is fully transparent, 1.0 is fully opaque.
colorsense.Viewport
pydantic-model
¶
Bases: BaseModel
Rendering viewport.
Show JSON schema:
{
"description": "Rendering viewport.",
"properties": {
"width": {
"description": "Viewport width in CSS pixels.",
"examples": [
1280
],
"minimum": 1,
"title": "Width",
"type": "integer"
},
"height": {
"description": "Viewport height in CSS pixels.",
"examples": [
800
],
"minimum": 1,
"title": "Height",
"type": "integer"
},
"device_scale_factor": {
"description": "Device pixel ratio (DIP\u2192raster multiplier); 1.0 is non-retina, 2.0 is retina.",
"examples": [
1.0
],
"exclusiveMinimum": 0.0,
"title": "Device Scale Factor",
"type": "number"
}
},
"required": [
"width",
"height",
"device_scale_factor"
],
"title": "Viewport",
"type": "object"
}
Config:
frozen:True
Fields:
-
width(int) -
height(int) -
device_scale_factor(float)
Enums¶
colorsense.Theme ¶
Bases: StrEnum
Color scheme a site is rendered under.
Attributes:
| Name | Type | Description |
|---|---|---|
LIGHT |
Light color scheme (the default). |
|
DARK |
Dark color scheme ( |
colorsense.UsageRole ¶
Bases: StrEnum
Developer-facing usage role — how a rendered color is used on the page.
The role taxonomy keys the role-keyed palette projection
(UsagePalette) and labels each
Usage slot of the color-keyed index. It splits the two axes the
old 4-value usage taxonomy conflated — which CSS property paints the color (the
PropertyFamily rollup) versus what kind of element it
is — so that, e.g., link text and CTA button backgrounds no longer share one slot.
Attributes:
| Name | Type | Description |
|---|---|---|
PAGE |
The base canvas (the page background). |
|
SURFACE |
Raised content backgrounds: cards, modals, hero, inputs. |
|
BANNER |
Chrome-bar backgrounds: header, nav, footer. |
|
CTA |
The primary action background (CTA buttons). |
|
ACTION |
Secondary action backgrounds: secondary buttons, badges. |
|
TEXT |
Body/heading typography at every layer. |
|
LINK |
Link color (typography of anchors). |
|
BORDER |
Borders and dividers. |
property_family
property
¶
property_family: PropertyFamily
The PropertyFamily this usage role rolls up to.
A fixed code-level convention, the role-side twin of
ComponentType.property_family: text
and link are painted by the element's color (the text family), border
by its border-color, and every other role
(page/surface/banner/cta/action) by a background property. link
is a text role even though it names an interactive element, because a link's painted
color is its typography color, not its (usually transparent) background.
Returns:
| Type | Description |
|---|---|
PropertyFamily
|
|
PropertyFamily
|
|
PropertyFamily
|
|
colorsense.PropertyFamily ¶
Bases: StrEnum
Which family of CSS properties paints the color — the rollup axis over roles.
Coarser than UsageRole: every role belongs to exactly one
family (the mapping is UsageRole.property_family).
Attributes:
| Name | Type | Description |
|---|---|---|
BACKGROUND |
|
|
TEXT |
The |
|
BORDER |
The |
colorsense.ComponentType ¶
Bases: StrEnum
Visual component a rendered element belongs to (source of a measured color).
Public: keys the fine-grained component evidence in the result tree — both
Usage.components on the color-keyed index and
UsageEntry.components on the role-keyed projection —
naming which component types contributed a color to a usage role.
Attributes:
| Name | Type | Description |
|---|---|---|
PAGE_BG |
Page background. |
|
PAGE_TEXT |
Page body text. |
|
HEADER_BG |
Header background. |
|
HEADER_TEXT |
Header text. |
|
NAV_BG |
Nav background. |
|
NAV_TEXT |
Nav text. |
|
FOOTER_BG |
Footer background. |
|
FOOTER_TEXT |
Footer text. |
|
HERO_BG |
Hero background. |
|
HERO_TEXT |
Hero text. |
|
CARD_BG |
Card background. |
|
CARD_TEXT |
Card text. |
|
CTA_BG |
Primary call-to-action button background. |
|
CTA_TEXT |
Primary call-to-action button text/label. |
|
LINK |
Hyperlink text color. |
|
BUTTON_SECONDARY |
Secondary button background. |
|
MODAL_BG |
Modal/dialog background. |
|
INPUT_BG |
Form input background. |
|
BORDER |
Border/divider color. |
|
BADGE |
Badge/chip/pill background. |
|
THIRD_PARTY |
Color from an embedded third-party widget. |
property_family
property
¶
property_family: PropertyFamily
The PropertyFamily this component's vote mass routes to.
The routing convention is fixed in code, the component-side twin of
UsageRole.property_family: components whose
value ends with _text — plus link, whose painted color is its typography color,
not its (usually transparent) background — are painted by the element's color (the
text family), border by its border-color, and everything else (including
badge, third_party and button_secondary) by its background-color (the
background family).
This is the single source of truth for component routing, shared by the component
classifier's per-family normalization (classify/components.py) and the inventory's
per-family attribution (palette/inventory.py). The two partitions MUST stay
identical, so both read this one property — it lives here in the shared-contracts module
so neither importer creates a cross-layer dependency.
Returns:
| Type | Description |
|---|---|
PropertyFamily
|
|
PropertyFamily
|
|
PropertyFamily
|
|
colorsense.TokenSemanticRole ¶
Bases: StrEnum
Semantic role inferred for a declared design token (CSS custom property).
Attributes:
| Name | Type | Description |
|---|---|---|
BRAND_PRIMARY |
Primary brand color. |
|
BRAND_SECONDARY |
Secondary brand color. |
|
BRAND_ACCENT |
Accent brand color. |
|
INTERACTIVE |
Interactive-element color (links, controls). |
|
SURFACE_BASE |
Base page/surface background. |
|
SURFACE_RAISED |
Raised surface background (cards, modals). |
|
TEXT_BODY |
Body text color. |
|
NEUTRAL |
Neutral/gray with no specific role. |
|
BORDER |
Border/divider color. |
|
TEXT_ON |
Foreground meant to sit on a colored fill (an "on" color). |
|
STATUS |
Status color (success/warning/error/info). |
|
IGNORE |
Not a meaningful palette token (filtered out). |
Configuration¶
colorsense.Config
pydantic-model
¶
Bases: BaseModel
The fully-loaded palette configuration plus token helper methods.
Show JSON schema:
{
"$defs": {
"AnchorRange": {
"description": "Inclusive ``[low, high]`` anchor-step range for a scale convention.",
"properties": {
"low": {
"title": "Low",
"type": "integer"
},
"high": {
"title": "High",
"type": "integer"
}
},
"required": [
"low",
"high"
],
"title": "AnchorRange",
"type": "object"
},
"ChannelRoute": {
"description": "One arm of ``UsageIntentOrChannel``: routes a token to a non-palette channel.\n\nChannel routes (``text_on`` / ``status`` / ``ignore``) carry no usage\ndistribution: the token is reported on its side-channel, not ranked by usage.",
"properties": {
"channel": {
"title": "Channel",
"type": "string"
}
},
"required": [
"channel"
],
"title": "ChannelRoute",
"type": "object"
},
"CircleBadgeConfig": {
"description": "Recognizes small recurring clickable circular chips as badges.\n\nA perfect circle (``rounded-full`` with ``width == height``) is NOT a pill \u2014 the pill\ntest demands ``width > height`` \u2014 so the badge geometry rule never matches it, and a\nsmall circular chip with no text node (an icon-only corner badge) falls through to the\ncard detector and leaks its accent into ``surface``. This family promotes a small circle\nto ``badge`` ONLY when it is clickable, paints a fill, and RECURS as a group of at least\n``min_siblings`` structurally-similar siblings \u2014 the signature of a repeated UI chip\n(supabase's 54 black corner badges), as opposed to a lone decorative dot (a single\n``rounded-full`` divot, kept out of every role) or a one-off clickable status dot (whose\ncolor already wins ``link`` from its text channel \u2014 the recurrence gate is what protects\nthat lone dot from being wrongly promoted to ``action``). No ``has_text`` gate, because\nthese chips carry an icon, not a text node.",
"properties": {
"max_h_px": {
"exclusiveMinimum": 0.0,
"title": "Max H Px",
"type": "number"
},
"min_siblings": {
"minimum": 1,
"title": "Min Siblings",
"type": "integer"
},
"votes": {
"additionalProperties": {
"type": "number"
},
"title": "Votes",
"type": "object"
}
},
"required": [
"max_h_px",
"min_siblings",
"votes"
],
"title": "CircleBadgeConfig",
"type": "object"
},
"ComponentClassifierConfig": {
"description": "The ``component_classifier`` config domain.",
"properties": {
"softmax_temperature": {
"exclusiveMinimum": 0.0,
"title": "Softmax Temperature",
"type": "number"
},
"min_component_prob": {
"maximum": 1.0,
"minimum": 0.0,
"title": "Min Component Prob",
"type": "number"
},
"semantic_tags": {
"items": {
"$ref": "#/$defs/VoteRule"
},
"title": "Semantic Tags",
"type": "array"
},
"geometry": {
"$ref": "#/$defs/GeometryConfig"
},
"class_tokens": {
"items": {
"$ref": "#/$defs/VoteRule"
},
"title": "Class Tokens",
"type": "array"
},
"interactivity": {
"items": {
"$ref": "#/$defs/WhenRule"
},
"title": "Interactivity",
"type": "array"
},
"border_presence": {
"$ref": "#/$defs/PresenceRule"
},
"text_presence": {
"$ref": "#/$defs/PresenceRule"
},
"repetition": {
"$ref": "#/$defs/RepetitionConfig"
},
"circle_badge": {
"$ref": "#/$defs/CircleBadgeConfig"
},
"page_canvas_fallback": {
"$ref": "#/$defs/PageCanvasFallbackConfig"
},
"third_party": {
"$ref": "#/$defs/ThirdPartyConfig"
},
"suppressors": {
"additionalProperties": {
"$ref": "#/$defs/Suppressor"
},
"title": "Suppressors",
"type": "object"
},
"brand_components": {
"items": {
"type": "string"
},
"title": "Brand Components",
"type": "array"
},
"contrast_relabel": {
"$ref": "#/$defs/ContrastRelabelConfig"
}
},
"required": [
"softmax_temperature",
"min_component_prob",
"semantic_tags",
"geometry",
"class_tokens",
"interactivity",
"border_presence",
"text_presence",
"repetition",
"circle_badge",
"page_canvas_fallback",
"third_party",
"suppressors",
"brand_components",
"contrast_relabel"
],
"title": "ComponentClassifierConfig",
"type": "object"
},
"ContrastRelabelConfig": {
"description": "Thresholds for the CTA-label contrast relabel (see ``classify.components``).\n\nA non-anchor clickable element whose label text sits on its own distinct interactive\nfill \u2014 legible against that fill but illegible on the page canvas \u2014 is a CTA *label*,\nnot an inline link: its text-channel ``link`` vote is relabeled to ``cta_text``\n(channel-preserving). The predicate compares the element's composited ``effective_bg``\nagainst the derived page canvas with ``ciede2000`` and tests text legibility with the\nWCAG ``contrast_ratio``. Both thresholds are principled constants, not panel-fitted:\n``wcag_min_contrast`` is the WCAG 2.x legibility floor and ``canvas_delta_e`` is the\nperceptual just-noticeable-difference floor that defines color identity.",
"properties": {
"wcag_min_contrast": {
"exclusiveMinimum": 1.0,
"maximum": 21.0,
"title": "Wcag Min Contrast",
"type": "number"
},
"canvas_delta_e": {
"exclusiveMinimum": 0.0,
"title": "Canvas Delta E",
"type": "number"
}
},
"required": [
"wcag_min_contrast",
"canvas_delta_e"
],
"title": "ContrastRelabelConfig",
"type": "object"
},
"GeometryConfig": {
"description": "The geometry feature family: thresholds plus positional rules.",
"properties": {
"thresholds": {
"$ref": "#/$defs/GeometryThresholds"
},
"rules": {
"items": {
"$ref": "#/$defs/WhenRule"
},
"title": "Rules",
"type": "array"
}
},
"required": [
"thresholds",
"rules"
],
"title": "GeometryConfig",
"type": "object"
},
"GeometryThresholds": {
"description": "Geometry thresholds (fractions of the viewport, or pixels).",
"properties": {
"top_band": {
"title": "Top Band",
"type": "number"
},
"bottom_band": {
"title": "Bottom Band",
"type": "number"
},
"full_width": {
"title": "Full Width",
"type": "number"
},
"short_h": {
"title": "Short H",
"type": "number"
},
"hero_min_h": {
"title": "Hero Min H",
"type": "number"
},
"sticky_top_px": {
"title": "Sticky Top Px",
"type": "number"
},
"small_area": {
"title": "Small Area",
"type": "number"
},
"badge_max_h_px": {
"title": "Badge Max H Px",
"type": "number"
}
},
"required": [
"top_band",
"bottom_band",
"full_width",
"short_h",
"hero_min_h",
"sticky_top_px",
"small_area",
"badge_max_h_px"
],
"title": "GeometryThresholds",
"type": "object"
},
"MatchType": {
"description": "How a name rule's ``match`` string is compared against a token name.",
"enum": [
"substring",
"exact",
"regex"
],
"title": "MatchType",
"type": "string"
},
"NameRule": {
"description": "A single token-name -> semantic-role rule.",
"properties": {
"match": {
"title": "Match",
"type": "string"
},
"role": {
"$ref": "#/$defs/TokenSemanticRole"
},
"weight": {
"minimum": 0.0,
"title": "Weight",
"type": "number"
},
"match_type": {
"$ref": "#/$defs/MatchType",
"default": "substring"
}
},
"required": [
"match",
"role",
"weight"
],
"title": "NameRule",
"type": "object"
},
"PageCanvasFallbackConfig": {
"description": "Fallback for sites whose canonical canvas (``html``/``body``/``main``) is transparent.\n\nModern utility-CSS sites (e.g. shadcn) leave ``html``/``body``/``main`` with\n``background: transparent`` and paint the white page surface on a single\nfull-viewport ``<div>`` instead. That div's only ``page_bg`` signal is a few weak\nclass-token votes, which the geometry ``hero_bg`` vote and the repetition ``card_bg``\nvote bury in the bg-channel softmax \u2014 so the real page color never reaches the ``page``\nrole. When (and only when) the canonical canvas paints no opaque background, the\nclassifier treats the largest viewport-spanning opaque-bg element near the top of the\npage as the page canvas: it injects a ``page_bg`` prior and removes the ``suppress``\ncomponents (the competing hero/card votes) from that one element. Opaque-body sites\nnever trigger this, so they are untouched. The full-width and top-band gates reuse the\ngeometry thresholds (``full_width``, ``top_band``), so this is a fraction-of-viewport\ntest, not absolute pixels.",
"properties": {
"page_bg_vote": {
"minimum": 0.0,
"title": "Page Bg Vote",
"type": "number"
},
"color_match_delta_e": {
"minimum": 0.0,
"title": "Color Match Delta E",
"type": "number"
},
"suppress": {
"items": {
"type": "string"
},
"title": "Suppress",
"type": "array"
}
},
"required": [
"page_bg_vote",
"color_match_delta_e",
"suppress"
],
"title": "PageCanvasFallbackConfig",
"type": "object"
},
"PresenceRule": {
"description": "A presence-gated feature family: ``{votes: {component: weight}}``.\n\nApplied when a structural fact holds for an element (e.g. it paints a border, or it\nhas direct text content) \u2014 there is no ``match``/``when`` string because the gating\npredicate is the feature family itself (fixed in ``classify.components``).",
"properties": {
"votes": {
"additionalProperties": {
"type": "number"
},
"title": "Votes",
"type": "object"
}
},
"required": [
"votes"
],
"title": "PresenceRule",
"type": "object"
},
"RelationalModifier": {
"description": "A regex modifier that reroutes a token to ``text-on-<base>``.\n\nThe ``pattern`` must compile and contain a named capture group ``base`` \u2014 enforced at\nload so a typo'd pattern fails loudly instead of becoming a rule that silently never\nreroutes anything (``match_relational`` skips matches without a ``base`` group).",
"properties": {
"pattern": {
"title": "Pattern",
"type": "string"
},
"type": {
"title": "Type",
"type": "string"
},
"weight": {
"minimum": 0.0,
"title": "Weight",
"type": "number"
}
},
"required": [
"pattern",
"type",
"weight"
],
"title": "RelationalModifier",
"type": "object"
},
"RepetitionConfig": {
"description": "The repetition (card detector) feature family.",
"properties": {
"min_siblings": {
"title": "Min Siblings",
"type": "integer"
},
"requires_any": {
"items": {
"type": "string"
},
"title": "Requires Any",
"type": "array"
},
"votes": {
"additionalProperties": {
"type": "number"
},
"title": "Votes",
"type": "object"
}
},
"required": [
"min_siblings",
"requires_any",
"votes"
],
"title": "RepetitionConfig",
"type": "object"
},
"ScaleDetectionConfig": {
"description": "Numbered-scale detection settings.",
"properties": {
"enabled": {
"title": "Enabled",
"type": "boolean"
},
"number_pattern": {
"title": "Number Pattern",
"type": "string"
},
"chromatic_families": {
"items": {
"type": "string"
},
"title": "Chromatic Families",
"type": "array"
},
"neutral_families": {
"items": {
"type": "string"
},
"title": "Neutral Families",
"type": "array"
},
"anchor_ranges": {
"additionalProperties": {
"$ref": "#/$defs/AnchorRange"
},
"title": "Anchor Ranges",
"type": "object"
},
"base_weight": {
"exclusiveMinimum": 0.0,
"title": "Base Weight",
"type": "number"
},
"scale_present_confidence_boost": {
"minimum": 0.0,
"title": "Scale Present Confidence Boost",
"type": "number"
}
},
"required": [
"enabled",
"number_pattern",
"chromatic_families",
"neutral_families",
"anchor_ranges",
"base_weight",
"scale_present_confidence_boost"
],
"title": "ScaleDetectionConfig",
"type": "object"
},
"Suppressor": {
"description": "A multiplicative veto applied after vote summation.\n\n``applies_to`` is restricted to the two scopes the classifier implements:\n``\"all\"`` (every accumulated vote) or ``\"brand_components\"`` (only the\nconfigured brand components, on third-party elements).",
"properties": {
"factor": {
"minimum": 0.0,
"title": "Factor",
"type": "number"
},
"applies_to": {
"enum": [
"all",
"brand_components"
],
"title": "Applies To",
"type": "string"
}
},
"required": [
"factor",
"applies_to"
],
"title": "Suppressor",
"type": "object"
},
"ThirdPartyConfig": {
"description": "The origin / third-party feature family.",
"properties": {
"votes_iframe": {
"additionalProperties": {
"type": "number"
},
"title": "Votes Iframe",
"type": "object"
},
"votes_cross_origin": {
"additionalProperties": {
"type": "number"
},
"title": "Votes Cross Origin",
"type": "object"
},
"votes_shadow_host": {
"additionalProperties": {
"type": "number"
},
"title": "Votes Shadow Host",
"type": "object"
},
"votes_vendor_match": {
"additionalProperties": {
"type": "number"
},
"title": "Votes Vendor Match",
"type": "object"
},
"vendor_prefixes": {
"items": {
"type": "string"
},
"title": "Vendor Prefixes",
"type": "array"
}
},
"required": [
"votes_iframe",
"votes_cross_origin",
"votes_shadow_host",
"votes_vendor_match",
"vendor_prefixes"
],
"title": "ThirdPartyConfig",
"type": "object"
},
"TokenSemanticRole": {
"description": "Semantic role inferred for a declared design token (CSS custom property).\n\nAttributes:\n BRAND_PRIMARY: Primary brand color.\n BRAND_SECONDARY: Secondary brand color.\n BRAND_ACCENT: Accent brand color.\n INTERACTIVE: Interactive-element color (links, controls).\n SURFACE_BASE: Base page/surface background.\n SURFACE_RAISED: Raised surface background (cards, modals).\n TEXT_BODY: Body text color.\n NEUTRAL: Neutral/gray with no specific role.\n BORDER: Border/divider color.\n TEXT_ON: Foreground meant to sit on a colored fill (an \"on\" color).\n STATUS: Status color (success/warning/error/info).\n IGNORE: Not a meaningful palette token (filtered out).",
"enum": [
"brand_primary",
"brand_secondary",
"brand_accent",
"interactive",
"surface_base",
"surface_raised",
"text_body",
"neutral",
"border",
"text_on",
"status",
"ignore"
],
"title": "TokenSemanticRole",
"type": "string"
},
"TokenVocabularyConfig": {
"description": "The ``token_vocabulary`` config domain.",
"properties": {
"namespace_prefixes": {
"items": {
"type": "string"
},
"title": "Namespace Prefixes",
"type": "array"
},
"strip_trailing": {
"items": {
"type": "string"
},
"title": "Strip Trailing",
"type": "array"
},
"known_system_confidence_boost": {
"minimum": 0.0,
"title": "Known System Confidence Boost",
"type": "number"
},
"name_rules": {
"items": {
"$ref": "#/$defs/NameRule"
},
"title": "Name Rules",
"type": "array"
},
"relational_modifiers": {
"items": {
"$ref": "#/$defs/RelationalModifier"
},
"title": "Relational Modifiers",
"type": "array"
},
"scale_detection": {
"$ref": "#/$defs/ScaleDetectionConfig"
},
"semantic_role_to_usage_intent_or_channel": {
"additionalProperties": {
"anyOf": [
{
"$ref": "#/$defs/UsageIntent"
},
{
"$ref": "#/$defs/ChannelRoute"
}
]
},
"propertyNames": {
"$ref": "#/$defs/TokenSemanticRole"
},
"title": "Semantic Role To Usage Intent Or Channel",
"type": "object"
},
"status_excluded_from_palette": {
"title": "Status Excluded From Palette",
"type": "boolean"
}
},
"required": [
"namespace_prefixes",
"strip_trailing",
"known_system_confidence_boost",
"name_rules",
"relational_modifiers",
"scale_detection",
"semantic_role_to_usage_intent_or_channel",
"status_excluded_from_palette"
],
"title": "TokenVocabularyConfig",
"type": "object"
},
"UsageIntent": {
"description": "One arm of ``UsageIntentOrChannel``: a normalized distribution over usage roles.\n\nExpresses where a semantic role's color is expected to be used, inferred\nfrom the token's name before the page is measured.",
"properties": {
"distribution": {
"additionalProperties": {
"type": "number"
},
"propertyNames": {
"$ref": "#/$defs/UsageRole"
},
"title": "Distribution",
"type": "object"
}
},
"required": [
"distribution"
],
"title": "UsageIntent",
"type": "object"
},
"UsageRole": {
"description": "Developer-facing usage role \u2014 how a rendered color is used on the page.\n\nThe role taxonomy keys the role-keyed palette projection\n([`UsagePalette`][colorsense.UsagePalette]) and labels each\n[`Usage`][colorsense.Usage] slot of the color-keyed index. It splits the two axes the\nold 4-value usage taxonomy conflated \u2014 *which CSS property paints the color* (the\n[`PropertyFamily`][colorsense.PropertyFamily] rollup) versus *what kind of element it\nis* \u2014 so that, e.g., link text and CTA button backgrounds no longer share one slot.\n\nAttributes:\n PAGE: The base canvas (the page background).\n SURFACE: Raised content backgrounds: cards, modals, hero, inputs.\n BANNER: Chrome-bar backgrounds: header, nav, footer.\n CTA: The primary action background (CTA buttons).\n ACTION: Secondary action backgrounds: secondary buttons, badges.\n TEXT: Body/heading typography at every layer.\n LINK: Link color (typography of anchors).\n BORDER: Borders and dividers.",
"enum": [
"page",
"surface",
"banner",
"cta",
"action",
"text",
"link",
"border"
],
"title": "UsageRole",
"type": "string"
},
"VoteRule": {
"description": "A ``{match: <token>, votes: {component: weight}}`` rule.",
"properties": {
"match": {
"title": "Match",
"type": "string"
},
"votes": {
"additionalProperties": {
"type": "number"
},
"title": "Votes",
"type": "object"
}
},
"required": [
"match",
"votes"
],
"title": "VoteRule",
"type": "object"
},
"WhenRule": {
"description": "A ``{when: <predicate>, votes: {component: weight}}`` rule.",
"properties": {
"when": {
"title": "When",
"type": "string"
},
"votes": {
"additionalProperties": {
"type": "number"
},
"title": "Votes",
"type": "object"
}
},
"required": [
"when",
"votes"
],
"title": "WhenRule",
"type": "object"
}
},
"description": "The fully-loaded palette configuration plus token helper methods.",
"properties": {
"token_vocabulary": {
"$ref": "#/$defs/TokenVocabularyConfig"
},
"component_classifier": {
"$ref": "#/$defs/ComponentClassifierConfig"
}
},
"required": [
"token_vocabulary",
"component_classifier"
],
"title": "Config",
"type": "object"
}
Config:
frozen:True
Fields:
-
token_vocabulary(TokenVocabularyConfig) -
component_classifier(ComponentClassifierConfig)
match_name_rule ¶
match_name_rule(
name: str,
) -> tuple[TokenSemanticRole, float] | None
Match name against name_rules with exact > regex > substring precedence.
If a known system namespace prefix was present, the returned weight is
multiplied by known_system_confidence_boost.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The token name to match (a leading |
required |
Returns:
| Type | Description |
|---|---|
tuple[TokenSemanticRole, float] | None
|
The matched |
detect_scale ¶
detect_scale(name: str) -> ScaleInfo | None
Detect a trailing scale number and identify its family.
family is the remainder once the namespace and the trailing number are
stripped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The token name to inspect (a leading |
required |
Returns:
| Type | Description |
|---|---|
ScaleInfo | None
|
A |
ScaleInfo | None
|
detection is disabled or no scale number is present. |
match_relational ¶
match_relational(name: str) -> RelationalInfo | None
Match relational_modifiers patterns against the post-strip name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The token name to match (a leading |
required |
Returns:
| Type | Description |
|---|---|
RelationalInfo | None
|
A |
RelationalInfo | None
|
and its |
colorsense.load_default_config ¶
load_default_config() -> Config
Load the palette configuration bundled with the installed package.
Resolves data/palette_config.yaml from the package itself (via
importlib.resources), so it works regardless of the current working
directory and whether the package is installed editable, as a wheel, or
zipped. This is what colorsense.analyze uses when no config_path
is given.
Returns:
| Type | Description |
|---|---|
Config
|
The validated |
colorsense.load_config ¶
load_config(path: str | Path) -> Config
Read, validate, and normalize the palette config YAML at path.
For the configuration shipped with the package, prefer
load_default_config.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str | Path
|
Filesystem path to the palette config YAML. |
required |
Returns:
| Type | Description |
|---|---|
Config
|
The validated |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the file cannot be read, or wrapping malformed YAML / a top-level non-mapping. |
ValidationError
|
On schema violations — never a bare |
Fetch policy & networking¶
colorsense.PolitenessPolicy ¶
Gate, pace, and cache page renders on behalf of a consumer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_agent
|
str
|
Identifiable User-Agent sent on the wire for both the |
DEFAULT_USER_AGENT
|
robots_agent
|
str
|
The product token matched against |
DEFAULT_ROBOTS_AGENT
|
respect_robots
|
bool
|
When |
True
|
allow_file_urls
|
bool
|
Whether |
False
|
request_filter
|
RequestFilter | None
|
Optional synchronous or asynchronous predicate
( |
None
|
max_concurrent_renders
|
int | None
|
Optional cap on simultaneous renders through this policy —
the SECURITY.md §2 concurrency bound, shipped as a knob. |
None
|
min_interval
|
float
|
Minimum seconds between same-host fetches (per-host rate limiter). When
the host's |
DEFAULT_MIN_INTERVAL
|
max_crawl_delay
|
float
|
Upper bound (seconds) on an honored |
DEFAULT_MAX_CRAWL_DELAY
|
max_cache_entries
|
int | None
|
Upper bound on the render cache ( |
DEFAULT_MAX_CACHE_ENTRIES
|
harvester
|
Harvester
|
The render function, injectable for testing. Defaults to
|
harvest_page
|
robots_loader
|
RobotsLoader
|
Injectable async |
_default_robots_loader
|
clock
|
Clock
|
Synchronous time source, injectable for tests. |
monotonic
|
sleeper
|
Sleeper
|
Injectable async sleep seam — swapped out by the test suite so no real wall-clock delay is incurred. |
sleep
|
can_fetch
async
¶
can_fetch(url: str) -> bool
Whether url may be fetched under this policy.
Non-network URLs (e.g. file:// fixtures) and a disabled robots check always
return True. A missing/unreachable robots.txt permits fetching.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The URL whose |
required |
Returns:
| Type | Description |
|---|---|
bool
|
|
fetch
async
¶
fetch(
url: str,
theme: Theme,
config: Config,
viewport: Viewport,
*,
browser: SharedBrowser | None = None,
) -> Harvest
Return a Harvest for url/theme/viewport, politely.
browser is an optional shared-browser handle forwarded to the harvester
verbatim — the policy itself knows nothing about browser lifecycle (neither
launching nor closing). colorsense.analyze passes one handle
to all of a call's theme fetches so they share a single Chromium launch; the handle is lazy,
so a fetch served from the cache (or coalesced onto an in-flight leader) never
triggers a launch. The cache key is unchanged (URL + theme + viewport): sharing a
browser does not change what is rendered, only where the context lives.
The URL scheme is validated first — before even the cache lookup, so a previously
cached file:// harvest can never be served by a policy that forbids file URLs
(raises UnsupportedSchemeError; see
allow_file_urls).
Cache hits return immediately (no robots check, no throttle, no render) and mark the
entry most-recently-used. Otherwise the per-host rate limiter is applied, the robots
gate is enforced (its robots.txt GET is the first throttled request to the host),
and the harvester awaited; the result is cached (LRU-evicting the least-recently-used
entry if the cache is bounded and now full) before return.
Concurrent misses for the same key are coalesced (single-flight): the first caller
becomes the leader and runs exactly one throttle → robots gate → render; any caller
that arrives while that render is in flight becomes a follower, awaiting the leader's
result instead of launching a redundant headless render. All followers receive the
leader's Harvest — or, if the leader's gate/render fails, the same
exception (the failure is not cached, and the next fetch re-renders). Distinct keys
never share, so unrelated renders still run in parallel.
Cancellation does not fan out like a failure: if the leader's task is
cancelled (e.g. its caller's analyze(max_total_seconds=...) deadline expires,
or its HTTP client disconnects in a server), followers never inherit the
CancelledError. They re-elect instead — exactly one follower becomes the new
leader (re-running throttle → robots gate → render) and the rest follow it; if
the render somehow completed first, the re-checking loop serves it from the cache.
A follower whose own task is cancelled still raises CancelledError normally,
and the cancelled leader still propagates its own CancelledError to its caller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
Page to fetch. Its scheme is validated before anything else. |
required |
theme
|
Theme
|
The theme to render under (part of the cache key). |
required |
config
|
Config
|
The loaded palette configuration forwarded to the harvester. |
required |
viewport
|
Viewport
|
The viewport to render at (part of the cache key). |
required |
browser
|
SharedBrowser | None
|
Optional shared-browser handle forwarded to the harvester verbatim; the policy never launches or closes it. |
None
|
Returns:
| Type | Description |
|---|---|
Harvest
|
The |
Harvest
|
the cache, or coalesced onto an in-flight render of the same key. |
Raises:
| Type | Description |
|---|---|
UnsupportedSchemeError
|
When |
RobotsDisallowedError
|
When |
colorsense.block_private_networks ¶
block_private_networks(
*,
allowed_hosts: Iterable[str] | None = None,
resolver: Resolver = _default_resolver,
ttl: float = DEFAULT_GUARD_TTL_SECONDS,
max_entries: int = DEFAULT_GUARD_MAX_ENTRIES,
clock: Clock = monotonic,
resolve_timeout: float = DEFAULT_GUARD_RESOLVE_TIMEOUT_SECONDS,
) -> Callable[[str], Awaitable[bool]]
Build a request_filter predicate that rejects non-public destinations.
The returned async predicate (await guard(url) -> bool; True permits,
False aborts; only usable under a running event loop, as the request_filter
seams are) is meant for PolitenessPolicy(request_filter=...), where the library applies
it to every http(s) URL the browser requests while rendering — the navigation,
every redirect hop, and all sub-resources, including the page's own fetch/XHR
calls — and to the policy's own robots.txt GET, whose initial URL and every
redirect Location are vetted before each request goes out. The two browser network
paths Playwright's context.route cannot intercept are closed rather than filtered:
WebSocket connections are refused outright whenever a request_filter is
configured (the handshake never reaches the network), and service workers are always
blocked at context creation — strictly stronger than per-URL vetting for both. It
implements the SECURITY.md §1 egress-filter item:
- only
http(s)URLs pass; URLs carrying userinfo (user:pass@host) are rejected; - the hostname is resolved (stdlib
getaddrinfo; IP literals pass through without a network round trip) and the URL is rejected if any resolved address is non-public: loopback, RFC 1918/private, link-local (including the 169.254.169.254 cloud metadata endpoint), CGNAT 100.64.0.0/10, unspecified, multicast, reserved, and their IPv6 equivalents (IPv6 zone suffixes are stripped before classification); - malformed URLs, resolution failures, and empty resolutions all fail closed.
The library treats a raising predicate as fail-closed, but this guard never relies on
that — it catches its own failure modes and returns False explicitly.
Honest residual gap: a URL-string predicate cannot fully defeat DNS rebinding —
Chromium resolves hostnames independently when it connects, so a hostname can flip from
public to internal between this check and the connection. Network isolation of the
browser environment remains the primary control per SECURITY.md; this filter is defense
in depth. Resolution runs off the event loop on a small thread pool the predicate
itself owns (GUARD_RESOLVER_MAX_WORKERS threads, created lazily on the first
cache miss and kept for the predicate's lifetime) — never the loop's shared default
to_thread executor, so guard lookups cannot starve the pipeline's CPU phase or an
embedding application's own thread-pool work. Concurrent misses for one host coalesce
into a single lookup; fan-out to distinct novel hostnames beyond the pool size
queues inside the guard's own pool (bounded threads, not one pinned thread per
hostile hostname); each lookup is capped at resolve_timeout seconds, after which
the URL fails closed and the negative verdict is cached. Verdicts land in a
per-hostname TTL+LRU cache (negative verdicts cached too) — so a slow resolver costs
bounded guard-pool time plus latency for that host only, never a loop stall. Honest
limit: a timed-out lookup's thread still runs the resolver to completion inside the
pool — the timeout bounds the caller's wait, not the thread's occupancy.
Single-event-loop-at-a-time contract: the coalescing machinery uses loop-bound
asyncio.Future\ s, so each returned predicate must only be used from one event
loop at a time. Reusing one predicate sequentially across loops (e.g. back-to-back
asyncio.run calls) is supported — when idle it re-binds to the new loop and keeps
its verdict cache (the lookup pool itself is loop-independent and carries over
unchanged). Concurrent use from multiple event loops raises
RuntimeError (detected best-effort). Direct callers see that error; through
request_filter it fails closed instead, so misuse there manifests as requests from
the other loop being aborted. Create a separate predicate per loop for concurrent use.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
allowed_hosts
|
Iterable[str] | None
|
Optional exact (lowercase-compared) hostname allowlist applied before resolution: a host not on the list is rejected, and a host on the list must still resolve to only-public addresses. The allowlist narrows the filter, never widens it. |
None
|
resolver
|
Resolver
|
|
_default_resolver
|
ttl
|
float
|
Seconds a hostname's verdict is reused before re-resolving. Defaults to
|
DEFAULT_GUARD_TTL_SECONDS
|
max_entries
|
int
|
LRU bound on the verdict cache. Defaults to
|
DEFAULT_GUARD_MAX_ENTRIES
|
clock
|
Clock
|
Monotonic time source for the TTL, injectable for tests. |
monotonic
|
resolve_timeout
|
float
|
Seconds a single lookup may take before the URL fails closed (and
the negative verdict is cached). Defaults to
|
DEFAULT_GUARD_RESOLVE_TIMEOUT_SECONDS
|
Returns:
| Type | Description |
|---|---|
Callable[[str], Awaitable[bool]]
|
An async |
colorsense.RequestFilter
module-attribute
¶
RequestFilter = (
Callable[[str], bool] | Callable[[str], Awaitable[bool]]
)
An egress predicate over a request URL: True permits, False aborts.
Either a plain synchronous callable or an async one. A sync predicate is invoked inline on
the event loop's request path, so it must not block (cheap string checks only); an async
predicate is awaited, free to move slow work (e.g. DNS resolution) off the loop — the
shipped colorsense.block_private_networks guard is
async for exactly that reason.
Both seams that apply a filter (the browser route handler and the robots loader) evaluate
it through evaluate_request_filter, so raising — sync or async — always fails
closed.
Errors¶
colorsense.RenderError ¶
Bases: Exception
A page failed to render or to harvest cleanly.
Raised when the underlying browser engine cannot load the target URL — e.g. DNS
resolution failure, connection refused, TLS error, navigation timeout, or any other
Playwright navigation/render failure. The version-private Playwright exception is wrapped
so consumers have a single, stable, documented type to catch instead of reaching into
playwright._impl.
Also raised when the harvest itself fails on a hostile or degenerate page: a page that
tampers with DOM APIs can make the in-page harvest payloads come back malformed
(surfacing as KeyError/TypeError/ValueError/pydantic ValidationError),
and a capture whose decoded dimensions exceed the decompression-bomb cap is rejected.
All of these are wrapped into this one public type.
The original error is chained via __cause__ (raise ... from err).
Attributes:
| Name | Type | Description |
|---|---|---|
url |
The offending URL that failed to render or harvest. |
colorsense.RobotsDisallowedError ¶
Bases: RuntimeError
Raised when robots.txt disallows a URL and the active policy respects it.
Attributes:
| Name | Type | Description |
|---|---|---|
url |
The disallowed URL. |
colorsense.UnsupportedSchemeError ¶
Bases: ValueError
Raised when a URL's scheme is not one the policy will render.
Raised by PolitenessPolicy.fetch.
Only http/https URLs are fetchable by default. file:// (a local-file-read
primitive) is an explicit opt-in via PolitenessPolicy(allow_file_urls=True); every
other scheme (ftp, data, javascript, scheme-less, ...) is always rejected.
Attributes:
| Name | Type | Description |
|---|---|---|
url |
The offending URL whose scheme was rejected. |
colorsense.AnalysisTimeoutError ¶
Bases: TimeoutError
Raised by analyze when max_total_seconds expires.
Subclasses the builtin TimeoutError, so a generic except TimeoutError
still catches it.
Attributes:
| Name | Type | Description |
|---|---|---|
url |
The URL whose analysis exceeded the deadline. |
|
max_total_seconds |
The configured overall deadline, in seconds. |