diff --git a/public/lint-schema.json b/public/lint-schema.json index b81281f5..eb2e98c1 100644 --- a/public/lint-schema.json +++ b/public/lint-schema.json @@ -229,6 +229,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "==": { "$ref": "#/definitions/LintComparisonBase" } }, "required": ["=="] @@ -237,6 +238,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "!=": { "$ref": "#/definitions/LintComparisonBase" } }, "required": ["!="] @@ -245,6 +247,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "<": { "$ref": "#/definitions/LintComparisonBase" } }, "required": ["<"] @@ -253,6 +256,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, ">": { "$ref": "#/definitions/LintComparisonBase" } }, "required": [">"] @@ -261,6 +265,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "similar": { "type": "object", "additionalProperties": false, @@ -358,6 +363,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "and": { "type": "array", "items": { "$ref": "#/definitions/LintExpression" } @@ -369,6 +375,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "or": { "type": "array", "items": { "$ref": "#/definitions/LintExpression" } @@ -380,6 +387,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "not": { "$ref": "#/definitions/LintExpression" } }, "required": ["not"] @@ -393,6 +401,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "all": { "$ref": "#/definitions/LintQuantifierBase" } }, "required": ["all"] @@ -401,6 +410,7 @@ "type": "object", "additionalProperties": false, "properties": { + "$schema": { "type": "string" }, "exist": { "$ref": "#/definitions/LintQuantifierBase" } }, "required": ["exist"] diff --git a/src/lib/lint-language/LintLanguage.test.ts b/src/lib/lint-language/LintLanguage.test.ts index 18f57a5e..1ea846ce 100644 --- a/src/lib/lint-language/LintLanguage.test.ts +++ b/src/lib/lint-language/LintLanguage.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "vitest"; -import { LLEval, prettyPrintLL } from "./lint-language"; +import { LLEval, prettyPrintLL, permutativeBlame } from "./lint-language"; import { Color } from "../Color"; import type { Palette } from "../../stores/color-store"; import type { LintProgram } from "./lint-type"; @@ -102,6 +102,64 @@ test("LintLanguage Quantifiers All - Simple", () => { expect(LLEval(simpProg(["#7bb9ff"]), exampleColors).result).toBe(true); }); +test("LintLanguage permutativeBlame - counts", () => { + const prog1 = { "<": { left: { count: "colors" }, right: 2 } }; + expect(permutativeBlame(prog1, exampleColors, "single")).toStrictEqual([ + 0, 1, 2, + ]); +}); + +test("LintLanguage permutativeBlame - Quantifiers Simple", () => { + const simpProg = (colors: string[]) => ({ + exist: { + in: "colors", + varb: "a", + predicate: { + exist: { + in: toColors(colors), + varb: "b", + predicate: { "==": { left: "a", right: "b" } }, + }, + }, + }, + }); + expect( + permutativeBlame(simpProg(["red"]), exampleColors, "single") + ).toStrictEqual([0, 1, 2]); + expect( + permutativeBlame(simpProg(["#7bb9ff"]), exampleColors, "single") + ).toStrictEqual([]); +}); + +test("LintLanguage permutativeBlame - Quantifiers deuteranopia", () => { + const prog = allBlindProg("deuteranopia"); + const pal = toPal(["#d4a8ff", "#7bb9ff", "#008694", "black"]); + expect(permutativeBlame(prog, pal, "single")).toStrictEqual([0, 1]); + expect(permutativeBlame(prog, pal, "pair")).toStrictEqual([[0, 1]]); +}); + +test("LintLanguage permutativeBlame - Tableau 10", () => { + const prog = allBlindProg("deuteranopia"); + const pal = toPal([ + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", + ]); + expect(permutativeBlame(prog, pal, "pair")).toStrictEqual([ + [0, 4], + [1, 8], + [2, 3], + [6, 9], + ]); +}); + const expectedOutBlind = (type: string) => `ALL a in colors, ALL b in colors WHERE index(a) != index(b), NOT similar(cvdSim(a, ${type}), cvdSim(b, ${type})) > 9`; const allBlindProg = (type: string) => ({ diff --git a/src/lib/lint-language/lint-language.ts b/src/lib/lint-language/lint-language.ts index 2a814f77..56d4c2e5 100644 --- a/src/lib/lint-language/lint-language.ts +++ b/src/lib/lint-language/lint-language.ts @@ -695,8 +695,12 @@ export class LLQuantifier extends LLNode { ); let blameIndices = new Set([]); let topEnv = env.copy(); + const checkedKeys = new Set(); const mappedEvaluations = carts .map((combo: any) => { + const key = combo.sort().join(","); + if (checkedKeys.has(key)) return "skip"; + checkedKeys.add(key); const varbIndex = this.varbs.map((varb, idx) => { return [varb, inputData[combo[idx]]]; }); @@ -964,3 +968,88 @@ export function prettyPrintLL( const ast = parseToAST({ id: [root] }, opts); return ast.toString(); } + +export function permutativeBlame( + root: LintProgram, + palette: Palette, + mode: "single" | "pair" +): number[] | number[][] { + const initialRun = LLEval(root, palette); + if (initialRun.result) return []; + + // single blame + const allIndices = palette.colors.map((_, i) => i); + let blamedIndices = [] as number[]; + if (mode === "single") { + // constructive blame + allIndices.forEach((x) => { + const tempPalette = { ...palette, colors: [palette.colors[x]] }; + const result = LLEval(root, tempPalette); + if (!result.result) { + blamedIndices.push(x); + console.log("constructive", x); + } + }); + if (blamedIndices.length > 0) return blamedIndices; + blamedIndices = palette.colors + .map((_, i) => { + const tempPalette = { + ...palette, + colors: palette.colors.filter((_, j) => i !== j), + }; + const result = LLEval(root, tempPalette); + // if it passes then this index is the problem + + return result.result ? i : -1; + }) + .filter((x) => x !== -1); + } else if (mode === "pair") { + // pair blame + // todo add trimming as appropriate + let checkIndices = new Set(); + const pairBlame = [] as number[][]; + // constructive blame + allIndices.forEach((x) => { + allIndices.forEach((y) => { + if (x === y) return; + const key = [x, y].sort().join(","); + if (checkIndices.has(key)) return; + checkIndices.add(key); + const tempPalette = { + ...palette, + colors: [palette.colors[x], palette.colors[y]], + }; + const result = LLEval(root, tempPalette); + if (!result.result) { + pairBlame.push([x, y]); + } + }); + if (pairBlame.length > 0) return pairBlame; + // reductive blame + blamedIndices.forEach((x) => { + blamedIndices.forEach((y) => { + const key = [x, y].sort().join(","); + if (checkIndices.has(key)) return; + if (x === y) return; + checkIndices.add(key); + // try out filter the pairs of blamed indices as pairs + const tempPalette = { + ...palette, + colors: palette.colors.filter((_, j) => x !== j && y !== j), + }; + const result = LLEval(root, tempPalette); + if (result.result) { + pairBlame.push([x, y]); + } + }); + }); + }); + + return pairBlame; + } + if (blamedIndices.length === 0) { + blamedIndices = [...allIndices]; + } + + return blamedIndices; +} diff --git a/src/lib/lint-language/lint-type.ts b/src/lib/lint-language/lint-type.ts index a1de8d76..9994ca8c 100644 --- a/src/lib/lint-language/lint-type.ts +++ b/src/lib/lint-language/lint-type.ts @@ -77,7 +77,7 @@ type LintReduce = Record< >; type LintColorFunction = | { - cvdSim: LintVariable | LintColor; + cvd_sim: LintVariable | LintColor; type: "protanomaly" | "deuteranomaly" | "tritanopia" | "grayscale"; } | { name: LintVariable | LintColor } diff --git a/src/lib/linter.ts b/src/lib/linter.ts index f24fbb5c..d8c198e2 100644 --- a/src/lib/linter.ts +++ b/src/lib/linter.ts @@ -3,7 +3,6 @@ import type { Palette } from "../stores/color-store"; import NameDiscrim from "./lints/name-discrim"; import Discrims from "./lints/size-discrim"; -import Blinds from "./lints/blind-check"; import ColorSimilarity from "./lints/color-similarity"; import BackgroundDifferentiability from "./lints/background-differentiability"; import SequentialOrder from "./lints/sequential-order"; @@ -24,7 +23,6 @@ export function runLintChecks( [ NameDiscrim, ...Discrims, - ...Blinds, ColorSimilarity, BackgroundDifferentiability, SequentialOrder, @@ -49,6 +47,3 @@ export function runLintChecks( } }); } - -// typesript type for an list of classes -// https://stackoverflow.com/questions/57465358/typescript-type-for-an-list-of-classes diff --git a/src/lib/lints/ColorLint.ts b/src/lib/lints/ColorLint.ts index 2552ea40..b06e0c3a 100644 --- a/src/lib/lints/ColorLint.ts +++ b/src/lib/lints/ColorLint.ts @@ -40,6 +40,7 @@ export class ColorLint { isCustom: false | string = false; group: string = ""; description: string = ""; + blameMode: "pair" | "single" | "none" = "none"; paramOptions: | { type: "number"; min: number; max: number; step: number } | { type: "enum"; options: string[] } diff --git a/src/lib/lints/CustomLint.ts b/src/lib/lints/CustomLint.ts index 61b48d7e..f0fade5e 100644 --- a/src/lib/lints/CustomLint.ts +++ b/src/lib/lints/CustomLint.ts @@ -1,7 +1,11 @@ import type { LintProgram } from "../lint-language/lint-type"; import { ColorLint } from "./ColorLint"; import type { TaskType } from "./ColorLint"; -import { LLEval, prettyPrintLL } from "../lint-language/lint-language"; +import { + LLEval, + prettyPrintLL, + permutativeBlame, +} from "../lint-language/lint-language"; import * as Json from "jsonc-parser"; export interface CustomLint { @@ -13,10 +17,11 @@ export interface CustomLint { description: string; failMessage: string; id: string; + blameMode: "pair" | "single" | "none"; } export function CreateCustomLint(props: CustomLint) { - return class CustomLint extends ColorLint { + return class CustomLint extends ColorLint { name = props.name; taskTypes = props.taskTypes; level = props.level; @@ -24,19 +29,38 @@ export function CreateCustomLint(props: CustomLint) { description = props.description; hasHeuristicFix = false; isCustom = props.id; + blameMode = props.blameMode; _runCheck() { const prog = Json.parse(props.program); const { blame, result } = LLEval(prog, this.palette, { debugCompare: false, }); - return { passCheck: result, data: blame }; + if (result) return { passCheck: true, data: blame }; + + return { + passCheck: result, + data: + props.blameMode !== "none" + ? permutativeBlame(prog, this.palette, props.blameMode) + : [], + }; } buildMessage() { - const blame = this.checkData - .map((x) => this.palette.colors[x].toHex()) - .join(", "); + let blame = ""; + if (this.blameMode === "pair") { + blame = (this.checkData as number[][]) + .map((x) => + x.map((x) => this.palette.colors[x].toHex()).join(" and ") + ) + .join(", "); + } else { + blame = (this.checkData as number[]) + .map((x) => this.palette.colors[x].toHex()) + .join(", "); + } + return props.failMessage.replace("{{blame}}", blame); } }; diff --git a/src/lib/lints/blind-check.ts b/src/lib/lints/blind-check.ts deleted file mode 100644 index 425a222c..00000000 --- a/src/lib/lints/blind-check.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { ColorLint } from "./ColorLint"; -import type { TaskType } from "./ColorLint"; -import { Color } from "../Color"; -import simulate_cvd from "../blindness"; -// import { LLEval } from "../lint-language"; -// import blind from "color-blind"; - -const blindnessTypes = ["deuteranopia", "protanopia", "tritanopia"] as const; -const blindnessLabels: Record<(typeof blindnessTypes)[number], string> = { - deuteranopia: "(ie can't see green)", - protanopia: "(ie can't see red)", - tritanopia: "(ie can't see blue)", -}; - -type BlindnessTypes = (typeof blindnessTypes)[number]; - -function indexesWithSmallDeltaE(colors: Color[]) { - let indexes: [number, number][] = []; - for (let i = 0; i < colors.length; i++) { - for (let j = i + 1; j < colors.length; j++) { - const deltaE = colors[i].symmetricDeltaE(colors[j]); - // const deltaE = difference(colors[i].toColorIO(), colors[j].toColorIO()); - if (deltaE < 9) { - indexes.push([i, j]); - } - } - } - return indexes; -} - -function checkType(colors: Color[], type: BlindnessTypes | "grayscale") { - const blindColors = colors.map((x) => simulate_cvd(type, x)); - let notOKColorIndexes: [number, number][] = - indexesWithSmallDeltaE(blindColors); - return { pass: notOKColorIndexes.length === 0, notOKColorIndexes }; -} - -// adapted from https://github.dev/gka/palettes -function checkTypeA(colors: Color[], type: BlindnessTypes) { - let notOKColorIndexes: [number, number][] = []; - let ratioThreshold = 5; - let smallestPerceivableDistance = 9; - let k = colors.length; - if (!k) { - return { pass: true, notOKColorIndexes }; - } - - // compute distances between colors - for (let a = 0; a < k; a++) { - for (let b = a + 1; b < k; b++) { - let [colorA, colorB] = [a, b].map((x) => colors[x]); - // let distanceNorm = difference(colorA.toColorIO(), colorB.toColorIO()); - let distanceNorm = colorA.symmetricDeltaE(colorB); - if (distanceNorm < smallestPerceivableDistance) continue; - // let aSim = Color.colorFromHex(blind[type](colorA.toHex()), "lab").toColorIO(); - // let bSim = Color.colorFromHex(blind[type](colorB.toHex()), "lab").toColorIO(); - // let distanceSim = difference(aSim, bSim); - let distanceSim = colorA.symmetricDeltaE(colorB); - let isNotOk = - distanceNorm / distanceSim > ratioThreshold && - distanceSim < smallestPerceivableDistance; - // count combinations that are problematic - if (isNotOk) { - notOKColorIndexes.push([a, b]); - } - } - } - // compute share of problematic colors - return { pass: notOKColorIndexes.length === 0, notOKColorIndexes }; -} - -// function difference(colorA: ColorIO, colorB: ColorIO) { -// return 0.5 * (colorA.deltaE(colorB, "2000") + colorB.deltaE(colorA, "2000")); -// } - -const checks = blindnessTypes.map((key) => { - return class ColorBlindCheck extends ColorLint<[number, number][], false> { - name = `Colorblind Friendly for ${key}`; - taskTypes = ["sequential", "diverging", "categorical"] as TaskType[]; - group: string = "accessibility"; - description: string = `All colors in a palette should be differentiable by people with ${blindnessLabels[key]}. This is because if they are not, then they will not be differentiable from each other in some contexts.`; - _runCheck() { - const colors = this.palette.colors; - const { pass, notOKColorIndexes } = checkType(colors, key); - return { passCheck: pass, data: notOKColorIndexes }; - } - buildMessage(): string { - const colors = this.palette.colors.map((x) => x.toHex()); - const pairs = this.checkData - .map(([a, b]) => `(${colors[a]} and ${colors[b]})`) - .join(", "); - return `This palette is not colorblind friendly for ${key} color blindness ${blindnessLabels[key]}. The following pairs are undifferentiable: ${pairs}`; - } - }; -}); - -class Grayscale extends ColorLint<[number, number][], false> { - name = `Grayscale friendly`; - taskTypes = ["sequential", "diverging", "categorical"] as TaskType[]; - group: string = "accessibility"; - description: string = `Colors should be discernable in gray scale. For instance if printing in black and white, colors should still be distinguishable.`; - _runCheck() { - const colors = this.palette.colors; - const { pass, notOKColorIndexes } = checkType(colors, "grayscale"); - return { passCheck: pass, data: notOKColorIndexes }; - } - buildMessage(): string { - const colors = this.palette.colors.map((x) => x.toHex()); - const pairs = this.checkData - .map(([a, b]) => `(${colors[a]} and ${colors[b]})`) - .join(", "); - return `These color pairs are not distinguishable in grayscale: ${pairs}.`; - } -} - -checks.push(Grayscale); - -export default checks; diff --git a/src/linting/Eval.svelte b/src/linting/Eval.svelte index ae271774..0a0a9724 100644 --- a/src/linting/Eval.svelte +++ b/src/linting/Eval.svelte @@ -11,12 +11,18 @@ import LintCustomizationModal from "./LintCustomizationModal.svelte"; import Nav from "../components/Nav.svelte"; import NewLintSuggestion from "./NewLintSuggestion.svelte"; + import { debounce } from "vega"; $: currentPal = $colorStore.palettes[$colorStore.currentPal]; $: palType = currentPal.type; $: evalConfig = currentPal.evalConfig; $: customLints = $lintStore.lints; - $: checks = runLintChecks(currentPal, palType, customLints, evalConfig); + // $: checks = runLintChecks(currentPal, palType, customLints, evalConfig); + let checks = [] as ColorLint[]; + let updateSearchDebounced = debounce(100, (pal: any) => { + checks = runLintChecks(pal, palType, customLints, evalConfig); + }); + $: updateSearchDebounced(currentPal); $: checkGroups = checks.reduce( (acc, check) => { diff --git a/src/linting/LintCustomizationModal.svelte b/src/linting/LintCustomizationModal.svelte index 3f97e616..649c0245 100644 --- a/src/linting/LintCustomizationModal.svelte +++ b/src/linting/LintCustomizationModal.svelte @@ -36,6 +36,8 @@ $: lintRun = runLint(lint); let showDoubleCheck = false; $: currentTaskTypes = lint.taskTypes as string[]; + $: checkData = lintRun?.checkData || []; + $: pairData = checkData as number[][]; {#if lint} @@ -59,9 +61,6 @@ {/if}
- {#if showDoubleCheck}
Are you sure you want to delete this lint?
@@ -81,6 +80,13 @@ No
+ {:else} + {/if}
@@ -181,17 +187,46 @@
This lint fails for the current palette.
- The following colors are blamed: + The following colors are blamed. Using + + Blame mode - currentPal.colors[x] - ), - }} - allowModification={false} - /> + {#if lint.blameMode === "pair"} +
+ {#each pairData as pair} +
+ currentPal.colors[x]), + }} + allowModification={false} + /> +
+ {/each} +
+ {:else} + x) + .map((x) => currentPal.colors[x]), + }} + allowModification={false} + /> + {/if} {/if} {#if errors} diff --git a/src/stores/built-in-lints.ts b/src/stores/built-in-lints.ts index 62bf8f1b..ccbb2ecf 100644 --- a/src/stores/built-in-lints.ts +++ b/src/stores/built-in-lints.ts @@ -1,13 +1,29 @@ import type { CustomLint } from "../lib/lints/CustomLint"; -import type { TaskType } from "../lib/lints/ColorLint"; +import type { LintProgram } from "../lib/lint-language/lint-type"; import { JSONStringify } from "../lib/utils"; +import type { TaskType } from "../lib/lints/ColorLint"; -const toString = (x: any) => JSONStringify(JSON.stringify(x)); +const toString = (x: LintProgram) => JSONStringify(JSON.stringify(x)); const $schema = `${location.href}lint-schema.json`; + +const blindTypes = [ + "deuteranopia", + "protanopia", + "tritanopia", + "grayscale", +] as const; +const blindnessLabels: Record<(typeof blindTypes)[number], string> = { + deuteranopia: "(ie can't see green)", + protanopia: "(ie can't see red)", + tritanopia: "(ie can't see blue)", + grayscale: "", +}; const BUILT_INS: CustomLint[] = [ // https://www.sciencedirect.com/science/article/pii/S0167947308005549?casa_token=s8jmZqboaYgAAAAA:7lsAu7YUHVBTQA_eaKJ_3FFGv309684j_NTisGO9mIr3UZNIJ6hlAlxPQo04xzsowG7-dH0vzm4 { + name: "Avoid extreme colors", program: toString({ + // @ts-ignore $schema, all: { in: "colors", @@ -23,31 +39,35 @@ const BUILT_INS: CustomLint[] = [ }, }, }), - name: "Avoid extreme colors", - taskTypes: ["sequential", "diverging", "categorical"] as TaskType[], + + taskTypes: ["sequential", "diverging", "categorical"] as const, level: "warning", group: "design", description: `Colors at either end of the lightness spectrum can be hard to discriminate in some contexts, and are sometimes advised against.`, failMessage: `Colors at either end of the lightness spectrum {{blame}} are hard to discriminate in some contexts, and are sometimes advised against`, id: "extreme-colors-built-in", + blameMode: "single", }, { name: "Avoid too many colors", program: toString({ + // @ts-ignore $schema, "<": { left: { count: "colors" }, right: 10 }, }), - taskTypes: ["sequential", "diverging", "categorical"] as TaskType[], + taskTypes: ["sequential", "diverging", "categorical"] as const, level: "warning", group: "design", description: "Palettes should have a maximum number of colors. Higher numbers of colors can make it hard to identify specific values.", failMessage: `This palette has too many colors and may be hard to discriminate in some contexts. Maximum: 10.`, id: "too-many-colors-built-in", + blameMode: "single", }, { name: "Palette does not have ugly colors", program: toString({ + // @ts-ignore $schema, all: { in: "colors", @@ -68,12 +88,48 @@ const BUILT_INS: CustomLint[] = [ }, }), taskTypes: ["categorical"], - description: `!!Colors that are close to what are known as ugly colors are sometimes advised against. See https://www.colourlovers.com/palette/1416250/The_Ugliest_Colors for more details.`, + description: `Colors that are close to what are known as ugly colors are sometimes advised against. See https://www.colourlovers.com/palette/1416250/The_Ugliest_Colors for more details.`, failMessage: `This palette has some colors (specifically {{blame}}) that are close to what are known as ugly colors`, level: "warning", group: "design", id: "ugly-colors-built-in", + blameMode: "single", }, + ...blindTypes.map((type) => ({ + // old algorithm - https://github.dev/gka/palettes + // let distanceNorm = colorA.symmetricDeltaE(colorB); + // if (distanceNorm < smallestPerceivableDistance) continue; + // let distanceSim = colorA.symmetricDeltaE(colorB); + // let isNotOk = + // distanceNorm / distanceSim > ratioThreshold && + // distanceSim < smallestPerceivableDistance; + program: toString({ + // @ts-ignore + $schema, + all: { + in: "colors", + varbs: ["a", "b"], + where: { "!=": { left: "index(a)", right: "index(b)" } }, + predicate: { + not: { + similar: { + left: { cvdSim: "a", type }, + right: { cvdSim: "b", type }, + threshold: 9, + }, + }, + }, + }, + }), + name: `Colorblind Friendly for ${type}`, + taskTypes: ["sequential", "diverging", "categorical"] as TaskType[], + group: "accessibility", + description: `All colors in a palette should be differentiable by people with ${type} ${blindnessLabels[type]}. This is because if they are not, then they will not be differentiable from each other in some contexts.`, + level: "error" as const, + failMessage: `Some colors in this palette ({{blame}}) are not differentiable by people with ${type}.`, + id: `colorblind-friendly-${type}-built-in`, + blameMode: "pair" as const, + })), ]; export default BUILT_INS; diff --git a/src/stores/lint-store.ts b/src/stores/lint-store.ts index c11b1cc2..25d5b868 100644 --- a/src/stores/lint-store.ts +++ b/src/stores/lint-store.ts @@ -75,6 +75,8 @@ function createStore() { lintUpdate((old) => ({ ...old, description })), setCurrentLintFailMessage: (failMessage: string) => lintUpdate((old) => ({ ...old, failMessage })), + setCurrentLintBlameMode: (blameMode: "pair" | "single" | "none") => + lintUpdate((old) => ({ ...old, blameMode })), deleteLint: (id: string) => persistUpdate((old) => ({ ...old, @@ -98,6 +100,7 @@ function newLint(newLintFrag: Partial): CustomLint { description: "v confusing", failMessage: "v confusing", id: Math.random().toString(), + blameMode: "none", ...newLintFrag, }; }