cbrowser 20.2.2 → 21.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analysis/accessibility-empathy.d.ts +29 -1
- package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +69 -35
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +11 -7
- package/dist/browser.js.map +1 -1
- package/dist/cif-score.d.ts +56 -5
- package/dist/cif-score.d.ts.map +1 -1
- package/dist/cif-score.js +595 -346
- package/dist/cif-score.js.map +1 -1
- package/dist/cli.js +10 -8
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/default-traits.d.ts +14 -0
- package/dist/cognitive/default-traits.d.ts.map +1 -0
- package/dist/cognitive/default-traits.js +36 -0
- package/dist/cognitive/default-traits.js.map +1 -0
- package/dist/cognitive/emotions.d.ts.map +1 -1
- package/dist/cognitive/emotions.js +104 -174
- package/dist/cognitive/emotions.js.map +1 -1
- package/dist/cognitive/index.d.ts +28 -0
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +353 -287
- package/dist/cognitive/index.js.map +1 -1
- package/dist/cognitive/temporal.d.ts +0 -24
- package/dist/cognitive/temporal.d.ts.map +1 -1
- package/dist/cognitive/temporal.js +45 -35
- package/dist/cognitive/temporal.js.map +1 -1
- package/dist/data/ctc-reference-v2-2026-10-24.d.ts +16 -0
- package/dist/data/ctc-reference-v2-2026-10-24.d.ts.map +1 -0
- package/dist/data/ctc-reference-v2-2026-10-24.js +1004 -0
- package/dist/data/ctc-reference-v2-2026-10-24.js.map +1 -0
- package/dist/ideal-profiles-gap.d.ts +1 -0
- package/dist/ideal-profiles-gap.d.ts.map +1 -1
- package/dist/ideal-profiles-gap.js +9 -1
- package/dist/ideal-profiles-gap.js.map +1 -1
- package/dist/ideal-profiles.d.ts +8 -0
- package/dist/ideal-profiles.d.ts.map +1 -1
- package/dist/ideal-profiles.js +28 -24
- package/dist/ideal-profiles.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +7 -8
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-tools/account-personas.d.ts.map +1 -1
- package/dist/mcp-tools/account-personas.js +3 -1
- package/dist/mcp-tools/account-personas.js.map +1 -1
- package/dist/mcp-tools/base/answer-provenance.d.ts +28 -0
- package/dist/mcp-tools/base/answer-provenance.d.ts.map +1 -0
- package/dist/mcp-tools/base/answer-provenance.js +32 -0
- package/dist/mcp-tools/base/answer-provenance.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +0 -3
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +152 -84
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +1 -1
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/chain-values.d.ts +38 -0
- package/dist/mcp-tools/base/chain-values.d.ts.map +1 -0
- package/dist/mcp-tools/base/chain-values.js +85 -0
- package/dist/mcp-tools/base/chain-values.js.map +1 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +126 -120
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/gif-tools.js +23 -5
- package/dist/mcp-tools/base/gif-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +229 -172
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/site-familiarity.d.ts +31 -0
- package/dist/mcp-tools/base/site-familiarity.d.ts.map +1 -0
- package/dist/mcp-tools/base/site-familiarity.js +30 -0
- package/dist/mcp-tools/base/site-familiarity.js.map +1 -0
- package/dist/mcp-tools/base/site-knowledge-tools.js +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
- package/dist/mcp-tools/base/story-narrative.d.ts +50 -0
- package/dist/mcp-tools/base/story-narrative.d.ts.map +1 -0
- package/dist/mcp-tools/base/story-narrative.js +88 -0
- package/dist/mcp-tools/base/story-narrative.js.map +1 -0
- package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/values-tools.js +19 -5
- package/dist/mcp-tools/base/values-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts +5 -5
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +38 -11
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +6 -6
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.js +5 -1
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
- package/dist/mcp-tools/tier-gate.js +1 -1
- package/dist/mcp-tools/tool-result-saver.d.ts +7 -0
- package/dist/mcp-tools/tool-result-saver.d.ts.map +1 -1
- package/dist/mcp-tools/tool-result-saver.js +11 -0
- package/dist/mcp-tools/tool-result-saver.js.map +1 -1
- package/dist/mcp-tools/trait-provenance.d.ts +109 -0
- package/dist/mcp-tools/trait-provenance.d.ts.map +1 -0
- package/dist/mcp-tools/trait-provenance.js +187 -0
- package/dist/mcp-tools/trait-provenance.js.map +1 -0
- package/dist/mcp-tools/ui-resources.d.ts +6 -7
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
- package/dist/mcp-tools/ui-resources.js +162 -12
- package/dist/mcp-tools/ui-resources.js.map +1 -1
- package/dist/mcp-tools/widget-kit.d.ts +129 -3
- package/dist/mcp-tools/widget-kit.d.ts.map +1 -1
- package/dist/mcp-tools/widget-kit.js +507 -28
- package/dist/mcp-tools/widget-kit.js.map +1 -1
- package/dist/performance/metrics.d.ts.map +1 -1
- package/dist/performance/metrics.js +4 -1
- package/dist/performance/metrics.js.map +1 -1
- package/dist/persona-questionnaire.js +1 -1
- package/dist/persona-questionnaire.js.map +1 -1
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +133 -82
- package/dist/personas.js.map +1 -1
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +5 -2
- package/dist/recording/engine.js.map +1 -1
- package/dist/recording/saliency-overlay.d.ts +8 -3
- package/dist/recording/saliency-overlay.d.ts.map +1 -1
- package/dist/recording/saliency-overlay.js +14 -5
- package/dist/recording/saliency-overlay.js.map +1 -1
- package/dist/recording/types.js +1 -1
- package/dist/testing/nl-test-suite.d.ts.map +1 -1
- package/dist/testing/nl-test-suite.js +2 -1
- package/dist/testing/nl-test-suite.js.map +1 -1
- package/dist/trait-reference.d.ts +19 -2
- package/dist/trait-reference.d.ts.map +1 -1
- package/dist/trait-reference.js +19 -2
- package/dist/trait-reference.js.map +1 -1
- package/dist/types.d.ts +32 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +62 -52
- package/dist/types.js.map +1 -1
- package/dist/values/persona-completeness.js +1 -1
- package/dist/values/persona-completeness.js.map +1 -1
- package/dist/visual/ab-comparison.d.ts.map +1 -1
- package/dist/visual/ab-comparison.js +4 -1
- package/dist/visual/ab-comparison.js.map +1 -1
- package/dist/visual/attention-quality.d.ts +1 -1
- package/dist/visual/attention-quality.js +1 -1
- package/dist/visual/attention-transport.d.ts +74 -10
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +353 -193
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/cognitive-models.d.ts +77 -55
- package/dist/visual/cognitive-models.d.ts.map +1 -1
- package/dist/visual/cognitive-models.js +231 -104
- package/dist/visual/cognitive-models.js.map +1 -1
- package/dist/visual/cognitive-transport-chain.d.ts +328 -30
- package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
- package/dist/visual/cognitive-transport-chain.js +576 -353
- package/dist/visual/cognitive-transport-chain.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +446 -13
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +1085 -245
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/cross-browser.d.ts.map +1 -1
- package/dist/visual/cross-browser.js +8 -6
- package/dist/visual/cross-browser.js.map +1 -1
- package/dist/visual/ctc-bands.d.ts +91 -0
- package/dist/visual/ctc-bands.d.ts.map +1 -0
- package/dist/visual/ctc-bands.js +117 -0
- package/dist/visual/ctc-bands.js.map +1 -0
- package/dist/visual/ctc-reference.d.ts +287 -0
- package/dist/visual/ctc-reference.d.ts.map +1 -0
- package/dist/visual/ctc-reference.js +376 -0
- package/dist/visual/ctc-reference.js.map +1 -0
- package/dist/visual/demand-laws.d.ts +145 -0
- package/dist/visual/demand-laws.d.ts.map +1 -0
- package/dist/visual/demand-laws.js +220 -0
- package/dist/visual/demand-laws.js.map +1 -0
- package/dist/visual/distance-metrics.d.ts +23 -1
- package/dist/visual/distance-metrics.d.ts.map +1 -1
- package/dist/visual/distance-metrics.js +49 -18
- package/dist/visual/distance-metrics.js.map +1 -1
- package/dist/visual/dro-bound.d.ts +220 -0
- package/dist/visual/dro-bound.d.ts.map +1 -0
- package/dist/visual/dro-bound.js +341 -0
- package/dist/visual/dro-bound.js.map +1 -0
- package/dist/visual/exact-w1.d.ts +60 -0
- package/dist/visual/exact-w1.d.ts.map +1 -0
- package/dist/visual/exact-w1.js +247 -0
- package/dist/visual/exact-w1.js.map +1 -0
- package/dist/visual/human-input.d.ts.map +1 -1
- package/dist/visual/human-input.js +45 -41
- package/dist/visual/human-input.js.map +1 -1
- package/dist/visual/index.d.ts +2 -0
- package/dist/visual/index.d.ts.map +1 -1
- package/dist/visual/index.js +2 -0
- package/dist/visual/index.js.map +1 -1
- package/dist/visual/law-times-report.d.ts +49 -0
- package/dist/visual/law-times-report.d.ts.map +1 -0
- package/dist/visual/law-times-report.js +38 -0
- package/dist/visual/law-times-report.js.map +1 -0
- package/dist/visual/model-provenance.d.ts +151 -0
- package/dist/visual/model-provenance.d.ts.map +1 -0
- package/dist/visual/model-provenance.js +334 -0
- package/dist/visual/model-provenance.js.map +1 -0
- package/dist/visual/normal-cdf.d.ts +14 -0
- package/dist/visual/normal-cdf.d.ts.map +1 -0
- package/dist/visual/normal-cdf.js +26 -0
- package/dist/visual/normal-cdf.js.map +1 -0
- package/dist/visual/perceptual-transport.d.ts +28 -4
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +66 -32
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/dist/visual/provenance/attention.d.ts +55 -0
- package/dist/visual/provenance/attention.d.ts.map +1 -0
- package/dist/visual/provenance/attention.js +188 -0
- package/dist/visual/provenance/attention.js.map +1 -0
- package/dist/visual/provenance/chain.d.ts +41 -0
- package/dist/visual/provenance/chain.d.ts.map +1 -0
- package/dist/visual/provenance/chain.js +66 -0
- package/dist/visual/provenance/chain.js.map +1 -0
- package/dist/visual/provenance/cif.d.ts +42 -0
- package/dist/visual/provenance/cif.d.ts.map +1 -0
- package/dist/visual/provenance/cif.js +116 -0
- package/dist/visual/provenance/cif.js.map +1 -0
- package/dist/visual/provenance/ctc-reference.d.ts +36 -0
- package/dist/visual/provenance/ctc-reference.d.ts.map +1 -0
- package/dist/visual/provenance/ctc-reference.js +96 -0
- package/dist/visual/provenance/ctc-reference.js.map +1 -0
- package/dist/visual/provenance/distance.d.ts +10 -0
- package/dist/visual/provenance/distance.d.ts.map +1 -0
- package/dist/visual/provenance/distance.js +72 -0
- package/dist/visual/provenance/distance.js.map +1 -0
- package/dist/visual/provenance/dro.d.ts +10 -0
- package/dist/visual/provenance/dro.d.ts.map +1 -0
- package/dist/visual/provenance/dro.js +2 -0
- package/dist/visual/provenance/dro.js.map +1 -0
- package/dist/visual/provenance/emotion.d.ts +26 -0
- package/dist/visual/provenance/emotion.d.ts.map +1 -0
- package/dist/visual/provenance/emotion.js +99 -0
- package/dist/visual/provenance/emotion.js.map +1 -0
- package/dist/visual/provenance/human-input.d.ts +19 -0
- package/dist/visual/provenance/human-input.d.ts.map +1 -0
- package/dist/visual/provenance/human-input.js +84 -0
- package/dist/visual/provenance/human-input.js.map +1 -0
- package/dist/visual/provenance/journey-group.d.ts +15 -0
- package/dist/visual/provenance/journey-group.d.ts.map +1 -0
- package/dist/visual/provenance/journey-group.js +15 -0
- package/dist/visual/provenance/journey-group.js.map +1 -0
- package/dist/visual/provenance/journey-mcp.d.ts +24 -0
- package/dist/visual/provenance/journey-mcp.d.ts.map +1 -0
- package/dist/visual/provenance/journey-mcp.js +54 -0
- package/dist/visual/provenance/journey-mcp.js.map +1 -0
- package/dist/visual/provenance/journey.d.ts +34 -0
- package/dist/visual/provenance/journey.d.ts.map +1 -0
- package/dist/visual/provenance/journey.js +184 -0
- package/dist/visual/provenance/journey.js.map +1 -0
- package/dist/visual/provenance/laws.d.ts +38 -0
- package/dist/visual/provenance/laws.d.ts.map +1 -0
- package/dist/visual/provenance/laws.js +40 -0
- package/dist/visual/provenance/laws.js.map +1 -0
- package/dist/visual/provenance/page-metrics.d.ts +42 -0
- package/dist/visual/provenance/page-metrics.d.ts.map +1 -0
- package/dist/visual/provenance/page-metrics.js +79 -0
- package/dist/visual/provenance/page-metrics.js.map +1 -0
- package/dist/visual/provenance/perceptual.d.ts +63 -0
- package/dist/visual/provenance/perceptual.d.ts.map +1 -0
- package/dist/visual/provenance/perceptual.js +115 -0
- package/dist/visual/provenance/perceptual.js.map +1 -0
- package/dist/visual/provenance/persona-rates.d.ts +84 -0
- package/dist/visual/provenance/persona-rates.d.ts.map +1 -0
- package/dist/visual/provenance/persona-rates.js +112 -0
- package/dist/visual/provenance/persona-rates.js.map +1 -0
- package/dist/visual/provenance/personas.d.ts +122 -0
- package/dist/visual/provenance/personas.d.ts.map +1 -0
- package/dist/visual/provenance/personas.js +298 -0
- package/dist/visual/provenance/personas.js.map +1 -0
- package/dist/visual/provenance/profiles.d.ts +39 -0
- package/dist/visual/provenance/profiles.d.ts.map +1 -0
- package/dist/visual/provenance/profiles.js +37 -0
- package/dist/visual/provenance/profiles.js.map +1 -0
- package/dist/visual/provenance/temporal.d.ts +21 -0
- package/dist/visual/provenance/temporal.d.ts.map +1 -0
- package/dist/visual/provenance/temporal.js +56 -0
- package/dist/visual/provenance/temporal.js.map +1 -0
- package/dist/visual/provenance/visual-distance.d.ts +22 -0
- package/dist/visual/provenance/visual-distance.d.ts.map +1 -0
- package/dist/visual/provenance/visual-distance.js +30 -0
- package/dist/visual/provenance/visual-distance.js.map +1 -0
- package/dist/visual/regression.d.ts.map +1 -1
- package/dist/visual/regression.js +14 -9
- package/dist/visual/regression.js.map +1 -1
- package/dist/visual/responsive.d.ts.map +1 -1
- package/dist/visual/responsive.js +4 -1
- package/dist/visual/responsive.js.map +1 -1
- package/dist/visual/scope-prose.js +2 -2
- package/dist/visual/scope-prose.js.map +1 -1
- package/dist/visual/seeded-random.d.ts +16 -0
- package/dist/visual/seeded-random.d.ts.map +1 -0
- package/dist/visual/seeded-random.js +25 -0
- package/dist/visual/seeded-random.js.map +1 -0
- package/dist/visual/stored-accessibility-traits.d.ts +27 -0
- package/dist/visual/stored-accessibility-traits.d.ts.map +1 -0
- package/dist/visual/stored-accessibility-traits.js +35 -0
- package/dist/visual/stored-accessibility-traits.js.map +1 -0
- package/package.json +1 -1
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* @license MIT
|
|
6
6
|
*/
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
import { resolveValuesForPersona } from "../../values/persona-values.js";
|
|
9
8
|
import { writeArtifact } from "../../artifact-store.js";
|
|
10
9
|
import { buildContentWithPreviews } from "../inline-image.js";
|
|
11
10
|
import { makeTempDir } from "../../temp-files.js";
|
|
@@ -14,8 +13,14 @@ import { refuseHostedUrl } from "../hosted-inputs.js";
|
|
|
14
13
|
import { comparePersonas } from "../../analysis/index.js";
|
|
15
14
|
import { isApiKeyConfigured } from "../../cognitive/index.js";
|
|
16
15
|
import { getAnyPersona, resolvePersonaOrThrow, getCognitiveProfile, createCognitivePersona, isAgentPersonaObject, BUILTIN_PERSONAS, ACCESSIBILITY_PERSONAS, EMOTIONAL_PERSONAS, } from "../../personas.js";
|
|
17
|
-
import { buildOTCognitiveProfile as buildOTProfile, cognitiveDistance, cognitiveBarycenter, cognitiveGeodesic, cognitiveDistanceMatrix, selectMaxCoveragePersonas, estimateCognitiveLoad, COGNITIVE_TRAITS, } from "../../visual/cognitive-transport.js";
|
|
18
|
-
import { bridgeCapacityTraits } from "../../visual/cognitive-models.js";
|
|
16
|
+
import { buildOTCognitiveProfile as buildOTProfile, cognitiveDistance, cognitiveBarycenter, cognitiveGeodesic, cognitiveDistanceMatrix, selectMaxCoveragePersonas, estimateCognitiveLoad, COGNITIVE_TRAITS, OVERALL_LEVEL_KEY, } from "../../visual/cognitive-transport.js";
|
|
17
|
+
import { bridgeCapacityTraits, personaRateContext } from "../../visual/cognitive-models.js";
|
|
18
|
+
import { lawTimesReport } from "../../visual/law-times-report.js";
|
|
19
|
+
import { activeProvenance, collectPersonaReads, isolatedProvenanceAsync, personaRateProvenance, ATTENTION_PROVENANCE_TEXT } from "../../visual/model-provenance.js";
|
|
20
|
+
import { ctcBandsDescription, ctcRank } from "../../visual/ctc-bands.js";
|
|
21
|
+
import { resolveSiteFamiliarity } from "./site-familiarity.js";
|
|
22
|
+
import { resolveChainValues, valueModulationReport } from "./chain-values.js";
|
|
23
|
+
import { recordedAnswer } from "./answer-provenance.js";
|
|
19
24
|
/**
|
|
20
25
|
* Most base64 characters cognitive_effort spends on its inline motor-overlay
|
|
21
26
|
* preview. The overlay is supporting evidence here, not the finding, and the
|
|
@@ -381,7 +386,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
381
386
|
// ── Cognitive Transport Tools (v18.27.0) ──
|
|
382
387
|
server.registerTool("cognitive_distance", {
|
|
383
388
|
title: "Cognitive Distance Between Personas",
|
|
384
|
-
description: "Compute the
|
|
389
|
+
description: "Compute the cognitive distance between two personas as a generalized (unbalanced) Wasserstein-1 distance over their trait vectors: it counts both the shape of the profile and its overall level, so a persona with every trait at 0.2 is far from one with every trait at 0.8. Moving trait mass between traits costs a hand-set ground metric (0.2 same domain, 0.5 related, 0.8 unrelated); creating or destroying it costs 0.4 per unit, also hand-set. Both are hypotheses, untested against human data. Returns w1 with per-trait contributions (plus overallLevel, the share due to a difference in level), the shape-only Bures-Wasserstein W2 and sliced distances, and each metric's percentile among built-in persona pairs.",
|
|
385
390
|
inputSchema: {
|
|
386
391
|
personaA: z.string().describe("First persona name"),
|
|
387
392
|
personaB: z.string().describe("Second persona name"),
|
|
@@ -404,8 +409,11 @@ Begin with the first persona: ${personas[0]}
|
|
|
404
409
|
const otA = buildOTProfile(personaA, profileA.traits || {});
|
|
405
410
|
const otB = buildOTProfile(personaB, profileB.traits || {});
|
|
406
411
|
const dist = cognitiveDistance(otA, otB);
|
|
407
|
-
// Top 5 trait contributions
|
|
412
|
+
// Top 5 trait contributions. The generalized W1's level term is reported
|
|
413
|
+
// on its own: it is not a trait, and listing it among traits read as one.
|
|
414
|
+
const overallLevelContribution = dist.traitContributions[OVERALL_LEVEL_KEY] ?? 0;
|
|
408
415
|
const topTraits = Object.entries(dist.traitContributions)
|
|
416
|
+
.filter(([trait]) => trait !== OVERALL_LEVEL_KEY)
|
|
409
417
|
.sort(([, a], [, b]) => b - a)
|
|
410
418
|
.slice(0, 5)
|
|
411
419
|
.map(([trait, contrib]) => ({ trait, contribution: Math.round(contrib * 10000) / 10000 }));
|
|
@@ -421,6 +429,9 @@ Begin with the first persona: ${personas[0]}
|
|
|
421
429
|
w1Distance: Math.round(dist.w1 * 10000) / 10000,
|
|
422
430
|
w2Distance: Math.round(dist.w2 * 10000) / 10000,
|
|
423
431
|
slicedWasserstein: Math.round(dist.sliced * 10000) / 10000,
|
|
432
|
+
// The part of w1 forced by the difference in mean trait level; the
|
|
433
|
+
// rest is shape. topDifferentiatingTraits lists traits only.
|
|
434
|
+
overallLevelContribution: Math.round(overallLevelContribution * 10000) / 10000,
|
|
424
435
|
// The comparable view: where this pair sits within all built-in
|
|
425
436
|
// persona pairs, per metric. 0 = closest pair seen, 100 = farthest.
|
|
426
437
|
metrics: {
|
|
@@ -435,11 +446,19 @@ Begin with the first persona: ${personas[0]}
|
|
|
435
446
|
scaleNote:
|
|
436
447
|
// The count is read from the list: "26" went stale when the three bridge
|
|
437
448
|
// traits arrived (v10 B42).
|
|
438
|
-
`w1 (
|
|
439
|
-
"
|
|
440
|
-
"
|
|
449
|
+
`w1 (exact generalized Wasserstein-1 distance over ${COGNITIVE_TRAITS.length} traits), ` +
|
|
450
|
+
"w2 (L2 norm) and sliced (mean absolute projection over uniform directions, computed exactly) are different quantities on different " +
|
|
451
|
+
"scales. Compare the percentiles, not the raw values. w1 sees both the shape of a profile " +
|
|
452
|
+
"and its overall level: each trait t carries mass t/" + COGNITIVE_TRAITS.length + ", moving mass " +
|
|
453
|
+
"between traits costs the trait ground metric (0.2 / 0.5 / 0.8) and creating or destroying it " +
|
|
454
|
+
"costs 0.4 per unit (a hand-set hypothesis), so w1 ranges from 0 to 0.4 and all traits 0.2 " +
|
|
455
|
+
"versus all traits 0.8 is 0.24. overallLevelContribution, reported apart from the traits, is " +
|
|
456
|
+
"half the cost the difference in mean trait level forces, 0.5 x 0.4 x |mean A - " +
|
|
457
|
+
"mean B| (the other half is charged to the traits where mass is created or destroyed). w2 and " +
|
|
458
|
+
"sliced are computed on profiles normalised to the simplex, so they measure shape only: " +
|
|
459
|
+
"proportional trait vectors are at distance 0 on both.",
|
|
441
460
|
// Verdict is a percentile band, not an absolute threshold. The former
|
|
442
|
-
// absolute bands put 209 of 210 built-in pairs in one bucket.
|
|
461
|
+
// absolute bands put 209 of the then 210 built-in pairs in one bucket.
|
|
443
462
|
interpretation: w1Pct < 10 ? "Nearly identical cognitive profiles (closer than 90% of persona pairs)"
|
|
444
463
|
: w1Pct < 30 ? "Similar cognitive profiles with minor differences"
|
|
445
464
|
: w1Pct < 70 ? "Moderately different cognitive profiles"
|
|
@@ -453,7 +472,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
453
472
|
});
|
|
454
473
|
server.registerTool("cognitive_coverage", {
|
|
455
474
|
title: "Maximum Coverage Persona Selection",
|
|
456
|
-
description: "Select the N most cognitively different personas from a list for maximum test coverage. Uses greedy farthest-point sampling
|
|
475
|
+
description: "Select the N most cognitively different personas from a list for maximum test coverage. Uses greedy farthest-point sampling under the generalized Wasserstein-1 distance of cognitive_distance, which counts differences in overall trait level as well as in profile shape.",
|
|
457
476
|
inputSchema: {
|
|
458
477
|
personas: z.array(z.string()).describe("List of persona names to select from"),
|
|
459
478
|
count: z.number().describe("Number of personas to select"),
|
|
@@ -555,10 +574,10 @@ Begin with the first persona: ${personas[0]}
|
|
|
555
574
|
});
|
|
556
575
|
server.registerTool("cognitive_load_estimate", {
|
|
557
576
|
title: "Cognitive Load Estimate",
|
|
558
|
-
description: "Estimate cognitive load for a specific persona on page metrics. Returns per-dimension breakdown (
|
|
577
|
+
description: "Estimate cognitive load for a specific persona on page metrics. Returns per-dimension breakdown (visual, attention, decision, motor, text, memory, patience) and identifies the bottleneck dimension. informationDensity is accepted and not read: through 20.x it was charged against the persona's comprehension, which is grasp of UI conventions, not reading; text load is the text dimension. Attention, motor and text use the persona's modelled capacities (sustained attention, Fitts pointing, reading). Visual is persona-invariant: no trait has a researched mapping onto visual-complexity tolerance, so it measures the page only and is listed under personaInvariant.",
|
|
559
578
|
inputSchema: {
|
|
560
579
|
persona: z.string().describe("Persona name"),
|
|
561
|
-
informationDensity: z.number().min(0).max(1).describe("Content density (0=sparse, 1=dense)"),
|
|
580
|
+
informationDensity: z.number().min(0).max(1).describe("Content density (0=sparse, 1=dense). Accepted for compatibility and not read since 21.0"),
|
|
562
581
|
visualComplexity: z.number().min(0).max(1).describe("Visual complexity (0=simple, 1=complex)"),
|
|
563
582
|
interactiveElements: z.number().describe("Number of interactive elements"),
|
|
564
583
|
textDensity: z.number().min(0).max(1).describe("Text heaviness (0=minimal, 1=wall of text)"),
|
|
@@ -584,7 +603,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
584
603
|
// bridged capacities. Unbridged, they sat at the 0.5 fallback and every
|
|
585
604
|
// persona scored identically on all three (B29, 2026-10-10). Same helper
|
|
586
605
|
// as cognitive_effort, site_cognitive_assessment and the story tool.
|
|
587
|
-
bridgeCapacityTraits(persona, traits, p.accessibilityTraits);
|
|
606
|
+
bridgeCapacityTraits(persona, traits, p.accessibilityTraits, personaRateContext(p));
|
|
588
607
|
const otProfile = buildOTProfile(persona, traits);
|
|
589
608
|
const load = estimateCognitiveLoad(otProfile, {
|
|
590
609
|
informationDensity, visualComplexity, interactiveElementCount: interactiveElements,
|
|
@@ -612,21 +631,21 @@ Begin with the first persona: ${personas[0]}
|
|
|
612
631
|
server.registerTool("cognitive_effort", {
|
|
613
632
|
_meta: { ui: { resourceUri: "ui://cbrowser/effort" } },
|
|
614
633
|
title: "Cognitive Effort Analysis",
|
|
615
|
-
description: "Compute total cognitive effort for a persona to use a page. MEASURES THE VIEWPORT BY DEFAULT (above the fold); pass scope='full_page' to measure the whole page. Uses the 8-layer Sequential Transport Chain:
|
|
634
|
+
description: "Compute total cognitive effort for a persona to use a page. MEASURES THE VIEWPORT BY DEFAULT (above the fold); pass scope='full_page' to measure the whole page. Uses the 8-layer Sequential Transport Chain: visual complexity (a count of visual elements and motion, not the attention model) → cognitive load → decision → motor (target acquisition) → motor procedure (executing a sequence) → frustration → readability (decoding) → reading attention (holding the line, re-reading). Site familiarity is per run: pass siteFamiliarity (0 first visit, 1 daily user) or every persona is scored as a first visit. No persona carries a familiarity of its own, and site knowledge from crawling is reported but does not change the score. The headline number, cognitiveTransportCost.total, is a percentile: the page's raw cost ranked among the real pages of the shipped reference set, for the built-in personas, at the same scope (0.8 = costlier than 80% of them). The verdict states that rank and its band; the cost is untested against human data, so it says nothing about how a person feels, and abandonmentRisk is a separate quantity.",
|
|
616
635
|
inputSchema: {
|
|
617
636
|
url: z.string().url().describe("URL to analyze"),
|
|
618
637
|
// Per RUN, not per persona. A persona is familiar with one site and new
|
|
619
638
|
// to another, so this only has an answer once a URL is named. The
|
|
620
639
|
// persona's stored value, where one exists, is the fallback. (2026-08-01)
|
|
621
640
|
siteFamiliarity: z.number().min(0).max(1).optional()
|
|
622
|
-
.describe("How familiar this persona is with THIS url: 0 first visit, 1 daily user.
|
|
623
|
-
persona: z.string().describe("Persona name.
|
|
641
|
+
.describe("How familiar this persona is with THIS url: 0 first visit, 1 daily user. Omitted, the run scores a first visit (0). Familiarity is a persona-site pair, not a disposition: no persona carries one, and site knowledge does not supply one."),
|
|
642
|
+
persona: z.string().describe("Persona name (e.g. first-timer, power-user, cognitive-adhd). To simulate someone who already knows this site, pass siteFamiliarity; the persona name does not imply it."),
|
|
624
643
|
_browserToken: z.string().optional().describe("Browser session token"),
|
|
625
644
|
userLocation: z.string().optional().describe("User's approximate location (e.g., 'Denver, Colorado, US')"),
|
|
626
645
|
userTimezone: z.string().optional().describe("User's timezone (e.g., 'America/Denver')"),
|
|
627
646
|
userLanguage: z.string().optional().describe("User's expected language (e.g., 'en-US')"),
|
|
628
647
|
proxy: z.string().optional().describe("Proxy server URL for geo-accurate testing"),
|
|
629
|
-
useValues: z.boolean().optional().default(false).describe("
|
|
648
|
+
useValues: z.boolean().optional().default(false).describe("Apply the persona's Schwartz values (opt-in). When true, the values modulate the page's demand inside the chain, through its ten registered coefficients (chain.valueModulation.*, hand-set hypotheses, counted in modelProvenance): stimulation and security on changeBlindness; achievement on satisficing and riskTolerance; conformity on socialProofSensitivity and anchoringBias; selfDirection on socialProofSensitivity; power on fearOfMissingOut; stimulation on patience; benevolence on emotionalContagion. Each adds coefficient x score x max(chain.valueModulation.demandFloor, demand) to that trait's demand, clamped to [0, 1]; hedonism, tradition and universalism change nothing. Applies to the aggregate score and, at scope 'sequential', to every screen. Scores come from the persona registry, the persona's own schwartzValues, its Big Five, or an account persona in the CMS; `valueModulation` in the response says whether any were found. Default: false (trait-only mode)."),
|
|
630
649
|
waitAfterLoad: z.number().optional().describe("Extra ms to wait after page loads (e.g., 3000 for sites with client-side translation)"),
|
|
631
650
|
waitForSelector: z.string().optional().describe("CSS selector to wait for after load (e.g., '[data-translated]')"),
|
|
632
651
|
scope: z.enum(["viewport", "full_page", "sequential"]).optional().default("viewport").describe("What to measure: 'viewport' (first impression, above-the-fold — default), 'full_page' (scroll the whole page, count every element), or 'sequential' (score each screen on its own content, carrying capacity forward, and report where people stop). Viewport and full_page match empathy_audit's parameter. 'sequential' answers a different question from either: not 'what would the whole page cost' but 'how far do they get'. NOTE: every published CTC number predates this parameter and is a viewport measurement."),
|
|
@@ -638,7 +657,9 @@ Begin with the first persona: ${personas[0]}
|
|
|
638
657
|
idempotentHint: true,
|
|
639
658
|
openWorldHint: true,
|
|
640
659
|
},
|
|
641
|
-
}, async ({ url, persona: personaName, siteFamiliarity, _browserToken, userLocation, userTimezone, userLanguage, proxy, useValues, waitAfterLoad, waitForSelector, scope }) => {
|
|
660
|
+
}, recordedAnswer(async ({ url, persona: personaName, siteFamiliarity, _browserToken, userLocation, userTimezone, userLanguage, proxy, useValues, waitAfterLoad, waitForSelector, scope }) => {
|
|
661
|
+
// recordedAnswer: one provenance recording per call, across every await,
|
|
662
|
+
// reported as modelProvenance at the end (answer-provenance.ts).
|
|
642
663
|
// Hosted: http(s) only (sec-paths, 2026-10-10; see hosted-inputs.ts).
|
|
643
664
|
const hostedRefusal = refuseHostedUrl("cognitive_effort", { url });
|
|
644
665
|
if (hostedRefusal)
|
|
@@ -734,12 +755,10 @@ Begin with the first persona: ${personas[0]}
|
|
|
734
755
|
const personaObj = isAgentPersonaObject(existingPersona)
|
|
735
756
|
? createCognitivePersona(personaName, personaName, {})
|
|
736
757
|
: existingPersona;
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
//
|
|
758
|
+
// siteFamiliarity: the caller's value for this run, else 0.0 (first
|
|
759
|
+
// visit). Site knowledge is looked up only to report it; it does not
|
|
760
|
+
// change the number (site-familiarity.ts has the 20.x history).
|
|
740
761
|
const traits = { ...(personaObj.cognitiveTraits || {}) };
|
|
741
|
-
const requestedFamiliarity = traits.siteFamiliarity ?? 0.5;
|
|
742
|
-
let familiarityWarning;
|
|
743
762
|
let hasSiteKnowledge = false;
|
|
744
763
|
try {
|
|
745
764
|
const pageUrl = await page.url();
|
|
@@ -750,26 +769,10 @@ Begin with the first persona: ${personas[0]}
|
|
|
750
769
|
hasSiteKnowledge = !!(stats && stats.navigationNodes > 0);
|
|
751
770
|
}
|
|
752
771
|
catch { }
|
|
753
|
-
//
|
|
754
|
-
//
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
const familiarityFromCaller = typeof siteFamiliarity === "number";
|
|
758
|
-
if (familiarityFromCaller) {
|
|
759
|
-
traits.siteFamiliarity = siteFamiliarity;
|
|
760
|
-
}
|
|
761
|
-
else if (hasSiteKnowledge) {
|
|
762
|
-
// Site knowledge exists — high-familiarity personas get max familiarity
|
|
763
|
-
if (requestedFamiliarity > 0.5)
|
|
764
|
-
traits.siteFamiliarity = 1.0;
|
|
765
|
-
}
|
|
766
|
-
else {
|
|
767
|
-
// No site knowledge — everyone is a first-time visitor
|
|
768
|
-
traits.siteFamiliarity = 0.0;
|
|
769
|
-
if (requestedFamiliarity > 0.5) {
|
|
770
|
-
familiarityWarning = `"${personaName}" has siteFamiliarity=${requestedFamiliarity.toFixed(1)} stored on the persona but no site knowledge exists for this domain. Downgraded to 0.0 (first visit). Familiarity is a persona-site pair: pass siteFamiliarity on this call to state it for this run, or build knowledge by navigating the site (page_understand goes deeper).`;
|
|
771
|
-
}
|
|
772
|
-
}
|
|
772
|
+
// The visit, not the persona: Cockburn et al.'s expertise e on the
|
|
773
|
+
// choosing time, handed to the demand below (Stage C). No trait carries it.
|
|
774
|
+
const familiarity = resolveSiteFamiliarity(siteFamiliarity, hasSiteKnowledge);
|
|
775
|
+
const demandOptions = { siteFamiliarity: familiarity.value };
|
|
773
776
|
// Bridge the formal models into the trait vector BEFORE the chain runs.
|
|
774
777
|
//
|
|
775
778
|
// getReadingProfile and getPointingProfile already model this persona's
|
|
@@ -785,6 +788,9 @@ Begin with the first persona: ${personas[0]}
|
|
|
785
788
|
// the persona's profile, so storing them would create a second copy that
|
|
786
789
|
// can drift from the model that produces it. (2026-08-02)
|
|
787
790
|
let pointingProfileForResponse;
|
|
791
|
+
// The ids the bridge read for THIS persona, reported as personaRateProvenance:
|
|
792
|
+
// collected from the call, so no cache state can change them.
|
|
793
|
+
let personaRateIds = new Set();
|
|
788
794
|
try {
|
|
789
795
|
const { bridgeCapacityTraits } = await import("../../visual/cognitive-models.js");
|
|
790
796
|
// accessibilityTraits passed so an author's STATED reading capacity is
|
|
@@ -794,14 +800,14 @@ Begin with the first persona: ${personas[0]}
|
|
|
794
800
|
// inline here and NOWHERE ELSE, which is why the two tools disagreed by
|
|
795
801
|
// 2.6x on the motor layer: the other path scored against the missing-trait
|
|
796
802
|
// fallback of 0.5 and called it a pointing ability.
|
|
797
|
-
pointingProfileForResponse = bridgeCapacityTraits(personaName, traits, accTraits);
|
|
803
|
+
({ result: pointingProfileForResponse, ids: personaRateIds } = collectPersonaReads(() => bridgeCapacityTraits(personaName, traits, accTraits, personaRateContext(personaObj))));
|
|
798
804
|
}
|
|
799
805
|
catch (e) {
|
|
800
806
|
// Left absent rather than defaulted: a missing capacity is visible as a
|
|
801
807
|
// gap, a defaulted one silently reads as average.
|
|
802
808
|
console.debug(`[cognitive_effort] capacity bridge unavailable: ${e.message}`);
|
|
803
809
|
}
|
|
804
|
-
// Build OT profile
|
|
810
|
+
// Build OT profile
|
|
805
811
|
const otProfile = buildOTProfile(personaName, traits);
|
|
806
812
|
// Extract page metrics
|
|
807
813
|
const { extractPageMetrics } = await import("../../visual/cognitive-transport.js");
|
|
@@ -833,111 +839,110 @@ Begin with the first persona: ${personas[0]}
|
|
|
833
839
|
// full_page since that is the comparable whole-document number.
|
|
834
840
|
const pageMetrics = await extractPageMetrics(page, measureScope === "sequential" ? "full_page" : measureScope, sharedGeometry);
|
|
835
841
|
// Compute full COT
|
|
836
|
-
const { computeDemandDistribution, computeSequentialCTC } = await import("../../visual/cognitive-transport-chain.js");
|
|
837
|
-
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
842
|
+
const { computeDemandDistribution, computeSequentialCTC, computeSequentialScrollCTC } = await import("../../visual/cognitive-transport-chain.js");
|
|
843
|
+
// Motivational values (opt-in, default off). Resolved here and handed to
|
|
844
|
+
// the chain, which applies them through its own registered modulation
|
|
845
|
+
// (VALUE_DEMAND_MODULATION, chain.valueModulation.*) and clamps the result
|
|
846
|
+
// to [0, 1].
|
|
847
|
+
//
|
|
848
|
+
// Through 21.0 this tool had a second path of its own: a
|
|
849
|
+
// conservation/openness formula with unregistered 0.3 and 0.5 constants
|
|
850
|
+
// that scaled five decision demands and emotionalContagion in place,
|
|
851
|
+
// unclamped (up to 1.3), while the chain's modulation was reached from no
|
|
852
|
+
// tool at all. Two hand-set models of one effect, and the one that ran was
|
|
853
|
+
// the one the provenance block could not see. (2026-10-22)
|
|
854
|
+
// One resolver, shared with visual_cognitive_story (chain-values.ts).
|
|
855
|
+
const schwartzValues = useValues ? await resolveChainValues(personaName) : undefined;
|
|
856
|
+
const chainOptions = { asymmetric: true, interactions: true, ...(schwartzValues ? { schwartzValues } : {}) };
|
|
857
|
+
// SEQUENTIAL PASS, measuring half. The per-screen metrics are read here and
|
|
858
|
+
// the curve is scored in the block below with everything else, so its
|
|
859
|
+
// constants are counted too. A failure here degrades to the normal answer
|
|
860
|
+
// rather than losing the whole call.
|
|
861
|
+
let perScreen;
|
|
862
|
+
let sequentialError;
|
|
841
863
|
if (measureScope === "sequential") {
|
|
842
864
|
try {
|
|
843
865
|
const { extractPerScreenMetrics } = await import("../../visual/cognitive-transport.js");
|
|
844
|
-
const { computeSequentialScrollCTC } = await import("../../visual/cognitive-transport-chain.js");
|
|
845
866
|
// The SAME measurement the caption came from, so the curve length and the
|
|
846
867
|
// caption cannot disagree.
|
|
847
|
-
|
|
848
|
-
const seqProfile = buildOTProfile(personaName, traits);
|
|
849
|
-
const seq = computeSequentialScrollCTC(seqProfile, perScreen.map((m) => computeDemandDistribution(m)), { asymmetric: true, interactions: true });
|
|
850
|
-
sequentialResult = {
|
|
851
|
-
screensMeasured: seq.screens.length,
|
|
852
|
-
// The INPUTS the screen count came from, published so a differing curve is
|
|
853
|
-
// diagnosable instead of guessable. An external client measured 5.8 screens
|
|
854
|
-
// on a session's first navigation and 6.7 on every later one; with only
|
|
855
|
-
// `screensMeasured: 7` in the payload a 6-screen curve is internally
|
|
856
|
-
// consistent and unmarked, which is the shape of every defect this tool has
|
|
857
|
-
// had. An invisible variance is indistinguishable from no variance.
|
|
858
|
-
geometry: {
|
|
859
|
-
documentHeight: sharedGeometry.documentHeight,
|
|
860
|
-
viewportHeight: sharedGeometry.viewportHeight,
|
|
861
|
-
pageScreens: sharedGeometry.pageScreens,
|
|
862
|
-
screenCount: sharedGeometry.screenCount,
|
|
863
|
-
note: "One measurement, shared by the curve length and by the pageScreens caption, so the two cannot disagree. Page height can change between navigations; if two runs of the same URL differ, compare documentHeight before anything else.",
|
|
864
|
-
},
|
|
865
|
-
totalAbandonment: `${Math.round(seq.totalAbandonment * 100)}%`,
|
|
866
|
-
expectedDepthScreens: seq.expectedDepthScreens,
|
|
867
|
-
dropOff: seq.dropOff
|
|
868
|
-
? { screen: seq.dropOff.screen, layer: seq.dropOff.layer, shareOfSessions: `${Math.round(seq.dropOff.share * 100)}%` }
|
|
869
|
-
: null,
|
|
870
|
-
reachWeightedCTC: seq.reachWeightedCTC,
|
|
871
|
-
curve: seq.screens.map((x) => ({
|
|
872
|
-
screen: x.screen,
|
|
873
|
-
reach: `${Math.round(x.reachProbability * 100)}%`,
|
|
874
|
-
stopHere: `${Math.round(x.stopHazard * 100)}%`,
|
|
875
|
-
cumulativeAbandonment: `${Math.round(x.cumulativeAbandonment * 100)}%`,
|
|
876
|
-
screenCTC: x.screenCTC,
|
|
877
|
-
bottleneck: x.bottleneckLayer,
|
|
878
|
-
})),
|
|
879
|
-
note: "Each screen is measured on ITS OWN content, and capacity carries forward -- screen 7 is scored against what screens 1-6 left, not against a fresh persona. Count-based demands (interactive elements, choices) charge only elements not seen on an earlier screen, so a sticky nav is one decision rather than one per screen; text and visual density are NOT deduplicated, because re-encountering text is re-reading it.",
|
|
880
|
-
differsFrom: "The aggregate `abandonmentRisk` above asks what the whole document would cost if it were all processed. This asks how far they get. They are different questions and will not agree.",
|
|
881
|
-
uncalibrated: "The per-screen hazard uses the same ABANDONMENT_HILL_EXPONENT as the aggregate, which nothing empirical set. At 2 the curve is superlinear, so this model inherits a thumb on the scale.",
|
|
882
|
-
readThisFirst: "TRUST THE SHAPE, NOT THE TOTAL. `totalAbandonment` compounds a per-screen hazard across every screen, so on a long page it saturates toward 100% almost regardless of the page -- measured, elderly-user reached 100% on a 7-screen university homepage. That is the uncalibrated exponent compounding, not a finding about that page. `expectedDepthScreens` and `dropOff` are the numbers to act on: they say how far people get and what stops them, and both depend on the RANKING of per-screen hazards rather than their absolute level, which is the part the exponent least disturbs. `dropOff` is persona-dependent by design and that is not noise: an impatient reader drops on screen one, a patient one survives to whichever screen is genuinely hardest, and the two answers are both correct for their reader.",
|
|
883
|
-
};
|
|
868
|
+
perScreen = await extractPerScreenMetrics(page, 20, sharedGeometry);
|
|
884
869
|
}
|
|
885
870
|
catch (e) {
|
|
886
|
-
|
|
871
|
+
sequentialError = e.message;
|
|
887
872
|
}
|
|
888
873
|
}
|
|
889
|
-
//
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const res = await fetch(`${cmsUrl}/api/personas`, { headers: { "Authorization": `Bearer ${_sessionApiKey}` } });
|
|
903
|
-
if (res.ok) {
|
|
904
|
-
const data = await res.json();
|
|
905
|
-
const match = data.personas.find((p) => p.slug === personaName || p.name.toLowerCase() === personaName.toLowerCase());
|
|
906
|
-
if (match?.schwartz_values) {
|
|
907
|
-
const sv = typeof match.schwartz_values === "string" ? JSON.parse(match.schwartz_values) : match.schwartz_values;
|
|
908
|
-
const pv = createPV2({ selfDirection: sv.selfDirection ?? 0.5, stimulation: sv.stimulation ?? 0.5, hedonism: sv.hedonism ?? 0.5, achievement: sv.achievement ?? 0.5, power: sv.power ?? 0.5, security: sv.security ?? 0.5, conformity: sv.conformity ?? 0.5, tradition: sv.tradition ?? 0.5, benevolence: sv.benevolence ?? 0.5, universalism: sv.universalism ?? 0.5 }, { autonomyNeed: sv.autonomyNeed ?? 0.5, competenceNeed: sv.competenceNeed ?? 0.5, relatednessNeed: sv.relatednessNeed ?? 0.5 }, "esteem");
|
|
909
|
-
regPV2([{ personaName, values: pv, rationale: "Custom persona from CMS" }]);
|
|
910
|
-
pValues = pv;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
catch { /* CMS fallback failed */ }
|
|
874
|
+
// The demand curves, the chain, the value modulation when useValues
|
|
875
|
+
// applied it, and the sequential curve when scope asks for it. These used
|
|
876
|
+
// to run under their own synchronous withProvenance, the only part of the
|
|
877
|
+
// answer the recorder could see while it was module state; the whole
|
|
878
|
+
// handler is one recording now (recordedAnswer), so this is just the
|
|
879
|
+
// computation, and the block reported below counts it with everything
|
|
880
|
+
// else the answer read. (2026-10-22)
|
|
881
|
+
const scored = (() => {
|
|
882
|
+
let seq;
|
|
883
|
+
let seqError = sequentialError;
|
|
884
|
+
if (perScreen) {
|
|
885
|
+
try {
|
|
886
|
+
seq = computeSequentialScrollCTC(otProfile, perScreen.map((m) => computeDemandDistribution(m, demandOptions)), chainOptions);
|
|
916
887
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const conservation = (pValues.security + pValues.conformity + pValues.tradition) / 3;
|
|
920
|
-
// Openness (selfDirection+stimulation)/2 reduces decision cost (comfortable with choices)
|
|
921
|
-
const openness = (pValues.selfDirection + pValues.stimulation) / 2;
|
|
922
|
-
// Achievement amplifies frustration layer (impatience)
|
|
923
|
-
const achMod = 1 + (pValues.achievement - 0.5) * 0.3;
|
|
924
|
-
// Scale decision-related demands: satisficing, anchoringBias, riskTolerance, fearOfMissingOut
|
|
925
|
-
const decisionScale = 1 + (conservation - openness) * 0.3; // -0.15 to +0.15 range
|
|
926
|
-
for (const dim of ["satisficing", "anchoringBias", "riskTolerance", "fearOfMissingOut", "socialProofSensitivity"]) {
|
|
927
|
-
if (demand.demands[dim] !== undefined) {
|
|
928
|
-
demand.demands[dim] *= decisionScale;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
// Scale frustration-related demands
|
|
932
|
-
for (const dim of ["resilience", "selfEfficacy", "emotionalContagion"]) {
|
|
933
|
-
if (demand.demands[dim] !== undefined) {
|
|
934
|
-
demand.demands[dim] *= achMod;
|
|
935
|
-
}
|
|
936
|
-
}
|
|
888
|
+
catch (e) {
|
|
889
|
+
seqError = e.message;
|
|
937
890
|
}
|
|
938
891
|
}
|
|
939
|
-
|
|
940
|
-
|
|
892
|
+
const demand = computeDemandDistribution(pageMetrics, demandOptions);
|
|
893
|
+
return { aggregate: computeSequentialCTC(otProfile, demand, chainOptions), seq, seqError };
|
|
894
|
+
})();
|
|
895
|
+
const result = scored.aggregate;
|
|
896
|
+
let sequentialResult;
|
|
897
|
+
if (scored.seq) {
|
|
898
|
+
const seq = scored.seq;
|
|
899
|
+
sequentialResult = {
|
|
900
|
+
screensMeasured: seq.screens.length,
|
|
901
|
+
// The INPUTS the screen count came from, published so a differing curve is
|
|
902
|
+
// diagnosable instead of guessable. An external client measured 5.8 screens
|
|
903
|
+
// on a session's first navigation and 6.7 on every later one; with only
|
|
904
|
+
// `screensMeasured: 7` in the payload a 6-screen curve is internally
|
|
905
|
+
// consistent and unmarked, which is the shape of every defect this tool has
|
|
906
|
+
// had. An invisible variance is indistinguishable from no variance.
|
|
907
|
+
geometry: {
|
|
908
|
+
documentHeight: sharedGeometry.documentHeight,
|
|
909
|
+
viewportHeight: sharedGeometry.viewportHeight,
|
|
910
|
+
pageScreens: sharedGeometry.pageScreens,
|
|
911
|
+
screenCount: sharedGeometry.screenCount,
|
|
912
|
+
note: "One measurement, shared by the curve length and by the pageScreens caption, so the two cannot disagree. Page height can change between navigations; if two runs of the same URL differ, compare documentHeight before anything else.",
|
|
913
|
+
},
|
|
914
|
+
totalAbandonment: `${Math.round(seq.totalAbandonment * 100)}%`,
|
|
915
|
+
expectedDepthScreens: seq.expectedDepthScreens,
|
|
916
|
+
dropOff: seq.dropOff
|
|
917
|
+
? { screen: seq.dropOff.screen, layer: seq.dropOff.layer, shareOfSessions: `${Math.round(seq.dropOff.share * 100)}%` }
|
|
918
|
+
: null,
|
|
919
|
+
// The reach-weighted MEAN of the screens' percentiles: where the typical
|
|
920
|
+
// screen this persona actually sees ranks. Replaces reachWeightedCTC,
|
|
921
|
+
// the reach-weighted SUM, which grew with every screen reached and so
|
|
922
|
+
// ranked a long page costlier for being long; a sum of percentiles is
|
|
923
|
+
// not a percentile of anything. The chain still returns the sum.
|
|
924
|
+
reachWeightedMeanScreenCTC: (() => {
|
|
925
|
+
const reach = seq.screens.reduce((a, x) => a + x.reachProbability, 0);
|
|
926
|
+
return reach > 0 ? Math.round((seq.screens.reduce((a, x) => a + x.reachProbability * x.screenCTC, 0) / reach) * 1000) / 1000 : null;
|
|
927
|
+
})(),
|
|
928
|
+
curve: seq.screens.map((x) => ({
|
|
929
|
+
screen: x.screen,
|
|
930
|
+
reach: `${Math.round(x.reachProbability * 100)}%`,
|
|
931
|
+
stopHere: `${Math.round(x.stopHazard * 100)}%`,
|
|
932
|
+
cumulativeAbandonment: `${Math.round(x.cumulativeAbandonment * 100)}%`,
|
|
933
|
+
screenCTC: x.screenCTC,
|
|
934
|
+
bottleneck: x.bottleneckLayer,
|
|
935
|
+
})),
|
|
936
|
+
note: "Each screen is measured on ITS OWN content, and capacity carries forward -- screen 7 is scored against what screens 1-6 left, not against a fresh persona. Count-based demands (interactive elements, choices) charge only elements not seen on an earlier screen, so a sticky nav is one decision rather than one per screen; text and visual density are NOT deduplicated, because re-encountering text is re-reading it.",
|
|
937
|
+
screenCTCNote: "screenCTC is a percentile, like the aggregate total: the screen's raw cost ranked among the first screens of the real reference pages, scored for the built-in personas arriving fresh. A later screen is scored against the capacity earlier screens left, so its rank reflects that depletion as well as its own content. reachWeightedMeanScreenCTC averages the screens' percentiles, each weighted by the chance of reaching it: the rank of the typical screen this persona sees. It replaces reachWeightedCTC, an unnormalised sum that grew with the number of screens reached.",
|
|
938
|
+
differsFrom: "The aggregate `abandonmentRisk` above asks what the whole document would cost if it were all processed. This asks how far they get. They are different questions and will not agree.",
|
|
939
|
+
uncalibrated: "The per-screen hazard uses the same ABANDONMENT_HILL_EXPONENT as the aggregate, which nothing empirical set. At 2 the curve is superlinear, so this model inherits a thumb on the scale.",
|
|
940
|
+
readThisFirst: "TRUST THE SHAPE, NOT THE TOTAL. `totalAbandonment` compounds a per-screen hazard across every screen, so on a long page it saturates toward 100% almost regardless of the page -- measured, elderly-user reached 100% on a 7-screen university homepage. That is the uncalibrated exponent compounding, not a finding about that page. `expectedDepthScreens` and `dropOff` are the numbers to act on: they say how far people get and what stops them, and both depend on the RANKING of per-screen hazards rather than their absolute level, which is the part the exponent least disturbs. `dropOff` is persona-dependent by design and that is not noise: an impatient reader drops on screen one, a patient one survives to whichever screen is genuinely hardest, and the two answers are both correct for their reader.",
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
else if (scored.seqError !== undefined) {
|
|
944
|
+
sequentialResult = { error: `sequential pass failed: ${scored.seqError}`, note: "The aggregate figures below are unaffected." };
|
|
945
|
+
}
|
|
941
946
|
// Also compute motor and readability from formal models
|
|
942
947
|
let motorResult = null;
|
|
943
948
|
let readabilityResult = null;
|
|
@@ -1106,6 +1111,9 @@ Begin with the first persona: ${personas[0]}
|
|
|
1106
1111
|
return "";
|
|
1107
1112
|
} };
|
|
1108
1113
|
const redirected = !!landedUrl && hostOf(landedUrl) !== "" && hostOf(landedUrl) !== hostOf(url);
|
|
1114
|
+
// Where the cost ranks among the real reference pages (ctc-bands.ts): the
|
|
1115
|
+
// band, the rank sentence and the verdict all read this one value.
|
|
1116
|
+
const rank = ctcRank(result);
|
|
1109
1117
|
const response = {
|
|
1110
1118
|
url,
|
|
1111
1119
|
...(redirected ? {
|
|
@@ -1113,27 +1121,61 @@ Begin with the first persona: ${personas[0]}
|
|
|
1113
1121
|
redirectNote: `Requested ${hostOf(url)} and landed on ${hostOf(landedUrl)}. Every number below describes ${hostOf(landedUrl)}.`,
|
|
1114
1122
|
} : { resolvedUrl: landedUrl }),
|
|
1115
1123
|
persona: personaName,
|
|
1124
|
+
// Status counts of every registry constant the score read, and the ids
|
|
1125
|
+
// of the hand-set ones. A placeholder here so the field keeps its place
|
|
1126
|
+
// in the JSON; filled as the last step before serialising, so the block
|
|
1127
|
+
// covers the whole score: page measurement, demand, chain, values,
|
|
1128
|
+
// sequential curve and verdict. The saliency overlay, drawn as context,
|
|
1129
|
+
// carries its own block on its layerOverlays entry.
|
|
1130
|
+
modelProvenance: undefined,
|
|
1131
|
+
// The scored persona's motor and reading rates, counted apart from the
|
|
1132
|
+
// score's constants (they describe the persona).
|
|
1133
|
+
personaRateProvenance: personaRateProvenance(personaRateIds),
|
|
1134
|
+
// Stage B: the four law times the motor, decision, procedure and reading
|
|
1135
|
+
// demands come from, beside the demand each makes against the budget.
|
|
1136
|
+
lawTimes: lawTimesReport(pageMetrics, familiarity.value),
|
|
1137
|
+
// Only when useValues asked for it, so a persona with no values is
|
|
1138
|
+
// visibly scored without them rather than silently.
|
|
1139
|
+
...(useValues ? { valueModulation: valueModulationReport(schwartzValues) } : {}),
|
|
1116
1140
|
cognitiveTransportCost: {
|
|
1141
|
+
// A percentile (21.x): raw ranked among the real reference pages for
|
|
1142
|
+
// the built-in personas at the same scope. 0.8 is costlier than 80%.
|
|
1117
1143
|
total: Math.round(result.totalCTC * 1000) / 1000,
|
|
1144
|
+
// The 95% interval of that percentile across bootstrap resamples of
|
|
1145
|
+
// the reference pages: how firmly the reference fixes the rank.
|
|
1146
|
+
totalCTCInterval: result.totalCTCInterval.map((x) => Math.round(x * 1000) / 1000),
|
|
1147
|
+
band: rank.band,
|
|
1148
|
+
referenceScope: result.referenceScope,
|
|
1149
|
+
rank: rank.sentence,
|
|
1150
|
+
// The whole percents that sentence states, for a view to say the
|
|
1151
|
+
// same numbers. total and totalCTCInterval are rounded to three
|
|
1152
|
+
// places, and recomputed from them the rank can differ by a point
|
|
1153
|
+
// (cbrowser.ai: 0.8098 is stated 80 and shipped as 0.81). end names
|
|
1154
|
+
// the two ends, where the sentence states a place, not a percent.
|
|
1155
|
+
rankFigures: {
|
|
1156
|
+
percent: rank.percent,
|
|
1157
|
+
interval: rank.interval,
|
|
1158
|
+
end: result.totalCTC === 0 ? "cheapest" : result.totalCTC === 1 ? "costliest" : null,
|
|
1159
|
+
},
|
|
1118
1160
|
additive: Math.round(result.additiveCTC * 1000) / 1000,
|
|
1119
|
-
// The un-normalized cost. `total` is
|
|
1120
|
-
// are not interchangeable and any ratio must use `raw`.
|
|
1161
|
+
// The un-normalized cost. `total` is its percentile among real pages,
|
|
1162
|
+
// so the two are not interchangeable and any ratio must use `raw`.
|
|
1121
1163
|
raw: Math.round(result.rawCTC * 1000) / 1000,
|
|
1122
1164
|
// raw / additive — both in layer-cost units, so this is the genuine
|
|
1123
1165
|
// contribution of interactions and sequential depletion.
|
|
1124
1166
|
chainCoefficient: Math.round((result.rawCTC / Math.max(0.001, result.additiveCTC)) * 1000) / 1000,
|
|
1125
1167
|
/**
|
|
1126
1168
|
* @deprecated Use chainCoefficient. NOTE ITS VALUE CHANGED: this used
|
|
1127
|
-
* to divide `total` (a
|
|
1169
|
+
* to divide `total` (then a logistic, 0-1) by `additive` (a raw sum) and
|
|
1128
1170
|
* reported ~0.3-0.5, which read as the chain dampening by 60%. That
|
|
1129
1171
|
* was a unit error, not a measurement — it was reporting the sigmoid.
|
|
1130
1172
|
* Preserving the old number bit-for-bit would preserve a meaningless
|
|
1131
1173
|
* one, so this now carries the corrected value.
|
|
1132
1174
|
*/
|
|
1133
1175
|
sequentialAmplification: Math.round((result.rawCTC / Math.max(0.001, result.additiveCTC)) * 1000) / 1000,
|
|
1134
|
-
chainNote: "chainCoefficient = raw / additive, both in layer-cost units. Above 1 means interactions and sequential depletion added cost. Re-measured 1.002-1.036 at EIGHT layers, after `motor` was split into target acquisition and sequence execution (2026-08-03). It was 1.003-1.054 at seven and 1.002-1.019 at six. The range narrowed rather than widening this time: splitting one layer into two halves each layer's per-step cost, so the same total is reached through smaller increments and less of it compounds. Real, and small. Do NOT compute total/additive — total is
|
|
1176
|
+
chainNote: "chainCoefficient = raw / additive, both in layer-cost units. Above 1 means interactions and sequential depletion added cost. Re-measured 1.002-1.036 at EIGHT layers, after `motor` was split into target acquisition and sequence execution (2026-08-03). It was 1.003-1.054 at seven and 1.002-1.019 at six. The range narrowed rather than widening this time: splitting one layer into two halves each layer's per-step cost, so the same total is reached through smaller increments and less of it compounds. Real, and small. Do NOT compute total/additive — total is the percentile of raw among the real reference pages, not a cost, and the ratio compares different units.",
|
|
1135
1177
|
interpretationBasis: "total",
|
|
1136
|
-
interpretationBands:
|
|
1178
|
+
interpretationBands: `total is a percentile of the real reference pages, banded ${ctcBandsDescription()}. The cut points are registered definitions (ctcBands.*), chosen as conventions and not fitted. A band is a rank among real pages: the CTC is untested against human data, so it says nothing about how a person experiences the page, and it is not an abandonment prediction (that is abandonmentRisk, a separate quantity). totalCTCInterval is the 95% interval of the percentile across bootstrap resamples of the reference pages: how firmly the reference pages fix the rank, not whether the cost is right.`,
|
|
1137
1179
|
},
|
|
1138
1180
|
layers: result.layers.map(l => ({
|
|
1139
1181
|
name: l.name,
|
|
@@ -1171,10 +1213,11 @@ Begin with the first persona: ${personas[0]}
|
|
|
1171
1213
|
abandonmentRisk: Math.round(result.abandonmentRisk * 100) + "%",
|
|
1172
1214
|
// Says out loud that these two are different constructs.
|
|
1173
1215
|
//
|
|
1174
|
-
// A run can
|
|
1175
|
-
// correct, which looks like a contradiction and is not one:
|
|
1176
|
-
// abandonmentRisk is a
|
|
1177
|
-
//
|
|
1216
|
+
// A run can rank in the lower half on CTC and read 53% on abandonment
|
|
1217
|
+
// and both be correct, which looks like a contradiction and is not one:
|
|
1218
|
+
// abandonmentRisk is a Hill hazard of the deficit (unmet demand; surplus
|
|
1219
|
+
// no longer relieves it, 21.0) against the blended patience and
|
|
1220
|
+
// resilience this persona has LEFT after the page (21.0), and never touches
|
|
1178
1221
|
// totalCTC. CTC asks how expensive the page is; abandonment asks
|
|
1179
1222
|
// whether THIS persona's tolerance runs out first. A page can be cheap
|
|
1180
1223
|
// in absolute terms and still exceed someone with little patience.
|
|
@@ -1219,7 +1262,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
1219
1262
|
pointingInputs: {
|
|
1220
1263
|
fittsThroughput: Math.round(pp.throughput * 100) / 100,
|
|
1221
1264
|
endpointDispersionPx: Math.round(((pp.sigmaX + pp.sigmaY) / 2) * 10) / 10,
|
|
1222
|
-
note: "throughput drives movementTimeMs; dispersion drives hitProbability. motorCapacity
|
|
1265
|
+
note: "throughput drives movementTimeMs; dispersion drives hitProbability. motorCapacity is throughput / (throughput + 4.9 bits/s, the reference mouse throughput) and does not read dispersion, so the `motor` layer and `hitProbability` legitimately rank personas differently.",
|
|
1223
1266
|
},
|
|
1224
1267
|
note: "These two are additive and unweighted against each other. There is no empirical basis for a relative weighting between acquiring a target and executing a sequence, so none is applied - see MOTOR_LAYER_WEIGHTS, both 1.0 and marked uncalibrated.",
|
|
1225
1268
|
};
|
|
@@ -1277,7 +1320,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
1277
1320
|
...(readabilityResult.blocks.length > 0 ? {
|
|
1278
1321
|
hardestBlockPenaltyMs: readabilityResult.blocks
|
|
1279
1322
|
.reduce((worst, b) => b.difficulty > worst.difficulty ? b : worst).penaltyBreakdown,
|
|
1280
|
-
penaltyNote: "hardestBlockPenaltyMs carries every component, including those that came to 0. A 0 means the component was evaluated and does not apply to this text -- crowding, for one, only begins below 16px (Pelli et al. 2004) -- not that it is unmodelled. crowdingSensitivity
|
|
1323
|
+
penaltyNote: "hardestBlockPenaltyMs carries every component, including those that came to 0. A 0 means the component was evaluated and does not apply to this text -- crowding, for one, only begins below 16px (Pelli et al. 2004) -- not that it is unmodelled. Since Stage B crowdingSensitivity does not enter readingCapacity: that is a rate ratio, from the persona's registered reading rate or, for a custom persona that states a reading profile, the fixation model's text-independent terms (base, phonological, visual span). Crowding is priced only here, in this audit's per-block time below 16px.",
|
|
1281
1324
|
} : {}),
|
|
1282
1325
|
},
|
|
1283
1326
|
} : {}),
|
|
@@ -1301,13 +1344,13 @@ Begin with the first persona: ${personas[0]}
|
|
|
1301
1344
|
const screens = pageMetrics.pageScreens > 1.2
|
|
1302
1345
|
? `${wholeDocument ? " Measured across" : `${where}; the page is`} ${pageMetrics.pageScreens} screens${wholeDocument ? "." : ", so most of it was not measured — pass scope='full_page' for the whole thing.)"}${seqTail}`
|
|
1303
1346
|
: where ? `${where}.)` : seqTail;
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1347
|
+
// The rank, not a comfort claim. Through 21.0 this said "should handle
|
|
1348
|
+
// comfortably", "will struggle significantly" and "is likely to
|
|
1349
|
+
// abandon" off fixed points on a logistic nobody fitted; the CTC is
|
|
1350
|
+
// untested against human data, so a sentence on it can say where the
|
|
1351
|
+
// page ranks among real pages and nothing about how a person feels.
|
|
1352
|
+
// Abandonment stays in abandonmentRisk, its own quantity.
|
|
1353
|
+
const verdict = `For ${personaName}, ${subject} is ${rank.statement}. ${rank.bandSentence} Bottleneck: ${result.bottleneckLayer}.`;
|
|
1311
1354
|
return verdict + screens;
|
|
1312
1355
|
})(),
|
|
1313
1356
|
// ONE AUTHORITATIVE FIELD PER READING DIMENSION, stated rather than
|
|
@@ -1337,7 +1380,6 @@ Begin with the first persona: ${personas[0]}
|
|
|
1337
1380
|
scope: measureScope,
|
|
1338
1381
|
pageScreens: pageMetrics.pageScreens,
|
|
1339
1382
|
scopeNote: prose.scopeNote,
|
|
1340
|
-
...(familiarityWarning ? { familiarityWarning, siteFamiliarityAdjusted: true, originalFamiliarity: requestedFamiliarity, effectiveFamiliarity: traits.siteFamiliarity } : {}),
|
|
1341
1383
|
// Reports what was USED, not what was passed.
|
|
1342
1384
|
//
|
|
1343
1385
|
// This previously said "supplied for this run" whenever the caller sent
|
|
@@ -1347,13 +1389,12 @@ Begin with the first persona: ${personas[0]}
|
|
|
1347
1389
|
// asserting the parameter had been applied. An attestation nobody
|
|
1348
1390
|
// verifies is worse than no attestation, because it makes an
|
|
1349
1391
|
// output-level audit report the knob as working. (2026-08-02)
|
|
1350
|
-
siteFamiliarity:
|
|
1351
|
-
siteFamiliaritySource:
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
siteFamiliarityEffect: `Consumed by the cognitiveLoad layer against a demand set by navigation depth. A page reachable in one click demands no site knowledge, so this changes nothing there.`,
|
|
1392
|
+
siteFamiliarity: familiarity.value,
|
|
1393
|
+
siteFamiliaritySource: familiarity.source,
|
|
1394
|
+
// Reported, not applied: crawling a site says nothing about whether
|
|
1395
|
+
// THIS persona has used it.
|
|
1396
|
+
siteKnowledgeAvailable: familiarity.siteKnowledgeAvailable,
|
|
1397
|
+
siteFamiliarityEffect: `Cockburn, Gutwin & Greenberg's (2007) expertise e on the choosing time: (1 - e) x novice visual search + e x the Hick-Hyman expert limit, so a familiar visitor is asked less search by every choice on the page. It moves the cognitiveLoad layer (convention knowledge, working memory, foraging, transfer), the same for every persona; the decision dispositions keep the first-visit time. It is not a persona trait. The score is still ranked against first visits (e = 0).`,
|
|
1357
1398
|
...(languageWarning ? { languageWarning } : {}),
|
|
1358
1399
|
...(userLocation ? { userContext: { location: userLocation, timezone: userTimezone, language: userLanguage } } : {}),
|
|
1359
1400
|
...(token ? { _browserToken: token } : {}),
|
|
@@ -1367,6 +1408,7 @@ Begin with the first persona: ${personas[0]}
|
|
|
1367
1408
|
let motorTemp;
|
|
1368
1409
|
try {
|
|
1369
1410
|
if (motorResult && motorResult.elements.length > 0) {
|
|
1411
|
+
const { writeFileSync, mkdirSync, existsSync } = await import("fs");
|
|
1370
1412
|
motorTemp = await makeTempDir("cog-effort");
|
|
1371
1413
|
const ssPath = motorTemp.file("page.png");
|
|
1372
1414
|
await page.screenshot({ path: ssPath, fullPage: false });
|
|
@@ -1435,20 +1477,32 @@ Begin with the first persona: ${personas[0]}
|
|
|
1435
1477
|
layersTemp = await makeTempDir("cog-effort-layers");
|
|
1436
1478
|
const ssPath = layersTemp.file("page.png");
|
|
1437
1479
|
await page.screenshot({ path: ssPath, fullPage: false });
|
|
1438
|
-
//
|
|
1439
|
-
// so this needs no API key and no model call.
|
|
1480
|
+
// visualComplexity -- keyless. Lab-space centre-surround over the
|
|
1481
|
+
// screenshot, so this needs no API key and no model call. It is shown as
|
|
1482
|
+
// visual context for the layer, NOT as its input: the layer's cost comes
|
|
1483
|
+
// from the visual-complexity count and the animation level, and this map
|
|
1484
|
+
// never reaches the chain. The legend says so, because the layer was
|
|
1485
|
+
// called "saliency" until 21.0 and this heatmap read as its evidence.
|
|
1440
1486
|
try {
|
|
1441
1487
|
const { computeLabSaliency } = await import("../../visual/attention-transport.js");
|
|
1442
1488
|
const { generateHeatmapOverlay } = await import("../../visual/heatmap-overlay.js");
|
|
1443
|
-
|
|
1444
|
-
|
|
1489
|
+
// Recorded apart from the score. The map reads the attention model's
|
|
1490
|
+
// constants (published colour science, fitted gaze calibration), and
|
|
1491
|
+
// counted in modelProvenance they made the score look partly fitted
|
|
1492
|
+
// while the map is context, not an input. Its own block rides on
|
|
1493
|
+
// this overlay entry.
|
|
1494
|
+
const { result: b64, provenance: mapProvenance } = await isolatedProvenanceAsync(async () => {
|
|
1495
|
+
const sal = await computeLabSaliency(ssPath);
|
|
1496
|
+
return generateHeatmapOverlay(ssPath, sal.cells, sal.rows, sal.cols, `${personaName} — saliency`);
|
|
1497
|
+
}, ATTENTION_PROVENANCE_TEXT);
|
|
1445
1498
|
const w = writeArtifact(Buffer.from(b64, "base64"), `saliency-${personaName}-${Date.now()}.png`);
|
|
1446
1499
|
if (w)
|
|
1447
|
-
layerOverlays.push({ layer: "
|
|
1448
|
-
legend: "Hotter = more visually salient
|
|
1500
|
+
layerOverlays.push({ layer: "visualComplexity", file: w.url.split("/").pop(), available: true,
|
|
1501
|
+
legend: "Lab-space saliency of the screenshot, shown as context. Hotter = more visually salient. The layer's cost is not computed from this map: it comes from the page's visual-complexity count and animation level.",
|
|
1502
|
+
provenance: mapProvenance });
|
|
1449
1503
|
}
|
|
1450
1504
|
catch (e) {
|
|
1451
|
-
layerOverlays.push({ layer: "
|
|
1505
|
+
layerOverlays.push({ layer: "visualComplexity", available: false, reason: `saliency map failed: ${e.message}` });
|
|
1452
1506
|
}
|
|
1453
1507
|
// motor -- already drawn above; surfaced here so the chain has one list.
|
|
1454
1508
|
if (response.motorOverlayUrl) {
|
|
@@ -1512,6 +1566,9 @@ Begin with the first persona: ${personas[0]}
|
|
|
1512
1566
|
response.layerOverlays = layerOverlays;
|
|
1513
1567
|
response.layerOverlayNote = "Each entry maps to a bar in the transport chain. Layers without an overlay say why.";
|
|
1514
1568
|
}
|
|
1569
|
+
// Last, so it covers everything above. Serialising the previews reads no
|
|
1570
|
+
// registered constant.
|
|
1571
|
+
response.modelProvenance = activeProvenance();
|
|
1515
1572
|
const content = motorPng
|
|
1516
1573
|
? await buildContentWithPreviews(response, [{ name: "motor", png: motorPng, fullResolution: motorFullResolution }], (p) => ({ motorOverlayPreview: p.motor }), { maxCharsPerImage: MOTOR_PREVIEW_MAX_CHARS })
|
|
1517
1574
|
: [{ type: "text", text: JSON.stringify(response, null, 2) }];
|
|
@@ -1537,6 +1594,6 @@ Begin with the first persona: ${personas[0]}
|
|
|
1537
1594
|
}],
|
|
1538
1595
|
};
|
|
1539
1596
|
}
|
|
1540
|
-
});
|
|
1597
|
+
}));
|
|
1541
1598
|
}
|
|
1542
1599
|
//# sourceMappingURL=persona-comparison-tools.js.map
|