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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ It is not a beautifier, generic design-system linter, prompt library, schema bro

## Product Thesis

AI-generated UI fails when the implementation model becomes the user experience. Tables become screens, schemas become forms, tool calls become buttons, and internal prompts become product vocabulary.
Implementation details can obscure the user’s work when record labels replace domain language, forms expose irrelevant fields, or controls name tool commands instead of the user’s actions. Technical detail remains useful for setup, debugging, auditing, and integration.

JudgmentKit catches when AI-generated UI turns implementation mechanics into UX, then gives the agent repair instructions before the work ships.
JudgmentKit gives the agent interaction guidance, checks declared interface requirements against supplied evidence, and returns repair instructions. The caller’s agent implements changes and resubmits; unresolved required verification remains visible.

The shippable value path is the public `/value/` page: it shows what JudgmentKit prevents with concrete before/after cases for implementation-language leakage, unsafe approval boundaries, and missing accessibility evidence. Eval reports remain supporting evidence for audit and reproduction, not the primary product story.
The public `/value/` page shows selected checks and curated repair examples for implementation-language leakage, approval boundaries, and missing accessibility evidence. Eval reports remain supporting evidence for audit and reproduction, with their original versions and conditions.

JudgmentKit gives the agent a better order of operations:

Expand Down
37 changes: 37 additions & 0 deletions docs/site-homepage-art-provenance.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"date": "2026-10-16",
"role": "JudgmentKit public homepage environmental background",
"asset": "site/assets/judgment-lens-environment-v2.webp",
"sha256": "4604767e61b39228ea0b0c079828d905cffd65de7ae2d467047b1ccb7e26979b",
"bytes": 154502,
"dimensions": [
1983,
793
],
"generator": "built-in image_gen",
"mode": "reference-based generation and composition",
"prompt": "Use case: compositing. Asset type: full-width website hero background. Image 1 is the EDIT TARGET: JudgmentKit's existing portrait artwork of rough stone fragments passing through a teal glass lens and emerging as an ordered curved path. Image 2 is a STYLE/COMPOSITION REFERENCE ONLY: the Surfaces architectural slab environment. Recompose and extend Image 1 into a very wide 2.5:1 landscape (approximately 2400x960) suitable for a full-bleed homepage hero behind live HTML text. Preserve the recognizable teal glass lens, stone fragments, ordered path, premium realistic materials and quiet lighting. Place the lens and ordered path predominantly in the right half to right third. Extend a spacious, dim Ink/Graphite studio environment across the left 50 percent so white text can be placed there. The artwork must fill the frame naturally with no visible card/container boundary. Use the restrained environmental depth, tonal balance and architectural scale of Image 2 without adding its slabs to this scene. Lens must remain recognizably the same subject, not become a logo, eye or portal to another world. No text, no typography, no logos, no UI, no border, no rounded frame, no watermark. Do not bake a dark gradient text overlay into the bitmap; the left environment should be naturally quiet and dark. Single final landscape image.",
"editSource": {
"path": "site/assets/judgment-lens-hero.webp",
"sha256": "becef95f1088efd5f0e01263bff7d5d74868fd523ba267da347ce722284e81f8",
"preserved": true
},
"styleAndCompositionReference": "https://www.surfaces.systems/",
"sharedLayoutContract": {
"path": "specs/website-family-layout.md",
"sha256": "ce279d508bb304f80a5c95bb8df3be1bc8e8ffe631254412cb3d10df2d57b007",
"source": "SurfacesBrand/specs/website-family-layout.md",
"observedViewports": [
[
1280,
720
],
[
390,
844
]
]
},
"artworkReview": "Root visually accepted the wide artwork before browser composition review. Final page acceptance remains separate.",
"preservation": "Canonical marks, lockups, favicon geometry and social artwork are unchanged. No text or UI is baked into this asset."
}
34 changes: 17 additions & 17 deletions scripts/verify-public-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -466,9 +466,10 @@ export function assertValueAppearanceContract(valueHtml, siteCss) {
[
'<link rel="canonical" href="https://judgmentkit.ai/value/">',
'<link rel="stylesheet" href="/assets/site.css">',
'<body class="site-presentation">',
'class="value-findings"',
"Baseline failure",
"JudgmentKit catches",
"JudgmentKit checks",
"Repaired outcome",
],
"value",
Expand All @@ -492,6 +493,11 @@ export function assertValueAppearanceContract(valueHtml, siteCss) {
["background: var(--soft-surface);"],
"value finding card rule",
);
const familyDarkRoot = siteCss.match(
/@media \(prefers-color-scheme: dark\) \{\s*\.site-presentation\s*\{([^}]*)\}/,
)?.[1];
assert.ok(familyDarkRoot, "site CSS should include the scoped website dark appearance root");
assertIncludes(familyDarkRoot, ["--bg: #0B0D0E;", "--ink: #F7F6F2;", "--soft-surface: #24282b;"], "website CSS dark appearance root");
}

