agentic-mermaid 0.1.0 → 0.1.1
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/AGENT_NATIVE.md +8 -3
- package/CHANGELOG.md +5 -0
- package/Instructions_for_agents.md +7 -1
- package/README.md +1 -1
- package/TODO.md +53 -31
- package/assets/fonts/FONT-LICENSES.md +9 -5
- package/assets/fonts/Inter-Bold.ttf +0 -0
- package/assets/fonts/Inter-Medium.ttf +0 -0
- package/assets/fonts/Inter-Regular.ttf +0 -0
- package/assets/fonts/Inter-SemiBold.ttf +0 -0
- package/dist/agent.d.ts +59 -5
- package/dist/agent.js +965 -479
- package/dist/agentic-mermaid-mcp.js +976 -485
- package/dist/am.js +8516 -4326
- package/dist/{index-x2Cj1dVv.d.ts → index-wjRjbsP-.d.ts} +13 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +634 -456
- package/docs/agent-api-cookbook.md +2 -2
- package/docs/api.md +13 -5
- package/docs/comparison.md +1 -1
- package/docs/contributing/releasing.md +16 -5
- package/docs/design/families/journey-experience-curve-implementation.png +0 -0
- package/docs/design/families/journey-migration-parity.md +11 -5
- package/docs/design/families/journey-section-overlap-after.png +0 -0
- package/docs/design/families/journey-section-overlap-before.png +0 -0
- package/docs/design/families/journey.md +33 -5
- package/docs/features.md +3 -3
- package/docs/fork-differences.md +1 -1
- package/docs/layout-characterization/contact-sheet-families.md +2 -0
- package/docs/layout-characterization/visual-quality.md +12 -12
- package/docs/layout-characterization/visual-snapshots/journey.svg +32 -52
- package/docs/quality.md +11 -8
- package/llms.txt +2 -2
- package/package.json +3 -1
- package/server.json +41 -0
- package/src/agent/describe.ts +5 -1
- package/src/agent/facts.ts +23 -0
- package/src/agent/families-builtin.ts +49 -63
- package/src/agent/family-layouts.ts +7 -1
- package/src/agent/font-coverage.ts +288 -0
- package/src/agent/journey-body.ts +111 -151
- package/src/agent/mutation-ops.ts +1 -1
- package/src/agent/op-schema.ts +6 -2
- package/src/agent/png.ts +61 -7
- package/src/agent/types.ts +19 -3
- package/src/agent/verify.ts +34 -2
- package/src/ascii/journey.ts +48 -68
- package/src/ascii/timeline.ts +21 -8
- package/src/ascii/wrap.ts +131 -0
- package/src/cli/agent-instructions.ts +1 -1
- package/src/cli/am-bin.ts +2 -2
- package/src/cli/index.ts +24 -9
- package/src/cli/run-entrypoint.ts +10 -0
- package/src/family-rubric.ts +425 -0
- package/src/font-manifest.ts +4 -0
- package/src/journey/layout.ts +95 -61
- package/src/journey/parse-core.ts +251 -0
- package/src/journey/parser.ts +35 -152
- package/src/journey/renderer.ts +126 -20
- package/src/mcp/sdk-decl.ts +7 -3
- package/src/mcp/tool-surface.ts +1 -1
- package/src/render-family-hooks.ts +1 -1
- package/src/shared/color-math.ts +21 -0
- package/src/theme.ts +86 -15
- package/src/types.ts +8 -0
- package/src/xychart/colors.ts +2 -2
package/dist/agent.js
CHANGED
|
@@ -67,7 +67,8 @@ var WARNING_SEVERITY = {
|
|
|
67
67
|
DECISION_BRANCH_UNLABELED: "warning",
|
|
68
68
|
COMMENT_DROPPED: "warning",
|
|
69
69
|
UNSUPPORTED_SYNTAX: "warning",
|
|
70
|
-
CONTENT_DROPPED_ON_ROUNDTRIP: "warning"
|
|
70
|
+
CONTENT_DROPPED_ON_ROUNDTRIP: "warning",
|
|
71
|
+
INEFFECTIVE_CONFIG: "warning"
|
|
71
72
|
};
|
|
72
73
|
var WARNING_TIER = {
|
|
73
74
|
EMPTY_DIAGRAM: "structural",
|
|
@@ -91,7 +92,8 @@ var WARNING_TIER = {
|
|
|
91
92
|
DECISION_BRANCH_UNLABELED: "lint",
|
|
92
93
|
COMMENT_DROPPED: "lint",
|
|
93
94
|
UNSUPPORTED_SYNTAX: "lint",
|
|
94
|
-
CONTENT_DROPPED_ON_ROUNDTRIP: "lint"
|
|
95
|
+
CONTENT_DROPPED_ON_ROUNDTRIP: "lint",
|
|
96
|
+
INEFFECTIVE_CONFIG: "lint"
|
|
95
97
|
};
|
|
96
98
|
var DEFAULT_LABEL_CHAR_CAP = 40;
|
|
97
99
|
|
|
@@ -686,7 +688,7 @@ var MUTATION_OPS_BY_FAMILY = {
|
|
|
686
688
|
timeline: ["set_title", "add_section", "remove_section", "set_section_label", "add_period", "remove_period", "set_period_label", "add_event", "remove_event", "set_event_text"],
|
|
687
689
|
class: ["set_title", "add_class", "remove_class", "rename_class", "add_member", "remove_member", "add_relation", "remove_relation", "add_note", "remove_note"],
|
|
688
690
|
er: ["add_entity", "remove_entity", "rename_entity", "add_attribute", "remove_attribute", "add_relation", "remove_relation"],
|
|
689
|
-
journey: ["set_title", "add_section", "remove_section", "set_section_label", "add_task", "remove_task", "set_task_text", "set_task_score", "set_task_actors", "rename_actor"],
|
|
691
|
+
journey: ["set_title", "add_section", "remove_section", "set_section_label", "add_task", "remove_task", "set_task_text", "set_task_score", "set_task_actors", "rename_actor", "move_task", "move_section", "set_accessibility_title", "set_accessibility_description"],
|
|
690
692
|
architecture: ["add_service", "remove_service", "rename_service", "set_service_label", "set_service_icon", "move_service", "add_group", "remove_group", "add_edge", "remove_edge"],
|
|
691
693
|
xychart: ["set_title", "set_x_axis", "set_y_axis", "add_series", "remove_series", "set_series_values", "set_series_name", "reorder_series"],
|
|
692
694
|
pie: ["set_title", "set_show_data", "add_slice", "remove_slice", "rename_slice", "set_slice_value", "reorder_slice"],
|
|
@@ -1904,33 +1906,164 @@ function mutateTimeline(input, op) {
|
|
|
1904
1906
|
return ok(body);
|
|
1905
1907
|
}
|
|
1906
1908
|
|
|
1907
|
-
// src/
|
|
1908
|
-
var
|
|
1909
|
+
// src/journey/parse-core.ts
|
|
1910
|
+
var JOURNEY_MIN_SCORE = 1;
|
|
1911
|
+
var JOURNEY_MAX_SCORE = 5;
|
|
1912
|
+
var JOURNEY_TITLE_RE = /^title\s+(.+)$/i;
|
|
1913
|
+
var JOURNEY_SECTION_RE = /^section\s+(.+)$/i;
|
|
1914
|
+
var JOURNEY_TASK_RE = /^(.+?)\s*:\s*([0-9]+)\s*(?::\s*(.*))?$/;
|
|
1915
|
+
var TASK_LIKE_RE = /^(.+?)\s*:\s*([^:]+?)(?:\s*:\s*.*)?$/;
|
|
1909
1916
|
var ACC_LINE_RE = (directive) => new RegExp(`^${directive}\\s*:[ \\t]*(.+)$`, "i");
|
|
1910
|
-
var
|
|
1911
|
-
var TASK_RE = /^(.+?)\s*:\s*([0-9]+)\s*(?::\s*(.*))?$/;
|
|
1912
|
-
function normalizeText(value) {
|
|
1913
|
-
return normalizeJourneyLabel(value).split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join("\n");
|
|
1914
|
-
}
|
|
1915
|
-
function normalizeActorText(value) {
|
|
1916
|
-
return normalizeText(value).split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join(" / ");
|
|
1917
|
-
}
|
|
1917
|
+
var ACC_DESCR_BLOCK_START_RE = /^accDescr\s*:?\s*\{\s*(.*)$/i;
|
|
1918
1918
|
function normalizeJourneyLabel(label) {
|
|
1919
1919
|
return label.replace(/<br\s*\/?>/gi, "\n").replace(/\\n/g, "\n").replace(/<\/?(?:sub|sup|small|mark)\s*>/gi, "").replace(/\*\*(.+?)\*\*/g, "<b>$1</b>").replace(/(?<!\*)\*([^\s*](?:[^*]*[^\s*])?)\*(?!\*)/g, "<i>$1</i>").replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
1920
1920
|
}
|
|
1921
|
-
function
|
|
1922
|
-
return value.split(/\r?\n/).map((part) => part.trim()).join("
|
|
1921
|
+
function normalizeJourneyText(value) {
|
|
1922
|
+
return normalizeJourneyLabel(value).split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join("\n");
|
|
1923
|
+
}
|
|
1924
|
+
function normalizeJourneyActor(value) {
|
|
1925
|
+
return normalizeJourneyText(value).split("\n").join(" / ");
|
|
1923
1926
|
}
|
|
1924
1927
|
function isJourneyComment(line2) {
|
|
1925
1928
|
return line2.startsWith("%%") || line2.startsWith("#") || line2.startsWith("%");
|
|
1926
1929
|
}
|
|
1927
|
-
function
|
|
1928
|
-
return Number.isInteger(score) && score >=
|
|
1930
|
+
function isValidJourneyScore(score) {
|
|
1931
|
+
return Number.isInteger(score) && score >= JOURNEY_MIN_SCORE && score <= JOURNEY_MAX_SCORE;
|
|
1932
|
+
}
|
|
1933
|
+
function walkJourneyLines(lines, startIndex, events) {
|
|
1934
|
+
for (let i2 = startIndex; i2 < lines.length; i2++) {
|
|
1935
|
+
const line2 = lines[i2].trim();
|
|
1936
|
+
if (!line2) continue;
|
|
1937
|
+
if (isJourneyComment(line2)) continue;
|
|
1938
|
+
const accDescrStart = line2.match(ACC_DESCR_BLOCK_START_RE);
|
|
1939
|
+
if (accDescrStart) {
|
|
1940
|
+
const collected = collectJourneyAccessibilityBlock(accDescrStart[1] ?? "", lines, i2);
|
|
1941
|
+
if (!collected) {
|
|
1942
|
+
const outcome = events.issue?.({
|
|
1943
|
+
code: "unclosed_accdescr",
|
|
1944
|
+
lineIndex: i2,
|
|
1945
|
+
statement: line2,
|
|
1946
|
+
detail: 'Journey accDescr block is missing a closing "}"'
|
|
1947
|
+
});
|
|
1948
|
+
if (outcome === "stop") return;
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
events.accDescr?.(normalizeJourneyText(collected.text), i2);
|
|
1952
|
+
i2 = collected.nextIndex;
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
for (const statement of splitJourneyStatements(line2)) {
|
|
1956
|
+
const outcome = classifyStatement(statement, i2, events);
|
|
1957
|
+
if (outcome === "stop") return;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
function classifyStatement(statement, lineIndex, events) {
|
|
1962
|
+
const issue = (code, detail) => events.issue?.({ code, lineIndex, statement, detail });
|
|
1963
|
+
const accTitle = matchAccessibilityLine(statement, "accTitle");
|
|
1964
|
+
if (accTitle !== void 0) {
|
|
1965
|
+
events.accTitle?.(accTitle, lineIndex);
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
const accDescr = matchAccessibilityLine(statement, "accDescr");
|
|
1969
|
+
if (accDescr !== void 0) {
|
|
1970
|
+
events.accDescr?.(accDescr, lineIndex);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const titleMatch = statement.match(JOURNEY_TITLE_RE);
|
|
1974
|
+
if (titleMatch) {
|
|
1975
|
+
const text3 = normalizeJourneyText(titleMatch[1]);
|
|
1976
|
+
if (!text3) return issue("empty_title", `Journey title is empty: "${statement}"`);
|
|
1977
|
+
events.title?.(text3, lineIndex);
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const sectionMatch = statement.match(JOURNEY_SECTION_RE);
|
|
1981
|
+
if (sectionMatch) {
|
|
1982
|
+
const label = normalizeJourneyText(sectionMatch[1]);
|
|
1983
|
+
if (!label || label.includes(":")) {
|
|
1984
|
+
return issue("section_colon", `Journey section labels must not contain ':': "${statement}"`);
|
|
1985
|
+
}
|
|
1986
|
+
events.section?.(label, lineIndex);
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const taskMatch = statement.match(JOURNEY_TASK_RE);
|
|
1990
|
+
if (taskMatch) {
|
|
1991
|
+
const text3 = normalizeJourneyText(taskMatch[1]);
|
|
1992
|
+
if (!text3) return issue("empty_task_text", `Journey task text is empty: "${statement}"`);
|
|
1993
|
+
const rawScore = taskMatch[2];
|
|
1994
|
+
const score = Number.parseInt(rawScore, 10);
|
|
1995
|
+
if (!isValidJourneyScore(score)) return issue("invalid_score", invalidScoreDetail(text3, rawScore));
|
|
1996
|
+
const actors = (taskMatch[3] ?? "").split(",").map(normalizeJourneyActor).filter(Boolean);
|
|
1997
|
+
events.task?.(text3, score, actors, lineIndex);
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
const taskLike = statement.match(TASK_LIKE_RE);
|
|
2001
|
+
if (taskLike) {
|
|
2002
|
+
const text3 = normalizeJourneyText(taskLike[1]);
|
|
2003
|
+
const rawScore = taskLike[2].trim();
|
|
2004
|
+
if (text3 && rawScore) return issue("invalid_score", invalidScoreDetail(text3, rawScore));
|
|
2005
|
+
}
|
|
2006
|
+
return issue(
|
|
2007
|
+
"unrecognized_line",
|
|
2008
|
+
`Invalid user journey line: "${statement}". Expected title, section, accessibility metadata, or "Task name: 3: Actor"`
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
function invalidScoreDetail(text3, rawScore) {
|
|
2012
|
+
return `Journey task "${text3}" has invalid score ${rawScore}. Expected an integer from ${JOURNEY_MIN_SCORE} through ${JOURNEY_MAX_SCORE}`;
|
|
2013
|
+
}
|
|
2014
|
+
var HTML_ENTITY_RE = /^&(?:[a-zA-Z][a-zA-Z0-9]{1,31}|#[0-9]{1,7}|#x[0-9a-fA-F]{1,6});$/;
|
|
2015
|
+
function splitJourneyStatements(line2) {
|
|
2016
|
+
if (!line2.includes(";")) return [line2];
|
|
2017
|
+
const parts = [];
|
|
2018
|
+
let start = 0;
|
|
2019
|
+
for (let i2 = 0; i2 < line2.length; i2++) {
|
|
2020
|
+
if (line2[i2] !== ";") continue;
|
|
2021
|
+
const amp = line2.lastIndexOf("&", i2);
|
|
2022
|
+
if (amp >= start && HTML_ENTITY_RE.test(line2.slice(amp, i2 + 1))) continue;
|
|
2023
|
+
parts.push(line2.slice(start, i2));
|
|
2024
|
+
start = i2 + 1;
|
|
2025
|
+
}
|
|
2026
|
+
parts.push(line2.slice(start));
|
|
2027
|
+
return parts.map((part) => part.trim()).filter((part) => part && !isJourneyComment(part));
|
|
2028
|
+
}
|
|
2029
|
+
function matchAccessibilityLine(line2, directive) {
|
|
2030
|
+
const match = line2.match(ACC_LINE_RE(directive));
|
|
2031
|
+
return match ? normalizeJourneyText(match[1]) : void 0;
|
|
2032
|
+
}
|
|
2033
|
+
function collectJourneyAccessibilityBlock(initial, lines, startIndex) {
|
|
2034
|
+
const initialEnd = initial.indexOf("}");
|
|
2035
|
+
if (initialEnd !== -1) {
|
|
2036
|
+
return {
|
|
2037
|
+
text: initial.slice(0, initialEnd).trim(),
|
|
2038
|
+
nextIndex: startIndex
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
const parts = [initial.trim()].filter(Boolean);
|
|
2042
|
+
for (let i2 = startIndex + 1; i2 < lines.length; i2++) {
|
|
2043
|
+
const line2 = lines[i2];
|
|
2044
|
+
const end = line2.indexOf("}");
|
|
2045
|
+
if (end !== -1) {
|
|
2046
|
+
const beforeBrace = line2.slice(0, end).trim();
|
|
2047
|
+
if (beforeBrace) parts.push(beforeBrace);
|
|
2048
|
+
return {
|
|
2049
|
+
text: parts.join("\n"),
|
|
2050
|
+
nextIndex: i2
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
parts.push(line2);
|
|
2054
|
+
}
|
|
2055
|
+
return null;
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/agent/journey-body.ts
|
|
2059
|
+
function formatJourneyInline(value) {
|
|
2060
|
+
return value.split(/\r?\n/).map((part) => part.trim()).join("<br>");
|
|
1929
2061
|
}
|
|
1930
2062
|
function parseJourneyBody(lines) {
|
|
1931
2063
|
const body = { kind: "journey", sections: [] };
|
|
1932
2064
|
let currentSection;
|
|
1933
2065
|
let sIdx = 0, tIdx = 0;
|
|
2066
|
+
let firstIssue;
|
|
1934
2067
|
const implicitSection = () => {
|
|
1935
2068
|
if (!currentSection) {
|
|
1936
2069
|
currentSection = { id: `section-${sIdx++}`, tasks: [] };
|
|
@@ -1938,60 +2071,36 @@ function parseJourneyBody(lines) {
|
|
|
1938
2071
|
}
|
|
1939
2072
|
return currentSection;
|
|
1940
2073
|
};
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
if (accDescrStart) {
|
|
1953
|
-
const initial = accDescrStart[1] ?? "";
|
|
1954
|
-
const parsed = collectAccessibilityBlock(initial, lines, i2);
|
|
1955
|
-
if (!parsed) return null;
|
|
1956
|
-
body.accessibilityDescription = normalizeAccessibilityText(parsed.text);
|
|
1957
|
-
i2 = parsed.nextIndex;
|
|
1958
|
-
continue;
|
|
1959
|
-
}
|
|
1960
|
-
const accDescr = parseAccessibilityLine(line2, "accDescr");
|
|
1961
|
-
if (accDescr !== void 0) {
|
|
1962
|
-
body.accessibilityDescription = accDescr;
|
|
1963
|
-
continue;
|
|
1964
|
-
}
|
|
1965
|
-
const tm = line2.match(TITLE_RE2);
|
|
1966
|
-
if (tm) {
|
|
1967
|
-
const title = normalizeText(tm[1]);
|
|
1968
|
-
if (!title) return null;
|
|
1969
|
-
body.title = title;
|
|
1970
|
-
continue;
|
|
1971
|
-
}
|
|
1972
|
-
const sm = line2.match(SECTION_RE);
|
|
1973
|
-
if (sm) {
|
|
1974
|
-
const label = normalizeText(sm[1]);
|
|
1975
|
-
if (!label || label.includes(":")) return null;
|
|
2074
|
+
walkJourneyLines(lines, 0, {
|
|
2075
|
+
title: (text3) => {
|
|
2076
|
+
body.title = text3;
|
|
2077
|
+
},
|
|
2078
|
+
accTitle: (text3) => {
|
|
2079
|
+
body.accessibilityTitle = text3;
|
|
2080
|
+
},
|
|
2081
|
+
accDescr: (text3) => {
|
|
2082
|
+
body.accessibilityDescription = text3;
|
|
2083
|
+
},
|
|
2084
|
+
section: (label) => {
|
|
1976
2085
|
currentSection = { id: `section-${sIdx++}`, label, tasks: [] };
|
|
1977
2086
|
body.sections.push(currentSection);
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const km = line2.match(TASK_RE);
|
|
1981
|
-
if (km) {
|
|
1982
|
-
const text3 = normalizeText(km[1]);
|
|
1983
|
-
const score = Number.parseInt(km[2], 10);
|
|
1984
|
-
if (!text3 || !validScore(score)) return null;
|
|
1985
|
-
const actors = (km[3] ?? "").split(",").map(normalizeActorText).filter(Boolean);
|
|
2087
|
+
},
|
|
2088
|
+
task: (text3, score, actors) => {
|
|
1986
2089
|
implicitSection().tasks.push({ id: `task-${tIdx++}`, text: text3, score, actors });
|
|
1987
|
-
|
|
2090
|
+
},
|
|
2091
|
+
issue: (issue) => {
|
|
2092
|
+
firstIssue = issue;
|
|
2093
|
+
return "stop";
|
|
1988
2094
|
}
|
|
1989
|
-
|
|
1990
|
-
}
|
|
2095
|
+
});
|
|
2096
|
+
if (firstIssue) return { ok: false, issue: firstIssue };
|
|
1991
2097
|
if (body.sections.length === 0 && body.sections.every((s2) => s2.tasks.length === 0) && !body.title && !body.accessibilityTitle && !body.accessibilityDescription) {
|
|
1992
|
-
return
|
|
2098
|
+
return {
|
|
2099
|
+
ok: false,
|
|
2100
|
+
issue: { code: "empty_journey", lineIndex: 0, statement: "", detail: "Journey has no title, sections, or scored tasks" }
|
|
2101
|
+
};
|
|
1993
2102
|
}
|
|
1994
|
-
return body;
|
|
2103
|
+
return { ok: true, body };
|
|
1995
2104
|
}
|
|
1996
2105
|
function renderJourney(body) {
|
|
1997
2106
|
const lines = ["journey"];
|
|
@@ -2046,53 +2155,38 @@ function makeIdAllocator(body) {
|
|
|
2046
2155
|
}
|
|
2047
2156
|
function normalizeOpText(value, field, opts = {}) {
|
|
2048
2157
|
if (typeof value !== "string") return err({ code: "INVALID_OP", message: `Journey ${field} must be a string` });
|
|
2049
|
-
const normalized =
|
|
2158
|
+
const normalized = normalizeJourneyText(value);
|
|
2050
2159
|
if (!normalized || !opts.allowColon && normalized.includes(":")) {
|
|
2051
2160
|
return err({ code: "INVALID_OP", message: `Journey ${field} must be non-empty and must not contain :` });
|
|
2052
2161
|
}
|
|
2053
2162
|
return ok(normalized);
|
|
2054
2163
|
}
|
|
2164
|
+
function resolveInsertIndex(index, length) {
|
|
2165
|
+
if (index === void 0) return ok(length);
|
|
2166
|
+
if (!Number.isInteger(index) || index < 0 || index > length) {
|
|
2167
|
+
return err({ code: "INVALID_OP", message: `Journey insert index ${index} out of range (0..${length})` });
|
|
2168
|
+
}
|
|
2169
|
+
return ok(index);
|
|
2170
|
+
}
|
|
2171
|
+
function normalizeAccessibilityOpText(value, field) {
|
|
2172
|
+
if (value === null) return ok(void 0);
|
|
2173
|
+
if (typeof value !== "string") return err({ code: "INVALID_OP", message: `Journey ${field} must be a string or null` });
|
|
2174
|
+
const normalized = normalizeJourneyText(value);
|
|
2175
|
+
if (!normalized || /[;{}]/.test(normalized)) {
|
|
2176
|
+
return err({ code: "INVALID_OP", message: `Journey ${field} must be non-empty and must not contain ;, {, or }` });
|
|
2177
|
+
}
|
|
2178
|
+
return ok(normalized);
|
|
2179
|
+
}
|
|
2055
2180
|
function normalizeActors(actors) {
|
|
2056
2181
|
const out = [];
|
|
2057
2182
|
for (const raw2 of actors ?? []) {
|
|
2058
2183
|
if (typeof raw2 !== "string") return err({ code: "INVALID_OP", message: "Journey actor must be a string" });
|
|
2059
|
-
const normalized =
|
|
2184
|
+
const normalized = normalizeJourneyActor(raw2);
|
|
2060
2185
|
if (!normalized) return err({ code: "INVALID_OP", message: "Journey actor must be non-empty" });
|
|
2061
2186
|
out.push(normalized);
|
|
2062
2187
|
}
|
|
2063
2188
|
return ok(out);
|
|
2064
2189
|
}
|
|
2065
|
-
function normalizeAccessibilityText(value) {
|
|
2066
|
-
return normalizeJourneyLabel(value).split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join("\n");
|
|
2067
|
-
}
|
|
2068
|
-
function parseAccessibilityLine(line2, directive) {
|
|
2069
|
-
const match = line2.match(ACC_LINE_RE(directive));
|
|
2070
|
-
return match ? normalizeAccessibilityText(match[1]) : void 0;
|
|
2071
|
-
}
|
|
2072
|
-
function collectAccessibilityBlock(initial, lines, startIndex) {
|
|
2073
|
-
const initialEnd = initial.indexOf("}");
|
|
2074
|
-
if (initialEnd !== -1) {
|
|
2075
|
-
return {
|
|
2076
|
-
text: initial.slice(0, initialEnd).trim(),
|
|
2077
|
-
nextIndex: startIndex
|
|
2078
|
-
};
|
|
2079
|
-
}
|
|
2080
|
-
const parts = [initial.trim()].filter(Boolean);
|
|
2081
|
-
for (let i2 = startIndex + 1; i2 < lines.length; i2++) {
|
|
2082
|
-
const line2 = lines[i2];
|
|
2083
|
-
const end = line2.indexOf("}");
|
|
2084
|
-
if (end !== -1) {
|
|
2085
|
-
const beforeBrace = line2.slice(0, end).trim();
|
|
2086
|
-
if (beforeBrace) parts.push(beforeBrace);
|
|
2087
|
-
return {
|
|
2088
|
-
text: parts.join("\n"),
|
|
2089
|
-
nextIndex: i2
|
|
2090
|
-
};
|
|
2091
|
-
}
|
|
2092
|
-
parts.push(line2);
|
|
2093
|
-
}
|
|
2094
|
-
return null;
|
|
2095
|
-
}
|
|
2096
2190
|
function mutateJourney(body, op) {
|
|
2097
2191
|
const next = cloneJourney(body);
|
|
2098
2192
|
const nextId = makeIdAllocator(next);
|
|
@@ -2111,7 +2205,9 @@ function mutateJourney(body, op) {
|
|
|
2111
2205
|
case "add_section": {
|
|
2112
2206
|
const label = normalizeOpText(op.label, "section label");
|
|
2113
2207
|
if (!label.ok) return label;
|
|
2114
|
-
|
|
2208
|
+
const index = resolveInsertIndex(op.index, next.sections.length);
|
|
2209
|
+
if (!index.ok) return index;
|
|
2210
|
+
next.sections.splice(index.value, 0, { id: nextId("section"), label: label.value, tasks: [] });
|
|
2115
2211
|
break;
|
|
2116
2212
|
}
|
|
2117
2213
|
case "remove_section": {
|
|
@@ -2132,10 +2228,12 @@ function mutateJourney(body, op) {
|
|
|
2132
2228
|
if (!s2) return err({ code: "SECTION_NOT_FOUND", message: `No section at index ${op.sectionIndex}` });
|
|
2133
2229
|
const text3 = normalizeOpText(op.text, "task text");
|
|
2134
2230
|
if (!text3.ok) return text3;
|
|
2135
|
-
if (!
|
|
2231
|
+
if (!isValidJourneyScore(op.score)) return err({ code: "INVALID_OP", message: `Journey score must be an integer 1..5, got ${op.score}` });
|
|
2136
2232
|
const actors = normalizeActors(op.actors);
|
|
2137
2233
|
if (!actors.ok) return actors;
|
|
2138
|
-
|
|
2234
|
+
const index = resolveInsertIndex(op.index, s2.tasks.length);
|
|
2235
|
+
if (!index.ok) return index;
|
|
2236
|
+
s2.tasks.splice(index.value, 0, { id: nextId("task"), text: text3.value, score: op.score, actors: actors.value });
|
|
2139
2237
|
break;
|
|
2140
2238
|
}
|
|
2141
2239
|
case "remove_task": {
|
|
@@ -2157,7 +2255,7 @@ function mutateJourney(body, op) {
|
|
|
2157
2255
|
case "set_task_score": {
|
|
2158
2256
|
const t2 = getTask(op.sectionIndex, op.taskIndex);
|
|
2159
2257
|
if (!t2) return err({ code: "TASK_NOT_FOUND", message: `No task at (${op.sectionIndex},${op.taskIndex})` });
|
|
2160
|
-
if (!
|
|
2258
|
+
if (!isValidJourneyScore(op.score)) return err({ code: "INVALID_OP", message: `Journey score must be an integer 1..5, got ${op.score}` });
|
|
2161
2259
|
t2.score = op.score;
|
|
2162
2260
|
break;
|
|
2163
2261
|
}
|
|
@@ -2169,6 +2267,45 @@ function mutateJourney(body, op) {
|
|
|
2169
2267
|
t2.actors = actors.value;
|
|
2170
2268
|
break;
|
|
2171
2269
|
}
|
|
2270
|
+
case "move_task": {
|
|
2271
|
+
const from = getSection(op.fromSection);
|
|
2272
|
+
if (!from) return err({ code: "SECTION_NOT_FOUND", message: `No section at index ${op.fromSection}` });
|
|
2273
|
+
if (!from.tasks[op.fromIndex]) return err({ code: "TASK_NOT_FOUND", message: `No task at index ${op.fromIndex}` });
|
|
2274
|
+
const to = getSection(op.toSection);
|
|
2275
|
+
if (!to) return err({ code: "SECTION_NOT_FOUND", message: `No section at index ${op.toSection}` });
|
|
2276
|
+
const [task] = from.tasks.splice(op.fromIndex, 1);
|
|
2277
|
+
if (!Number.isInteger(op.toIndex) || op.toIndex < 0 || op.toIndex > to.tasks.length) {
|
|
2278
|
+
return err({ code: "TASK_NOT_FOUND", message: `No insert position ${op.toIndex} in section ${op.toSection} (0..${to.tasks.length})` });
|
|
2279
|
+
}
|
|
2280
|
+
to.tasks.splice(op.toIndex, 0, task);
|
|
2281
|
+
if (from !== to && from.label === void 0 && from.tasks.length === 0) {
|
|
2282
|
+
next.sections.splice(next.sections.indexOf(from), 1);
|
|
2283
|
+
}
|
|
2284
|
+
break;
|
|
2285
|
+
}
|
|
2286
|
+
case "move_section": {
|
|
2287
|
+
if (!getSection(op.from)) return err({ code: "SECTION_NOT_FOUND", message: `No section at index ${op.from}` });
|
|
2288
|
+
if (!Number.isInteger(op.to) || op.to < 0 || op.to >= next.sections.length) {
|
|
2289
|
+
return err({ code: "SECTION_NOT_FOUND", message: `No section position ${op.to} (0..${next.sections.length - 1})` });
|
|
2290
|
+
}
|
|
2291
|
+
const [section] = next.sections.splice(op.from, 1);
|
|
2292
|
+
next.sections.splice(op.to, 0, section);
|
|
2293
|
+
break;
|
|
2294
|
+
}
|
|
2295
|
+
case "set_accessibility_title": {
|
|
2296
|
+
const title = normalizeAccessibilityOpText(op.title, "accessibility title");
|
|
2297
|
+
if (!title.ok) return title;
|
|
2298
|
+
if (title.value === void 0) delete next.accessibilityTitle;
|
|
2299
|
+
else next.accessibilityTitle = title.value;
|
|
2300
|
+
break;
|
|
2301
|
+
}
|
|
2302
|
+
case "set_accessibility_description": {
|
|
2303
|
+
const description = normalizeAccessibilityOpText(op.description, "accessibility description");
|
|
2304
|
+
if (!description.ok) return description;
|
|
2305
|
+
if (description.value === void 0) delete next.accessibilityDescription;
|
|
2306
|
+
else next.accessibilityDescription = description.value;
|
|
2307
|
+
break;
|
|
2308
|
+
}
|
|
2172
2309
|
case "rename_actor": {
|
|
2173
2310
|
const from = normalizeOpText(op.from, "actor", { allowColon: true });
|
|
2174
2311
|
if (!from.ok) return from;
|
|
@@ -2234,7 +2371,7 @@ var JUNCTION_RE = new RegExp(`^junction\\s+(${IDENT})(?:\\s+in\\s+(${IDENT}))?\\
|
|
|
2234
2371
|
var SOURCE_RE = new RegExp(`^(${IDENT}):(L|R|T|B)$`);
|
|
2235
2372
|
var TARGET_RE = new RegExp(`^(L|R|T|B):(${IDENT})$`);
|
|
2236
2373
|
var SIDES = /* @__PURE__ */ new Set(["L", "R", "T", "B"]);
|
|
2237
|
-
function
|
|
2374
|
+
function normalizeText(value) {
|
|
2238
2375
|
return value.split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join(" ");
|
|
2239
2376
|
}
|
|
2240
2377
|
function parseArrow(token) {
|
|
@@ -2245,7 +2382,7 @@ function parseArrow(token) {
|
|
|
2245
2382
|
if (t2 === "--") return { hasArrowStart: false, hasArrowEnd: false };
|
|
2246
2383
|
const m2 = t2.match(/^(<)?-\[(.*)\]-(>)?$/);
|
|
2247
2384
|
if (!m2) return null;
|
|
2248
|
-
const label =
|
|
2385
|
+
const label = normalizeText(m2[2] ?? "") || void 0;
|
|
2249
2386
|
if (label && (label.includes("[") || label.includes("]"))) return null;
|
|
2250
2387
|
return { label, hasArrowStart: Boolean(m2[1]), hasArrowEnd: Boolean(m2[3]) };
|
|
2251
2388
|
}
|
|
@@ -2285,7 +2422,7 @@ function parseArchitectureBody(lines) {
|
|
|
2285
2422
|
ids.add(id);
|
|
2286
2423
|
groupIds.add(id);
|
|
2287
2424
|
const icon = gm[2]?.trim() || void 0;
|
|
2288
|
-
const label =
|
|
2425
|
+
const label = normalizeText(gm[3] ?? id);
|
|
2289
2426
|
const parentId = gm[4] ?? void 0;
|
|
2290
2427
|
if (parentId) pendingParents.push({ parent: parentId });
|
|
2291
2428
|
groups.push({ id, label, icon, parentId });
|
|
@@ -2298,7 +2435,7 @@ function parseArchitectureBody(lines) {
|
|
|
2298
2435
|
ids.add(id);
|
|
2299
2436
|
endpointIds.add(id);
|
|
2300
2437
|
const icon = sm[2]?.trim() || void 0;
|
|
2301
|
-
const label =
|
|
2438
|
+
const label = normalizeText(sm[3] ?? id);
|
|
2302
2439
|
const parentId = sm[4] ?? void 0;
|
|
2303
2440
|
if (parentId) pendingParents.push({ parent: parentId });
|
|
2304
2441
|
services.push({ id, label, icon, parentId });
|
|
@@ -2389,7 +2526,7 @@ function validId(value, field) {
|
|
|
2389
2526
|
}
|
|
2390
2527
|
function normalizeLabel(value, field) {
|
|
2391
2528
|
if (typeof value !== "string") return err({ code: "INVALID_OP", message: `Architecture ${field} must be a string` });
|
|
2392
|
-
const normalized =
|
|
2529
|
+
const normalized = normalizeText(value);
|
|
2393
2530
|
if (!normalized || normalized.includes("[") || normalized.includes("]")) {
|
|
2394
2531
|
return err({ code: "INVALID_OP", message: `Architecture ${field} must be non-empty and must not contain [ or ]` });
|
|
2395
2532
|
}
|
|
@@ -2603,7 +2740,7 @@ function isFiniteNumber(n2) {
|
|
|
2603
2740
|
var NUMBER = String.raw`[+-]?(?:\d+(?:\.\d+)?|\.\d+)`;
|
|
2604
2741
|
var RANGE_RE = new RegExp(`^(${NUMBER})\\s*-->\\s*(${NUMBER})$`);
|
|
2605
2742
|
var BARE_TEXT_RE = /^[^"'\[\];]+$/;
|
|
2606
|
-
function
|
|
2743
|
+
function normalizeText2(value) {
|
|
2607
2744
|
return value.split(/\r?\n/).map((part) => part.trim()).filter(Boolean).join(" ");
|
|
2608
2745
|
}
|
|
2609
2746
|
function isBareText(value) {
|
|
@@ -2705,7 +2842,7 @@ function parseXyChartBody(lines) {
|
|
|
2705
2842
|
if (line2.startsWith("%%")) continue;
|
|
2706
2843
|
const titleMatch = line2.match(/^title\s+(.+)$/);
|
|
2707
2844
|
if (titleMatch) {
|
|
2708
|
-
const title = bareToken(
|
|
2845
|
+
const title = bareToken(normalizeText2(titleMatch[1]));
|
|
2709
2846
|
if (title === null) return null;
|
|
2710
2847
|
body.title = title;
|
|
2711
2848
|
continue;
|
|
@@ -2783,7 +2920,7 @@ function makeSeriesIdAllocator(body) {
|
|
|
2783
2920
|
}
|
|
2784
2921
|
function validBareText(value, field) {
|
|
2785
2922
|
if (typeof value !== "string") return err({ code: "INVALID_OP", message: `XY chart ${field} must be a string` });
|
|
2786
|
-
const normalized =
|
|
2923
|
+
const normalized = normalizeText2(value);
|
|
2787
2924
|
if (!isBareText(normalized)) {
|
|
2788
2925
|
return err({ code: "INVALID_OP", message: `XY chart ${field} must be non-empty bare text (no quotes, brackets, ; or -->)` });
|
|
2789
2926
|
}
|
|
@@ -3142,7 +3279,7 @@ function formatNumber3(n2) {
|
|
|
3142
3279
|
function isCoord(n2) {
|
|
3143
3280
|
return typeof n2 === "number" && Number.isFinite(n2) && n2 >= 0 && n2 <= 1;
|
|
3144
3281
|
}
|
|
3145
|
-
var
|
|
3282
|
+
var TITLE_RE2 = /^title\s+(.+)$/i;
|
|
3146
3283
|
var AXIS_RE = /^([xy])-axis\s+(.+)$/i;
|
|
3147
3284
|
var QUADRANT_RE = /^quadrant-([1-4])\s+(.+)$/i;
|
|
3148
3285
|
var POINT_RE = /^(.+?)\s*:\s*\[\s*([^,\]]+)\s*,\s*([^,\]]+)\s*\]\s*$/;
|
|
@@ -3177,7 +3314,7 @@ function parseQuadrantBody(lines) {
|
|
|
3177
3314
|
if (line2.startsWith("%%")) continue;
|
|
3178
3315
|
if (/^classDef\b/i.test(line2) || line2.includes(":::")) return null;
|
|
3179
3316
|
let m2;
|
|
3180
|
-
if (m2 = line2.match(
|
|
3317
|
+
if (m2 = line2.match(TITLE_RE2)) {
|
|
3181
3318
|
const title = m2[1].trim();
|
|
3182
3319
|
if (!title) return null;
|
|
3183
3320
|
body.title = title;
|
|
@@ -4840,8 +4977,8 @@ function applyGanttFrontmatterConfig(model, frontmatter) {
|
|
|
4840
4977
|
// src/agent/gantt-body.ts
|
|
4841
4978
|
var DIRECTIVE_LINE_RE = /^(dateFormat|axisFormat|tickInterval|inclusiveEndDates|topAxis|excludes|includes|todayMarker|weekday|weekend|click|accTitle|accDescr)\b/i;
|
|
4842
4979
|
var ACC_DESCR_BLOCK_OPEN_RE = /^accDescr\s*\{\s*$/i;
|
|
4843
|
-
var
|
|
4844
|
-
var
|
|
4980
|
+
var TITLE_RE3 = /^title\s+(.+)$/i;
|
|
4981
|
+
var SECTION_RE = /^section\s+(.+)$/i;
|
|
4845
4982
|
var TASK_ID_RE = /^[\w-]+$/;
|
|
4846
4983
|
function parseGanttBody(trimmedLines, rawLines) {
|
|
4847
4984
|
const raw2 = rawLines ?? trimmedLines;
|
|
@@ -4890,13 +5027,13 @@ function parseGanttBody(trimmedLines, rawLines) {
|
|
|
4890
5027
|
continue;
|
|
4891
5028
|
}
|
|
4892
5029
|
let m2;
|
|
4893
|
-
if (m2 = line2.match(
|
|
5030
|
+
if (m2 = line2.match(TITLE_RE3)) {
|
|
4894
5031
|
body.title = m2[1].trim();
|
|
4895
5032
|
statements.push({ kind: "title" });
|
|
4896
5033
|
i2++;
|
|
4897
5034
|
continue;
|
|
4898
5035
|
}
|
|
4899
|
-
if (m2 = line2.match(
|
|
5036
|
+
if (m2 = line2.match(SECTION_RE)) {
|
|
4900
5037
|
body.sections.push({ id: `section-${sIdx++}`, label: m2[1].trim(), tasks: [] });
|
|
4901
5038
|
currentSection = body.sections.length - 1;
|
|
4902
5039
|
statements.push({ kind: "section", ref: currentSection });
|
|
@@ -5028,7 +5165,7 @@ function validateTask(task) {
|
|
|
5028
5165
|
const colon = line2.indexOf(":");
|
|
5029
5166
|
const label = colon > 0 ? line2.slice(0, colon).trim() : "";
|
|
5030
5167
|
const meta = colon > 0 ? parseGanttTaskMeta(line2.slice(colon + 1).trim()) : null;
|
|
5031
|
-
const reparsesAsDirective =
|
|
5168
|
+
const reparsesAsDirective = TITLE_RE3.test(line2) || SECTION_RE.test(line2) || DIRECTIVE_LINE_RE.test(line2);
|
|
5032
5169
|
if (!meta || label !== task.label || reparsesAsDirective) {
|
|
5033
5170
|
return { code: "INVALID_OP", message: `Gantt task does not round-trip through its canonical line ("${line2}")` };
|
|
5034
5171
|
}
|
|
@@ -5043,7 +5180,7 @@ function validateSectionLabel(label) {
|
|
|
5043
5180
|
return { code: "INVALID_OP", message: 'Gantt section label must be non-empty and must not contain ":" or newlines' };
|
|
5044
5181
|
}
|
|
5045
5182
|
const line2 = `section ${label.trim()}`;
|
|
5046
|
-
if (!
|
|
5183
|
+
if (!SECTION_RE.test(line2)) return { code: "INVALID_OP", message: "Gantt section label does not round-trip" };
|
|
5047
5184
|
return null;
|
|
5048
5185
|
}
|
|
5049
5186
|
function allTaskIds(body) {
|
|
@@ -6182,78 +6319,54 @@ registerFamily({
|
|
|
6182
6319
|
function extractJourneyLabels(source) {
|
|
6183
6320
|
const out = [];
|
|
6184
6321
|
const lines = source.split(/\r?\n/);
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
if (m2 = raw2.match(/^accDescr\s*:?\s*\{\s*(.*)$/i)) {
|
|
6190
|
-
const first = m2[1];
|
|
6191
|
-
const end = first.indexOf("}");
|
|
6192
|
-
if (end >= 0) {
|
|
6193
|
-
const text3 = first.slice(0, end).trim();
|
|
6194
|
-
if (text3) out.push({ text: text3, target: `line${i2 + 1}` });
|
|
6195
|
-
} else {
|
|
6196
|
-
const text3 = first.trim();
|
|
6197
|
-
if (text3) out.push({ text: text3, target: `line${i2 + 1}` });
|
|
6198
|
-
for (i2 += 1; i2 < lines.length; i2++) {
|
|
6199
|
-
const line2 = lines[i2].trim();
|
|
6200
|
-
const close = line2.indexOf("}");
|
|
6201
|
-
const text4 = (close >= 0 ? line2.slice(0, close) : line2).trim();
|
|
6202
|
-
if (text4) out.push({ text: text4, target: `line${i2 + 1}` });
|
|
6203
|
-
if (close >= 0) break;
|
|
6204
|
-
}
|
|
6205
|
-
}
|
|
6206
|
-
} else if (m2 = raw2.match(/^accTitle\s*:?\s*(.+)$/i)) {
|
|
6207
|
-
out.push({ text: m2[1].trim(), target: `line${i2 + 1}` });
|
|
6208
|
-
} else if (m2 = raw2.match(/^accDescr\s*:?\s*(.+)$/i)) {
|
|
6209
|
-
out.push({ text: m2[1].replace(/^\{/, "").replace(/\}$/, "").trim(), target: `line${i2 + 1}` });
|
|
6210
|
-
} else if (m2 = raw2.match(/^title\s+(.+)$/i)) {
|
|
6211
|
-
out.push({ text: m2[1].trim(), target: `line${i2 + 1}` });
|
|
6212
|
-
} else if (m2 = raw2.match(/^section\s+(.+)$/i)) {
|
|
6213
|
-
out.push({ text: m2[1].trim(), target: `line${i2 + 1}` });
|
|
6214
|
-
} else if (m2 = raw2.match(/^(.+?):\s*\d+(?:\s*:\s*(.*))?$/)) {
|
|
6215
|
-
out.push({ text: m2[1].trim(), target: `line${i2 + 1}` });
|
|
6216
|
-
for (const actor of (m2[2] ?? "").split(",")) {
|
|
6217
|
-
const text3 = actor.trim();
|
|
6218
|
-
if (text3) out.push({ text: text3, target: `line${i2 + 1}` });
|
|
6219
|
-
}
|
|
6322
|
+
const headerIndex = lines.findIndex((line2) => /^\s*journey\b/i.test(line2));
|
|
6323
|
+
const push = (text3, lineIndex) => {
|
|
6324
|
+
for (const part of text3.split("\n")) {
|
|
6325
|
+
if (part) out.push({ text: part, target: `line${lineIndex + 1}` });
|
|
6220
6326
|
}
|
|
6221
|
-
}
|
|
6327
|
+
};
|
|
6328
|
+
walkJourneyLines(lines, headerIndex + 1, {
|
|
6329
|
+
title: push,
|
|
6330
|
+
accTitle: push,
|
|
6331
|
+
accDescr: push,
|
|
6332
|
+
section: push,
|
|
6333
|
+
task: (text3, _score, actors, lineIndex) => {
|
|
6334
|
+
push(text3, lineIndex);
|
|
6335
|
+
for (const actor of actors) push(actor, lineIndex);
|
|
6336
|
+
}
|
|
6337
|
+
});
|
|
6222
6338
|
return out;
|
|
6223
6339
|
}
|
|
6224
6340
|
function verifyOpaqueJourney(body) {
|
|
6225
6341
|
if (body.kind !== "opaque" || body.family !== "journey") return [];
|
|
6226
6342
|
const warnings = [];
|
|
6227
6343
|
const lines = body.source.split(/\r?\n/);
|
|
6228
|
-
|
|
6229
|
-
|
|
6230
|
-
|
|
6344
|
+
const headerIndex = lines.findIndex((line2) => /^\s*journey\b/i.test(line2));
|
|
6345
|
+
let blocking;
|
|
6346
|
+
walkJourneyLines(lines, headerIndex + 1, {
|
|
6347
|
+
issue: (issue) => {
|
|
6348
|
+
if (issue.code === "invalid_score") {
|
|
6349
|
+
warnings.push({
|
|
6350
|
+
code: "UNSUPPORTED_SYNTAX",
|
|
6351
|
+
line: issue.lineIndex + 1,
|
|
6352
|
+
syntax: "journey_invalid_score",
|
|
6353
|
+
message: `${issue.detail}.`
|
|
6354
|
+
});
|
|
6355
|
+
return;
|
|
6356
|
+
}
|
|
6357
|
+
blocking ??= issue;
|
|
6358
|
+
}
|
|
6359
|
+
});
|
|
6360
|
+
if (blocking) {
|
|
6231
6361
|
warnings.push({
|
|
6232
6362
|
code: "UNSUPPORTED_SYNTAX",
|
|
6233
|
-
line:
|
|
6234
|
-
syntax:
|
|
6235
|
-
message:
|
|
6363
|
+
line: blocking.lineIndex + 1,
|
|
6364
|
+
syntax: `journey_${blocking.code}`,
|
|
6365
|
+
message: `${blocking.detail}. The diagram is preserved verbatim as source; fix this line to unlock typed journey mutation.`
|
|
6236
6366
|
});
|
|
6237
6367
|
}
|
|
6238
6368
|
return warnings;
|
|
6239
6369
|
}
|
|
6240
|
-
function invalidJourneyScoreDiagnostic(raw2) {
|
|
6241
|
-
if (!raw2 || raw2.startsWith("%%")) return null;
|
|
6242
|
-
if (/^(?:journey|title|section)\b/i.test(raw2)) return null;
|
|
6243
|
-
if (/^acc(?:Title|Descr)\b/i.test(raw2)) return null;
|
|
6244
|
-
const numeric = raw2.match(/^(.+?)\s*:\s*([0-9]+)\s*(?::\s*(.*))?$/);
|
|
6245
|
-
if (numeric) {
|
|
6246
|
-
const score = Number.parseInt(numeric[2], 10);
|
|
6247
|
-
if (Number.isInteger(score) && score >= 1 && score <= 5) return null;
|
|
6248
|
-
return { text: numeric[1].trim(), rawScore: numeric[2] };
|
|
6249
|
-
}
|
|
6250
|
-
const taskLike = raw2.match(/^(.+?)\s*:\s*([^:]+?)(?:\s*:\s*.*)?$/);
|
|
6251
|
-
if (!taskLike) return null;
|
|
6252
|
-
const text3 = taskLike[1].trim();
|
|
6253
|
-
const rawScore = taskLike[2].trim();
|
|
6254
|
-
if (!text3 || !rawScore) return null;
|
|
6255
|
-
return { text: text3, rawScore };
|
|
6256
|
-
}
|
|
6257
6370
|
registerFamily({
|
|
6258
6371
|
id: "journey",
|
|
6259
6372
|
detect: (l2) => l2.startsWith("journey"),
|
|
@@ -6263,7 +6376,10 @@ registerFamily({
|
|
|
6263
6376
|
verify: (body, opts) => body.kind === "journey" ? verifyJourney(body, opts) : verifyOpaqueJourney(body),
|
|
6264
6377
|
...structuredFamilyHooks("journey", {
|
|
6265
6378
|
headerOk: (h2) => /^journey\s*$/i.test(h2),
|
|
6266
|
-
parseBody:
|
|
6379
|
+
parseBody: (lines) => {
|
|
6380
|
+
const outcome = parseJourneyBody(lines);
|
|
6381
|
+
return outcome.ok ? outcome.body : null;
|
|
6382
|
+
},
|
|
6267
6383
|
serialize: renderJourney,
|
|
6268
6384
|
mutate: mutateJourney
|
|
6269
6385
|
})
|
|
@@ -7052,15 +7168,19 @@ var ER_SCHEMA = {
|
|
|
7052
7168
|
};
|
|
7053
7169
|
var JOURNEY_SCHEMA = {
|
|
7054
7170
|
set_title: { fields: { title: strOrNull() } },
|
|
7055
|
-
add_section: { fields: { label: str() } },
|
|
7171
|
+
add_section: { fields: { label: str(), index: withNote(num(false), "insert position; omit to append") } },
|
|
7056
7172
|
remove_section: { fields: { index: num() } },
|
|
7057
7173
|
set_section_label: { fields: { index: num(), label: str() } },
|
|
7058
|
-
add_task: { fields: { sectionIndex: num(), text: str(), score: withNote(num(), "integer 1..5"), actors: strArr(false) } },
|
|
7174
|
+
add_task: { fields: { sectionIndex: num(), text: str(), score: withNote(num(), "integer 1..5"), actors: strArr(false), index: withNote(num(false), "insert position; omit to append") } },
|
|
7059
7175
|
remove_task: { fields: { sectionIndex: num(), taskIndex: num() } },
|
|
7060
7176
|
set_task_text: { fields: { sectionIndex: num(), taskIndex: num(), text: str() } },
|
|
7061
7177
|
set_task_score: { fields: { sectionIndex: num(), taskIndex: num(), score: withNote(num(), "integer 1..5") } },
|
|
7062
7178
|
set_task_actors: { fields: { sectionIndex: num(), taskIndex: num(), actors: strArr() } },
|
|
7063
|
-
rename_actor: { fields: { from: str(), to: str() } }
|
|
7179
|
+
rename_actor: { fields: { from: str(), to: str() } },
|
|
7180
|
+
move_task: { fields: { fromSection: num(), fromIndex: num(), toSection: num(), toIndex: withNote(num(), "insert position in the target section, applied after removal") } },
|
|
7181
|
+
move_section: { fields: { from: num(), to: num() } },
|
|
7182
|
+
set_accessibility_title: { fields: { title: strOrNull() } },
|
|
7183
|
+
set_accessibility_description: { fields: { description: strOrNull() } }
|
|
7064
7184
|
};
|
|
7065
7185
|
var ARCHITECTURE_SCHEMA = {
|
|
7066
7186
|
add_service: { fields: { id: str(), label: str(false), icon: strOrNull(false), group: strOrNull(false) } },
|
|
@@ -7503,6 +7623,22 @@ function isSixDigitHex(s2) {
|
|
|
7503
7623
|
function luma255(r4, g2, b2) {
|
|
7504
7624
|
return (r4 * 299 + g2 * 587 + b2 * 114) / 1e3;
|
|
7505
7625
|
}
|
|
7626
|
+
function wcagRelativeLuminance(hex) {
|
|
7627
|
+
const rgb = tryParseHex(hex);
|
|
7628
|
+
if (!rgb) return null;
|
|
7629
|
+
const lin = (channel) => {
|
|
7630
|
+
const c2 = channel / 255;
|
|
7631
|
+
return c2 <= 0.04045 ? c2 / 12.92 : ((c2 + 0.055) / 1.055) ** 2.4;
|
|
7632
|
+
};
|
|
7633
|
+
return 0.2126 * lin(rgb[0]) + 0.7152 * lin(rgb[1]) + 0.0722 * lin(rgb[2]);
|
|
7634
|
+
}
|
|
7635
|
+
function wcagContrastRatio(a2, b2) {
|
|
7636
|
+
const la = wcagRelativeLuminance(a2);
|
|
7637
|
+
const lb = wcagRelativeLuminance(b2);
|
|
7638
|
+
if (la === null || lb === null) return null;
|
|
7639
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la];
|
|
7640
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
7641
|
+
}
|
|
7506
7642
|
|
|
7507
7643
|
// src/theme.ts
|
|
7508
7644
|
var DEFAULTS = {
|
|
@@ -7703,12 +7839,37 @@ function isColorDark(color) {
|
|
|
7703
7839
|
const [r4, g2, b2] = parseHex(color);
|
|
7704
7840
|
return luma255(r4, g2, b2) / 255 < 0.4;
|
|
7705
7841
|
}
|
|
7842
|
+
function isPlainFontFamily(font) {
|
|
7843
|
+
return /^[A-Za-z0-9][A-Za-z0-9 _-]*$/.test(font);
|
|
7844
|
+
}
|
|
7845
|
+
var RASTER_FONT_FALLBACK = "'Inter'";
|
|
7846
|
+
function resolveRasterFontFamily(fallback) {
|
|
7847
|
+
if (!fallback.includes("var(")) return fallback;
|
|
7848
|
+
const inner = /^var\(\s*--[\w-]+\s*,\s*([^()]+)\)$/.exec(fallback)?.[1]?.trim();
|
|
7849
|
+
return inner || RASTER_FONT_FALLBACK;
|
|
7850
|
+
}
|
|
7706
7851
|
function inlineFontVarForRaster(svg2) {
|
|
7707
|
-
|
|
7852
|
+
const marker = "var(--font,";
|
|
7853
|
+
let out = "";
|
|
7854
|
+
let i2 = 0;
|
|
7855
|
+
while (true) {
|
|
7856
|
+
const at = svg2.indexOf(marker, i2);
|
|
7857
|
+
if (at === -1) return out + svg2.slice(i2);
|
|
7858
|
+
out += svg2.slice(i2, at);
|
|
7859
|
+
let depth = 1;
|
|
7860
|
+
let j2 = at + marker.length;
|
|
7861
|
+
while (j2 < svg2.length && depth > 0) {
|
|
7862
|
+
if (svg2[j2] === "(") depth++;
|
|
7863
|
+
else if (svg2[j2] === ")") depth--;
|
|
7864
|
+
j2++;
|
|
7865
|
+
}
|
|
7866
|
+
out += resolveRasterFontFamily(svg2.slice(at + marker.length, j2 - 1).trim());
|
|
7867
|
+
i2 = j2;
|
|
7868
|
+
}
|
|
7708
7869
|
}
|
|
7709
7870
|
function buildStyleBlock(font, hasMonoFont, shadow, embedFontImport = true) {
|
|
7710
7871
|
const fontImports = embedFontImport ? [
|
|
7711
|
-
`@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(font)}:wght@400;500;600;700&display=swap')
|
|
7872
|
+
...isPlainFontFamily(font) ? [`@import url('https://fonts.googleapis.com/css2?family=${encodeURIComponent(font)}:wght@400;500;600;700&display=swap');`] : [],
|
|
7712
7873
|
...hasMonoFont ? [`@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');`] : []
|
|
7713
7874
|
] : [];
|
|
7714
7875
|
const derivedVars = `
|
|
@@ -7726,8 +7887,9 @@ function buildStyleBlock(font, hasMonoFont, shadow, embedFontImport = true) {
|
|
|
7726
7887
|
--_inner-stroke: color-mix(in srgb, var(--fg) ${MIX.innerStroke}%, var(--bg));
|
|
7727
7888
|
--_key-badge: color-mix(in srgb, var(--fg) ${MIX.keyBadge}%, var(--bg));`;
|
|
7728
7889
|
const shadowRules = shadow ? '\n .node, .class-node, .entity, .actor[data-type="participant"], .note, .block, .timeline-event, .journey-task { filter: url(#bm-shadow); }' : "";
|
|
7729
|
-
const
|
|
7730
|
-
const
|
|
7890
|
+
const fontFallback = isPlainFontFamily(font) ? `'${font}'` : font;
|
|
7891
|
+
const fontFamilyDecl = ` text { font-family: var(--font, ${fontFallback}), system-ui, sans-serif; }`;
|
|
7892
|
+
const styleHead = fontImports.length > 0 ? [` ${fontImports.join("\n ")}`, fontFamilyDecl] : [fontFamilyDecl];
|
|
7731
7893
|
return [
|
|
7732
7894
|
"<style>",
|
|
7733
7895
|
...styleHead,
|
|
@@ -7805,6 +7967,11 @@ function inlineResolvedColors(svg2, colors) {
|
|
|
7805
7967
|
vars.set("_key-badge", rc.keyBadge);
|
|
7806
7968
|
let text3 = svg2;
|
|
7807
7969
|
const SKIP_VARS = /* @__PURE__ */ new Set(["font"]);
|
|
7970
|
+
if (colors.font) {
|
|
7971
|
+
for (const m2 of colors.font.matchAll(/var\(\s*--([\w-]+)/g)) {
|
|
7972
|
+
if (!vars.has(m2[1])) SKIP_VARS.add(m2[1]);
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7808
7975
|
for (let pass = 0; pass < 10; pass++) {
|
|
7809
7976
|
const prev = text3;
|
|
7810
7977
|
text3 = text3.replace(/var\(--([\w-]+)\)/g, (match, name) => {
|
|
@@ -15268,7 +15435,7 @@ function indentLines(s2, n2) {
|
|
|
15268
15435
|
${pad}`);
|
|
15269
15436
|
}
|
|
15270
15437
|
function group(fields) {
|
|
15271
|
-
const
|
|
15438
|
+
const join3 = fields.join ?? "\n";
|
|
15272
15439
|
const segments = [fields.open];
|
|
15273
15440
|
for (const child of fields.children) {
|
|
15274
15441
|
segments.push(indentLines(child.node.crisp, child.indent));
|
|
@@ -15276,13 +15443,13 @@ function group(fields) {
|
|
|
15276
15443
|
segments.push(fields.close);
|
|
15277
15444
|
return {
|
|
15278
15445
|
kind: "group",
|
|
15279
|
-
crisp: segments.join(
|
|
15446
|
+
crisp: segments.join(join3),
|
|
15280
15447
|
id: fields.id,
|
|
15281
15448
|
role: fields.role,
|
|
15282
15449
|
open: fields.open,
|
|
15283
15450
|
close: fields.close,
|
|
15284
15451
|
children: fields.children,
|
|
15285
|
-
join:
|
|
15452
|
+
join: join3,
|
|
15286
15453
|
channels: fields.channels
|
|
15287
15454
|
};
|
|
15288
15455
|
}
|
|
@@ -15305,8 +15472,8 @@ function prelude(fields, crisp) {
|
|
|
15305
15472
|
}
|
|
15306
15473
|
|
|
15307
15474
|
// src/scene/backend.ts
|
|
15308
|
-
function composeGroup(open, close,
|
|
15309
|
-
return [open, ...children.map((c2) => indentLines(c2.serialized, c2.indent)), close].join(
|
|
15475
|
+
function composeGroup(open, close, join3, children) {
|
|
15476
|
+
return [open, ...children.map((c2) => indentLines(c2.serialized, c2.indent)), close].join(join3);
|
|
15310
15477
|
}
|
|
15311
15478
|
function pageRectFor(doc, ctx) {
|
|
15312
15479
|
const prelude2 = doc.parts[0];
|
|
@@ -16131,21 +16298,21 @@ function parseSequenceDiagram(lines) {
|
|
|
16131
16298
|
const blockStack = [];
|
|
16132
16299
|
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
16133
16300
|
const line2 = lines[i2];
|
|
16134
|
-
const accTitle =
|
|
16301
|
+
const accTitle = parseAccessibilityLine(line2, "accTitle");
|
|
16135
16302
|
if (accTitle !== void 0) {
|
|
16136
16303
|
diagram.accessibilityTitle = accTitle;
|
|
16137
16304
|
continue;
|
|
16138
16305
|
}
|
|
16139
16306
|
const accDescrStart = line2.match(/^accDescr\s*:?\s*\{\s*(.*)$/i);
|
|
16140
16307
|
if (accDescrStart) {
|
|
16141
|
-
const parsed =
|
|
16308
|
+
const parsed = collectAccessibilityBlock(accDescrStart[1] ?? "", lines, i2);
|
|
16142
16309
|
if (parsed) {
|
|
16143
16310
|
diagram.accessibilityDescription = normalizeBrTags(parsed.text);
|
|
16144
16311
|
i2 = parsed.nextIndex;
|
|
16145
16312
|
}
|
|
16146
16313
|
continue;
|
|
16147
16314
|
}
|
|
16148
|
-
const accDescr =
|
|
16315
|
+
const accDescr = parseAccessibilityLine(line2, "accDescr");
|
|
16149
16316
|
if (accDescr !== void 0) {
|
|
16150
16317
|
diagram.accessibilityDescription = accDescr;
|
|
16151
16318
|
continue;
|
|
@@ -16263,11 +16430,11 @@ function parseSequenceDiagram(lines) {
|
|
|
16263
16430
|
}
|
|
16264
16431
|
return diagram;
|
|
16265
16432
|
}
|
|
16266
|
-
function
|
|
16433
|
+
function parseAccessibilityLine(line2, directive) {
|
|
16267
16434
|
const match = line2.match(new RegExp(`^${directive}\\s*:?[ \\t]+(.+)$`, "i"));
|
|
16268
16435
|
return match ? normalizeBrTags(match[1].trim()) : void 0;
|
|
16269
16436
|
}
|
|
16270
|
-
function
|
|
16437
|
+
function collectAccessibilityBlock(initial, lines, startIndex) {
|
|
16271
16438
|
const initialEnd = initial.indexOf("}");
|
|
16272
16439
|
if (initialEnd !== -1) return { text: initial.slice(0, initialEnd).trim(), nextIndex: startIndex };
|
|
16273
16440
|
const parts = [initial.trim()].filter(Boolean);
|
|
@@ -17136,19 +17303,19 @@ function parseClassDiagram(lines) {
|
|
|
17136
17303
|
let braceDepth = 0;
|
|
17137
17304
|
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
17138
17305
|
const line2 = lines[i2];
|
|
17139
|
-
const accTitle =
|
|
17306
|
+
const accTitle = parseAccessibilityLine2(line2, "accTitle");
|
|
17140
17307
|
if (accTitle !== void 0) {
|
|
17141
17308
|
diagram.accessibilityTitle = accTitle;
|
|
17142
17309
|
continue;
|
|
17143
17310
|
}
|
|
17144
17311
|
const accDescrStart = line2.match(/^accDescr\s*:?\s*\{\s*(.*)$/i);
|
|
17145
17312
|
if (accDescrStart) {
|
|
17146
|
-
const parsed =
|
|
17313
|
+
const parsed = collectAccessibilityBlock2(accDescrStart[1] ?? "", lines, i2);
|
|
17147
17314
|
diagram.accessibilityDescription = normalizeBrTags(parsed.text);
|
|
17148
17315
|
i2 = parsed.nextIndex;
|
|
17149
17316
|
continue;
|
|
17150
17317
|
}
|
|
17151
|
-
const accDescr =
|
|
17318
|
+
const accDescr = parseAccessibilityLine2(line2, "accDescr");
|
|
17152
17319
|
if (accDescr !== void 0) {
|
|
17153
17320
|
diagram.accessibilityDescription = accDescr;
|
|
17154
17321
|
continue;
|
|
@@ -17247,11 +17414,11 @@ function parseClassDiagram(lines) {
|
|
|
17247
17414
|
diagram.classes = [...classMap.values()];
|
|
17248
17415
|
return diagram;
|
|
17249
17416
|
}
|
|
17250
|
-
function
|
|
17417
|
+
function parseAccessibilityLine2(line2, directive) {
|
|
17251
17418
|
const match = line2.match(new RegExp(`^${directive}\\s*:?[ \\t]+(.+)$`, "i"));
|
|
17252
17419
|
return match ? normalizeBrTags(match[1].trim()) : void 0;
|
|
17253
17420
|
}
|
|
17254
|
-
function
|
|
17421
|
+
function collectAccessibilityBlock2(initial, lines, startIndex) {
|
|
17255
17422
|
const initialEnd = initial.indexOf("}");
|
|
17256
17423
|
if (initialEnd !== -1) return { text: initial.slice(0, initialEnd).trim(), nextIndex: startIndex };
|
|
17257
17424
|
const parts = [initial.trim()].filter(Boolean);
|
|
@@ -17999,19 +18166,19 @@ function parseErDiagram(lines) {
|
|
|
17999
18166
|
let currentEntity = null;
|
|
18000
18167
|
for (let i2 = 1; i2 < lines.length; i2++) {
|
|
18001
18168
|
const line2 = lines[i2];
|
|
18002
|
-
const accTitle =
|
|
18169
|
+
const accTitle = parseAccessibilityLine3(line2, "accTitle");
|
|
18003
18170
|
if (accTitle !== void 0) {
|
|
18004
18171
|
diagram.accessibilityTitle = accTitle;
|
|
18005
18172
|
continue;
|
|
18006
18173
|
}
|
|
18007
18174
|
const accDescrStart = line2.match(/^accDescr\s*:?\s*\{\s*(.*)$/i);
|
|
18008
18175
|
if (accDescrStart) {
|
|
18009
|
-
const parsed =
|
|
18176
|
+
const parsed = collectAccessibilityBlock3(accDescrStart[1] ?? "", lines, i2);
|
|
18010
18177
|
diagram.accessibilityDescription = normalizeBrTags(parsed.text);
|
|
18011
18178
|
i2 = parsed.nextIndex;
|
|
18012
18179
|
continue;
|
|
18013
18180
|
}
|
|
18014
|
-
const accDescr =
|
|
18181
|
+
const accDescr = parseAccessibilityLine3(line2, "accDescr");
|
|
18015
18182
|
if (accDescr !== void 0) {
|
|
18016
18183
|
diagram.accessibilityDescription = accDescr;
|
|
18017
18184
|
continue;
|
|
@@ -18045,11 +18212,11 @@ function parseErDiagram(lines) {
|
|
|
18045
18212
|
diagram.entities = [...entityMap.values()];
|
|
18046
18213
|
return diagram;
|
|
18047
18214
|
}
|
|
18048
|
-
function
|
|
18215
|
+
function parseAccessibilityLine3(line2, directive) {
|
|
18049
18216
|
const match = line2.match(new RegExp(`^${directive}\\s*:?[ \\t]+(.+)$`, "i"));
|
|
18050
18217
|
return match ? normalizeBrTags(match[1].trim()) : void 0;
|
|
18051
18218
|
}
|
|
18052
|
-
function
|
|
18219
|
+
function collectAccessibilityBlock3(initial, lines, startIndex) {
|
|
18053
18220
|
const initialEnd = initial.indexOf("}");
|
|
18054
18221
|
if (initialEnd !== -1) return { text: initial.slice(0, initialEnd).trim(), nextIndex: startIndex };
|
|
18055
18222
|
const parts = [initial.trim()].filter(Boolean);
|
|
@@ -19678,15 +19845,6 @@ function readTimelineScale(themeVariables, prefix, index) {
|
|
|
19678
19845
|
}
|
|
19679
19846
|
|
|
19680
19847
|
// src/journey/parser.ts
|
|
19681
|
-
function normalizeActorLabel(label) {
|
|
19682
|
-
return normalizeJourneyLabel2(label.trim()).split("\n").map((part) => part.trim()).filter(Boolean).join(" / ");
|
|
19683
|
-
}
|
|
19684
|
-
function normalizeJourneyLabel2(label) {
|
|
19685
|
-
return label.replace(/<br\s*\/?>/gi, "\n").replace(/\\n/g, "\n").replace(/<\/?(?:sub|sup|small|mark)\s*>/gi, "").replace(/\*\*(.+?)\*\*/g, "<b>$1</b>").replace(/(?<!\*)\*([^\s*](?:[^*]*[^\s*])?)\*(?!\*)/g, "<i>$1</i>").replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
19686
|
-
}
|
|
19687
|
-
function isJourneyComment2(line2) {
|
|
19688
|
-
return line2.startsWith("%%") || line2.startsWith("#") || line2.startsWith("%");
|
|
19689
|
-
}
|
|
19690
19848
|
function parseJourneyDiagram(lines) {
|
|
19691
19849
|
const diagram = { sections: [] };
|
|
19692
19850
|
let currentSection;
|
|
@@ -19701,67 +19859,25 @@ function parseJourneyDiagram(lines) {
|
|
|
19701
19859
|
diagram.sections.push(currentSection);
|
|
19702
19860
|
return currentSection;
|
|
19703
19861
|
};
|
|
19704
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
|
|
19712
|
-
|
|
19713
|
-
|
|
19714
|
-
|
|
19715
|
-
const initial = accDescrStart[1] ?? "";
|
|
19716
|
-
const parsed = collectAccessibilityBlock5(initial, lines, i2);
|
|
19717
|
-
diagram.accessibilityDescription = normalizeJourneyLabel2(parsed.text);
|
|
19718
|
-
i2 = parsed.nextIndex;
|
|
19719
|
-
continue;
|
|
19720
|
-
}
|
|
19721
|
-
const accDescr = parseAccessibilityLine5(line2, "accDescr");
|
|
19722
|
-
if (accDescr !== void 0) {
|
|
19723
|
-
diagram.accessibilityDescription = accDescr;
|
|
19724
|
-
continue;
|
|
19725
|
-
}
|
|
19726
|
-
const titleMatch = line2.match(/^title\s+(.+)$/i);
|
|
19727
|
-
if (titleMatch) {
|
|
19728
|
-
diagram.title = normalizeJourneyLabel2(titleMatch[1].trim());
|
|
19729
|
-
continue;
|
|
19730
|
-
}
|
|
19731
|
-
const sectionMatch = line2.match(/^section\s+(.+)$/i);
|
|
19732
|
-
if (sectionMatch) {
|
|
19733
|
-
const label = normalizeJourneyLabel2(sectionMatch[1].trim());
|
|
19734
|
-
if (label.includes(":")) {
|
|
19735
|
-
throw syntaxError({
|
|
19736
|
-
what: `Invalid user journey section: "${line2}"`,
|
|
19737
|
-
expectedForm: "section Section name",
|
|
19738
|
-
example: "section Go to work"
|
|
19739
|
-
});
|
|
19740
|
-
}
|
|
19862
|
+
walkJourneyLines(lines, 1, {
|
|
19863
|
+
title: (text3) => {
|
|
19864
|
+
diagram.title = text3;
|
|
19865
|
+
},
|
|
19866
|
+
accTitle: (text3) => {
|
|
19867
|
+
diagram.accessibilityTitle = text3;
|
|
19868
|
+
},
|
|
19869
|
+
accDescr: (text3) => {
|
|
19870
|
+
diagram.accessibilityDescription = text3;
|
|
19871
|
+
},
|
|
19872
|
+
section: (label) => {
|
|
19741
19873
|
currentSection = {
|
|
19742
19874
|
id: `section-${sectionIndex++}`,
|
|
19743
19875
|
label,
|
|
19744
19876
|
tasks: []
|
|
19745
19877
|
};
|
|
19746
19878
|
diagram.sections.push(currentSection);
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
const taskMatch = line2.match(/^(.+?)\s*:\s*([0-9]+)\s*(?::\s*(.*))?$/);
|
|
19750
|
-
if (taskMatch) {
|
|
19751
|
-
const text3 = normalizeJourneyLabel2(taskMatch[1].trim());
|
|
19752
|
-
const rawScore = taskMatch[2];
|
|
19753
|
-
const score = Number.parseInt(rawScore, 10);
|
|
19754
|
-
if (!text3) {
|
|
19755
|
-
throw syntaxError({
|
|
19756
|
-
what: `Invalid user journey task: "${line2}"`,
|
|
19757
|
-
expectedForm: "Task name: score: Actor[, Actor\u2026]",
|
|
19758
|
-
example: "Pay: 3: Shopper"
|
|
19759
|
-
});
|
|
19760
|
-
}
|
|
19761
|
-
if (!Number.isInteger(score) || score < 1 || score > 5) {
|
|
19762
|
-
throw invalidJourneyScoreError(text3, rawScore);
|
|
19763
|
-
}
|
|
19764
|
-
const actors = (taskMatch[3] ?? "").split(",").map(normalizeActorLabel).filter(Boolean);
|
|
19879
|
+
},
|
|
19880
|
+
task: (text3, score, actors) => {
|
|
19765
19881
|
const task = {
|
|
19766
19882
|
id: `task-${taskIndex++}`,
|
|
19767
19883
|
text: text3,
|
|
@@ -19769,57 +19885,38 @@ function parseJourneyDiagram(lines) {
|
|
|
19769
19885
|
actors
|
|
19770
19886
|
};
|
|
19771
19887
|
ensureSection().tasks.push(task);
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
if (invalidScore) {
|
|
19776
|
-
throw invalidJourneyScoreError(invalidScore.text, invalidScore.rawScore);
|
|
19888
|
+
},
|
|
19889
|
+
issue: (issue) => {
|
|
19890
|
+
throw journeyIssueError(issue);
|
|
19777
19891
|
}
|
|
19778
|
-
|
|
19779
|
-
}
|
|
19892
|
+
});
|
|
19780
19893
|
if (diagram.sections.length === 0 && !diagram.title && diagram.sections.every((section) => section.tasks.length === 0) && !diagram.accessibilityTitle && !diagram.accessibilityDescription) {
|
|
19781
19894
|
throw new Error("Journey diagram must include at least one scored task, a section, or a title");
|
|
19782
19895
|
}
|
|
19783
19896
|
return diagram;
|
|
19784
19897
|
}
|
|
19785
|
-
function
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
}
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
}
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
};
|
|
19807
|
-
}
|
|
19808
|
-
const parts = [initial.trim()].filter(Boolean);
|
|
19809
|
-
for (let i2 = startIndex + 1; i2 < lines.length; i2++) {
|
|
19810
|
-
const line2 = lines[i2];
|
|
19811
|
-
const end = line2.indexOf("}");
|
|
19812
|
-
if (end !== -1) {
|
|
19813
|
-
const beforeBrace = line2.slice(0, end).trim();
|
|
19814
|
-
if (beforeBrace) parts.push(beforeBrace);
|
|
19815
|
-
return {
|
|
19816
|
-
text: parts.join("\n"),
|
|
19817
|
-
nextIndex: i2
|
|
19818
|
-
};
|
|
19819
|
-
}
|
|
19820
|
-
parts.push(line2);
|
|
19898
|
+
function journeyIssueError(issue) {
|
|
19899
|
+
switch (issue.code) {
|
|
19900
|
+
case "section_colon":
|
|
19901
|
+
return syntaxError({
|
|
19902
|
+
what: `Invalid user journey section: "${issue.statement}"`,
|
|
19903
|
+
expectedForm: "section Section name",
|
|
19904
|
+
example: "section Go to work"
|
|
19905
|
+
});
|
|
19906
|
+
case "empty_task_text":
|
|
19907
|
+
return syntaxError({
|
|
19908
|
+
what: `Invalid user journey task: "${issue.statement}"`,
|
|
19909
|
+
expectedForm: "Task name: score: Actor[, Actor\u2026]",
|
|
19910
|
+
example: "Pay: 3: Shopper"
|
|
19911
|
+
});
|
|
19912
|
+
case "invalid_score":
|
|
19913
|
+
case "unclosed_accdescr":
|
|
19914
|
+
return new Error(issue.detail);
|
|
19915
|
+
case "empty_title":
|
|
19916
|
+
case "unrecognized_line":
|
|
19917
|
+
case "empty_journey":
|
|
19918
|
+
return new Error(`Invalid user journey line: "${issue.statement}". Expected title, section, accessibility metadata, or "Task name: 3: Actor"`);
|
|
19821
19919
|
}
|
|
19822
|
-
throw new Error('Journey accDescr block is missing a closing "}"');
|
|
19823
19920
|
}
|
|
19824
19921
|
|
|
19825
19922
|
// src/journey/layout.ts
|
|
@@ -19835,6 +19932,7 @@ var JY = {
|
|
|
19835
19932
|
sectionHeaderPadX: 16,
|
|
19836
19933
|
sectionTaskGap: 30,
|
|
19837
19934
|
sectionSpanPadX: 10,
|
|
19935
|
+
sectionGap: 8,
|
|
19838
19936
|
legendGap: 26,
|
|
19839
19937
|
legendMinWidth: 96,
|
|
19840
19938
|
legendTitleGap: 26,
|
|
@@ -19855,8 +19953,10 @@ var JY = {
|
|
|
19855
19953
|
taskToGuideGap: 56,
|
|
19856
19954
|
scoreStep: 40,
|
|
19857
19955
|
markerRadius: 16,
|
|
19858
|
-
baselineGap:
|
|
19859
|
-
|
|
19956
|
+
baselineGap: 32,
|
|
19957
|
+
baselineClearance: 6,
|
|
19958
|
+
arrowOverhang: 28,
|
|
19959
|
+
minPlotWidth: 200
|
|
19860
19960
|
};
|
|
19861
19961
|
var JOURNEY_STYLE_DEFAULTS = {
|
|
19862
19962
|
nodeLabelFontSize: JY.taskFontSize,
|
|
@@ -19931,7 +20031,12 @@ function layoutJourneyDiagram(diagram, options = {}) {
|
|
|
19931
20031
|
)
|
|
19932
20032
|
) : 0;
|
|
19933
20033
|
const sectionMetrics = diagram.sections.map((section) => {
|
|
19934
|
-
const label = section.label ?
|
|
20034
|
+
const label = section.label ? wrapLabelToWidth(
|
|
20035
|
+
applyTextTransform(section.label, style.groupTextTransform),
|
|
20036
|
+
visual.maxLabelWidth,
|
|
20037
|
+
style.groupHeaderFontSize,
|
|
20038
|
+
style.groupHeaderFontWeight
|
|
20039
|
+
) : void 0;
|
|
19935
20040
|
const labelWidth = label ? measureMultilineText(label, style.groupHeaderFontSize, style.groupHeaderFontWeight).width : 0;
|
|
19936
20041
|
const tasks = section.tasks.map((task) => measureTask(task.text, task.actors.length, style, visual));
|
|
19937
20042
|
const emptyWidth = Math.max(visual.taskMinWidth, labelWidth + JY.sectionHeaderPadX * 2);
|
|
@@ -19959,57 +20064,53 @@ function layoutJourneyDiagram(diagram, options = {}) {
|
|
|
19959
20064
|
for (let sectionIndex = 0; sectionIndex < diagram.sections.length; sectionIndex++) {
|
|
19960
20065
|
const section = diagram.sections[sectionIndex];
|
|
19961
20066
|
const metric = sectionMetrics[sectionIndex];
|
|
20067
|
+
const framed = !!metric.label;
|
|
20068
|
+
if (sectionIndex > 0) cursorX += JY.sectionGap;
|
|
20069
|
+
const taskSpan = metric.tasks.length > 0 ? metric.tasks.reduce((sum, task) => sum + task.width, 0) + (metric.tasks.length - 1) * visual.taskGap : 0;
|
|
20070
|
+
const spanWidth = framed ? Math.max(
|
|
20071
|
+
taskSpan > 0 ? taskSpan + JY.sectionSpanPadX * 2 : metric.emptyWidth,
|
|
20072
|
+
metric.labelWidth + JY.sectionHeaderPadX * 2
|
|
20073
|
+
) : Math.max(taskSpan, metric.tasks.length === 0 ? metric.emptyWidth : 0);
|
|
20074
|
+
const spanX = cursorX;
|
|
19962
20075
|
const tasks = [];
|
|
19963
|
-
let
|
|
19964
|
-
let
|
|
19965
|
-
|
|
19966
|
-
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
|
|
19980
|
-
|
|
19981
|
-
|
|
19982
|
-
|
|
19983
|
-
|
|
19984
|
-
|
|
19985
|
-
|
|
19986
|
-
x:
|
|
19987
|
-
|
|
19988
|
-
|
|
19989
|
-
|
|
19990
|
-
|
|
19991
|
-
|
|
19992
|
-
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
cx: centerX,
|
|
20000
|
-
cy: markerY,
|
|
20001
|
-
r: JY.markerRadius,
|
|
20002
|
-
score: task.score
|
|
20003
|
-
},
|
|
20004
|
-
actorDots
|
|
20005
|
-
});
|
|
20006
|
-
cursorX += taskMetric.width;
|
|
20007
|
-
sectionEndX = cursorX;
|
|
20008
|
-
}
|
|
20076
|
+
let taskX = spanX + (spanWidth - taskSpan) / 2;
|
|
20077
|
+
for (let taskIndex = 0; taskIndex < section.tasks.length; taskIndex++) {
|
|
20078
|
+
const task = section.tasks[taskIndex];
|
|
20079
|
+
const taskMetric = metric.tasks[taskIndex];
|
|
20080
|
+
if (taskIndex > 0) taskX += visual.taskGap;
|
|
20081
|
+
const x2 = taskX;
|
|
20082
|
+
const centerX = x2 + taskMetric.width / 2;
|
|
20083
|
+
const markerY = scoreToY(task.score, guideTop);
|
|
20084
|
+
const actorDots = positionActorDots(task, actorIndex, centerX, taskY + taskMetric.height - style.nodePaddingY - JY.actorDotRadius);
|
|
20085
|
+
tasks.push({
|
|
20086
|
+
id: task.id,
|
|
20087
|
+
sectionId: section.id,
|
|
20088
|
+
text: taskMetric.text,
|
|
20089
|
+
score: task.score,
|
|
20090
|
+
actors: task.actors,
|
|
20091
|
+
x: x2,
|
|
20092
|
+
y: taskY,
|
|
20093
|
+
width: taskMetric.width,
|
|
20094
|
+
height: taskMetric.height,
|
|
20095
|
+
textX: centerX,
|
|
20096
|
+
textY: taskY + style.nodePaddingY + taskMetric.textHeight / 2,
|
|
20097
|
+
centerX,
|
|
20098
|
+
track: {
|
|
20099
|
+
x: centerX,
|
|
20100
|
+
y1: taskY + taskMetric.height,
|
|
20101
|
+
y2: baselineY
|
|
20102
|
+
},
|
|
20103
|
+
marker: {
|
|
20104
|
+
cx: centerX,
|
|
20105
|
+
cy: markerY,
|
|
20106
|
+
r: JY.markerRadius,
|
|
20107
|
+
score: task.score
|
|
20108
|
+
},
|
|
20109
|
+
actorDots
|
|
20110
|
+
});
|
|
20111
|
+
taskX += taskMetric.width;
|
|
20009
20112
|
}
|
|
20010
|
-
|
|
20011
|
-
const spanX = framed ? sectionStartX - JY.sectionSpanPadX : sectionStartX;
|
|
20012
|
-
const spanWidth = framed ? Math.max(metric.labelWidth + JY.sectionHeaderPadX * 2, sectionEndX - sectionStartX + JY.sectionSpanPadX * 2) : Math.max(0, sectionEndX - sectionStartX);
|
|
20113
|
+
cursorX = spanX + spanWidth;
|
|
20013
20114
|
sections.push({
|
|
20014
20115
|
id: section.id,
|
|
20015
20116
|
label: metric.label,
|
|
@@ -20024,7 +20125,7 @@ function layoutJourneyDiagram(diagram, options = {}) {
|
|
|
20024
20125
|
tasks
|
|
20025
20126
|
});
|
|
20026
20127
|
}
|
|
20027
|
-
const naturalPlotRight = cursorX;
|
|
20128
|
+
const naturalPlotRight = cursorX + JY.arrowOverhang;
|
|
20028
20129
|
const plotRight = Math.max(naturalPlotRight, plotLeft + JY.minPlotWidth);
|
|
20029
20130
|
const maxSectionRight = Math.max(plotRight, ...sections.map((section) => section.x + section.width));
|
|
20030
20131
|
const width = maxSectionRight + visual.paddingX;
|
|
@@ -20043,7 +20144,7 @@ function layoutJourneyDiagram(diagram, options = {}) {
|
|
|
20043
20144
|
});
|
|
20044
20145
|
const legendBottom = actors.length > 0 ? actors[actors.length - 1].y + JY.legendDotRadius : 0;
|
|
20045
20146
|
const height = Math.max(
|
|
20046
|
-
baselineY + JY.
|
|
20147
|
+
baselineY + JY.baselineClearance + visual.paddingY,
|
|
20047
20148
|
legendBottom + visual.paddingY
|
|
20048
20149
|
);
|
|
20049
20150
|
return {
|
|
@@ -20084,7 +20185,12 @@ function layoutJourneyDiagram(diagram, options = {}) {
|
|
|
20084
20185
|
};
|
|
20085
20186
|
}
|
|
20086
20187
|
function measureTask(text3, actorCount, style, visual) {
|
|
20087
|
-
const taskText =
|
|
20188
|
+
const taskText = wrapLabelToWidth(
|
|
20189
|
+
applyTextTransform(text3, style.nodeTextTransform),
|
|
20190
|
+
visual.maxLabelWidth,
|
|
20191
|
+
style.nodeLabelFontSize,
|
|
20192
|
+
style.nodeLabelFontWeight
|
|
20193
|
+
);
|
|
20088
20194
|
const textMetrics = measureMultilineText(taskText, style.nodeLabelFontSize, style.nodeLabelFontWeight);
|
|
20089
20195
|
const actorDotsWidth = actorCount > 0 ? actorCount * JY.actorDotRadius * 2 + (actorCount - 1) * JY.actorDotGap : 0;
|
|
20090
20196
|
const width = Math.max(
|
|
@@ -20147,7 +20253,8 @@ function breakWordToWidth(word, maxWidth, fontSize, fontWeight) {
|
|
|
20147
20253
|
for (const char of [...word]) {
|
|
20148
20254
|
const candidate = current + char;
|
|
20149
20255
|
if (current && measureTextWidth(stripFormattingTags(candidate), fontSize, fontWeight) > maxWidth) {
|
|
20150
|
-
|
|
20256
|
+
const breakIsFullwidth = isFullwidthChar(current[current.length - 1]) && isFullwidthChar(char);
|
|
20257
|
+
lines.push(breakIsFullwidth ? current : `${current}-`);
|
|
20151
20258
|
current = char;
|
|
20152
20259
|
} else {
|
|
20153
20260
|
current = candidate;
|
|
@@ -20156,6 +20263,9 @@ function breakWordToWidth(word, maxWidth, fontSize, fontWeight) {
|
|
|
20156
20263
|
if (current) lines.push(current);
|
|
20157
20264
|
return lines.join("\n");
|
|
20158
20265
|
}
|
|
20266
|
+
function isFullwidthChar(char) {
|
|
20267
|
+
return /[ᄀ-ᅟ⺀-가-힣豈-︰-﹏-⦆¢-₩\u{1F300}-\u{1FAFF}\u{20000}-\u{2FA1F}]/u.test(char);
|
|
20268
|
+
}
|
|
20159
20269
|
function cssFontSizeToPx(value, fallback) {
|
|
20160
20270
|
if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
|
|
20161
20271
|
if (typeof value !== "string") return fallback;
|
|
@@ -20191,9 +20301,9 @@ function positionActorDots(task, actorIndex, centerX, y2) {
|
|
|
20191
20301
|
});
|
|
20192
20302
|
}
|
|
20193
20303
|
function scoreToY(score, guideTop) {
|
|
20194
|
-
return guideTop + (
|
|
20304
|
+
return guideTop + (JOURNEY_MAX_SCORE2 - score) * JY.scoreStep;
|
|
20195
20305
|
}
|
|
20196
|
-
var
|
|
20306
|
+
var JOURNEY_MAX_SCORE2 = 5;
|
|
20197
20307
|
|
|
20198
20308
|
// src/xychart/colors.ts
|
|
20199
20309
|
var CHART_ACCENT_FALLBACK = "#3b82f6";
|
|
@@ -20284,7 +20394,7 @@ var JY2 = {
|
|
|
20284
20394
|
legendLabelGap: 12,
|
|
20285
20395
|
legendTitleGap: 26
|
|
20286
20396
|
};
|
|
20287
|
-
var
|
|
20397
|
+
var JOURNEY_MAX_SCORE3 = 5;
|
|
20288
20398
|
function renderJourneySvg(ctx) {
|
|
20289
20399
|
return DefaultBackend.render(lowerJourneyScene(ctx), { seed: 0 });
|
|
20290
20400
|
}
|
|
@@ -20295,13 +20405,18 @@ function lowerJourneyScene(ctx) {
|
|
|
20295
20405
|
const parts = [];
|
|
20296
20406
|
const style = resolveJourneyStyle(options);
|
|
20297
20407
|
const visual = resolveJourneyVisualConfig(options);
|
|
20298
|
-
const paints = journeyPaints(style, visual, colors
|
|
20408
|
+
const paints = journeyPaints(style, visual, colors, {
|
|
20409
|
+
sectionCount: diagram.sections.length,
|
|
20410
|
+
actorCount: diagram.actors.length
|
|
20411
|
+
});
|
|
20299
20412
|
const accessibility = buildJourneyAccessibility(diagram);
|
|
20300
20413
|
const uid = journeyNamespace(diagram);
|
|
20301
20414
|
const arrowMarkerId = `${uid}-arrowhead`;
|
|
20302
20415
|
const titleId = `${uid}-title`;
|
|
20303
20416
|
const descId = `${uid}-desc`;
|
|
20304
|
-
const
|
|
20417
|
+
const curveMarkers = options.journey?.experienceCurve !== false ? diagram.sections.flatMap((section) => section.tasks.map((task) => task.marker)) : [];
|
|
20418
|
+
const drawCurve = curveMarkers.length >= 2;
|
|
20419
|
+
const journeyCss = journeyStyles(style, visual, paints, drawCurve);
|
|
20305
20420
|
parts.push(prelude(
|
|
20306
20421
|
{
|
|
20307
20422
|
id: "prelude",
|
|
@@ -20313,7 +20428,7 @@ function lowerJourneyScene(ctx) {
|
|
|
20313
20428
|
hasMonoFont: false,
|
|
20314
20429
|
extraCss: journeyCss
|
|
20315
20430
|
},
|
|
20316
|
-
openJourneySvgTag(diagram, colors, transparent, accessibility, titleId, descId)
|
|
20431
|
+
openJourneySvgTag(diagram, colors, transparent, accessibility, titleId, descId, options.mermaidConfig?.journey?.useMaxWidth === true)
|
|
20317
20432
|
));
|
|
20318
20433
|
if (accessibility.title) {
|
|
20319
20434
|
parts.push(raw(
|
|
@@ -20342,6 +20457,9 @@ function lowerJourneyScene(ctx) {
|
|
|
20342
20457
|
parts.push(renderSectionFrame(section, style, paints, index));
|
|
20343
20458
|
}
|
|
20344
20459
|
});
|
|
20460
|
+
if (drawCurve) {
|
|
20461
|
+
parts.push(renderExperienceCurve(curveMarkers, style, paints));
|
|
20462
|
+
}
|
|
20345
20463
|
for (const section of diagram.sections) {
|
|
20346
20464
|
for (const task of section.tasks) {
|
|
20347
20465
|
parts.push(renderTask(task, section.label, style, paints));
|
|
@@ -20397,8 +20515,9 @@ function buildJourneyAccessibility(diagram) {
|
|
|
20397
20515
|
description: diagram.accessibilityDescription
|
|
20398
20516
|
};
|
|
20399
20517
|
}
|
|
20400
|
-
function openJourneySvgTag(diagram, colors, transparent, accessibility, titleId, descId) {
|
|
20518
|
+
function openJourneySvgTag(diagram, colors, transparent, accessibility, titleId, descId, useMaxWidth) {
|
|
20401
20519
|
return svgOpenTag(diagram.width, diagram.height, colors, transparent, {
|
|
20520
|
+
...useMaxWidth ? { width: "100%", height: "100%", style: `max-width:${diagram.width}px` } : {},
|
|
20402
20521
|
attrs: buildAccessibilityAttrs(accessibility.title, accessibility.description, titleId, descId, "user journey")
|
|
20403
20522
|
});
|
|
20404
20523
|
}
|
|
@@ -20413,7 +20532,7 @@ function buildJourneyDefs(colors, paints, arrowMarkerId) {
|
|
|
20413
20532
|
${defs.join("\n")}
|
|
20414
20533
|
</defs>`;
|
|
20415
20534
|
}
|
|
20416
|
-
function journeyStyles(style, visual, paints) {
|
|
20535
|
+
function journeyStyles(style, visual, paints, drawCurve) {
|
|
20417
20536
|
const sectionPalette = sectionPaletteCss(paints);
|
|
20418
20537
|
const sectionBandPalette = sectionBandPaletteCss(paints);
|
|
20419
20538
|
const sectionLabelPalette = sectionLabelPaletteCss(paints);
|
|
@@ -20428,7 +20547,8 @@ ${sectionBandPalette}
|
|
|
20428
20547
|
${sectionLabelPalette}
|
|
20429
20548
|
.journey-task-box { fill: ${paints.nodeFill}; stroke: ${paints.nodeStroke}; stroke-width: ${style.nodeLineWidth}; }
|
|
20430
20549
|
.journey-task-text { fill: ${style.nodeTextColor ?? "var(--_text)"};${visual.taskFontFamily ? ` font-family: ${visual.taskFontFamily};` : ""} }
|
|
20431
|
-
.journey-track { stroke: color-mix(in srgb, ${paints.nodeStroke} 78%, var(--bg)); stroke-width: ${style.lineWidth}; stroke-dasharray: 4 7; }
|
|
20550
|
+
.journey-track { stroke: color-mix(in srgb, ${paints.nodeStroke} 78%, var(--bg)); stroke-width: ${style.lineWidth}; stroke-dasharray: 4 7; }${drawCurve ? `
|
|
20551
|
+
.journey-curve { fill: none; stroke: color-mix(in srgb, ${paints.arrow} 55%, var(--bg)); stroke-width: ${Math.max(2, style.lineWidth * 2)}; stroke-linecap: round; }` : ""}
|
|
20432
20552
|
.journey-guide { stroke: color-mix(in srgb, ${paints.nodeStroke} 62%, var(--bg)); stroke-width: 1; }
|
|
20433
20553
|
.journey-score-label { fill: ${style.edgeTextColor ?? "var(--_text-sec)"}; }
|
|
20434
20554
|
.journey-baseline { stroke: ${paints.arrow}; stroke-width: ${Math.max(2, style.lineWidth * 2)}; }
|
|
@@ -20441,6 +20561,34 @@ ${sectionLabelPalette}
|
|
|
20441
20561
|
${actorPalette2}
|
|
20442
20562
|
</style>`;
|
|
20443
20563
|
}
|
|
20564
|
+
function renderExperienceCurve(markers, style, paints) {
|
|
20565
|
+
const points = markers.map((marker) => ({ x: marker.cx, y: marker.cy }));
|
|
20566
|
+
const d2 = smoothCurvePath(points);
|
|
20567
|
+
return connector(
|
|
20568
|
+
{
|
|
20569
|
+
id: "experience-curve",
|
|
20570
|
+
role: "series",
|
|
20571
|
+
geometry: { kind: "path", d: d2, points },
|
|
20572
|
+
lineStyle: "solid",
|
|
20573
|
+
paint: {
|
|
20574
|
+
stroke: `color-mix(in srgb, ${paints.arrow} 55%, var(--bg))`,
|
|
20575
|
+
strokeWidth: String(Math.max(2, style.lineWidth * 2))
|
|
20576
|
+
}
|
|
20577
|
+
},
|
|
20578
|
+
`<path class="journey-curve" d="${d2}" />`
|
|
20579
|
+
);
|
|
20580
|
+
}
|
|
20581
|
+
function smoothCurvePath(points) {
|
|
20582
|
+
const first = points[0];
|
|
20583
|
+
let d2 = `M${first.x},${first.y}`;
|
|
20584
|
+
for (let i2 = 1; i2 < points.length; i2++) {
|
|
20585
|
+
const from = points[i2 - 1];
|
|
20586
|
+
const to = points[i2];
|
|
20587
|
+
const dx = (to.x - from.x) / 3;
|
|
20588
|
+
d2 += ` C${from.x + dx},${from.y} ${to.x - dx},${to.y} ${to.x},${to.y}`;
|
|
20589
|
+
}
|
|
20590
|
+
return d2;
|
|
20591
|
+
}
|
|
20444
20592
|
function renderScoreGuide(guide, style, arrowMarkerId) {
|
|
20445
20593
|
const children = [];
|
|
20446
20594
|
for (const tick of guide.ticks) {
|
|
@@ -20456,7 +20604,7 @@ function renderScoreGuide(guide, style, arrowMarkerId) {
|
|
|
20456
20604
|
stroke: `color-mix(in srgb, ${style.nodeBorderColor ?? "var(--_node-stroke)"} 62%, var(--bg))`,
|
|
20457
20605
|
strokeWidth: "1"
|
|
20458
20606
|
},
|
|
20459
|
-
channels: { value: tick.score /
|
|
20607
|
+
channels: { value: tick.score / JOURNEY_MAX_SCORE3 }
|
|
20460
20608
|
},
|
|
20461
20609
|
`<line class="journey-guide" x1="${tick.x1}" y1="${tick.y}" x2="${tick.x2}" y2="${tick.y}" />`
|
|
20462
20610
|
)
|
|
@@ -20473,7 +20621,7 @@ function renderScoreGuide(guide, style, arrowMarkerId) {
|
|
|
20473
20621
|
fontSize: style.edgeLabelFontSize,
|
|
20474
20622
|
anchor: "end",
|
|
20475
20623
|
paint: { fill: style.edgeTextColor ?? "var(--_text-sec)" },
|
|
20476
|
-
channels: { value: tick.score /
|
|
20624
|
+
channels: { value: tick.score / JOURNEY_MAX_SCORE3 }
|
|
20477
20625
|
},
|
|
20478
20626
|
renderMultilineText(
|
|
20479
20627
|
String(tick.score),
|
|
@@ -20664,7 +20812,7 @@ function renderSectionLabelBand(section, style, paints, sectionIndex) {
|
|
|
20664
20812
|
function renderTask(task, sectionLabel, style, paints) {
|
|
20665
20813
|
const sectionAttr = sectionLabel ? ` data-section="${escapeAttr(sectionLabel)}"` : "";
|
|
20666
20814
|
const actorAttr = task.actors.length > 0 ? ` data-actors="${escapeAttr(task.actors.join(", "))}"` : "";
|
|
20667
|
-
const value = task.score /
|
|
20815
|
+
const value = task.score / JOURNEY_MAX_SCORE3;
|
|
20668
20816
|
const channels = sectionLabel ? { value, category: sectionLabel } : { value };
|
|
20669
20817
|
const children = [
|
|
20670
20818
|
{ indent: 2, node: renderTrack(task.track, task, style, paints) },
|
|
@@ -20696,7 +20844,7 @@ function renderTrack(track, task, style, paints) {
|
|
|
20696
20844
|
strokeWidth: String(style.lineWidth),
|
|
20697
20845
|
strokeDasharray: "4 7"
|
|
20698
20846
|
},
|
|
20699
|
-
channels: { value: task.score /
|
|
20847
|
+
channels: { value: task.score / JOURNEY_MAX_SCORE3 }
|
|
20700
20848
|
},
|
|
20701
20849
|
`<line class="journey-track" x1="${track.x}" y1="${track.y1}" x2="${track.x}" y2="${track.y2}" />`
|
|
20702
20850
|
);
|
|
@@ -20827,18 +20975,18 @@ function mouthPath(marker) {
|
|
|
20827
20975
|
if (marker.score === 2) return `M${cx - 8},${cy + 10} Q${cx},${cy + 2} ${cx + 8},${cy + 10}`;
|
|
20828
20976
|
return `M${cx - 9},${cy + 11} Q${cx},${cy} ${cx + 9},${cy + 11}`;
|
|
20829
20977
|
}
|
|
20830
|
-
function journeyPaints(style, visual, colors) {
|
|
20978
|
+
function journeyPaints(style, visual, colors, counts) {
|
|
20831
20979
|
const arrow = style.edgeStrokeColor ?? "var(--_arrow)";
|
|
20832
20980
|
const nodeFill = style.nodeFillColor ?? "var(--_node-fill)";
|
|
20833
20981
|
const nodeStroke = style.nodeBorderColor ?? "var(--_node-stroke)";
|
|
20834
20982
|
const groupFill = style.groupFillColor ?? `color-mix(in srgb, ${arrow} 8%, var(--bg))`;
|
|
20835
20983
|
const groupStroke = style.groupBorderColor ?? "var(--_node-stroke)";
|
|
20836
20984
|
const groupText = style.groupTextColor ?? "var(--_text-sec)";
|
|
20837
|
-
const sectionBases = paletteBases(arrow, colors, style);
|
|
20985
|
+
const sectionBases = paletteBases(arrow, colors, style, counts.sectionCount);
|
|
20838
20986
|
const configuredSectionCount = Math.max(
|
|
20839
20987
|
visual.sectionFills.length,
|
|
20840
20988
|
visual.sectionColours.length,
|
|
20841
|
-
sectionBases.length
|
|
20989
|
+
Math.min(sectionBases.length, Math.max(counts.sectionCount, 1))
|
|
20842
20990
|
);
|
|
20843
20991
|
const sectionFills = Array.from({ length: configuredSectionCount }, (_unused, index) => {
|
|
20844
20992
|
const explicitFill = visual.sectionFills[index % visual.sectionFills.length];
|
|
@@ -20846,7 +20994,7 @@ function journeyPaints(style, visual, colors) {
|
|
|
20846
20994
|
});
|
|
20847
20995
|
const sectionBands = Array.from({ length: configuredSectionCount }, (_unused, index) => {
|
|
20848
20996
|
const explicitFill = visual.sectionFills[index % visual.sectionFills.length];
|
|
20849
|
-
return explicitFill
|
|
20997
|
+
return explicitFill ?? style.groupHeaderFillColor ?? `color-mix(in srgb, ${sectionBases[index % sectionBases.length]} ${index === 0 ? 14 : 16}%, var(--bg))`;
|
|
20850
20998
|
});
|
|
20851
20999
|
const sectionStrokes = Array.from({ length: configuredSectionCount }, (_unused, index) => {
|
|
20852
21000
|
const explicitFill = visual.sectionFills[index % visual.sectionFills.length];
|
|
@@ -20854,9 +21002,13 @@ function journeyPaints(style, visual, colors) {
|
|
|
20854
21002
|
});
|
|
20855
21003
|
const sectionTextColors = Array.from(
|
|
20856
21004
|
{ length: configuredSectionCount },
|
|
20857
|
-
(_unused, index) =>
|
|
21005
|
+
(_unused, index) => contrastGuardedLabelColor(
|
|
21006
|
+
visual.sectionColours.length > 0 ? visual.sectionColours[index % visual.sectionColours.length] : void 0,
|
|
21007
|
+
sectionBands[index],
|
|
21008
|
+
style.groupTextColor ?? "var(--_text)"
|
|
21009
|
+
)
|
|
20858
21010
|
);
|
|
20859
|
-
const actorColors = visual.actorColours.length > 0 ? visual.actorColours : actorPalette(arrow, colors, style);
|
|
21011
|
+
const actorColors = visual.actorColours.length > 0 ? visual.actorColours : actorPalette(arrow, colors, style, counts.actorCount);
|
|
20860
21012
|
return {
|
|
20861
21013
|
arrow,
|
|
20862
21014
|
nodeFill,
|
|
@@ -20875,8 +21027,8 @@ function journeyPaints(style, visual, colors) {
|
|
|
20875
21027
|
scoreFaceInk: style.edgeTextColor ?? style.nodeTextColor ?? "var(--_text)"
|
|
20876
21028
|
};
|
|
20877
21029
|
}
|
|
20878
|
-
function paletteBases(arrow, colors, style) {
|
|
20879
|
-
const series = concreteSeriesColors(colors, style, 6);
|
|
21030
|
+
function paletteBases(arrow, colors, style, sectionCount) {
|
|
21031
|
+
const series = concreteSeriesColors(colors, style, Math.max(6, sectionCount));
|
|
20880
21032
|
if (series) return series;
|
|
20881
21033
|
return [
|
|
20882
21034
|
arrow,
|
|
@@ -20887,10 +21039,29 @@ function paletteBases(arrow, colors, style) {
|
|
|
20887
21039
|
"var(--fg)"
|
|
20888
21040
|
];
|
|
20889
21041
|
}
|
|
20890
|
-
function
|
|
20891
|
-
|
|
20892
|
-
if (
|
|
20893
|
-
|
|
21042
|
+
function contrastGuardedLabelColor(explicit, band, fallback) {
|
|
21043
|
+
if (!tryParseHex(band)) return explicit ?? fallback;
|
|
21044
|
+
if (explicit) {
|
|
21045
|
+
const ratio = wcagContrastRatio(explicit, band);
|
|
21046
|
+
if (ratio === null || ratio >= 4.5) return explicit;
|
|
21047
|
+
}
|
|
21048
|
+
return contrastTextColor(band) ?? explicit ?? fallback;
|
|
21049
|
+
}
|
|
21050
|
+
function actorPalette(arrow, colors, style, actorCount) {
|
|
21051
|
+
const count = Math.max(actorCount, 1);
|
|
21052
|
+
if (isHexColor(colors.bg) && isHexColor(colors.fg)) {
|
|
21053
|
+
const resolved = resolveColors(colors);
|
|
21054
|
+
const accent = firstHexColor(style.edgeStrokeColor, colors.accent, resolved.arrow);
|
|
21055
|
+
if (accent) {
|
|
21056
|
+
const [accentHue, accentSat] = hexToHsl(accent);
|
|
21057
|
+
const dark = isDarkBackground(colors.bg);
|
|
21058
|
+
const baseHue = accentSat < 20 ? 230 : accentHue;
|
|
21059
|
+
const saturation = Math.min(72, Math.max(42, accentSat < 20 ? 46 : accentSat));
|
|
21060
|
+
const lightness = dark ? 64 : 38;
|
|
21061
|
+
return Array.from({ length: count }, (_unused, index) => hslToHex((baseHue + index * 137.508) % 360, saturation, lightness));
|
|
21062
|
+
}
|
|
21063
|
+
}
|
|
21064
|
+
const fallback = [
|
|
20894
21065
|
arrow,
|
|
20895
21066
|
"var(--line, var(--_line))",
|
|
20896
21067
|
"var(--accent, var(--_arrow))",
|
|
@@ -20900,6 +21071,7 @@ function actorPalette(arrow, colors, style) {
|
|
|
20900
21071
|
"color-mix(in srgb, var(--accent, var(--_arrow)) 62%, var(--bg))",
|
|
20901
21072
|
"color-mix(in srgb, var(--line, var(--_line)) 72%, var(--bg))"
|
|
20902
21073
|
];
|
|
21074
|
+
return fallback.slice(0, Math.max(count <= fallback.length ? count : fallback.length, 1));
|
|
20903
21075
|
}
|
|
20904
21076
|
function concreteSeriesColors(colors, style, count) {
|
|
20905
21077
|
if (!isHexColor(colors.bg) || !isHexColor(colors.fg)) return void 0;
|
|
@@ -22814,7 +22986,7 @@ function letterAttr7(value) {
|
|
|
22814
22986
|
}
|
|
22815
22987
|
|
|
22816
22988
|
// src/quadrant/parser.ts
|
|
22817
|
-
var
|
|
22989
|
+
var TITLE_RE4 = /^title\s+(.+)$/i;
|
|
22818
22990
|
var AXIS_RE2 = /^([xy])-axis\s+(.+)$/i;
|
|
22819
22991
|
var QUADRANT_RE2 = /^quadrant-([1-4])\s+(.+)$/i;
|
|
22820
22992
|
var POINT_RE2 = /^(.+?)\s*:\s*\[\s*([^,\]]+)\s*,\s*([^,\]]+)\s*\]\s*(.*)$/;
|
|
@@ -22844,7 +23016,7 @@ function parseQuadrantChart(lines) {
|
|
|
22844
23016
|
continue;
|
|
22845
23017
|
}
|
|
22846
23018
|
let m2;
|
|
22847
|
-
if (m2 = line2.match(
|
|
23019
|
+
if (m2 = line2.match(TITLE_RE4)) {
|
|
22848
23020
|
title = normalizeBrTags(m2[1].trim());
|
|
22849
23021
|
continue;
|
|
22850
23022
|
}
|
|
@@ -30013,6 +30185,72 @@ function renderErAscii(text3, config, colorMode, theme) {
|
|
|
30013
30185
|
return canvasToString(canvas, { roleCanvas: rc, colorMode, theme });
|
|
30014
30186
|
}
|
|
30015
30187
|
|
|
30188
|
+
// src/ascii/wrap.ts
|
|
30189
|
+
var segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
30190
|
+
function graphemes(text3) {
|
|
30191
|
+
return Array.from(segmenter.segment(text3), (s2) => s2.segment);
|
|
30192
|
+
}
|
|
30193
|
+
function wrapText(text3, maxWidth, options) {
|
|
30194
|
+
if (!maxWidth || !Number.isFinite(maxWidth) || maxWidth <= 0) return text3.split("\n");
|
|
30195
|
+
const limit = Math.max(1, Math.floor(maxWidth));
|
|
30196
|
+
const lines = [];
|
|
30197
|
+
for (const paragraph of text3.split("\n")) {
|
|
30198
|
+
lines.push(...wrapParagraph(paragraph, limit, options));
|
|
30199
|
+
}
|
|
30200
|
+
return lines.length > 0 ? lines : [""];
|
|
30201
|
+
}
|
|
30202
|
+
function wrapParagraph(text3, maxWidth, options) {
|
|
30203
|
+
if (visualWidth(text3) <= maxWidth) return [text3];
|
|
30204
|
+
const words = text3.split(/\s+/).filter(Boolean);
|
|
30205
|
+
if (words.length === 0) return [""];
|
|
30206
|
+
const lines = [];
|
|
30207
|
+
let current = "";
|
|
30208
|
+
for (const word of words) {
|
|
30209
|
+
if (!current) {
|
|
30210
|
+
const chunks2 = breakWord(word, maxWidth, options);
|
|
30211
|
+
lines.push(...chunks2.slice(0, -1));
|
|
30212
|
+
current = chunks2[chunks2.length - 1] ?? "";
|
|
30213
|
+
continue;
|
|
30214
|
+
}
|
|
30215
|
+
const candidate = `${current} ${word}`;
|
|
30216
|
+
if (visualWidth(candidate) <= maxWidth) {
|
|
30217
|
+
current = candidate;
|
|
30218
|
+
continue;
|
|
30219
|
+
}
|
|
30220
|
+
lines.push(current);
|
|
30221
|
+
const chunks = breakWord(word, maxWidth, options);
|
|
30222
|
+
lines.push(...chunks.slice(0, -1));
|
|
30223
|
+
current = chunks[chunks.length - 1] ?? "";
|
|
30224
|
+
}
|
|
30225
|
+
if (current) lines.push(current);
|
|
30226
|
+
return lines;
|
|
30227
|
+
}
|
|
30228
|
+
function breakWord(word, maxWidth, options) {
|
|
30229
|
+
if (visualWidth(word) <= maxWidth) return [word];
|
|
30230
|
+
const hyphenate = options?.hyphenate ?? true;
|
|
30231
|
+
const clusters = graphemes(word);
|
|
30232
|
+
const lines = [];
|
|
30233
|
+
let start = 0;
|
|
30234
|
+
while (start < clusters.length) {
|
|
30235
|
+
let end = start + 1;
|
|
30236
|
+
while (end < clusters.length && fitsWithBreak(clusters, start, end + 1, maxWidth, hyphenate)) end++;
|
|
30237
|
+
const chunk = clusters.slice(start, end).join("");
|
|
30238
|
+
const hyphen = end < clusters.length && breakNeedsHyphen(clusters[end - 1], clusters[end], hyphenate) && visualWidth(chunk) + 1 <= maxWidth;
|
|
30239
|
+
lines.push(hyphen ? `${chunk}-` : chunk);
|
|
30240
|
+
start = end;
|
|
30241
|
+
}
|
|
30242
|
+
return lines;
|
|
30243
|
+
}
|
|
30244
|
+
function fitsWithBreak(clusters, start, end, maxWidth, hyphenate) {
|
|
30245
|
+
const width = visualWidth(clusters.slice(start, end).join(""));
|
|
30246
|
+
const hyphen = end < clusters.length && breakNeedsHyphen(clusters[end - 1], clusters[end], hyphenate) ? 1 : 0;
|
|
30247
|
+
return width + hyphen <= maxWidth;
|
|
30248
|
+
}
|
|
30249
|
+
function breakNeedsHyphen(before, after, hyphenate) {
|
|
30250
|
+
if (!hyphenate) return false;
|
|
30251
|
+
return !(visualWidth(before) === 2 && visualWidth(after) === 2);
|
|
30252
|
+
}
|
|
30253
|
+
|
|
30016
30254
|
// src/ascii/timeline.ts
|
|
30017
30255
|
function renderStyledLine(segments, colorMode, theme) {
|
|
30018
30256
|
const chars = [];
|
|
@@ -30025,7 +30263,7 @@ function renderStyledLine(segments, colorMode, theme) {
|
|
|
30025
30263
|
}
|
|
30026
30264
|
return colorizeLine(chars, roles, theme, colorMode);
|
|
30027
30265
|
}
|
|
30028
|
-
function renderTimelineAscii(lines, config, colorMode = "none", theme = DEFAULT_ASCII_THEME) {
|
|
30266
|
+
function renderTimelineAscii(lines, config, colorMode = "none", theme = DEFAULT_ASCII_THEME, maxWidth) {
|
|
30029
30267
|
const diagram = parseTimelineDiagram(lines);
|
|
30030
30268
|
const useAscii = config.useAscii;
|
|
30031
30269
|
const marker = useAscii ? "o" : "\u25CB";
|
|
@@ -30039,7 +30277,7 @@ function renderTimelineAscii(lines, config, colorMode = "none", theme = DEFAULT_
|
|
|
30039
30277
|
out.push(segments.length === 0 ? "" : renderStyledLine(segments, colorMode, theme));
|
|
30040
30278
|
};
|
|
30041
30279
|
if (diagram.title) {
|
|
30042
|
-
for (const line2 of diagram.title
|
|
30280
|
+
for (const line2 of wrapText(diagram.title, maxWidth)) {
|
|
30043
30281
|
pushLine([{ text: line2, role: "text" }]);
|
|
30044
30282
|
}
|
|
30045
30283
|
pushLine();
|
|
@@ -30047,15 +30285,19 @@ function renderTimelineAscii(lines, config, colorMode = "none", theme = DEFAULT_
|
|
|
30047
30285
|
for (let sectionIndex = 0; sectionIndex < diagram.sections.length; sectionIndex++) {
|
|
30048
30286
|
const section = diagram.sections[sectionIndex];
|
|
30049
30287
|
if (section.label) {
|
|
30050
|
-
|
|
30051
|
-
|
|
30052
|
-
|
|
30053
|
-
|
|
30054
|
-
|
|
30288
|
+
const labelLines = wrapText(section.label.replace(/\n/g, " / "), maxWidth ? Math.max(1, maxWidth - 2) : void 0);
|
|
30289
|
+
labelLines.forEach((line2, index) => {
|
|
30290
|
+
const segments = [
|
|
30291
|
+
index === 0 ? { text: "[", role: "border" } : { text: " ", role: null },
|
|
30292
|
+
{ text: line2, role: "text" }
|
|
30293
|
+
];
|
|
30294
|
+
if (index === labelLines.length - 1) segments.push({ text: "]", role: "border" });
|
|
30295
|
+
pushLine(segments);
|
|
30296
|
+
});
|
|
30055
30297
|
}
|
|
30056
30298
|
for (let periodIndex = 0; periodIndex < section.periods.length; periodIndex++) {
|
|
30057
30299
|
const period = section.periods[periodIndex];
|
|
30058
|
-
const periodLines = period.label.
|
|
30300
|
+
const periodLines = wrapText(period.label, maxWidth ? Math.max(1, maxWidth - 3) : void 0);
|
|
30059
30301
|
pushLine([
|
|
30060
30302
|
{ text: marker, role: "junction" },
|
|
30061
30303
|
{ text: " ", role: null },
|
|
@@ -30069,7 +30311,7 @@ function renderTimelineAscii(lines, config, colorMode = "none", theme = DEFAULT_
|
|
|
30069
30311
|
}
|
|
30070
30312
|
for (let eventIndex = 0; eventIndex < period.events.length; eventIndex++) {
|
|
30071
30313
|
const event = period.events[eventIndex];
|
|
30072
|
-
const eventLines = event.text.
|
|
30314
|
+
const eventLines = wrapText(event.text, maxWidth ? Math.max(1, maxWidth - 6) : void 0);
|
|
30073
30315
|
const junction = eventIndex === period.events.length - 1 ? lastBranch : branch;
|
|
30074
30316
|
pushLine([
|
|
30075
30317
|
{ text: vertical, role: "line" },
|
|
@@ -30313,6 +30555,10 @@ function renderScoreSegments(score, useAscii) {
|
|
|
30313
30555
|
];
|
|
30314
30556
|
return segments.filter((segment) => segment.text.length > 0);
|
|
30315
30557
|
}
|
|
30558
|
+
var SCORE_BLOCKS = ["\u2581", "\u2582", "\u2584", "\u2586", "\u2588"];
|
|
30559
|
+
function renderScoreStrip(diagram, useAscii) {
|
|
30560
|
+
return diagram.sections.map((section) => section.tasks.map((task) => useAscii ? String(task.score) : SCORE_BLOCKS[task.score - 1]).join("")).filter((group2) => group2.length > 0).join(" ");
|
|
30561
|
+
}
|
|
30316
30562
|
function renderJourneyAscii(text3, config, colorMode = "none", theme = DEFAULT_ASCII_THEME, maxWidth) {
|
|
30317
30563
|
const lines = preprocessMermaidLines(text3);
|
|
30318
30564
|
const diagram = parseJourneyDiagram(lines);
|
|
@@ -30327,16 +30573,30 @@ function renderJourneyAscii(text3, config, colorMode = "none", theme = DEFAULT_A
|
|
|
30327
30573
|
}
|
|
30328
30574
|
pushLine();
|
|
30329
30575
|
}
|
|
30576
|
+
const scoreStrip = renderScoreStrip(diagram, useAscii);
|
|
30577
|
+
if (scoreStrip) {
|
|
30578
|
+
const stripPrefix = "scores: ";
|
|
30579
|
+
const stripLines = wrapText(scoreStrip, maxWidth ? Math.max(1, maxWidth - stripPrefix.length) : void 0, { hyphenate: false });
|
|
30580
|
+
stripLines.forEach((line2, index) => {
|
|
30581
|
+
pushLine([
|
|
30582
|
+
index === 0 ? { text: stripPrefix, role: "border" } : { text: " ".repeat(stripPrefix.length), role: null },
|
|
30583
|
+
{ text: line2, role: "arrow" }
|
|
30584
|
+
]);
|
|
30585
|
+
});
|
|
30586
|
+
pushLine();
|
|
30587
|
+
}
|
|
30330
30588
|
for (let sectionIndex = 0; sectionIndex < diagram.sections.length; sectionIndex++) {
|
|
30331
30589
|
const section = diagram.sections[sectionIndex];
|
|
30332
30590
|
if (section.label) {
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
{ text:
|
|
30337
|
-
{ text:
|
|
30338
|
-
]
|
|
30339
|
-
|
|
30591
|
+
const labelLines = wrapText(section.label.replace(/\n/g, " / "), maxWidth ? Math.max(1, maxWidth - 2) : void 0);
|
|
30592
|
+
labelLines.forEach((line2, index) => {
|
|
30593
|
+
const segments = [
|
|
30594
|
+
index === 0 ? { text: "[", role: "border" } : { text: " ", role: null },
|
|
30595
|
+
{ text: line2, role: "text" }
|
|
30596
|
+
];
|
|
30597
|
+
if (index === labelLines.length - 1) segments.push({ text: "]", role: "border" });
|
|
30598
|
+
pushLine(segments);
|
|
30599
|
+
});
|
|
30340
30600
|
}
|
|
30341
30601
|
for (let taskIndex = 0; taskIndex < section.tasks.length; taskIndex++) {
|
|
30342
30602
|
const task = section.tasks[taskIndex];
|
|
@@ -30376,60 +30636,6 @@ function renderJourneyAscii(text3, config, colorMode = "none", theme = DEFAULT_A
|
|
|
30376
30636
|
}
|
|
30377
30637
|
return out.join("\n").trimEnd();
|
|
30378
30638
|
}
|
|
30379
|
-
function wrapText(text3, maxWidth) {
|
|
30380
|
-
if (!maxWidth || !Number.isFinite(maxWidth) || maxWidth <= 0) return text3.split("\n");
|
|
30381
|
-
const limit = Math.max(1, Math.floor(maxWidth));
|
|
30382
|
-
const lines = [];
|
|
30383
|
-
for (const paragraph of text3.split("\n")) {
|
|
30384
|
-
lines.push(...wrapParagraph(paragraph, limit));
|
|
30385
|
-
}
|
|
30386
|
-
return lines.length > 0 ? lines : [""];
|
|
30387
|
-
}
|
|
30388
|
-
function wrapParagraph(text3, maxWidth) {
|
|
30389
|
-
if (visualWidth(text3) <= maxWidth) return [text3];
|
|
30390
|
-
const words = text3.split(/\s+/).filter(Boolean);
|
|
30391
|
-
if (words.length === 0) return [""];
|
|
30392
|
-
const lines = [];
|
|
30393
|
-
let current = "";
|
|
30394
|
-
for (const word of words) {
|
|
30395
|
-
if (!current) {
|
|
30396
|
-
const chunks2 = breakWord(word, maxWidth);
|
|
30397
|
-
lines.push(...chunks2.slice(0, -1));
|
|
30398
|
-
current = chunks2[chunks2.length - 1] ?? "";
|
|
30399
|
-
continue;
|
|
30400
|
-
}
|
|
30401
|
-
const candidate = `${current} ${word}`;
|
|
30402
|
-
if (visualWidth(candidate) <= maxWidth) {
|
|
30403
|
-
current = candidate;
|
|
30404
|
-
continue;
|
|
30405
|
-
}
|
|
30406
|
-
lines.push(current);
|
|
30407
|
-
const chunks = breakWord(word, maxWidth);
|
|
30408
|
-
lines.push(...chunks.slice(0, -1));
|
|
30409
|
-
current = chunks[chunks.length - 1] ?? "";
|
|
30410
|
-
}
|
|
30411
|
-
if (current) lines.push(current);
|
|
30412
|
-
return lines;
|
|
30413
|
-
}
|
|
30414
|
-
function breakWord(word, maxWidth) {
|
|
30415
|
-
if (visualWidth(word) <= maxWidth) return [word];
|
|
30416
|
-
const lines = [];
|
|
30417
|
-
let current = "";
|
|
30418
|
-
let width = 0;
|
|
30419
|
-
for (const char of word) {
|
|
30420
|
-
const charWidth = visualWidth(char);
|
|
30421
|
-
if (current && width + charWidth > maxWidth) {
|
|
30422
|
-
lines.push(current);
|
|
30423
|
-
current = char;
|
|
30424
|
-
width = charWidth;
|
|
30425
|
-
} else {
|
|
30426
|
-
current += char;
|
|
30427
|
-
width += charWidth;
|
|
30428
|
-
}
|
|
30429
|
-
}
|
|
30430
|
-
if (current) lines.push(current);
|
|
30431
|
-
return lines;
|
|
30432
|
-
}
|
|
30433
30639
|
|
|
30434
30640
|
// src/ascii/xychart.ts
|
|
30435
30641
|
var PLOT_WIDTH = 60;
|
|
@@ -31418,7 +31624,7 @@ registerRenderHooks("timeline", {
|
|
|
31418
31624
|
layout: (ctx) => layoutResult(layoutTimelineDiagram(parseTimelineDiagram(ctx.source.lines), ctx.options)),
|
|
31419
31625
|
renderSvg: svg(renderTimelineSvg),
|
|
31420
31626
|
lowerScene: scene(lowerTimelineScene),
|
|
31421
|
-
renderAscii: (ctx) => renderTimelineAscii(ctx.source.lines, ctx.config, ctx.colorMode, ctx.theme)
|
|
31627
|
+
renderAscii: (ctx) => renderTimelineAscii(ctx.source.lines, ctx.config, ctx.colorMode, ctx.theme, ctx.options.maxWidth)
|
|
31422
31628
|
});
|
|
31423
31629
|
registerRenderHooks("journey", {
|
|
31424
31630
|
layout: (ctx) => layoutResult(layoutJourneyDiagram(parseJourneyDiagram(ctx.source.lines), ctx.renderOptions)),
|
|
@@ -34446,7 +34652,8 @@ function journeyToRendered(d2) {
|
|
|
34446
34652
|
nodes.push({ id: t2.id, x: f3(t2.x), y: f3(t2.y), w: f3(t2.width), h: f3(t2.height), shape: "rectangle", label: t2.text });
|
|
34447
34653
|
memberIds.push(t2.id);
|
|
34448
34654
|
}
|
|
34449
|
-
|
|
34655
|
+
const bottom = s2.tasks.length > 0 ? Math.max(s2.y + s2.height, ...s2.tasks.map((t2) => t2.y + t2.height)) : s2.y + s2.height;
|
|
34656
|
+
groups.push({ id: s2.id, x: f3(s2.x), y: f3(s2.y), w: f3(s2.width), h: f3(bottom - s2.y), members: memberIds, label: s2.label });
|
|
34450
34657
|
}
|
|
34451
34658
|
return { version: 1, kind: d2.kind, nodes, edges: [], groups, bounds: { w: f3(positioned.width), h: f3(positioned.height) } };
|
|
34452
34659
|
} catch {
|
|
@@ -34923,6 +35130,35 @@ function withRenderParity(input, result, opts) {
|
|
|
34923
35130
|
return { ...result, ok: false, warnings: [...result.warnings, { code: "RENDER_FAILED", reason }] };
|
|
34924
35131
|
}
|
|
34925
35132
|
}
|
|
35133
|
+
var JOURNEY_NOOP_CONFIG_FIELDS = [
|
|
35134
|
+
"boxMargin",
|
|
35135
|
+
"boxTextMargin",
|
|
35136
|
+
"noteMargin",
|
|
35137
|
+
"messageMargin",
|
|
35138
|
+
"messageAlign",
|
|
35139
|
+
"bottomMarginAdj",
|
|
35140
|
+
"rightAngles",
|
|
35141
|
+
"activationWidth",
|
|
35142
|
+
"textPlacement"
|
|
35143
|
+
];
|
|
35144
|
+
function journeyIneffectiveConfigWarnings(d2) {
|
|
35145
|
+
const configs = [
|
|
35146
|
+
d2.meta.frontmatter?.journey,
|
|
35147
|
+
...d2.meta.initDirectives.map((directive) => directive.parsed?.journey)
|
|
35148
|
+
];
|
|
35149
|
+
const present = /* @__PURE__ */ new Set();
|
|
35150
|
+
for (const config of configs) {
|
|
35151
|
+
if (!config || typeof config !== "object") continue;
|
|
35152
|
+
for (const field of JOURNEY_NOOP_CONFIG_FIELDS) {
|
|
35153
|
+
if (field in config) present.add(field);
|
|
35154
|
+
}
|
|
35155
|
+
}
|
|
35156
|
+
return [...present].sort().map((field) => ({
|
|
35157
|
+
code: "INEFFECTIVE_CONFIG",
|
|
35158
|
+
field,
|
|
35159
|
+
message: `Journey config field "${field}" is accepted for Mermaid config-shape compatibility but has no effect on journey geometry or paint.`
|
|
35160
|
+
}));
|
|
35161
|
+
}
|
|
34926
35162
|
function verifyStructure(input, opts = {}) {
|
|
34927
35163
|
const d2 = typeof input === "string" ? unwrap(input) : input;
|
|
34928
35164
|
if (!d2) return finalize([{ code: "EMPTY_DIAGRAM" }], emptyRenderedLayout("flowchart"), opts);
|
|
@@ -34930,7 +35166,8 @@ function verifyStructure(input, opts = {}) {
|
|
|
34930
35166
|
const metaWarnings = d2.meta.droppedComments?.length ? [{ code: "COMMENT_DROPPED", count: d2.meta.droppedComments.length, lines: d2.meta.droppedComments.map((c2) => c2.line) }] : [];
|
|
34931
35167
|
const sourceWarnings = d2.kind === "flowchart" ? flowchartUnsupportedSyntaxWarnings(d2.canonicalSource) : [];
|
|
34932
35168
|
const faithfulnessWarnings = roundtripFaithfulnessWarnings(d2);
|
|
34933
|
-
const
|
|
35169
|
+
const configWarnings = d2.kind === "journey" ? journeyIneffectiveConfigWarnings(d2) : [];
|
|
35170
|
+
const pluginWarnings = dedupedConcat(dedupedConcat(dedupedConcat(dedupedConcat(metaWarnings, dispatchFamilyVerify(d2, opts)), sourceWarnings), faithfulnessWarnings), configWarnings);
|
|
34934
35171
|
if (d2.body.kind === "sequence") return mergeFinalize(verifySequence(d2, cap, opts), pluginWarnings, opts);
|
|
34935
35172
|
if (d2.body.kind === "timeline") return mergeFinalize(verifyTimeline(d2, cap, opts), pluginWarnings, opts);
|
|
34936
35173
|
if (d2.body.kind === "gantt") {
|
|
@@ -34943,7 +35180,9 @@ function verifyStructure(input, opts = {}) {
|
|
|
34943
35180
|
const layout = layoutFamilyToRendered(d2) ?? emptyRenderedLayout(d2.kind);
|
|
34944
35181
|
const familyGeometry = d2.body.kind === "class" || d2.body.kind === "er" ? layoutGeometryWarnings(layout, { edgeAnchors: true, nodeOverlaps: true }) : layoutGeometryWarnings(layout, {
|
|
34945
35182
|
nodeOverlaps: d2.body.kind === "journey",
|
|
34946
|
-
|
|
35183
|
+
// Journey sections are groups with task members (family-layouts.ts),
|
|
35184
|
+
// so a task laid outside its section band is a reportable breach.
|
|
35185
|
+
groupContainment: d2.body.kind === "xychart" || d2.body.kind === "quadrant" || d2.body.kind === "journey"
|
|
34947
35186
|
});
|
|
34948
35187
|
return finalize(dedupedConcat(pluginWarnings, familyGeometry), layout, opts);
|
|
34949
35188
|
}
|
|
@@ -35968,6 +36207,25 @@ function factsJourney(out, body) {
|
|
|
35968
36207
|
if (s2.label) add(out, `journey task ${clean(t2.text)} section ${clean(s2.label)}`);
|
|
35969
36208
|
});
|
|
35970
36209
|
});
|
|
36210
|
+
const tasks = body.sections.flatMap((s2) => s2.tasks);
|
|
36211
|
+
add(out, `journey sections ${body.sections.length}`);
|
|
36212
|
+
add(out, `journey tasks ${tasks.length}`);
|
|
36213
|
+
const participation = /* @__PURE__ */ new Map();
|
|
36214
|
+
for (const t2 of tasks) for (const a2 of t2.actors) participation.set(a2, (participation.get(a2) ?? 0) + 1);
|
|
36215
|
+
add(out, `journey actors ${participation.size}`);
|
|
36216
|
+
if (tasks.length > 0) {
|
|
36217
|
+
const scores = tasks.map((t2) => t2.score);
|
|
36218
|
+
const min = Math.min(...scores);
|
|
36219
|
+
const max = Math.max(...scores);
|
|
36220
|
+
const average = Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length * 100) / 100;
|
|
36221
|
+
add(out, `journey score range ${min}..${max}`);
|
|
36222
|
+
add(out, `journey average score ${num2(average)}`);
|
|
36223
|
+
for (const t2 of tasks) {
|
|
36224
|
+
if (t2.score === min) add(out, `journey lowest task ${clean(t2.text)} score ${num2(t2.score)}`);
|
|
36225
|
+
if (t2.score === max) add(out, `journey highest task ${clean(t2.text)} score ${num2(t2.score)}`);
|
|
36226
|
+
}
|
|
36227
|
+
}
|
|
36228
|
+
for (const [actor, count] of participation) add(out, `journey actor ${clean(actor)} tasks ${count}`);
|
|
35971
36229
|
}
|
|
35972
36230
|
function factsArchitecture(out, body) {
|
|
35973
36231
|
for (const g2 of body.groups) {
|
|
@@ -36135,7 +36393,8 @@ function describeMermaidTree(d2) {
|
|
|
36135
36393
|
}
|
|
36136
36394
|
} else if (d2.body.kind === "journey") {
|
|
36137
36395
|
for (const s2 of d2.body.sections) for (const t2 of s2.tasks) {
|
|
36138
|
-
|
|
36396
|
+
const actors = t2.actors.length ? `; ${t2.actors.join(", ")}` : "";
|
|
36397
|
+
tree.nodes.push({ id: t2.id, label: `${t2.text} (score ${t2.score}${actors})` });
|
|
36139
36398
|
}
|
|
36140
36399
|
} else if (d2.body.kind === "architecture") {
|
|
36141
36400
|
for (const g2 of d2.body.groups) tree.nodes.push({ id: g2.id, label: g2.label || g2.id });
|
|
@@ -36790,16 +37049,223 @@ function sourceLineHints(d2) {
|
|
|
36790
37049
|
|
|
36791
37050
|
// src/agent/png.ts
|
|
36792
37051
|
import { fileURLToPath } from "url";
|
|
36793
|
-
import { join, dirname } from "path";
|
|
37052
|
+
import { join as join2, dirname } from "path";
|
|
36794
37053
|
import { existsSync } from "fs";
|
|
36795
37054
|
import { Resvg } from "@resvg/resvg-js";
|
|
37055
|
+
|
|
37056
|
+
// src/agent/font-coverage.ts
|
|
37057
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
37058
|
+
import { join } from "path";
|
|
37059
|
+
import { decodeXML as decodeXML3 } from "entities";
|
|
37060
|
+
var TAG_TTCF = 1953784678;
|
|
37061
|
+
var TAG_OTTO = 1330926671;
|
|
37062
|
+
var TAG_TRUE = 1953658213;
|
|
37063
|
+
var TAG_CMAP = 1668112752;
|
|
37064
|
+
function parseFontFile(bytes) {
|
|
37065
|
+
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
37066
|
+
try {
|
|
37067
|
+
if (dv.getUint32(0) === TAG_TTCF) {
|
|
37068
|
+
const numFonts = dv.getUint32(8);
|
|
37069
|
+
const lookups = [];
|
|
37070
|
+
for (let i2 = 0; i2 < numFonts; i2++) lookups.push(...parseSfnt(dv, dv.getUint32(12 + i2 * 4)));
|
|
37071
|
+
return lookups;
|
|
37072
|
+
}
|
|
37073
|
+
return parseSfnt(dv, 0);
|
|
37074
|
+
} catch {
|
|
37075
|
+
return [];
|
|
37076
|
+
}
|
|
37077
|
+
}
|
|
37078
|
+
function parseSfnt(dv, base) {
|
|
37079
|
+
const version = dv.getUint32(base);
|
|
37080
|
+
if (version !== 65536 && version !== TAG_OTTO && version !== TAG_TRUE) return [];
|
|
37081
|
+
const numTables = dv.getUint16(base + 4);
|
|
37082
|
+
for (let i2 = 0; i2 < numTables; i2++) {
|
|
37083
|
+
const rec = base + 12 + i2 * 16;
|
|
37084
|
+
if (dv.getUint32(rec) === TAG_CMAP) return parseCmapTable(dv, dv.getUint32(rec + 8));
|
|
37085
|
+
}
|
|
37086
|
+
return [];
|
|
37087
|
+
}
|
|
37088
|
+
function parseCmapTable(dv, cmap) {
|
|
37089
|
+
const lookups = [];
|
|
37090
|
+
const numTables = dv.getUint16(cmap + 2);
|
|
37091
|
+
for (let i2 = 0; i2 < numTables; i2++) {
|
|
37092
|
+
const rec = cmap + 4 + i2 * 8;
|
|
37093
|
+
const platform = dv.getUint16(rec);
|
|
37094
|
+
const encoding = dv.getUint16(rec + 2);
|
|
37095
|
+
if (!(platform === 0 || platform === 3 && (encoding === 1 || encoding === 10))) continue;
|
|
37096
|
+
const sub = cmap + dv.getUint32(rec + 4);
|
|
37097
|
+
const format = dv.getUint16(sub);
|
|
37098
|
+
if (format === 4) lookups.push(format4Lookup(dv, sub));
|
|
37099
|
+
else if (format === 12) lookups.push(format12Lookup(dv, sub));
|
|
37100
|
+
else if (format === 6) lookups.push(format6Lookup(dv, sub));
|
|
37101
|
+
else if (format === 0) lookups.push(format0Lookup(dv, sub));
|
|
37102
|
+
}
|
|
37103
|
+
return lookups;
|
|
37104
|
+
}
|
|
37105
|
+
function format4Lookup(dv, sub) {
|
|
37106
|
+
const segCount = dv.getUint16(sub + 6) / 2;
|
|
37107
|
+
const endsAt = sub + 14;
|
|
37108
|
+
const startsAt = endsAt + segCount * 2 + 2;
|
|
37109
|
+
const deltasAt = startsAt + segCount * 2;
|
|
37110
|
+
const rangeOffsetsAt = deltasAt + segCount * 2;
|
|
37111
|
+
return {
|
|
37112
|
+
has(cp) {
|
|
37113
|
+
if (cp > 65535) return false;
|
|
37114
|
+
let lo = 0;
|
|
37115
|
+
let hi = segCount - 1;
|
|
37116
|
+
while (lo < hi) {
|
|
37117
|
+
const mid = lo + hi >> 1;
|
|
37118
|
+
if (dv.getUint16(endsAt + mid * 2) < cp) lo = mid + 1;
|
|
37119
|
+
else hi = mid;
|
|
37120
|
+
}
|
|
37121
|
+
if (dv.getUint16(startsAt + lo * 2) > cp || cp === 65535) return false;
|
|
37122
|
+
const rangeOffset = dv.getUint16(rangeOffsetsAt + lo * 2);
|
|
37123
|
+
if (rangeOffset === 0) return (cp + dv.getInt16(deltasAt + lo * 2) & 65535) !== 0;
|
|
37124
|
+
const glyphAt = rangeOffsetsAt + lo * 2 + rangeOffset + (cp - dv.getUint16(startsAt + lo * 2)) * 2;
|
|
37125
|
+
if (glyphAt + 2 > dv.byteLength) return false;
|
|
37126
|
+
const glyph = dv.getUint16(glyphAt);
|
|
37127
|
+
return glyph !== 0 && (glyph + dv.getInt16(deltasAt + lo * 2) & 65535) !== 0;
|
|
37128
|
+
}
|
|
37129
|
+
};
|
|
37130
|
+
}
|
|
37131
|
+
function format12Lookup(dv, sub) {
|
|
37132
|
+
const numGroups = dv.getUint32(sub + 12);
|
|
37133
|
+
const groupsAt = sub + 16;
|
|
37134
|
+
return {
|
|
37135
|
+
has(cp) {
|
|
37136
|
+
let lo = 0;
|
|
37137
|
+
let hi = numGroups - 1;
|
|
37138
|
+
while (lo <= hi) {
|
|
37139
|
+
const mid = lo + hi >> 1;
|
|
37140
|
+
const at = groupsAt + mid * 12;
|
|
37141
|
+
if (cp < dv.getUint32(at)) hi = mid - 1;
|
|
37142
|
+
else if (cp > dv.getUint32(at + 4)) lo = mid + 1;
|
|
37143
|
+
else return dv.getUint32(at + 8) + (cp - dv.getUint32(at)) !== 0;
|
|
37144
|
+
}
|
|
37145
|
+
return false;
|
|
37146
|
+
}
|
|
37147
|
+
};
|
|
37148
|
+
}
|
|
37149
|
+
function format6Lookup(dv, sub) {
|
|
37150
|
+
const first = dv.getUint16(sub + 6);
|
|
37151
|
+
const count = dv.getUint16(sub + 8);
|
|
37152
|
+
return {
|
|
37153
|
+
has: (cp) => cp >= first && cp < first + count && dv.getUint16(sub + 10 + (cp - first) * 2) !== 0
|
|
37154
|
+
};
|
|
37155
|
+
}
|
|
37156
|
+
function format0Lookup(dv, sub) {
|
|
37157
|
+
return { has: (cp) => cp < 256 && dv.getUint8(sub + 6 + cp) !== 0 };
|
|
37158
|
+
}
|
|
37159
|
+
var FONT_EXT = /\.(ttf|otf|ttc|otc)$/i;
|
|
37160
|
+
var fileLookupCache = /* @__PURE__ */ new Map();
|
|
37161
|
+
function fontFilesUnder(dir, depth = 4) {
|
|
37162
|
+
if (depth < 0) return [];
|
|
37163
|
+
let entries;
|
|
37164
|
+
try {
|
|
37165
|
+
entries = readdirSync(dir).sort();
|
|
37166
|
+
} catch {
|
|
37167
|
+
return [];
|
|
37168
|
+
}
|
|
37169
|
+
const files = [];
|
|
37170
|
+
for (const entry of entries) {
|
|
37171
|
+
const path = join(dir, entry);
|
|
37172
|
+
try {
|
|
37173
|
+
const stat = statSync(path);
|
|
37174
|
+
if (stat.isDirectory()) files.push(...fontFilesUnder(path, depth - 1));
|
|
37175
|
+
else if (FONT_EXT.test(entry)) files.push(path);
|
|
37176
|
+
} catch {
|
|
37177
|
+
}
|
|
37178
|
+
}
|
|
37179
|
+
return files;
|
|
37180
|
+
}
|
|
37181
|
+
function lookupsForDirs(dirs) {
|
|
37182
|
+
const lookups = [];
|
|
37183
|
+
for (const dir of dirs) {
|
|
37184
|
+
for (const file of fontFilesUnder(dir)) {
|
|
37185
|
+
let parsed = fileLookupCache.get(file);
|
|
37186
|
+
if (!parsed) {
|
|
37187
|
+
try {
|
|
37188
|
+
parsed = parseFontFile(readFileSync(file));
|
|
37189
|
+
} catch {
|
|
37190
|
+
parsed = [];
|
|
37191
|
+
}
|
|
37192
|
+
fileLookupCache.set(file, parsed);
|
|
37193
|
+
}
|
|
37194
|
+
lookups.push(...parsed);
|
|
37195
|
+
}
|
|
37196
|
+
}
|
|
37197
|
+
return lookups;
|
|
37198
|
+
}
|
|
37199
|
+
function extractSvgTextContent(svg2) {
|
|
37200
|
+
let out = "";
|
|
37201
|
+
for (const m2 of svg2.matchAll(/<text\b[^>]*>([\s\S]*?)<\/text>/g)) {
|
|
37202
|
+
out += m2[1].replace(/<[^>]*>/g, "");
|
|
37203
|
+
}
|
|
37204
|
+
return decodeXML3(out);
|
|
37205
|
+
}
|
|
37206
|
+
function isIgnorable(cp) {
|
|
37207
|
+
return cp <= 32 || cp === 127 || cp >= 8203 && cp <= 8207 || // zero-width space/joiners, directional marks
|
|
37208
|
+
cp === 8232 || cp === 8233 || cp >= 65024 && cp <= 65039 || // variation selectors (emoji presentation)
|
|
37209
|
+
cp === 65279;
|
|
37210
|
+
}
|
|
37211
|
+
function classifyScript(cp) {
|
|
37212
|
+
const ranges = [
|
|
37213
|
+
[1328, 1423, "Armenian"],
|
|
37214
|
+
[1424, 1535, "Hebrew"],
|
|
37215
|
+
[1536, 1919, "Arabic"],
|
|
37216
|
+
[2304, 2431, "Devanagari"],
|
|
37217
|
+
[2432, 2559, "Bengali"],
|
|
37218
|
+
[3584, 3711, "Thai"],
|
|
37219
|
+
[4256, 4351, "Georgian"],
|
|
37220
|
+
[4352, 4607, "CJK"],
|
|
37221
|
+
// Hangul jamo
|
|
37222
|
+
[9728, 10175, "emoji"],
|
|
37223
|
+
// misc symbols + dingbats
|
|
37224
|
+
[11904, 40959, "CJK"],
|
|
37225
|
+
// radicals, kana, CJK symbols, unified ideographs
|
|
37226
|
+
[43360, 43391, "CJK"],
|
|
37227
|
+
[44032, 55295, "CJK"],
|
|
37228
|
+
// Hangul syllables
|
|
37229
|
+
[63744, 64255, "CJK"],
|
|
37230
|
+
// compatibility ideographs
|
|
37231
|
+
[65072, 65103, "CJK"],
|
|
37232
|
+
[65280, 65519, "CJK"],
|
|
37233
|
+
// fullwidth/halfwidth forms
|
|
37234
|
+
[127462, 127487, "emoji"],
|
|
37235
|
+
// regional indicators (flags)
|
|
37236
|
+
[126976, 129791, "emoji"],
|
|
37237
|
+
[131072, 262143, "CJK"]
|
|
37238
|
+
// ideograph extensions
|
|
37239
|
+
];
|
|
37240
|
+
for (const [lo, hi, script] of ranges) if (cp >= lo && cp <= hi) return script;
|
|
37241
|
+
return "other";
|
|
37242
|
+
}
|
|
37243
|
+
function findUncoveredScripts(svg2, fontDirs) {
|
|
37244
|
+
const text3 = extractSvgTextContent(svg2);
|
|
37245
|
+
if (!text3) return [];
|
|
37246
|
+
const unique2 = /* @__PURE__ */ new Set();
|
|
37247
|
+
for (const ch of text3) unique2.add(ch.codePointAt(0));
|
|
37248
|
+
const lookups = lookupsForDirs(fontDirs);
|
|
37249
|
+
const byScript = /* @__PURE__ */ new Map();
|
|
37250
|
+
for (const cp of unique2) {
|
|
37251
|
+
if (isIgnorable(cp)) continue;
|
|
37252
|
+
if (lookups.some((l2) => l2.has(cp))) continue;
|
|
37253
|
+
const script = classifyScript(cp);
|
|
37254
|
+
const bucket = byScript.get(script);
|
|
37255
|
+
if (bucket) bucket.push(cp);
|
|
37256
|
+
else byScript.set(script, [cp]);
|
|
37257
|
+
}
|
|
37258
|
+
return [...byScript.entries()].sort(([a2], [b2]) => a2 < b2 ? -1 : 1).map(([script, cps]) => ({ script, chars: cps.sort((a2, b2) => a2 - b2).map((cp) => String.fromCodePoint(cp)) }));
|
|
37259
|
+
}
|
|
37260
|
+
|
|
37261
|
+
// src/agent/png.ts
|
|
36796
37262
|
var cachedFontDir = null;
|
|
36797
37263
|
function resolveFontDir() {
|
|
36798
37264
|
if (cachedFontDir !== null) return cachedFontDir;
|
|
36799
37265
|
const here = typeof import.meta.url === "string" ? fileURLToPath(import.meta.url) : __filename;
|
|
36800
37266
|
let dir = dirname(here);
|
|
36801
37267
|
for (let i2 = 0; i2 < 6; i2++) {
|
|
36802
|
-
const candidate =
|
|
37268
|
+
const candidate = join2(dir, "assets", "fonts");
|
|
36803
37269
|
if (existsSync(candidate)) {
|
|
36804
37270
|
cachedFontDir = candidate;
|
|
36805
37271
|
return candidate;
|
|
@@ -36818,16 +37284,36 @@ function renderMermaidPNG(input, opts = {}) {
|
|
|
36818
37284
|
);
|
|
36819
37285
|
const scale = opts.scale ?? 2;
|
|
36820
37286
|
const fontDir = resolveFontDir();
|
|
37287
|
+
const loadSystemFonts = opts.loadSystemFonts ?? false;
|
|
37288
|
+
const fontDirs = [...fontDir ? [fontDir] : [], ...opts.fontDirs ?? []];
|
|
37289
|
+
if (!loadSystemFonts) {
|
|
37290
|
+
const emit = opts.onWarning ?? ((w2) => process.stderr.write(`agentic-mermaid renderMermaidPNG: warning ${w2.code}: ${w2.message}
|
|
37291
|
+
`));
|
|
37292
|
+
for (const { script, chars } of findUncoveredScripts(svg2, fontDirs)) {
|
|
37293
|
+
const examples = chars.slice(0, 5).join(" ");
|
|
37294
|
+
emit({
|
|
37295
|
+
code: "PNG_FONT_COVERAGE",
|
|
37296
|
+
script,
|
|
37297
|
+
chars,
|
|
37298
|
+
message: `no bundled font covers ${chars.length} ${script} character${chars.length === 1 ? "" : "s"} (${examples}); they will draw as empty tofu boxes. Point fontDirs (CLI: --font-dirs <dir>) at a font that covers them, or opt in to system fonts with loadSystemFonts: true (CLI: --system-fonts).`
|
|
37299
|
+
});
|
|
37300
|
+
}
|
|
37301
|
+
}
|
|
36821
37302
|
const resvgOpts = {
|
|
36822
37303
|
background: opts.background ?? "white",
|
|
36823
37304
|
fitTo: opts.fitTo?.width ? { mode: "width", value: opts.fitTo.width } : opts.fitTo?.height ? { mode: "height", value: opts.fitTo.height } : { mode: "zoom", value: scale },
|
|
36824
37305
|
font: {
|
|
36825
|
-
loadSystemFonts
|
|
36826
|
-
// Bundled fonts (DejaVu Sans + the faces built-in styles
|
|
36827
|
-
// for cross-runtime determinism, plus caller-supplied
|
|
36828
|
-
// Falls back to resvg's built-in fonts if nothing is found.
|
|
36829
|
-
fontDirs
|
|
36830
|
-
|
|
37306
|
+
loadSystemFonts,
|
|
37307
|
+
// Bundled fonts (Inter + DejaVu Sans + the faces built-in styles
|
|
37308
|
+
// reference) for cross-runtime determinism, plus caller-supplied
|
|
37309
|
+
// directories. Falls back to resvg's built-in fonts if nothing is found.
|
|
37310
|
+
fontDirs,
|
|
37311
|
+
// Inter is the metrics font: src/text-metrics.ts is calibrated for it
|
|
37312
|
+
// and the SVG @import requests it, so rasterizing with anything else
|
|
37313
|
+
// (DejaVu is ~14% wider) pushes long labels outside their measured
|
|
37314
|
+
// boxes. resvg falls back per-glyph across every loaded font, so
|
|
37315
|
+
// DejaVu still covers glyphs Inter lacks (arrows, math, Armenian, …).
|
|
37316
|
+
defaultFontFamily: "Inter"
|
|
36831
37317
|
}
|
|
36832
37318
|
};
|
|
36833
37319
|
const resvg = new Resvg(svg2, resvgOpts);
|