function evalRunTitle(run) {
Expand Down Expand Up @@ -1144,8 +1150,7 @@ export function assertReleaseFooter(html, expectedPackageVersion, label) {
[
'<footer class="site-footer">',
'<a class="site-footer-brand" href="/" aria-label="JudgmentKit home">',
'srcset="/assets/brand/judgmentkit-lockup-reversed.svg" media="(prefers-color-scheme: dark)"',
'class="site-brand-lockup" src="/assets/brand/judgmentkit-lockup.svg"',
'class="site-brand-lockup" src="/assets/brand/judgmentkit-lockup-reversed.svg"',
`href="https://github.com/mikeylong/judgmentkit/releases/tag/v${expectedPackageVersion}"`,
`aria-label="Release v${expectedPackageVersion} on GitHub"`,
`<span>Release v${expectedPackageVersion}</span>`,
Expand All @@ -1163,20 +1168,15 @@ async function verifyPublicRoutes(baseUrl, options = {}) {
assertIncludes(
home.text,
[
"Stop AI from building the wrong interface.",
"The judgment layer for AI-generated UI",
"A design system tells an agent how interface elements should look and behave.",
"JudgmentKit tells it which interface the user’s work requires, what should stay hidden, and what must be repaired.",
"Use JudgmentKit’s design system—or bring your own.",
"A design system can make the wrong interface consistent.",
"JudgmentKit prevents that mistake before the components are composed.",
"Artifact Inspector",
"Proposed",
"one rendered artifact stays primary",
"inspector chrome and inspection overlay",
"The artifact keeps its own visual language.",
'href="/docs/#artifact-inspector"',
`href="https://github.com/mikeylong/judgmentkit/releases/tag/v${expectedPackageVersion}"`,
"Judgment before components.",
"Guide interface design with your AI agent.",
"JudgmentKit gives your AI agent interaction guidance, implementation checks, and repair instructions.",
"Keep the interface in the language of the work.",
'class="homepage-scene"',
'class="family-problem-row"',
'class="family-workflow-row"',
"Your agent creates or revises the interface.",
"The builder owns the product decisions.",
"See a screen repaired",
"Explore examples",
'<link rel="canonical" href="https://judgmentkit.ai/">',
Expand Down
Binary file added site/assets/judgment-lens-environment-v2.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 33 additions & 5 deletions site/brand-assets.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ export const SITE_BRAND_LEGACY_MANIFEST_ROUTES = SITE_BRAND.legacy_icon_aliases
.filter(entry => entry.target === SITE_BRAND.manifest.route)
.map(entry => entry.route);

// Titles are owned by these published pages, independently of their visible headings.
const PUBLIC_PAGE_PURPOSES = Object.freeze({
"/": "Interface Design Guidance for AI Agents",
"/value/": "Interface checks and repair examples",
"/docs/": "Setup and agent workflow",
"/examples/": "Interface examples and repair fixtures",
"/design-system/": "Interface design system reference",
"/design-system/tokens/": "Design token reference",
"/design-system/fonts/": "Interface typography reference",
"/design-system/icons/": "Interface icon reference",
"/design-system/components/": "Component contracts and states",
"/design-system/patterns/": "Activity-based interface patterns",
"/design-system/accessibility/": "Accessibility evidence requirements",
"/evals/": "Historical UI evaluation reports",
"/evals/judgmentkit-mcp/": "Historical paired-artifact evaluation",
"/evals/site-rebuild-log/": "Website rebuild evidence log"
});

export function siteBrandHead({ title, description, canonicalUrl, existingContext = [] }) {
const context = [
["property", "og:title", title], ["property", "og:description", description],
Expand Down Expand Up @@ -100,21 +118,31 @@ function attribute(tag, name) {
return tag.match(new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, "i"))?.slice(1).find(value => value !== undefined) ?? "";
}

// Copied examples and reports have independent HTML shells. Apply the same
// public identity to their heads without changing their body or page copy.
// Authored routes receive their approved document/social titles. Copied examples
// and reports keep their original titles and context; every body stays unchanged.
export function applySiteBrandHead(html, relativePath) {
let found = false;
const result = html.replace(/(<head\b[^>]*>)([\s\S]*?)(<\/head>)/i, (_, open, original, close) => {
found = true;
const active = original.replace(/<!--[\s\S]*?-->|<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, "");
const title = active.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i)?.[1].trim() || "JudgmentKit";
const route = "/" + relativePath.replace(/index\.html$/, "");
const purpose = PUBLIC_PAGE_PURPOSES[route];
const title = purpose
? `${purpose} | JudgmentKit`.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;")
: active.match(/<title\b[^>]*>([\s\S]*?)<\/title>/i)?.[1].trim() || "JudgmentKit";
const descriptionTag = [...active.matchAll(/<meta\b[^>]*>/gi)].map(match => match[0]).find(tag => attribute(tag, "name").toLowerCase() === "description");
const description = descriptionTag ? attribute(descriptionTag, "content") : "JudgmentKit is an activity-first judgment layer for AI-generated product work.";
const canonicalTag = [...active.matchAll(/<link\b[^>]*>/gi)].map(match => match[0]).find(tag => attribute(tag, "rel").toLowerCase() === "canonical");
const route = "/" + relativePath.replace(/index\.html$/, "");
const canonicalUrl = canonicalTag ? attribute(canonicalTag, "href") : SITE_BRAND.origin + route;
const existingContext = [...active.matchAll(/<meta\b[^>]*>/gi)].map(([tag]) => (attribute(tag, "name") || attribute(tag, "property")).toLowerCase());
const head = original.replace(/<noscript\b[^>]*\bdata-brand-favicon-fallback[^>]*>[\s\S]*?<\/noscript\s*>/gi, "").replace(/<!--[\s\S]*?-->|<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>|<(?:link|meta)\b[^>]*>/gi, tag => {
const titled = original.replace(/<title\b[^>]*>[\s\S]*?<\/title>/i, tag => purpose ? `<title>${title}</title>` : tag)
.replace(/<meta\b[^>]*>/gi, tag => {
const key = (attribute(tag, "name") || attribute(tag, "property")).toLowerCase();
return purpose && ["og:title", "twitter:title"].includes(key)
? tag.replace(/(\bcontent\s*=\s*)(["'])([\s\S]*?)\2/i, (_, prefix, quote) => `${prefix}${quote}${title}${quote}`)
: tag;
});
const head = titled.replace(/<noscript\b[^>]*\bdata-brand-favicon-fallback[^>]*>[\s\S]*?<\/noscript\s*>/gi, "").replace(/<!--[\s\S]*?-->|<(script|style)\b[^>]*>[\s\S]*?<\/\1\s*>|<(?:link|meta)\b[^>]*>/gi, tag => {
if (/^<script\b/i.test(tag) && /\bdata-brand-favicon-controller(?:\s|=|>)/i.test(tag)) return "";
if (/^<!--|^<(?:script|style)\b/i.test(tag)) return tag;
const rel = attribute(tag, "rel").toLowerCase();
Expand Down
Loading
Loading